mod common;
use cartan_core::{Curvature, GeodesicInterpolation, Manifold, ParallelTransport, Real};
use cartan_manifolds::Spd;
use nalgebra::SMatrix;
use rand::SeedableRng;
use rand::rngs::StdRng;
#[test]
fn spd3_base_identities() {
let m = Spd::<3>;
common::matrix_harness::test_matrix_manifold_base::<3, _>(&m, 1e-7, 100);
}
#[test]
fn spd4_base_identities() {
let m = Spd::<4>;
common::matrix_harness::test_matrix_manifold_base::<4, _>(&m, 1e-6, 50);
}
#[test]
fn spd3_transport() {
let m = Spd::<3>;
common::matrix_harness::test_matrix_transport::<3, _>(&m, 1e-7, 100);
}
#[test]
fn spd3_parallel_transport() {
let m = Spd::<3>;
common::matrix_harness::test_matrix_parallel_transport::<3, _>(&m, 1e-7, 100);
}
#[test]
fn spd3_retraction() {
let m = Spd::<3>;
common::matrix_harness::test_matrix_retraction::<3, _>(&m, 1e-7, 100);
}
#[test]
fn spd3_curvature() {
let m = Spd::<3>;
common::matrix_harness::test_matrix_curvature::<3, _>(&m, 1e-7, 100);
}
#[test]
fn spd3_geodesic() {
let m = Spd::<3>;
common::matrix_harness::test_matrix_geodesic::<3, _>(&m, 1e-7, 100);
}
#[test]
fn spd_injectivity_radius_is_infinite() {
let m = Spd::<3>;
let p = SMatrix::<Real, 3, 3>::identity();
assert!(
m.injectivity_radius(&p).is_infinite(),
"SPD injectivity radius should be infinite"
);
}
#[test]
fn spd3_sectional_curvature_nonpositive() {
let m = Spd::<3>;
let mut rng = StdRng::seed_from_u64(100);
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 uu = m.inner(&p, &u, &u);
let vv = m.inner(&p, &v, &v);
let uv = m.inner(&p, &u, &v);
let denom = uu * vv - uv * uv;
if denom > 1e-10 {
let k = m.sectional_curvature(&p, &u, &v);
assert!(
k <= 1e-10,
"SPD sectional curvature should be ≤ 0, got {k:.6e}"
);
}
}
}
#[test]
fn spd3_scalar_curvature_nonpositive() {
let m = Spd::<3>;
let mut rng = StdRng::seed_from_u64(101);
for _ in 0..50 {
let p = m.random_point(&mut rng);
let s = m.scalar_curvature(&p);
assert!(
s <= 1e-10,
"SPD scalar curvature should be ≤ 0, got {s:.6e}"
);
}
}
#[test]
fn spd3_geodesic_from_identity_to_diagonal() {
let m = Spd::<3>;
let p = SMatrix::<Real, 3, 3>::identity();
let q = SMatrix::<Real, 3, 3>::from_diagonal(&nalgebra::SVector::from([2.0, 3.0, 4.0]));
let g0 = m.geodesic(&p, &q, 0.0).unwrap();
let g1 = m.geodesic(&p, &q, 1.0).unwrap();
assert!(
(g0 - p).norm() < 1e-12,
"geodesic(P,Q,0) ≠ P: err = {:.2e}",
(g0 - p).norm()
);
assert!(
(g1 - q).norm() < 1e-10,
"geodesic(P,Q,1) ≠ Q: err = {:.2e}",
(g1 - q).norm()
);
let mid = m.geodesic(&p, &q, 0.5).unwrap();
m.check_point(&mid)
.expect("geodesic midpoint not a valid SPD matrix");
let mid_eig = nalgebra::SymmetricEigen::new(mid);
for &ev in mid_eig.eigenvalues.iter() {
assert!(
ev > 0.0,
"geodesic midpoint has non-positive eigenvalue {ev:.4e}"
);
}
}
#[test]
fn spd3_affine_invariant_metric() {
let m = Spd::<3>;
let mut rng = StdRng::seed_from_u64(102);
let p = m.random_point(&mut rng);
let u = m.random_tangent(&p, &mut rng);
let v = m.random_tangent(&p, &mut rng);
let a = SMatrix::<Real, 3, 3>::from_row_slice(&[2.0, 0.0, 0.0, 0.5, 1.5, 0.0, 0.3, 0.2, 1.0]);
let p2 = a * p * a.transpose();
let u2 = a * u * a.transpose();
let v2 = a * v * a.transpose();
let inner_orig = m.inner(&p, &u, &v);
let inner_conj = m.inner(&p2, &u2, &v2);
assert!(
(inner_orig - inner_conj).abs() < 1e-10,
"affine invariance violated: <U,V>_P = {inner_orig:.8e}, <AUA^T, AVA^T>_{{APA^T}} = {inner_conj:.8e}"
);
}
#[test]
fn spd_random_points_are_valid() {
let m = Spd::<4>;
let mut rng = StdRng::seed_from_u64(103);
for _ in 0..100 {
let p = m.random_point(&mut rng);
m.check_point(&p)
.expect("random_point not a valid SPD matrix");
}
}
#[test]
fn spd3_parallel_transport_exact_norm() {
let m = Spd::<3>;
let mut rng = StdRng::seed_from_u64(104);
for _ in 0..100 {
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.3;
let q = m.exp(&p, &d_small);
if let (Ok(u_t), Ok(v_t)) = (m.transport(&p, &q, &u), m.transport(&p, &q, &v)) {
let before = m.inner(&p, &u, &v);
let after = m.inner(&q, &u_t, &v_t);
assert!(
(before - after).abs() < 1e-7,
"PT inner product: before={before:.8e}, after={after:.8e}, diff={:.2e}",
(before - after).abs()
);
}
}
}