use crate::{ClusteringError, Result};
use ndarray::ArrayView1;
#[derive(Debug, Clone)]
pub enum Metric {
Euclidean,
Manhattan,
Chebyshev,
Minkowski(f64),
Cosine,
Correlation,
Mahalanobis(Vec<Vec<f64>>),
Geodesic(Manifold),
SpectralAngle,
DynamicTimeWarping,
Haversine,
Hamming,
Levenshtein,
JaroWinkler,
KullbackLeibler,
JensenShannon,
Wasserstein,
Hellinger,
Custom(fn(&ArrayView1<f64>, &ArrayView1<f64>) -> f64),
}
impl Metric {
pub fn distance(&self, x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> Result<f64> {
match self {
Metric::Euclidean => Ok(euclidean_distance(x, y)),
Metric::Manhattan => Ok(manhattan_distance(x, y)),
Metric::Cosine => Ok(cosine_distance(x, y)),
Metric::Custom(f) => Ok(f(x, y)),
_ => unimplemented!("Metric::distance for {:?}", self),
}
}
}
pub fn euclidean_distance(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> f64 {
x.iter()
.zip(y.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt()
}
pub fn manhattan_distance(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> f64 {
x.iter().zip(y.iter()).map(|(a, b)| (a - b).abs()).sum()
}
pub fn cosine_distance(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> f64 {
let dot_product: f64 = x.iter().zip(y.iter()).map(|(a, b)| a * b).sum();
let norm_x: f64 = x.iter().map(|a| a.powi(2)).sum::<f64>().sqrt();
let norm_y: f64 = y.iter().map(|b| b.powi(2)).sum::<f64>().sqrt();
if norm_x == 0.0 || norm_y == 0.0 {
return 1.0;
}
1.0 - (dot_product / (norm_x * norm_y))
}
#[derive(Debug, Clone)]
pub enum Manifold {
Schwarzschild { mass: f64 },
Kerr { mass: f64, spin: f64 },
Euclidean,
}
impl Manifold {
pub fn geodesic_distance(&self, x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> Result<f64> {
match self {
Manifold::Euclidean => Ok(euclidean_distance(x, y)),
_ => unimplemented!("Manifold::geodesic_distance for {:?}", self),
}
}
}