nx_wordclust_gate.nx source
↩ module page · 118 lines · 7569 B
1// nx_wordclust_gate.nx -- GATE for the PPMI word clusterer (nx_wordclust), driven IN-PROCESS on a PLANTED NXPPMI1 model
2// under /tmp/nx_wordclust_gate (six words, three orthogonal context vectors, written by the library's own fixture
3// writer through the ONE loader's format). Proves: the fixture is read back by the canonical loader, words the model
4// knows resolve and one it does not reads -1, k-medoids at K=3 groups the three synonym pairs and separates the three
5// pairs, a word unseen at build time is assigned on demand to its pair's cluster, ids spell as two bytes and -1 as one,
6// two builds agree, and a missing model fails closed (no clusters, never a crash). Every fixture asserts its own
7// condition first. No network. license_tier: ORIGINAL No hw writes (Rule 26).
8import "nx_syscalls.nx"
9import "nx_gatekit_lib.nx"
10import "nx_reviewmine_lib.nx"
11import "nx_wordclust.nx"
12import "nx_gate_verdict.nx"
13
14const G_ROOT: *u8 = "/tmp/nx_wordclust_gate"
15const G_MODEL: *u8 = "/tmp/nx_wordclust_gate/mini.ppmi"
16const G_MISSING: *u8 = "/tmp/nx_wordclust_gate/absent.ppmi"
17const G_I64: i64 = 8
18const G_NW: i64 = 6
19const G_MODEL_BYTES: i64 = 280 // 32 + 6*8 + 7*8 + 6*8 + 6*8 + 6*8
20const G_K: i64 = 3
21const G_BUILD_WORDS: i64 = 5 // food is held back and assigned on demand
22
23func main() -> i64 {
24 gv_head("=== nx_wordclust_gate -- PPMI word clusters group synonyms and separate the rest on a planted model ===" as *u8)
25 let c: *i64 = gv_ctr()
26 gk_mkdir(G_ROOT)
27 // the planted vocabulary: battery~charger (context 0), screen~display (context 1), waiter~food (context 2)
28 let wb: *u8 = sys_mmap(128)
29 let wo: *i64 = sys_mmap(G_NW * G_I64) as *i64
30 let wl: *i64 = sys_mmap(G_NW * G_I64) as *i64
31 let wx: *i64 = sys_mmap(G_NW * G_I64) as *i64
32 var w: i64 = 0
33 wo[0] = w; wl[0] = 7; wx[0] = 0; w = rm_catn(wb, w, "battery" as *u8, 7)
34 wo[1] = w; wl[1] = 7; wx[1] = 0; w = rm_catn(wb, w, "charger" as *u8, 7)
35 wo[2] = w; wl[2] = 6; wx[2] = 1; w = rm_catn(wb, w, "screen" as *u8, 6)
36 wo[3] = w; wl[3] = 7; wx[3] = 1; w = rm_catn(wb, w, "display" as *u8, 7)
37 wo[4] = w; wl[4] = 6; wx[4] = 2; w = rm_catn(wb, w, "waiter" as *u8, 6)
38 wo[5] = w; wl[5] = 4; wx[5] = 2; w = rm_catn(wb, w, "food" as *u8, 4)
39 let planted: i64 = wc_plant_model(G_MODEL, wb, wo, wl, wx, G_NW)
40 gv_check_eq("fixture-reached-the-condition: the planted model was written" as *u8, planted, 1, c)
41 gv_check_eq("fixture-reached-the-condition: the planted model has the format's exact byte count" as *u8, gk_size(G_MODEL), G_MODEL_BYTES, c)
42
43 wc_reset()
44 gv_check_eq("the-canonical-loader-reads-the-planted-model" as *u8, wc_load_model(G_MODEL), 1, c)
45 wc_set_k(G_K, G_K, G_K)
46 // frequencies seed the medoids: one word per pair leads, the pairs' partners trail, an unknown word is added too
47 var i: i64 = 0
48 while i < 5 { wc_add("battery" as *u8, 7); i = i + 1 }
49 i = 0
50 while i < 4 { wc_add("screen" as *u8, 6); i = i + 1 }
51 i = 0
52 while i < 3 { wc_add("waiter" as *u8, 6); i = i + 1 }
53 wc_add("charger" as *u8, 7)
54 wc_add("display" as *u8, 7)
55 wc_add("zzzq" as *u8, 4)
56 let o: *i64 = sys_mmap(WC_O_N * G_I64) as *i64
57 let built: i64 = wc_build(o)
58 gv_check_eq("build-returns-one" as *u8, built, 1, c)
59 gv_check_eq("vocabulary-added-six (five in the model plus one the model does not know)" as *u8, o[WC_O_VOCAB], G_BUILD_WORDS + 1, c)
60 gv_check_eq("words-the-model-knows-five" as *u8, o[WC_O_INMODEL], G_BUILD_WORDS, c)
61 gv_check_eq("medoids-used-equal-K-at-level-0" as *u8, o[WC_O_K0], G_K, c)
62 gv_check_eq("medoids-used-equal-K-at-level-2" as *u8, o[WC_O_K2], G_K, c)
63 let cb: i64 = wc_cluster("battery" as *u8, 7, 0)
64 let cc: i64 = wc_cluster("charger" as *u8, 7, 0)
65 let cs: i64 = wc_cluster("screen" as *u8, 6, 0)
66 let cd: i64 = wc_cluster("display" as *u8, 7, 0)
67 let cw: i64 = wc_cluster("waiter" as *u8, 6, 0)
68 gv_check("every-known-word-has-a-cluster (ids 0..K-1)" as *u8, ((cb >= 0) & (cc >= 0) & (cs >= 0) & (cd >= 0) & (cw >= 0) & (cb < G_K) & (cs < G_K) & (cw < G_K)) as i64, c)
69 gv_check_eq("synonyms-share-a-cluster: battery and charger" as *u8, cc, cb, c)
70 gv_check_eq("synonyms-share-a-cluster: screen and display" as *u8, cd, cs, c)
71 gv_check("different-pairs-are-separated: battery, screen and waiter in three clusters" as *u8, ((cb != cs) & (cs != cw) & (cb != cw)) as i64, c)
72 gv_check_eq("neg-control-a-word-the-model-does-not-know-reads-minus-one" as *u8, wc_cluster("zzzq" as *u8, 4, 0), WC_NONE, c)
73 // on-demand assignment: food was never added at build time, its vector matches waiter's context
74 let cf: i64 = wc_cluster("food" as *u8, 4, 0)
75 gv_check_eq("unseen-word-assigned-on-demand-to-its-pair's-cluster (food with waiter)" as *u8, cf, cw, c)
76 gv_check_eq("on-demand-assignment-is-cached (a second lookup answers the same id)" as *u8, wc_cluster("food" as *u8, 4, 0), cf, c)
77 gv_check_eq("case-folds-at-lookup (Battery reads battery)" as *u8, wc_cluster("Battery" as *u8, 7, 0), cb, c)
78 // spelling
79 let sb: *u8 = sys_mmap(4)
80 gv_check_eq("spell-a-cluster-id-as-two-bytes" as *u8, wc_spell(65, sb), 2, c)
81 gv_check_eq("spell-two-bytes-high-digit (65 / 64 = 1)" as *u8, sb[0] as i64, 49, c)
82 gv_check_eq("spell-two-bytes-low-digit (65 mod 64 = 1)" as *u8, sb[1] as i64, 49, c)
83 gv_check_eq("spell-minus-one-as-one-byte" as *u8, wc_spell(WC_NONE, sb), 1, c)
84 // determinism: a second reset+build gives the same ids
85 wc_reset()
86 wc_load_model(G_MODEL)
87 wc_set_k(G_K, G_K, G_K)
88 i = 0
89 while i < 5 { wc_add("battery" as *u8, 7); i = i + 1 }
90 i = 0
91 while i < 4 { wc_add("screen" as *u8, 6); i = i + 1 }
92 i = 0
93 while i < 3 { wc_add("waiter" as *u8, 6); i = i + 1 }
94 wc_add("charger" as *u8, 7)
95 wc_add("display" as *u8, 7)
96 wc_add("zzzq" as *u8, 4)
97 let o2: *i64 = sys_mmap(WC_O_N * G_I64) as *i64
98 wc_build(o2)
99 gv_check_eq("clustering-is-deterministic (battery's id agrees across two builds)" as *u8, wc_cluster("battery" as *u8, 7, 0), cb, c)
100 gv_check_eq("clustering-is-deterministic (screen's id agrees across two builds)" as *u8, wc_cluster("screen" as *u8, 6, 0), cs, c)
101 // neg-control: a missing model fails closed
102 wc_reset()
103 gv_check_eq("neg-control-missing-model-refuses-to-load" as *u8, wc_load_model(G_MISSING), 0, c)
104 wc_add("battery" as *u8, 7)
105 let o3: *i64 = sys_mmap(WC_O_N * G_I64) as *i64
106 gv_check_eq("neg-control-build-without-a-model-returns-zero" as *u8, wc_build(o3), 0, c)
107 gv_check_eq("neg-control-no-clusters-without-a-model (battery reads minus one)" as *u8, wc_cluster("battery" as *u8, 7, 0), WC_NONE, c)
108
109 gv_values_head()
110 gv_kv("vocab" as *u8, o[WC_O_VOCAB])
111 gv_kv("in_model" as *u8, o[WC_O_INMODEL])
112 gv_kv("k0" as *u8, o[WC_O_K0])
113 gv_kv("cluster_battery" as *u8, cb)
114 gv_kv("cluster_screen" as *u8, cs)
115 gv_kv("cluster_waiter" as *u8, cw)
116 gv_kv("cluster_food_on_demand" as *u8, cf)
117 return gv_verdict("nx_wordclust_gate" as *u8, c, "the PPMI word clusterer proven on a planted six-word model written in the canonical loader's format: the loader reads it, known words resolve and an unknown one reads -1, k-medoids at K=3 groups each synonym pair and separates the pairs, an unseen word is assigned on demand to its pair and cached, lookups case-fold, ids spell as two bytes and -1 as one, two builds agree, and a missing model fails closed with no clusters; every fixture asserts its own condition first" as *u8)
118}