apex_solver/manifold/mod.rs
1//! Manifold representations for optimization on non-Euclidean spaces.
2//!
3//! This module provides manifold representations commonly used in computer vision and robotics:
4//! - **SE(3)**: Special Euclidean group (rigid body transformations)
5//! - **SO(3)**: Special Orthogonal group (rotations)
6//! - **Sim(3)**: Similarity transformations
7//! - **SE(2)**: Rigid transformations in 2D
8//! - **SO(2)**: Rotations in 2D
9//!
10//! Lie group M,° | size | dim | X ∈ M | Constraint | T_E M | T_X M | Exp(T) | Comp. | Action
11//! ------------- | ------ | --- | ----------------------- | --------------- | ----------------- | --------------------- | ------------------ | ----- | ------
12//! n-D vector | Rⁿ,+ | n | n | v ∈ Rⁿ | |v-v|=0 | v ∈ Rⁿ | v ∈ Rⁿ | v = exp(v) | v₁+v₂ | v + x
13//! Circle | S¹,. | 2 | 1 | z ∈ C | z*z = 1 | iθ ∈ iR | θ ∈ R | z = exp(iθ) | z₁z₂ | zx
14//! Rotation | SO(2),.| 4 | 1 | R | RᵀR = I | [θ]x ∈ so(2) | [θ] ∈ R² | R = exp([θ]x) | R₁R₂ | Rx
15//! Rigid motion | SE(2),.| 9 | 3 | M = [R t; 0 1] | RᵀR = I | [v̂] ∈ se(2) | [v̂] ∈ R³ | Exp([v̂]) | M₁M₂ | Rx+t
16//! 3-sphere | S³,. | 4 | 3 | q ∈ H | q*q = 1 | θ/2 ∈ Hp | θ ∈ R³ | q = exp(uθ/2) | q₁q₂ | qxq*
17//! Rotation | SO(3),.| 9 | 3 | R | RᵀR = I | [θ]x ∈ so(3) | [θ] ∈ R³ | R = exp([θ]x) | R₁R₂ | Rx
18//! Rigid motion | SE(3),.| 16 | 6 | M = [R t; 0 1] | RᵀR = I | [v̂] ∈ se(3) | [v̂] ∈ R⁶ | Exp([v̂]) | M₁M₂ | Rx+t
19//!
20//! The design is inspired by the [manif](https://github.com/artivis/manif) C++ library
21//! and provides:
22//! - Analytic Jacobian computations for all operations
23//! - Right and left perturbation models
24//! - Composition and inverse operations
25//! - Exponential and logarithmic maps
26//! - Tangent space operations
27//!
28//! # Mathematical Background
29//!
30//! This module implements Lie group theory for robotics applications. Each manifold
31//! represents a Lie group with its associated tangent space (Lie algebra).
32//! Operations are differentiated with respect to perturbations on the local tangent space.
33//!
34
35use nalgebra;
36use std::fmt::Debug;
37use std::ops::{Mul, Neg};
38
39pub mod rn;
40pub mod se2;
41pub mod se3;
42pub mod so2;
43pub mod so3;
44
45/// Errors that can occur during manifold operations.
46#[derive(Debug, Clone, PartialEq)]
47pub enum ManifoldError {
48 /// Invalid tangent vector dimension
49 InvalidTangentDimension { expected: usize, actual: usize },
50 /// Numerical instability in computation
51 NumericalInstability(String),
52 /// Invalid manifold element
53 InvalidElement(String),
54 /// Dimension validation failed during conversion
55 DimensionMismatch { expected: usize, actual: usize },
56 /// NaN or Inf detected in manifold element
57 InvalidNumber,
58 /// Normalization failed for manifold element
59 NormalizationFailed(String),
60}
61
62#[derive(Debug, Clone, PartialEq)]
63pub enum ManifoldType {
64 RN,
65 SE2,
66 SE3,
67 SO2,
68 SO3,
69}
70
71impl std::fmt::Display for ManifoldError {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 ManifoldError::InvalidTangentDimension { expected, actual } => {
75 write!(
76 f,
77 "Invalid tangent dimension: expected {expected}, got {actual}"
78 )
79 }
80 ManifoldError::NumericalInstability(msg) => {
81 write!(f, "Numerical instability: {msg}")
82 }
83 ManifoldError::InvalidElement(msg) => {
84 write!(f, "Invalid manifold element: {msg}")
85 }
86 ManifoldError::DimensionMismatch { expected, actual } => {
87 write!(f, "Dimension mismatch: expected {expected}, got {actual}")
88 }
89 ManifoldError::InvalidNumber => {
90 write!(f, "Invalid number: NaN or Inf detected")
91 }
92 ManifoldError::NormalizationFailed(msg) => {
93 write!(f, "Normalization failed: {msg}")
94 }
95 }
96 }
97}
98
99impl std::error::Error for ManifoldError {}
100
101/// Result type for manifold operations.
102pub type ManifoldResult<T> = Result<T, ManifoldError>;
103
104/// Core trait for Lie group operations.
105///
106/// This trait provides the fundamental operations for Lie groups, including:
107/// - Group operations (composition, inverse, identity)
108/// - Exponential and logarithmic maps
109/// - Lie group plus/minus operations with Jacobians
110/// - Adjoint operations
111/// - Random sampling and normalization
112///
113/// The design closely follows the [manif](https://github.com/artivis/manif) C++ library.
114///
115/// # Type Parameters
116///
117/// Associated types define the mathematical structure:
118/// - `Element`: The Lie group element type (e.g., `Isometry3<f64>` for SE(3))
119/// - `TangentVector`: The tangent space vector type (e.g., `Vector6<f64>` for SE(3))
120/// - `JacobianMatrix`: The Jacobian matrix type for this Lie group
121/// - `LieAlgebra`: Associated Lie algebra type
122///
123/// # Dimensions
124///
125/// Three key dimensions characterize each Lie group:
126/// - `DIM`: Space dimension - dimension of ambient space (e.g., 3 for SE(3))
127/// - `DOF`: Degrees of freedom - tangent space dimension (e.g., 6 for SE(3))
128/// - `REP_SIZE`: Representation size - underlying data size (e.g., 7 for SE(3))
129pub trait LieGroup: Clone + Debug + PartialEq {
130 /// The tangent space vector type
131 type TangentVector: Tangent<Self>;
132
133 /// The Jacobian matrix type
134 type JacobianMatrix: Clone
135 + Debug
136 + PartialEq
137 + Neg<Output = Self::JacobianMatrix>
138 + Mul<Output = Self::JacobianMatrix>;
139
140 /// Associated Lie algebra type
141 type LieAlgebra: Clone + Debug + PartialEq;
142
143 // Core group operations
144
145 /// Compute the inverse of this manifold element.
146 ///
147 /// For a group element g, returns g⁻¹ such that g ∘ g⁻¹ = e.
148 ///
149 /// # Arguments
150 /// * `jacobian` - Optional mutable reference to store the Jacobian ∂(g⁻¹)/∂g
151 fn inverse(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self;
152
153 /// Compose this element with another (group multiplication).
154 ///
155 /// Computes g₁ ∘ g₂ where ∘ is the group operation.
156 ///
157 /// # Arguments
158 /// * `other` - The right operand for composition
159 /// * `jacobian_self` - Optional Jacobian ∂(g₁ ∘ g₂)/∂g₁
160 /// * `jacobian_other` - Optional Jacobian ∂(g₁ ∘ g₂)/∂g₂
161 fn compose(
162 &self,
163 other: &Self,
164 jacobian_self: Option<&mut Self::JacobianMatrix>,
165 jacobian_other: Option<&mut Self::JacobianMatrix>,
166 ) -> Self;
167
168 /// Logarithmic map from manifold to tangent space.
169 ///
170 /// Maps a group element g ∈ G to its tangent vector log(g)^∨ ∈ 𝔤.
171 ///
172 /// # Arguments
173 /// * `jacobian` - Optional Jacobian ∂log(g)^∨/∂g
174 fn log(&self, jacobian: Option<&mut Self::JacobianMatrix>) -> Self::TangentVector;
175
176 /// Vee operator: log(g)^∨.
177 ///
178 /// Maps a group element g ∈ G to its tangent vector log(g)^∨ ∈ 𝔤.
179 ///
180 /// # Arguments
181 /// * `jacobian` - Optional Jacobian ∂log(g)^∨/∂g
182 fn vee(&self) -> Self::TangentVector;
183
184 /// Act on a vector v: g ⊙ v.
185 ///
186 /// Group action on vectors (e.g., rotation for SO(3), transformation for SE(3)).
187 ///
188 /// # Arguments
189 /// * `vector` - Vector to transform
190 /// * `jacobian_self` - Optional Jacobian ∂(g ⊙ v)/∂g
191 /// * `jacobian_vector` - Optional Jacobian ∂(g ⊙ v)/∂v
192 fn act(
193 &self,
194 vector: &nalgebra::Vector3<f64>,
195 jacobian_self: Option<&mut Self::JacobianMatrix>,
196 jacobian_vector: Option<&mut nalgebra::Matrix3<f64>>,
197 ) -> nalgebra::Vector3<f64>;
198
199 // Adjoint operations
200
201 /// Adjoint matrix Ad(g).
202 ///
203 /// The adjoint representation maps the group to linear transformations
204 /// on the Lie algebra: Ad(g) φ = log(g ∘ exp(φ^∧) ∘ g⁻¹)^∨.
205 fn adjoint(&self) -> Self::JacobianMatrix;
206
207 // Utility operations
208
209 /// Generate a random element (useful for testing and initialization).
210 fn random() -> Self;
211
212 /// Normalize/project the element to the manifold.
213 ///
214 /// Ensures the element satisfies manifold constraints (e.g., orthogonality for rotations).
215 fn normalize(&mut self);
216
217 /// Check if the element is approximately on the manifold.
218 fn is_valid(&self, tolerance: f64) -> bool;
219
220 /// Check if the element is approximately equal to another element.
221 ///
222 /// # Arguments
223 /// * `other` - The other element to compare with
224 /// * `tolerance` - The tolerance for the comparison
225 fn is_approx(&self, other: &Self, tolerance: f64) -> bool;
226
227 // Manifold plus/minus operations
228
229 /// Right plus operation: g ⊞ φ = g ∘ exp(φ^∧).
230 ///
231 /// Applies a tangent space perturbation to this manifold element.
232 ///
233 /// # Arguments
234 /// * `tangent` - Tangent vector perturbation
235 /// * `jacobian_self` - Optional Jacobian ∂(g ⊞ φ)/∂g
236 /// * `jacobian_tangent` - Optional Jacobian ∂(g ⊞ φ)/∂φ
237 ///
238 /// # Notes
239 /// # Equation 148:
240 /// J_R⊕θ_R = R(θ)ᵀ
241 /// J_R⊕θ_θ = J_r(θ)
242 fn right_plus(
243 &self,
244 tangent: &Self::TangentVector,
245 jacobian_self: Option<&mut Self::JacobianMatrix>,
246 jacobian_tangent: Option<&mut Self::JacobianMatrix>,
247 ) -> Self {
248 let exp_tangent = tangent.exp(None);
249
250 if let Some(jac_tangent) = jacobian_tangent {
251 *jac_tangent = tangent.right_jacobian();
252 }
253
254 self.compose(&exp_tangent, jacobian_self, None)
255 }
256
257 /// Right minus operation: g₁ ⊟ g₂ = log(g₂⁻¹ ∘ g₁)^∨.
258 ///
259 /// Computes the tangent vector that transforms g₂ to g₁.
260 ///
261 /// # Arguments
262 /// * `other` - The reference element g₂
263 /// * `jacobian_self` - Optional Jacobian ∂(g₁ ⊟ g₂)/∂g₁
264 /// * `jacobian_other` - Optional Jacobian ∂(g₁ ⊟ g₂)/∂g₂
265 ///
266 /// # Notes
267 /// # Equation 149:
268 /// J_Q⊖R_Q = J_r⁻¹(θ)
269 /// J_Q⊖R_R = -J_l⁻¹(θ)
270 fn right_minus(
271 &self,
272 other: &Self,
273 jacobian_self: Option<&mut Self::JacobianMatrix>,
274 jacobian_other: Option<&mut Self::JacobianMatrix>,
275 ) -> Self::TangentVector {
276 let other_inverse = other.inverse(None);
277 let result_group = other_inverse.compose(self, None, None);
278 let result = result_group.log(None);
279
280 if let Some(jac_self) = jacobian_self {
281 *jac_self = -result.left_jacobian_inv();
282 }
283
284 if let Some(jac_other) = jacobian_other {
285 *jac_other = result.right_jacobian_inv();
286 }
287
288 result
289 }
290
291 /// Left plus operation: φ ⊞ g = exp(φ^∧) ∘ g.
292 ///
293 /// # Arguments
294 /// * `tangent` - Tangent vector perturbation
295 /// * `jacobian_tangent` - Optional Jacobian ∂(φ ⊞ g)/∂φ
296 /// * `jacobian_self` - Optional Jacobian ∂(φ ⊞ g)/∂g
297 fn left_plus(
298 &self,
299 tangent: &Self::TangentVector,
300 jacobian_tangent: Option<&mut Self::JacobianMatrix>,
301 jacobian_self: Option<&mut Self::JacobianMatrix>,
302 ) -> Self {
303 // Left plus: τ ⊕ g = exp(τ) * g
304 let exp_tangent = tangent.exp(None);
305 let result = exp_tangent.compose(self, None, None);
306
307 if let Some(jac_self) = jacobian_self {
308 // Note: jacobian_identity() is now implemented in concrete types
309 // This will be handled by the concrete implementation
310 *jac_self = self.adjoint();
311 }
312
313 if let Some(jac_tangent) = jacobian_tangent {
314 *jac_tangent = self.inverse(None).adjoint() * tangent.right_jacobian();
315 }
316
317 result
318 }
319
320 /// Left minus operation: g₁ ⊟ g₂ = log(g₁ ∘ g₂⁻¹)^∨.
321 ///
322 /// # Arguments
323 /// * `other` - The reference element g₂
324 /// * `jacobian_self` - Optional Jacobian ∂(g₁ ⊟ g₂)/∂g₁
325 /// * `jacobian_other` - Optional Jacobian ∂(g₁ ⊟ g₂)/∂g₂
326 fn left_minus(
327 &self,
328 other: &Self,
329 jacobian_self: Option<&mut Self::JacobianMatrix>,
330 jacobian_other: Option<&mut Self::JacobianMatrix>,
331 ) -> Self::TangentVector {
332 // Left minus: g1 ⊖ g2 = log(g1 * g2^{-1})
333 let other_inverse = other.inverse(None);
334 let result_group = self.compose(&other_inverse, None, None);
335 let result = result_group.log(None);
336
337 if let Some(jac_self) = jacobian_self {
338 *jac_self = result.right_jacobian_inv() * other.adjoint();
339 }
340
341 if let Some(jac_other) = jacobian_other {
342 *jac_other = -(result.right_jacobian_inv() * other.adjoint());
343 }
344
345 result
346 }
347
348 // Convenience methods (use right operations by default)
349
350 /// Convenience method for right_plus. Equivalent to g ⊞ φ.
351 fn plus(
352 &self,
353 tangent: &Self::TangentVector,
354 jacobian_self: Option<&mut Self::JacobianMatrix>,
355 jacobian_tangent: Option<&mut Self::JacobianMatrix>,
356 ) -> Self {
357 self.right_plus(tangent, jacobian_self, jacobian_tangent)
358 }
359
360 /// Convenience method for right_minus. Equivalent to g₁ ⊟ g₂.
361 fn minus(
362 &self,
363 other: &Self,
364 jacobian_self: Option<&mut Self::JacobianMatrix>,
365 jacobian_other: Option<&mut Self::JacobianMatrix>,
366 ) -> Self::TangentVector {
367 self.right_minus(other, jacobian_self, jacobian_other)
368 }
369
370 // Additional operations
371
372 /// Compute g₁⁻¹ ∘ g₂ (relative transformation).
373 ///
374 /// # Arguments
375 /// * `other` - The target element g₂
376 /// * `jacobian_self` - Optional Jacobian with respect to g₁
377 /// * `jacobian_other` - Optional Jacobian with respect to g₂
378 fn between(
379 &self,
380 other: &Self,
381 jacobian_self: Option<&mut Self::JacobianMatrix>,
382 jacobian_other: Option<&mut Self::JacobianMatrix>,
383 ) -> Self {
384 // Between: g1.between(g2) = g1^{-1} * g2
385 let self_inverse = self.inverse(None);
386 let result = self_inverse.compose(other, None, None);
387
388 if let Some(jac_self) = jacobian_self {
389 *jac_self = -result.inverse(None).adjoint();
390 }
391
392 if let Some(jac_other) = jacobian_other {
393 // Note: jacobian_identity() is now implemented in concrete types
394 // This will be handled by the concrete implementation
395 *jac_other = other.adjoint();
396 }
397
398 result
399 }
400
401 /// Get the dimension of the tangent space for this manifold element.
402 ///
403 /// For most manifolds, this returns the compile-time constant from the TangentVector type.
404 /// For dynamically-sized manifolds like Rⁿ, this method should be overridden to return
405 /// the actual runtime dimension.
406 ///
407 /// # Returns
408 /// The dimension of the tangent space (degrees of freedom)
409 ///
410 /// # Default Implementation
411 /// Returns `Self::TangentVector::DIM` which works for fixed-size manifolds
412 /// (SE2=3, SE3=6, SO2=1, SO3=3).
413 fn tangent_dim(&self) -> usize {
414 Self::TangentVector::DIM
415 }
416}
417
418/// Trait for Lie algebra operations.
419///
420/// This trait provides operations for vectors in the Lie algebra of a Lie group,
421/// including vector space operations, adjoint actions, and conversions to matrix form.
422///
423/// # Type Parameters
424///
425/// - `G`: The associated Lie group type
426pub trait Tangent<Group: LieGroup>: Clone + Debug + PartialEq {
427 // Dimension constants
428
429 /// Dimension of the tangent space
430 const DIM: usize;
431
432 // Exponential map and Jacobians
433
434 /// Exponential map to Lie group: exp(φ^∧).
435 ///
436 /// # Arguments
437 /// * `jacobian` - Optional Jacobian ∂exp(φ^∧)/∂φ
438 fn exp(&self, jacobian: Option<&mut Group::JacobianMatrix>) -> Group;
439
440 /// Right Jacobian Jr.
441 ///
442 /// Matrix Jr such that for small δφ:
443 /// exp((φ + δφ)^∧) ≈ exp(φ^∧) ∘ exp((Jr δφ)^∧)
444 fn right_jacobian(&self) -> Group::JacobianMatrix;
445
446 /// Left Jacobian Jl.
447 ///
448 /// Matrix Jl such that for small δφ:
449 /// exp((φ + δφ)^∧) ≈ exp((Jl δφ)^∧) ∘ exp(φ^∧)
450 fn left_jacobian(&self) -> Group::JacobianMatrix;
451
452 /// Inverse of right Jacobian Jr⁻¹.
453 fn right_jacobian_inv(&self) -> Group::JacobianMatrix;
454
455 /// Inverse of left Jacobian Jl⁻¹.
456 fn left_jacobian_inv(&self) -> Group::JacobianMatrix;
457
458 // Matrix representations
459
460 /// Hat operator: φ^∧ (vector to matrix).
461 ///
462 /// Maps the tangent vector to its matrix representation in the Lie algebra.
463 /// For SO(3): 3×1 vector → 3×3 skew-symmetric matrix
464 /// For SE(3): 6×1 vector → 4×4 transformation matrix
465 fn hat(&self) -> Group::LieAlgebra;
466
467 /// Small adjugate operator: adj(φ) = φ^∧.
468 ///
469 /// Maps the tangent vector to its matrix representation in the Lie algebra.
470 /// For SO(3): 3×1 vector → 3×3 skew-symmetric matrix
471 /// For SE(3): 6×1 vector → 4×4 transformation matrix
472 fn small_adj(&self) -> Group::JacobianMatrix;
473
474 /// Lie bracket: [φ, ψ] = φ ∘ ψ - ψ ∘ φ.
475 ///
476 /// Computes the Lie bracket of two tangent vectors in the Lie algebra.
477 /// For SO(3): 3×1 vector → 3×1 vector
478 /// For SE(3): 6×1 vector → 6×1 vector
479 fn lie_bracket(&self, other: &Self) -> Group::TangentVector;
480
481 /// Check if the tangent vector is approximately equal to another tangent vector.
482 ///
483 /// # Arguments
484 /// * `other` - The other tangent vector to compare with
485 /// * `tolerance` - The tolerance for the comparison
486 fn is_approx(&self, other: &Self, tolerance: f64) -> bool;
487
488 /// Get the i-th generator of the Lie algebra.
489 fn generator(&self, i: usize) -> Group::LieAlgebra;
490
491 // Utility functions
492
493 /// Zero tangent vector.
494 fn zero() -> Group::TangentVector;
495
496 /// Random tangent vector (useful for testing).
497 fn random() -> Group::TangentVector;
498
499 /// Check if the tangent vector is approximately zero.
500 fn is_zero(&self, tolerance: f64) -> bool;
501
502 /// Normalize the tangent vector to unit norm.
503 fn normalize(&mut self);
504
505 /// Return a unit tangent vector in the same direction.
506 fn normalized(&self) -> Group::TangentVector;
507}
508
509/// Trait for Lie groups that support interpolation.
510pub trait Interpolatable: LieGroup {
511 /// Linear interpolation in the manifold.
512 ///
513 /// For parameter t ∈ [0,1]: interp(g₁, g₂, 0) = g₁, interp(g₁, g₂, 1) = g₂.
514 ///
515 /// # Arguments
516 /// * `other` - Target element for interpolation
517 /// * `t` - Interpolation parameter in [0,1]
518 fn interp(&self, other: &Self, t: f64) -> Self;
519
520 /// Spherical linear interpolation (when applicable).
521 fn slerp(&self, other: &Self, t: f64) -> Self;
522}