Skip to main content

numra_optim/
error.rs

1//! Error types for optimization algorithms.
2//!
3//! Author: Moussa Leblouba
4//! Date: 8 February 2026
5//! Modified: 2 May 2026
6
7use thiserror::Error;
8
9/// Errors that can occur during optimization.
10#[derive(Debug, Error)]
11pub enum OptimError {
12    #[error("line search failed at iteration {iteration}: {reason}")]
13    LineSearchFailed { iteration: usize, reason: String },
14    #[error("not a descent direction (dg = {directional_derivative:.2e})")]
15    NotDescentDirection { directional_derivative: f64 },
16    #[error("invalid function value: {value}")]
17    InvalidFunctionValue { value: f64 },
18    #[error("singular matrix in solver")]
19    SingularMatrix,
20    #[error("dimension mismatch: expected {expected}, got {actual}")]
21    DimensionMismatch { expected: usize, actual: usize },
22    #[error("no objective function specified")]
23    NoObjective,
24    #[error("no initial point specified")]
25    NoInitialPoint,
26    #[error("infeasible: constraint violation = {violation:.2e}")]
27    Infeasible { violation: f64 },
28    #[error("problem is unbounded")]
29    Unbounded,
30    #[error("linear program is infeasible")]
31    LPInfeasible,
32    #[error("QP Hessian is not positive semi-definite")]
33    QPNotPositiveSemiDefinite,
34    #[error("mixed-integer linear program is infeasible")]
35    MILPInfeasible,
36    #[error("{0}")]
37    Other(String),
38}
39
40impl From<String> for OptimError {
41    fn from(s: String) -> Self {
42        OptimError::Other(s)
43    }
44}
45
46impl From<&str> for OptimError {
47    fn from(s: &str) -> Self {
48        OptimError::Other(s.to_string())
49    }
50}
51
52impl From<numra_nonlinear::LineSearchError> for OptimError {
53    fn from(e: numra_nonlinear::LineSearchError) -> Self {
54        OptimError::Other(e.to_string())
55    }
56}
57
58impl From<numra_nonlinear::LinalgError> for OptimError {
59    fn from(e: numra_nonlinear::LinalgError) -> Self {
60        match e {
61            numra_nonlinear::LinalgError::Singular { .. }
62            | numra_nonlinear::LinalgError::NotSquare { .. }
63            | numra_nonlinear::LinalgError::NotPositiveDefinite => OptimError::SingularMatrix,
64            numra_nonlinear::LinalgError::DimensionMismatch { expected, actual } => {
65                OptimError::DimensionMismatch {
66                    expected: expected.0,
67                    actual: actual.0,
68                }
69            }
70            _ => OptimError::Other(e.to_string()),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_display_formatting() {
81        let e = OptimError::LineSearchFailed {
82            iteration: 5,
83            reason: "step too small".into(),
84        };
85        assert_eq!(
86            e.to_string(),
87            "line search failed at iteration 5: step too small"
88        );
89
90        let e = OptimError::NotDescentDirection {
91            directional_derivative: 1.5,
92        };
93        assert!(e.to_string().contains("1.50e0"));
94
95        let e = OptimError::SingularMatrix;
96        assert_eq!(e.to_string(), "singular matrix in solver");
97
98        let e = OptimError::DimensionMismatch {
99            expected: 3,
100            actual: 5,
101        };
102        assert_eq!(e.to_string(), "dimension mismatch: expected 3, got 5");
103    }
104
105    #[test]
106    fn test_lp_qp_error_variants() {
107        let e = OptimError::Unbounded;
108        assert_eq!(e.to_string(), "problem is unbounded");
109
110        let e = OptimError::LPInfeasible;
111        assert_eq!(e.to_string(), "linear program is infeasible");
112
113        let e = OptimError::QPNotPositiveSemiDefinite;
114        assert_eq!(e.to_string(), "QP Hessian is not positive semi-definite");
115
116        let e = OptimError::MILPInfeasible;
117        assert_eq!(e.to_string(), "mixed-integer linear program is infeasible");
118    }
119
120    #[test]
121    fn test_from_string() {
122        let e: OptimError = "some error".into();
123        assert_eq!(e.to_string(), "some error");
124
125        let e: OptimError = String::from("another error").into();
126        assert_eq!(e.to_string(), "another error");
127    }
128}