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}