use ulid::Ulid;
pub const RRF_K: f32 = 60.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Fused {
pub record_id: Ulid,
pub score: f32,
}
pub fn fuse(vector: &[Ulid], text: &[Ulid], limit: usize) -> Vec<Fused> {
fuse_lists(&[vector, text], limit)
}
pub fn fuse_lists(lists: &[&[Ulid]], limit: usize) -> Vec<Fused> {
assert!(
lists.len() <= 8,
"fuse_lists: the `counted` bitmask is a u8, at most 8 lists"
);
struct Entry {
id: Ulid,
score: f32,
tiebreak: (u8, u32),
counted: u8,
}
let mut acc: Vec<Entry> = Vec::new();
let mut add = |list: u8, ranked: &[Ulid]| {
let bit = 1u8 << list;
for (rank, &id) in ranked.iter().enumerate() {
let contribution = 1.0 / (RRF_K + rank as f32 + 1.0);
match acc.iter_mut().find(|e| e.id == id) {
Some(e) => {
if e.counted & bit == 0 {
e.score += contribution;
e.counted |= bit;
}
}
None => acc.push(Entry {
id,
score: contribution,
tiebreak: (list, rank as u32),
counted: bit,
}),
}
}
};
for (i, ranked) in lists.iter().enumerate() {
add(i as u8, ranked);
}
acc.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.tiebreak.cmp(&b.tiebreak))
});
acc.truncate(limit);
acc.into_iter()
.map(|e| Fused {
record_id: e.id,
score: e.score,
})
.collect()
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
fn ids(n: usize) -> Vec<Ulid> {
(0..n).map(|i| Ulid::from(i as u128 + 1)).collect()
}
#[test]
fn rank_one_in_both_beats_rank_one_in_one() {
let all = ids(3);
let (a, b, c) = (all[0], all[1], all[2]);
let fused = fuse(&[a, c], &[a, b], 10);
assert_eq!(fused[0].record_id, a, "top of both lists must win");
let one = 1.0 / (RRF_K + 1.0);
assert!((fused[0].score - 2.0 * one).abs() < 1e-6);
}
#[test]
fn union_keeps_singletons_from_each_list() {
let all = ids(3);
let (v_only, t_only, _) = (all[0], all[1], all[2]);
let fused = fuse(&[v_only], &[t_only], 10);
let out: Vec<Ulid> = fused.iter().map(|f| f.record_id).collect();
assert!(out.contains(&v_only), "vector-only hit survives");
assert!(out.contains(&t_only), "text-only hit survives");
assert_eq!(fused[0].record_id, v_only);
}
#[test]
fn empty_text_list_is_vector_order() {
let all = ids(4);
let fused = fuse(&all, &[], 10);
let out: Vec<Ulid> = fused.iter().map(|f| f.record_id).collect();
assert_eq!(out, all, "no text list ⇒ vector order preserved");
}
#[test]
fn empty_vector_list_is_text_order() {
let all = ids(4);
let fused = fuse(&[], &all, 10);
let out: Vec<Ulid> = fused.iter().map(|f| f.record_id).collect();
assert_eq!(out, all);
}
#[test]
fn both_empty_yields_empty() {
assert!(fuse(&[], &[], 10).is_empty());
}
#[test]
fn limit_caps_the_union() {
let v = ids(10);
let mut t = ids(20);
t.reverse();
let fused = fuse(&v, &t, 5);
assert_eq!(fused.len(), 5);
}
#[test]
fn duplicate_id_in_a_list_counts_its_best_rank_once() {
let all = ids(2);
let (a, b) = (all[0], all[1]);
let fused = fuse(&[a, a, b], &[], 10);
assert_eq!(fused.len(), 2, "no phantom duplicate id");
let one = 1.0 / (RRF_K + 1.0);
assert!((fused[0].score - one).abs() < 1e-6);
assert_eq!(fused[0].record_id, a);
}
#[test]
fn duplicate_within_a_list_does_not_inflate_cross_list_overlap() {
let all = ids(2);
let (a, b) = (all[0], all[1]);
let fused = fuse(&[a, a, b], &[a], 10);
let a_hit = fused.iter().find(|f| f.record_id == a).unwrap();
let one = 1.0 / (RRF_K + 1.0);
assert!(
(a_hit.score - 2.0 * one).abs() < 1e-6,
"one rank-0 hit per list ⇒ 2×, never 3× for the intra-list repeat"
);
}
proptest::proptest! {
#[test]
fn fuse_holds_its_invariants_for_any_input(
v_seeds in proptest::collection::vec(1u128..40, 0..12),
t_seeds in proptest::collection::vec(1u128..40, 0..12),
limit in 0usize..24,
) {
let vector: Vec<Ulid> = v_seeds.iter().map(|&s| Ulid::from(s)).collect();
let text: Vec<Ulid> = t_seeds.iter().map(|&s| Ulid::from(s)).collect();
let fused = fuse(&vector, &text, limit);
proptest::prop_assert_eq!(&fused, &fuse(&vector, &text, limit));
proptest::prop_assert!(fused.len() <= limit);
for w in fused.windows(2) {
proptest::prop_assert!(w[0].score >= w[1].score);
}
let distinct: std::collections::BTreeSet<Ulid> =
vector.iter().chain(text.iter()).copied().collect();
let out: std::collections::BTreeSet<Ulid> =
fused.iter().map(|f| f.record_id).collect();
proptest::prop_assert_eq!(out.len(), fused.len(), "no id repeats");
proptest::prop_assert!(out.is_subset(&distinct), "no invented ids");
if limit >= distinct.len() {
proptest::prop_assert_eq!(&out, &distinct, "union, never intersection");
}
for f in &fused {
proptest::prop_assert!(f.score > 0.0);
}
}
}
#[test]
fn output_is_sorted_descending_and_deterministic() {
let all = ids(6);
let v = vec![all[0], all[1], all[2], all[3]];
let t = vec![all[3], all[2], all[4], all[5]];
let a = fuse(&v, &t, 10);
let b = fuse(&v, &t, 10);
assert_eq!(a, b, "same inputs ⇒ same output");
for w in a.windows(2) {
assert!(w[0].score >= w[1].score, "scores must be non-increasing");
}
}
mod recency {
use super::*;
#[test]
fn two_list_fuse_matches_fuse_lists_with_two_lists() {
let all = ids(4);
let v = vec![all[0], all[1], all[2]];
let t = vec![all[2], all[3]];
assert_eq!(fuse(&v, &t, 10), fuse_lists(&[&v, &t], 10));
}
#[test]
fn fact_and_correction_the_newer_one_wins_the_tie() {
let all = ids(2);
let (fact, correction) = (all[0], all[1]);
let vector = vec![fact, correction];
let text = vec![correction, fact];
let recency = vec![correction, fact];
let fused = fuse_lists(&[&vector, &text, &recency], 10);
assert_eq!(
fused[0].record_id, correction,
"tied content match ⇒ the newer memory (correction) wins"
);
assert_eq!(fused[1].record_id, fact);
}
#[test]
fn old_strong_match_beats_new_weak_match() {
let all = ids(2);
let (old, new) = (all[0], all[1]);
let vector = vec![old];
let text = vec![old];
let recency = vec![new, old]; let fused = fuse_lists(&[&vector, &text, &recency], 10);
assert_eq!(
fused[0].record_id, old,
"two content lists (2 contributions) must beat recency alone (1 contribution)"
);
}
#[test]
fn recency_alone_cannot_invert_a_two_list_content_match() {
let one = 1.0 / (RRF_K + 1.0);
let two_list_min = one + 1.0 / (RRF_K + 8.0); assert!(
two_list_min > one,
"two-list floor must exceed any single list's ceiling"
);
}
#[test]
fn recency_never_introduces_an_id_outside_the_content_union() {
let all = ids(5);
let vector = vec![all[0], all[1], all[2]];
let text = vec![all[2], all[3]];
let mut recency = vec![all[0], all[1], all[2], all[3]];
recency.reverse();
let fused = fuse_lists(&[&vector, &text, &recency], 10);
let out: std::collections::BTreeSet<Ulid> = fused.iter().map(|f| f.record_id).collect();
let union: std::collections::BTreeSet<Ulid> =
vector.iter().chain(text.iter()).copied().collect();
assert_eq!(out, union, "recency reorders the union, never extends it");
assert!(
!out.contains(&all[4]),
"id absent from both content lists stays absent"
);
}
proptest::proptest! {
#[test]
fn fuse_lists_holds_its_invariants_for_any_number_of_lists(
seeds in proptest::collection::vec(
proptest::collection::vec(1u128..40, 0..12),
0..5,
),
limit in 0usize..24,
) {
let lists: Vec<Vec<Ulid>> = seeds
.iter()
.map(|s| s.iter().map(|&x| Ulid::from(x)).collect())
.collect();
let refs: Vec<&[Ulid]> = lists.iter().map(Vec::as_slice).collect();
let fused = fuse_lists(&refs, limit);
proptest::prop_assert_eq!(&fused, &fuse_lists(&refs, limit));
proptest::prop_assert!(fused.len() <= limit);
for w in fused.windows(2) {
proptest::prop_assert!(w[0].score >= w[1].score);
}
let distinct: std::collections::BTreeSet<Ulid> =
lists.iter().flatten().copied().collect();
let out: std::collections::BTreeSet<Ulid> =
fused.iter().map(|f| f.record_id).collect();
proptest::prop_assert_eq!(out.len(), fused.len(), "no id repeats");
proptest::prop_assert!(out.is_subset(&distinct), "no invented ids");
if limit >= distinct.len() {
proptest::prop_assert_eq!(&out, &distinct, "union, never intersection");
}
for f in &fused {
proptest::prop_assert!(f.score > 0.0);
}
}
#[test]
fn a_recency_only_hit_never_outranks_a_two_content_list_hit(
strong_v_rank in 0usize..20,
strong_t_rank in 0usize..20,
recency_only_rank in 0usize..20,
) {
let ids2 = ids(2);
let (strong, recency_only) = (ids2[0], ids2[1]);
let mut vector = vec![Ulid::from(1000u128); strong_v_rank];
vector.push(strong);
let mut text = vec![Ulid::from(2000u128); strong_t_rank];
text.push(strong);
let mut recency = vec![Ulid::from(3000u128); recency_only_rank];
recency.push(recency_only);
let fused = fuse_lists(&[&vector, &text, &recency], 100);
let strong_score = fused.iter().find(|f| f.record_id == strong).unwrap().score;
let recency_only_score = fused
.iter()
.find(|f| f.record_id == recency_only)
.map(|f| f.score)
.unwrap_or(0.0);
proptest::prop_assert!(strong_score > recency_only_score);
}
}
}
}