Skip to main content

oxirs_vec/
vector_index.rs

1//! In-memory vector index implementations and the `VectorIndex` trait.
2
3use anyhow::Result;
4use std::collections::HashMap;
5
6use crate::similarity;
7use crate::Vector;
8use crate::VectorId;
9
10/// Vector index trait for efficient similarity search.
11///
12/// # Score contract (must be honored by every implementation)
13///
14/// All `VectorIndex` methods that return `(id, score)` tuples use **similarity**
15/// semantics, *not* raw distance:
16///
17/// * The `f32` score is a **similarity**: larger means *more similar / closer*.
18/// * [`VectorIndex::search_knn`] returns results sorted by **descending**
19///   similarity (best match first).
20/// * [`VectorIndex::search_threshold`] returns every vector whose similarity is
21///   `>= threshold` (i.e. the comparison is `similarity >= threshold`, never
22///   `distance <= threshold`).
23///
24/// This mirrors the public [`crate::VectorStore::similarity_search`] API and the
25/// reference [`MemoryVectorIndex`] implementation. Backends whose internal
26/// algorithm works in distance space (HNSW, LSH, memory-mapped, IVF, PQ, NSG)
27/// **must** convert their distances to a monotonically-decreasing similarity
28/// (the crate convention is `similarity = 1.0 / (1.0 + distance)`) before
29/// returning, so that all backends agree on units and ordering. A caller that
30/// dispatches the same logical query to different backends (e.g.
31/// `DynamicIndexSelector`) must be able to compare and re-rank the returned
32/// scores without knowing which backend produced them.
33pub trait VectorIndex: Send + Sync {
34    /// Insert a vector with associated URI
35    fn insert(&mut self, uri: String, vector: Vector) -> Result<()>;
36
37    /// Find the `k` nearest neighbors, returned as `(id, similarity)` sorted by
38    /// **descending** similarity (best match first). See the trait-level score
39    /// contract: the score is a similarity, not a distance.
40    fn search_knn(&self, query: &Vector, k: usize) -> Result<Vec<(String, f32)>>;
41
42    /// Find all vectors whose **similarity** to `query` is `>= threshold`.
43    ///
44    /// The score is a similarity (larger = closer), consistent with
45    /// [`VectorIndex::search_knn`]; the filter is `similarity >= threshold`.
46    fn search_threshold(&self, query: &Vector, threshold: f32) -> Result<Vec<(String, f32)>>;
47
48    /// Get a vector by its URI
49    fn get_vector(&self, uri: &str) -> Option<&Vector>;
50
51    /// Add a vector with associated ID and metadata
52    fn add_vector(
53        &mut self,
54        id: VectorId,
55        vector: Vector,
56        _metadata: Option<HashMap<String, String>>,
57    ) -> Result<()> {
58        // Default implementation that delegates to insert
59        self.insert(id, vector)
60    }
61
62    /// Update an existing vector
63    fn update_vector(&mut self, id: VectorId, vector: Vector) -> Result<()> {
64        // Default implementation that delegates to insert
65        self.insert(id, vector)
66    }
67
68    /// Update metadata for a vector
69    fn update_metadata(&mut self, _id: VectorId, _metadata: HashMap<String, String>) -> Result<()> {
70        // Default implementation (no-op)
71        Ok(())
72    }
73
74    /// Remove a vector by its ID
75    fn remove_vector(&mut self, _id: VectorId) -> Result<()> {
76        // Default implementation (no-op)
77        Ok(())
78    }
79
80    /// Iterate all stored (id, vector) pairs.
81    ///
82    /// The default returns an empty list; concrete index types that hold their
83    /// vectors in memory (or can reconstruct them, e.g. via decoding quantized
84    /// codes) should override this **and** [`VectorIndex::supports_enumeration`]
85    /// so callers like `VectorStore::save_to_disk` can tell real emptiness
86    /// apart from "this index type cannot enumerate its vectors".
87    fn iter_vectors(&self) -> Vec<(String, Vector)> {
88        Vec::new()
89    }
90
91    /// Whether [`VectorIndex::iter_vectors`] returns a real, complete
92    /// enumeration of the vectors held by this index.
93    ///
94    /// Index types that override `iter_vectors` with a real implementation
95    /// (e.g. [`MemoryVectorIndex`], `HnswIndex`, `IvfIndex`, `PQIndex`) must
96    /// also override this to return `true`. Callers that need to persist or
97    /// otherwise fully enumerate an index (e.g. `VectorStore::save_to_disk`)
98    /// should check this flag and fail loudly instead of silently persisting
99    /// an empty snapshot when it is `false`.
100    fn supports_enumeration(&self) -> bool {
101        false
102    }
103}
104
105/// In-memory vector index implementation
106pub struct MemoryVectorIndex {
107    vectors: Vec<(String, Vector)>,
108    similarity_config: similarity::SimilarityConfig,
109}
110
111impl MemoryVectorIndex {
112    /// Create a new empty in-memory vector index with default similarity config.
113    pub fn new() -> Self {
114        Self {
115            vectors: Vec::new(),
116            similarity_config: similarity::SimilarityConfig::default(),
117        }
118    }
119
120    /// Create a new in-memory vector index with a custom similarity configuration.
121    pub fn with_similarity_config(config: similarity::SimilarityConfig) -> Self {
122        Self {
123            vectors: Vec::new(),
124            similarity_config: config,
125        }
126    }
127}
128
129impl Default for MemoryVectorIndex {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl VectorIndex for MemoryVectorIndex {
136    fn insert(&mut self, uri: String, vector: Vector) -> Result<()> {
137        // Check if vector already exists and update it
138        if let Some(pos) = self.vectors.iter().position(|(id, _)| id == &uri) {
139            self.vectors[pos] = (uri, vector);
140        } else {
141            self.vectors.push((uri, vector));
142        }
143        Ok(())
144    }
145
146    fn search_knn(&self, query: &Vector, k: usize) -> Result<Vec<(String, f32)>> {
147        let metric = self.similarity_config.primary_metric;
148        let query_f32 = query.as_f32();
149        let mut similarities: Vec<(String, f32)> = self
150            .vectors
151            .iter()
152            .map(|(uri, vec)| {
153                let vec_f32 = vec.as_f32();
154                let sim = metric.similarity(&query_f32, &vec_f32).unwrap_or(0.0);
155                (uri.clone(), sim)
156            })
157            .collect();
158
159        similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
160        similarities.truncate(k);
161
162        Ok(similarities)
163    }
164
165    fn search_threshold(&self, query: &Vector, threshold: f32) -> Result<Vec<(String, f32)>> {
166        let metric = self.similarity_config.primary_metric;
167        let query_f32 = query.as_f32();
168        let similarities: Vec<(String, f32)> = self
169            .vectors
170            .iter()
171            .filter_map(|(uri, vec)| {
172                let vec_f32 = vec.as_f32();
173                let sim = metric.similarity(&query_f32, &vec_f32).unwrap_or(0.0);
174                if sim >= threshold {
175                    Some((uri.clone(), sim))
176                } else {
177                    None
178                }
179            })
180            .collect();
181
182        Ok(similarities)
183    }
184
185    fn get_vector(&self, uri: &str) -> Option<&Vector> {
186        self.vectors.iter().find(|(u, _)| u == uri).map(|(_, v)| v)
187    }
188
189    fn update_vector(&mut self, id: VectorId, vector: Vector) -> Result<()> {
190        if let Some(pos) = self.vectors.iter().position(|(uri, _)| uri == &id) {
191            self.vectors[pos] = (id, vector);
192            Ok(())
193        } else {
194            Err(anyhow::anyhow!("Vector with id '{}' not found", id))
195        }
196    }
197
198    fn remove_vector(&mut self, id: VectorId) -> Result<()> {
199        if let Some(pos) = self.vectors.iter().position(|(uri, _)| uri == &id) {
200            self.vectors.remove(pos);
201            Ok(())
202        } else {
203            Err(anyhow::anyhow!("Vector with id '{}' not found", id))
204        }
205    }
206
207    fn iter_vectors(&self) -> Vec<(String, Vector)> {
208        self.vectors.clone()
209    }
210
211    fn supports_enumeration(&self) -> bool {
212        true
213    }
214}