Skip to main content

foxstash_core/vector/
mod.rs

1//! Vector operations and utilities for RAG system
2//!
3//! This module provides high-performance vector operations essential for
4//! similarity search and embedding manipulation in the RAG system.
5//!
6//! # Core Operations
7//!
8//! - **Similarity Metrics**: Cosine similarity for measuring vector similarity
9//! - **Distance Metrics**: L2 (Euclidean) distance for spatial relationships
10//! - **Vector Algebra**: Dot product, normalization, and comparison operations
11//!
12//! # Performance Characteristics
13//!
14//! All operations in this module are optimized for hot-path performance:
15//! - Functions are marked with inline hints for small vectors
16//! - Efficient iterator usage for auto-vectorization
17//! - Minimal allocations and cache-friendly access patterns
18//! - SIMD acceleration with runtime CPU detection
19//!
20//! # SIMD Acceleration
21//!
22//! The module automatically uses SIMD instructions when available:
23//! - **x86_64**: AVX2, SSE2, or scalar fallback
24//! - **ARM**: NEON or scalar fallback
25//! - Runtime detection ensures optimal performance on any CPU
26//!
27//! Use the `*_auto()` functions for automatic SIMD/scalar selection:
28//! ```
29//! use foxstash_core::vector::{cosine_similarity_auto, dot_product_auto};
30//!
31//! let a = vec![1.0; 384];
32//! let b = vec![2.0; 384];
33//!
34//! // Automatically uses SIMD if available
35//! let similarity = cosine_similarity_auto(&a, &b).unwrap();
36//! let dot = dot_product_auto(&a, &b).unwrap();
37//! ```
38//!
39//! # Usage
40//!
41//! ```
42//! use foxstash_core::vector::ops::{cosine_similarity, normalize};
43//!
44//! let mut embedding = vec![1.0, 2.0, 3.0];
45//! normalize(&mut embedding);
46//!
47//! let query = vec![0.6, 0.8, 0.0];
48//! let similarity = cosine_similarity(&embedding, &query).unwrap();
49//! ```
50
51pub mod ops;
52pub mod rabitq;
53pub mod simd;
54
55use crate::{RagError, Result};
56
57// Re-export commonly used functions
58pub use ops::{cosine_similarity, dot_product, l2_distance, normalize};
59
60// Re-export SIMD functions
61pub use simd::{
62    cosine_distance_prenorm, cosine_similarity_simd, dot_product_simd, l2_distance_simd, norm_simd,
63};
64
65/// Automatically selects between SIMD and scalar cosine similarity.
66///
67/// This function uses runtime CPU detection to choose the fastest available
68/// implementation. On x86_64 with AVX2 or ARM with NEON, it uses SIMD
69/// instructions. Otherwise, it falls back to scalar operations.
70///
71/// # Arguments
72///
73/// * `a` - First vector
74/// * `b` - Second vector
75///
76/// # Returns
77///
78/// Returns cosine similarity in range [-1, 1].
79///
80/// # Errors
81///
82/// Returns `RagError::DimensionMismatch` if vectors have different dimensions.
83///
84/// # Examples
85///
86/// ```
87/// use foxstash_core::vector::cosine_similarity_auto;
88///
89/// let a = vec![1.0, 0.0, 0.0];
90/// let b = vec![0.0, 1.0, 0.0];
91/// let similarity = cosine_similarity_auto(&a, &b).unwrap();
92/// assert!((similarity - 0.0).abs() < 1e-5);
93/// ```
94#[inline]
95pub fn cosine_similarity_auto(a: &[f32], b: &[f32]) -> Result<f32> {
96    if a.len() != b.len() {
97        return Err(RagError::DimensionMismatch {
98            expected: a.len(),
99            actual: b.len(),
100        });
101    }
102
103    // Always use SIMD - pulp handles runtime detection and fallback
104    Ok(simd::cosine_similarity_simd(a, b))
105}
106
107/// Automatically selects between SIMD and scalar L2 distance.
108///
109/// This function uses runtime CPU detection to choose the fastest available
110/// implementation.
111///
112/// # Arguments
113///
114/// * `a` - First vector
115/// * `b` - Second vector
116///
117/// # Returns
118///
119/// Returns the non-negative L2 distance.
120///
121/// # Errors
122///
123/// Returns `RagError::DimensionMismatch` if vectors have different dimensions.
124///
125/// # Examples
126///
127/// ```
128/// use foxstash_core::vector::l2_distance_auto;
129///
130/// let a = vec![0.0, 0.0];
131/// let b = vec![3.0, 4.0];
132/// let distance = l2_distance_auto(&a, &b).unwrap();
133/// assert!((distance - 5.0).abs() < 1e-5);
134/// ```
135#[inline]
136pub fn l2_distance_auto(a: &[f32], b: &[f32]) -> Result<f32> {
137    if a.len() != b.len() {
138        return Err(RagError::DimensionMismatch {
139            expected: a.len(),
140            actual: b.len(),
141        });
142    }
143
144    Ok(simd::l2_distance_simd(a, b))
145}
146
147/// Automatically selects between SIMD and scalar dot product.
148///
149/// This function uses runtime CPU detection to choose the fastest available
150/// implementation.
151///
152/// # Arguments
153///
154/// * `a` - First vector
155/// * `b` - Second vector
156///
157/// # Returns
158///
159/// Returns the dot product as a scalar value.
160///
161/// # Errors
162///
163/// Returns `RagError::DimensionMismatch` if vectors have different dimensions.
164///
165/// # Examples
166///
167/// ```
168/// use foxstash_core::vector::dot_product_auto;
169///
170/// let a = vec![1.0, 2.0, 3.0];
171/// let b = vec![4.0, 5.0, 6.0];
172/// let product = dot_product_auto(&a, &b).unwrap();
173/// assert!((product - 32.0).abs() < 1e-5);
174/// ```
175#[inline]
176pub fn dot_product_auto(a: &[f32], b: &[f32]) -> Result<f32> {
177    if a.len() != b.len() {
178        return Err(RagError::DimensionMismatch {
179            expected: a.len(),
180            actual: b.len(),
181        });
182    }
183
184    Ok(simd::dot_product_simd(a, b))
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    const EPSILON: f32 = 1e-5;
192
193    #[test]
194    fn test_auto_functions_match_scalar() {
195        let a = vec![1.0, 2.0, 3.0, 4.0, 5.0];
196        let b = vec![5.0, 4.0, 3.0, 2.0, 1.0];
197
198        // Test cosine similarity
199        let auto_sim = cosine_similarity_auto(&a, &b).unwrap();
200        let scalar_sim = cosine_similarity(&a, &b).unwrap();
201        assert!((auto_sim - scalar_sim).abs() < EPSILON);
202
203        // Test L2 distance
204        let auto_dist = l2_distance_auto(&a, &b).unwrap();
205        let scalar_dist = l2_distance(&a, &b).unwrap();
206        assert!((auto_dist - scalar_dist).abs() < EPSILON);
207
208        // Test dot product
209        let auto_dot = dot_product_auto(&a, &b).unwrap();
210        let scalar_dot = dot_product(&a, &b).unwrap();
211        assert!((auto_dot - scalar_dot).abs() < EPSILON);
212    }
213
214    #[test]
215    fn test_auto_functions_dimension_mismatch() {
216        let a = vec![1.0, 2.0];
217        let b = vec![1.0, 2.0, 3.0];
218
219        assert!(matches!(
220            cosine_similarity_auto(&a, &b),
221            Err(RagError::DimensionMismatch { .. })
222        ));
223
224        assert!(matches!(
225            l2_distance_auto(&a, &b),
226            Err(RagError::DimensionMismatch { .. })
227        ));
228
229        assert!(matches!(
230            dot_product_auto(&a, &b),
231            Err(RagError::DimensionMismatch { .. })
232        ));
233    }
234
235    #[test]
236    fn test_auto_functions_typical_embeddings() {
237        // Test with typical embedding sizes
238        for size in [384, 768, 1024] {
239            let a: Vec<f32> = (0..size).map(|i| (i as f32) / (size as f32)).collect();
240            let b: Vec<f32> = (0..size)
241                .map(|i| 1.0 - (i as f32) / (size as f32))
242                .collect();
243
244            let auto_sim = cosine_similarity_auto(&a, &b).unwrap();
245            let scalar_sim = cosine_similarity(&a, &b).unwrap();
246            assert!(
247                (auto_sim - scalar_sim).abs() < 1e-4, // Relaxed for large vectors
248                "Size {}: auto={}, scalar={}",
249                size,
250                auto_sim,
251                scalar_sim
252            );
253
254            let auto_dist = l2_distance_auto(&a, &b).unwrap();
255            let scalar_dist = l2_distance(&a, &b).unwrap();
256            // Use relative epsilon for distance
257            let epsilon = scalar_dist.abs() * 1e-5;
258            assert!(
259                (auto_dist - scalar_dist).abs() < epsilon,
260                "Size {}: auto={}, scalar={}",
261                size,
262                auto_dist,
263                scalar_dist
264            );
265        }
266    }
267}