use crate::error::{SpatialError, SpatialResult};
#[derive(Debug, Clone)]
pub struct HausdorffResult {
pub distance: f64,
pub forward_distance: f64,
pub backward_distance: f64,
pub forward_index_a: usize,
pub forward_index_b: usize,
pub backward_index_b: usize,
pub backward_index_a: usize,
}
pub fn hausdorff_distance_detailed(
set_a: &[[f64; 2]],
set_b: &[[f64; 2]],
) -> SpatialResult<HausdorffResult> {
if set_a.is_empty() || set_b.is_empty() {
return Err(SpatialError::ValueError(
"Both point sets must be non-empty".to_string(),
));
}
let (fwd_dist, fwd_ia, fwd_ib) = directed_hausdorff_impl(set_a, set_b);
let (bwd_dist, bwd_ib, bwd_ia) = directed_hausdorff_impl(set_b, set_a);
Ok(HausdorffResult {
distance: fwd_dist.max(bwd_dist),
forward_distance: fwd_dist,
backward_distance: bwd_dist,
forward_index_a: fwd_ia,
forward_index_b: fwd_ib,
backward_index_b: bwd_ib,
backward_index_a: bwd_ia,
})
}
fn directed_hausdorff_impl(set_a: &[[f64; 2]], set_b: &[[f64; 2]]) -> (f64, usize, usize) {
let mut max_min_dist = f64::NEG_INFINITY;
let mut best_a = 0;
let mut best_b = 0;
for (i, pa) in set_a.iter().enumerate() {
let mut min_dist = f64::INFINITY;
let mut closest_b = 0;
for (j, pb) in set_b.iter().enumerate() {
let dx = pa[0] - pb[0];
let dy = pa[1] - pb[1];
let d = (dx * dx + dy * dy).sqrt();
if d < min_dist {
min_dist = d;
closest_b = j;
}
}
if min_dist > max_min_dist {
max_min_dist = min_dist;
best_a = i;
best_b = closest_b;
}
}
(max_min_dist, best_a, best_b)
}
pub fn discrete_frechet_distance(curve_p: &[[f64; 2]], curve_q: &[[f64; 2]]) -> SpatialResult<f64> {
let n = curve_p.len();
let m = curve_q.len();
if n == 0 || m == 0 {
return Err(SpatialError::ValueError(
"Both curves must have at least one point".to_string(),
));
}
let mut dp = vec![vec![f64::NEG_INFINITY; m]; n];
let dist = |p: &[f64; 2], q: &[f64; 2]| -> f64 {
let dx = p[0] - q[0];
let dy = p[1] - q[1];
(dx * dx + dy * dy).sqrt()
};
dp[0][0] = dist(&curve_p[0], &curve_q[0]);
for i in 1..n {
dp[i][0] = dp[i - 1][0].max(dist(&curve_p[i], &curve_q[0]));
}
for j in 1..m {
dp[0][j] = dp[0][j - 1].max(dist(&curve_p[0], &curve_q[j]));
}
for i in 1..n {
for j in 1..m {
let d = dist(&curve_p[i], &curve_q[j]);
let prev_min = dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]);
dp[i][j] = d.max(prev_min);
}
}
Ok(dp[n - 1][m - 1])
}
pub fn discrete_frechet_distance_nd(
curve_p: &[Vec<f64>],
curve_q: &[Vec<f64>],
) -> SpatialResult<f64> {
let n = curve_p.len();
let m = curve_q.len();
if n == 0 || m == 0 {
return Err(SpatialError::ValueError(
"Both curves must have at least one point".to_string(),
));
}
let ndim = curve_p[0].len();
for (i, p) in curve_p.iter().enumerate() {
if p.len() != ndim {
return Err(SpatialError::DimensionError(format!(
"Point {} in curve_p has {} dimensions, expected {}",
i,
p.len(),
ndim
)));
}
}
for (j, q) in curve_q.iter().enumerate() {
if q.len() != ndim {
return Err(SpatialError::DimensionError(format!(
"Point {} in curve_q has {} dimensions, expected {}",
j,
q.len(),
ndim
)));
}
}
let dist = |p: &Vec<f64>, q: &Vec<f64>| -> f64 {
let mut sum = 0.0;
for d in 0..ndim {
let diff = p[d] - q[d];
sum += diff * diff;
}
sum.sqrt()
};
let mut dp = vec![vec![f64::NEG_INFINITY; m]; n];
dp[0][0] = dist(&curve_p[0], &curve_q[0]);
for i in 1..n {
dp[i][0] = dp[i - 1][0].max(dist(&curve_p[i], &curve_q[0]));
}
for j in 1..m {
dp[0][j] = dp[0][j - 1].max(dist(&curve_p[0], &curve_q[j]));
}
for i in 1..n {
for j in 1..m {
let d = dist(&curve_p[i], &curve_q[j]);
let prev_min = dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]);
dp[i][j] = d.max(prev_min);
}
}
Ok(dp[n - 1][m - 1])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hausdorff_identical_sets() {
let set = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let result = hausdorff_distance_detailed(&set, &set).expect("compute");
assert!((result.distance).abs() < 1e-10);
}
#[test]
fn test_hausdorff_simple() {
let set_a = vec![[0.0, 0.0], [1.0, 0.0]];
let set_b = vec![[0.0, 1.0], [1.0, 1.0]];
let result = hausdorff_distance_detailed(&set_a, &set_b).expect("compute");
assert!((result.distance - 1.0).abs() < 1e-10);
}
#[test]
fn test_hausdorff_asymmetric() {
let set_a = vec![[0.0, 0.0]];
let set_b = vec![[1.0, 0.0], [2.0, 0.0]];
let result = hausdorff_distance_detailed(&set_a, &set_b).expect("compute");
assert!((result.forward_distance - 1.0).abs() < 1e-10);
assert!((result.backward_distance - 2.0).abs() < 1e-10);
assert!((result.distance - 2.0).abs() < 1e-10);
}
#[test]
fn test_hausdorff_empty() {
let set_a: Vec<[f64; 2]> = vec![];
let set_b = vec![[0.0, 0.0]];
assert!(hausdorff_distance_detailed(&set_a, &set_b).is_err());
}
#[test]
fn test_frechet_parallel_curves() {
let curve_p = vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
let curve_q = vec![[0.0, 1.0], [1.0, 1.0], [2.0, 1.0]];
let dist = discrete_frechet_distance(&curve_p, &curve_q).expect("compute");
assert!((dist - 1.0).abs() < 1e-10);
}
#[test]
fn test_frechet_identical_curves() {
let curve = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]];
let dist = discrete_frechet_distance(&curve, &curve).expect("compute");
assert!(dist.abs() < 1e-10);
}
#[test]
fn test_frechet_single_point() {
let curve_p = vec![[0.0, 0.0]];
let curve_q = vec![[3.0, 4.0]];
let dist = discrete_frechet_distance(&curve_p, &curve_q).expect("compute");
assert!((dist - 5.0).abs() < 1e-10);
}
#[test]
fn test_frechet_ordering_matters() {
let curve_p = vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
let curve_q = vec![[2.0, 1.0], [1.0, 1.0], [0.0, 1.0]];
let dist = discrete_frechet_distance(&curve_p, &curve_q).expect("compute");
assert!(dist > 1.0); }
#[test]
fn test_frechet_empty() {
let empty: Vec<[f64; 2]> = vec![];
let curve = vec![[0.0, 0.0]];
assert!(discrete_frechet_distance(&empty, &curve).is_err());
}
#[test]
fn test_frechet_nd() {
let curve_p = vec![vec![0.0, 0.0, 0.0], vec![1.0, 0.0, 0.0]];
let curve_q = vec![vec![0.0, 0.0, 1.0], vec![1.0, 0.0, 1.0]];
let dist = discrete_frechet_distance_nd(&curve_p, &curve_q).expect("compute");
assert!((dist - 1.0).abs() < 1e-10);
}
}