pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Vectors must have same length");
let len = a.len();
let chunks = len / 8;
let remainder = len % 8;
let mut sum0 = 0.0f32;
let mut sum1 = 0.0f32;
let mut sum2 = 0.0f32;
let mut sum3 = 0.0f32;
let mut sum4 = 0.0f32;
let mut sum5 = 0.0f32;
let mut sum6 = 0.0f32;
let mut sum7 = 0.0f32;
let mut i = 0;
for _ in 0..chunks {
sum0 += a[i] * b[i];
sum1 += a[i + 1] * b[i + 1];
sum2 += a[i + 2] * b[i + 2];
sum3 += a[i + 3] * b[i + 3];
sum4 += a[i + 4] * b[i + 4];
sum5 += a[i + 5] * b[i + 5];
sum6 += a[i + 6] * b[i + 6];
sum7 += a[i + 7] * b[i + 7];
i += 8;
}
for j in 0..remainder {
sum0 += a[i + j] * b[i + j];
}
sum0 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7
}
pub fn normalize(v: &mut [f32]) {
let norm = dot_product(v, v).sqrt();
if norm > 1e-10 {
let inv_norm = 1.0 / norm;
for x in v.iter_mut() {
*x *= inv_norm;
}
}
}
pub fn batch_cosine(query: &[f32], candidates: &[&[f32]]) -> Vec<f32> {
candidates.iter().map(|c| dot_product(query, c)).collect()
}
pub fn l2_norm(v: &[f32]) -> f32 {
dot_product(v, v).sqrt()
}
pub fn add(a: &[f32], b: &[f32]) -> Vec<f32> {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| x + y).collect()
}
pub fn sub(a: &[f32], b: &[f32]) -> Vec<f32> {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| x - y).collect()
}
pub fn scale(a: &[f32], scalar: f32) -> Vec<f32> {
a.iter().map(|x| x * scalar).collect()
}
pub fn mean(vectors: &[&[f32]]) -> Vec<f32> {
if vectors.is_empty() {
return Vec::new();
}
let dim = vectors[0].len();
let mut result = vec![0.0f32; dim];
let scale = 1.0 / vectors.len() as f32;
for v in vectors {
for (i, x) in v.iter().enumerate() {
result[i] += x * scale;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dot_product_basic() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let result = dot_product(&a, &b);
assert!((result - 32.0).abs() < 1e-5);
}
#[test]
fn test_dot_product_large() {
let a: Vec<f32> = (0..100).map(|i| i as f32).collect();
let b: Vec<f32> = (0..100).map(|i| i as f32).collect();
let result = dot_product(&a, &b);
let expected: f32 = (0..100).map(|i| (i * i) as f32).sum();
assert!((result - expected).abs() < 1e-3);
}
#[test]
fn test_normalize() {
let mut v = vec![3.0, 4.0];
normalize(&mut v);
assert!((v[0] - 0.6).abs() < 1e-5);
assert!((v[1] - 0.8).abs() < 1e-5);
let norm = l2_norm(&v);
assert!((norm - 1.0).abs() < 1e-5);
}
#[test]
fn test_normalize_zero() {
let mut v = vec![0.0, 0.0, 0.0];
normalize(&mut v);
assert_eq!(v, vec![0.0, 0.0, 0.0]);
}
#[test]
fn test_batch_cosine() {
let query = vec![1.0, 0.0];
let c1 = vec![1.0, 0.0]; let c2 = vec![0.0, 1.0]; let c3 = vec![-1.0, 0.0];
let candidates: Vec<&[f32]> = vec![&c1, &c2, &c3];
let scores = batch_cosine(&query, &candidates);
assert!((scores[0] - 1.0).abs() < 1e-5); assert!(scores[1].abs() < 1e-5); assert!((scores[2] + 1.0).abs() < 1e-5); }
#[test]
fn test_add() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let result = add(&a, &b);
assert_eq!(result, vec![5.0, 7.0, 9.0]);
}
#[test]
fn test_sub() {
let a = vec![4.0, 5.0, 6.0];
let b = vec![1.0, 2.0, 3.0];
let result = sub(&a, &b);
assert_eq!(result, vec![3.0, 3.0, 3.0]);
}
#[test]
fn test_scale() {
let a = vec![1.0, 2.0, 3.0];
let result = scale(&a, 2.0);
assert_eq!(result, vec![2.0, 4.0, 6.0]);
}
#[test]
fn test_mean() {
let v1 = vec![1.0, 2.0, 3.0];
let v2 = vec![3.0, 4.0, 5.0];
let vectors: Vec<&[f32]> = vec![&v1, &v2];
let result = mean(&vectors);
assert_eq!(result, vec![2.0, 3.0, 4.0]);
}
#[test]
fn test_mean_empty() {
let vectors: Vec<&[f32]> = vec![];
let result = mean(&vectors);
assert!(result.is_empty());
}
}