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