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}