use ndarray::{Array1, Array2, ArrayView1};
use crate::manifold::{
GeometryError, GeometryResult, ManifoldSpec, RiemannianManifold, cholesky_spd, dot, flatten,
from_flat, jacobi_symmetric, norm, qr_thin,
};
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn uniform(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
fn normal(&mut self) -> f64 {
let u1 = self.uniform().max(1.0e-300);
let u2 = self.uniform();
(-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos()
}
fn gaussian_vec(&mut self, n: usize) -> Array1<f64> {
Array1::from_shape_fn(n, |_| self.normal())
}
fn angle(&mut self) -> f64 {
(self.uniform() - 0.5) * std::f64::consts::TAU
}
fn orthogonal(&mut self, k: usize) -> Array2<f64> {
let a = Array2::from_shape_fn((k, k), |_| self.normal());
qr_thin(&a).0
}
}
const SEED: u64 = 0x2545_F491_4F6C_DD1D;
const TRIALS: usize = 40;
fn inventory() -> Vec<(&'static str, ManifoldSpec)> {
use ManifoldSpec::*;
vec![
("Euclidean(3)", Euclidean(3)),
("Euclidean(0)", Euclidean(0)),
("Circle", Circle),
("Sphere(0)", Sphere { intrinsic_dim: 0 }),
("Sphere(1)", Sphere { intrinsic_dim: 1 }),
("Sphere(2)", Sphere { intrinsic_dim: 2 }),
("Sphere(4)", Sphere { intrinsic_dim: 4 }),
("Torus(3)", Torus { dim: 3 }),
("Gr(1,1)", Grassmann { k: 1, n: 1 }),
("Gr(1,4)", Grassmann { k: 1, n: 4 }),
("Gr(2,5)", Grassmann { k: 2, n: 5 }),
("Gr(3,6)", Grassmann { k: 3, n: 6 }),
("Gr(4,5)", Grassmann { k: 4, n: 5 }),
("Gr(3,3)", Grassmann { k: 3, n: 3 }),
("St(1,1)", Stiefel { k: 1, n: 1 }),
("St(1,4)", Stiefel { k: 1, n: 4 }),
("St(2,5)", Stiefel { k: 2, n: 5 }),
("St(3,4)", Stiefel { k: 3, n: 4 }),
("St(4,5)", Stiefel { k: 4, n: 5 }),
("St(3,3)", Stiefel { k: 3, n: 3 }),
("Spd(1)", Spd { n: 1 }),
("Spd(2)", Spd { n: 2 }),
("Spd(3)", Spd { n: 3 }),
("Spd(5)", Spd { n: 5 }),
("Product[]", Product(vec![])),
("Product[Circle,E2]", Product(vec![Circle, Euclidean(2)])),
(
"Product[S2,Spd2]",
Product(vec![Sphere { intrinsic_dim: 2 }, Spd { n: 2 }]),
),
]
}
fn variant_name(spec: &ManifoldSpec) -> &'static str {
match spec {
ManifoldSpec::Euclidean(_) => "Euclidean",
ManifoldSpec::Circle => "Circle",
ManifoldSpec::Sphere { .. } => "Sphere",
ManifoldSpec::Torus { .. } => "Torus",
ManifoldSpec::Grassmann { .. } => "Grassmann",
ManifoldSpec::Stiefel { .. } => "Stiefel",
ManifoldSpec::Spd { .. } => "Spd",
ManifoldSpec::Product(_) => "Product",
}
}
fn random_point(spec: &ManifoldSpec, rng: &mut Rng) -> Array1<f64> {
match spec {
ManifoldSpec::Euclidean(dim) => rng.gaussian_vec(*dim),
ManifoldSpec::Circle => Array1::from(vec![rng.angle()]),
ManifoldSpec::Sphere { intrinsic_dim } => {
let v = rng.gaussian_vec(intrinsic_dim + 1);
let scale = norm(v.view());
v / scale
}
ManifoldSpec::Torus { dim } => Array1::from_shape_fn(*dim, |_| rng.angle()),
ManifoldSpec::Grassmann { k, n } | ManifoldSpec::Stiefel { k, n } => {
let a = Array2::from_shape_fn((*n, *k), |_| rng.normal());
flatten(&qr_thin(&a).0)
}
ManifoldSpec::Spd { n } => {
let a = Array2::from_shape_fn((*n, *n), |_| rng.normal());
let mut p = a.dot(&a.t());
for i in 0..*n {
p[[i, i]] += *n as f64;
}
flatten(&p)
}
ManifoldSpec::Product(parts) => {
let mut out: Vec<f64> = Vec::new();
for part in parts {
out.extend(random_point(part, rng).iter().copied());
}
Array1::from(out)
}
}
}
fn on_manifold_defect(spec: &ManifoldSpec, x: &Array1<f64>) -> f64 {
match spec {
ManifoldSpec::Euclidean(_) | ManifoldSpec::Circle | ManifoldSpec::Torus { .. } => 0.0,
ManifoldSpec::Sphere { .. } => (norm(x.view()) - 1.0).abs(),
ManifoldSpec::Grassmann { k, n } | ManifoldSpec::Stiefel { k, n } => {
let y = from_flat(x.view(), *n, *k).expect("frame shape");
let gram = y.t().dot(&y);
let mut worst = 0.0_f64;
for i in 0..*k {
for j in 0..*k {
let want = if i == j { 1.0 } else { 0.0 };
worst = worst.max((gram[[i, j]] - want).abs());
}
}
worst
}
ManifoldSpec::Spd { n } => {
let a = from_flat(x.view(), *n, *n).expect("spd shape");
let mut worst = 0.0_f64;
for i in 0..*n {
for j in 0..*n {
worst = worst.max((a[[i, j]] - a[[j, i]]).abs());
}
}
if cholesky_spd(&a).is_err() {
worst = worst.max(1.0);
}
worst
}
ManifoldSpec::Product(parts) => {
let mut offset = 0usize;
let mut worst = 0.0_f64;
for part in parts {
let width = part.build().expect("component builds").ambient_dim();
let sub = x.slice(ndarray::s![offset..offset + width]).to_owned();
worst = worst.max(on_manifold_defect(part, &sub));
offset += width;
}
worst
}
}
}
fn sample_tangent(
manifold: &dyn RiemannianManifold,
point: ArrayView1<'_, f64>,
rng: &mut Rng,
) -> Array1<f64> {
let ambient = manifold.ambient_dim();
let raw = rng.gaussian_vec(ambient);
let projected = manifold
.project_tangent(point, raw.view())
.expect("project_tangent must accept an ambient vector at a manifold point");
let magnitude = norm(projected.view());
if magnitude > 1.0e-8 * norm(raw.view()).max(1.0) {
projected * (0.3 / magnitude)
} else {
Array1::zeros(ambient)
}
}
fn subspace_projector(flat: &Array1<f64>, n: usize, k: usize) -> Array2<f64> {
let y = from_flat(flat.view(), n, k).expect("frame shape");
y.dot(&y.t())
}
fn regauge(flat: &Array1<f64>, n: usize, k: usize, rot: &Array2<f64>) -> Array1<f64> {
flatten(&from_flat(flat.view(), n, k).expect("frame shape").dot(rot))
}
fn grassmann_pair_conditioning(p: &Array1<f64>, q: &Array1<f64>, n: usize, k: usize) -> f64 {
let y = from_flat(p.view(), n, k).expect("frame shape");
let z = from_flat(q.view(), n, k).expect("frame shape");
let cross = y.t().dot(&z);
let gram = cross.t().dot(&cross);
let (eigenvalues, _) = jacobi_symmetric(&gram).expect("symmetric k×k eigendecomposition");
let smallest = eigenvalues.iter().fold(f64::INFINITY, |a, &b| a.min(b));
smallest.max(0.0).sqrt()
}
fn grassmann_tolerance(sigma_min: f64) -> f64 {
1.0e-9 + 1.0e-13 / (sigma_min * sigma_min).max(f64::MIN_POSITIVE)
}
fn point_scale(p: &Array1<f64>) -> f64 {
1.0 + p.iter().fold(0.0_f64, |a, x| a.max(x.abs()))
}
fn sup_diff(a: &Array1<f64>, b: &Array1<f64>) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.fold(0.0, f64::max)
}
fn sup_diff_2d(a: &Array2<f64>, b: &Array2<f64>) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.fold(0.0, f64::max)
}
fn is_declared_unsupported<T>(result: &GeometryResult<T>) -> bool {
matches!(result, Err(GeometryError::Unsupported(_)))
}
fn is_pair_out_of_domain<T>(result: &GeometryResult<T>) -> bool {
matches!(
result,
Err(GeometryError::Unsupported(_))
| Err(GeometryError::Singular(_))
| Err(GeometryError::NonConvergence { .. })
)
}
#[test]
fn inventory_covers_every_manifold_spec_variant() {
let covered: Vec<&'static str> = inventory()
.iter()
.map(|(_, spec)| variant_name(spec))
.collect();
for variant in [
"Euclidean",
"Circle",
"Sphere",
"Torus",
"Grassmann",
"Stiefel",
"Spd",
"Product",
] {
assert!(
covered.contains(&variant),
"ManifoldSpec::{variant} is not exercised by the conformance inventory — \
add it, or a manifold can inherit the trait's flat defaults unnoticed"
);
}
for (label, spec) in inventory() {
let manifold = spec
.build()
.unwrap_or_else(|e| panic!("{label} must build: {e}"));
assert!(
manifold.dim() <= manifold.ambient_dim(),
"{label}: dim {} exceeds ambient_dim {}",
manifold.dim(),
manifold.ambient_dim()
);
}
}
#[test]
fn exp_and_log_are_mutually_inverse() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let q = random_point(&spec, &mut rng);
let v = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let scale = point_scale(&p);
let at_zero = manifold
.exp_map(p.view(), Array1::zeros(manifold.ambient_dim()).view())
.expect("exp at zero tangent");
assert!(
sup_diff(&at_zero, &p) <= 1.0e-12 * scale,
"{label} trial {trial}: exp_p(0) != p (sup {:.3e}, scale {scale:.3e})",
sup_diff(&at_zero, &p)
);
let self_log = manifold.log_map(p.view(), p.view()).expect("log_p(p)");
assert!(
norm(self_log.view()) <= 1.0e-9 * scale,
"{label} trial {trial}: log_p(p) != 0 (norm {:.3e}, scale {scale:.3e})",
norm(self_log.view())
);
let moved = manifold.exp_map(p.view(), v.view()).expect("exp_p(v)");
let recovered = manifold
.log_map(p.view(), moved.view())
.expect("log_p(exp_p(v)) is inside the injectivity radius");
assert!(
sup_diff(&recovered, &v) <= 1.0e-9 * scale,
"{label} trial {trial}: log_p(exp_p(v)) != v (sup {:.3e}, scale {scale:.3e})",
sup_diff(&recovered, &v)
);
verified += 1;
let to_q = manifold.log_map(p.view(), q.view());
if is_pair_out_of_domain(&to_q) {
continue;
}
let to_q = to_q.expect("log_p(q)");
let round_trip = manifold.exp_map(p.view(), to_q.view()).expect("exp_p(log)");
if let ManifoldSpec::Grassmann { k, n } = spec {
let gap = sup_diff_2d(
&subspace_projector(&round_trip, n, k),
&subspace_projector(&q, n, k),
);
let sigma_min = grassmann_pair_conditioning(&p, &q, n, k);
let tolerance = grassmann_tolerance(sigma_min);
assert!(
gap <= tolerance,
"{label} trial {trial}: exp_p(log_p(q)) spans a different subspace \
(projector sup {gap:.3e} > {tolerance:.3e}, \
cos of largest principal angle {sigma_min:.3e})"
);
} else {
let target_scale = scale.max(point_scale(&q));
assert!(
sup_diff(&round_trip, &q) <= 1.0e-9 * target_scale,
"{label} trial {trial}: exp_p(log_p(q)) != q (sup {:.3e}, scale {target_scale:.3e})",
sup_diff(&round_trip, &q)
);
}
verified += 1;
}
}
assert!(verified > 0, "no exp/log round trip was actually evaluated");
}
#[test]
fn tangent_projection_is_idempotent_and_log_lands_tangent() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let q = random_point(&spec, &mut rng);
let v = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let again = manifold
.project_tangent(p.view(), v.view())
.expect("re-project a tangent vector");
assert!(
sup_diff(&again, &v) <= 1.0e-12 * point_scale(&p),
"{label} trial {trial}: project_tangent is not idempotent (sup {:.3e})",
sup_diff(&again, &v)
);
let to_q = manifold.log_map(p.view(), q.view());
if is_pair_out_of_domain(&to_q) {
continue;
}
let to_q = to_q.expect("log_p(q)");
let projected = manifold
.project_tangent(p.view(), to_q.view())
.expect("project the logarithm");
assert!(
sup_diff(&projected, &to_q) <= 1.0e-9 * point_scale(&to_q),
"{label} trial {trial}: log_p(q) is not in the tangent space at p (sup {:.3e})",
sup_diff(&projected, &to_q)
);
verified += 1;
}
}
assert!(verified > 0, "no logarithm was actually projected");
}
#[test]
fn tangent_basis_is_metric_orthonormal_and_tangent() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let (dim, ambient) = (manifold.dim(), manifold.ambient_dim());
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let basis = manifold.tangent_basis(p.view()).expect("tangent_basis");
assert_eq!(
(basis.nrows(), basis.ncols()),
(ambient, dim),
"{label} trial {trial}: tangent_basis has the wrong shape"
);
let metric = manifold.metric_tensor(p.view()).expect("metric_tensor");
let gram = basis.t().dot(&metric.dot(&basis));
for i in 0..dim {
for j in 0..dim {
let want = if i == j { 1.0 } else { 0.0 };
assert!(
(gram[[i, j]] - want).abs() <= 1.0e-9,
"{label} trial {trial}: BᵀGB[{i},{j}] = {} (want {want})",
gram[[i, j]]
);
}
}
for j in 0..dim {
let column = basis.column(j).to_owned();
let projected = manifold
.project_tangent(p.view(), column.view())
.expect("project a basis column");
assert!(
sup_diff(&projected, &column) <= 1.0e-8,
"{label} trial {trial}: basis column {j} is not tangent (sup {:.3e})",
sup_diff(&projected, &column)
);
}
verified += 1;
}
}
assert!(verified > 0, "no tangent basis was actually checked");
}
#[test]
fn riemannian_gradient_is_the_metric_riesz_representative() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let (dim, ambient) = (manifold.dim(), manifold.ambient_dim());
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let differential = rng.gaussian_vec(ambient);
let basis = manifold.tangent_basis(p.view()).expect("tangent_basis");
let metric = manifold.metric_tensor(p.view()).expect("metric_tensor");
let gradient = manifold
.riemannian_gradient(p.view(), differential.view())
.expect("riemannian_gradient");
let projected = manifold
.project_tangent(p.view(), gradient.view())
.expect("project the gradient");
assert!(
sup_diff(&projected, &gradient) <= 1.0e-8,
"{label} trial {trial}: the Riemannian gradient is not tangent (sup {:.3e})",
sup_diff(&projected, &gradient)
);
for j in 0..dim {
let xi = basis.column(j).to_owned();
let raised = gradient.view().dot(&metric.dot(&xi));
let paired = dot(differential.view(), xi.view());
let scale = raised.abs().max(paired.abs()).max(1.0);
assert!(
(raised - paired).abs() <= 1.0e-8 * scale,
"{label} trial {trial}: g(grad, ξ_{j}) = {raised} != ⟨e, ξ_{j}⟩ = {paired}"
);
}
verified += 1;
}
}
assert!(verified > 0, "no Riemannian gradient was actually checked");
}
#[test]
fn exp_and_retract_land_on_the_manifold() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let v = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let stepped = manifold.exp_map(p.view(), v.view()).expect("exp_p(v)");
let defect = on_manifold_defect(&spec, &stepped);
let bound = 1.0e-9 * point_scale(&stepped);
assert!(
defect <= bound,
"{label} trial {trial}: exp_p(v) is off the manifold (defect {defect:.3e} > {bound:.3e})"
);
let retracted = manifold.retract(p.view(), v.view()).expect("retract");
let defect = on_manifold_defect(&spec, &retracted);
let bound = 1.0e-9 * point_scale(&retracted);
assert!(
defect <= bound,
"{label} trial {trial}: retract_p(v) is off the manifold (defect {defect:.3e} > {bound:.3e})"
);
verified += 1;
}
}
assert!(verified > 0, "no step was actually checked for membership");
}
#[test]
fn geodesics_have_constant_speed_and_symmetric_distance() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let q = random_point(&spec, &mut rng);
let v = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let speed = norm(v.view());
for fraction in [0.25_f64, 0.5, 0.75, 1.0] {
let scaled = &v * fraction;
let along = manifold
.exp_map(p.view(), scaled.view())
.expect("exp along the geodesic");
let back = manifold
.log_map(p.view(), along.view())
.expect("log along a short geodesic");
let travelled = norm(back.view());
assert!(
(travelled - fraction * speed).abs() <= 1.0e-9 * speed.max(1.0),
"{label} trial {trial}: geodesic at t={fraction} travelled {travelled}, \
want {}",
fraction * speed
);
}
let (out, back) = (
manifold.log_map(p.view(), q.view()),
manifold.log_map(q.view(), p.view()),
);
if is_pair_out_of_domain(&out) || is_pair_out_of_domain(&back) {
continue;
}
let (out, back) = (out.expect("log_p(q)"), back.expect("log_q(p)"));
let gp = manifold.metric_tensor(p.view()).expect("metric at p");
let gq = manifold.metric_tensor(q.view()).expect("metric at q");
let forward = out.view().dot(&gp.dot(&out)).abs().sqrt();
let reverse = back.view().dot(&gq.dot(&back)).abs().sqrt();
assert!(
(forward - reverse).abs() <= 1.0e-8 * forward.max(1.0),
"{label} trial {trial}: d(p,q) = {forward} but d(q,p) = {reverse}"
);
verified += 1;
}
}
assert!(verified > 0, "no geodesic was actually traversed");
}
#[test]
fn parallel_transport_is_an_isometry_where_it_is_supported() {
let mut verified = 0usize;
let mut declined = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let ambient = manifold.ambient_dim();
if manifold.dim() < 2 {
continue;
}
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let q = random_point(&spec, &mut rng);
let u1 = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let u2 = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let mut path = Array2::<f64>::zeros((2, ambient));
path.row_mut(0).assign(&p);
path.row_mut(1).assign(&q);
let t1 = manifold.parallel_transport(path.view(), u1.view());
if is_declared_unsupported(&t1) {
declined += 1;
continue;
}
let t1 = match t1 {
Ok(t) => t,
Err(GeometryError::Singular(_)) => continue,
Err(e) => panic!("{label} trial {trial}: parallel_transport failed: {e}"),
};
let t2 = match manifold.parallel_transport(path.view(), u2.view()) {
Ok(t) => t,
Err(GeometryError::Singular(_)) => continue,
Err(e) => panic!("{label} trial {trial}: parallel_transport failed: {e}"),
};
let landed = manifold
.project_tangent(q.view(), t1.view())
.expect("project the transported vector at q");
assert!(
sup_diff(&landed, &t1) <= 1.0e-8,
"{label} trial {trial}: transported vector is not tangent at q (sup {:.3e})",
sup_diff(&landed, &t1)
);
let gp = manifold.metric_tensor(p.view()).expect("metric at p");
let gq = manifold.metric_tensor(q.view()).expect("metric at q");
let before = u1.view().dot(&gp.dot(&u2));
let after = t1.view().dot(&gq.dot(&t2));
assert!(
(before - after).abs() <= 1.0e-8 * before.abs().max(1.0),
"{label} trial {trial}: transport changed the inner product \
({before} -> {after})"
);
verified += 1;
}
}
assert!(
verified > 0,
"parallel transport was declined everywhere ({declined} declines) — \
the isometry property was never evaluated"
);
}
#[test]
fn sectional_curvature_is_symmetric_and_scale_invariant() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
if manifold.dim() < 2 {
continue;
}
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let u = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let v = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let forward = manifold.sectional_curvature(p.view(), (u.view(), v.view()));
if is_declared_unsupported(&forward) {
continue;
}
let forward = forward.expect("K(u,v)");
let swapped = manifold
.sectional_curvature(p.view(), (v.view(), u.view()))
.expect("K(v,u)");
assert!(
(forward - swapped).abs() <= 1.0e-9 * forward.abs().max(1.0),
"{label} trial {trial}: K(u,v) = {forward} but K(v,u) = {swapped}"
);
let (su, sv) = (&u * 3.7, &v * 0.21);
let rescaled = manifold
.sectional_curvature(p.view(), (su.view(), sv.view()))
.expect("K(au,bv)");
assert!(
(forward - rescaled).abs() <= 1.0e-9 * forward.abs().max(1.0),
"{label} trial {trial}: K is not scale-invariant ({forward} -> {rescaled})"
);
verified += 1;
}
}
assert!(
verified > 0,
"sectional curvature was never actually evaluated"
);
}
#[test]
fn exp_map_vjp_matches_central_finite_differences() {
const H: f64 = 1.0e-4;
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let ambient = manifold.ambient_dim();
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let v = sample_tangent(manifold.as_ref(), p.view(), &mut rng);
let seed_cotangent = rng.gaussian_vec(ambient);
let (grad_point, grad_tangent) = manifold
.exp_map_vjp(p.view(), v.view(), seed_cotangent.view())
.expect("exp_map_vjp");
let mut worst = 0.0_f64;
let mut scale = 1.0_f64;
for i in 0..ambient {
for (analytic, base_is_point) in [(&grad_point, true), (&grad_tangent, false)] {
let (mut plus, mut minus) = if base_is_point {
(p.clone(), p.clone())
} else {
(v.clone(), v.clone())
};
plus[i] += H;
minus[i] -= H;
let (up, down) = if base_is_point {
(
manifold.exp_map(plus.view(), v.view()),
manifold.exp_map(minus.view(), v.view()),
)
} else {
(
manifold.exp_map(p.view(), plus.view()),
manifold.exp_map(p.view(), minus.view()),
)
};
let (Ok(up), Ok(down)) = (up, down) else {
continue;
};
let directional = dot(seed_cotangent.view(), (&up - &down).view()) / (2.0 * H);
worst = worst.max((directional - analytic[i]).abs());
scale = scale.max(directional.abs().max(analytic[i].abs()));
}
}
assert!(
worst <= 1.0e-7 * scale,
"{label} trial {trial}: exp_map_vjp disagrees with finite differences \
(abs {worst:.3e}, scale {scale:.3e})"
);
verified += 1;
}
}
assert!(verified > 0, "no VJP was actually differenced");
}
#[test]
fn grassmann_operations_are_invariant_to_the_frame_representative() {
let mut verified = 0usize;
for (label, spec) in inventory() {
let ManifoldSpec::Grassmann { k, n } = spec else {
continue;
};
let manifold = spec.build().expect("build");
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let q = random_point(&spec, &mut rng);
let rot = rng.orthogonal(k);
let base = manifold.log_map(p.view(), q.view()).expect("log_Y(Z)");
let tolerance = grassmann_tolerance(grassmann_pair_conditioning(&p, &q, n, k));
let regauged_target = regauge(&q, n, k, &rot);
let from_regauged = manifold
.log_map(p.view(), regauged_target.view())
.expect("log_Y(ZR)");
assert!(
sup_diff(&base, &from_regauged) <= tolerance,
"{label} trial {trial}: log depends on the TARGET's frame \
(sup {:.3e} > {tolerance:.3e})",
sup_diff(&base, &from_regauged)
);
let regauged_base = regauge(&p, n, k, &rot);
let at_regauged = manifold
.log_map(regauged_base.view(), q.view())
.expect("log_{{YR}}(Z)");
let expected = regauge(&base, n, k, &rot);
assert!(
sup_diff(&expected, &at_regauged) <= tolerance,
"{label} trial {trial}: log is not equivariant in the BASE's frame \
(sup {:.3e} > {tolerance:.3e})",
sup_diff(&expected, &at_regauged)
);
let y = from_flat(p.view(), n, k).expect("frame");
let xi = from_flat(base.view(), n, k).expect("tangent");
let horizontality = y.t().dot(&xi);
let worst = horizontality.iter().fold(0.0_f64, |a, x| a.max(x.abs()));
assert!(
worst <= tolerance,
"{label} trial {trial}: Yᵀlog_Y(Z) = {worst:.3e} != 0 (not horizontal)"
);
verified += 1;
}
}
assert!(
verified > 0,
"no Grassmann gauge invariance was actually checked"
);
}
#[test]
fn a_gradient_step_descends_at_the_riemannian_gradient_norm() {
const TRAVEL: f64 = 1.0e-4;
let mut verified = 0usize;
for (label, spec) in inventory() {
let manifold = spec.build().expect("build");
let ambient = manifold.ambient_dim();
if manifold.dim() == 0 {
continue;
}
let mut rng = Rng::new(SEED);
for trial in 0..TRIALS {
let p = random_point(&spec, &mut rng);
let differential = rng.gaussian_vec(ambient);
let negated = &differential * -1.0;
let gradient = manifold
.riemannian_gradient(p.view(), differential.view())
.expect("riemannian_gradient");
let metric = manifold.metric_tensor(p.view()).expect("metric_tensor");
let squared_norm = gradient.view().dot(&metric.dot(&gradient));
if squared_norm <= 1.0e-12 {
continue;
}
let ambient_norm = norm(gradient.view());
if ambient_norm <= 1.0e-12 {
continue;
}
let rate = TRAVEL / ambient_norm;
let downhill = manifold
.riemannian_gradient_step(p.view(), differential.view(), rate)
.expect("gradient step");
let uphill = manifold
.riemannian_gradient_step(p.view(), negated.view(), rate)
.expect("reverse gradient step");
let jumped = sup_diff(&downhill, &p).max(sup_diff(&uphill, &p)) > 0.5;
if jumped {
continue;
}
let slope = (dot(differential.view(), uphill.view())
- dot(differential.view(), downhill.view()))
/ (2.0 * rate);
assert!(
(slope - squared_norm).abs() <= 1.0e-8 * squared_norm,
"{label} trial {trial}: a gradient step descends at {slope}, but the \
Riemannian gradient norm is {squared_norm}"
);
verified += 1;
}
}
assert!(verified > 0, "no gradient step was actually differenced");
}