use crate::distance::base::DistanceCalculator;
use crate::traits::AsCoord;
pub fn erp_standard<D: DistanceCalculator, C: AsCoord>(
calculator: &D,
g: &C,
use_full_matrix: bool,
) -> crate::distance::DpResult {
let n0 = calculator.len_seq1();
let n1 = calculator.len_seq2();
if n0 == 0 || n1 == 0 {
return crate::distance::DpResult::new(f64::MAX);
}
let mut gt0_dist = Vec::with_capacity(n0);
let mut gt1_dist = Vec::with_capacity(n1);
for i in 0..n0 {
let dist = calculator.compute_dis_for_extra_point(0, i, Some(g));
gt0_dist.push(dist);
}
for j in 0..n1 {
let dist = calculator.compute_dis_for_extra_point(1, j, Some(g));
gt1_dist.push(dist);
}
if use_full_matrix {
let mut c = vec![0.0; (n0 + 1) * (n1 + 1)];
let mut sum_t0 = 0.0;
for i in 1..=n0 {
sum_t0 += gt0_dist[i - 1];
c[i * (n1 + 1)] = sum_t0;
}
let mut sum_t1 = 0.0;
#[allow(clippy::needless_range_loop)]
for j in 1..=n1 {
sum_t1 += gt1_dist[j - 1];
c[j] = sum_t1;
}
for i in 1..=n0 {
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = c[(i - 1) * (n1 + 1) + j] + gt0_dist[i - 1];
let derp1 = c[i * (n1 + 1) + (j - 1)] + gt1_dist[j - 1];
let derp01 = c[(i - 1) * (n1 + 1) + (j - 1)] + dist;
c[i * (n1 + 1) + j] = derp0.min(derp1).min(derp01);
}
}
crate::distance::DpResult::with_matrix(c[n0 * (n1 + 1) + n1], c)
} else {
let mut prev_row = vec![0.0; n1 + 1];
let mut curr_row = vec![0.0; n1 + 1];
let mut sum_t1 = 0.0;
for j in 1..=n1 {
sum_t1 += gt1_dist[j - 1];
prev_row[j] = sum_t1;
}
let mut sum_t0 = 0.0;
for i in 1..=n0 {
curr_row[0] = sum_t0;
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = prev_row[j] + gt0_dist[i - 1];
let derp1 = curr_row[j - 1] + gt1_dist[j - 1];
let derp01 = prev_row[j - 1] + dist;
curr_row[j] = derp0.min(derp1).min(derp01);
}
sum_t0 += gt0_dist[i - 1];
std::mem::swap(&mut prev_row, &mut curr_row);
}
crate::distance::DpResult::new(prev_row[n1])
}
}
pub fn erp_compat_traj_dist<D: DistanceCalculator, C: AsCoord>(
calculator: &D,
g: &C,
use_full_matrix: bool,
) -> crate::distance::DpResult {
let n0 = calculator.len_seq1();
let n1 = calculator.len_seq2();
if n0 == 0 || n1 == 0 {
return crate::distance::DpResult::new(f64::MAX);
}
let mut gt0_dist = Vec::with_capacity(n0);
let mut gt1_dist = Vec::with_capacity(n1);
for i in 0..n0 {
let dist = calculator.compute_dis_for_extra_point(0, i, Some(g));
gt0_dist.push(dist);
}
for j in 0..n1 {
let dist = calculator.compute_dis_for_extra_point(1, j, Some(g));
gt1_dist.push(dist);
}
let sum_t0_total: f64 = gt0_dist.iter().sum();
let sum_t1_total: f64 = gt1_dist.iter().sum();
if use_full_matrix {
let mut c = vec![0.0; (n0 + 1) * (n1 + 1)];
for i in 1..=n0 {
c[i * (n1 + 1)] = sum_t0_total;
}
#[allow(clippy::needless_range_loop)]
for j in 1..=n1 {
c[j] = sum_t1_total;
}
for i in 1..=n0 {
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = c[(i - 1) * (n1 + 1) + j] + gt0_dist[i - 1];
let derp1 = c[i * (n1 + 1) + (j - 1)] + gt1_dist[j - 1];
let derp01 = c[(i - 1) * (n1 + 1) + (j - 1)] + dist;
c[i * (n1 + 1) + j] = derp0.min(derp1).min(derp01);
}
}
crate::distance::DpResult::with_matrix(c[n0 * (n1 + 1) + n1], c)
} else {
let mut prev_row = vec![0.0; n1 + 1];
let mut curr_row = vec![0.0; n1 + 1];
#[allow(clippy::needless_range_loop)]
for j in 1..=n1 {
prev_row[j] = sum_t1_total;
}
for i in 1..=n0 {
curr_row[0] = sum_t0_total;
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = prev_row[j] + gt0_dist[i - 1];
let derp1 = curr_row[j - 1] + gt1_dist[j - 1];
let derp01 = prev_row[j - 1] + dist;
curr_row[j] = derp0.min(derp1).min(derp01);
}
std::mem::swap(&mut prev_row, &mut curr_row);
}
crate::distance::DpResult::new(prev_row[n1])
}
}
pub fn erp_compat_traj_dist_with_distances<D: DistanceCalculator>(
calculator: &D,
gt0_dist: &[f64],
gt1_dist: &[f64],
use_full_matrix: bool,
) -> crate::distance::DpResult {
let n0 = calculator.len_seq1();
let n1 = calculator.len_seq2();
if n0 == 0 || n1 == 0 {
return crate::distance::DpResult::new(f64::MAX);
}
let sum_t0_total: f64 = gt0_dist.iter().sum();
let sum_t1_total: f64 = gt1_dist.iter().sum();
if use_full_matrix {
let mut c = vec![0.0; (n0 + 1) * (n1 + 1)];
for i in 1..=n0 {
c[i * (n1 + 1)] = sum_t0_total;
}
#[allow(clippy::needless_range_loop)]
for j in 1..=n1 {
c[j] = sum_t1_total;
}
for i in 1..=n0 {
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = c[(i - 1) * (n1 + 1) + j] + gt0_dist[i - 1];
let derp1 = c[i * (n1 + 1) + (j - 1)] + gt1_dist[j - 1];
let derp01 = c[(i - 1) * (n1 + 1) + (j - 1)] + dist;
c[i * (n1 + 1) + j] = derp0.min(derp1).min(derp01);
}
}
crate::distance::DpResult::with_matrix(c[n0 * (n1 + 1) + n1], c)
} else {
let mut prev_row = vec![0.0; n1 + 1];
let mut curr_row = vec![0.0; n1 + 1];
#[allow(clippy::needless_range_loop)]
for j in 1..=n1 {
prev_row[j] = sum_t1_total;
}
for i in 1..=n0 {
curr_row[0] = sum_t0_total;
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = prev_row[j] + gt0_dist[i - 1];
let derp1 = curr_row[j - 1] + gt1_dist[j - 1];
let derp01 = prev_row[j - 1] + dist;
curr_row[j] = derp0.min(derp1).min(derp01);
}
std::mem::swap(&mut prev_row, &mut curr_row);
}
crate::distance::DpResult::new(prev_row[n1])
}
}
pub fn erp_standard_with_distances<D: DistanceCalculator>(
calculator: &D,
gt0_dist: &[f64],
gt1_dist: &[f64],
use_full_matrix: bool,
) -> crate::distance::DpResult {
let n0 = calculator.len_seq1();
let n1 = calculator.len_seq2();
if n0 == 0 || n1 == 0 {
return crate::distance::DpResult::new(f64::MAX);
}
if use_full_matrix {
let mut c = vec![0.0; (n0 + 1) * (n1 + 1)];
let mut sum_t0 = 0.0;
for i in 1..=n0 {
sum_t0 += gt0_dist[i - 1];
c[i * (n1 + 1)] = sum_t0;
}
let mut sum_t1 = 0.0;
#[allow(clippy::needless_range_loop)]
for j in 1..=n1 {
sum_t1 += gt1_dist[j - 1];
c[j] = sum_t1;
}
for i in 1..=n0 {
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = c[(i - 1) * (n1 + 1) + j] + gt0_dist[i - 1];
let derp1 = c[i * (n1 + 1) + (j - 1)] + gt1_dist[j - 1];
let derp01 = c[(i - 1) * (n1 + 1) + (j - 1)] + dist;
c[i * (n1 + 1) + j] = derp0.min(derp1).min(derp01);
}
}
crate::distance::DpResult::with_matrix(c[n0 * (n1 + 1) + n1], c)
} else {
let mut prev_row = vec![0.0; n1 + 1];
let mut curr_row = vec![0.0; n1 + 1];
let mut sum_t1 = 0.0;
for j in 1..=n1 {
sum_t1 += gt1_dist[j - 1];
prev_row[j] = sum_t1;
}
let mut sum_t0 = 0.0;
for i in 1..=n0 {
sum_t0 += gt0_dist[i - 1];
curr_row[0] = sum_t0;
for j in 1..=n1 {
let dist = calculator.dis_between(i - 1, j - 1);
let derp0 = prev_row[j] + gt0_dist[i - 1];
let derp1 = curr_row[j - 1] + gt1_dist[j - 1];
let derp01 = prev_row[j - 1] + dist;
curr_row[j] = derp0.min(derp1).min(derp01);
}
std::mem::swap(&mut prev_row, &mut curr_row);
}
crate::distance::DpResult::new(prev_row[n1])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::distance::base::TrajectoryCalculator;
use crate::distance::distance_type::DistanceType;
#[test]
fn test_erp_euclidean_simple() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0], [1.0, 0.0]];
let g = [0.0, 0.0];
let calc_compat = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let calc_standard = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let dist_compat = erp_compat_traj_dist(&calc_compat, &g, false);
let dist_standard = erp_standard(&calc_standard, &g, false);
println!("ERP Euclidean (compat) distance: {}", dist_compat);
println!("ERP Euclidean (standard) distance: {}", dist_standard);
assert!(dist_compat.distance >= 0.0);
assert!(dist_standard.distance >= 0.0);
}
#[test]
fn test_erp_euclidean_different_lengths() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0]];
let g = [0.0, 0.0];
let calc_compat = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let calc_standard = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let dist_compat = erp_compat_traj_dist(&calc_compat, &g, false);
let dist_standard = erp_standard(&calc_standard, &g, false);
println!(
"ERP Euclidean (compat) distance (different lengths): {}",
dist_compat
);
println!(
"ERP Euclidean (standard) distance (different lengths): {}",
dist_standard
);
assert!(dist_compat != dist_standard);
}
#[test]
fn test_erp_euclidean_identical() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0]];
let g = [0.0, 0.0];
let calc_compat = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let calc_standard = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let dist_compat = erp_compat_traj_dist(&calc_compat, &g, false);
let dist_standard = erp_standard(&calc_standard, &g, false);
println!(
"ERP Euclidean (compat) distance for identical trajectories: {}",
dist_compat
);
println!(
"ERP Euclidean (standard) distance for identical trajectories: {}",
dist_standard
);
assert!(dist_compat.distance < 1e-6);
assert!(dist_standard.distance < 1e-6);
}
#[test]
fn test_erp_spherical_simple() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0], [1.0, 0.0]];
let g = [0.0, 0.0];
let calc_compat = TrajectoryCalculator::new(&t0, &t1, DistanceType::Spherical);
let calc_standard = TrajectoryCalculator::new(&t0, &t1, DistanceType::Spherical);
let dist_compat = erp_compat_traj_dist(&calc_compat, &g, false);
let dist_standard = erp_standard(&calc_standard, &g, false);
println!("ERP Spherical (compat) distance: {}", dist_compat);
println!("ERP Spherical (standard) distance: {}", dist_standard);
assert!(dist_compat != dist_standard);
assert!(dist_compat.distance > 0.0);
assert!(dist_standard.distance > 0.0);
}
#[test]
fn test_erp_difference_between_implementations() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0]];
let g = [0.0, 0.0];
let calc_compat_euclidean = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let calc_standard_euclidean = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let calc_compat_spherical = TrajectoryCalculator::new(&t0, &t1, DistanceType::Spherical);
let calc_standard_spherical = TrajectoryCalculator::new(&t0, &t1, DistanceType::Spherical);
let dist_compat_euclidean = erp_compat_traj_dist(&calc_compat_euclidean, &g, false);
let dist_standard_euclidean = erp_standard(&calc_standard_euclidean, &g, false);
let dist_compat_spherical = erp_compat_traj_dist(&calc_compat_spherical, &g, false);
let dist_standard_spherical = erp_standard(&calc_standard_spherical, &g, false);
println!(
"ERP Euclidean (compat): {}, (standard): {}",
dist_compat_euclidean, dist_standard_euclidean
);
println!(
"ERP Spherical (compat): {}, (standard): {}",
dist_compat_spherical, dist_standard_spherical
);
assert!(dist_compat_euclidean != dist_standard_euclidean);
assert!(dist_compat_spherical != dist_standard_spherical);
}
#[test]
fn test_erp_consistency_between_modes_standard() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0], [1.0, 0.0], [2.0, 3.0]];
let g = [0.0, 0.0];
let calculator = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let dist_optimized = erp_standard(&calculator, &g, false);
let dist_full = erp_standard(&calculator, &g, true);
assert!((dist_optimized.distance - dist_full.distance).abs() < 1e-10);
}
#[test]
fn test_erp_consistency_between_modes_compat() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0], [2.0, 2.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0], [1.0, 0.0], [2.0, 3.0]];
let g = [0.0, 0.0];
let calculator = TrajectoryCalculator::new(&t0, &t1, DistanceType::Euclidean);
let dist_optimized = erp_compat_traj_dist(&calculator, &g, false);
let dist_full = erp_compat_traj_dist(&calculator, &g, true);
assert!((dist_optimized.distance - dist_full.distance).abs() < 1e-10);
}
#[test]
fn test_erp_with_precomputed_distances() {
let t0: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 1.0]];
let t1: Vec<[f64; 2]> = vec![[0.0, 1.0], [1.0, 0.0]];
let g = [0.0, 0.0];
let distance_matrix =
crate::distance::utils::precompute_distance_matrix(&t0, &t1, DistanceType::Euclidean);
let mut gt0_dist = Vec::with_capacity(t0.len());
let mut gt1_dist = Vec::with_capacity(t1.len());
for i in 0..t0.len() {
let dist = DistanceType::Euclidean.distance(&g, &t0[i]);
gt0_dist.push(dist);
}
for j in 0..t1.len() {
let dist = DistanceType::Euclidean.distance(&g, &t1[j]);
gt1_dist.push(dist);
}
let calculator = crate::distance::base::PrecomputedDistanceCalculator::with_extra_distances(
&distance_matrix,
t0.len(),
t1.len(),
Some(>0_dist),
Some(>1_dist),
);
let result = erp_standard(&calculator, &g, false);
println!(
"ERP distance with precomputed distances: {}",
result.distance
);
assert!(result.distance >= 0.0);
}
}