#[inline]
pub fn l2_distance(a: &[f32], b: &[f32]) -> f32 {
l2_distance_squared(a, b).sqrt()
}
#[inline]
pub fn l2_distance_squared(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| {
let diff = x - y;
diff * diff
})
.sum()
}
#[inline]
pub fn inner_product(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| x * y)
.sum()
}
#[inline]
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let dot = inner_product(a, b);
let norm_a = norm(a);
let norm_b = norm(b);
if norm_a == 0.0 || norm_b == 0.0 {
0.0
} else {
dot / (norm_a * norm_b)
}
}
#[inline]
pub fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
1.0 - cosine_similarity(a, b)
}
#[inline]
pub fn norm(a: &[f32]) -> f32 {
a.iter().map(|x| x * x).sum::<f32>().sqrt()
}
#[inline]
pub fn norm_squared(a: &[f32]) -> f32 {
a.iter().map(|x| x * x).sum()
}
#[inline]
#[must_use]
pub fn normalize(a: &[f32]) -> Vec<f32> {
let n = norm(a);
if n == 0.0 {
vec![0.0; a.len()]
} else {
a.iter().map(|x| x / n).collect()
}
}
#[inline]
pub fn normalize_in_place(a: &mut [f32]) -> f32 {
let n = norm(a);
if n > 0.0 {
for x in a.iter_mut() {
*x /= n;
}
}
n
}
pub fn normalize_batch(vectors: &mut [&mut [f32]]) -> Vec<f32> {
vectors.iter_mut()
.map(|v| normalize_in_place(v))
.collect()
}
pub fn normalize_batch_flat(data: &mut [f32], dim: usize) -> Vec<f32> {
assert!(dim > 0, "Dimension must be > 0");
assert!(data.len() % dim == 0, "Data length must be multiple of dimension");
let n_vectors = data.len() / dim;
let mut norms = Vec::with_capacity(n_vectors);
for i in 0..n_vectors {
let start = i * dim;
let end = start + dim;
let vector = &mut data[start..end];
let n = normalize_in_place(vector);
norms.push(n);
}
norms
}
pub fn compute_norms_batch(data: &[f32], dim: usize) -> Vec<f32> {
assert!(dim > 0, "Dimension must be > 0");
assert!(data.len() % dim == 0, "Data length must be multiple of dimension");
let n_vectors = data.len() / dim;
let mut norms = Vec::with_capacity(n_vectors);
for i in 0..n_vectors {
let start = i * dim;
let end = start + dim;
let vector = &data[start..end];
norms.push(norm(vector));
}
norms
}
pub fn find_unnormalized(data: &[f32], dim: usize, tolerance: f32) -> Vec<usize> {
assert!(dim > 0, "Dimension must be > 0");
assert!(data.len() % dim == 0, "Data length must be multiple of dimension");
let n_vectors = data.len() / dim;
let mut unnormalized = Vec::new();
for i in 0..n_vectors {
let start = i * dim;
let end = start + dim;
let vector = &data[start..end];
let n = norm(vector);
if (n - 1.0).abs() > tolerance {
unnormalized.push(i);
}
}
unnormalized
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 1e-6;
fn assert_approx_eq(a: f32, b: f32) {
assert!((a - b).abs() < EPSILON, "Expected {} ≈ {}", a, b);
}
#[test]
fn test_l2_distance_identical() {
let a = [1.0, 2.0, 3.0, 4.0];
assert_approx_eq(l2_distance(&a, &a), 0.0);
}
#[test]
fn test_l2_distance_orthogonal() {
let a = [1.0, 0.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0, 0.0];
assert_approx_eq(l2_distance(&a, &b), std::f32::consts::SQRT_2);
}
#[test]
fn test_l2_distance_known_value() {
let a = [0.0, 0.0, 0.0];
let b = [3.0, 4.0, 0.0];
assert_approx_eq(l2_distance(&a, &b), 5.0); }
#[test]
fn test_inner_product_orthogonal() {
let a = [1.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0];
assert_approx_eq(inner_product(&a, &b), 0.0);
}
#[test]
fn test_inner_product_parallel() {
let a = [1.0, 2.0, 3.0];
let b = [2.0, 4.0, 6.0];
assert_approx_eq(inner_product(&a, &b), 28.0); }
#[test]
fn test_inner_product_known_value() {
let a = [1.0, 2.0, 3.0, 4.0];
let b = [4.0, 3.0, 2.0, 1.0];
assert_approx_eq(inner_product(&a, &b), 20.0); }
#[test]
fn test_cosine_identical() {
let a = [1.0, 2.0, 3.0];
assert_approx_eq(cosine_similarity(&a, &a), 1.0);
}
#[test]
fn test_cosine_orthogonal() {
let a = [1.0, 0.0];
let b = [0.0, 1.0];
assert_approx_eq(cosine_similarity(&a, &b), 0.0);
}
#[test]
fn test_cosine_opposite() {
let a = [1.0, 0.0];
let b = [-1.0, 0.0];
assert_approx_eq(cosine_similarity(&a, &b), -1.0);
}
#[test]
fn test_cosine_zero_vector() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 2.0, 3.0];
assert_approx_eq(cosine_similarity(&a, &b), 0.0);
}
#[test]
fn test_cosine_distance() {
let a = [1.0, 0.0];
let b = [1.0, 0.0];
assert_approx_eq(cosine_distance(&a, &b), 0.0);
let c = [1.0, 0.0];
let d = [-1.0, 0.0];
assert_approx_eq(cosine_distance(&c, &d), 2.0);
}
#[test]
fn test_norm() {
let a = [3.0, 4.0];
assert_approx_eq(norm(&a), 5.0);
let b = [0.0, 0.0, 0.0];
assert_approx_eq(norm(&b), 0.0);
}
#[test]
fn test_normalize() {
let a = [3.0, 4.0];
let n = normalize(&a);
assert_approx_eq(n[0], 0.6);
assert_approx_eq(n[1], 0.8);
assert_approx_eq(norm(&n), 1.0);
}
#[test]
fn test_normalize_zero_vector() {
let a = [0.0, 0.0];
let n = normalize(&a);
assert_approx_eq(n[0], 0.0);
assert_approx_eq(n[1], 0.0);
}
#[test]
fn test_normalize_in_place() {
let mut a = [3.0, 4.0];
let original_norm = normalize_in_place(&mut a);
assert_approx_eq(original_norm, 5.0);
assert_approx_eq(a[0], 0.6);
assert_approx_eq(a[1], 0.8);
}
#[test]
fn test_symmetry() {
let a = [1.0, 2.0, 3.0, 4.0];
let b = [5.0, 6.0, 7.0, 8.0];
assert_approx_eq(l2_distance(&a, &b), l2_distance(&b, &a));
assert_approx_eq(inner_product(&a, &b), inner_product(&b, &a));
assert_approx_eq(cosine_similarity(&a, &b), cosine_similarity(&b, &a));
}
#[test]
fn test_high_dimension() {
let dim = 512;
let a: Vec<f32> = (0..dim).map(|i| i as f32 * 0.01).collect();
let b: Vec<f32> = (0..dim).map(|i| (dim - i) as f32 * 0.01).collect();
let l2 = l2_distance(&a, &b);
let ip = inner_product(&a, &b);
let cos = cosine_similarity(&a, &b);
assert!(l2.is_finite());
assert!(ip.is_finite());
assert!(cos.is_finite());
assert!(cos >= -1.0 && cos <= 1.0);
}
}