mod common;
use cartan_core::{Curvature, GeodesicInterpolation, Manifold, ParallelTransport, Real};
use cartan_manifolds::Corr;
use nalgebra::SMatrix;
use rand::Rng;
use rand::SeedableRng;
use rand::rngs::StdRng;
#[test]
fn corr3_base_identities() {
let m = Corr::<3>;
common::matrix_harness::test_matrix_manifold_base::<3, _>(&m, 1e-10, 200);
}
#[test]
fn corr4_base_identities() {
let m = Corr::<4>;
common::matrix_harness::test_matrix_manifold_base::<4, _>(&m, 1e-10, 100);
}
#[test]
fn corr3_transport() {
let m = Corr::<3>;
common::matrix_harness::test_matrix_transport::<3, _>(&m, 1e-12, 200);
}
#[test]
fn corr3_parallel_transport() {
let m = Corr::<3>;
common::matrix_harness::test_matrix_parallel_transport::<3, _>(&m, 1e-12, 200);
}
#[test]
fn corr3_retraction() {
let m = Corr::<3>;
common::matrix_harness::test_matrix_retraction::<3, _>(&m, 1e-8, 200);
}
#[test]
fn corr3_curvature() {
let m = Corr::<3>;
common::matrix_harness::test_matrix_curvature::<3, _>(&m, 1e-14, 200);
}
#[test]
fn corr3_geodesic() {
let m = Corr::<3>;
common::matrix_harness::test_matrix_geodesic::<3, _>(&m, 1e-12, 200);
}
#[test]
fn corr3_curvature_exactly_zero() {
let m = Corr::<3>;
let mut rng = StdRng::seed_from_u64(200);
for _ in 0..200 {
let p = m.random_point(&mut rng);
let u = m.random_tangent(&p, &mut rng);
let v = m.random_tangent(&p, &mut rng);
let w = m.random_tangent(&p, &mut rng);
let r = m.riemann_curvature(&p, &u, &v, &w);
assert!(
r.norm() < 1e-15,
"R(u,v)w should be exactly 0, got ||R|| = {:.2e}",
r.norm()
);
let k = m.sectional_curvature(&p, &u, &v);
assert_eq!(k, 0.0, "sectional curvature should be exactly 0");
let s = m.scalar_curvature(&p);
assert_eq!(s, 0.0, "scalar curvature should be exactly 0");
}
}
#[test]
fn corr3_parallel_transport_is_identity() {
let m = Corr::<3>;
let mut rng = StdRng::seed_from_u64(201);
for _ in 0..200 {
let p = m.random_point(&mut rng);
let u = m.random_tangent(&p, &mut rng);
let v = m.random_tangent(&p, &mut rng);
let d = m.random_tangent(&p, &mut rng);
let d_small = d * 0.05;
let q = m.exp(&p, &d_small);
let u_t = m.transport(&p, &q, &u).unwrap();
let v_t = m.transport(&p, &q, &v).unwrap();
assert!(
(u_t - u).norm() < 1e-14,
"PT should be identity: ||u_t - u|| = {:.2e}",
(u_t - u).norm()
);
assert!(
(v_t - v).norm() < 1e-14,
"PT should be identity: ||v_t - v|| = {:.2e}",
(v_t - v).norm()
);
}
}
#[test]
fn corr3_geodesic_is_linear() {
let m = Corr::<3>;
let mut rng = StdRng::seed_from_u64(202);
for _ in 0..100 {
let p = m.random_point(&mut rng);
let v = m.random_tangent(&p, &mut rng);
let d_small = v * 0.1;
let q = m.exp(&p, &d_small);
for k in 0..=10 {
let t = k as Real / 10.0;
let gamma_t = m.geodesic(&p, &q, t).unwrap();
let linear_t = p * (1.0 - t) + q * t;
let err = (gamma_t - linear_t).norm();
assert!(
err < 1e-13,
"geodesic is not linear at t={t}: err = {err:.2e}"
);
}
}
}
#[test]
fn corr3_project_point_finds_valid_corr() {
let m = Corr::<3>;
let rng = StdRng::seed_from_u64(203);
for _ in 0..50 {
let a: SMatrix<Real, 3, 3> = SMatrix::from_fn(|i, j| {
if i == j {
1.0
} else {
let mut rng2 = rng.clone();
rng2.random::<Real>() * 1.6 - 0.8
}
});
let sym = (a + a.transpose()) * 0.5;
let mut input = sym;
input[(0, 0)] = 1.0;
input[(1, 1)] = 1.0;
input[(2, 2)] = 1.0;
let c = m.project_point(&input);
m.check_point(&c)
.expect("project_point result not a valid correlation matrix");
}
}
#[test]
fn corr3_project_point_fixpoint() {
let m = Corr::<3>;
let mut rng = StdRng::seed_from_u64(204);
for _ in 0..100 {
let c = m.random_point(&mut rng);
let c2 = m.project_point(&c);
let err = (c - c2).norm();
assert!(
err < 1e-8,
"project_point moved a valid correlation matrix: err = {err:.2e}"
);
}
}
#[test]
fn corr2_is_interval() {
let m = Corr::<2>;
for k in [-9i32, -7, -5, -3, 0, 3, 5, 7, 9] {
let r = k as Real / 10.0;
let c = SMatrix::<Real, 2, 2>::from_row_slice(&[1.0, r, r, 1.0]);
m.check_point(&c)
.unwrap_or_else(|e| panic!("Corr(2) point r={r} invalid: {e}"));
}
for &r in &[-1.0_f64, 1.0_f64] {
let c = SMatrix::<Real, 2, 2>::from_row_slice(&[1.0, r, r, 1.0]);
assert!(
m.check_point(&c).is_err(),
"Corr(2) r={r} should fail (singular)"
);
}
}
#[test]
fn corr3_injectivity_radius_is_min_eigenvalue() {
let m = Corr::<3>;
let mut rng = StdRng::seed_from_u64(205);
for _ in 0..50 {
let c = m.random_point(&mut rng);
let r = m.injectivity_radius(&c);
assert!(r > 0.0, "injectivity radius should be positive, got {r}");
assert!(
r <= 1.0,
"injectivity radius of a correlation matrix cannot exceed 1, got {r}"
);
}
}
#[test]
fn corr_dim() {
assert_eq!(Corr::<2>.dim(), 1);
assert_eq!(Corr::<3>.dim(), 3);
assert_eq!(Corr::<4>.dim(), 6);
assert_eq!(Corr::<5>.dim(), 10);
}