cartan

Error Handling

cartan uses a single CartanError enum for all fallible operations. Most methods succeed for any valid input; only a handful of geometric degeneracies can cause failure.

CartanError Variants

#[derive(Debug, thiserror::Error)]
pub enum CartanError {
    #[error("point does not lie on the manifold (residual = {residual:.2e})")]
    PointNotOnManifold { residual: f64 },
 
    #[error("vector is not tangent at the given point (residual = {residual:.2e})")]
    NotATangentVector { residual: f64 },
 
    #[error("points are antipodal; log map is undefined")]
    AntipodalPoints,
 
    #[error("dimension mismatch: expected {expected}, got {got}")]
    DimensionMismatch { expected: usize, got: usize },
 
    #[error("numerical failure: {msg}")]
    NumericalFailure { msg: String },
}

Which Operations Can Fail

MethodReturn typeFailure condition
log(p, q)Option<Tangent>q on cut locus of p
geodesic(p, q, t)Option<Point>q on cut locus of p
parallel_transport(p, q, v)Option<Tangent>q antipodal to p
vector_transport(p, q, v)Option<Tangent>same as above
inverse_retract(p, q)Option<Tangent>same as log
check_point(p)Result<(), CartanError>p not on M
check_tangent(p, v)Result<(), CartanError>v not in T_pM
exp(p, v)Pointnever fails
retract(p, v)Pointnever fails
dist(p, q)f64never fails
inner(p, u, v)f64never fails
random_pointPointnever fails

The Option-returning methods use None (not Err) because the failure condition is a predictable geometric degeneracy, not a programming error.

Handling log and Transport

match manifold.log(&p, &q) {
    Some(v) => {
        // Normal path: use v for gradient descent, geodesic, etc.
    }
    None => {
        // p and q are antipodal. Options:
        // 1. Perturb q slightly off the cut locus.
        // 2. Use a retraction-based fallback.
        // 3. Skip this pair (e.g. in a Fréchet mean iteration).
        eprintln!("antipodal pair encountered; skipping");
    }
}

Handling Validation Errors

use cartan::CartanError;
 
fn process(manifold: &Sphere<3>, p: Point, v: Tangent) -> Result<(), CartanError> {
    manifold.check_point(&p)?;
    manifold.check_tangent(&p, &v)?;
 
    let q = manifold.exp(&p, &v);   // safe after validation
    Ok(())
}

check_point and check_tangent are cheap: they compute a residual norm and compare against a tolerance (default 1e-8). Use them at system boundaries (deserialising stored points, user-provided inputs), not inside hot loops.

Antipodal Points in Practice

The cut locus of $p$ on is the single point . In random sampling or optimisation this is a measure-zero event. If it occurs repeatedly, it signals that your algorithm is taking steps of size $\approx \pi$: too large.

// Safe Fréchet mean iteration; skip antipodal pairs.
let mean_update: Tangent = points.iter()
    .filter_map(|q| manifold.log(&current, q))
    .fold(zero_tangent(), |acc, v| acc + v);

NumericalFailure

Reserved for operations that require a matrix decomposition that fails due to near-singularity (e.g. SPD matrices near the boundary of the cone, or Grassmann points with nearly equal singular values). These are returned as Err(CartanError::NumericalFailure { msg }) from methods that return Result.

// Example: SPD geodesic near a rank-deficient matrix.
match spd.log(&p_near_boundary, &q) {
    Some(v) => { /* proceed */ }
    None    => { /* Cholesky failed internally, treat as cut locus */ }
}

Summary

  • Option<T>: geometric degeneracy (antipodal points, cut locus). Expected, recoverable.
  • Result<T, CartanError>: validation failure or numerical breakdown. Use ? to propagate.
  • Infallible: exp, retract, dist, inner never fail on valid inputs.