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}