Skip to main content

apex_solver/core/
mod.rs

1//! Core optimization components for the apex-solver library
2//!
3//! This module contains the fundamental building blocks for nonlinear least squares optimization:
4//! - Problem formulation and management
5//! - Residual blocks
6//! - Variables and manifold handling
7//! - Loss functions for robust estimation
8//! - Correctors for applying loss functions
9
10pub mod corrector;
11pub mod loss_functions;
12pub mod noise;
13pub mod problem;
14pub mod residual_block;
15pub mod variable;
16
17use slotmap::new_key_type;
18use thiserror::Error;
19
20new_key_type! {
21    /// Stable generational handle for a variable in the Problem.
22    pub struct VarKey;
23    /// Stable generational handle for a residual block (factor) in the Problem.
24    pub struct FactorKey;
25}
26
27/// Core module error types for optimization problems and factors
28#[derive(Debug, Clone, Error)]
29pub enum CoreError {
30    /// Residual block operation failed
31    #[error("Residual block error: {0}")]
32    ResidualBlock(String),
33
34    /// Variable initialization or constraint error
35    #[error("Variable error: {0}")]
36    Variable(String),
37
38    /// Factor linearization failed
39    #[error("Factor linearization failed: {0}")]
40    FactorLinearization(String),
41
42    /// Symbolic structure construction failed
43    #[error("Symbolic structure error: {0}")]
44    SymbolicStructure(String),
45
46    /// Parallel computation error (thread/mutex failures)
47    #[error("Parallel computation error: {0}")]
48    ParallelComputation(String),
49
50    /// Dimension mismatch between residual/Jacobian/variables
51    #[error("Dimension mismatch: {0}")]
52    DimensionMismatch(String),
53
54    /// Invalid constraint specification (bounds, fixed indices)
55    #[error("Invalid constraint: {0}")]
56    InvalidConstraint(String),
57
58    /// Loss function error
59    #[error("Loss function error: {0}")]
60    LossFunction(String),
61
62    /// Invalid input parameter or configuration
63    #[error("Invalid input: {0}")]
64    InvalidInput(String),
65}
66
67/// Result type for core module operations
68pub type CoreResult<T> = Result<T, CoreError>;
69
70impl From<crate::linearizer::LinearizerError> for CoreError {
71    fn from(err: crate::linearizer::LinearizerError) -> Self {
72        match err {
73            crate::linearizer::LinearizerError::SymbolicStructure(msg) => {
74                CoreError::SymbolicStructure(msg)
75            }
76            crate::linearizer::LinearizerError::ParallelComputation(msg) => {
77                CoreError::ParallelComputation(msg)
78            }
79            crate::linearizer::LinearizerError::Variable(msg) => CoreError::Variable(msg),
80            crate::linearizer::LinearizerError::FactorLinearization(msg) => {
81                CoreError::FactorLinearization(msg)
82            }
83            crate::linearizer::LinearizerError::InvalidInput(msg) => CoreError::InvalidInput(msg),
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::error::ErrorLogging;
92
93    // -------------------------------------------------------------------------
94    // CoreError Display — one test per variant
95    // -------------------------------------------------------------------------
96
97    #[test]
98    fn test_core_error_residual_block_display() {
99        let e = CoreError::ResidualBlock("bad block".into());
100        assert!(e.to_string().contains("bad block"));
101    }
102
103    #[test]
104    fn test_core_error_variable_display() {
105        let e = CoreError::Variable("bad var".into());
106        assert!(e.to_string().contains("bad var"));
107    }
108
109    #[test]
110    fn test_core_error_factor_linearization_display() {
111        let e = CoreError::FactorLinearization("jacobian fail".into());
112        assert!(e.to_string().contains("jacobian fail"));
113    }
114
115    #[test]
116    fn test_core_error_symbolic_structure_display() {
117        let e = CoreError::SymbolicStructure("sparse error".into());
118        assert!(e.to_string().contains("sparse error"));
119    }
120
121    #[test]
122    fn test_core_error_parallel_computation_display() {
123        let e = CoreError::ParallelComputation("thread panic".into());
124        assert!(e.to_string().contains("thread panic"));
125    }
126
127    #[test]
128    fn test_core_error_dimension_mismatch_display() {
129        let e = CoreError::DimensionMismatch("3 vs 6".into());
130        assert!(e.to_string().contains("3 vs 6"));
131    }
132
133    #[test]
134    fn test_core_error_invalid_constraint_display() {
135        let e = CoreError::InvalidConstraint("out of bounds".into());
136        assert!(e.to_string().contains("out of bounds"));
137    }
138
139    #[test]
140    fn test_core_error_loss_function_display() {
141        let e = CoreError::LossFunction("huber weight".into());
142        assert!(e.to_string().contains("huber weight"));
143    }
144
145    #[test]
146    fn test_core_error_invalid_input_display() {
147        let e = CoreError::InvalidInput("null pointer".into());
148        assert!(e.to_string().contains("null pointer"));
149    }
150
151    // -------------------------------------------------------------------------
152    // log() and log_with_source() return self (variant preserved)
153    // -------------------------------------------------------------------------
154
155    #[test]
156    fn test_core_error_log_returns_self() {
157        let e = CoreError::InvalidInput("test_log".into());
158        let returned = e.log();
159        assert!(returned.to_string().contains("test_log"));
160    }
161
162    #[test]
163    fn test_core_error_log_with_source_returns_self() {
164        let e = CoreError::DimensionMismatch("mismatch".into());
165        let source = std::io::Error::other("source error");
166        let returned = e.log_with_source(source);
167        assert!(returned.to_string().contains("mismatch"));
168    }
169
170    // -------------------------------------------------------------------------
171    // CoreResult type alias
172    // -------------------------------------------------------------------------
173
174    #[test]
175    fn test_core_result_ok() {
176        let r: CoreResult<i32> = Ok(42);
177        assert!(matches!(r, Ok(42)));
178    }
179
180    #[test]
181    fn test_core_result_err() {
182        let r: CoreResult<i32> = Err(CoreError::InvalidInput("oops".into()));
183        assert!(r.is_err());
184    }
185}