use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IndexType {
HNSW,
IVF,
IVFPQ,
Flat,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HnswParams {
pub m: usize,
pub ef_construction: usize,
pub ef_search: usize,
}
impl HnswParams {
pub fn new(m: usize, ef_construction: usize, ef_search: usize) -> Self {
Self {
m,
ef_construction,
ef_search,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IvfParams {
pub n_lists: usize,
pub n_probes: usize,
}
impl IvfParams {
pub fn new(n_lists: usize, n_probes: usize) -> Self {
Self { n_lists, n_probes }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IndexParams {
Hnsw(HnswParams),
Ivf(IvfParams),
}
impl IndexParams {
pub fn as_hnsw(&self) -> Option<&HnswParams> {
match self {
Self::Hnsw(p) => Some(p),
_ => None,
}
}
pub fn as_ivf(&self) -> Option<&IvfParams> {
match self {
Self::Ivf(p) => Some(p),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub enum OptimizationTarget {
MaxRecall,
MaxQPS,
BalancedRecallQPS {
recall_weight: f64,
},
}
#[derive(Debug, Clone)]
pub struct BenchmarkPoint {
pub params: IndexParams,
pub recall_at_10: f64,
pub qps: f64,
pub build_time_ms: u64,
}
impl BenchmarkPoint {
pub fn new(params: IndexParams, recall_at_10: f64, qps: f64, build_time_ms: u64) -> Self {
Self {
params,
recall_at_10,
qps,
build_time_ms,
}
}
}
pub struct IndexOptimizer {
index_type: IndexType,
target: OptimizationTarget,
benchmarks: Vec<BenchmarkPoint>,
}
impl IndexOptimizer {
pub fn new(index_type: IndexType, target: OptimizationTarget) -> Self {
Self {
index_type,
target,
benchmarks: Vec::new(),
}
}
pub fn add_benchmark(&mut self, point: BenchmarkPoint) {
self.benchmarks.push(point);
}
pub fn benchmark_count(&self) -> usize {
self.benchmarks.len()
}
fn score(&self, point: &BenchmarkPoint) -> f64 {
match &self.target {
OptimizationTarget::MaxRecall => point.recall_at_10,
OptimizationTarget::MaxQPS => point.qps,
OptimizationTarget::BalancedRecallQPS { recall_weight } => {
let max_qps = self
.benchmarks
.iter()
.map(|b| b.qps)
.fold(f64::NEG_INFINITY, f64::max);
let norm_qps = if max_qps > 0.0 {
point.qps / max_qps
} else {
0.0
};
recall_weight * point.recall_at_10 + (1.0 - recall_weight) * norm_qps
}
}
}
pub fn best_params(&self) -> Option<&BenchmarkPoint> {
self.benchmarks.iter().max_by(|a, b| {
self.score(a)
.partial_cmp(&self.score(b))
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn pareto_front(&self) -> Vec<&BenchmarkPoint> {
let mut front: Vec<&BenchmarkPoint> = Vec::new();
for candidate in &self.benchmarks {
let dominated = front.iter().any(|existing| {
existing.recall_at_10 >= candidate.recall_at_10
&& existing.qps >= candidate.qps
&& (existing.recall_at_10 > candidate.recall_at_10
|| existing.qps > candidate.qps)
});
if !dominated {
front.retain(|existing| {
!(candidate.recall_at_10 >= existing.recall_at_10
&& candidate.qps >= existing.qps
&& (candidate.recall_at_10 > existing.recall_at_10
|| candidate.qps > existing.qps))
});
front.push(candidate);
}
}
front.sort_by(|a, b| {
b.recall_at_10
.partial_cmp(&a.recall_at_10)
.unwrap_or(std::cmp::Ordering::Equal)
});
front
}
pub fn suggest_next_params(&self) -> Option<IndexParams> {
let best = self.best_params()?;
match &best.params {
IndexParams::Hnsw(p) => {
let next = if p.ef_search < 512 {
HnswParams::new(p.m, p.ef_construction, p.ef_search * 2)
} else if p.m < 64 {
HnswParams::new(p.m * 2, p.ef_construction, p.ef_search)
} else {
HnswParams::new(p.m, p.ef_construction * 2, p.ef_search)
};
Some(IndexParams::Hnsw(next))
}
IndexParams::Ivf(p) => {
let next = if p.n_probes < p.n_lists {
IvfParams::new(p.n_lists, (p.n_probes * 2).min(p.n_lists))
} else {
IvfParams::new(p.n_lists * 2, p.n_probes)
};
Some(IndexParams::Ivf(next))
}
}
}
pub fn index_type(&self) -> &IndexType {
&self.index_type
}
pub fn benchmarks(&self) -> &[BenchmarkPoint] {
&self.benchmarks
}
pub fn clear(&mut self) {
self.benchmarks.clear();
}
pub fn ranked_benchmarks(&self) -> Vec<&BenchmarkPoint> {
let mut ranked: Vec<&BenchmarkPoint> = self.benchmarks.iter().collect();
ranked.sort_by(|a, b| {
self.score(b)
.partial_cmp(&self.score(a))
.unwrap_or(std::cmp::Ordering::Equal)
});
ranked
}
pub fn score_of(&self, point: &BenchmarkPoint) -> f64 {
self.score(point)
}
pub fn filter_by_recall(&self, min_recall: f64) -> Vec<&BenchmarkPoint> {
self.benchmarks
.iter()
.filter(|b| b.recall_at_10 >= min_recall)
.collect()
}
pub fn filter_by_qps(&self, min_qps: f64) -> Vec<&BenchmarkPoint> {
self.benchmarks
.iter()
.filter(|b| b.qps >= min_qps)
.collect()
}
pub fn recall_stats(&self) -> Option<RecallStats> {
if self.benchmarks.is_empty() {
return None;
}
let recalls: Vec<f64> = self.benchmarks.iter().map(|b| b.recall_at_10).collect();
let min = recalls.iter().cloned().fold(f64::INFINITY, f64::min);
let max = recalls.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let mean = recalls.iter().sum::<f64>() / recalls.len() as f64;
Some(RecallStats { min, max, mean })
}
pub fn group_by_variant(&self) -> HashMap<&'static str, Vec<&BenchmarkPoint>> {
let mut groups: HashMap<&'static str, Vec<&BenchmarkPoint>> = HashMap::new();
for b in &self.benchmarks {
let key = match &b.params {
IndexParams::Hnsw(_) => "hnsw",
IndexParams::Ivf(_) => "ivf",
};
groups.entry(key).or_default().push(b);
}
groups
}
}
#[derive(Debug, Clone)]
pub struct RecallStats {
pub min: f64,
pub max: f64,
pub mean: f64,
}
#[cfg(test)]
mod tests {
use super::*;
fn hnsw_point(m: usize, ef_c: usize, ef_s: usize, recall: f64, qps: f64) -> BenchmarkPoint {
BenchmarkPoint::new(
IndexParams::Hnsw(HnswParams::new(m, ef_c, ef_s)),
recall,
qps,
100,
)
}
fn ivf_point(n_lists: usize, n_probes: usize, recall: f64, qps: f64) -> BenchmarkPoint {
BenchmarkPoint::new(
IndexParams::Ivf(IvfParams::new(n_lists, n_probes)),
recall,
qps,
200,
)
}
fn make_hnsw_optimizer(target: OptimizationTarget) -> IndexOptimizer {
IndexOptimizer::new(IndexType::HNSW, target)
}
fn make_ivf_optimizer(target: OptimizationTarget) -> IndexOptimizer {
IndexOptimizer::new(IndexType::IVF, target)
}
#[test]
fn test_new_optimizer_empty() {
let opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
assert_eq!(opt.benchmark_count(), 0);
assert!(opt.best_params().is_none());
assert!(opt.pareto_front().is_empty());
assert!(opt.suggest_next_params().is_none());
}
#[test]
fn test_index_type_stored() {
let opt = IndexOptimizer::new(IndexType::IVF, OptimizationTarget::MaxQPS);
assert_eq!(opt.index_type(), &IndexType::IVF);
}
#[test]
fn test_flat_index_type() {
let opt = IndexOptimizer::new(IndexType::Flat, OptimizationTarget::MaxRecall);
assert_eq!(opt.index_type(), &IndexType::Flat);
}
#[test]
fn test_add_single_benchmark() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
assert_eq!(opt.benchmark_count(), 1);
}
#[test]
fn test_add_multiple_benchmarks() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
for i in 0..10 {
opt.add_benchmark(hnsw_point(
16,
200,
50 + i * 10,
0.8 + i as f64 * 0.01,
5000.0 - i as f64 * 100.0,
));
}
assert_eq!(opt.benchmark_count(), 10);
}
#[test]
fn test_best_params_max_recall_single() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
let best = opt.best_params().expect("should have best");
assert_eq!(best.recall_at_10, 0.9);
}
#[test]
fn test_best_params_max_recall_picks_highest() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.75, 8000.0));
opt.add_benchmark(hnsw_point(32, 400, 100, 0.95, 3000.0));
opt.add_benchmark(hnsw_point(16, 200, 80, 0.85, 6000.0));
let best = opt.best_params().expect("some best");
assert_eq!(best.recall_at_10, 0.95);
}
#[test]
fn test_best_params_max_recall_ignores_qps() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 100_000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 100.0));
let best = opt.best_params().expect("some best");
assert_eq!(best.recall_at_10, 0.99);
}
#[test]
fn test_best_params_max_qps_picks_highest_qps() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxQPS);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 3000.0));
opt.add_benchmark(hnsw_point(4, 50, 10, 0.6, 12000.0));
opt.add_benchmark(hnsw_point(32, 400, 100, 0.95, 1500.0));
let best = opt.best_params().expect("some best");
assert_eq!(best.qps, 12000.0);
}
#[test]
fn test_best_params_max_qps_ignores_recall() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxQPS);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.1, 50000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 1.0, 100.0));
let best = opt.best_params().expect("some best");
assert_eq!(best.qps, 50000.0);
}
#[test]
fn test_best_params_balanced_equal_weight() {
let mut opt =
make_hnsw_optimizer(OptimizationTarget::BalancedRecallQPS { recall_weight: 0.5 });
opt.add_benchmark(hnsw_point(64, 800, 400, 1.0, 100.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 10000.0));
let best = opt.best_params();
assert!(best.is_some());
}
#[test]
fn test_best_params_balanced_recall_heavy() {
let mut opt =
make_hnsw_optimizer(OptimizationTarget::BalancedRecallQPS { recall_weight: 0.9 });
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 1000.0));
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 10000.0));
let best = opt.best_params().expect("some best");
assert_eq!(best.recall_at_10, 0.99);
}
#[test]
fn test_best_params_balanced_qps_heavy() {
let mut opt =
make_hnsw_optimizer(OptimizationTarget::BalancedRecallQPS { recall_weight: 0.1 });
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 1000.0));
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 10000.0));
let best = opt.best_params().expect("some best");
assert_eq!(best.qps, 10000.0);
}
#[test]
fn test_pareto_front_empty() {
let opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
assert!(opt.pareto_front().is_empty());
}
#[test]
fn test_pareto_front_single_point() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
let front = opt.pareto_front();
assert_eq!(front.len(), 1);
}
#[test]
fn test_pareto_front_no_dominated() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 500.0)); opt.add_benchmark(hnsw_point(16, 200, 50, 0.85, 5000.0)); opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0)); let front = opt.pareto_front();
assert_eq!(front.len(), 3);
}
#[test]
fn test_pareto_front_dominated_excluded() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0)); opt.add_benchmark(hnsw_point(8, 100, 30, 0.8, 4000.0)); opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0)); let front = opt.pareto_front();
assert_eq!(front.len(), 2);
let recalls: Vec<f64> = front.iter().map(|p| p.recall_at_10).collect();
assert!(!recalls.contains(&0.8));
}
#[test]
fn test_pareto_front_sorted_by_recall_desc() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.85, 5000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 500.0));
let front = opt.pareto_front();
for window in front.windows(2) {
assert!(window[0].recall_at_10 >= window[1].recall_at_10);
}
}
#[test]
fn test_pareto_front_all_dominated_except_best() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(64, 800, 400, 1.0, 20000.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.8, 5000.0));
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 3000.0));
let front = opt.pareto_front();
assert_eq!(front.len(), 1);
assert_eq!(front[0].recall_at_10, 1.0);
}
#[test]
fn test_suggest_next_none_when_empty() {
let opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
assert!(opt.suggest_next_params().is_none());
}
#[test]
fn test_suggest_next_hnsw_increments_ef_search() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
let next = opt.suggest_next_params().expect("suggestion");
if let IndexParams::Hnsw(p) = next {
assert_eq!(p.ef_search, 100);
assert_eq!(p.m, 16);
} else {
panic!("Expected Hnsw params");
}
}
#[test]
fn test_suggest_next_hnsw_increments_m_when_ef_search_maxed() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 512, 0.99, 1000.0));
let next = opt.suggest_next_params().expect("suggestion");
if let IndexParams::Hnsw(p) = next {
assert_eq!(p.ef_search, 512);
assert_eq!(p.m, 32);
} else {
panic!("Expected Hnsw params");
}
}
#[test]
fn test_suggest_next_ivf_increments_n_probes() {
let mut opt = make_ivf_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(ivf_point(256, 4, 0.7, 8000.0));
let next = opt.suggest_next_params().expect("suggestion");
if let IndexParams::Ivf(p) = next {
assert_eq!(p.n_probes, 8);
assert_eq!(p.n_lists, 256);
} else {
panic!("Expected IVF params");
}
}
#[test]
fn test_suggest_next_ivf_grows_n_lists_when_probes_maxed() {
let mut opt = make_ivf_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(ivf_point(64, 64, 0.95, 2000.0));
let next = opt.suggest_next_params().expect("suggestion");
if let IndexParams::Ivf(p) = next {
assert_eq!(p.n_lists, 128);
} else {
panic!("Expected IVF params");
}
}
#[test]
fn test_ivf_best_params_max_recall() {
let mut opt = make_ivf_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(ivf_point(64, 4, 0.6, 9000.0));
opt.add_benchmark(ivf_point(64, 32, 0.9, 4000.0));
opt.add_benchmark(ivf_point(256, 64, 0.97, 1500.0));
let best = opt.best_params().expect("best");
assert_eq!(best.recall_at_10, 0.97);
}
#[test]
fn test_ivf_best_params_max_qps() {
let mut opt = make_ivf_optimizer(OptimizationTarget::MaxQPS);
opt.add_benchmark(ivf_point(64, 4, 0.6, 9000.0));
opt.add_benchmark(ivf_point(64, 32, 0.9, 4000.0));
opt.add_benchmark(ivf_point(256, 64, 0.97, 1500.0));
let best = opt.best_params().expect("best");
assert_eq!(best.qps, 9000.0);
}
#[test]
fn test_score_of_max_recall() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
let p = hnsw_point(16, 200, 50, 0.92, 5000.0);
opt.add_benchmark(p.clone());
assert!((opt.score_of(&p) - 0.92).abs() < 1e-9);
}
#[test]
fn test_score_of_max_qps() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxQPS);
let p = hnsw_point(16, 200, 50, 0.92, 7777.0);
opt.add_benchmark(p.clone());
assert!((opt.score_of(&p) - 7777.0).abs() < 1e-9);
}
#[test]
fn test_ranked_benchmarks_descending() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.85, 5000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 500.0));
let ranked = opt.ranked_benchmarks();
assert_eq!(ranked[0].recall_at_10, 0.99);
assert_eq!(ranked[1].recall_at_10, 0.85);
assert_eq!(ranked[2].recall_at_10, 0.5);
}
#[test]
fn test_filter_by_recall() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.85, 5000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 500.0));
let filtered = opt.filter_by_recall(0.8);
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_filter_by_qps() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxQPS);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.85, 5000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 500.0));
let filtered = opt.filter_by_qps(5000.0);
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_recall_stats_none_when_empty() {
let opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
assert!(opt.recall_stats().is_none());
}
#[test]
fn test_recall_stats_single() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.88, 5000.0));
let stats = opt.recall_stats().expect("stats");
assert!((stats.min - 0.88).abs() < 1e-9);
assert!((stats.max - 0.88).abs() < 1e-9);
assert!((stats.mean - 0.88).abs() < 1e-9);
}
#[test]
fn test_recall_stats_multiple() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(4, 50, 10, 0.6, 15000.0));
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
opt.add_benchmark(hnsw_point(64, 800, 400, 0.99, 500.0));
let stats = opt.recall_stats().expect("stats");
assert!((stats.min - 0.6).abs() < 1e-9);
assert!((stats.max - 0.99).abs() < 1e-9);
assert!((stats.mean - (0.6 + 0.9 + 0.99) / 3.0).abs() < 1e-9);
}
#[test]
fn test_clear_removes_all() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
opt.add_benchmark(hnsw_point(4, 50, 10, 0.5, 15000.0));
opt.clear();
assert_eq!(opt.benchmark_count(), 0);
assert!(opt.best_params().is_none());
}
#[test]
fn test_group_by_variant_mixed() {
let mut opt = IndexOptimizer::new(IndexType::IVFPQ, OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
opt.add_benchmark(ivf_point(64, 8, 0.75, 7000.0));
opt.add_benchmark(hnsw_point(32, 400, 100, 0.95, 2000.0));
let groups = opt.group_by_variant();
assert_eq!(groups["hnsw"].len(), 2);
assert_eq!(groups["ivf"].len(), 1);
}
#[test]
fn test_index_params_as_hnsw() {
let p = IndexParams::Hnsw(HnswParams::new(16, 200, 50));
assert!(p.as_hnsw().is_some());
assert!(p.as_ivf().is_none());
}
#[test]
fn test_index_params_as_ivf() {
let p = IndexParams::Ivf(IvfParams::new(64, 8));
assert!(p.as_ivf().is_some());
assert!(p.as_hnsw().is_none());
}
#[test]
fn test_benchmarks_accessor() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 5000.0));
assert_eq!(opt.benchmarks().len(), 1);
}
#[test]
fn test_zero_qps_does_not_panic() {
let mut opt =
make_hnsw_optimizer(OptimizationTarget::BalancedRecallQPS { recall_weight: 0.5 });
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 0.0));
let best = opt.best_params().expect("some best");
let s = opt.score_of(best);
assert!((s - 0.45).abs() < 1e-9);
}
#[test]
fn test_identical_recall_uses_qps_tiebreak() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(hnsw_point(16, 200, 50, 0.9, 1000.0));
opt.add_benchmark(hnsw_point(32, 400, 100, 0.9, 5000.0));
assert!(opt.best_params().is_some());
}
#[test]
fn test_build_time_stored() {
let mut opt = make_hnsw_optimizer(OptimizationTarget::MaxRecall);
opt.add_benchmark(BenchmarkPoint::new(
IndexParams::Hnsw(HnswParams::new(16, 200, 50)),
0.9,
5000.0,
12345,
));
assert_eq!(opt.benchmarks()[0].build_time_ms, 12345);
}
#[test]
fn test_hnsw_params_equality() {
let a = HnswParams::new(16, 200, 50);
let b = HnswParams::new(16, 200, 50);
assert_eq!(a, b);
}
#[test]
fn test_ivf_params_equality() {
let a = IvfParams::new(64, 8);
let b = IvfParams::new(64, 8);
assert_eq!(a, b);
}
#[test]
fn test_index_type_equality() {
assert_eq!(IndexType::HNSW, IndexType::HNSW);
assert_ne!(IndexType::HNSW, IndexType::IVF);
assert_ne!(IndexType::IVF, IndexType::IVFPQ);
assert_ne!(IndexType::IVFPQ, IndexType::Flat);
}
}