use crate::{
flat::{
FlatIndex,
test::{
harness,
provider::{self as flat_provider, ElementCounter, Strategy},
},
},
graph::test::synthetic::Grid,
test::{
TestPath, TestRoot,
cmp::{assert_eq_verbose, verbose_eq},
get_or_save_test_results,
},
};
fn root() -> TestRoot {
TestRoot::new("flat/test/cases/flat_knn_search")
}
const KS: [usize; 3] = [1, 4, 10];
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct FlatKnnBaseline {
description: String,
query: Vec<f32>,
grid_dims: usize,
grid_size: usize,
k: usize,
top_k_distances: Vec<f32>,
ground_truth: Vec<(u32, f32)>,
comparisons: usize,
result_count: usize,
metrics: ElementCounter,
}
verbose_eq!(FlatKnnBaseline {
description,
query,
grid_dims,
grid_size,
k,
top_k_distances,
ground_truth,
comparisons,
result_count,
metrics,
});
fn run_row(
index: &FlatIndex<flat_provider::Provider>,
grid_dim: usize,
grid_size: usize,
query: &[f32],
k: usize,
desc: &str,
) -> FlatKnnBaseline {
let len = index.provider().len();
let metrics_before = index.provider().metrics();
let outcome =
harness::KnnOracleRun::run_sync(index, &Strategy::new(index.provider().dim()), query, k)
.unwrap();
let stats = outcome.stats;
assert_eq!(
stats.cmps as usize, len,
"flat scan must touch every element exactly once",
);
assert_eq!(
stats.result_count as usize,
k.min(len),
"result_count must equal min(k, provider.len())",
);
let gt_distances: Vec<f32> = outcome.ground_truth.iter().map(|(_, d)| *d).collect();
assert_eq!(
outcome.top_k_distances, gt_distances,
"flat scan top-k distance multiset must agree with brute force",
);
let metrics_after = index.provider().metrics();
let metrics = ElementCounter {
count: metrics_after.count - metrics_before.count,
};
assert_eq!(
metrics.count, len,
"expected exactly one scan (from knn_search) to increment get_element",
);
FlatKnnBaseline {
description: desc.to_string(),
query: query.to_vec(),
grid_dims: grid_dim,
grid_size,
k,
top_k_distances: outcome.top_k_distances,
ground_truth: outcome.ground_truth,
comparisons: stats.cmps as usize,
result_count: stats.result_count as usize,
metrics,
}
}
fn _flat_knn_search(grid: Grid, size: usize, mut parent: TestPath<'_>) {
let dim: usize = grid.dim().into();
let provider = flat_provider::Provider::grid(grid, size).unwrap();
let len = provider.len();
assert_eq!(
len,
size.pow(dim as u32),
"flat::test::Provider::grid should produce size^dim rows",
);
let index = FlatIndex::new(provider);
let queries: [(Vec<f32>, &str); 2] = [
(
vec![-1.0; dim],
"All -1: nearest is the all-zeros corner; result_count = min(k, len).",
),
(
vec![(size - 1) as f32; dim],
"All `size-1`: query coincides with the last grid corner.",
),
];
let index_ref = &index;
let results: Vec<FlatKnnBaseline> = queries
.iter()
.flat_map(|(q, desc)| {
KS.iter()
.map(move |&k| run_row(index_ref, dim, size, q, k, desc))
})
.collect();
let name = parent.push(format!("search_{dim}_{size}"));
let expected = get_or_save_test_results(&name, &results);
assert_eq_verbose!(expected, results);
}
#[test]
fn flat_knn_search_1_100() {
_flat_knn_search(Grid::One, 100, root().path());
}
#[test]
fn flat_knn_search_2_5() {
_flat_knn_search(Grid::Two, 5, root().path());
}
#[test]
fn flat_knn_search_3_4() {
_flat_knn_search(Grid::Three, 4, root().path());
}