code wiki / _hdl_build / nx_food_recipe_mine.nx
nx_food_recipe_mine.nx source
↩ module page · 126 lines · 4960 B
1// nx_food_recipe_mine.nx -- LIB: GROUND the engine's pairings in real recipes (R-RECIPE). Mines the
2// license-clean fetched corpora (Wikibooks/Wikisource/Wikipedia recipe + the Nature food-pairing paper) for
3// INGREDIENT CO-OCCURRENCE -- the food-pairing-hypothesis signal: ingredients that appear together in real
4// recipe/research text pair well. Method: split a corpus into fixed passages; within a passage, mark which
5// KNOWN ingredients (matched by lowercased display name, word-boundary-anchored) are present; every co-present
6// pair scores +1. FACTS-ONLY (only known ingredient tokens are ever recorded, never prose) and LICENSE-GATED
7// (callers pass only fd_source_admissible corpora). Promotes the counts into food:pairlearned:<a>:<b> records,
8// provenance-distinct from the v1 seed (food:pair:*) so learned-vs-seeded stays auditable. license_tier: ORIGINAL
9import "nx_food_science.nx"
10import "nx_food_provenance.nx"
11import "nx_seg_store.nx"
12import "nx_syscalls.nx"
13
14// lowercase ASCII A-Z in place
15func fm_lower(buf: *u8, n: i64) -> i64 {
16 var i: i64 = 0
17 while i < n {
18 let c: i64 = buf[i] as i64
19 if c >= 65 { if c <= 90 { buf[i] = (c + 32) as u8 } }
20 i = i + 1
21 }
22 return 0
23}
24
25// 1 if lowercased term occurs in c[ws..we) anchored at a word boundary (preceding char not a-z).
26// A trailing letter IS allowed (so "onion" matches "onions", "oyster" matches "oysters").
27func fm_passage_has(c: *u8, ws: i64, we: i64, term: *u8, tl: i64) -> i64 {
28 if tl == 0 { return 0 }
29 var i: i64 = ws
30 while i + tl <= we {
31 var bok: i64 = 1
32 if i > 0 { let pc: i64 = c[i - 1] as i64; if pc >= 97 { if pc <= 122 { bok = 0 } } }
33 if bok == 1 {
34 var k: i64 = 0
35 var m: i64 = 1
36 while k < tl { if c[i + k] != term[k] { m = 0; k = tl } else { k = k + 1 } }
37 if m == 1 { return 1 }
38 }
39 i = i + 1
40 }
41 return 0
42}
43
44// mine one (already-lowercased) corpus into cooc[n*n] (symmetric). termlc[i]/termlen[i] = lowercased name
45// table; `pres` is a caller-owned scratch array of >= n slots (hoisted to avoid per-passage allocation).
46// Returns the number of (passage, ingredient) presence hits observed.
47func fm_mine(world: *i64, c: *u8, clen: i64, window: i64, termlc: *i64, termlen: *i64, cooc: *i64, pres: *i64) -> i64 {
48 let n: i64 = world[0]
49 var hits: i64 = 0
50 var ws: i64 = 0
51 while ws < clen {
52 var we: i64 = ws + window
53 if we > clen { we = clen }
54 var i: i64 = 0
55 while i < n {
56 if fm_passage_has(c, ws, we, termlc[i] as *u8, termlen[i]) == 1 { pres[i] = 1; hits = hits + 1 } else { pres[i] = 0 }
57 i = i + 1
58 }
59 i = 0
60 while i < n {
61 if pres[i] == 1 {
62 var j: i64 = i + 1
63 while j < n {
64 if pres[j] == 1 { cooc[i * n + j] = cooc[i * n + j] + 1; cooc[j * n + i] = cooc[j * n + i] + 1 }
65 j = j + 1
66 }
67 }
68 i = i + 1
69 }
70 ws = ws + window
71 }
72 return hits
73}
74
75func fm_cooc(cooc: *i64, n: i64, i: i64, j: i64) -> i64 { return cooc[i * n + j] }
76
77// build the lowercased match-term table from the world's display names (termlc[i]=*u8, termlen[i]=len)
78func fm_build_terms(world: *i64, termlc: *i64, termlen: *i64) -> i64 {
79 let n: i64 = world[0]
80 let names: *i64 = world[4] as *i64
81 var i: i64 = 0
82 while i < n {
83 let src: *u8 = names[i] as *u8
84 let l: i64 = as_len(src)
85 let cp: *u8 = sys_mmap(l + 4)
86 var k: i64 = 0
87 while k <= l { cp[k] = src[k]; k = k + 1 }
88 fm_lower(cp, l)
89 termlc[i] = cp as i64
90 termlen[i] = l
91 i = i + 1
92 }
93 return 0
94}
95
96// PROMOTE cooc>0 pairs into food:pairlearned:<idA>:<idB> = "<count>" (additive, idempotent skip-if-unchanged).
97// Returns the number of learned-pair records written this pass.
98func fm_promote(prefix: *u8, world: *i64, cooc: *i64) -> i64 {
99 let n: i64 = world[0]
100 let ids: *i64 = world[1] as *i64
101 let w: *i64 = ss_begin()
102 var cnt: i64 = 0
103 var i: i64 = 0
104 while i < n {
105 var j: i64 = i + 1
106 while j < n {
107 let cv: i64 = cooc[i * n + j]
108 if cv > 0 {
109 let key: *u8 = sys_mmap(96)
110 var o: i64 = 0
111 o = as_append(key, o, "food:pairlearned:" as *u8)
112 o = as_append(key, o, ids[i] as *u8)
113 key[o] = 58 as u8; o = o + 1
114 o = as_append(key, o, ids[j] as *u8)
115 key[o] = 0 as u8
116 let val: *u8 = sys_mmap(24)
117 let vl: i64 = fd_apnum(val, 0, cv)
118 if fd_streq_store(prefix, key, val, vl) == 0 { ss_add(w, 1, key, val, vl); cnt = cnt + 1 }
119 }
120 j = j + 1
121 }
122 i = i + 1
123 }
124 if cnt > 0 { let seg: i64 = fd_seg_next(prefix); ss_commit(prefix, w, seg) }
125 return cnt
126}