code wiki / (root) / nx_rrf.nx

nx_rrf.nx source

↩ module page · 48 lines · 1816 B

1// nx_rrf.nx -- Reciprocal Rank Fusion (bits-up, Q16.16). 2// 3// module: nishi-core.search.rrf 4// depends: fx.nx, syscalls.nx 5// capability: CORE_COMPUTE 6// wired_status: FULLY_WIRED 7// 8// genealogy_id: cormack_clarke_buettcher_2009_rrf 9// 10// WHY: the leaderboard needs to combine multiple ranking signals -- the source- 11// tier PRIOR and the BM25 content score (nx_bm25), later novelty (nx_simhash) 12// and provenance -- without tuning weights or breaking determinism. RRF fuses 13// rankings by rank position alone: score(item) = sum over rankers of 14// 1/(k + rank). It is parameter-free (k=60), monotone, and integer-exact in 15// Q16.16. Crucially it neutralizes SEO: an item must rank well in BOTH the tier 16// and the content ranker to top the fused list, so thin junk cannot ride domain 17// authority and a unique low-tier page with strong content relevance still rises. 18 19import "fx.nx" 20import "syscalls.nx" 21 22const NX_RRF_K: i64 = 60 // Cormack-Clarke-Buettcher SIGIR 2009 default 23 24// Accumulate one ranker's RRF contributions into scores_out (Q16.16). order[r] 25// is the item-id at 1-based rank r+1; its contribution is 1/(k + rank). The 26// caller zeroes scores_out, then calls this once per ranker (tier, BM25, ...). 27func nx_rrf_add(order: *i64, m: i64, scores_out: *i64) -> i64 { 28 var r: i64 = 0 29 while r < m { 30 let id: i64 = order[r] 31 scores_out[id] = scores_out[id] + fx_from_frac(1, NX_RRF_K + r + 1) 32 r = r + 1 33 } 34 return 0 35} 36 37// Argmax over fused scores: item-id with the highest RRF score (ties -> lowest 38// id). Returns -1 for an empty set. 39func nx_rrf_argmax(scores: *i64, n: i64) -> i64 { 40 if n <= 0 { return 0 - 1 } 41 var best: i64 = 0 42 var i: i64 = 1 43 while i < n { 44 if scores[i] > scores[best] { best = i } 45 i = i + 1 46 } 47 return best 48}