pub mod distance_inline;
pub mod pq;
pub mod spann;
pub mod vamana;
pub mod vamana_persist;
pub use pq::{CompressedVectorStore, PQConfig, ProductQuantizer};
pub use spann::{SpannConfig, SpannIndex};
pub use vamana::{DistanceMetric, VamanaConfig, VamanaIndex, REBUILD_THRESHOLD};
use anyhow::Result;
use std::path::Path;
pub const SPANN_AUTO_THRESHOLD: usize = 100_000;
#[derive(Debug, Clone)]
pub struct BackendConfig {
pub dimension: usize,
pub distance_metric: DistanceMetric,
pub force_backend: Option<BackendType>,
pub use_pq: bool,
pub spann_probes: usize,
pub vamana_max_degree: usize,
pub vamana_search_list_size: usize,
}
impl Default for BackendConfig {
fn default() -> Self {
Self {
dimension: 384, distance_metric: DistanceMetric::NormalizedDotProduct,
force_backend: None,
use_pq: true,
spann_probes: 20,
vamana_max_degree: 32,
vamana_search_list_size: 100,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendType {
Vamana,
Spann,
}
pub enum VectorIndexBackend {
Vamana(VamanaIndex),
Spann(SpannIndex),
}
impl VectorIndexBackend {
pub fn auto(config: BackendConfig, expected_vectors: usize) -> Result<Self> {
let backend_type = config.force_backend.unwrap_or_else(|| {
if expected_vectors >= SPANN_AUTO_THRESHOLD {
BackendType::Spann
} else {
BackendType::Vamana
}
});
match backend_type {
BackendType::Vamana => Self::new_vamana(config),
BackendType::Spann => Self::new_spann(config),
}
}
pub fn new_vamana(config: BackendConfig) -> Result<Self> {
let vamana_config = VamanaConfig {
dimension: config.dimension,
max_degree: config.vamana_max_degree,
search_list_size: config.vamana_search_list_size,
distance_metric: config.distance_metric,
..Default::default()
};
Ok(Self::Vamana(VamanaIndex::new(vamana_config)?))
}
pub fn new_spann(config: BackendConfig) -> Result<Self> {
let spann_config = SpannConfig {
dimension: config.dimension,
use_pq: config.use_pq,
num_probes: config.spann_probes,
distance_metric: config.distance_metric,
..Default::default()
};
Ok(Self::Spann(SpannIndex::new(spann_config)))
}
pub fn backend_type(&self) -> BackendType {
match self {
Self::Vamana(_) => BackendType::Vamana,
Self::Spann(_) => BackendType::Spann,
}
}
pub fn add_vector(&mut self, vector: Vec<f32>) -> Result<u32> {
match self {
Self::Vamana(idx) => idx.add_vector(vector),
Self::Spann(idx) => {
let id = idx.len() as u32;
idx.insert(id, &vector)?;
Ok(id)
}
}
}
pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<(u32, f32)>> {
match self {
Self::Vamana(idx) => idx.search(query, k),
Self::Spann(idx) => idx.search(query, k),
}
}
pub fn len(&self) -> usize {
match self {
Self::Vamana(idx) => idx.len(),
Self::Spann(idx) => idx.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn save_to_file(&self, path: &Path) -> Result<()> {
match self {
Self::Vamana(idx) => idx.save_to_file(path),
Self::Spann(idx) => idx.save_to_file(path),
}
}
pub fn load_from_file(path: &Path, backend_type: BackendType) -> Result<Self> {
match backend_type {
BackendType::Vamana => Ok(Self::Vamana(VamanaIndex::load_from_file(path)?)),
BackendType::Spann => Ok(Self::Spann(SpannIndex::load_from_file(path)?)),
}
}
pub fn build(&mut self, vectors: Vec<Vec<f32>>) -> Result<()> {
match self {
Self::Vamana(idx) => idx.build(vectors),
Self::Spann(idx) => idx.build(vectors),
}
}
pub fn needs_rebuild(&self) -> bool {
match self {
Self::Vamana(idx) => idx.needs_rebuild(),
Self::Spann(_) => false, }
}
pub fn auto_rebuild_if_needed(&mut self) -> Result<bool> {
match self {
Self::Vamana(idx) => idx.auto_rebuild_if_needed(),
Self::Spann(_) => Ok(false),
}
}
pub fn incremental_insert_count(&self) -> usize {
match self {
Self::Vamana(idx) => idx.incremental_insert_count(),
Self::Spann(_) => 0,
}
}
pub fn deleted_count(&self) -> usize {
match self {
Self::Vamana(idx) => idx.deleted_count(),
Self::Spann(_) => 0,
}
}
pub fn deletion_ratio(&self) -> f32 {
match self {
Self::Vamana(idx) => idx.deletion_ratio(),
Self::Spann(_) => 0.0,
}
}
pub fn needs_compaction(&self) -> bool {
match self {
Self::Vamana(idx) => idx.needs_compaction(),
Self::Spann(_) => false,
}
}
pub fn verify_index_file(path: &Path, backend_type: BackendType) -> Result<bool> {
match backend_type {
BackendType::Vamana => VamanaIndex::verify_index_file(path),
BackendType::Spann => SpannIndex::verify_index_file(path),
}
}
}