#![allow(clippy::needless_range_loop)]
#![allow(dead_code)]
use std::cmp::Ordering;
use std::collections::BinaryHeap;
pub trait MetricSpace {
type Point;
fn distance(&self, a: &Self::Point, b: &Self::Point) -> f64;
fn ball<'a>(
&self,
center: &Self::Point,
radius: f64,
candidates: &'a [Self::Point],
) -> Vec<&'a Self::Point> {
candidates
.iter()
.filter(|p| self.distance(center, p) <= radius)
.collect()
}
fn is_bounded(&self, points: &[Self::Point]) -> bool {
if points.is_empty() {
return true;
}
let d = self.diameter(points);
d.is_finite()
}
fn diameter(&self, points: &[Self::Point]) -> f64 {
if points.len() < 2 {
return 0.0;
}
let mut max_d = 0.0_f64;
for i in 0..points.len() {
for j in (i + 1)..points.len() {
let d = self.distance(&points[i], &points[j]);
if d > max_d {
max_d = d;
}
}
}
max_d
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EuclideanMetric;
impl MetricSpace for EuclideanMetric {
type Point = Vec<f64>;
fn distance(&self, a: &Vec<f64>, b: &Vec<f64>) -> f64 {
assert_eq!(a.len(), b.len(), "EuclideanMetric: dimension mismatch");
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
}
impl EuclideanMetric {
pub fn norm(v: &[f64]) -> f64 {
v.iter().map(|x| x.powi(2)).sum::<f64>().sqrt()
}
pub fn dot(a: &[f64], b: &[f64]) -> f64 {
assert_eq!(a.len(), b.len(), "dot: dimension mismatch");
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
pub fn cauchy_schwarz_holds(a: &[f64], b: &[f64]) -> bool {
let dot = Self::dot(a, b).abs();
let product = Self::norm(a) * Self::norm(b);
dot <= product + 1e-9
}
pub fn scalar_projection(v: &[f64], u: &[f64]) -> f64 {
Self::dot(v, u) / Self::norm(u)
}
pub fn normalize(v: &[f64]) -> Option<Vec<f64>> {
let n = Self::norm(v);
if n < 1e-15 {
return None;
}
Some(v.iter().map(|x| x / n).collect())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ManhattanMetric;
impl MetricSpace for ManhattanMetric {
type Point = Vec<f64>;
fn distance(&self, a: &Vec<f64>, b: &Vec<f64>) -> f64 {
assert_eq!(a.len(), b.len(), "ManhattanMetric: dimension mismatch");
a.iter().zip(b.iter()).map(|(x, y)| (x - y).abs()).sum()
}
}
impl ManhattanMetric {
pub fn norm(v: &[f64]) -> f64 {
v.iter().map(|x| x.abs()).sum()
}
pub fn in_unit_ball(v: &[f64]) -> bool {
Self::norm(v) <= 1.0 + 1e-12
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ChebyshevMetric;
impl MetricSpace for ChebyshevMetric {
type Point = Vec<f64>;
fn distance(&self, a: &Vec<f64>, b: &Vec<f64>) -> f64 {
assert_eq!(a.len(), b.len(), "ChebyshevMetric: dimension mismatch");
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.fold(0.0_f64, f64::max)
}
}
impl ChebyshevMetric {
pub fn norm(v: &[f64]) -> f64 {
v.iter().map(|x| x.abs()).fold(0.0_f64, f64::max)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct HammingMetric;
#[derive(Debug, Clone, PartialEq)]
pub struct HammingPoint(pub Vec<u8>);
impl MetricSpace for HammingMetric {
type Point = HammingPoint;
fn distance(&self, a: &HammingPoint, b: &HammingPoint) -> f64 {
assert_eq!(
a.0.len(),
b.0.len(),
"HammingMetric: length mismatch ({} vs {})",
a.0.len(),
b.0.len()
);
a.0.iter().zip(b.0.iter()).filter(|(x, y)| x != y).count() as f64
}
}
impl HammingMetric {
pub fn hamming_distance(a: &[u8], b: &[u8]) -> usize {
assert_eq!(a.len(), b.len(), "hamming_distance: length mismatch");
a.iter().zip(b.iter()).filter(|(x, y)| x != y).count()
}
pub fn hamming_weight(v: &[u8]) -> usize {
v.iter().map(|b| b.count_ones() as usize).sum()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EditDistance;
#[derive(Debug, Clone, PartialEq)]
pub struct Sequence(pub Vec<char>);
impl MetricSpace for EditDistance {
type Point = Sequence;
fn distance(&self, a: &Sequence, b: &Sequence) -> f64 {
levenshtein(&a.0, &b.0) as f64
}
}
pub fn levenshtein(a: &[char], b: &[char]) -> usize {
let m = a.len();
let n = b.len();
if m == 0 {
return n;
}
if n == 0 {
return m;
}
let mut prev: Vec<usize> = (0..=n).collect();
let mut curr = vec![0usize; n + 1];
for i in 1..=m {
curr[0] = i;
for j in 1..=n {
let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}
pub fn levenshtein_str(a: &str, b: &str) -> usize {
let ac: Vec<char> = a.chars().collect();
let bc: Vec<char> = b.chars().collect();
levenshtein(&ac, &bc)
}
impl EditDistance {
pub fn lcs_length(a: &[char], b: &[char]) -> usize {
let m = a.len();
let n = b.len();
let mut dp = vec![vec![0usize; n + 1]; m + 1];
for i in 1..=m {
for j in 1..=n {
dp[i][j] = if a[i - 1] == b[j - 1] {
dp[i - 1][j - 1] + 1
} else {
dp[i - 1][j].max(dp[i][j - 1])
};
}
}
dp[m][n]
}
#[allow(clippy::too_many_arguments)]
pub fn weighted_edit(
a: &[char],
b: &[char],
insert_cost: f64,
delete_cost: f64,
subst_cost: f64,
) -> f64 {
let m = a.len();
let n = b.len();
if m == 0 {
return n as f64 * insert_cost;
}
if n == 0 {
return m as f64 * delete_cost;
}
let mut prev: Vec<f64> = (0..=n).map(|j| j as f64 * insert_cost).collect();
let mut curr = vec![0.0_f64; n + 1];
for i in 1..=m {
curr[0] = i as f64 * delete_cost;
for j in 1..=n {
let cost = if a[i - 1] == b[j - 1] {
0.0
} else {
subst_cost
};
curr[j] = (prev[j] + delete_cost)
.min(curr[j - 1] + insert_cost)
.min(prev[j - 1] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}
}
#[derive(Debug, Clone, Copy)]
pub struct MinkowskiMetric {
pub p: f64,
}
impl MinkowskiMetric {
pub fn new(p: f64) -> Self {
assert!(p >= 1.0, "MinkowskiMetric: p must be >= 1, got {p}");
Self { p }
}
}
impl MetricSpace for MinkowskiMetric {
type Point = Vec<f64>;
fn distance(&self, a: &Vec<f64>, b: &Vec<f64>) -> f64 {
assert_eq!(a.len(), b.len(), "MinkowskiMetric: dimension mismatch");
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs().powf(self.p))
.sum::<f64>()
.powf(1.0 / self.p)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CosineDistance;
impl CosineDistance {
pub fn similarity(a: &[f64], b: &[f64]) -> f64 {
let dot: f64 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let na = EuclideanMetric::norm(a);
let nb = EuclideanMetric::norm(b);
if na < 1e-15 || nb < 1e-15 {
return 0.0;
}
(dot / (na * nb)).clamp(-1.0, 1.0)
}
pub fn distance(a: &[f64], b: &[f64]) -> f64 {
1.0 - Self::similarity(a, b)
}
}
#[derive(Debug, Clone)]
struct BallTreeNode {
pivot_idx: usize,
radius: f64,
left: usize,
right: usize,
leaf_points: Vec<usize>,
}
impl BallTreeNode {
fn is_leaf(&self) -> bool {
self.left == usize::MAX && self.right == usize::MAX
}
}
pub struct MetricBallTree {
points: Vec<Vec<f64>>,
nodes: Vec<BallTreeNode>,
root: usize,
leaf_size: usize,
}
#[derive(Debug, Clone)]
pub struct NearestNeighbor {
pub index: usize,
pub distance: f64,
}
impl PartialEq for NearestNeighbor {
fn eq(&self, other: &Self) -> bool {
self.distance == other.distance
}
}
impl Eq for NearestNeighbor {}
impl PartialOrd for NearestNeighbor {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NearestNeighbor {
fn cmp(&self, other: &Self) -> Ordering {
other
.distance
.partial_cmp(&self.distance)
.unwrap_or(Ordering::Equal)
}
}
impl MetricBallTree {
pub fn build(points: Vec<Vec<f64>>, leaf_size: usize) -> Self {
let n = points.len();
assert!(!points.is_empty(), "MetricBallTree: empty dataset");
let leaf_size = leaf_size.max(1);
let mut nodes: Vec<BallTreeNode> = Vec::with_capacity(2 * n / leaf_size + 4);
let indices: Vec<usize> = (0..n).collect();
let root = build_node(&points, &indices, &mut nodes, leaf_size);
Self {
points,
nodes,
root,
leaf_size,
}
}
pub fn knn(&self, query: &[f64], k: usize) -> Vec<NearestNeighbor> {
let mut heap: BinaryHeap<NearestNeighbor> = BinaryHeap::new();
let metric = EuclideanMetric;
knn_search(
&self.points,
&self.nodes,
self.root,
query,
k,
&mut heap,
&metric,
);
let mut result: Vec<NearestNeighbor> = heap.into_sorted_vec();
result.sort_by(|a, b| {
a.distance
.partial_cmp(&b.distance)
.unwrap_or(Ordering::Equal)
});
result
}
pub fn range_query(&self, query: &[f64], radius: f64) -> Vec<NearestNeighbor> {
let metric = EuclideanMetric;
let mut result = Vec::new();
range_search(
&self.points,
&self.nodes,
self.root,
query,
radius,
&mut result,
&metric,
);
result.sort_by(|a, b| {
a.distance
.partial_cmp(&b.distance)
.unwrap_or(Ordering::Equal)
});
result
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn leaf_size(&self) -> usize {
self.leaf_size
}
}
fn euclidean_dist(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
fn centroid(points: &[Vec<f64>], indices: &[usize]) -> Vec<f64> {
let dim = points[indices[0]].len();
let mut c = vec![0.0; dim];
for &idx in indices {
for (d, &v) in c.iter_mut().zip(points[idx].iter()) {
*d += v;
}
}
let n = indices.len() as f64;
c.iter_mut().for_each(|v| *v /= n);
c
}
fn build_node(
points: &[Vec<f64>],
indices: &[usize],
nodes: &mut Vec<BallTreeNode>,
leaf_size: usize,
) -> usize {
let c = centroid(points, indices);
let pivot_idx = indices
.iter()
.copied()
.max_by(|&a, &b| {
euclidean_dist(&points[a], &c)
.partial_cmp(&euclidean_dist(&points[b], &c))
.unwrap_or(Ordering::Equal)
})
.expect("indices is non-empty");
let radius = indices
.iter()
.map(|&i| euclidean_dist(&points[i], &points[pivot_idx]))
.fold(0.0_f64, f64::max);
let node_idx = nodes.len();
if indices.len() <= leaf_size {
nodes.push(BallTreeNode {
pivot_idx,
radius,
left: usize::MAX,
right: usize::MAX,
leaf_points: indices.to_vec(),
});
return node_idx;
}
let mut left_idx: Vec<usize> = Vec::new();
let mut right_idx: Vec<usize> = Vec::new();
for &i in indices {
if i == pivot_idx {
left_idx.push(i);
continue;
}
if left_idx.len() <= right_idx.len() {
left_idx.push(i);
} else {
right_idx.push(i);
}
}
if left_idx.is_empty() {
left_idx.push(pivot_idx);
}
if right_idx.is_empty() {
right_idx = left_idx.split_off(left_idx.len() / 2 + 1);
}
nodes.push(BallTreeNode {
pivot_idx,
radius,
left: usize::MAX,
right: usize::MAX,
leaf_points: vec![],
});
let left_child = build_node(points, &left_idx, nodes, leaf_size);
let right_child = build_node(points, &right_idx, nodes, leaf_size);
nodes[node_idx].left = left_child;
nodes[node_idx].right = right_child;
node_idx
}
fn knn_search(
points: &[Vec<f64>],
nodes: &[BallTreeNode],
node_idx: usize,
query: &[f64],
k: usize,
heap: &mut BinaryHeap<NearestNeighbor>,
_metric: &EuclideanMetric,
) {
let node = &nodes[node_idx];
let pivot_dist = euclidean_dist(query, &points[node.pivot_idx]);
if heap.len() >= k {
let worst = heap.peek().map(|n| n.distance).unwrap_or(f64::MAX);
if pivot_dist - node.radius > worst {
return;
}
}
if node.is_leaf() {
for &idx in &node.leaf_points {
let d = euclidean_dist(query, &points[idx]);
if heap.len() < k {
heap.push(NearestNeighbor {
index: idx,
distance: d,
});
} else if let Some(worst) = heap.peek()
&& d < worst.distance
{
heap.pop();
heap.push(NearestNeighbor {
index: idx,
distance: d,
});
}
}
return;
}
let left_dist = if node.left != usize::MAX {
euclidean_dist(query, &points[nodes[node.left].pivot_idx])
} else {
f64::MAX
};
let right_dist = if node.right != usize::MAX {
euclidean_dist(query, &points[nodes[node.right].pivot_idx])
} else {
f64::MAX
};
if left_dist <= right_dist {
if node.left != usize::MAX {
knn_search(points, nodes, node.left, query, k, heap, _metric);
}
if node.right != usize::MAX {
knn_search(points, nodes, node.right, query, k, heap, _metric);
}
} else {
if node.right != usize::MAX {
knn_search(points, nodes, node.right, query, k, heap, _metric);
}
if node.left != usize::MAX {
knn_search(points, nodes, node.left, query, k, heap, _metric);
}
}
}
fn range_search(
points: &[Vec<f64>],
nodes: &[BallTreeNode],
node_idx: usize,
query: &[f64],
radius: f64,
result: &mut Vec<NearestNeighbor>,
_metric: &EuclideanMetric,
) {
let node = &nodes[node_idx];
let pivot_dist = euclidean_dist(query, &points[node.pivot_idx]);
if pivot_dist - node.radius > radius {
return;
}
if node.is_leaf() {
for &idx in &node.leaf_points {
let d = euclidean_dist(query, &points[idx]);
if d <= radius {
result.push(NearestNeighbor {
index: idx,
distance: d,
});
}
}
return;
}
if node.left != usize::MAX {
range_search(points, nodes, node.left, query, radius, result, _metric);
}
if node.right != usize::MAX {
range_search(points, nodes, node.right, query, radius, result, _metric);
}
}
pub struct FrechetDistance;
impl FrechetDistance {
pub fn compute(p: &[Vec<f64>], q: &[Vec<f64>]) -> f64 {
let m = p.len();
let n = q.len();
if m == 0 || n == 0 {
return 0.0;
}
let mut dp = vec![vec![f64::MAX; n]; m];
for i in 0..m {
for j in 0..n {
let d = euclidean_dist(&p[i], &q[j]);
dp[i][j] = if i == 0 && j == 0 {
d
} else if i == 0 {
dp[i][j - 1].max(d)
} else if j == 0 {
dp[i - 1][j].max(d)
} else {
dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]).max(d)
};
}
}
dp[m - 1][n - 1]
}
pub fn compute_with_metric<M: MetricSpace<Point = Vec<f64>>>(
p: &[Vec<f64>],
q: &[Vec<f64>],
metric: &M,
) -> f64 {
let m = p.len();
let n = q.len();
if m == 0 || n == 0 {
return 0.0;
}
let mut dp = vec![vec![f64::MAX; n]; m];
for i in 0..m {
for j in 0..n {
let d = metric.distance(&p[i], &q[j]);
dp[i][j] = if i == 0 && j == 0 {
d
} else if i == 0 {
dp[i][j - 1].max(d)
} else if j == 0 {
dp[i - 1][j].max(d)
} else {
dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]).max(d)
};
}
}
dp[m - 1][n - 1]
}
pub fn hausdorff(p: &[Vec<f64>], q: &[Vec<f64>]) -> f64 {
let one_sided = |a: &[Vec<f64>], b: &[Vec<f64>]| {
a.iter()
.map(|ai| {
b.iter()
.map(|bj| euclidean_dist(ai, bj))
.fold(f64::MAX, f64::min)
})
.fold(0.0_f64, f64::max)
};
one_sided(p, q).max(one_sided(q, p))
}
}
pub struct GeodesicMetric {
pub adj: Vec<Vec<(usize, f64)>>,
}
impl GeodesicMetric {
pub fn new(n: usize) -> Self {
Self {
adj: vec![Vec::new(); n],
}
}
pub fn add_edge(&mut self, u: usize, v: usize, weight: f64) {
self.adj[u].push((v, weight));
self.adj[v].push((u, weight));
}
pub fn dijkstra(&self, s: usize) -> Vec<f64> {
let n = self.adj.len();
let mut dist = vec![f64::INFINITY; n];
dist[s] = 0.0;
let mut heap: BinaryHeap<(ordered_float::OrderedF64, usize)> = BinaryHeap::new();
heap.push((ordered_float::OrderedF64(0.0), s));
while let Some((ordered_float::OrderedF64(d), u)) = heap.pop() {
if d > dist[u] {
continue;
}
for &(v, w) in &self.adj[u] {
let nd = dist[u] + w;
if nd < dist[v] {
dist[v] = nd;
heap.push((ordered_float::OrderedF64(nd), v));
}
}
}
dist
}
}
mod ordered_float {
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OrderedF64(pub f64);
impl Eq for OrderedF64 {}
impl PartialOrd for OrderedF64 {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrderedF64 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.0
.partial_cmp(&self.0)
.unwrap_or(std::cmp::Ordering::Equal)
}
}
}
impl MetricSpace for GeodesicMetric {
type Point = usize;
fn distance(&self, a: &usize, b: &usize) -> f64 {
let dists = self.dijkstra(*a);
dists[*b]
}
}
pub fn check_triangle_inequality(matrix: &[Vec<f64>]) -> Result<(), (usize, usize, usize)> {
let n = matrix.len();
for i in 0..n {
for j in 0..n {
for k in 0..n {
if matrix[i][k] > matrix[i][j] + matrix[j][k] + 1e-9 {
return Err((i, j, k));
}
}
}
}
Ok(())
}
pub fn set_diameter<M: MetricSpace>(metric: &M, points: &[M::Point]) -> f64 {
metric.diameter(points)
}
pub fn distance_matrix<M: MetricSpace>(metric: &M, points: &[M::Point]) -> Vec<Vec<f64>> {
let n = points.len();
let mut mat = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
mat[i][j] = metric.distance(&points[i], &points[j]);
}
}
mat
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euclidean_zero_distance() {
let m = EuclideanMetric;
let p = vec![1.0, 2.0, 3.0];
assert!((m.distance(&p, &p) - 0.0).abs() < 1e-12);
}
#[test]
fn test_euclidean_known_distance() {
let m = EuclideanMetric;
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((m.distance(&a, &b) - 5.0).abs() < 1e-12);
}
#[test]
fn test_euclidean_symmetry() {
let m = EuclideanMetric;
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, -1.0, 0.0];
let diff = (m.distance(&a, &b) - m.distance(&b, &a)).abs();
assert!(diff < 1e-12);
}
#[test]
fn test_euclidean_triangle_inequality() {
let m = EuclideanMetric;
let a = vec![0.0, 0.0];
let b = vec![1.0, 0.0];
let c = vec![0.5, 1.0];
let dab = m.distance(&a, &b);
let dbc = m.distance(&b, &c);
let dac = m.distance(&a, &c);
assert!(dac <= dab + dbc + 1e-10);
}
#[test]
fn test_euclidean_norm() {
let v = vec![3.0, 4.0];
assert!((EuclideanMetric::norm(&v) - 5.0).abs() < 1e-12);
}
#[test]
fn test_cauchy_schwarz() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, -1.0, 2.0];
assert!(EuclideanMetric::cauchy_schwarz_holds(&a, &b));
}
#[test]
fn test_euclidean_normalize() {
let v = vec![3.0, 4.0];
let u = EuclideanMetric::normalize(&v).unwrap();
assert!((EuclideanMetric::norm(&u) - 1.0).abs() < 1e-12);
}
#[test]
fn test_euclidean_diameter() {
let m = EuclideanMetric;
let pts = vec![vec![0.0, 0.0], vec![1.0, 0.0], vec![0.0, 1.0]];
let d = m.diameter(&pts);
assert!((d - 2.0_f64.sqrt()).abs() < 1e-10);
}
#[test]
fn test_euclidean_ball() {
let m = EuclideanMetric;
let center = vec![0.0, 0.0];
let pts = vec![vec![0.5, 0.0], vec![2.0, 0.0], vec![0.0, 0.8]];
let ball = m.ball(¢er, 1.0, &pts);
assert_eq!(ball.len(), 2);
}
#[test]
fn test_euclidean_is_bounded() {
let m = EuclideanMetric;
let pts = vec![vec![0.0, 0.0], vec![100.0, 100.0]];
assert!(m.is_bounded(&pts));
}
#[test]
fn test_manhattan_distance() {
let m = ManhattanMetric;
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((m.distance(&a, &b) - 7.0).abs() < 1e-12);
}
#[test]
fn test_manhattan_symmetry() {
let m = ManhattanMetric;
let a = vec![1.0, -2.0, 3.0];
let b = vec![-1.0, 4.0, 0.0];
assert!((m.distance(&a, &b) - m.distance(&b, &a)).abs() < 1e-12);
}
#[test]
fn test_manhattan_norm() {
let v = vec![-3.0, 4.0];
assert!((ManhattanMetric::norm(&v) - 7.0).abs() < 1e-12);
}
#[test]
fn test_manhattan_diameter() {
let m = ManhattanMetric;
let pts = vec![vec![0.0, 0.0], vec![1.0, 1.0], vec![-1.0, -1.0]];
let d = m.diameter(&pts);
assert!((d - 4.0).abs() < 1e-12);
}
#[test]
fn test_manhattan_ball() {
let m = ManhattanMetric;
let center = vec![0.0, 0.0];
let pts = vec![vec![1.0, 0.0], vec![1.0, 1.0], vec![0.5, 0.5]];
let b = m.ball(¢er, 1.0, &pts);
assert_eq!(b.len(), 2);
}
#[test]
fn test_chebyshev_distance() {
let m = ChebyshevMetric;
let a = vec![1.0, 3.0, 5.0];
let b = vec![4.0, 2.0, 1.0];
assert!((m.distance(&a, &b) - 4.0).abs() < 1e-12);
}
#[test]
fn test_chebyshev_zero() {
let m = ChebyshevMetric;
let p = vec![1.0, 2.0];
assert!((m.distance(&p, &p)).abs() < 1e-12);
}
#[test]
fn test_chebyshev_norm() {
let v = vec![-5.0, 3.0, 2.0];
assert!((ChebyshevMetric::norm(&v) - 5.0).abs() < 1e-12);
}
#[test]
fn test_chebyshev_diameter() {
let m = ChebyshevMetric;
let pts = vec![vec![0.0, 0.0], vec![2.0, 1.0], vec![1.0, 3.0]];
let d = m.diameter(&pts);
assert!((d - 3.0).abs() < 1e-12);
}
#[test]
fn test_hamming_equal() {
let m = HammingMetric;
let a = HammingPoint(vec![1, 0, 1, 1]);
assert!((m.distance(&a, &a)).abs() < 1e-12);
}
#[test]
fn test_hamming_known() {
let m = HammingMetric;
let a = HammingPoint(vec![0, 0, 0, 1]);
let b = HammingPoint(vec![1, 1, 0, 0]);
assert!((m.distance(&a, &b) - 3.0).abs() < 1e-12);
}
#[test]
fn test_hamming_symmetry() {
let m = HammingMetric;
let a = HammingPoint(vec![1, 0, 1]);
let b = HammingPoint(vec![0, 1, 1]);
assert!((m.distance(&a, &b) - m.distance(&b, &a)).abs() < 1e-12);
}
#[test]
fn test_hamming_weight() {
let v = vec![0b1010_1010u8, 0b1111_0000u8];
assert_eq!(HammingMetric::hamming_weight(&v), 8);
}
#[test]
fn test_edit_identical() {
let m = EditDistance;
let s: Vec<char> = "hello".chars().collect();
let a = Sequence(s.clone());
let b = Sequence(s);
assert!((m.distance(&a, &b)).abs() < 1e-12);
}
#[test]
fn test_edit_known() {
assert_eq!(levenshtein_str("kitten", "sitting"), 3);
}
#[test]
fn test_edit_empty() {
assert_eq!(levenshtein_str("", "abc"), 3);
assert_eq!(levenshtein_str("abc", ""), 3);
}
#[test]
fn test_edit_symmetry() {
let a: Vec<char> = "sunday".chars().collect();
let b: Vec<char> = "saturday".chars().collect();
assert_eq!(levenshtein(&a, &b), levenshtein(&b, &a));
}
#[test]
fn test_lcs_length() {
let a: Vec<char> = "ABCBDAB".chars().collect();
let b: Vec<char> = "BDCAB".chars().collect();
assert_eq!(EditDistance::lcs_length(&a, &b), 4);
}
#[test]
fn test_weighted_edit() {
let a: Vec<char> = "abc".chars().collect();
let b: Vec<char> = "axc".chars().collect();
let d = EditDistance::weighted_edit(&a, &b, 1.0, 1.0, 2.0);
assert!((d - 2.0).abs() < 1e-12);
}
#[test]
fn test_minkowski_p1_equals_manhattan() {
let m1 = MinkowskiMetric::new(1.0);
let m2 = ManhattanMetric;
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 0.0, -1.0];
assert!((m1.distance(&a, &b) - m2.distance(&a, &b)).abs() < 1e-10);
}
#[test]
fn test_minkowski_p2_equals_euclidean() {
let m1 = MinkowskiMetric::new(2.0);
let m2 = EuclideanMetric;
let a = vec![0.0, 0.0];
let b = vec![3.0, 4.0];
assert!((m1.distance(&a, &b) - m2.distance(&a, &b)).abs() < 1e-10);
}
#[test]
fn test_cosine_identical() {
let v = vec![1.0, 2.0, 3.0];
assert!((CosineDistance::similarity(&v, &v) - 1.0).abs() < 1e-12);
}
#[test]
fn test_cosine_orthogonal() {
let a = vec![1.0, 0.0];
let b = vec![0.0, 1.0];
assert!((CosineDistance::similarity(&a, &b)).abs() < 1e-12);
}
#[test]
fn test_cosine_distance_range() {
let a = vec![1.0, 0.0];
let b = vec![-1.0, 0.0];
let d = CosineDistance::distance(&a, &b);
assert!((0.0..=2.0 + 1e-12).contains(&d));
}
#[test]
fn test_ball_tree_knn_1() {
let points: Vec<Vec<f64>> = vec![
vec![0.0, 0.0],
vec![1.0, 0.0],
vec![0.0, 1.0],
vec![5.0, 5.0],
];
let tree = MetricBallTree::build(points, 2);
let result = tree.knn(&[0.1, 0.1], 1);
assert_eq!(result.len(), 1);
assert_eq!(result[0].index, 0);
}
#[test]
fn test_ball_tree_knn_k() {
let points: Vec<Vec<f64>> = (0..20).map(|i| vec![i as f64, 0.0]).collect();
let tree = MetricBallTree::build(points, 4);
let result = tree.knn(&[9.5, 0.0], 3);
assert_eq!(result.len(), 3);
let indices: Vec<usize> = result.iter().map(|n| n.index).collect();
assert!(indices.contains(&9) || indices.contains(&10));
}
#[test]
fn test_ball_tree_range_query() {
let points: Vec<Vec<f64>> = vec![
vec![0.0, 0.0],
vec![0.5, 0.0],
vec![1.5, 0.0],
vec![10.0, 10.0],
];
let tree = MetricBallTree::build(points, 2);
let result = tree.range_query(&[0.0, 0.0], 1.0);
let indices: Vec<usize> = result.iter().map(|n| n.index).collect();
assert!(indices.contains(&0));
assert!(indices.contains(&1));
assert!(!indices.contains(&3));
}
#[test]
fn test_ball_tree_len() {
let points: Vec<Vec<f64>> = (0..10).map(|i| vec![i as f64]).collect();
let tree = MetricBallTree::build(points, 3);
assert_eq!(tree.len(), 10);
assert!(!tree.is_empty());
}
#[test]
fn test_frechet_identical_curves() {
let c: Vec<Vec<f64>> = vec![vec![0.0, 0.0], vec![1.0, 0.0], vec![2.0, 0.0]];
let d = FrechetDistance::compute(&c, &c);
assert!(d < 1e-12, "identical curves → Fréchet = 0, got {d}");
}
#[test]
fn test_frechet_offset_curves() {
let p: Vec<Vec<f64>> = vec![vec![0.0, 0.0], vec![1.0, 0.0]];
let q: Vec<Vec<f64>> = vec![vec![0.0, 1.0], vec![1.0, 1.0]];
let d = FrechetDistance::compute(&p, &q);
assert!((d - 1.0).abs() < 1e-10, "expected 1.0, got {d}");
}
#[test]
fn test_frechet_single_point() {
let p = vec![vec![0.0, 0.0]];
let q = vec![vec![3.0, 4.0]];
let d = FrechetDistance::compute(&p, &q);
assert!((d - 5.0).abs() < 1e-10, "expected 5.0, got {d}");
}
#[test]
fn test_frechet_with_manhattan_metric() {
let p: Vec<Vec<f64>> = vec![vec![0.0, 0.0], vec![1.0, 0.0]];
let q: Vec<Vec<f64>> = vec![vec![0.0, 1.0], vec![1.0, 1.0]];
let d = FrechetDistance::compute_with_metric(&p, &q, &ManhattanMetric);
assert!((d - 1.0).abs() < 1e-10);
}
#[test]
fn test_hausdorff() {
let p: Vec<Vec<f64>> = vec![vec![0.0, 0.0], vec![2.0, 0.0]];
let q: Vec<Vec<f64>> = vec![vec![0.0, 1.0]];
let h = FrechetDistance::hausdorff(&p, &q);
assert!(h > 2.0);
}
#[test]
fn test_distance_matrix_diagonal_zero() {
let m = EuclideanMetric;
let pts = vec![vec![0.0, 0.0], vec![1.0, 0.0], vec![0.0, 1.0]];
let mat = distance_matrix(&m, &pts);
for i in 0..3 {
assert!(mat[i][i].abs() < 1e-12);
}
}
#[test]
fn test_distance_matrix_symmetry() {
let m = ManhattanMetric;
let pts = vec![vec![0.0, 0.0], vec![1.0, 2.0], vec![3.0, -1.0]];
let mat = distance_matrix(&m, &pts);
for i in 0..3 {
for j in 0..3 {
assert!((mat[i][j] - mat[j][i]).abs() < 1e-12);
}
}
}
#[test]
fn test_triangle_inequality_check_pass() {
let m = EuclideanMetric;
let pts = vec![vec![0.0, 0.0], vec![1.0, 0.0], vec![0.0, 1.0]];
let mat = distance_matrix(&m, &pts);
assert!(check_triangle_inequality(&mat).is_ok());
}
#[test]
fn test_set_diameter_empty() {
let m = EuclideanMetric;
let pts: Vec<Vec<f64>> = vec![];
assert!((set_diameter(&m, &pts)).abs() < 1e-12);
}
#[test]
fn test_geodesic_simple() {
let mut g = GeodesicMetric::new(4);
g.add_edge(0, 1, 1.0);
g.add_edge(1, 2, 2.0);
g.add_edge(2, 3, 1.0);
assert!((g.distance(&0, &3) - 4.0).abs() < 1e-12);
}
#[test]
fn test_geodesic_shortest_path() {
let mut g = GeodesicMetric::new(3);
g.add_edge(0, 1, 10.0);
g.add_edge(0, 2, 1.0);
g.add_edge(2, 1, 2.0);
assert!((g.distance(&0, &1) - 3.0).abs() < 1e-12);
}
#[test]
fn test_geodesic_self_distance() {
let g = GeodesicMetric::new(3);
assert!((g.distance(&0, &0)).abs() < 1e-12);
}
}