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 (3-4x speedup)
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 product_quantize;
53pub mod quantize;
54pub mod simd;
55
56use crate::{RagError, Result};
57
58// Re-export commonly used functions
59pub use ops::{approx_equal, cosine_similarity, dot_product, l2_distance, normalize};
60
61// Re-export SIMD functions
62pub use simd::{
63    cosine_distance_prenorm, cosine_similarity_simd, dot_product_simd, l2_distance_simd, norm_simd,
64};
65
66/// Automatically selects between SIMD and scalar cosine similarity.
67///
68/// This function uses runtime CPU detection to choose the fastest available
69/// implementation. On x86_64 with AVX2 or ARM with NEON, it uses SIMD
70/// acceleration for 3-4x speedup. Otherwise, it falls back to scalar operations.
71///
72/// # Arguments
73///
74/// * `a` - First vector
75/// * `b` - Second vector
76///
77/// # Returns
78///
79/// Returns cosine similarity in range [-1, 1].
80///
81/// # Errors
82///
83/// Returns `RagError::DimensionMismatch` if vectors have different dimensions.
84///
85/// # Examples
86///
87/// ```
88/// use foxstash_core::vector::cosine_similarity_auto;
89///
90/// let a = vec![1.0, 0.0, 0.0];
91/// let b = vec![0.0, 1.0, 0.0];
92/// let similarity = cosine_similarity_auto(&a, &b).unwrap();
93/// assert!((similarity - 0.0).abs() < 1e-5);
94/// ```
95#[inline]
96pub fn cosine_similarity_auto(a: &[f32], b: &[f32]) -> Result<f32> {
97    if a.len() != b.len() {
98        return Err(RagError::DimensionMismatch {
99            expected: a.len(),
100            actual: b.len(),
101        });
102    }
103
104    // Always use SIMD - pulp handles runtime detection and fallback
105    Ok(simd::cosine_similarity_simd(a, b))
106}
107
108/// Automatically selects between SIMD and scalar L2 distance.
109///
110/// This function uses runtime CPU detection to choose the fastest available
111/// implementation.
112///
113/// # Arguments
114///
115/// * `a` - First vector
116/// * `b` - Second vector
117///
118/// # Returns
119///
120/// Returns the non-negative L2 distance.
121///
122/// # Errors
123///
124/// Returns `RagError::DimensionMismatch` if vectors have different dimensions.
125///
126/// # Examples
127///
128/// ```
129/// use foxstash_core::vector::l2_distance_auto;
130///
131/// let a = vec![0.0, 0.0];
132/// let b = vec![3.0, 4.0];
133/// let distance = l2_distance_auto(&a, &b).unwrap();
134/// assert!((distance - 5.0).abs() < 1e-5);
135/// ```
136#[inline]
137pub fn l2_distance_auto(a: &[f32], b: &[f32]) -> Result<f32> {
138    if a.len() != b.len() {
139        return Err(RagError::DimensionMismatch {
140            expected: a.len(),
141            actual: b.len(),
142        });
143    }
144
145    Ok(simd::l2_distance_simd(a, b))
146}
147
148/// Automatically selects between SIMD and scalar dot product.
149///
150/// This function uses runtime CPU detection to choose the fastest available
151/// implementation.
152///
153/// # Arguments
154///
155/// * `a` - First vector
156/// * `b` - Second vector
157///
158/// # Returns
159///
160/// Returns the dot product as a scalar value.
161///
162/// # Errors
163///
164/// Returns `RagError::DimensionMismatch` if vectors have different dimensions.
165///
166/// # Examples
167///
168/// ```
169/// use foxstash_core::vector::dot_product_auto;
170///
171/// let a = vec![1.0, 2.0, 3.0];
172/// let b = vec![4.0, 5.0, 6.0];
173/// let product = dot_product_auto(&a, &b).unwrap();
174/// assert!((product - 32.0).abs() < 1e-5);
175/// ```
176#[inline]
177pub fn dot_product_auto(a: &[f32], b: &[f32]) -> Result<f32> {
178    if a.len() != b.len() {
179        return Err(RagError::DimensionMismatch {
180            expected: a.len(),
181            actual: b.len(),
182        });
183    }
184
185    Ok(simd::dot_product_simd(a, b))
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    const EPSILON: f32 = 1e-5;
193
194    #[test]
195    fn test_auto_functions_match_scalar() {
196        let a = vec![1.0, 2.0, 3.0, 4.0, 5.0];
197        let b = vec![5.0, 4.0, 3.0, 2.0, 1.0];
198
199        // Test cosine similarity
200        let auto_sim = cosine_similarity_auto(&a, &b).unwrap();
201        let scalar_sim = cosine_similarity(&a, &b).unwrap();
202        assert!((auto_sim - scalar_sim).abs() < EPSILON);
203
204        // Test L2 distance
205        let auto_dist = l2_distance_auto(&a, &b).unwrap();
206        let scalar_dist = l2_distance(&a, &b).unwrap();
207        assert!((auto_dist - scalar_dist).abs() < EPSILON);
208
209        // Test dot product
210        let auto_dot = dot_product_auto(&a, &b).unwrap();
211        let scalar_dot = dot_product(&a, &b).unwrap();
212        assert!((auto_dot - scalar_dot).abs() < EPSILON);
213    }
214
215    #[test]
216    fn test_auto_functions_dimension_mismatch() {
217        let a = vec![1.0, 2.0];
218        let b = vec![1.0, 2.0, 3.0];
219
220        assert!(matches!(
221            cosine_similarity_auto(&a, &b),
222            Err(RagError::DimensionMismatch { .. })
223        ));
224
225        assert!(matches!(
226            l2_distance_auto(&a, &b),
227            Err(RagError::DimensionMismatch { .. })
228        ));
229
230        assert!(matches!(
231            dot_product_auto(&a, &b),
232            Err(RagError::DimensionMismatch { .. })
233        ));
234    }
235
236    #[test]
237    fn test_auto_functions_typical_embeddings() {
238        // Test with typical embedding sizes
239        for size in [384, 768, 1024] {
240            let a: Vec<f32> = (0..size).map(|i| (i as f32) / (size as f32)).collect();
241            let b: Vec<f32> = (0..size)
242                .map(|i| 1.0 - (i as f32) / (size as f32))
243                .collect();
244
245            let auto_sim = cosine_similarity_auto(&a, &b).unwrap();
246            let scalar_sim = cosine_similarity(&a, &b).unwrap();
247            assert!(
248                (auto_sim - scalar_sim).abs() < 1e-4, // Relaxed for large vectors
249                "Size {}: auto={}, scalar={}",
250                size,
251                auto_sim,
252                scalar_sim
253            );
254
255            let auto_dist = l2_distance_auto(&a, &b).unwrap();
256            let scalar_dist = l2_distance(&a, &b).unwrap();
257            // Use relative epsilon for distance
258            let epsilon = scalar_dist.abs() * 1e-5;
259            assert!(
260                (auto_dist - scalar_dist).abs() < epsilon,
261                "Size {}: auto={}, scalar={}",
262                size,
263                auto_dist,
264                scalar_dist
265            );
266        }
267    }
268}