1use std::collections::BTreeMap;
14use std::sync::Arc;
15
16use uqa_core::{DocId, Payload, PostingEntry, PostingList};
17
18use crate::{StorageBackendError, StorageBackendResult};
19
20mod canonical;
21mod collection;
22mod config;
23mod cosine;
24mod memory_snapshot;
25pub mod query;
26pub use query::statistics::{DiskANNExecutionRoute, DiskANNExecutionStats, VectorQueryResult};
27pub(crate) mod retained;
28
29pub(crate) use canonical::{
30 canonical_vectors_equal, copy_vector, ordinal_count, selected_fingerprint,
31};
32pub use canonical::{decode_vector_bytes, SelectedVectorRead, VectorRead, VectorReadSnapshot};
33pub use collection::{
34 RetainedVectorIndexesBuilder, VectorIndexSource, VectorIndexes, VectorIndexesIter,
35};
36pub use config::{
37 DiskANNAlpha, DiskANNIndexParams, HNSWIndexParams, IVFIndexParams, VectorIndexOpenMode,
38 VectorIndexSpec,
39};
40pub use cosine::cosine_similarity;
41pub(crate) use cosine::cosine_similarity_controlled;
42pub use retained::{RetainedVectorIndex, RetainedVectorIndexBuilder};
43
44#[cfg(test)]
45mod physical_snapshots;
46
47pub fn validate_vector_values(dimensions: u32, vector: &[f32]) -> StorageBackendResult<()> {
48 validate_vector_values_controlled(dimensions, vector, None)
49}
50
51pub fn validate_vector_values_controlled(
53 dimensions: u32,
54 vector: &[f32],
55 control: Option<&crate::read_control::StorageReadControl>,
56) -> StorageBackendResult<()> {
57 if let Some(control) = control {
58 control.check()?;
59 }
60 let dimensions = usize::try_from(dimensions).map_err(|_| {
61 StorageBackendError::Other(format!(
62 "vector dimension {dimensions} exceeds the platform usize range"
63 ))
64 })?;
65 if vector.len() != dimensions {
66 return Err(StorageBackendError::Other(format!(
67 "vector dimension mismatch: expected {dimensions}, got {}",
68 vector.len()
69 )));
70 }
71 for (index, value) in vector.iter().copied().enumerate() {
72 if index.is_multiple_of(1024) {
73 if let Some(control) = control {
74 control.check()?;
75 }
76 }
77 if !value.is_finite() {
78 return Err(StorageBackendError::Other(format!(
79 "vector component {index} must be finite, got {value}"
80 )));
81 }
82 }
83 Ok(())
84}
85
86fn checked_vector_count(counts: impl IntoIterator<Item = usize>) -> StorageBackendResult<usize> {
87 counts.into_iter().try_fold(0_usize, |total, count| {
88 total
89 .checked_add(count)
90 .ok_or_else(|| StorageBackendError::Other("vector count overflow".into()))
91 })
92}
93
94pub(crate) fn validate_threshold(threshold: f32) -> StorageBackendResult<()> {
95 if threshold.is_finite() {
96 Ok(())
97 } else {
98 Err(StorageBackendError::Other(format!(
99 "vector similarity threshold must be finite, got {threshold}"
100 )))
101 }
102}
103
104pub fn select_top_k_scored(scored: &mut Vec<(DocId, f32)>, k: usize) {
105 if scored.len() > k {
106 scored.select_nth_unstable_by(k, |a, b| b.1.total_cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
107 scored.truncate(k);
108 }
109}
110
111pub(crate) fn deduplicate_scored_values(scored: &mut [(DocId, f32)]) -> usize {
115 if scored.len() < 2 {
116 return scored.len();
117 }
118 scored.sort_unstable_by_key(|(doc_id, _)| *doc_id);
119 let mut write = 1;
120 for read in 1..scored.len() {
121 let (doc_id, score) = scored[read];
122 if scored[write - 1].0 == doc_id {
123 scored[write - 1].1 = scored[write - 1].1.max(score);
124 } else {
125 scored[write] = (doc_id, score);
126 write += 1;
127 }
128 }
129 write
130}
131
132pub(crate) fn vector_norm(vector: &[f32]) -> f32 {
133 let mut squared_norm = 0.0_f32;
134 for value in vector {
135 squared_norm += value * value;
136 }
137 squared_norm.sqrt()
138}
139
140pub(crate) fn cosine_similarity_with_norms(a: &[f32], b: &[f32], norm_a: f32, norm_b: f32) -> f32 {
144 if a.len() != b.len() || a.is_empty() || norm_a == 0.0 || norm_b == 0.0 {
145 return 0.0;
146 }
147 let mut dot = 0.0_f32;
148 for (x, y) in a.iter().zip(b) {
149 dot += x * y;
150 }
151 dot / (norm_a * norm_b)
152}
153
154pub trait VectorIndex: Send + Sync {
155 fn dimensions(&self) -> u32;
156 fn index_kind(&self) -> &'static str {
157 "vector"
158 }
159 fn diskann_query_metadata(
161 &self,
162 control: &crate::read_control::StorageReadControl,
163 ) -> StorageBackendResult<Option<crate::diskann_index::DiskANNQueryMetadata>> {
164 control.check()?;
165 Ok(None)
166 }
167 fn add(&mut self, doc_id: DocId, vector: Vec<f32>) -> StorageBackendResult<()>;
168 fn add_many(&mut self, doc_id: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()>;
169 fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()>;
170 fn clear(&mut self) -> StorageBackendResult<()>;
171 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList>;
172 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList>;
173 fn search_knn_with_control(
175 &self,
176 query: &[f32],
177 k: usize,
178 control: &crate::read_control::StorageReadControl,
179 ) -> StorageBackendResult<PostingList> {
180 control.check()?;
181 self.search_knn(query, k)
182 }
183 fn search_threshold_with_control(
185 &self,
186 query: &[f32],
187 threshold: f32,
188 control: &crate::read_control::StorageReadControl,
189 ) -> StorageBackendResult<PostingList> {
190 control.check()?;
191 self.search_threshold(query, threshold)
192 }
193 fn search_knn_with_statistics(
195 &self,
196 query: &[f32],
197 k: usize,
198 control: Option<&crate::read_control::StorageReadControl>,
199 ) -> StorageBackendResult<VectorQueryResult> {
200 match control {
201 Some(control) => self.search_knn_with_control(query, k, control),
202 None => self.search_knn(query, k),
203 }
204 .map(VectorQueryResult::unmeasured)
205 }
206 fn search_threshold_with_statistics(
208 &self,
209 query: &[f32],
210 threshold: f32,
211 control: Option<&crate::read_control::StorageReadControl>,
212 ) -> StorageBackendResult<VectorQueryResult> {
213 match control {
214 Some(control) => self.search_threshold_with_control(query, threshold, control),
215 None => self.search_threshold(query, threshold),
216 }
217 .map(VectorQueryResult::unmeasured)
218 }
219 fn count(&self) -> StorageBackendResult<usize>;
220
221 fn contains_document(&self, _doc_id: DocId) -> StorageBackendResult<bool> {
223 Err(StorageBackendError::Other(
224 "canonical vector membership is not supported by this backend".into(),
225 ))
226 }
227
228 fn initialize(&mut self) -> StorageBackendResult<()> {
233 Ok(())
234 }
235
236 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>>;
238
239 fn snapshot_with_control(
241 &self,
242 control: &crate::read_control::StorageReadControl,
243 ) -> StorageBackendResult<Arc<dyn VectorIndex>> {
244 control.check()?;
245 self.snapshot()
246 }
247
248 fn diskann_read_snapshot(
250 &self,
251 control: &crate::read_control::StorageReadControl,
252 ) -> StorageBackendResult<Option<crate::diskann_index::DiskANNReadSnapshot>> {
253 control.check()?;
254 Ok(None)
255 }
256
257 fn vector_read_snapshot(
259 &self,
260 control: &crate::read_control::StorageReadControl,
261 ) -> StorageBackendResult<Option<VectorReadSnapshot>> {
262 self.diskann_read_snapshot(control)?
263 .map(|source| {
264 uqa_core::memory::Budgeted::new(source, control.memory().empty_reservation())
265 .into_shared()
266 .map(|source| source as VectorReadSnapshot)
267 .map_err(Into::into)
268 })
269 .transpose()
270 }
271
272 fn matches_document_vectors(
274 &self,
275 document: DocId,
276 vectors: &[Vec<f32>],
277 control: &crate::read_control::StorageReadControl,
278 ) -> StorageBackendResult<bool> {
279 self.vector_read_snapshot(control)?
280 .map_or(Ok(false), |source| {
281 canonical_vectors_equal(source.as_ref(), document, vectors, control)
282 })
283 }
284
285 fn snapshot_with_vector_read(
287 &self,
288 _source: VectorReadSnapshot,
289 control: &crate::read_control::StorageReadControl,
290 ) -> StorageBackendResult<Option<Arc<dyn VectorIndex>>> {
291 control.check()?;
292 Ok(None)
293 }
294
295 fn snapshot_with_diskann_changes(
297 &self,
298 _changes: &crate::diskann_index::DiskANNReadChanges,
299 control: &crate::read_control::StorageReadControl,
300 ) -> StorageBackendResult<Option<Arc<dyn VectorIndex>>> {
301 control.check()?;
302 Ok(None)
303 }
304
305 fn writable_snapshot(&self) -> StorageBackendResult<Box<dyn VectorIndex>> {
309 Err(StorageBackendError::Other(
310 "writable vector-index snapshots are not supported by this backend".into(),
311 ))
312 }
313}
314
315#[derive(Debug, Clone)]
316pub struct MemoryVectorIndex {
317 dimensions: u32,
318 vectors: BTreeMap<DocId, Vec<Vec<f32>>>,
319}
320
321impl MemoryVectorIndex {
322 pub fn new(dimensions: u32) -> Self {
323 Self {
324 dimensions,
325 vectors: BTreeMap::new(),
326 }
327 }
328
329 pub fn vectors(&self) -> &BTreeMap<DocId, Vec<Vec<f32>>> {
330 &self.vectors
331 }
332}
333
334impl VectorIndex for MemoryVectorIndex {
335 fn contains_document(&self, doc_id: DocId) -> StorageBackendResult<bool> {
336 Ok(self.vectors.contains_key(&doc_id))
337 }
338
339 fn dimensions(&self) -> u32 {
340 self.dimensions
341 }
342
343 fn index_kind(&self) -> &'static str {
344 "memory-bruteforce"
345 }
346
347 fn add(&mut self, doc_id: DocId, vector: Vec<f32>) -> StorageBackendResult<()> {
348 self.validate_dimensions(&vector)?;
349 self.vectors.insert(doc_id, vec![vector]);
350 Ok(())
351 }
352
353 fn add_many(&mut self, doc_id: DocId, vectors: Vec<Vec<f32>>) -> StorageBackendResult<()> {
354 for vector in &vectors {
355 self.validate_dimensions(vector)?;
356 }
357 if vectors.is_empty() {
358 self.vectors.remove(&doc_id);
359 } else {
360 self.vectors.insert(doc_id, vectors);
361 }
362 Ok(())
363 }
364
365 fn delete(&mut self, doc_id: DocId) -> StorageBackendResult<()> {
366 self.vectors.remove(&doc_id);
367 Ok(())
368 }
369
370 fn clear(&mut self) -> StorageBackendResult<()> {
371 self.vectors.clear();
372 Ok(())
373 }
374
375 fn search_knn(&self, query: &[f32], k: usize) -> StorageBackendResult<PostingList> {
377 self.validate_dimensions(query)?;
378 if k == 0 || self.vectors.is_empty() {
379 return Ok(PostingList::new());
380 }
381 let mut scored: Vec<(DocId, f32)> = self
382 .vectors
383 .iter()
384 .filter_map(|(&doc_id, vectors)| best_vector_score(query, vectors).map(|s| (doc_id, s)))
385 .collect();
386 select_top_k_scored(&mut scored, k);
387 scored.sort_by_key(|(id, _)| *id);
390 let entries = scored
391 .into_iter()
392 .map(|(doc_id, sim)| PostingEntry::new(doc_id, Payload::with_score(f64::from(sim))))
393 .collect::<Vec<_>>();
394 Ok(PostingList::from_sorted_unchecked(entries))
395 }
396
397 fn search_threshold(&self, query: &[f32], threshold: f32) -> StorageBackendResult<PostingList> {
399 self.validate_dimensions(query)?;
400 validate_threshold(threshold)?;
401 let mut entries: Vec<PostingEntry> = self
402 .vectors
403 .iter()
404 .filter_map(|(&doc_id, vectors)| {
405 let sim = best_vector_score(query, vectors)?;
406 if sim >= threshold {
407 Some(PostingEntry::new(
408 doc_id,
409 Payload::with_score(f64::from(sim)),
410 ))
411 } else {
412 None
413 }
414 })
415 .collect();
416 entries.sort_by_key(|e| e.doc_id);
419 Ok(PostingList::from_sorted_unchecked(entries))
420 }
421
422 fn count(&self) -> StorageBackendResult<usize> {
423 checked_vector_count(self.vectors.values().map(Vec::len))
424 }
425
426 fn snapshot(&self) -> StorageBackendResult<Arc<dyn VectorIndex>> {
427 Ok(Arc::new(self.clone()))
428 }
429
430 fn snapshot_with_control(
431 &self,
432 control: &crate::read_control::StorageReadControl,
433 ) -> StorageBackendResult<Arc<dyn VectorIndex>> {
434 memory_snapshot::capture(self, control)
435 }
436
437 fn writable_snapshot(&self) -> StorageBackendResult<Box<dyn VectorIndex>> {
438 Ok(Box::new(self.clone()))
439 }
440}
441
442impl MemoryVectorIndex {
443 fn validate_dimensions(&self, vector: &[f32]) -> StorageBackendResult<()> {
444 validate_vector_values(self.dimensions, vector)
445 }
446}
447
448fn best_vector_score(query: &[f32], vectors: &[Vec<f32>]) -> Option<f32> {
449 vectors
450 .iter()
451 .map(|vector| cosine_similarity(query, vector))
452 .max_by(f32::total_cmp)
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
460 fn canonical_membership_tracks_tensor_replacement_and_retained_snapshots() {
461 let indexes: [Box<dyn VectorIndex>; 3] = [
462 Box::new(MemoryVectorIndex::new(2)),
463 Box::new(crate::IVFIndex::new(2)),
464 Box::new(crate::HNSWIndex::new(2)),
465 ];
466 for mut index in indexes {
467 assert!(!index.contains_document(1).unwrap());
468 index
469 .add_many(1, vec![vec![1.0, 0.0], vec![0.0, 1.0]])
470 .unwrap();
471 let retained = index.snapshot().unwrap();
472 assert!(index.contains_document(1).unwrap());
473 assert!(!index.contains_document(2).unwrap());
474 index.add_many(1, Vec::new()).unwrap();
475 assert!(!index.contains_document(1).unwrap());
476 assert!(retained.contains_document(1).unwrap());
477 index.add(1, vec![0.5, 0.5]).unwrap();
478 index.delete(1).unwrap();
479 assert!(!index.contains_document(1).unwrap());
480 }
481 }
482
483 fn approx_eq(a: f32, b: f32, eps: f32) {
484 assert!((a - b).abs() < eps, "expected {a} ~ {b} within {eps}");
485 }
486
487 #[test]
488 fn cosine_identity_is_one() {
489 let v = vec![1.0, 2.0, 3.0];
490 approx_eq(cosine_similarity(&v, &v), 1.0, 1e-6);
491 }
492
493 #[test]
494 fn vector_count_overflow_is_reported() {
495 let error = checked_vector_count([usize::MAX, 1]).unwrap_err();
496 assert!(error.to_string().contains("vector count overflow"));
497 }
498
499 #[test]
500 fn cosine_orthogonal_is_zero() {
501 let a = vec![1.0, 0.0];
502 let b = vec![0.0, 1.0];
503 approx_eq(cosine_similarity(&a, &b), 0.0, 1e-6);
504 }
505
506 #[test]
507 fn cosine_zero_norm_is_zero() {
508 let a = vec![0.0, 0.0];
509 let b = vec![1.0, 1.0];
510 approx_eq(cosine_similarity(&a, &b), 0.0, 1e-6);
511 }
512
513 #[test]
514 fn knn_orders_by_similarity_descending_then_doc_id() {
515 let mut idx = MemoryVectorIndex::new(2);
516 idx.add(1, vec![1.0, 0.0]).unwrap();
517 idx.add(2, vec![0.5, 0.5]).unwrap();
518 idx.add(3, vec![0.0, 1.0]).unwrap();
519 let pl = idx.search_knn(&[1.0, 0.0], 2).unwrap();
520 let docs: Vec<_> = pl.iter().map(|e| e.doc_id).collect();
521 assert_eq!(docs, vec![1, 2]);
523 let entry1 = pl.get_entry(1).unwrap();
524 let entry2 = pl.get_entry(2).unwrap();
525 assert!(entry1.payload.score > entry2.payload.score);
526 }
527
528 #[test]
529 fn partial_top_k_keeps_deterministic_doc_id_ties() {
530 let mut scored = vec![(10, 0.5), (3, 0.9), (1, 0.9), (8, 0.7), (2, 0.1)];
531 select_top_k_scored(&mut scored, 2);
532 scored.sort_by_key(|(doc_id, _)| *doc_id);
533 assert_eq!(scored, vec![(1, 0.9), (3, 0.9)]);
534 }
535
536 #[test]
537 fn precomputed_norm_cosine_matches_reference_bits() {
538 let a = [0.25, -3.0, 1.5, 8.0];
539 let b = [2.0, 0.75, -4.0, 0.5];
540 let expected = cosine_similarity(&a, &b);
541 let actual = cosine_similarity_with_norms(&a, &b, vector_norm(&a), vector_norm(&b));
542 assert_eq!(actual.to_bits(), expected.to_bits());
543 }
544
545 #[test]
546 fn score_deduplication_keeps_best_tensor_vector() {
547 let mut scored = vec![(7, 0.3), (2, 0.8), (7, 0.9), (2, 0.4), (9, -0.2)];
548 let count = deduplicate_scored_values(&mut scored);
549 scored.truncate(count);
550 assert_eq!(scored, vec![(2, 0.8), (7, 0.9), (9, -0.2)]);
551 }
552
553 #[test]
554 fn threshold_filters_below_cutoff() {
555 let mut idx = MemoryVectorIndex::new(2);
556 idx.add(1, vec![1.0, 0.0]).unwrap();
557 idx.add(2, vec![0.5, 0.5]).unwrap();
558 idx.add(3, vec![0.0, 1.0]).unwrap();
559 let pl = idx.search_threshold(&[1.0, 0.0], 0.5).unwrap();
560 let docs: Vec<_> = pl.iter().map(|e| e.doc_id).collect();
561 assert_eq!(docs, vec![1, 2]);
562 }
563
564 #[test]
565 fn delete_removes_vector() {
566 let mut idx = MemoryVectorIndex::new(2);
567 idx.add(1, vec![1.0, 0.0]).unwrap();
568 idx.delete(1).unwrap();
569 assert_eq!(idx.count().unwrap(), 0);
570 }
571
572 #[test]
573 fn non_finite_vectors_queries_and_thresholds_are_errors() {
574 let mut idx = MemoryVectorIndex::new(2);
575 assert!(idx.add(1, vec![f32::NAN, 0.0]).is_err());
576 idx.add(1, vec![1.0, 0.0]).unwrap();
577 assert!(idx.search_knn(&[f32::INFINITY, 0.0], 1).is_err());
578 assert!(idx
579 .search_threshold(&[1.0, 0.0], f32::NEG_INFINITY)
580 .is_err());
581 }
582}