1mod constrained;
15mod karcher;
16mod nd;
17mod pairwise;
18mod quality;
19mod set;
20mod srsf;
21mod tsrvf;
22
23#[cfg(test)]
24mod tests;
25
26pub use constrained::{
28 elastic_align_pair_constrained, elastic_align_pair_with_landmarks, ConstrainedAlignmentResult,
29};
30pub use karcher::karcher_mean;
31pub use nd::{
32 elastic_align_pair_nd, elastic_distance_nd, srsf_inverse_nd, srsf_transform_nd,
33 AlignmentResultNd,
34};
35pub use pairwise::{
36 amplitude_distance, amplitude_self_distance_matrix, elastic_align_pair,
37 elastic_cross_distance_matrix, elastic_distance, elastic_self_distance_matrix,
38 phase_distance_pair, phase_self_distance_matrix,
39};
40pub use quality::{
41 alignment_quality, pairwise_consistency, warp_complexity, warp_smoothness, AlignmentQuality,
42};
43pub use set::{align_to_target, elastic_decomposition, DecompositionResult};
44pub use srsf::{compose_warps, reparameterize_curve, srsf_inverse, srsf_transform};
45pub use tsrvf::{
46 tsrvf_from_alignment, tsrvf_from_alignment_with_method, tsrvf_inverse, tsrvf_transform,
47 tsrvf_transform_with_method, TransportMethod, TsrvfResult,
48};
49
50pub(crate) use karcher::sqrt_mean_inverse;
52
53use crate::helpers::linear_interp;
54use crate::matrix::FdMatrix;
55use crate::warping::normalize_warp;
56
57#[derive(Debug, Clone, PartialEq)]
61pub struct AlignmentResult {
62 pub gamma: Vec<f64>,
64 pub f_aligned: Vec<f64>,
66 pub distance: f64,
68}
69
70#[derive(Debug, Clone, PartialEq)]
72pub struct AlignmentSetResult {
73 pub gammas: FdMatrix,
75 pub aligned_data: FdMatrix,
77 pub distances: Vec<f64>,
79}
80
81#[derive(Debug, Clone, PartialEq)]
83pub struct KarcherMeanResult {
84 pub mean: Vec<f64>,
86 pub mean_srsf: Vec<f64>,
88 pub gammas: FdMatrix,
90 pub aligned_data: FdMatrix,
92 pub n_iter: usize,
94 pub converged: bool,
96 pub aligned_srsfs: Option<FdMatrix>,
99}
100
101#[rustfmt::skip]
108const COPRIME_NBHD_7: [(usize, usize); 35] = [
109 (1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),
110 (2,1), (2,3), (2,5), (2,7),
111 (3,1),(3,2), (3,4),(3,5), (3,7),
112 (4,1), (4,3), (4,5), (4,7),
113 (5,1),(5,2),(5,3),(5,4), (5,6),(5,7),
114 (6,1), (6,5), (6,7),
115 (7,1),(7,2),(7,3),(7,4),(7,5),(7,6),
116];
117
118#[inline]
126pub(super) fn dp_edge_weight(
127 q1: &[f64],
128 q2: &[f64],
129 argvals: &[f64],
130 sc: usize,
131 tc: usize,
132 sr: usize,
133 tr: usize,
134) -> f64 {
135 let n1 = tc - sc;
136 let n2 = tr - sr;
137 if n1 == 0 || n2 == 0 {
138 return f64::INFINITY;
139 }
140
141 let slope = (argvals[tr] - argvals[sr]) / (argvals[tc] - argvals[sc]);
142 let rslope = slope.sqrt();
143
144 let mut weight = 0.0;
146 let mut i1 = 0usize; let mut i2 = 0usize; while i1 < n1 && i2 < n2 {
150 let left1 = i1 as f64 / n1 as f64;
152 let right1 = (i1 + 1) as f64 / n1 as f64;
153 let left2 = i2 as f64 / n2 as f64;
154 let right2 = (i2 + 1) as f64 / n2 as f64;
155
156 let left = left1.max(left2);
157 let right = right1.min(right2);
158 let dt = right - left;
159
160 if dt > 0.0 {
161 let diff = q1[sc + i1] - rslope * q2[sr + i2];
162 weight += diff * diff * dt;
163 }
164
165 if right1 < right2 {
167 i1 += 1;
168 } else if right2 < right1 {
169 i2 += 1;
170 } else {
171 i1 += 1;
172 i2 += 1;
173 }
174 }
175
176 weight * (argvals[tc] - argvals[sc])
178}
179
180#[inline]
182pub(super) fn dp_lambda_penalty(
183 argvals: &[f64],
184 sc: usize,
185 tc: usize,
186 sr: usize,
187 tr: usize,
188 lambda: f64,
189) -> f64 {
190 if lambda > 0.0 {
191 let dt = argvals[tc] - argvals[sc];
192 let slope = (argvals[tr] - argvals[sr]) / dt;
193 lambda * (slope - 1.0).powi(2) * dt
194 } else {
195 0.0
196 }
197}
198
199fn dp_traceback(parent: &[u32], nrows: usize, ncols: usize) -> Vec<(usize, usize)> {
203 let mut path = Vec::with_capacity(nrows + ncols);
204 let mut cur = (nrows - 1) * ncols + (ncols - 1);
205 loop {
206 path.push((cur / ncols, cur % ncols));
207 if cur == 0 || parent[cur] == u32::MAX {
208 break;
209 }
210 cur = parent[cur] as usize;
211 }
212 path.reverse();
213 path
214}
215
216#[inline]
218fn dp_relax_cell<F>(
219 e: &mut [f64],
220 parent: &mut [u32],
221 ncols: usize,
222 tr: usize,
223 tc: usize,
224 edge_cost: &F,
225) where
226 F: Fn(usize, usize, usize, usize) -> f64,
227{
228 let idx = tr * ncols + tc;
229 for &(dr, dc) in &COPRIME_NBHD_7 {
230 if dr > tr || dc > tc {
231 continue;
232 }
233 let sr = tr - dr;
234 let sc = tc - dc;
235 let src_idx = sr * ncols + sc;
236 if e[src_idx] == f64::INFINITY {
237 continue;
238 }
239 let cost = e[src_idx] + edge_cost(sr, sc, tr, tc);
240 if cost < e[idx] {
241 e[idx] = cost;
242 parent[idx] = src_idx as u32;
243 }
244 }
245}
246
247pub(super) fn dp_grid_solve<F>(nrows: usize, ncols: usize, edge_cost: F) -> Vec<(usize, usize)>
253where
254 F: Fn(usize, usize, usize, usize) -> f64,
255{
256 let mut e = vec![f64::INFINITY; nrows * ncols];
257 let mut parent = vec![u32::MAX; nrows * ncols];
258 e[0] = 0.0;
259
260 for tr in 0..nrows {
261 for tc in 0..ncols {
262 if tr == 0 && tc == 0 {
263 continue;
264 }
265 dp_relax_cell(&mut e, &mut parent, ncols, tr, tc, &edge_cost);
266 }
267 }
268
269 dp_traceback(&parent, nrows, ncols)
270}
271
272pub(super) fn dp_path_to_gamma(path: &[(usize, usize)], argvals: &[f64]) -> Vec<f64> {
274 let path_tc: Vec<f64> = path.iter().map(|&(_, c)| argvals[c]).collect();
275 let path_tr: Vec<f64> = path.iter().map(|&(r, _)| argvals[r]).collect();
276 let mut gamma: Vec<f64> = argvals
277 .iter()
278 .map(|&t| linear_interp(&path_tc, &path_tr, t))
279 .collect();
280 normalize_warp(&mut gamma, argvals);
281 gamma
282}
283
284pub(crate) fn dp_alignment_core(q1: &[f64], q2: &[f64], argvals: &[f64], lambda: f64) -> Vec<f64> {
290 let m = argvals.len();
291 if m < 2 {
292 return argvals.to_vec();
293 }
294
295 let norm1 = q1.iter().map(|&v| v * v).sum::<f64>().sqrt().max(1e-10);
296 let norm2 = q2.iter().map(|&v| v * v).sum::<f64>().sqrt().max(1e-10);
297 let q1n: Vec<f64> = q1.iter().map(|&v| v / norm1).collect();
298 let q2n: Vec<f64> = q2.iter().map(|&v| v / norm2).collect();
299
300 let path = dp_grid_solve(m, m, |sr, sc, tr, tc| {
301 dp_edge_weight(&q1n, &q2n, argvals, sc, tc, sr, tr)
302 + dp_lambda_penalty(argvals, sc, tc, sr, tr, lambda)
303 });
304
305 dp_path_to_gamma(&path, argvals)
306}
307
308#[cfg(test)]
310pub(super) fn gcd(a: usize, b: usize) -> usize {
311 if b == 0 {
312 a
313 } else {
314 gcd(b, a % b)
315 }
316}
317
318#[cfg(test)]
321pub(super) fn generate_coprime_nbhd(nbhd_dim: usize) -> Vec<(usize, usize)> {
322 let mut pairs = Vec::new();
323 for i in 1..=nbhd_dim {
324 for j in 1..=nbhd_dim {
325 if gcd(i, j) == 1 {
326 pairs.push((i, j));
327 }
328 }
329 }
330 pairs
331}