mod bayesian;
mod closed;
mod clustering;
mod constrained;
mod diagnostics;
mod elastic_depth;
mod fpns;
mod generative;
mod geodesic;
mod karcher;
mod lambda_cv;
mod multires;
mod nd;
mod outlier;
mod pairwise;
mod partial_match;
mod persistence;
mod phase_boxplot;
mod quality;
mod robust_karcher;
mod set;
mod shape;
mod shape_ci;
mod srsf;
mod transfer;
mod tsrvf;
mod warp_stats;
#[cfg(test)]
mod tests;
pub use bayesian::{bayesian_align_pair, BayesianAlignConfig, BayesianAlignmentResult};
pub use closed::{
elastic_align_pair_closed, elastic_distance_closed, karcher_mean_closed, ClosedAlignmentResult,
ClosedKarcherMeanResult,
};
pub use clustering::{
cut_dendrogram, hierarchical_from_distances, kmedoids_from_distances, Dendrogram,
KMedoidsConfig, KMedoidsResult, Linkage,
};
pub use constrained::{
elastic_align_pair_constrained, elastic_align_pair_with_landmarks, ConstrainedAlignmentResult,
};
pub use diagnostics::{
diagnose_alignment, diagnose_pairwise, AlignmentDiagnostic, AlignmentDiagnosticSummary,
DiagnosticConfig,
};
pub use elastic_depth::{elastic_depth, ElasticDepthResult};
pub use fpns::{horiz_fpns, FpnsResult};
pub use generative::{gauss_model, joint_gauss_model, GenerativeModelResult};
pub use geodesic::{curve_geodesic, curve_geodesic_nd, GeodesicPath, GeodesicPathNd};
pub use karcher::karcher_mean;
pub use lambda_cv::{lambda_cv, LambdaCvConfig, LambdaCvResult};
pub use multires::{elastic_align_pair_multires, MultiresConfig};
pub use nd::{
elastic_align_pair_nd, elastic_distance_nd, srsf_inverse_nd, srsf_transform_nd,
AlignmentResultNd,
};
pub use nd::{karcher_covariance_nd, karcher_mean_nd, pca_nd, KarcherMeanResultNd, PcaNdResult};
pub use outlier::{elastic_outlier_detection, ElasticOutlierConfig, ElasticOutlierResult};
pub use pairwise::{
amplitude_distance, amplitude_self_distance_matrix, elastic_align_pair,
elastic_align_pair_penalized, elastic_cross_distance_matrix, elastic_distance,
elastic_self_distance_matrix, phase_distance_pair, phase_self_distance_matrix, WarpPenaltyType,
};
pub use partial_match::{elastic_partial_match, PartialMatchConfig, PartialMatchResult};
pub use persistence::{peak_persistence, PersistenceDiagramResult};
pub use phase_boxplot::{phase_boxplot, PhaseBoxplot};
pub use quality::{
alignment_quality, pairwise_consistency, warp_complexity, warp_smoothness, AlignmentQuality,
};
pub use robust_karcher::{
karcher_median, robust_karcher_mean, RobustKarcherConfig, RobustKarcherResult,
};
pub use set::{align_to_target, elastic_decomposition, DecompositionResult};
pub use shape::{
orbit_representative, shape_distance, shape_mean, shape_self_distance_matrix,
OrbitRepresentative, ShapeDistanceResult, ShapeMeanResult, ShapeQuotient,
};
pub use shape_ci::{shape_confidence_interval, ShapeCiConfig, ShapeCiResult};
pub use srsf::{
compose_warps, invert_warp, reparameterize_curve, srsf_inverse, srsf_transform,
warp_inverse_error,
};
pub use transfer::{transfer_alignment, TransferAlignConfig, TransferAlignResult};
pub use tsrvf::{
tsrvf_from_alignment, tsrvf_from_alignment_with_method, tsrvf_inverse, tsrvf_transform,
tsrvf_transform_with_method, TransportMethod, TsrvfResult,
};
pub use warp_stats::{warp_statistics, WarpStatistics};
pub(crate) use karcher::sqrt_mean_inverse;
use crate::helpers::linear_interp;
use crate::matrix::FdMatrix;
use crate::warping::normalize_warp;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct AlignmentResult {
pub gamma: Vec<f64>,
pub f_aligned: Vec<f64>,
pub distance: f64,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct AlignmentSetResult {
pub gammas: FdMatrix,
pub aligned_data: FdMatrix,
pub distances: Vec<f64>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct KarcherMeanResult {
pub mean: Vec<f64>,
pub mean_srsf: Vec<f64>,
pub gammas: FdMatrix,
pub aligned_data: FdMatrix,
pub n_iter: usize,
pub converged: bool,
pub aligned_srsfs: Option<FdMatrix>,
}
impl KarcherMeanResult {
pub fn new(
mean: Vec<f64>,
mean_srsf: Vec<f64>,
gammas: FdMatrix,
aligned_data: FdMatrix,
n_iter: usize,
converged: bool,
aligned_srsfs: Option<FdMatrix>,
) -> Self {
Self {
mean,
mean_srsf,
gammas,
aligned_data,
n_iter,
converged,
aligned_srsfs,
}
}
}
pub trait AlignmentOutput {
fn mean(&self) -> &[f64];
fn mean_srsf(&self) -> &[f64];
fn aligned_data(&self) -> &FdMatrix;
fn gammas(&self) -> &FdMatrix;
fn converged(&self) -> bool;
fn n_iter(&self) -> usize;
}
impl AlignmentOutput for KarcherMeanResult {
fn mean(&self) -> &[f64] {
&self.mean
}
fn mean_srsf(&self) -> &[f64] {
&self.mean_srsf
}
fn aligned_data(&self) -> &FdMatrix {
&self.aligned_data
}
fn gammas(&self) -> &FdMatrix {
&self.gammas
}
fn converged(&self) -> bool {
self.converged
}
fn n_iter(&self) -> usize {
self.n_iter
}
}
impl AlignmentOutput for RobustKarcherResult {
fn mean(&self) -> &[f64] {
&self.mean
}
fn mean_srsf(&self) -> &[f64] {
&self.mean_srsf
}
fn aligned_data(&self) -> &FdMatrix {
&self.aligned_data
}
fn gammas(&self) -> &FdMatrix {
&self.gammas
}
fn converged(&self) -> bool {
self.converged
}
fn n_iter(&self) -> usize {
self.n_iter
}
}
impl From<RobustKarcherResult> for KarcherMeanResult {
fn from(r: RobustKarcherResult) -> Self {
Self {
mean: r.mean,
mean_srsf: r.mean_srsf,
gammas: r.gammas,
aligned_data: r.aligned_data,
n_iter: r.n_iter,
converged: r.converged,
aligned_srsfs: None,
}
}
}
#[rustfmt::skip]
const COPRIME_NBHD_7: [(usize, usize); 35] = [
(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),
(2,1), (2,3), (2,5), (2,7),
(3,1),(3,2), (3,4),(3,5), (3,7),
(4,1), (4,3), (4,5), (4,7),
(5,1),(5,2),(5,3),(5,4), (5,6),(5,7),
(6,1), (6,5), (6,7),
(7,1),(7,2),(7,3),(7,4),(7,5),(7,6),
];
#[inline]
pub(super) fn dp_edge_weight(
q1: &[f64],
q2: &[f64],
argvals: &[f64],
sc: usize,
tc: usize,
sr: usize,
tr: usize,
) -> f64 {
let n1 = tc - sc;
let n2 = tr - sr;
if n1 == 0 || n2 == 0 {
return f64::INFINITY;
}
let slope = (argvals[tr] - argvals[sr]) / (argvals[tc] - argvals[sc]);
let rslope = slope.sqrt();
let mut weight = 0.0;
let mut i1 = 0usize; let mut i2 = 0usize;
while i1 < n1 && i2 < n2 {
let left1 = i1 as f64 / n1 as f64;
let right1 = (i1 + 1) as f64 / n1 as f64;
let left2 = i2 as f64 / n2 as f64;
let right2 = (i2 + 1) as f64 / n2 as f64;
let left = left1.max(left2);
let right = right1.min(right2);
let dt = right - left;
if dt > 0.0 {
let diff = q1[sc + i1] - rslope * q2[sr + i2];
weight += diff * diff * dt;
}
if right1 < right2 {
i1 += 1;
} else if right2 < right1 {
i2 += 1;
} else {
i1 += 1;
i2 += 1;
}
}
weight * (argvals[tc] - argvals[sc])
}
#[inline]
pub(super) fn dp_lambda_penalty(
argvals: &[f64],
sc: usize,
tc: usize,
sr: usize,
tr: usize,
lambda: f64,
) -> f64 {
if lambda > 0.0 {
let dt = argvals[tc] - argvals[sc];
let slope = (argvals[tr] - argvals[sr]) / dt;
lambda * (slope - 1.0).powi(2) * dt
} else {
0.0
}
}
fn dp_traceback(parent: &[u32], nrows: usize, ncols: usize) -> Vec<(usize, usize)> {
let mut path = Vec::with_capacity(nrows + ncols);
let mut cur = (nrows - 1) * ncols + (ncols - 1);
loop {
path.push((cur / ncols, cur % ncols));
if cur == 0 || parent[cur] == u32::MAX {
break;
}
cur = parent[cur] as usize;
}
path.reverse();
path
}
#[inline]
fn dp_relax_cell<F>(
e: &mut [f64],
parent: &mut [u32],
ncols: usize,
tr: usize,
tc: usize,
edge_cost: &F,
) where
F: Fn(usize, usize, usize, usize) -> f64,
{
let idx = tr * ncols + tc;
for &(dr, dc) in &COPRIME_NBHD_7 {
if dr > tr || dc > tc {
continue;
}
let sr = tr - dr;
let sc = tc - dc;
let src_idx = sr * ncols + sc;
if e[src_idx] == f64::INFINITY {
continue;
}
let cost = e[src_idx] + edge_cost(sr, sc, tr, tc);
if cost < e[idx] {
e[idx] = cost;
parent[idx] = src_idx as u32;
}
}
}
pub(super) fn dp_grid_solve<F>(nrows: usize, ncols: usize, edge_cost: F) -> Vec<(usize, usize)>
where
F: Fn(usize, usize, usize, usize) -> f64,
{
let mut e = vec![f64::INFINITY; nrows * ncols];
let mut parent = vec![u32::MAX; nrows * ncols];
e[0] = 0.0;
for tr in 0..nrows {
for tc in 0..ncols {
if tr == 0 && tc == 0 {
continue;
}
dp_relax_cell(&mut e, &mut parent, ncols, tr, tc, &edge_cost);
}
}
dp_traceback(&parent, nrows, ncols)
}
pub(super) fn dp_path_to_gamma(path: &[(usize, usize)], argvals: &[f64]) -> Vec<f64> {
let path_tc: Vec<f64> = path.iter().map(|&(_, c)| argvals[c]).collect();
let path_tr: Vec<f64> = path.iter().map(|&(r, _)| argvals[r]).collect();
let mut gamma: Vec<f64> = argvals
.iter()
.map(|&t| linear_interp(&path_tc, &path_tr, t))
.collect();
normalize_warp(&mut gamma, argvals);
gamma
}
pub(crate) fn dp_alignment_core(q1: &[f64], q2: &[f64], argvals: &[f64], lambda: f64) -> Vec<f64> {
let m = argvals.len();
if m < 2 {
return argvals.to_vec();
}
let norm1 = q1.iter().map(|&v| v * v).sum::<f64>().sqrt().max(1e-10);
let norm2 = q2.iter().map(|&v| v * v).sum::<f64>().sqrt().max(1e-10);
let q1n: Vec<f64> = q1.iter().map(|&v| v / norm1).collect();
let q2n: Vec<f64> = q2.iter().map(|&v| v / norm2).collect();
let path = dp_grid_solve(m, m, |sr, sc, tr, tc| {
dp_edge_weight(&q1n, &q2n, argvals, sc, tc, sr, tr)
+ dp_lambda_penalty(argvals, sc, tc, sr, tr, lambda)
});
dp_path_to_gamma(&path, argvals)
}
#[cfg(test)]
pub(super) fn gcd(a: usize, b: usize) -> usize {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
#[cfg(test)]
pub(super) fn generate_coprime_nbhd(nbhd_dim: usize) -> Vec<(usize, usize)> {
let mut pairs = Vec::new();
for i in 1..=nbhd_dim {
for j in 1..=nbhd_dim {
if gcd(i, j) == 1 {
pairs.push((i, j));
}
}
}
pairs
}