use ordvec::rank::rank_transform;
use ordvec::Bitmap;
#[cfg(feature = "experimental")]
use ordvec::MultiBucketBitmap;
fn ref_doc_bitmap(doc: &[f32], dim: usize, n_top: usize) -> Vec<u64> {
let qpv = dim / 64;
let cutoff = (dim - n_top) as u16;
let ranks = rank_transform(doc);
let mut bm = vec![0u64; qpv];
for j in 0..dim {
if ranks[j] >= cutoff {
bm[j / 64] |= 1u64 << (j % 64);
}
}
bm
}
fn ref_overlap(a: &[u64], b: &[u64]) -> u32 {
a.iter()
.zip(b.iter())
.map(|(&x, &y)| (x & y).count_ones())
.sum()
}
#[test]
#[should_panic(expected = "out of range")]
fn rt1_subset_rejects_oob_doc_id_at_simd_dim() {
const DIM: usize = 1024;
const N_TOP: usize = 256;
let mut idx = Bitmap::new(DIM, N_TOP);
let corpus: Vec<f32> = (0..4 * DIM)
.map(|i| ((i * 7) % 101) as f32 - 50.0)
.collect();
idx.add(&corpus);
let q: Vec<f32> = (0..DIM).map(|i| ((i * 13) % 97) as f32 - 48.0).collect();
let qb = idx.build_query_bitmap_fp32(&q);
let doc_ids = [4u32]; let mut out = vec![0u32; 1];
idx.body_overlap_scores_subset(&qb, &doc_ids, &mut out);
}
#[test]
fn rt1_subset_in_range_matches_reference_popcount() {
const DIM: usize = 1024;
const N_TOP: usize = 256;
let n_docs = 6usize;
let mut idx = Bitmap::new(DIM, N_TOP);
let corpus: Vec<f32> = (0..n_docs * DIM)
.map(|i| (((i * 31) % 211) as f32) - 105.0)
.collect();
idx.add(&corpus);
let q: Vec<f32> = (0..DIM).map(|i| ((i * 17) % 89) as f32 - 44.0).collect();
let qb = idx.build_query_bitmap_fp32(&q);
let doc_ids = [0u32, 2, 3, 5];
let mut out = vec![0u32; doc_ids.len()];
idx.body_overlap_scores_subset(&qb, &doc_ids, &mut out);
for (i, &di) in doc_ids.iter().enumerate() {
let doc = &corpus[di as usize * DIM..(di as usize + 1) * DIM];
let expected = ref_overlap(&qb, &ref_doc_bitmap(doc, DIM, N_TOP));
assert_eq!(
out[i], expected,
"subset overlap mismatch at doc {di}: kernel {} vs reference {expected}",
out[i],
);
}
}
#[cfg(feature = "experimental")]
#[test]
#[should_panic(expected = "out of range")]
fn pf_bilinear_score_rejects_oob_doc_idx() {
const DIM: usize = 128;
let n_docs = 4usize;
let mut mb = MultiBucketBitmap::new(DIM, 2);
let corpus: Vec<f32> = (0..n_docs * DIM)
.map(|i| ((i * 5) % 71) as f32 - 35.0)
.collect();
mb.add(&corpus);
let q: Vec<f32> = (0..DIM).map(|i| ((i * 9) % 67) as f32 - 33.0).collect();
let qb = mb.query_bitmaps_from_ranks(&q);
let w = mb.outer_product_weights();
let _ = mb.bilinear_score(&qb, &w, n_docs);
}
#[cfg(feature = "experimental")]
#[test]
#[should_panic(expected = "dim must be > 0")]
fn pg_multi_bucket_new_rejects_dim_zero() {
let _ = MultiBucketBitmap::new(0, 2);
}
#[test]
fn ph_bitmap_search_clamps_huge_k() {
const DIM: usize = 128;
let n_docs = 16usize;
let mut idx = Bitmap::new(DIM, DIM / 4);
let corpus: Vec<f32> = (0..n_docs * DIM)
.map(|i| ((i * 3) % 53) as f32 - 26.0)
.collect();
idx.add(&corpus);
let q: Vec<f32> = (0..DIM).map(|i| ((i * 11) % 59) as f32 - 29.0).collect();
let res = idx.search(&q, usize::MAX);
assert_eq!(res.nq, 1);
let valid = res.indices_for_query(0).iter().filter(|&&i| i >= 0).count();
assert!(
valid <= n_docs,
"search returned {valid} results, exceeds n_vectors {n_docs}",
);
assert!(
res.indices_for_query(0).len() <= n_docs,
"result row length {} exceeds n_vectors {n_docs} (k not clamped)",
res.indices_for_query(0).len(),
);
}
#[cfg(feature = "experimental")]
#[test]
fn ph_multi_bucket_top_m_bilinear_clamps_huge_m() {
const DIM: usize = 128;
let n_docs = 16usize;
let mut mb = MultiBucketBitmap::new(DIM, 2);
let corpus: Vec<f32> = (0..n_docs * DIM)
.map(|i| ((i * 7) % 61) as f32 - 30.0)
.collect();
mb.add(&corpus);
let q: Vec<f32> = (0..DIM).map(|i| ((i * 13) % 73) as f32 - 36.0).collect();
let qb = mb.query_bitmaps_from_ranks(&q);
let w = mb.outer_product_weights();
let head = mb.top_m_bilinear(&qb, &w, usize::MAX);
assert!(
head.len() <= n_docs,
"top_m_bilinear returned {} ids, exceeds n_vectors {n_docs}",
head.len(),
);
}
#[test]
fn r2_topk_breaks_ties_by_lower_doc_id() {
const DIM: usize = 128;
let n_top = DIM / 4;
let mut idx = Bitmap::new(DIM, n_top);
let dup: Vec<f32> = (0..DIM).map(|i| i as f32).collect();
let mut corpus: Vec<f32> = Vec::new();
for d in 0..3usize {
for i in 0..DIM {
corpus.push((DIM - 1 - i) as f32 + d as f32 * 0.001);
}
}
for _ in 0..4 {
corpus.extend_from_slice(&dup);
}
idx.add(&corpus);
let res = idx.search(&dup, 4);
let top = res.indices_for_query(0);
assert_eq!(
top,
&[3i64, 4, 5, 6],
"tied winners must be ordered by ascending doc_id (composite key)",
);
let scores = res.scores_for_query(0);
let max = scores[0];
for (slot, &s) in scores.iter().enumerate() {
assert_eq!(s, max, "winner at slot {slot} must share the max overlap");
}
let res2 = idx.search(&dup, 4);
assert_eq!(
res2.indices_for_query(0),
top,
"tie-break must be deterministic"
);
}