use oxiproj_core::CovMatrix2x2;
pub fn propagate_through_affine_2d(
sigma: &CovMatrix2x2,
a00: f64,
a01: f64,
a10: f64,
a11: f64,
) -> CovMatrix2x2 {
sigma.propagate(a00, a01, a10, a11)
}
pub fn propagate_through_helmert_7_xy(
sigma: &CovMatrix2x2,
rot_z: f64,
scale: f64,
) -> CovMatrix2x2 {
let s1 = 1.0 + scale;
sigma.propagate(s1, -rot_z * s1, rot_z * s1, s1)
}
pub fn propagate_through_helmert_3(sigma: &CovMatrix2x2) -> CovMatrix2x2 {
*sigma
}
#[cfg(test)]
mod tests {
use super::*;
use oxiproj_core::CovMatrix2x2;
const EPS: f64 = 1e-12;
fn approx_eq(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
fn cov_approx_eq(a: &CovMatrix2x2, b: &CovMatrix2x2, eps: f64) -> bool {
approx_eq(a.var_x, b.var_x, eps)
&& approx_eq(a.var_y, b.var_y, eps)
&& approx_eq(a.cov_xy, b.cov_xy, eps)
}
#[test]
fn affine_identity_preserves_covariance() {
let sigma = CovMatrix2x2::new(4.0, 9.0, 2.0);
let out = propagate_through_affine_2d(&sigma, 1.0, 0.0, 0.0, 1.0);
assert!(
cov_approx_eq(&out, &sigma, EPS),
"Identity affine should preserve covariance: got {:?}, want {:?}",
out,
sigma
);
}
#[test]
fn affine_rotation_90_swaps_variances() {
let sigma = CovMatrix2x2::diagonal(4.0, 9.0);
let out = propagate_through_affine_2d(&sigma, 0.0, -1.0, 1.0, 0.0);
let expected = CovMatrix2x2::diagonal(9.0, 4.0);
assert!(
cov_approx_eq(&out, &expected, EPS),
"90° rotation should swap diagonal variances: got {:?}, want {:?}",
out,
expected
);
}
#[test]
fn helmert_3_preserves_covariance() {
let sigma = CovMatrix2x2::new(4.0, 9.0, 2.0);
let out = propagate_through_helmert_3(&sigma);
assert!(
cov_approx_eq(&out, &sigma, EPS),
"Translation-only Helmert should preserve covariance: got {:?}, want {:?}",
out,
sigma
);
}
#[test]
fn helmert_7_typical_rotation_is_psd() {
let sigma = CovMatrix2x2::new(4.0, 9.0, 1.0);
let rot_z = 1e-5_f64; let scale = 0.0;
let out = propagate_through_helmert_7_xy(&sigma, rot_z, scale);
assert!(
out.is_psd(),
"Output covariance should be PSD for typical Helmert rotation: {:?}",
out
);
}
#[test]
fn affine_manual_propagation_matches() {
let sigma = CovMatrix2x2::new(4.0, 9.0, 1.0);
let out = propagate_through_affine_2d(&sigma, 2.0, 1.0, 0.0, 3.0);
assert!(
approx_eq(out.var_x, 29.0, EPS),
"var_x should be 29, got {}",
out.var_x
);
assert!(
approx_eq(out.var_y, 81.0, EPS),
"var_y should be 81, got {}",
out.var_y
);
assert!(
approx_eq(out.cov_xy, 33.0, EPS),
"cov_xy should be 33, got {}",
out.cov_xy
);
}
#[test]
fn helmert_7_identity_preserves_covariance() {
let sigma = CovMatrix2x2::new(4.0, 9.0, 2.0);
let out = propagate_through_helmert_7_xy(&sigma, 0.0, 0.0);
assert!(
cov_approx_eq(&out, &sigma, EPS),
"Helmert-7 identity should preserve covariance: got {:?}, want {:?}",
out,
sigma
);
}
}