#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
use num_traits::Float;
use wide::{f32x8, f64x4};
pub trait DistanceLinalg: Float + 'static {
fn euclidean_distance(a: &[Self], b: &[Self]) -> Self;
fn normalized_distance(a: &[Self], b: &[Self], scales: &[Self]) -> Self;
fn weighted_distance(a: &[Self], b: &[Self], weights: &[Self]) -> Self;
fn manhattan_distance(a: &[Self], b: &[Self]) -> Self;
fn chebyshev_distance(a: &[Self], b: &[Self]) -> Self;
fn minkowski_distance(a: &[Self], b: &[Self], p: Self) -> Self;
fn euclidean_distance_squared(a: &[Self], b: &[Self]) -> Self;
fn normalized_distance_squared(a: &[Self], b: &[Self], scales: &[Self]) -> Self;
fn weighted_distance_squared(a: &[Self], b: &[Self], weights: &[Self]) -> Self;
fn manhattan_distance_squared(a: &[Self], b: &[Self]) -> Self;
fn chebyshev_distance_squared(a: &[Self], b: &[Self]) -> Self;
fn minkowski_distance_squared(a: &[Self], b: &[Self], p: Self) -> Self;
}
impl DistanceLinalg for f64 {
#[inline]
fn euclidean_distance(a: &[Self], b: &[Self]) -> Self {
simd_distance::euclidean_f64(a, b)
}
#[inline]
fn normalized_distance(a: &[Self], b: &[Self], scales: &[Self]) -> Self {
simd_distance::normalized_f64(a, b, scales)
}
#[inline]
fn weighted_distance(a: &[Self], b: &[Self], weights: &[Self]) -> Self {
simd_distance::weighted_f64(a, b, weights)
}
#[inline]
fn manhattan_distance(a: &[Self], b: &[Self]) -> Self {
simd_distance::manhattan_f64(a, b)
}
#[inline]
fn chebyshev_distance(a: &[Self], b: &[Self]) -> Self {
simd_distance::chebyshev_f64(a, b)
}
#[inline]
fn minkowski_distance(a: &[Self], b: &[Self], p: Self) -> Self {
simd_distance::minkowski_f64(a, b, p)
}
#[inline]
fn euclidean_distance_squared(a: &[Self], b: &[Self]) -> Self {
simd_distance::euclidean_sq_f64(a, b)
}
#[inline]
fn normalized_distance_squared(a: &[Self], b: &[Self], scales: &[Self]) -> Self {
simd_distance::normalized_sq_f64(a, b, scales)
}
#[inline]
fn weighted_distance_squared(a: &[Self], b: &[Self], weights: &[Self]) -> Self {
simd_distance::weighted_sq_f64(a, b, weights)
}
#[inline]
fn manhattan_distance_squared(a: &[Self], b: &[Self]) -> Self {
let d = simd_distance::manhattan_f64(a, b);
d * d
}
#[inline]
fn chebyshev_distance_squared(a: &[Self], b: &[Self]) -> Self {
let d = simd_distance::chebyshev_f64(a, b);
d * d
}
#[inline]
fn minkowski_distance_squared(a: &[Self], b: &[Self], p: Self) -> Self {
let d = simd_distance::minkowski_f64(a, b, p);
d * d
}
}
impl DistanceLinalg for f32 {
#[inline]
fn euclidean_distance(a: &[Self], b: &[Self]) -> Self {
simd_distance::euclidean_f32(a, b)
}
#[inline]
fn normalized_distance(a: &[Self], b: &[Self], scales: &[Self]) -> Self {
simd_distance::normalized_f32(a, b, scales)
}
#[inline]
fn weighted_distance(a: &[Self], b: &[Self], weights: &[Self]) -> Self {
simd_distance::weighted_f32(a, b, weights)
}
#[inline]
fn manhattan_distance(a: &[Self], b: &[Self]) -> Self {
simd_distance::manhattan_f32(a, b)
}
#[inline]
fn chebyshev_distance(a: &[Self], b: &[Self]) -> Self {
simd_distance::chebyshev_f32(a, b)
}
#[inline]
fn minkowski_distance(a: &[Self], b: &[Self], p: Self) -> Self {
simd_distance::minkowski_f32(a, b, p)
}
#[inline]
fn euclidean_distance_squared(a: &[Self], b: &[Self]) -> Self {
simd_distance::euclidean_sq_f32(a, b)
}
#[inline]
fn normalized_distance_squared(a: &[Self], b: &[Self], scales: &[Self]) -> Self {
simd_distance::normalized_sq_f32(a, b, scales)
}
#[inline]
fn weighted_distance_squared(a: &[Self], b: &[Self], weights: &[Self]) -> Self {
simd_distance::weighted_sq_f32(a, b, weights)
}
#[inline]
fn manhattan_distance_squared(a: &[Self], b: &[Self]) -> Self {
let d = simd_distance::manhattan_f32(a, b);
d * d
}
#[inline]
fn chebyshev_distance_squared(a: &[Self], b: &[Self]) -> Self {
let d = simd_distance::chebyshev_f32(a, b);
d * d
}
#[inline]
fn minkowski_distance_squared(a: &[Self], b: &[Self], p: Self) -> Self {
let d = simd_distance::minkowski_f32(a, b, p);
d * d
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum DistanceMetric<T> {
Euclidean,
#[default]
Normalized,
Manhattan,
Chebyshev,
Minkowski(T),
Weighted(Vec<T>),
}
impl<T: DistanceLinalg> DistanceMetric<T> {
#[inline]
pub fn euclidean(a: &[T], b: &[T]) -> T {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
T::euclidean_distance(a, b)
}
#[inline]
pub fn normalized(a: &[T], b: &[T], scales: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
debug_assert_eq!(a.len(), scales.len());
T::normalized_distance(a, b, scales)
}
#[inline]
pub fn manhattan(a: &[T], b: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::manhattan_distance(a, b)
}
#[inline]
pub fn chebyshev(a: &[T], b: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::chebyshev_distance(a, b)
}
#[inline]
pub fn minkowski(a: &[T], b: &[T], p: T) -> T {
debug_assert_eq!(a.len(), b.len());
T::minkowski_distance(a, b, p)
}
#[inline]
pub fn weighted(a: &[T], b: &[T], weights: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
debug_assert_eq!(a.len(), weights.len());
T::weighted_distance(a, b, weights)
}
#[inline]
pub fn euclidean_squared(a: &[T], b: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::euclidean_distance_squared(a, b)
}
#[inline]
pub fn normalized_squared(a: &[T], b: &[T], scales: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::normalized_distance_squared(a, b, scales)
}
#[inline]
pub fn weighted_squared(a: &[T], b: &[T], weights: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::weighted_distance_squared(a, b, weights)
}
#[inline]
pub fn manhattan_squared(a: &[T], b: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::manhattan_distance_squared(a, b)
}
#[inline]
pub fn chebyshev_squared(a: &[T], b: &[T]) -> T {
debug_assert_eq!(a.len(), b.len());
T::chebyshev_distance_squared(a, b)
}
#[inline]
pub fn minkowski_squared(a: &[T], b: &[T], p: T) -> T {
debug_assert_eq!(a.len(), b.len());
T::minkowski_distance_squared(a, b, p)
}
}
pub mod simd_distance {
use super::*;
#[inline]
pub fn euclidean_f64(a: &[f64], b: &[f64]) -> f64 {
euclidean_sq_f64(a, b).sqrt()
}
#[inline]
pub fn euclidean_sq_f64(a: &[f64], b: &[f64]) -> f64 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
let n = a.len();
if n < 4 {
return euclidean_sq_scalar(a, b);
}
let chunks = n / 4;
let remainder = n % 4;
let mut sum = f64x4::ZERO;
for i in 0..chunks {
let base = i * 4;
let va = f64x4::new([a[base], a[base + 1], a[base + 2], a[base + 3]]);
let vb = f64x4::new([b[base], b[base + 1], b[base + 2], b[base + 3]]);
let diff = va - vb;
sum += diff * diff;
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3];
let base = chunks * 4;
for i in 0..remainder {
let diff = a[base + i] - b[base + i];
total += diff * diff;
}
total
}
#[inline]
pub fn euclidean_sq_f32(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
let n = a.len();
if n < 8 {
return euclidean_sq_scalar(a, b);
}
let chunks = n / 8;
let remainder = n % 8;
let mut sum = f32x8::ZERO;
for i in 0..chunks {
let base = i * 8;
let va = f32x8::new([
a[base],
a[base + 1],
a[base + 2],
a[base + 3],
a[base + 4],
a[base + 5],
a[base + 6],
a[base + 7],
]);
let vb = f32x8::new([
b[base],
b[base + 1],
b[base + 2],
b[base + 3],
b[base + 4],
b[base + 5],
b[base + 6],
b[base + 7],
]);
let diff = va - vb;
sum += diff * diff;
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7];
let base = chunks * 8;
for i in 0..remainder {
let diff = a[base + i] - b[base + i];
total += diff * diff;
}
total
}
#[inline]
pub fn euclidean_f32(a: &[f32], b: &[f32]) -> f32 {
euclidean_sq_f32(a, b).sqrt()
}
#[inline]
pub fn normalized_f64(a: &[f64], b: &[f64], scales: &[f64]) -> f64 {
normalized_sq_f64(a, b, scales).sqrt()
}
#[inline]
pub fn normalized_sq_f64(a: &[f64], b: &[f64], scales: &[f64]) -> f64 {
debug_assert_eq!(a.len(), b.len());
debug_assert_eq!(a.len(), scales.len());
let n = a.len();
if n < 4 {
return normalized_sq_scalar(a, b, scales);
}
let chunks = n / 4;
let remainder = n % 4;
let mut sum = f64x4::ZERO;
for i in 0..chunks {
let base = i * 4;
let va = f64x4::new([a[base], a[base + 1], a[base + 2], a[base + 3]]);
let vb = f64x4::new([b[base], b[base + 1], b[base + 2], b[base + 3]]);
let vs = f64x4::new([
scales[base],
scales[base + 1],
scales[base + 2],
scales[base + 3],
]);
let diff = (va - vb) * vs;
sum += diff * diff;
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3];
let base = chunks * 4;
for i in 0..remainder {
let diff = (a[base + i] - b[base + i]) * scales[base + i];
total += diff * diff;
}
total
}
#[inline]
pub fn normalized_f32(a: &[f32], b: &[f32], scales: &[f32]) -> f32 {
normalized_sq_f32(a, b, scales).sqrt()
}
#[inline]
pub fn normalized_sq_f32(a: &[f32], b: &[f32], scales: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
debug_assert_eq!(a.len(), scales.len());
let n = a.len();
if n < 8 {
return normalized_sq_scalar(a, b, scales);
}
let chunks = n / 8;
let remainder = n % 8;
let mut sum = f32x8::ZERO;
for i in 0..chunks {
let base = i * 8;
let va = f32x8::new([
a[base],
a[base + 1],
a[base + 2],
a[base + 3],
a[base + 4],
a[base + 5],
a[base + 6],
a[base + 7],
]);
let vb = f32x8::new([
b[base],
b[base + 1],
b[base + 2],
b[base + 3],
b[base + 4],
b[base + 5],
b[base + 6],
b[base + 7],
]);
let vs = f32x8::new([
scales[base],
scales[base + 1],
scales[base + 2],
scales[base + 3],
scales[base + 4],
scales[base + 5],
scales[base + 6],
scales[base + 7],
]);
let diff = (va - vb) * vs;
sum += diff * diff;
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7];
let base = chunks * 8;
for i in 0..remainder {
let diff = (a[base + i] - b[base + i]) * scales[base + i];
total += diff * diff;
}
total
}
#[inline]
pub fn weighted_f64(a: &[f64], b: &[f64], weights: &[f64]) -> f64 {
weighted_sq_f64(a, b, weights).sqrt()
}
#[inline]
pub fn weighted_sq_f64(a: &[f64], b: &[f64], weights: &[f64]) -> f64 {
debug_assert_eq!(a.len(), b.len());
debug_assert_eq!(a.len(), weights.len());
let n = a.len();
if n < 4 {
return weighted_sq_scalar(a, b, weights);
}
let chunks = n / 4;
let remainder = n % 4;
let mut sum = f64x4::ZERO;
for i in 0..chunks {
let base = i * 4;
let va = f64x4::new([a[base], a[base + 1], a[base + 2], a[base + 3]]);
let vb = f64x4::new([b[base], b[base + 1], b[base + 2], b[base + 3]]);
let vw = f64x4::new([
weights[base],
weights[base + 1],
weights[base + 2],
weights[base + 3],
]);
let diff = va - vb;
sum += vw * diff * diff;
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3];
let base = chunks * 4;
for i in 0..remainder {
let diff = a[base + i] - b[base + i];
total += weights[base + i] * diff * diff;
}
total
}
#[inline]
pub fn weighted_sq_f32(a: &[f32], b: &[f32], weights: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
debug_assert_eq!(a.len(), weights.len());
let n = a.len();
if n < 8 {
return weighted_sq_scalar(a, b, weights);
}
let chunks = n / 8;
let remainder = n % 8;
let mut sum = f32x8::ZERO;
for i in 0..chunks {
let base = i * 8;
let va = f32x8::new([
a[base],
a[base + 1],
a[base + 2],
a[base + 3],
a[base + 4],
a[base + 5],
a[base + 6],
a[base + 7],
]);
let vb = f32x8::new([
b[base],
b[base + 1],
b[base + 2],
b[base + 3],
b[base + 4],
b[base + 5],
b[base + 6],
b[base + 7],
]);
let vw = f32x8::new([
weights[base],
weights[base + 1],
weights[base + 2],
weights[base + 3],
weights[base + 4],
weights[base + 5],
weights[base + 6],
weights[base + 7],
]);
let diff = va - vb;
sum += vw * diff * diff;
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7];
let base = chunks * 8;
for i in 0..remainder {
let diff = a[base + i] - b[base + i];
total += weights[base + i] * diff * diff;
}
total
}
#[inline]
pub fn weighted_f32(a: &[f32], b: &[f32], weights: &[f32]) -> f32 {
weighted_sq_f32(a, b, weights).sqrt()
}
#[inline]
pub fn manhattan_f64(a: &[f64], b: &[f64]) -> f64 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
let n = a.len();
if n < 4 {
return manhattan_scalar(a, b);
}
let chunks = n / 4;
let remainder = n % 4;
let mut sum = f64x4::ZERO;
for i in 0..chunks {
let base = i * 4;
let va = f64x4::new([a[base], a[base + 1], a[base + 2], a[base + 3]]);
let vb = f64x4::new([b[base], b[base + 1], b[base + 2], b[base + 3]]);
let diff = va - vb;
sum += diff.abs();
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3];
let base = chunks * 4;
for i in 0..remainder {
total += (a[base + i] - b[base + i]).abs();
}
total
}
#[inline]
pub fn manhattan_f32(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
let n = a.len();
if n < 8 {
return manhattan_scalar(a, b);
}
let chunks = n / 8;
let remainder = n % 8;
let mut sum = f32x8::ZERO;
for i in 0..chunks {
let base = i * 8;
let va = f32x8::new([
a[base],
a[base + 1],
a[base + 2],
a[base + 3],
a[base + 4],
a[base + 5],
a[base + 6],
a[base + 7],
]);
let vb = f32x8::new([
b[base],
b[base + 1],
b[base + 2],
b[base + 3],
b[base + 4],
b[base + 5],
b[base + 6],
b[base + 7],
]);
let diff = va - vb;
sum += diff.abs();
}
let arr = sum.to_array();
let mut total = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7];
let base = chunks * 8;
for i in 0..remainder {
total += (a[base + i] - b[base + i]).abs();
}
total
}
#[inline]
pub fn chebyshev_f64(a: &[f64], b: &[f64]) -> f64 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
let n = a.len();
if n < 4 {
return chebyshev_scalar(a, b);
}
let chunks = n / 4;
let remainder = n % 4;
let mut max_vec = f64x4::ZERO;
for i in 0..chunks {
let base = i * 4;
let va = f64x4::new([a[base], a[base + 1], a[base + 2], a[base + 3]]);
let vb = f64x4::new([b[base], b[base + 1], b[base + 2], b[base + 3]]);
let diff = (va - vb).abs();
max_vec = max_vec.max(diff);
}
let arr = max_vec.to_array();
let mut total = arr[0].max(arr[1]).max(arr[2]).max(arr[3]);
let base = chunks * 4;
for i in 0..remainder {
total = total.max((a[base + i] - b[base + i]).abs());
}
total
}
#[inline]
pub fn chebyshev_f32(a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
let n = a.len();
if n < 8 {
return chebyshev_scalar(a, b);
}
let chunks = n / 8;
let remainder = n % 8;
let mut max_vec = f32x8::ZERO;
for i in 0..chunks {
let base = i * 8;
let va = f32x8::new([
a[base],
a[base + 1],
a[base + 2],
a[base + 3],
a[base + 4],
a[base + 5],
a[base + 6],
a[base + 7],
]);
let vb = f32x8::new([
b[base],
b[base + 1],
b[base + 2],
b[base + 3],
b[base + 4],
b[base + 5],
b[base + 6],
b[base + 7],
]);
let diff = (va - vb).abs();
max_vec = max_vec.max(diff);
}
let arr = max_vec.to_array();
let mut total = arr[0]
.max(arr[1])
.max(arr[2])
.max(arr[3])
.max(arr[4])
.max(arr[5])
.max(arr[6])
.max(arr[7]);
let base = chunks * 8;
for i in 0..remainder {
total = total.max((a[base + i] - b[base + i]).abs());
}
total
}
#[inline]
pub fn minkowski_f64(a: &[f64], b: &[f64], p: f64) -> f64 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
if (p - 1.0).abs() < f64::EPSILON {
return manhattan_f64(a, b);
}
if (p - 2.0).abs() < f64::EPSILON {
return euclidean_f64(a, b);
}
let sum_pow: f64 = a
.iter()
.zip(b.iter())
.map(|(&ai, &bi)| (ai - bi).abs().powf(p))
.sum();
sum_pow.powf(1.0 / p)
}
#[inline]
pub fn minkowski_f32(a: &[f32], b: &[f32], p: f32) -> f32 {
debug_assert_eq!(a.len(), b.len(), "Points must have same dimension");
if (p - 1.0).abs() < f32::EPSILON {
return manhattan_f32(a, b);
}
if (p - 2.0).abs() < f32::EPSILON {
return euclidean_f32(a, b);
}
let sum_pow: f32 = a
.iter()
.zip(b.iter())
.map(|(&ai, &bi)| (ai - bi).abs().powf(p))
.sum();
sum_pow.powf(1.0 / p)
}
#[inline]
fn euclidean_sq_scalar<T: Float>(a: &[T], b: &[T]) -> T {
a.iter()
.zip(b.iter())
.map(|(&ai, &bi)| {
let diff = ai - bi;
diff * diff
})
.fold(T::zero(), |acc, x| acc + x)
}
#[inline]
fn normalized_sq_scalar<T: Float>(a: &[T], b: &[T], scales: &[T]) -> T {
a.iter()
.zip(b.iter())
.zip(scales.iter())
.map(|((&ai, &bi), &scale)| {
let diff = (ai - bi) * scale;
diff * diff
})
.fold(T::zero(), |acc, x| acc + x)
}
#[inline]
fn weighted_sq_scalar<T: Float>(a: &[T], b: &[T], weights: &[T]) -> T {
a.iter()
.zip(b.iter())
.zip(weights.iter())
.map(|((&ai, &bi), &w)| {
let diff = ai - bi;
w * diff * diff
})
.fold(T::zero(), |acc, x| acc + x)
}
#[inline]
fn manhattan_scalar<T: Float>(a: &[T], b: &[T]) -> T {
a.iter()
.zip(b.iter())
.map(|(&ai, &bi)| (ai - bi).abs())
.fold(T::zero(), |acc, x| acc + x)
}
#[inline]
fn chebyshev_scalar<T: Float>(a: &[T], b: &[T]) -> T {
a.iter()
.zip(b.iter())
.map(|(&ai, &bi)| (ai - bi).abs())
.fold(T::zero(), T::max)
}
}