apex_solver/factors/mod.rs
1//! Factor implementations for graph-based optimization problems.
2//!
3//! Factors (also called constraints or error functions) represent measurements or relationships
4//! between variables in a factor graph. Each factor computes a residual (error) vector and its
5//! Jacobian with respect to the connected variables.
6//!
7//! # Factor Graph Formulation
8//!
9//! In graph-based SLAM and bundle adjustment, the optimization problem is represented as:
10//!
11//! ```text
12//! minimize Σ_i ||r_i(x)||²
13//! ```
14//!
15//! where:
16//! - `x` is the set of variables (poses, landmarks, etc.)
17//! - `r_i(x)` is the residual function for factor i
18//! - Each factor connects one or more variables
19//!
20//! # Factor Types
21//!
22//! ## Pose Factors
23//! - **Between factors**: Relative pose constraints (SE2, SE3)
24//! - **Prior factors**: Unary constraints on single variables
25//!
26//! ## Camera Projection Factors
27//!
28//! Each camera model provides two factor types:
29//! 1. **CameraParamsFactor**: Optimizes camera intrinsic parameters (fx, fy, cx, cy, distortion)
30//! 2. **ProjectionFactor**: Optimizes 3D point positions with fixed camera parameters
31//!
32//! ### Double Sphere
33//! - [`DoubleSphereCameraParamsFactor`]: Optimize camera intrinsics
34//! - [`DoubleSphereProjectionFactor`]: Optimize 3D point positions
35//!
36//! ### EUCM (Extended Unified Camera Model)
37//! - [`EucmCameraParamsFactor`]: Optimize camera intrinsics
38//! - [`EucmProjectionFactor`]: Optimize 3D point positions
39//!
40//! ### FOV (Field of View)
41//! - [`FovCameraParamsFactor`]: Optimize camera intrinsics
42//! - [`FovProjectionFactor`]: Optimize 3D point positions
43//!
44//! ### Kannala-Brandt
45//! - [`KannalaBrandtCameraParamsFactor`]: Optimize camera intrinsics
46//! - [`KannalaBrandtProjectionFactor`]: Optimize 3D point positions
47//!
48//! ### Radial-Tangential (RadTan)
49//! - [`RadTanCameraParamsFactor`]: Optimize camera intrinsics
50//! - [`RadTanProjectionFactor`]: Optimize 3D point positions
51//!
52//! ### UCM (Unified Camera Model)
53//! - [`UcmCameraParamsFactor`]: Optimize camera intrinsics
54//! - [`UcmProjectionFactor`]: Optimize 3D point positions
55//!
56//! # Linearization
57//!
58//! Each factor must provide a `linearize` method that computes:
59//! 1. **Residual** `r(x)`: The error at the current variable values
60//! 2. **Jacobian** `J = ∂r/∂x`: How the residual changes with each variable
61//!
62//! This information is used by the optimizer to compute parameter updates via Newton-type methods.
63
64use nalgebra::{DMatrix, DVector};
65use thiserror::Error;
66use tracing::error;
67
68/// Factor-specific error types for apex-solver
69#[derive(Debug, Clone, Error)]
70pub enum FactorError {
71 /// Invalid dimension mismatch between expected and actual
72 #[error("Invalid dimension: expected {expected}, got {actual}")]
73 InvalidDimension { expected: usize, actual: usize },
74
75 /// Invalid projection (point behind camera or outside valid range)
76 #[error("Invalid projection: {0}")]
77 InvalidProjection(String),
78
79 /// Jacobian computation failed
80 #[error("Jacobian computation failed: {0}")]
81 JacobianFailed(String),
82
83 /// Invalid parameter values
84 #[error("Invalid parameter values: {0}")]
85 InvalidParameters(String),
86
87 /// Numerical instability detected
88 #[error("Numerical instability: {0}")]
89 NumericalInstability(String),
90}
91
92impl FactorError {
93 /// Log the error with tracing::error and return self for chaining
94 ///
95 /// This method allows for a consistent error logging pattern throughout
96 /// the factors module, ensuring all errors are properly recorded.
97 ///
98 /// # Example
99 /// ```ignore
100 /// operation()
101 /// .map_err(|e| FactorError::from(e).log())?;
102 /// ```
103 #[must_use]
104 pub fn log(self) -> Self {
105 error!("{}", self);
106 self
107 }
108
109 /// Log the error with the original source error for debugging context
110 ///
111 /// This method logs both the FactorError and the underlying error
112 /// from external libraries or internal operations, providing full
113 /// debugging context when errors occur.
114 ///
115 /// # Arguments
116 /// * `source_error` - The original error (must implement Debug)
117 ///
118 /// # Example
119 /// ```ignore
120 /// compute_jacobian()
121 /// .map_err(|e| {
122 /// FactorError::JacobianFailed("Matrix computation failed".to_string())
123 /// .log_with_source(e)
124 /// })?;
125 /// ```
126 #[must_use]
127 pub fn log_with_source<E: std::fmt::Debug>(self, source_error: E) -> Self {
128 error!("{} | Source: {:?}", self, source_error);
129 self
130 }
131}
132
133/// Result type for factor operations
134pub type FactorResult<T> = Result<T, FactorError>;
135
136/// Trait for factor (constraint) implementations in factor graph optimization.
137///
138/// A factor represents a measurement or constraint connecting one or more variables.
139/// It computes the residual (error) and Jacobian for the current variable values,
140/// which are used by the optimizer to minimize the total cost.
141///
142/// # Implementing Custom Factors
143///
144/// To create a custom factor:
145/// 1. Implement this trait
146/// 2. Define the residual function `r(x)` (how to compute error from variable values)
147/// 3. Compute the Jacobian `J = ∂r/∂x` (analytically or numerically)
148/// 4. Return the residual dimension
149///
150/// # Thread Safety
151///
152/// Factors must be `Send + Sync` to enable parallel residual/Jacobian evaluation.
153///
154/// # Example
155///
156/// ```
157/// use apex_solver::factors::Factor;
158/// use nalgebra::{DMatrix, DVector};
159///
160/// // Simple 1D range measurement factor
161/// struct RangeFactor {
162/// measurement: f64, // Measured distance
163/// }
164///
165/// impl Factor for RangeFactor {
166/// fn linearize(&self, params: &[DVector<f64>], compute_jacobian: bool) -> (DVector<f64>, Option<DMatrix<f64>>) {
167/// // params[0] is a 2D point [x, y]
168/// let x = params[0][0];
169/// let y = params[0][1];
170///
171/// // Residual: measured distance - actual distance
172/// let predicted_distance = (x * x + y * y).sqrt();
173/// let residual = DVector::from_vec(vec![self.measurement - predicted_distance]);
174///
175/// // Jacobian: ∂(residual)/∂[x, y]
176/// let jacobian = if compute_jacobian {
177/// Some(DMatrix::from_row_slice(1, 2, &[
178/// -x / predicted_distance,
179/// -y / predicted_distance,
180/// ]))
181/// } else {
182/// None
183/// };
184///
185/// (residual, jacobian)
186/// }
187///
188/// fn get_dimension(&self) -> usize { 1 }
189/// }
190/// ```
191pub trait Factor: Send + Sync {
192 /// Compute the residual and Jacobian at the given parameter values.
193 ///
194 /// # Arguments
195 ///
196 /// * `params` - Slice of variable values (one `DVector` per connected variable)
197 /// * `compute_jacobian` - Whether to compute the Jacobian matrix
198 ///
199 /// # Returns
200 ///
201 /// Tuple `(residual, jacobian)` where:
202 /// - `residual`: N-dimensional error vector
203 /// - `jacobian`: N × M matrix where M is the total DOF of all variables
204 ///
205 /// # Example
206 ///
207 /// For a between factor connecting two SE2 poses (3 DOF each):
208 /// - Input: `params = [pose1 (3×1), pose2 (3×1)]`
209 /// - Output: `(residual (3×1), jacobian (3×6))`
210 fn linearize(
211 &self,
212 params: &[DVector<f64>],
213 compute_jacobian: bool,
214 ) -> (DVector<f64>, Option<DMatrix<f64>>);
215
216 /// Get the dimension of the residual vector.
217 ///
218 /// # Returns
219 ///
220 /// Number of elements in the residual vector (number of constraints)
221 ///
222 /// # Example
223 ///
224 /// - SE2 between factor: 3 (dx, dy, dtheta)
225 /// - SE3 between factor: 6 (translation + rotation)
226 /// - Prior factor: dimension of the variable
227 fn get_dimension(&self) -> usize;
228}
229
230// Pose factors
231pub mod prior_factor;
232pub mod se2_factor;
233pub mod se3_factor;
234
235// Camera projection factors
236pub mod double_sphere_factor;
237pub mod eucm_factor;
238pub mod fov_factor;
239pub mod kannala_brandt_factor;
240pub mod rad_tan_factor;
241pub mod ucm_factor;
242
243// Re-export all factor types
244
245// Pose factors
246pub use prior_factor::PriorFactor;
247pub use se2_factor::BetweenFactorSE2;
248pub use se3_factor::BetweenFactorSE3;
249
250// Camera projection factors - Double Sphere
251pub use double_sphere_factor::{DoubleSphereCameraParamsFactor, DoubleSphereProjectionFactor};
252
253// Camera projection factors - EUCM
254pub use eucm_factor::{EucmCameraParamsFactor, EucmProjectionFactor};
255
256// Camera projection factors - FOV
257pub use fov_factor::{FovCameraParamsFactor, FovProjectionFactor};
258
259// Camera projection factors - Kannala-Brandt
260pub use kannala_brandt_factor::{KannalaBrandtCameraParamsFactor, KannalaBrandtProjectionFactor};
261
262// Camera projection factors - RadTan
263pub use rad_tan_factor::{RadTanCameraParamsFactor, RadTanProjectionFactor};
264
265// Camera projection factors - UCM
266pub use ucm_factor::{UcmCameraParamsFactor, UcmProjectionFactor};