use super::types::DistanceMetric;
#[inline]
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "vectors must have same length");
let mut dot = 0.0f32;
let mut norm_a = 0.0f32;
let mut norm_b = 0.0f32;
for i in 0..a.len() {
dot += a[i] * b[i];
norm_a += a[i] * a[i];
norm_b += b[i] * b[i];
}
let denom = libm::sqrtf(norm_a) * libm::sqrtf(norm_b);
if denom > 1e-10 { dot / denom } else { 0.0 }
}
#[inline]
pub fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
1.0 - cosine_similarity(a, b)
}
#[inline]
pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "vectors must have same length");
let mut sum = 0.0f32;
for i in 0..a.len() {
let diff = a[i] - b[i];
sum += diff * diff;
}
libm::sqrtf(sum)
}
#[inline]
pub fn euclidean_distance_squared(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "vectors must have same length");
let mut sum = 0.0f32;
for i in 0..a.len() {
let diff = a[i] - b[i];
sum += diff * diff;
}
sum
}
#[inline]
pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "vectors must have same length");
let mut sum = 0.0f32;
for i in 0..a.len() {
sum += a[i] * b[i];
}
sum
}
#[inline]
pub fn negative_dot_product(a: &[f32], b: &[f32]) -> f32 {
-dot_product(a, b)
}
#[inline]
pub fn manhattan_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "vectors must have same length");
let mut sum = 0.0f32;
for i in 0..a.len() {
sum += libm::fabsf(a[i] - b[i]);
}
sum
}
#[inline]
pub fn hamming_distance(a: &[u8], b: &[u8]) -> u32 {
debug_assert_eq!(a.len(), b.len(), "vectors must have same length");
let mut distance = 0u32;
for i in 0..a.len() {
distance += (a[i] ^ b[i]).count_ones();
}
distance
}
#[inline]
pub fn compute_distance(a: &[f32], b: &[f32], metric: DistanceMetric) -> f32 {
match metric {
DistanceMetric::Cosine => cosine_distance(a, b),
DistanceMetric::Euclidean => euclidean_distance(a, b),
DistanceMetric::DotProduct => negative_dot_product(a, b),
DistanceMetric::Manhattan => manhattan_distance(a, b),
DistanceMetric::Hamming => {
euclidean_distance(a, b)
}
}
}
#[inline]
pub fn compute_similarity(a: &[f32], b: &[f32], metric: DistanceMetric) -> f32 {
match metric {
DistanceMetric::Cosine => {
(cosine_similarity(a, b) + 1.0) / 2.0
}
DistanceMetric::Euclidean => {
let dist = euclidean_distance(a, b);
libm::expf(-dist)
}
DistanceMetric::DotProduct => {
let dp = dot_product(a, b);
1.0 / (1.0 + libm::expf(-dp))
}
DistanceMetric::Manhattan => {
let dist = manhattan_distance(a, b);
libm::expf(-dist / a.len() as f32)
}
DistanceMetric::Hamming => {
(cosine_similarity(a, b) + 1.0) / 2.0
}
}
}
#[inline]
pub fn l2_norm(v: &[f32]) -> f32 {
let mut sum = 0.0f32;
for &x in v {
sum += x * x;
}
libm::sqrtf(sum)
}
#[inline]
pub fn l1_norm(v: &[f32]) -> f32 {
let mut sum = 0.0f32;
for &x in v {
sum += libm::fabsf(x);
}
sum
}
#[inline]
pub fn normalize_inplace(v: &mut [f32]) {
let norm = l2_norm(v);
if norm > 1e-10 {
for x in v.iter_mut() {
*x /= norm;
}
}
}
#[inline]
pub fn normalize(v: &[f32]) -> alloc::vec::Vec<f32> {
let norm = l2_norm(v);
if norm > 1e-10 {
v.iter().map(|x| x / norm).collect()
} else {
v.to_vec()
}
}
#[inline]
pub fn vector_add(a: &[f32], b: &[f32]) -> alloc::vec::Vec<f32> {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| x + y).collect()
}
#[inline]
pub fn vector_sub(a: &[f32], b: &[f32]) -> alloc::vec::Vec<f32> {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| x - y).collect()
}
#[inline]
pub fn vector_scale(v: &[f32], s: f32) -> alloc::vec::Vec<f32> {
v.iter().map(|x| x * s).collect()
}
pub fn centroid(vectors: &[&[f32]]) -> alloc::vec::Vec<f32> {
if vectors.is_empty() {
return alloc::vec::Vec::new();
}
let dim = vectors[0].len();
let mut result = alloc::vec![0.0f32; dim];
let n = vectors.len() as f32;
for v in vectors {
debug_assert_eq!(v.len(), dim);
for (i, &x) in v.iter().enumerate() {
result[i] += x;
}
}
for x in &mut result {
*x /= n;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 1e-6;
fn approx_eq(a: f32, b: f32) -> bool {
(a - b).abs() < EPSILON
}
#[test]
fn test_cosine_similarity_identical() {
let a = [1.0, 2.0, 3.0];
let b = [1.0, 2.0, 3.0];
assert!(approx_eq(cosine_similarity(&a, &b), 1.0));
}
#[test]
fn test_cosine_similarity_orthogonal() {
let a = [1.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0];
assert!(approx_eq(cosine_similarity(&a, &b), 0.0));
}
#[test]
fn test_cosine_similarity_opposite() {
let a = [1.0, 0.0, 0.0];
let b = [-1.0, 0.0, 0.0];
assert!(approx_eq(cosine_similarity(&a, &b), -1.0));
}
#[test]
fn test_cosine_similarity_scaled() {
let a = [1.0, 2.0, 3.0];
let b = [2.0, 4.0, 6.0]; assert!(approx_eq(cosine_similarity(&a, &b), 1.0));
}
#[test]
fn test_euclidean_distance_identical() {
let a = [1.0, 2.0, 3.0];
let b = [1.0, 2.0, 3.0];
assert!(approx_eq(euclidean_distance(&a, &b), 0.0));
}
#[test]
fn test_euclidean_distance_known() {
let a = [0.0, 0.0];
let b = [3.0, 4.0];
assert!(approx_eq(euclidean_distance(&a, &b), 5.0)); }
#[test]
fn test_euclidean_distance_unit() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
assert!(approx_eq(euclidean_distance(&a, &b), 1.0));
}
#[test]
fn test_dot_product_orthogonal() {
let a = [1.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0];
assert!(approx_eq(dot_product(&a, &b), 0.0));
}
#[test]
fn test_dot_product_parallel() {
let a = [1.0, 2.0, 3.0];
let b = [1.0, 2.0, 3.0];
assert!(approx_eq(dot_product(&a, &b), 14.0)); }
#[test]
fn test_manhattan_distance_known() {
let a = [0.0, 0.0];
let b = [3.0, 4.0];
assert!(approx_eq(manhattan_distance(&a, &b), 7.0)); }
#[test]
fn test_hamming_distance() {
let a = [0b10101010, 0b11110000];
let b = [0b10101010, 0b00001111];
assert_eq!(hamming_distance(&a, &b), 8); }
#[test]
fn test_l2_norm() {
let v = [3.0, 4.0];
assert!(approx_eq(l2_norm(&v), 5.0));
}
#[test]
fn test_normalize() {
let v = [3.0, 4.0];
let normalized = normalize(&v);
assert!(approx_eq(normalized[0], 0.6));
assert!(approx_eq(normalized[1], 0.8));
assert!(approx_eq(l2_norm(&normalized), 1.0));
}
#[test]
fn test_centroid() {
let v1 = [1.0, 0.0];
let v2 = [0.0, 1.0];
let v3 = [1.0, 1.0];
let c = centroid(&[&v1[..], &v2[..], &v3[..]]);
assert!(approx_eq(c[0], 2.0 / 3.0));
assert!(approx_eq(c[1], 2.0 / 3.0));
}
#[test]
fn test_compute_distance_cosine() {
let a = [1.0, 0.0];
let b = [0.0, 1.0];
let dist = compute_distance(&a, &b, DistanceMetric::Cosine);
assert!(approx_eq(dist, 1.0)); }
#[test]
fn test_compute_similarity_cosine() {
let a = [1.0, 0.0];
let b = [1.0, 0.0];
let sim = compute_similarity(&a, &b, DistanceMetric::Cosine);
assert!(approx_eq(sim, 1.0)); }
#[test]
fn test_high_dimensional_vectors() {
let dim = 512;
let a: alloc::vec::Vec<f32> = (0..dim).map(|i| (i as f32) / dim as f32).collect();
let b: alloc::vec::Vec<f32> = (0..dim).map(|i| (i as f32) / dim as f32).collect();
assert!(approx_eq(cosine_similarity(&a, &b), 1.0));
assert!(approx_eq(euclidean_distance(&a, &b), 0.0));
}
}