cartan
Riemannian geometry, manifold optimization, and geodesic computation in Rust.
cartan is a general-purpose Rust library for Riemannian geometry. It provides a backend-agnostic trait system with const-generic manifolds, correct numerics, and clean composability: from basic exp/log maps through second-order optimization to discrete exterior calculus for covariant PDE solvers.
Documentation: cartan.sotofranco.dev
Features
- Generic trait hierarchy:
Manifold,Retraction,ParallelTransport,VectorTransport,Connection,Curvature,GeodesicInterpolation - Const-generic manifolds:
Sphere<3>,Grassmann<5,2>, dimensions checked at compile time - Correct numerics: Taylor expansions near singularities, cut locus detection, structured error handling
- Zero-cost abstractions: manifold types are zero-sized; all geometry lives in the trait impls
- Optimization:
cartan-optimprovides RGD, RCG, RTR, and Fréchet mean on anyManifold - Geodesic tools:
cartan-geoprovides parameterized geodesics, curvature queries, and Jacobi field integration - DEC layer:
cartan-decdiscretizes covariant differential operators on simplicial meshes for PDE solvers - Python bindings:
cartan-pyexposes the full library to Python via PyO3 with numpy interop
Quick Start
use *;
use Sphere;
let s2 = ; // the 2-sphere in R^3
let mut rng = rng;
let p = s2.random_point;
let v = s2.random_tangent;
// Exponential map: walk along the geodesic
let q = s2.exp;
// Logarithmic map: recover the tangent vector
let v_recovered = s2.log.unwrap;
// Geodesic distance
let d = s2.dist.unwrap;
// Parallel transport a vector from p to q
let u = s2.random_tangent;
let u_at_q = s2.transport.unwrap;
// Sectional curvature (K = 1 for the unit sphere)
let k = s2.sectional_curvature;
Manifolds
Every manifold implements all seven traits in the hierarchy. Intrinsic dimensions are checked at compile time via const generics.
| Manifold | Type | Dim | Geometry |
|---|---|---|---|
| Euclidean R^N | Euclidean<N> |
N | flat, K = 0 |
| Sphere S^(N-1) | Sphere<N> |
N−1 | K = 1 |
| Special orthogonal SO(N) | SpecialOrthogonal<N> |
N(N−1)/2 | K ≥ 0 (bi-invariant) |
| Special Euclidean SE(N) | SpecialEuclidean<N> |
N(N+1)/2 | flat × sphere |
| Symmetric positive definite SPD(N) | Spd<N> |
N(N+1)/2 | K ≤ 0 (Cartan-Hadamard) |
| Grassmann Gr(N, K) | Grassmann<N, K> |
K(N−K) | 0 ≤ K ≤ 2 |
| Correlation Corr(N) | Corr<N> |
N(N−1)/2 | flat, K = 0 |
| Q-tensor Sym_0(R^3) | QTensor3 |
5 | flat, Frobenius metric |
Crate Structure
cartan facade crate (re-exports everything)
cartan-core trait definitions, CartanError, Real alias
cartan-manifolds concrete manifold implementations (8 manifolds + FrameField3D)
cartan-optim Riemannian optimization: RGD, RCG, RTR, Frechet mean
cartan-geo geodesic curves, curvature queries, Jacobi fields
cartan-dec discrete exterior calculus for PDE solvers
cartan-py Python bindings via PyO3 (pip install cartan)
All manifolds use nalgebra SVector/SMatrix types directly; no intermediate backend crate is needed.
Embedded and no_std Targets
cartan is designed to run on embedded and no_std targets. The geometry core (manifolds, geodesics, optimization) compiles without the standard library. The discrete exterior calculus layer (cartan-dec) requires std because it depends on rayon for parallelism and operates on heap-allocated mesh structures; it is not designed for embedded use.
What is available without std
The following manifolds compile on any target with an allocator (alloc), with all float arithmetic implemented via libm:
| Crate | --no-default-features --features alloc |
|---|---|
cartan-core |
All traits, CartanError, Real |
cartan-manifolds |
Euclidean<N>, Sphere<N>, SpecialOrthogonal<N>, SpecialEuclidean<N>, Grassmann<N,K> |
cartan-geo |
Geodesic, CurvatureQuery, integrate_jacobi |
cartan-optim |
All four optimizers (RGD, RCG, RTR, Fréchet mean) |
The following require std because their algorithms depend on iterative eigendecomposition (symmetric_eigen) or std::collections:
| Crate | Requires std |
|---|---|
cartan-manifolds |
Spd<N>, Corr<N>, QTensor3, FrameField3D |
cartan-geo |
Disclination, holonomy scanning |
cartan-dec |
entire crate (rayon, mesh allocation) |
For robotics and embedded work the key manifolds (SO(3) for attitude, SE(3) for pose, S² for bearing vectors, Grassmann for subspace tracking) are all available without std.
How to depend on cartan without std
The cartan facade crate supports no_std targets. The default full feature includes cartan-dec; disable it to drop the std requirement:
[]
# no_std with allocator (recommended for most embedded targets, e.g. RTIC, Embassy)
= { = "0.1", = false, = ["alloc"] }
# std, but without the cartan-dec mesh/PDE layer
= { = "0.1", = false, = ["std"] }
Alternatively, depend on sub-crates directly if you want finer control over which geometry modules are included:
[]
= { = "0.1", = false, = ["alloc"] }
= { = "0.1", = false, = ["alloc"] }
If you are on a fully bare-metal target with no allocator, depend only on cartan-core and implement your own manifold types against its traits.
Example: attitude control on a microcontroller
extern crate alloc;
use SpecialOrthogonal;
use Manifold;
// SO(3): 3x3 rotation matrices with bi-invariant metric
let so3 = ;
// Riemannian log: tangent vector from R_current to R_target (in so(3))
let error_tangent = so3.log.unwrap;
// Scale by gain and retract back to SO(3)
let r_next = so3.exp;
// Geodesic interpolation for trajectory following
let r_interp = so3.geodesic_interpolate.unwrap;
SE(3) pose estimation, Riemannian optimization over attitude, and geodesic path planning on S² all follow the same pattern.
cartan-optim
Four algorithms on any Manifold:
| Algorithm | Function | Traits required |
|---|---|---|
| Riemannian gradient descent | minimize_rgd |
Manifold + Retraction |
| Riemannian conjugate gradient (FR / PR+) | minimize_rcg |
+ ParallelTransport |
| Riemannian trust region (Steihaug-Toint) | minimize_rtr |
+ Connection |
| Fréchet mean (Karcher flow) | frechet_mean |
Manifold |
use ;
use Sphere;
let s2 = ;
let result = minimize_rgd;
cartan-geo
use ;
use Sphere;
let s2 = ;
// Parameterized geodesic from p to q
let geo = from_two_points.unwrap;
let points = geo.sample; // 20 evenly-spaced points on [0,1]
println!;
// Jacobi field: D²J/dt² + R(J, γ')γ' = 0
let result = integrate_jacobi;
cartan-dec
cartan-dec is the bridge between cartan's continuous geometry and discrete PDE solvers. It builds a 2D simplicial complex, precomputes Hodge operators and covariant derivatives, and exposes them for time-stepping loops.
On a well-centered Delaunay mesh the Hodge star is diagonal, so the full Laplace-Beltrami operator factors into sparse {0, +1, -1} incidence matrix-vector products interleaved with diagonal scalings (cache-friendly and SIMD-vectorizable). Fields use structure-of-arrays layout.
use ;
use Euclidean;
let mesh = unit_square_grid; // 32x32 uniform grid on [0,1]^2
let ops = from_mesh;
// Scalar Laplacian, Bochner Laplacian (vector fields),
// Lichnerowicz Laplacian (symmetric 2-tensors / Q-tensor equation)
let lf = ops.apply_laplace_beltrami;
let lu = ops.apply_bochner_laplacian;
let lq = ops.apply_lichnerowicz_laplacian;
Also provided: ExteriorDerivative (d₀, d₁), HodgeStar (⋆₀, ⋆₁, ⋆₂), upwind apply_scalar_advection / apply_vector_advection, and apply_divergence / apply_tensor_divergence.
cartan-py (Python bindings)
cartan-py exposes cartan's full geometry stack to Python with zero-copy numpy interop. A single abi3 wheel covers Python 3.9+.
# Manifolds
=
=
=
=
# Optimization
=
# Geodesics
=
=
# Curvature
=
# DEC
=
=
=
=
=
# Batch operations
=
All 8 manifolds (Euclidean, Sphere, SPD up to 8, SO, SE, Grassmann, Corr, QTensor3), all 4 optimizers, geodesic/Jacobi field integration, holonomy/disclination scanning, and the full DEC layer are exposed.