1use std::collections::HashSet;
2use std::path::Path;
3
4use rayon::prelude::*;
5use serde::{Deserialize, Serialize};
6
7use crate::errors::{Result, VecDbError};
8use crate::index::backend::IndexBackend;
9use crate::index::distance::compute_distance;
10use crate::types::{CollectionConfig, DistanceMetric, IndexType, Vector, VectorId};
11
12const DEFAULT_N_LISTS: usize = 256;
13const DEFAULT_N_PROBE: usize = 16;
14const DEFAULT_MAX_ITER: usize = 25;
15
16#[derive(Serialize, Deserialize)]
17pub struct IvfIndex {
18 n_lists: usize,
19 n_probe: usize,
20 max_iter: usize,
21 dimension: usize,
22 metric: DistanceMetric,
23 centroids: Vec<Vec<f32>>,
24 lists: Vec<Vec<(VectorId, Vec<f32>)>>,
25 deleted: HashSet<VectorId>,
26 is_trained: bool,
27}
28
29impl IvfIndex {
30 pub fn new(config: &CollectionConfig) -> Self {
31 Self {
32 n_lists: DEFAULT_N_LISTS,
33 n_probe: DEFAULT_N_PROBE,
34 max_iter: DEFAULT_MAX_ITER,
35 dimension: config.dimension,
36 metric: config.metric.clone(),
37 centroids: Vec::new(),
38 lists: vec![Vec::new()],
39 deleted: HashSet::new(),
40 is_trained: false,
41 }
42 }
43
44 pub fn load_file(path: &Path) -> Result<Self> {
45 let json = std::fs::read_to_string(path)
46 .map_err(|e| VecDbError::StorageError(format!("ivf load failed: {e}")))?;
47 serde_json::from_str(&json).map_err(|e| VecDbError::SerializationError(e.to_string()))
48 }
49
50 fn train(&mut self, vectors: &[(VectorId, Vec<f32>)]) {
51 let n = vectors.len();
52 if n == 0 {
53 self.centroids.clear();
54 self.lists = vec![Vec::new()];
55 self.is_trained = false;
56 return;
57 }
58
59 let mut n_lists = self.n_lists;
60 if n < n_lists {
61 n_lists = n.max(1);
62 }
63
64 let mut centroids: Vec<Vec<f32>> = (0..n_lists)
66 .map(|i| vectors[i * (n / n_lists)].1.clone())
67 .collect();
68
69 for _iter in 0..self.max_iter {
70 let mut assignments: Vec<Vec<usize>> = vec![Vec::new(); n_lists];
72 for (vi, (_, v)) in vectors.iter().enumerate() {
73 let ci = Self::nearest_centroid_idx_of(¢roids, v, &self.metric);
74 assignments[ci].push(vi);
75 }
76
77 let mut new_centroids = centroids.clone();
79 for (ci, assigned) in assignments.iter().enumerate() {
80 if assigned.is_empty() {
81 let fallback_idx = (ci * 7) % n;
83 new_centroids[ci] = vectors[fallback_idx].1.clone();
84 } else {
85 let dim = self.dimension;
86 let mut mean = vec![0.0f32; dim];
87 for &vi in assigned {
88 for (j, x) in vectors[vi].1.iter().enumerate() {
89 mean[j] += x;
90 }
91 }
92 let count = assigned.len() as f32;
93 for x in &mut mean {
94 *x /= count;
95 }
96 new_centroids[ci] = mean;
97 }
98 }
99
100 let converged = centroids
102 .iter()
103 .zip(new_centroids.iter())
104 .all(|(old, new)| {
105 old.iter()
106 .zip(new.iter())
107 .all(|(a, b)| (a - b).abs() < 1e-6)
108 });
109 centroids = new_centroids;
110 if converged {
111 break;
112 }
113 }
114
115 self.centroids = centroids;
116 self.n_lists = n_lists;
117
118 self.lists = vec![Vec::new(); n_lists];
120 for (id, v) in vectors {
121 let ci = Self::nearest_centroid_idx_of(&self.centroids, v, &self.metric);
122 self.lists[ci].push((id.clone(), v.clone()));
123 }
124
125 self.is_trained = true;
126 }
127
128 fn nearest_centroid_idx_of(
129 centroids: &[Vec<f32>],
130 query: &[f32],
131 metric: &DistanceMetric,
132 ) -> usize {
133 centroids
134 .iter()
135 .enumerate()
136 .map(|(i, c)| (i, compute_distance(query, c, metric)))
137 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
138 .map(|(i, _)| i)
139 .unwrap_or(0)
140 }
141
142 fn nearest_centroid_indices(&self, query: &[f32], n_probe: usize) -> Vec<usize> {
143 let mut scored: Vec<(usize, f32)> = self
147 .centroids
148 .par_iter()
149 .enumerate()
150 .map(|(i, c)| (i, compute_distance(query, c, &self.metric)))
151 .collect();
152 scored.sort_unstable_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
153 scored.truncate(n_probe);
154 scored.iter().map(|(i, _)| *i).collect()
155 }
156
157 fn total_count(&self) -> usize {
158 self.lists.iter().map(|l| l.len()).sum()
159 }
160
161 fn distance_to_score(&self, dist: f32) -> f32 {
162 match self.metric {
163 DistanceMetric::Cosine => 1.0 - dist,
164 DistanceMetric::Euclidean => 1.0 / (1.0 + dist),
165 DistanceMetric::DotProduct => 1.0 - dist,
166 }
167 }
168
169 fn rebuild_from_lists(&mut self) -> Result<()> {
170 let all_vectors: Vec<(VectorId, Vec<f32>)> = self
171 .lists
172 .iter()
173 .flat_map(|l| l.iter())
174 .filter(|(id, _)| !self.deleted.contains(id))
175 .map(|(id, v)| (id.clone(), v.clone()))
176 .collect();
177
178 self.deleted.clear();
179
180 if all_vectors.is_empty() {
181 self.centroids.clear();
182 self.lists = vec![Vec::new()];
183 self.is_trained = false;
184 return Ok(());
185 }
186
187 self.train(&all_vectors);
188 Ok(())
189 }
190}
191
192impl IndexBackend for IvfIndex {
193 fn build(&mut self, vectors: Vec<(VectorId, Vector)>) -> Result<()> {
194 self.deleted.clear();
195 self.train(&vectors);
196 Ok(())
197 }
198
199 fn insert(&mut self, id: VectorId, vector: Vector) -> Result<()> {
200 if vector.len() != self.dimension {
201 return Err(VecDbError::DimensionMismatch {
202 expected: self.dimension,
203 got: vector.len(),
204 });
205 }
206
207 self.deleted.remove(&id);
209
210 for list in &mut self.lists {
212 list.retain(|(lid, _)| lid != &id);
213 }
214
215 if self.is_trained && !self.centroids.is_empty() {
216 let ci = Self::nearest_centroid_idx_of(&self.centroids, &vector, &self.metric);
217 self.lists[ci].push((id, vector));
218 } else {
219 if self.lists.is_empty() {
221 self.lists.push(Vec::new());
222 }
223 self.lists[0].push((id, vector));
224 }
225
226 let total = self.total_count();
227 if !self.is_trained || total.is_multiple_of(1000) {
228 self.rebuild_from_lists()?;
229 }
230
231 Ok(())
232 }
233
234 fn search(&self, query: &Vector, k: usize) -> Result<Vec<(VectorId, f32)>> {
235 if query.len() != self.dimension {
236 return Err(VecDbError::DimensionMismatch {
237 expected: self.dimension,
238 got: query.len(),
239 });
240 }
241
242 let mut candidates: Vec<(VectorId, f32)> = if !self.is_trained || self.centroids.is_empty()
243 {
244 let pairs: Vec<(&VectorId, &Vec<f32>)> = self
249 .lists
250 .iter()
251 .flat_map(|l| l.iter())
252 .filter(|(id, _)| !self.deleted.contains(id))
253 .map(|(id, v)| (id, v))
254 .collect();
255 let metric = &self.metric;
256 pairs
257 .par_iter()
258 .map(|(id, v)| {
259 let dist = compute_distance(query, v, metric);
260 let score = match metric {
261 crate::types::DistanceMetric::Cosine => 1.0 - dist,
262 crate::types::DistanceMetric::Euclidean => 1.0 / (1.0 + dist),
263 crate::types::DistanceMetric::DotProduct => 1.0 - dist,
264 };
265 ((*id).clone(), score)
266 })
267 .collect()
268 } else {
269 let n_probe = self.n_probe.min(self.centroids.len());
270 let centroid_indices = self.nearest_centroid_indices(query, n_probe);
271 centroid_indices
272 .iter()
273 .flat_map(|&ci| self.lists[ci].iter())
274 .filter(|(id, _)| !self.deleted.contains(id))
275 .map(|(id, v)| {
276 let dist = compute_distance(query, v, &self.metric);
277 (id.clone(), self.distance_to_score(dist))
278 })
279 .collect()
280 };
281
282 candidates
283 .sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
284 candidates.truncate(k);
285 Ok(candidates)
286 }
287
288 fn delete(&mut self, id: &VectorId) -> Result<()> {
289 self.deleted.insert(id.clone());
290 Ok(())
291 }
292
293 fn save(&self, path: &Path) -> Result<()> {
294 let json = serde_json::to_string(self)
295 .map_err(|e| VecDbError::SerializationError(e.to_string()))?;
296 std::fs::write(path, json)
297 .map_err(|e| VecDbError::StorageError(format!("ivf save failed: {e}")))?;
298 tracing::info!("IVF index saved to {:?}", path);
299 Ok(())
300 }
301
302 fn load_from(path: &Path, _config: &CollectionConfig) -> Result<Box<dyn IndexBackend>>
303 where
304 Self: Sized,
305 {
306 Ok(Box::new(Self::load_file(path)?))
307 }
308
309 fn len(&self) -> usize {
310 self.total_count().saturating_sub(self.deleted.len())
311 }
312
313 fn is_empty(&self) -> bool {
314 self.len() == 0
315 }
316
317 fn index_type(&self) -> IndexType {
318 IndexType::IVF
319 }
320
321 fn rebuild(&mut self, vectors: Vec<(VectorId, Vector)>) -> Result<()> {
322 self.build(vectors)
323 }
324}