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}