pub trait VectorDistance {
fn dot_product(&self, other: &Self) -> f64;
fn cosine_similarity(&self, other: &Self, normalized: bool) -> f64;
fn angular_distance(&self, other: &Self, normalized: bool) -> f64 {
self.cosine_similarity(other, normalized)
.clamp(-1.0, 1.0)
.acos()
/ std::f64::consts::PI
}
fn euclidean_distance(&self, other: &Self) -> f64;
fn manhattan_distance(&self, other: &Self) -> f64;
fn chebyshev_distance(&self, other: &Self) -> f64;
}
const CHUNK: usize = 256;
fn pair_sum(a: &[f64], b: &[f64], term: impl Fn(f64, f64) -> f64) -> f64 {
a.chunks(CHUNK)
.zip(b.chunks(CHUNK))
.map(|(a, b)| a.iter().zip(b).map(|(x, y)| term(*x, *y)).sum::<f64>())
.sum()
}
impl VectorDistance for crate::embeddings::Embedding {
fn dot_product(&self, other: &Self) -> f64 {
dot(&self.vec, &other.vec)
}
fn cosine_similarity(&self, other: &Self, normalized: bool) -> f64 {
let dot_product = dot(&self.vec, &other.vec);
if normalized {
return dot_product;
}
dot_product / (magnitude(&self.vec) * magnitude(&other.vec))
}
fn euclidean_distance(&self, other: &Self) -> f64 {
pair_sum(&self.vec, &other.vec, |x, y| (x - y).powi(2)).sqrt()
}
fn manhattan_distance(&self, other: &Self) -> f64 {
pair_sum(&self.vec, &other.vec, |x, y| (x - y).abs())
}
fn chebyshev_distance(&self, other: &Self) -> f64 {
self.vec
.iter()
.zip(&other.vec)
.map(|(x, y)| (x - y).abs())
.fold(0.0, f64::max)
}
}
pub(crate) fn dot(a: &[f64], b: &[f64]) -> f64 {
pair_sum(a, b, |x, y| x * y)
}
pub(crate) fn magnitude(vec: &[f64]) -> f64 {
pair_sum(vec, vec, |x, _| x.powi(2)).sqrt()
}
#[cfg(test)]
mod tests;