Skip to main content

oxirs_vec/
index.rs

1//! Advanced vector indexing with HNSW and other efficient algorithms
2
3use crate::Vector;
4
5// Re-export VectorIndex trait for use by other modules
6pub 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
19/// Type alias for filter functions
20pub type FilterFunction = Box<dyn Fn(&str) -> bool>;
21/// Type alias for filter functions with Send + Sync
22pub type FilterFunctionSync = Box<dyn Fn(&str) -> bool + Send + Sync>;
23
24/// Configuration for vector index
25#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
26pub struct IndexConfig {
27    /// Index type to use
28    pub index_type: IndexType,
29    /// Maximum number of connections for each node (for HNSW)
30    pub max_connections: usize,
31    /// Construction parameter (for HNSW)
32    pub ef_construction: usize,
33    /// Search parameter (for HNSW)
34    pub ef_search: usize,
35    /// Distance metric to use
36    pub distance_metric: DistanceMetric,
37    /// Whether to enable parallel operations
38    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/// Available index types
55#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
56pub enum IndexType {
57    /// Hierarchical Navigable Small World
58    Hnsw,
59    /// Simple flat index (brute force)
60    Flat,
61    /// IVF (Inverted File) index
62    Ivf,
63    /// Product Quantization
64    PQ,
65}
66
67/// Distance metrics supported
68#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
69pub enum DistanceMetric {
70    /// Cosine distance (1 - cosine_similarity)
71    Cosine,
72    /// Euclidean (L2) distance
73    Euclidean,
74    /// Manhattan (L1) distance
75    Manhattan,
76    /// Dot product (negative for max-heap behavior)
77    DotProduct,
78}
79
80impl DistanceMetric {
81    /// Calculate distance between two vectors
82    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), // Negative for max-heap
90        }
91    }
92
93    /// Calculate distance between two Vector objects
94    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/// Search result with distance/score
102#[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
126/// Advanced vector index with multiple implementations
127pub struct AdvancedVectorIndex {
128    config: IndexConfig,
129    vectors: Vec<(String, Vector)>,
130    uri_to_id: HashMap<String, usize>,
131    hnsw_index: Option<HnswIndex>,
132    /// Trained IVF index (populated after `build()` when `IndexType::Ivf`)
133    ivf_index: Option<IvfIndex>,
134    /// Trained PQ index (populated after `build()` when `IndexType::PQ`)
135    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    /// Build the index after adding all vectors
153    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                // No special building needed for flat index
164            }
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    /// Build an IVF index by training on all stored vectors and then adding them.
199    ///
200    /// Uses the default `IvfConfig` with `n_clusters` derived from the number of
201    /// stored vectors (capped at 256) so that every cluster has at least one
202    /// training sample.
203    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    /// Build a PQ index by training on all stored vectors and then encoding them.
224    ///
225    /// Chooses `n_subquantizers` so that it evenly divides the vector dimension
226    /// and stays within [1, 8].  Falls back gracefully with a descriptive error
227    /// if dimensions are not yet known.
228    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        // Pick the largest power-of-two divisor of dims in [1, 8]
234        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, // small for small test sets
243            ..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    /// Add metadata to a vector
258    pub fn add_metadata(&mut self, _uri: &str, _metadata: HashMap<String, String>) -> Result<()> {
259        // For now, we'll store metadata separately
260        // In a full implementation, this would be integrated with the index
261        Ok(())
262    }
263
264    /// Search with advanced parameters
265    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            // `VectorIndex::search_knn` returns similarity (larger = closer);
285            // reconstruct a distance-like value for `SearchResult.distance`.
286            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        // `VectorIndex::search_knn` returns similarity (larger = closer).
307        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        // `VectorIndex::search_knn` returns similarity (larger = closer).
325        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            // For parallel search, we need Send + Sync filter
344            if filter.is_some() {
345                // Fall back to sequential if filter is present but not Send + Sync
346                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, // Convert distance to similarity score
377                    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, // Convert distance to similarity score
386                        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        // Split vectors into chunks for parallel processing
409        let chunk_size = (self.vectors.len() / num_threads()).max(100);
410
411        // Use Arc for thread-safe sharing of the filter
412        let filter_arc = filter.map(Arc::new);
413
414        // Process chunks in parallel and collect top-k from each
415        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, // Convert distance to similarity score
436                            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, // Convert distance to similarity score
445                                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        // Merge results from all chunks
460        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    /// Get index statistics
485    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    /// Get the number of vectors in the index
506    pub fn len(&self) -> usize {
507        self.vectors.len()
508    }
509
510    /// Check if the index is empty
511    pub fn is_empty(&self) -> bool {
512        self.vectors.is_empty()
513    }
514
515    /// Add a vector with RDF triple and metadata (for compatibility with tests)
516    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    /// Search for nearest neighbors (for compatibility with tests)
528    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        // For AdvancedVectorIndex, vectors are stored in the vectors field
577        // regardless of the index type being used
578        self.vectors.iter().find(|(u, _)| u == uri).map(|(_, v)| v)
579    }
580}
581
582/// Index performance statistics
583#[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
591/// Quantized vector index for memory efficiency
592pub 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    /// Train quantization centroids using k-means
612    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        // Simple k-means clustering for centroids
621        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        // Find nearest centroid for each dimension chunk
630        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 = &centroid_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        // Quantized index doesn't store original vectors
704        // Return None as we only have quantized representations
705        None
706    }
707}
708
709// Helper functions that don't have SIMD equivalents
710
711fn hamming_distance(a: &[u8], b: &[u8]) -> f32 {
712    a.iter().zip(b).filter(|(x, y)| x != y).count() as f32
713}
714
715// K-means clustering for quantization
716fn 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    // Initialize centroids randomly
725    for i in 0..k {
726        let idx = i % vectors.len();
727        centroids.push(vectors[idx].clone());
728    }
729
730    // Simple k-means iterations
731    for _ in 0..10 {
732        let mut clusters: Vec<Vec<&Vector>> = vec![Vec::new(); k];
733
734        // Assign vectors to nearest centroid
735        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, &centroid_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        // Update centroids
754        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
777/// Multi-index system that combines multiple index types
778pub 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}