cvx_analytics/
trajectory.rs1pub fn discrete_frechet(a: &[&[f32]], b: &[&[f32]]) -> f64 {
38 let n = a.len();
39 let m = b.len();
40 if n == 0 || m == 0 {
41 return f64::INFINITY;
42 }
43
44 let mut dp = vec![vec![f64::NEG_INFINITY; m]; n];
46
47 for i in 0..n {
48 for (j, bj) in b.iter().enumerate().take(m) {
49 let d = l2_dist(a[i], bj);
50 if i == 0 && j == 0 {
51 dp[i][j] = d;
52 } else if i == 0 {
53 dp[i][j] = dp[i][j - 1].max(d);
54 } else if j == 0 {
55 dp[i][j] = dp[i - 1][j].max(d);
56 } else {
57 let prev = dp[i - 1][j].min(dp[i][j - 1]).min(dp[i - 1][j - 1]);
58 dp[i][j] = prev.max(d);
59 }
60 }
61 }
62
63 dp[n - 1][m - 1]
64}
65
66pub fn discrete_frechet_temporal(a: &[(i64, &[f32])], b: &[(i64, &[f32])]) -> f64 {
70 let va: Vec<&[f32]> = a.iter().map(|(_, v)| *v).collect();
71 let vb: Vec<&[f32]> = b.iter().map(|(_, v)| *v).collect();
72 discrete_frechet(&va, &vb)
73}
74
75#[inline]
77fn l2_dist(a: &[f32], b: &[f32]) -> f64 {
78 a.iter()
79 .zip(b.iter())
80 .map(|(x, y)| {
81 let d = *x as f64 - *y as f64;
82 d * d
83 })
84 .sum::<f64>()
85 .sqrt()
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn frechet_identical_paths() {
94 let a: Vec<&[f32]> = vec![&[0.0, 0.0], &[1.0, 0.0], &[1.0, 1.0]];
95 let b: Vec<&[f32]> = vec![&[0.0, 0.0], &[1.0, 0.0], &[1.0, 1.0]];
96 let d = discrete_frechet(&a, &b);
97 assert!(
98 d < 1e-10,
99 "identical paths should have distance ~0, got {d}"
100 );
101 }
102
103 #[test]
104 fn frechet_shifted_paths() {
105 let a: Vec<&[f32]> = vec![&[0.0, 0.0], &[1.0, 0.0], &[2.0, 0.0]];
106 let b: Vec<&[f32]> = vec![&[0.0, 1.0], &[1.0, 1.0], &[2.0, 1.0]];
107 let d = discrete_frechet(&a, &b);
108 assert!(
110 (d - 1.0).abs() < 1e-10,
111 "parallel offset should be 1.0, got {d}"
112 );
113 }
114
115 #[test]
116 fn frechet_different_lengths() {
117 let a: Vec<&[f32]> = vec![&[0.0], &[1.0], &[2.0]];
118 let b: Vec<&[f32]> = vec![&[0.0], &[2.0]];
119 let d = discrete_frechet(&a, &b);
120 assert!(d <= 1.0 + 1e-10, "should handle unequal lengths, got {d}");
122 }
123
124 #[test]
125 fn frechet_preserves_order() {
126 let a: Vec<&[f32]> = vec![&[0.0, 0.0], &[1.0, 0.0], &[1.0, 1.0]];
128 let b: Vec<&[f32]> = vec![&[0.0, 0.0], &[0.0, 1.0], &[1.0, 1.0]];
130 let d = discrete_frechet(&a, &b);
131 assert!(
133 d > 0.5,
134 "different-order paths should have positive distance, got {d}"
135 );
136 }
137
138 #[test]
139 fn frechet_empty_paths() {
140 let a: Vec<&[f32]> = vec![];
141 let b: Vec<&[f32]> = vec![&[0.0]];
142 assert!(discrete_frechet(&a, &b).is_infinite());
143 }
144
145 #[test]
146 fn frechet_temporal_wrapper() {
147 let a: Vec<(i64, &[f32])> = vec![(0, &[0.0f32, 0.0] as &[f32]), (1, &[1.0f32, 1.0])];
148 let b: Vec<(i64, &[f32])> = vec![(0, &[0.0f32, 0.0] as &[f32]), (1, &[1.0f32, 1.0])];
149 let d = discrete_frechet_temporal(&a, &b);
150 assert!(d < 1e-10);
151 }
152}