pub fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
return 2.0; }
let similarity = dot_product / (norm_a * norm_b);
let clamped_similarity = similarity.clamp(-1.0, 1.0);
1.0 - clamped_similarity
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cosine_distance_basic() {
let vec1 = vec![1.0, 0.0, 0.0];
let vec2 = vec![1.0, 0.0, 0.0];
let vec3 = vec![0.0, 1.0, 0.0];
let vec4 = vec![-1.0, 0.0, 0.0];
let vec5 = vec![0.0, 0.0, 0.0];
assert!((cosine_distance(&vec1, &vec2) - 0.0).abs() < 1e-6);
assert!((cosine_distance(&vec1, &vec3) - 1.0).abs() < 1e-6); assert!((cosine_distance(&vec1, &vec4) - 2.0).abs() < 1e-6); assert!((cosine_distance(&vec1, &vec5) - 2.0).abs() < 1e-6); assert!((cosine_distance(&vec5, &vec5) - 2.0).abs() < 1e-6); }
#[test]
fn test_cosine_distance_non_unit() {
let vec1 = vec![2.0, 0.0];
let vec2 = vec![4.0, 0.0];
let vec3 = vec![0.0, 3.0];
assert!(cosine_distance(&vec1, &vec2) < 1e-6);
assert!((cosine_distance(&vec1, &vec3) - 1.0).abs() < 1e-6);
}
}