Something went wrong. Try again.
semantic bufo search find-bufo.com
bufo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127//! weighted reciprocal rank fusion — ported from the retired rust backend.//!//! score(doc) = α/(k + rank_semantic) + (1-α)/(k + rank_keyword), k = 60,//! 1-based ranks; a doc missing from a list contributes nothing for that leg.//! ties go to the keyword leg, zero-score docs are dropped, and the top result//! is normalized to 1.0 for display.
const std = @import("std");const Allocator = std.mem.Allocator;
pub const RRF_K: f32 = 60.0;
pub const Fused = struct { id: []const u8, score: f32,};
const Entry = struct { id: []const u8, score: f32, kw_rank: usize,};
fn better(_: void, a: Entry, b: Entry) bool { if (a.score != b.score) return a.score > b.score; if (a.kw_rank != b.kw_rank) return a.kw_rank < b.kw_rank; return std.mem.lessThan(u8, a.id, b.id);}
/// `semantic` and `keyword` are id lists in rank order (best first). ids are/// borrowed, not copied — the caller keeps them alive.pub fn fuseRanks( alloc: Allocator, semantic: []const []const u8, keyword: []const []const u8, alpha_in: f32,) ![]Fused { const alpha = std.math.clamp(alpha_in, 0.0, 1.0);
var by_id: std.StringHashMapUnmanaged(Entry) = .empty; defer by_id.deinit(alloc);
for (semantic, 0..) |id, rank| { const gop = try by_id.getOrPut(alloc, id); if (!gop.found_existing) gop.value_ptr.* = .{ .id = id, .score = 0, .kw_rank = std.math.maxInt(usize) }; gop.value_ptr.score += alpha / (RRF_K + @as(f32, @floatFromInt(rank)) + 1.0); } for (keyword, 0..) |id, rank| { const gop = try by_id.getOrPut(alloc, id); if (!gop.found_existing) gop.value_ptr.* = .{ .id = id, .score = 0, .kw_rank = std.math.maxInt(usize) }; gop.value_ptr.score += (1.0 - alpha) / (RRF_K + @as(f32, @floatFromInt(rank)) + 1.0); gop.value_ptr.kw_rank = rank; }
var entries: std.ArrayList(Entry) = .empty; defer entries.deinit(alloc); var it = by_id.valueIterator(); while (it.next()) |e| { if (e.score > 0) try entries.append(alloc, e.*); } std.mem.sort(Entry, entries.items, {}, better);
const out = try alloc.alloc(Fused, entries.items.len); const max = if (entries.items.len > 0) entries.items[0].score else 1.0; for (entries.items, 0..) |e, i| { out[i] = .{ .id = e.id, .score = e.score / max }; } return out;}
// --- tests: the rust regression suite, ported ---
const t = std.testing;
fn ids(comptime names: []const []const u8) []const []const u8 { return names;}
test "keyword-only rank-1 hit wins the tie at default alpha" { const semantic = ids(&.{ "noise-1", "noise-2", "noise-3", "noise-4", "noise-5" }); const keyword = ids(&.{"bufo-lgtm"}); const fused = try fuseRanks(t.allocator, semantic, keyword, 0.5); defer t.allocator.free(fused); try t.expectEqualStrings("bufo-lgtm", fused[0].id);}
test "agreement beats a single list" { const fused = try fuseRanks(t.allocator, ids(&.{ "sem-only", "both" }), ids(&.{ "kw-only", "both" }), 0.5); defer t.allocator.free(fused); try t.expectEqualStrings("both", fused[0].id);}
test "pure keyword ignores semantic" { const fused = try fuseRanks(t.allocator, ids(&.{ "sem-1", "sem-2" }), ids(&.{ "kw-1", "kw-2" }), 0.0); defer t.allocator.free(fused); try t.expectEqual(@as(usize, 2), fused.len); try t.expectEqualStrings("kw-1", fused[0].id); try t.expectEqualStrings("kw-2", fused[1].id);}
test "pure semantic ignores keyword" { const fused = try fuseRanks(t.allocator, ids(&.{ "sem-1", "sem-2" }), ids(&.{"kw-1"}), 1.0); defer t.allocator.free(fused); try t.expectEqual(@as(usize, 2), fused.len); try t.expectEqualStrings("sem-1", fused[0].id);}
test "top score normalized to one" { const fused = try fuseRanks(t.allocator, ids(&.{ "a", "b" }), ids(&.{"a"}), 0.5); defer t.allocator.free(fused); try t.expectApproxEqAbs(@as(f32, 1.0), fused[0].score, 1e-6); try t.expect(fused[1].score < 1.0);}
test "zero-weight leg contributes no results" { const fused = try fuseRanks(t.allocator, ids(&.{ "sem-1", "sem-2" }), &.{}, 0.0); defer t.allocator.free(fused); try t.expectEqual(@as(usize, 0), fused.len);}
test "alpha is clamped" { const fused = try fuseRanks(t.allocator, ids(&.{"s"}), ids(&.{"k"}), 7.0); defer t.allocator.free(fused); try t.expectEqualStrings("s", fused[0].id); try t.expectEqual(@as(usize, 1), fused.len);}