use crate::error::FdarError;
use crate::fdata::deriv_1d;
use crate::helpers::{cumulative_trapz, linear_interp};
use crate::matrix::FdMatrix;
#[must_use = "expensive computation whose result should not be discarded"]
pub fn srsf_transform(data: &FdMatrix, argvals: &[f64]) -> FdMatrix {
let (n, m) = data.shape();
if n == 0 || m == 0 || argvals.len() != m {
return FdMatrix::zeros(n, m);
}
let deriv = deriv_1d(data, argvals, 1);
let mut result = FdMatrix::zeros(n, m);
for i in 0..n {
for j in 0..m {
let d = deriv[(i, j)];
result[(i, j)] = d.signum() * d.abs().sqrt();
}
}
result
}
pub fn srsf_inverse(q: &[f64], argvals: &[f64], f0: f64) -> Vec<f64> {
let m = q.len();
if m == 0 {
return Vec::new();
}
let integrand: Vec<f64> = q.iter().map(|&qi| qi * qi.abs()).collect();
let integral = cumulative_trapz(&integrand, argvals);
integral.iter().map(|&v| f0 + v).collect()
}
pub fn reparameterize_curve(f: &[f64], argvals: &[f64], gamma: &[f64]) -> Vec<f64> {
gamma
.iter()
.map(|&g| linear_interp(argvals, f, g))
.collect()
}
pub fn compose_warps(gamma1: &[f64], gamma2: &[f64], argvals: &[f64]) -> Vec<f64> {
gamma2
.iter()
.map(|&g| linear_interp(argvals, gamma1, g))
.collect()
}
pub(crate) fn srsf_single(f: &[f64], argvals: &[f64]) -> Vec<f64> {
let m = f.len();
let mat = FdMatrix::from_slice(f, 1, m).expect("dimension invariant: data.len() == n * m");
let q_mat = srsf_transform(&mat, argvals);
q_mat.row(0)
}
pub fn invert_warp(gamma: &[f64], argvals: &[f64]) -> Result<Vec<f64>, FdarError> {
let m = gamma.len();
if m != argvals.len() {
return Err(FdarError::InvalidDimension {
parameter: "gamma",
expected: format!("length {}", argvals.len()),
actual: format!("length {m}"),
});
}
if m < 2 {
return Err(FdarError::InvalidDimension {
parameter: "gamma",
expected: "length >= 2".to_string(),
actual: format!("length {m}"),
});
}
let t0 = argvals[0];
let domain = argvals[m - 1] - t0;
if domain <= 0.0 {
return Err(FdarError::InvalidParameter {
parameter: "argvals",
message: format!("domain must be positive, got {domain}"),
});
}
let gam_01: Vec<f64> = gamma.iter().map(|&g| (g - t0) / domain).collect();
let time_01: Vec<f64> = argvals.iter().map(|&t| (t - t0) / domain).collect();
let inv_01 = crate::warping::invert_gamma(&gam_01, &time_01);
let mut result: Vec<f64> = inv_01.iter().map(|&g| t0 + g * domain).collect();
crate::warping::normalize_warp(&mut result, argvals);
Ok(result)
}
pub fn warp_inverse_error(gamma: &[f64], gamma_inv: &[f64], argvals: &[f64]) -> f64 {
let roundtrip = compose_warps(gamma, gamma_inv, argvals);
roundtrip
.iter()
.zip(argvals.iter())
.map(|(&r, &t)| (r - t).abs())
.fold(0.0_f64, f64::max)
}