code wiki / _hdl_build / nx_bm25_test.nx
nx_bm25_test.nx source
↩ module page · 55 lines · 3995 B
1// nx_bm25_test.nx -- prove the team's integer BM25 reproduces the PRODUCTION ranking and corrects the
2// old TF-IDF ranker's length bias. Corpus is engineered so length-normalization FLIPS the winner:
3// doc0 "injury injury" (tf=2, dl=2 -- short + focused)
4// doc1 "injury injury injury f f f f f f f f f" (tf=3, dl=12 -- more hits but diluted)
5// Naive tf*idf (no length norm) picks doc1 (higher raw tf). BM25 picks doc0 (focused), which matches a
6// float reference BM25 -- the triangulation the runner checks. Exit 0 if the team's logic holds 5/5;
7// the runner then confirms the team's BM25_BEST == the Python reference's. license_tier: ORIGINAL
8
9import "nx_bm25.nx"
10import "nx_research_extract.nx" // re_strlen
11import "nx_syscalls.nx"
12
13func bt_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
14func bt_num(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64=v; if m<0 {m=0-m; sys_write(1,"-" as *u8,1)}; let t: *u8 = sys_mmap(28); var k: i64=0; if m==0 {t[0]=48;k=1}; while m>0 {t[k]=48+(m%10); m=m/10; k=k+1}; var i: i64=0; while i<k {bb[i]=t[k-1-i]; i=i+1}; sys_write(1, bb, k); return 0 }
15
16func main() -> i64 {
17 bt_puts("=== BM25 (production ranker) -- integer, triangulated vs a float reference ===\n" as *u8)
18 let N: i64 = 4
19 let d0: *u8 = "injury injury" as *u8
20 let d1: *u8 = "injury injury injury f f f f f f f f f" as *u8
21 let d2: *u8 = "settlement settlement filler" as *u8
22 let d3: *u8 = "court court court" as *u8
23 let ptrs: *i64 = sys_mmap(8 * 8) as *i64
24 let lens: *i64 = sys_mmap(8 * 8) as *i64
25 let dls: *i64 = sys_mmap(8 * 8) as *i64
26 ptrs[0]=d0 as i64; ptrs[1]=d1 as i64; ptrs[2]=d2 as i64; ptrs[3]=d3 as i64
27 var total: i64 = 0; var i: i64 = 0
28 while i < N { lens[i] = re_strlen(ptrs[i] as *u8); dls[i] = bm_token_count(ptrs[i] as *u8, lens[i]); total = total + dls[i]; i = i + 1 }
29 let avgdl: i64 = total / N
30
31 let q: *i64 = sys_mmap(4 * 8) as *i64; q[0] = "injury" as *u8 as i64
32 let bm_b: i64 = bm_best(ptrs, lens, dls, N, avgdl, q, 1)
33 let nv_b: i64 = bm_naive_best(ptrs, lens, N, q, 1)
34 let s0: i64 = bm_score(ptrs, lens, dls, N, 0, avgdl, q, 1)
35 let s1: i64 = bm_score(ptrs, lens, dls, N, 1, avgdl, q, 1)
36 let n0: i64 = bm_naive_tfidf(ptrs, lens, N, 0, q, 1)
37 let n1: i64 = bm_naive_tfidf(ptrs, lens, N, 1, q, 1)
38
39 bt_puts(" dls=[" as *u8); i=0; while i<N { bt_num(dls[i]); if i<N-1 { bt_puts("," as *u8) } i=i+1 } bt_puts("] avgdl=" as *u8); bt_num(avgdl); bt_puts("\n" as *u8)
40 bt_puts(" BM25 : doc0=" as *u8); bt_num(s0); bt_puts(" doc1=" as *u8); bt_num(s1); bt_puts(" -> best=doc" as *u8); bt_num(bm_b); bt_puts(" (short+focused, length-normalized)\n" as *u8)
41 bt_puts(" naive : doc0=" as *u8); bt_num(n0); bt_puts(" doc1=" as *u8); bt_num(n1); bt_puts(" -> best=doc" as *u8); bt_num(nv_b); bt_puts(" (raw tf, NO length norm -- the OLD bias)\n" as *u8)
42 bt_puts("BM25_BEST " as *u8); bt_num(bm_b); bt_puts("\n" as *u8) // <- the runner triangulates this vs Python
43
44 let r: *i64 = sys_mmap(8 * 8) as *i64
45 r[0] = 0; if avgdl == 5 { r[0] = 1 } // corpus shape as designed
46 r[1] = 0; if bm_b == 0 { r[1] = 1 } // BM25 picks the focused short doc
47 r[2] = 0; if nv_b == 1 { r[2] = 1 } // naive tf picks the diluted long doc (the flip)
48 r[3] = 0; if s0 > s1 { r[3] = 1 } // BM25 ranks doc0 over doc1
49 r[4] = 0; if n1 > n0 { r[4] = 1 } // naive ranks doc1 over doc0 (opposite)
50 var pass: i64 = 0; i = 0
51 while i < 5 { pass = pass + r[i]; i = i + 1 }
52 bt_puts("----\n passed " as *u8); bt_num(pass); bt_puts("/5\n" as *u8)
53 if pass == 5 { bt_puts(" LENGTH-NORM PROVEN: BM25 corrects the old ranker's bias toward long diluted docs; runner now checks BM25_BEST == float reference.\n" as *u8); sys_exit(0); return 0 }
54 bt_puts(" FAIL\n" as *u8); sys_exit(1); return 1
55}