use ruvector_core::index::hnsw::HnswIndex;
use ruvector_core::index::VectorIndex;
use ruvector_core::types::{DistanceMetric, HnswConfig};
use std::time::Instant;
fn read_f32(p: &str) -> Vec<f32> {
std::fs::read(p)
.unwrap()
.chunks_exact(4)
.map(|b| f32::from_le_bytes(b.try_into().unwrap()))
.collect()
}
fn read_i32(p: &str) -> Vec<i32> {
std::fs::read(p)
.unwrap()
.chunks_exact(4)
.map(|b| i32::from_le_bytes(b.try_into().unwrap()))
.collect()
}
fn main() {
let a: Vec<String> = std::env::args().collect();
let dim: usize = a[4].parse().unwrap();
let m: usize = a.get(5).map(|s| s.parse().unwrap()).unwrap_or(16);
let efc: usize = a.get(6).map(|s| s.parse().unwrap()).unwrap_or(200);
let base = read_f32(&a[1]);
let q = read_f32(&a[2]);
let gt = read_i32(&a[3]);
let n = base.len() / dim;
let nq = q.len() / dim;
let cfg = HnswConfig {
m,
ef_construction: efc,
ef_search: 50,
max_elements: n + 10,
};
let mut idx = HnswIndex::new(dim, DistanceMetric::Euclidean, cfg).unwrap();
let t = Instant::now();
for i in 0..n {
idx.add(i.to_string(), base[i * dim..(i + 1) * dim].to_vec())
.unwrap();
}
println!("build_s {:.2}", t.elapsed().as_secs_f64());
for ef in [10usize, 20, 40, 80, 160, 320] {
let mut best = f64::MAX;
let mut hits = 0usize;
for pass in 0..3 {
let t = Instant::now();
let mut cnt = 0usize;
for qi in 0..nq {
let r = idx
.search_with_ef(&q[qi * dim..(qi + 1) * dim], 10, ef)
.unwrap();
if pass == 0 {
for h in &r {
let id: i32 = h.id.parse().unwrap();
if gt[qi * 10..qi * 10 + 10].contains(&id) {
cnt += 1;
}
}
}
}
best = best.min(t.elapsed().as_secs_f64());
if pass == 0 {
hits = cnt;
}
}
println!(
"ef {ef:4} recall@10 {:.4} qps {:.0}",
hits as f64 / (nq * 10) as f64,
nq as f64 / best
);
}
}