1use crate::Vector;
4
5pub use crate::VectorIndex;
7use anyhow::{anyhow, Result};
8use oxirs_core::parallel::*;
9use oxirs_core::Triple;
10use serde::{Deserialize, Serialize};
11use std::cmp::Ordering;
12use std::collections::{BinaryHeap, HashMap};
13use std::sync::Arc;
14
15use crate::hnsw::{HnswConfig, HnswIndex};
16use crate::ivf::{IvfConfig, IvfIndex};
17use crate::pq::{PQConfig, PQIndex};
18
19pub type FilterFunction = Box<dyn Fn(&str) -> bool>;
21pub type FilterFunctionSync = Box<dyn Fn(&str) -> bool + Send + Sync>;
23
24#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
26pub struct IndexConfig {
27 pub index_type: IndexType,
29 pub max_connections: usize,
31 pub ef_construction: usize,
33 pub ef_search: usize,
35 pub distance_metric: DistanceMetric,
37 pub parallel: bool,
39}
40
41impl Default for IndexConfig {
42 fn default() -> Self {
43 Self {
44 index_type: IndexType::Hnsw,
45 max_connections: 16,
46 ef_construction: 200,
47 ef_search: 50,
48 distance_metric: DistanceMetric::Cosine,
49 parallel: true,
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
56pub enum IndexType {
57 Hnsw,
59 Flat,
61 Ivf,
63 PQ,
65}
66
67#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
69pub enum DistanceMetric {
70 Cosine,
72 Euclidean,
74 Manhattan,
76 DotProduct,
78}
79
80impl DistanceMetric {
81 pub fn distance(&self, a: &[f32], b: &[f32]) -> f32 {
83 use oxirs_core::simd::SimdOps;
84
85 match self {
86 DistanceMetric::Cosine => f32::cosine_distance(a, b),
87 DistanceMetric::Euclidean => f32::euclidean_distance(a, b),
88 DistanceMetric::Manhattan => f32::manhattan_distance(a, b),
89 DistanceMetric::DotProduct => -f32::dot(a, b), }
91 }
92
93 pub fn distance_vectors(&self, a: &Vector, b: &Vector) -> f32 {
95 let a_f32 = a.as_f32();
96 let b_f32 = b.as_f32();
97 self.distance(&a_f32, &b_f32)
98 }
99}
100
101#[derive(Debug, Clone, PartialEq)]
103pub struct SearchResult {
104 pub uri: String,
105 pub distance: f32,
106 pub score: f32,
107 pub metadata: Option<HashMap<String, String>>,
108}
109
110impl Eq for SearchResult {}
111
112impl Ord for SearchResult {
113 fn cmp(&self, other: &Self) -> Ordering {
114 self.distance
115 .partial_cmp(&other.distance)
116 .unwrap_or(Ordering::Equal)
117 }
118}
119
120impl PartialOrd for SearchResult {
121 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
122 Some(self.cmp(other))
123 }
124}
125
126pub struct AdvancedVectorIndex {
128 config: IndexConfig,
129 vectors: Vec<(String, Vector)>,
130 uri_to_id: HashMap<String, usize>,
131 hnsw_index: Option<HnswIndex>,
132 ivf_index: Option<IvfIndex>,
134 pq_index: Option<PQIndex>,
136 dimensions: Option<usize>,
137}
138
139impl AdvancedVectorIndex {
140 pub fn new(config: IndexConfig) -> Self {
141 Self {
142 config,
143 vectors: Vec::new(),
144 uri_to_id: HashMap::new(),
145 hnsw_index: None,
146 ivf_index: None,
147 pq_index: None,
148 dimensions: None,
149 }
150 }
151
152 pub fn build(&mut self) -> Result<()> {
154 if self.vectors.is_empty() {
155 return Ok(());
156 }
157
158 match self.config.index_type {
159 IndexType::Hnsw => {
160 self.build_hnsw_index()?;
161 }
162 IndexType::Flat => {
163 }
165 IndexType::Ivf => {
166 self.build_ivf_index()?;
167 }
168 IndexType::PQ => {
169 self.build_pq_index()?;
170 }
171 }
172
173 Ok(())
174 }
175
176 fn build_hnsw_index(&mut self) -> Result<()> {
177 if self.dimensions.is_some() {
178 let hnsw_config = HnswConfig {
179 m: self.config.max_connections,
180 m_l0: self.config.max_connections * 2,
181 ef_construction: self.config.ef_construction,
182 ef: self.config.ef_search,
183 ..HnswConfig::default()
184 };
185
186 let mut hnsw = HnswIndex::new_cpu_only(hnsw_config);
187
188 for (uri, vector) in &self.vectors {
189 hnsw.insert(uri.clone(), vector.clone())?;
190 }
191
192 self.hnsw_index = Some(hnsw);
193 }
194
195 Ok(())
196 }
197
198 fn build_ivf_index(&mut self) -> Result<()> {
204 let training_vectors: Vec<Vector> = self.vectors.iter().map(|(_, v)| v.clone()).collect();
205 let n_clusters = (self.vectors.len() / 4).clamp(2, 256);
206
207 let config = IvfConfig {
208 n_clusters,
209 n_probes: (n_clusters / 8).max(1),
210 ..Default::default()
211 };
212 let mut ivf = IvfIndex::new(config)?;
213 ivf.train(&training_vectors)?;
214
215 for (uri, vector) in &self.vectors {
216 ivf.insert(uri.clone(), vector.clone())?;
217 }
218
219 self.ivf_index = Some(ivf);
220 Ok(())
221 }
222
223 fn build_pq_index(&mut self) -> Result<()> {
229 let dims = self
230 .dimensions
231 .ok_or_else(|| anyhow!("Cannot build PQ index: no vectors have been inserted yet"))?;
232
233 let n_subquantizers = [8usize, 4, 2, 1]
235 .iter()
236 .copied()
237 .find(|&s| dims % s == 0)
238 .unwrap_or(1);
239
240 let config = PQConfig {
241 n_subquantizers,
242 n_centroids: 16, ..Default::default()
244 };
245 let mut pq = PQIndex::new(config);
246 let training_vectors: Vec<Vector> = self.vectors.iter().map(|(_, v)| v.clone()).collect();
247 pq.train(&training_vectors)?;
248
249 for (uri, vector) in &self.vectors {
250 pq.insert(uri.clone(), vector.clone())?;
251 }
252
253 self.pq_index = Some(pq);
254 Ok(())
255 }
256
257 pub fn add_metadata(&mut self, _uri: &str, _metadata: HashMap<String, String>) -> Result<()> {
259 Ok(())
262 }
263
264 pub fn search_advanced(
266 &self,
267 query: &Vector,
268 k: usize,
269 _ef: Option<usize>,
270 filter: Option<FilterFunction>,
271 ) -> Result<Vec<SearchResult>> {
272 match self.config.index_type {
273 IndexType::Hnsw => self.search_hnsw(query, k),
274 IndexType::Ivf => self.search_ivf(query, k),
275 IndexType::PQ => self.search_pq(query, k),
276 IndexType::Flat => self.search_flat(query, k, filter),
277 }
278 }
279
280 fn search_hnsw(&self, query: &Vector, k: usize) -> Result<Vec<SearchResult>> {
281 if let Some(ref hnsw) = self.hnsw_index {
282 let results = hnsw.search_knn(query, k)?;
283
284 Ok(results
287 .into_iter()
288 .map(|(uri, similarity)| SearchResult {
289 uri,
290 distance: 1.0 - similarity,
291 score: similarity,
292 metadata: None,
293 })
294 .collect())
295 } else {
296 Err(anyhow!("HNSW index not built"))
297 }
298 }
299
300 fn search_ivf(&self, query: &Vector, k: usize) -> Result<Vec<SearchResult>> {
301 let ivf = self
302 .ivf_index
303 .as_ref()
304 .ok_or_else(|| anyhow!("IVF index not built — call build() first"))?;
305 let results = ivf.search_knn(query, k)?;
306 Ok(results
308 .into_iter()
309 .map(|(uri, similarity)| SearchResult {
310 uri,
311 distance: 1.0 - similarity,
312 score: similarity,
313 metadata: None,
314 })
315 .collect())
316 }
317
318 fn search_pq(&self, query: &Vector, k: usize) -> Result<Vec<SearchResult>> {
319 let pq = self
320 .pq_index
321 .as_ref()
322 .ok_or_else(|| anyhow!("PQ index not built — call build() first"))?;
323 let results = pq.search_knn(query, k)?;
324 Ok(results
326 .into_iter()
327 .map(|(uri, similarity)| SearchResult {
328 uri,
329 distance: 1.0 - similarity,
330 score: similarity,
331 metadata: None,
332 })
333 .collect())
334 }
335
336 fn search_flat(
337 &self,
338 query: &Vector,
339 k: usize,
340 filter: Option<FilterFunction>,
341 ) -> Result<Vec<SearchResult>> {
342 if self.config.parallel && self.vectors.len() > 1000 {
343 if filter.is_some() {
345 self.search_flat_sequential(query, k, filter)
347 } else {
348 self.search_flat_parallel(query, k, None)
349 }
350 } else {
351 self.search_flat_sequential(query, k, filter)
352 }
353 }
354
355 fn search_flat_sequential(
356 &self,
357 query: &Vector,
358 k: usize,
359 filter: Option<FilterFunction>,
360 ) -> Result<Vec<SearchResult>> {
361 let mut heap = BinaryHeap::new();
362
363 for (uri, vector) in &self.vectors {
364 if let Some(ref filter_fn) = filter {
365 if !filter_fn(uri) {
366 continue;
367 }
368 }
369
370 let distance = self.config.distance_metric.distance_vectors(query, vector);
371
372 if heap.len() < k {
373 heap.push(std::cmp::Reverse(SearchResult {
374 uri: uri.clone(),
375 distance,
376 score: 1.0 - distance, metadata: None,
378 }));
379 } else if let Some(std::cmp::Reverse(worst)) = heap.peek() {
380 if distance < worst.distance {
381 heap.pop();
382 heap.push(std::cmp::Reverse(SearchResult {
383 uri: uri.clone(),
384 distance,
385 score: 1.0 - distance, metadata: None,
387 }));
388 }
389 }
390 }
391
392 let mut results: Vec<SearchResult> = heap.into_iter().map(|r| r.0).collect();
393 results.sort_by(|a, b| {
394 a.distance
395 .partial_cmp(&b.distance)
396 .unwrap_or(std::cmp::Ordering::Equal)
397 });
398
399 Ok(results)
400 }
401
402 fn search_flat_parallel(
403 &self,
404 query: &Vector,
405 k: usize,
406 filter: Option<FilterFunctionSync>,
407 ) -> Result<Vec<SearchResult>> {
408 let chunk_size = (self.vectors.len() / num_threads()).max(100);
410
411 let filter_arc = filter.map(Arc::new);
413
414 let partial_results: Vec<Vec<SearchResult>> = self
416 .vectors
417 .par_chunks(chunk_size)
418 .map(|chunk| {
419 let mut local_heap = BinaryHeap::new();
420 let filter_ref = filter_arc.as_ref();
421
422 for (uri, vector) in chunk {
423 if let Some(filter_fn) = filter_ref {
424 if !filter_fn(uri) {
425 continue;
426 }
427 }
428
429 let distance = self.config.distance_metric.distance_vectors(query, vector);
430
431 if local_heap.len() < k {
432 local_heap.push(std::cmp::Reverse(SearchResult {
433 uri: uri.clone(),
434 distance,
435 score: 1.0 - distance, metadata: None,
437 }));
438 } else if let Some(std::cmp::Reverse(worst)) = local_heap.peek() {
439 if distance < worst.distance {
440 local_heap.pop();
441 local_heap.push(std::cmp::Reverse(SearchResult {
442 uri: uri.clone(),
443 distance,
444 score: 1.0 - distance, metadata: None,
446 }));
447 }
448 }
449 }
450
451 local_heap
452 .into_sorted_vec()
453 .into_iter()
454 .map(|r| r.0)
455 .collect()
456 })
457 .collect();
458
459 let mut final_heap = BinaryHeap::new();
461 for partial in partial_results {
462 for result in partial {
463 if final_heap.len() < k {
464 final_heap.push(std::cmp::Reverse(result));
465 } else if let Some(std::cmp::Reverse(worst)) = final_heap.peek() {
466 if result.distance < worst.distance {
467 final_heap.pop();
468 final_heap.push(std::cmp::Reverse(result));
469 }
470 }
471 }
472 }
473
474 let mut results: Vec<SearchResult> = final_heap.into_iter().map(|r| r.0).collect();
475 results.sort_by(|a, b| {
476 a.distance
477 .partial_cmp(&b.distance)
478 .unwrap_or(std::cmp::Ordering::Equal)
479 });
480
481 Ok(results)
482 }
483
484 pub fn stats(&self) -> IndexStats {
486 IndexStats {
487 num_vectors: self.vectors.len(),
488 dimensions: self.dimensions.unwrap_or(0),
489 index_type: self.config.index_type,
490 memory_usage: self.estimate_memory_usage(),
491 }
492 }
493
494 fn estimate_memory_usage(&self) -> usize {
495 let vector_memory = self.vectors.len()
496 * (std::mem::size_of::<String>()
497 + self.dimensions.unwrap_or(0) * std::mem::size_of::<f32>());
498
499 let uri_map_memory =
500 self.uri_to_id.len() * (std::mem::size_of::<String>() + std::mem::size_of::<usize>());
501
502 vector_memory + uri_map_memory
503 }
504
505 pub fn len(&self) -> usize {
507 self.vectors.len()
508 }
509
510 pub fn is_empty(&self) -> bool {
512 self.vectors.is_empty()
513 }
514
515 pub fn add(
517 &mut self,
518 id: String,
519 vector: Vec<f32>,
520 _triple: Triple,
521 _metadata: HashMap<String, String>,
522 ) -> Result<()> {
523 let vector_obj = Vector::new(vector);
524 self.insert(id, vector_obj)
525 }
526
527 pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<SearchResult>> {
529 let query_vector = Vector::new(query.to_vec());
530 let results = self.search_advanced(&query_vector, k, None, None)?;
531 Ok(results)
532 }
533}
534
535impl VectorIndex for AdvancedVectorIndex {
536 fn insert(&mut self, uri: String, vector: Vector) -> Result<()> {
537 if let Some(dims) = self.dimensions {
538 if vector.dimensions != dims {
539 return Err(anyhow!(
540 "Vector dimensions ({}) don't match index dimensions ({})",
541 vector.dimensions,
542 dims
543 ));
544 }
545 } else {
546 self.dimensions = Some(vector.dimensions);
547 }
548
549 let id = self.vectors.len();
550 self.uri_to_id.insert(uri.clone(), id);
551 self.vectors.push((uri, vector));
552
553 Ok(())
554 }
555
556 fn search_knn(&self, query: &Vector, k: usize) -> Result<Vec<(String, f32)>> {
557 let results = self.search_advanced(query, k, None, None)?;
558 Ok(results.into_iter().map(|r| (r.uri, r.distance)).collect())
559 }
560
561 fn search_threshold(&self, query: &Vector, threshold: f32) -> Result<Vec<(String, f32)>> {
562 let mut results = Vec::new();
563
564 for (uri, vector) in &self.vectors {
565 let distance = self.config.distance_metric.distance_vectors(query, vector);
566 if distance <= threshold {
567 results.push((uri.clone(), distance));
568 }
569 }
570
571 results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
572 Ok(results)
573 }
574
575 fn get_vector(&self, uri: &str) -> Option<&Vector> {
576 self.vectors.iter().find(|(u, _)| u == uri).map(|(_, v)| v)
579 }
580}
581
582#[derive(Debug, Clone)]
584pub struct IndexStats {
585 pub num_vectors: usize,
586 pub dimensions: usize,
587 pub index_type: IndexType,
588 pub memory_usage: usize,
589}
590
591pub struct QuantizedVectorIndex {
593 config: IndexConfig,
594 quantized_vectors: Vec<Vec<u8>>,
595 centroids: Vec<Vector>,
596 uri_to_id: HashMap<String, usize>,
597 dimensions: Option<usize>,
598}
599
600impl QuantizedVectorIndex {
601 pub fn new(config: IndexConfig, num_centroids: usize) -> Self {
602 Self {
603 config,
604 quantized_vectors: Vec::new(),
605 centroids: Vec::with_capacity(num_centroids),
606 uri_to_id: HashMap::new(),
607 dimensions: None,
608 }
609 }
610
611 pub fn train_quantization(&mut self, training_vectors: &[Vector]) -> Result<()> {
613 if training_vectors.is_empty() {
614 return Err(anyhow!("No training vectors provided"));
615 }
616
617 let dimensions = training_vectors[0].dimensions;
618 self.dimensions = Some(dimensions);
619
620 self.centroids = kmeans_clustering(training_vectors, self.centroids.capacity())?;
622
623 Ok(())
624 }
625
626 fn quantize_vector(&self, vector: &Vector) -> Vec<u8> {
627 let mut quantized = Vec::new();
628
629 let chunk_size = vector.dimensions / self.centroids.len().max(1);
631
632 let vector_f32 = vector.as_f32();
633 for chunk in vector_f32.chunks(chunk_size) {
634 let mut best_centroid = 0u8;
635 let mut best_distance = f32::INFINITY;
636
637 for (i, centroid) in self.centroids.iter().enumerate() {
638 let centroid_f32 = centroid.as_f32();
639 let centroid_chunk = ¢roid_f32[0..chunk.len().min(centroid.dimensions)];
640 use oxirs_core::simd::SimdOps;
641 let distance = f32::euclidean_distance(chunk, centroid_chunk);
642 if distance < best_distance {
643 best_distance = distance;
644 best_centroid = i as u8;
645 }
646 }
647
648 quantized.push(best_centroid);
649 }
650
651 quantized
652 }
653}
654
655impl VectorIndex for QuantizedVectorIndex {
656 fn insert(&mut self, uri: String, vector: Vector) -> Result<()> {
657 if self.centroids.is_empty() {
658 return Err(anyhow!(
659 "Quantization not trained. Call train_quantization first."
660 ));
661 }
662
663 let id = self.quantized_vectors.len();
664 self.uri_to_id.insert(uri.clone(), id);
665
666 let quantized = self.quantize_vector(&vector);
667 self.quantized_vectors.push(quantized);
668
669 Ok(())
670 }
671
672 fn search_knn(&self, query: &Vector, k: usize) -> Result<Vec<(String, f32)>> {
673 let query_quantized = self.quantize_vector(query);
674 let mut results = Vec::new();
675
676 for (uri, quantized) in self.uri_to_id.keys().zip(&self.quantized_vectors) {
677 let distance = hamming_distance(&query_quantized, quantized);
678 results.push((uri.clone(), distance));
679 }
680
681 results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
682 results.truncate(k);
683
684 Ok(results)
685 }
686
687 fn search_threshold(&self, query: &Vector, threshold: f32) -> Result<Vec<(String, f32)>> {
688 let query_quantized = self.quantize_vector(query);
689 let mut results = Vec::new();
690
691 for (uri, quantized) in self.uri_to_id.keys().zip(&self.quantized_vectors) {
692 let distance = hamming_distance(&query_quantized, quantized);
693 if distance <= threshold {
694 results.push((uri.clone(), distance));
695 }
696 }
697
698 results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
699 Ok(results)
700 }
701
702 fn get_vector(&self, _uri: &str) -> Option<&Vector> {
703 None
706 }
707}
708
709fn hamming_distance(a: &[u8], b: &[u8]) -> f32 {
712 a.iter().zip(b).filter(|(x, y)| x != y).count() as f32
713}
714
715fn kmeans_clustering(vectors: &[Vector], k: usize) -> Result<Vec<Vector>> {
717 if vectors.is_empty() || k == 0 {
718 return Ok(Vec::new());
719 }
720
721 let dimensions = vectors[0].dimensions;
722 let mut centroids = Vec::with_capacity(k);
723
724 for i in 0..k {
726 let idx = i % vectors.len();
727 centroids.push(vectors[idx].clone());
728 }
729
730 for _ in 0..10 {
732 let mut clusters: Vec<Vec<&Vector>> = vec![Vec::new(); k];
733
734 for vector in vectors {
736 let mut best_centroid = 0;
737 let mut best_distance = f32::INFINITY;
738
739 for (i, centroid) in centroids.iter().enumerate() {
740 let vector_f32 = vector.as_f32();
741 let centroid_f32 = centroid.as_f32();
742 use oxirs_core::simd::SimdOps;
743 let distance = f32::euclidean_distance(&vector_f32, ¢roid_f32);
744 if distance < best_distance {
745 best_distance = distance;
746 best_centroid = i;
747 }
748 }
749
750 clusters[best_centroid].push(vector);
751 }
752
753 for (i, cluster) in clusters.iter().enumerate() {
755 if !cluster.is_empty() {
756 let mut new_centroid = vec![0.0; dimensions];
757
758 for vector in cluster {
759 let vector_f32 = vector.as_f32();
760 for (j, &value) in vector_f32.iter().enumerate() {
761 new_centroid[j] += value;
762 }
763 }
764
765 for value in &mut new_centroid {
766 *value /= cluster.len() as f32;
767 }
768
769 centroids[i] = Vector::new(new_centroid);
770 }
771 }
772 }
773
774 Ok(centroids)
775}
776
777pub struct MultiIndex {
779 indices: HashMap<String, Box<dyn VectorIndex>>,
780 default_index: String,
781}
782
783impl MultiIndex {
784 pub fn new() -> Self {
785 Self {
786 indices: HashMap::new(),
787 default_index: String::new(),
788 }
789 }
790
791 pub fn add_index(&mut self, name: String, index: Box<dyn VectorIndex>) {
792 if self.indices.is_empty() {
793 self.default_index = name.clone();
794 }
795 self.indices.insert(name, index);
796 }
797
798 pub fn set_default(&mut self, name: &str) -> Result<()> {
799 if self.indices.contains_key(name) {
800 self.default_index = name.to_string();
801 Ok(())
802 } else {
803 Err(anyhow!("Index '{}' not found", name))
804 }
805 }
806
807 pub fn search_index(
808 &self,
809 index_name: &str,
810 query: &Vector,
811 k: usize,
812 ) -> Result<Vec<(String, f32)>> {
813 if let Some(index) = self.indices.get(index_name) {
814 index.search_knn(query, k)
815 } else {
816 Err(anyhow!("Index '{}' not found", index_name))
817 }
818 }
819}
820
821impl Default for MultiIndex {
822 fn default() -> Self {
823 Self::new()
824 }
825}
826
827impl VectorIndex for MultiIndex {
828 fn insert(&mut self, uri: String, vector: Vector) -> Result<()> {
829 if let Some(index) = self.indices.get_mut(&self.default_index) {
830 index.insert(uri, vector)
831 } else {
832 Err(anyhow!("No default index set"))
833 }
834 }
835
836 fn search_knn(&self, query: &Vector, k: usize) -> Result<Vec<(String, f32)>> {
837 if let Some(index) = self.indices.get(&self.default_index) {
838 index.search_knn(query, k)
839 } else {
840 Err(anyhow!("No default index set"))
841 }
842 }
843
844 fn search_threshold(&self, query: &Vector, threshold: f32) -> Result<Vec<(String, f32)>> {
845 if let Some(index) = self.indices.get(&self.default_index) {
846 index.search_threshold(query, threshold)
847 } else {
848 Err(anyhow!("No default index set"))
849 }
850 }
851
852 fn get_vector(&self, uri: &str) -> Option<&Vector> {
853 if let Some(index) = self.indices.get(&self.default_index) {
854 index.get_vector(uri)
855 } else {
856 None
857 }
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864
865 fn sample_vectors() -> Vec<(&'static str, Vector)> {
866 vec![
867 (
868 "http://example.org/a",
869 Vector::new(vec![1.0, 0.0, 0.0, 1.0]),
870 ),
871 (
872 "http://example.org/b",
873 Vector::new(vec![0.0, 1.0, 1.0, 0.0]),
874 ),
875 (
876 "http://example.org/c",
877 Vector::new(vec![-1.0, 0.0, 0.0, -1.0]),
878 ),
879 (
880 "http://example.org/d",
881 Vector::new(vec![0.0, -1.0, -1.0, 0.0]),
882 ),
883 (
884 "http://example.org/e",
885 Vector::new(vec![0.5, 0.5, 0.5, 0.5]),
886 ),
887 (
888 "http://example.org/f",
889 Vector::new(vec![-0.5, 0.5, -0.5, 0.5]),
890 ),
891 (
892 "http://example.org/g",
893 Vector::new(vec![1.0, 1.0, 0.0, 0.0]),
894 ),
895 (
896 "http://example.org/h",
897 Vector::new(vec![0.0, 0.0, 1.0, 1.0]),
898 ),
899 ]
900 }
901
902 fn build_index(index_type: IndexType) -> Result<AdvancedVectorIndex> {
903 let config = IndexConfig {
904 index_type,
905 ..Default::default()
906 };
907 let mut idx = AdvancedVectorIndex::new(config);
908 for (uri, vec) in sample_vectors() {
909 idx.insert(uri.to_string(), vec)?;
910 }
911 idx.build()?;
912 Ok(idx)
913 }
914
915 #[test]
916 fn test_ivf_build_and_search() -> Result<()> {
917 let idx = build_index(IndexType::Ivf)?;
918 assert!(idx.ivf_index.is_some(), "IVF index should be built");
919
920 let query = Vector::new(vec![1.0, 0.0, 0.0, 1.0]);
921 let results = idx.search(&query.as_f32(), 3)?;
922 assert!(!results.is_empty(), "IVF search should return results");
923 Ok(())
924 }
925
926 #[test]
927 fn test_pq_build_and_search() -> Result<()> {
928 let idx = build_index(IndexType::PQ)?;
929 assert!(idx.pq_index.is_some(), "PQ index should be built");
930
931 let query = Vector::new(vec![0.0, 1.0, 1.0, 0.0]);
932 let results = idx.search(&query.as_f32(), 3)?;
933 assert!(!results.is_empty(), "PQ search should return results");
934 Ok(())
935 }
936
937 #[test]
938 fn test_flat_search_unchanged() -> Result<()> {
939 let idx = build_index(IndexType::Flat)?;
940 let query = Vector::new(vec![1.0, 0.0, 0.0, 1.0]);
941 let results = idx.search(&query.as_f32(), 2)?;
942 assert_eq!(
943 results.len(),
944 2,
945 "Flat search should return exactly k results"
946 );
947 Ok(())
948 }
949}