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