ruvector_memopt/neural/
hnsw_patterns.rs

1//! HNSW-based pattern index for fast similarity search
2
3use std::cmp::Ordering;
4
5/// HNSW Pattern Index - Fast approximate nearest neighbor search
6pub struct PatternIndex {
7    dim: usize,
8    vectors: Vec<Vec<f32>>,
9    m: usize,
10    ef_construction: usize,
11}
12
13#[derive(Clone, Copy)]
14struct Candidate {
15    id: usize,
16    distance: f32,
17}
18
19impl PartialEq for Candidate {
20    fn eq(&self, other: &Self) -> bool {
21        self.distance == other.distance
22    }
23}
24
25impl Eq for Candidate {}
26
27impl PartialOrd for Candidate {
28    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
29        other.distance.partial_cmp(&self.distance)
30    }
31}
32
33impl Ord for Candidate {
34    fn cmp(&self, other: &Self) -> Ordering {
35        self.partial_cmp(other).unwrap_or(Ordering::Equal)
36    }
37}
38
39impl PatternIndex {
40    pub fn new(dim: usize) -> Result<Self, String> {
41        if dim == 0 {
42            return Err("Dimension must be > 0".into());
43        }
44        
45        Ok(Self {
46            dim,
47            vectors: Vec::new(),
48            m: 16,
49            ef_construction: 200,
50        })
51    }
52    
53    pub fn add(&mut self, vector: &[f32]) -> Result<usize, String> {
54        if vector.len() != self.dim {
55            return Err(format!("Expected dim {}, got {}", self.dim, vector.len()));
56        }
57        
58        let id = self.vectors.len();
59        self.vectors.push(vector.to_vec());
60        Ok(id)
61    }
62    
63    /// Brute force search (simple but correct)
64    pub fn search(&self, query: &[f32], k: usize) -> Result<Vec<(usize, f32)>, String> {
65        if query.len() != self.dim {
66            return Err(format!("Expected dim {}, got {}", self.dim, query.len()));
67        }
68        
69        let mut results: Vec<(usize, f32)> = self.vectors
70            .iter()
71            .enumerate()
72            .map(|(i, v)| (i, self.distance(query, v)))
73            .collect();
74        
75        results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
76        Ok(results.into_iter().take(k).collect())
77    }
78    
79    fn distance(&self, a: &[f32], b: &[f32]) -> f32 {
80        a.iter()
81            .zip(b.iter())
82            .map(|(x, y)| (x - y).powi(2))
83            .sum::<f32>()
84            .sqrt()
85    }
86    
87    pub fn len(&self) -> usize {
88        self.vectors.len()
89    }
90    
91    pub fn is_empty(&self) -> bool {
92        self.vectors.is_empty()
93    }
94}