nx_recall_eval.nx source
↩ module page · 83 lines · 4415 B
1// nx_recall_eval.nx -- the recall RULER as a service (R0 on MCP/API). Parse a ranked
2// graded-relevance vector (argv gains-csv) and emit an honest IR scorecard JSON
3// (nDCG@k / MRR / Recall@k / P@k / AP) via nx_ir_eval. Any caller -- the compare C5
4// grader, a workstream, a session -- grades a ranking without re-implementing the
5// metrics; sovereign, integer-deterministic, same number on every machine.
6// nx_recall_eval <gains-csv> [total_relevant] [k]
7// e.g. nx_recall_eval 3,0,1,0,0 -> {..."ndcg_permil":964,"mrr_permil":1000,...}
8// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
9import "fx.nx"
10import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
11import "nx_ir_eval.nx"
12import "syscalls.nx"
13const K_MAGIC_32768: i64 = 32768
14const K_MAGIC_65536: i64 = 65536
15
16func re_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
17func re_q() -> i64 { let b: *u8 = sys_mmap(1); b[0] = 34 as u8; sys_write(1, b, 1); return 0 }
18func re_key(name: *u8) -> i64 { re_q(); re_puts(name); re_q(); re_puts(":" as *u8); return 0 }
19// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
20// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
21// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
22// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
23func re_num(v: i64) -> i64 { nxi_out(v); return 0 }
24// Q16.16 -> permil (x1000), round-to-nearest.
25func re_permil(fxv: i64) -> i64 { return (fxv * 1000 + K_MAGIC_32768) / K_MAGIC_65536 }
26// parse comma-separated non-negative ints; returns count written to out[0..maxn).
27func re_parse_csv(s: *u8, n: i64, out: *i64, maxn: i64) -> i64 {
28 var cnt: i64 = 0; var cur: i64 = 0; var have: i64 = 0; var i: i64 = 0
29 while i < n {
30 let c: i64 = s[i] as i64
31 if c >= 48 { if c <= 57 { cur = cur * 10 + (c - 48); have = 1 } }
32 if c == 44 {
33 if cnt < maxn { out[cnt] = cur; cnt = cnt + 1 }
34 cur = 0; have = 0
35 }
36 i = i + 1
37 }
38 if have == 1 { if cnt < maxn { out[cnt] = cur; cnt = cnt + 1 } }
39 return cnt
40}
41func re_arg_int(a: *u8, dflt: i64) -> i64 {
42 var l: i64 = 0; while a[l] != (0 as u8) { l = l + 1 }
43 let tb: *i64 = sys_mmap(8) as *i64
44 if re_parse_csv(a, l, tb, 1) > 0 { return tb[0] }
45 return dflt
46}
47
48func main(argc: i64, argv: *i64) -> i64 {
49 if argc < 2 { re_puts("usage: nx_recall_eval <gains-csv> [total_relevant] [k]\n" as *u8); sys_exit(2); return 2 }
50 let s: *u8 = argv[1] as *u8
51 var sl: i64 = 0; while s[sl] != (0 as u8) { sl = sl + 1 }
52 let gains: *i64 = sys_mmap(256 * 8) as *i64
53 let n: i64 = re_parse_csv(s, sl, gains, 256)
54 if n <= 0 { re_puts("{" as *u8); re_key("error" as *u8); re_q(); re_puts("empty gains" as *u8); re_q(); re_puts("}\n" as *u8); sys_exit(1); return 1 }
55
56 let rels: *i64 = sys_mmap(256 * 8) as *i64
57 var tr: i64 = 0; var i: i64 = 0
58 while i < n { if gains[i] > 0 { rels[i] = 1; tr = tr + 1 } else { rels[i] = 0 } i = i + 1 }
59 if argc > 2 { tr = re_arg_int(argv[2] as *u8, tr) }
60 var k: i64 = n
61 if argc > 3 { k = re_arg_int(argv[3] as *u8, n) }
62
63 let ndcg: i64 = ie_ndcg_at_k(gains, n, k)
64 let mrr: i64 = ie_mrr(rels, n)
65 let recall: i64 = ie_recall_at_k(rels, n, k, tr)
66 let prec: i64 = ie_precision_at_k(rels, n, k)
67 let ap: i64 = ie_ap(rels, n, tr)
68
69 re_puts("{" as *u8)
70 re_key("api" as *u8); re_q(); re_puts("nishi-recall-eval" as *u8); re_q(); re_puts("," as *u8)
71 re_key("v" as *u8); re_num(1); re_puts("," as *u8)
72 re_key("n" as *u8); re_num(n); re_puts("," as *u8)
73 re_key("k" as *u8); re_num(k); re_puts("," as *u8)
74 re_key("total_relevant" as *u8); re_num(tr); re_puts("," as *u8)
75 re_key("ndcg_permil" as *u8); re_num(re_permil(ndcg)); re_puts("," as *u8)
76 re_key("mrr_permil" as *u8); re_num(re_permil(mrr)); re_puts("," as *u8)
77 re_key("recall_permil" as *u8); re_num(re_permil(recall)); re_puts("," as *u8)
78 re_key("precision_permil" as *u8); re_num(re_permil(prec)); re_puts("," as *u8)
79 re_key("ap_permil" as *u8); re_num(re_permil(ap)); re_puts("," as *u8)
80 re_key("note" as *u8); re_q(); re_puts("Q16.16 integer-exact; permil=x1000; sovereign nx_ir_eval" as *u8); re_q()
81 re_puts("}\n" as *u8)
82 sys_exit(0); return 0
83}