#[inline]
pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let sum: f32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| {
let diff = x - y;
diff * diff
})
.sum();
sum.sqrt()
}
#[inline]
pub fn euclidean_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 cosine_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let mut dot = 0.0f32;
let mut norm_a = 0.0f32;
let mut norm_b = 0.0f32;
for (x, y) in a.iter().zip(b.iter()) {
dot += x * y;
norm_a += x * x;
norm_b += y * y;
}
let denominator = (norm_a * norm_b).sqrt();
if denominator == 0.0 {
return 1.0; }
1.0 - (dot / denominator)
}
#[inline]
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
1.0 - cosine_distance(a, b)
}
#[inline]
pub fn inner_product_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
-dot
}
#[inline]
pub fn dot_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 manhattan_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter().zip(b.iter()).map(|(x, y)| (x - y).abs()).sum()
}
#[inline]
pub fn hamming_distance_f32(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let count: u32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| {
let sign_a = x.to_bits() >> 31;
let sign_b = y.to_bits() >> 31;
(sign_a ^ sign_b) as u32
})
.sum();
count as f32
}
#[inline]
pub fn hamming_distance_binary(a: &[u64], b: &[u64]) -> u32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| (x ^ y).count_ones())
.sum()
}
#[inline]
pub fn jaccard_distance(a: &[u64], b: &[u64]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let mut intersection = 0u32;
let mut union = 0u32;
for (x, y) in a.iter().zip(b.iter()) {
intersection += (x & y).count_ones();
union += (x | y).count_ones();
}
if union == 0 {
return 0.0;
}
1.0 - (intersection as f32 / union as f32)
}
#[inline]
pub fn chebyshev_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.fold(0.0f32, f32::max)
}
#[inline]
pub fn minkowski_distance(a: &[f32], b: &[f32], p: f32) -> f32 {
debug_assert_eq!(a.len(), b.len());
if p == 1.0 {
return manhattan_distance(a, b);
}
if p == 2.0 {
return euclidean_distance(a, b);
}
if p == f32::INFINITY {
return chebyshev_distance(a, b);
}
let sum: f32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs().powf(p))
.sum();
sum.powf(1.0 / p)
}
#[inline]
pub fn canberra_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.map(|(x, y)| {
let num = (x - y).abs();
let denom = x.abs() + y.abs();
if denom == 0.0 {
0.0
} else {
num / denom
}
})
.sum()
}
#[inline]
pub fn bray_curtis_distance(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
let mut sum_diff = 0.0f32;
let mut sum_total = 0.0f32;
for (x, y) in a.iter().zip(b.iter()) {
sum_diff += (x - y).abs();
sum_total += x.abs() + y.abs();
}
if sum_total == 0.0 {
return 0.0;
}
sum_diff / sum_total
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euclidean() {
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((euclidean_distance(&a, &b) - 5.0).abs() < 1e-6);
}
#[test]
fn test_euclidean_squared() {
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((euclidean_distance_squared(&a, &b) - 25.0).abs() < 1e-6);
}
#[test]
fn test_cosine_same_direction() {
let a = vec![1.0, 0.0, 0.0];
let b = vec![2.0, 0.0, 0.0];
assert!(cosine_distance(&a, &b).abs() < 1e-6);
}
#[test]
fn test_cosine_opposite() {
let a = vec![1.0, 0.0, 0.0];
let b = vec![-1.0, 0.0, 0.0];
assert!((cosine_distance(&a, &b) - 2.0).abs() < 1e-6);
}
#[test]
fn test_cosine_orthogonal() {
let a = vec![1.0, 0.0, 0.0];
let b = vec![0.0, 1.0, 0.0];
assert!((cosine_distance(&a, &b) - 1.0).abs() < 1e-6);
}
#[test]
fn test_inner_product() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
assert!((inner_product_distance(&a, &b) - (-32.0)).abs() < 1e-6);
}
#[test]
fn test_manhattan() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 6.0, 8.0];
assert!((manhattan_distance(&a, &b) - 12.0).abs() < 1e-6);
}
#[test]
fn test_hamming_binary() {
let a = vec![0b1010_1010u64];
let b = vec![0b1111_0000u64];
let dist = hamming_distance_binary(&a, &b);
assert_eq!(dist, 4);
}
#[test]
fn test_chebyshev() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 10.0, 5.0];
assert!((chebyshev_distance(&a, &b) - 8.0).abs() < 1e-6);
}
#[test]
fn test_minkowski_p1() {
let a = vec![1.0, 2.0];
let b = vec![4.0, 6.0];
assert!((minkowski_distance(&a, &b, 1.0) - manhattan_distance(&a, &b)).abs() < 1e-6);
}
#[test]
fn test_minkowski_p2() {
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((minkowski_distance(&a, &b, 2.0) - euclidean_distance(&a, &b)).abs() < 1e-6);
}
}