1use roaring::RoaringBitmap;
13
14use crate::distance::{DistanceMetric, distance};
15use crate::error::{VectorError, check_dim};
16use crate::hnsw::SearchResult;
17use crate::hnsw::search::decode_filter_bitmap;
18
19pub const DEFAULT_FLAT_INDEX_THRESHOLD: usize = 10_000;
21
22pub struct FlatIndex {
24 dim: usize,
25 metric: DistanceMetric,
26 data: Vec<f32>,
28 deleted: Vec<bool>,
30 live_count: usize,
32}
33
34impl FlatIndex {
35 pub fn new(dim: usize, metric: DistanceMetric) -> Self {
37 Self {
38 dim,
39 metric,
40 data: Vec::new(),
41 deleted: Vec::new(),
42 live_count: 0,
43 }
44 }
45
46 pub fn insert(&mut self, vector: Vec<f32>) -> Result<u32, VectorError> {
50 check_dim(self.dim, vector.len())?;
51 let id = self.len() as u32;
52 self.data.extend_from_slice(&vector);
53 self.deleted.push(false);
54 self.live_count += 1;
55 Ok(id)
56 }
57
58 pub fn delete(&mut self, id: u32) -> bool {
60 let idx = id as usize;
61 if idx < self.deleted.len() && !self.deleted[idx] {
62 self.deleted[idx] = true;
63 self.live_count -= 1;
64 true
65 } else {
66 false
67 }
68 }
69
70 pub fn undelete(&mut self, id: u32) -> bool {
76 let idx = id as usize;
77 if idx < self.deleted.len() && self.deleted[idx] {
78 self.deleted[idx] = false;
79 self.live_count += 1;
80 true
81 } else {
82 false
83 }
84 }
85
86 pub fn search_with_metric(
89 &self,
90 query: &[f32],
91 top_k: usize,
92 metric: DistanceMetric,
93 ) -> Result<Vec<SearchResult>, VectorError> {
94 self.scan(query, top_k, metric, None)
95 }
96
97 pub fn search(&self, query: &[f32], top_k: usize) -> Result<Vec<SearchResult>, VectorError> {
99 self.scan(query, top_k, self.metric, None)
100 }
101
102 pub fn search_filtered(
104 &self,
105 query: &[f32],
106 top_k: usize,
107 bitmap: &[u8],
108 ) -> Result<Vec<SearchResult>, VectorError> {
109 self.search_filtered_offset(query, top_k, bitmap, 0)
110 }
111
112 pub fn search_filtered_offset_with_metric(
114 &self,
115 query: &[f32],
116 top_k: usize,
117 bitmap: &[u8],
118 id_offset: u32,
119 metric: DistanceMetric,
120 ) -> Result<Vec<SearchResult>, VectorError> {
121 let filter = decode_filter_bitmap(bitmap)?;
122 self.scan(query, top_k, metric, Some((&filter, id_offset)))
123 }
124
125 pub fn search_filtered_offset(
132 &self,
133 query: &[f32],
134 top_k: usize,
135 bitmap: &[u8],
136 id_offset: u32,
137 ) -> Result<Vec<SearchResult>, VectorError> {
138 self.search_filtered_offset_with_metric(query, top_k, bitmap, id_offset, self.metric)
139 }
140
141 fn scan(
144 &self,
145 query: &[f32],
146 top_k: usize,
147 metric: DistanceMetric,
148 filter: Option<(&RoaringBitmap, u32)>,
149 ) -> Result<Vec<SearchResult>, VectorError> {
150 check_dim(self.dim, query.len())?;
151 let n = self.len();
152 if n == 0 || top_k == 0 {
153 return Ok(Vec::new());
154 }
155
156 let mut candidates: Vec<SearchResult> = Vec::with_capacity(n.min(top_k * 2));
157 for i in 0..n {
158 if self.deleted[i] {
159 continue;
160 }
161 if let Some((bitmap, id_offset)) = filter
162 && !bitmap.contains((i as u32).saturating_add(id_offset))
163 {
164 continue;
165 }
166 let start = i * self.dim;
167 let vec_slice = &self.data[start..start + self.dim];
168 candidates.push(SearchResult {
169 id: i as u32,
170 distance: distance(query, vec_slice, metric),
171 });
172 }
173
174 if candidates.len() > top_k {
175 candidates.select_nth_unstable_by(top_k, |a, b| {
176 a.distance
177 .partial_cmp(&b.distance)
178 .unwrap_or(std::cmp::Ordering::Equal)
179 });
180 candidates.truncate(top_k);
181 }
182 candidates.sort_by(|a, b| {
183 a.distance
184 .partial_cmp(&b.distance)
185 .unwrap_or(std::cmp::Ordering::Equal)
186 });
187 Ok(candidates)
188 }
189
190 pub fn len(&self) -> usize {
191 self.deleted.len()
192 }
193
194 pub fn truncate(&mut self, len: usize) {
197 if len >= self.deleted.len() {
198 return;
199 }
200 let dropped_live = self.deleted[len..]
201 .iter()
202 .filter(|deleted| !**deleted)
203 .count();
204 self.live_count -= dropped_live;
205 self.deleted.truncate(len);
206 self.data.truncate(len * self.dim);
207 }
208
209 pub fn live_count(&self) -> usize {
210 self.live_count
211 }
212
213 pub fn is_empty(&self) -> bool {
214 self.live_count == 0
215 }
216
217 pub fn get_vector(&self, id: u32) -> Option<&[f32]> {
218 let idx = id as usize;
219 if idx < self.deleted.len() && !self.deleted[idx] {
220 let start = idx * self.dim;
221 Some(&self.data[start..start + self.dim])
222 } else {
223 None
224 }
225 }
226
227 pub fn get_vector_raw(&self, id: u32) -> Option<&[f32]> {
229 let idx = id as usize;
230 if idx < self.deleted.len() {
231 let start = idx * self.dim;
232 Some(&self.data[start..start + self.dim])
233 } else {
234 None
235 }
236 }
237
238 pub fn is_deleted(&self, id: u32) -> bool {
240 let idx = id as usize;
241 idx < self.deleted.len() && self.deleted[idx]
242 }
243
244 pub fn insert_tombstoned(&mut self, vector: Vec<f32>) -> Result<u32, VectorError> {
246 check_dim(self.dim, vector.len())?;
247 let id = self.len() as u32;
248 self.data.extend_from_slice(&vector);
249 self.deleted.push(true);
250 Ok(id)
252 }
253
254 pub fn dim(&self) -> usize {
255 self.dim
256 }
257
258 pub fn metric(&self) -> DistanceMetric {
259 self.metric
260 }
261
262 pub fn tombstone_count(&self) -> usize {
263 self.len().saturating_sub(self.live_count)
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn insert_and_search() {
273 let mut idx = FlatIndex::new(3, DistanceMetric::L2);
274 for i in 0..100u32 {
275 idx.insert(vec![i as f32, 0.0, 0.0]).unwrap();
276 }
277 assert_eq!(idx.len(), 100);
278 assert_eq!(idx.live_count(), 100);
279
280 let results = idx.search(&[50.0, 0.0, 0.0], 3).unwrap();
281 assert_eq!(results.len(), 3);
282 assert_eq!(results[0].id, 50);
283 assert!(results[0].distance < 0.01);
284 }
285
286 #[test]
287 fn delete_excludes_from_search() {
288 let mut idx = FlatIndex::new(2, DistanceMetric::L2);
289 idx.insert(vec![0.0, 0.0]).unwrap();
290 idx.insert(vec![1.0, 0.0]).unwrap();
291 idx.insert(vec![2.0, 0.0]).unwrap();
292
293 assert!(idx.delete(1));
294 assert_eq!(idx.live_count(), 2);
295
296 let results = idx.search(&[1.0, 0.0], 3).unwrap();
297 assert_eq!(results.len(), 2);
298 assert!(results.iter().all(|r| r.id != 1));
299 }
300
301 #[test]
302 fn exact_results() {
303 let mut idx = FlatIndex::new(2, DistanceMetric::Cosine);
304 idx.insert(vec![1.0, 0.0]).unwrap();
305 idx.insert(vec![0.0, 1.0]).unwrap();
306 idx.insert(vec![1.0, 1.0]).unwrap();
307
308 let results = idx.search(&[1.0, 0.0], 1).unwrap();
309 assert_eq!(results.len(), 1);
310 assert_eq!(results[0].id, 0);
311 }
312
313 #[test]
314 fn empty_search() {
315 let idx = FlatIndex::new(3, DistanceMetric::L2);
316 let results = idx.search(&[1.0, 0.0, 0.0], 5).unwrap();
317 assert!(results.is_empty());
318 }
319
320 #[test]
321 fn filtered_search() {
322 let mut idx = FlatIndex::new(2, DistanceMetric::L2);
323 for i in 0..8u32 {
324 idx.insert(vec![i as f32, 0.0]).unwrap();
325 }
326 let filter: RoaringBitmap = [2u32, 3, 6, 7].into_iter().collect();
327 let mut bitmap = Vec::new();
328 filter.serialize_into(&mut bitmap).unwrap();
329 let results = idx.search_filtered(&[4.0, 0.0], 2, &bitmap).unwrap();
330 assert_eq!(results.len(), 2);
331 assert_eq!(results[0].id, 3);
332 assert!(results.iter().all(|r| filter.contains(r.id)), "{results:?}");
333 }
334
335 #[test]
336 fn wrong_dimension_is_a_typed_error() {
337 let mut idx = FlatIndex::new(2, DistanceMetric::L2);
338 assert!(matches!(
339 idx.insert(vec![1.0, 2.0, 3.0]),
340 Err(VectorError::DimensionMismatch {
341 expected: 2,
342 got: 3
343 })
344 ));
345 assert!(matches!(
346 idx.insert_tombstoned(vec![1.0]),
347 Err(VectorError::DimensionMismatch {
348 expected: 2,
349 got: 1
350 })
351 ));
352 idx.insert(vec![1.0, 0.0]).unwrap();
353 let filter: RoaringBitmap = [0u32].into_iter().collect();
354 let mut bitmap = Vec::new();
355 filter.serialize_into(&mut bitmap).unwrap();
356 let short = [1.0_f32];
357 for result in [
358 idx.search(&short, 1),
359 idx.search_with_metric(&short, 1, DistanceMetric::Cosine),
360 idx.search_filtered(&short, 1, &bitmap),
361 idx.search_filtered_offset(&short, 1, &bitmap, 0),
362 idx.search_filtered_offset_with_metric(&short, 1, &bitmap, 0, DistanceMetric::L2),
363 ] {
364 assert!(
365 matches!(
366 result,
367 Err(VectorError::DimensionMismatch {
368 expected: 2,
369 got: 1
370 })
371 ),
372 "{result:?}"
373 );
374 }
375 }
376
377 #[test]
378 fn undecodable_filter_bitmap_is_a_typed_error() {
379 let mut idx = FlatIndex::new(2, DistanceMetric::L2);
380 idx.insert(vec![1.0, 0.0]).unwrap();
381 let result = idx.search_filtered(&[1.0, 0.0], 1, &[0b1100_1100]);
382 assert!(
383 matches!(result, Err(VectorError::InvalidFilterBitmap { .. })),
384 "{result:?}"
385 );
386 }
387}