use crate::l2_normalize;
fn lcg_f32(state: &mut u64) -> f32 {
*state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
((*state >> 33) as f32) / ((1u64 << 31) as f32)
}
fn lcg_uniform(state: &mut u64) -> f32 {
lcg_f32(state) * 2.0 - 1.0
}
pub fn generate_matryoshka_dataset(
n: usize,
n_queries: usize,
full_dim: usize,
signal_dim: usize,
seed: u64,
) -> (Vec<Vec<f32>>, Vec<Vec<f32>>) {
let mut rng = seed;
let num_clusters: usize = (n / 10).clamp(10, 100);
let centres: Vec<Vec<f32>> = (0..num_clusters)
.map(|_| {
let mut c: Vec<f32> = (0..signal_dim).map(|_| lcg_uniform(&mut rng)).collect();
l2_normalize(&mut c);
c
})
.collect();
let make_vec = |rng: &mut u64, cluster: usize| -> Vec<f32> {
let centre = ¢res[cluster % centres.len()];
let mut v: Vec<f32> = Vec::with_capacity(full_dim);
for &c in centre {
v.push(c + lcg_uniform(rng) * 0.08);
}
for _ in signal_dim..full_dim {
v.push(lcg_uniform(rng) * 0.25);
}
l2_normalize(&mut v);
v
};
let vectors: Vec<Vec<f32>> = (0..n).map(|i| make_vec(&mut rng, i)).collect();
let queries: Vec<Vec<f32>> = (0..n_queries).map(|i| make_vec(&mut rng, i * 3)).collect();
(vectors, queries)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dataset_is_normalised() {
let (vecs, queries) = generate_matryoshka_dataset(50, 5, 128, 32, 999);
for v in vecs.iter().chain(queries.iter()) {
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!(
(norm - 1.0).abs() < 1e-4,
"vector not unit-normalised: norm={norm}"
);
}
}
#[test]
fn dataset_is_deterministic() {
let (a, _) = generate_matryoshka_dataset(10, 3, 64, 16, 12345);
let (b, _) = generate_matryoshka_dataset(10, 3, 64, 16, 12345);
for (av, bv) in a.iter().zip(b.iter()) {
for (ai, bi) in av.iter().zip(bv.iter()) {
assert!((ai - bi).abs() < 1e-8);
}
}
}
#[test]
fn prefix_captures_cluster_structure() {
use crate::{brute_force_knn, recall_at_k};
let (vecs, queries) = generate_matryoshka_dataset(200, 10, 128, 32, 77777);
let mut coarse_recall_sum = 0.0f32;
for q in &queries {
let gt_full = brute_force_knn(&vecs, q, 10, 128);
let gt_coarse = brute_force_knn(&vecs, q, 50, 32);
coarse_recall_sum += recall_at_k(>_coarse, >_full);
}
let avg = coarse_recall_sum / queries.len() as f32;
assert!(
avg >= 0.40,
"coarse-dim recall@10 = {:.3} < 0.40; dataset may lack signal",
avg
);
}
}