Skip to main content

conspire/math/optimize/
mod.rs

1#[cfg(test)]
2mod test;
3
4mod constraint;
5mod gradient_descent;
6mod line_search;
7mod newton_raphson;
8
9pub use constraint::EqualityConstraint;
10pub use gradient_descent::GradientDescent;
11pub use line_search::{LineSearch, LineSearchError};
12pub use newton_raphson::NewtonRaphson;
13
14use crate::math::{
15    Jacobian, Scalar, Solution, Style, StyledError,
16    assert::AssertionError,
17    matrix::square::SquareMatrixError,
18    sparse::{SparseError, SparseSolver},
19    styled_error,
20};
21use std::{fmt::Debug, ops::Mul};
22
23/// Zeroth-order root-finding algorithms.
24pub trait ZerothOrderRootFinding<X> {
25    fn root(
26        &self,
27        function: impl FnMut(&X) -> Result<X, String>,
28        initial_guess: X,
29        equality_constraint: EqualityConstraint,
30    ) -> Result<X, OptimizationError>;
31}
32
33/// First-order root-finding algorithms.
34pub trait FirstOrderRootFinding<F, J, X> {
35    fn root(
36        &self,
37        function: impl FnMut(&X) -> Result<F, String>,
38        jacobian: impl FnMut(&X) -> Result<J, String>,
39        initial_guess: X,
40        equality_constraint: EqualityConstraint,
41        sparse: Option<SparseSolver>,
42    ) -> Result<X, OptimizationError>;
43}
44
45/// First-order optimization algorithms.
46pub trait FirstOrderOptimization<F, X> {
47    fn minimize(
48        &self,
49        function: impl FnMut(&X) -> Result<F, String>,
50        jacobian: impl FnMut(&X) -> Result<X, String>,
51        initial_guess: X,
52        equality_constraint: EqualityConstraint,
53    ) -> Result<X, OptimizationError>;
54}
55
56/// Second-order optimization algorithms.
57pub trait SecondOrderOptimization<F, J, H, X> {
58    fn minimize(
59        &self,
60        function: impl FnMut(&X) -> Result<F, String>,
61        jacobian: impl FnMut(&X) -> Result<J, String>,
62        hessian: impl FnMut(&X) -> Result<H, String>,
63        initial_guess: X,
64        equality_constraint: EqualityConstraint,
65        sparse: Option<SparseSolver>,
66    ) -> Result<X, OptimizationError>;
67}
68
69trait BacktrackingLineSearch<J, X>
70where
71    Self: Debug,
72{
73    fn backtracking_line_search(
74        &self,
75        mut function: impl FnMut(&X) -> Result<Scalar, String>,
76        mut jacobian: impl FnMut(&X) -> Result<J, String>,
77        argument: &X,
78        jacobian0: &J,
79        decrement: &X,
80        step_size: Scalar,
81    ) -> Result<Scalar, OptimizationError>
82    where
83        J: Jacobian,
84        for<'a> &'a J: From<&'a X>,
85        X: Solution,
86        for<'a> &'a X: Mul<Scalar, Output = X>,
87    {
88        if matches!(self.get_line_search(), LineSearch::None) {
89            Ok(step_size)
90        } else {
91            match self.get_line_search().backtrack(
92                &mut function,
93                &mut jacobian,
94                argument,
95                jacobian0,
96                decrement,
97                step_size,
98            ) {
99                Ok(step_size) => Ok(step_size),
100                Err(error) => Err(OptimizationError::Upstream(
101                    format!("{error}"),
102                    format!("{self:?}"),
103                )),
104            }
105        }
106    }
107    fn get_line_search(&self) -> &LineSearch;
108}
109
110/// Possible errors encountered during optimization.
111pub enum OptimizationError {
112    Intermediate(String),
113    MaximumStepsReached(usize, String),
114    NotMinimum(String, String),
115    Upstream(String, String),
116    SingularMatrix,
117}
118
119impl From<String> for OptimizationError {
120    fn from(error: String) -> Self {
121        Self::Intermediate(error)
122    }
123}
124
125impl StyledError for OptimizationError {
126    fn message(&self, style: &Style) -> String {
127        let (h, c) = (style.headline, style.frame);
128        match self {
129            Self::Intermediate(message) => message.to_string(),
130            Self::MaximumStepsReached(steps, solver) => format!(
131                "{h}Maximum number of steps ({steps}) reached.{c}\n\
132                In solver: {solver}."
133            ),
134            Self::NotMinimum(solution, solver) => format!(
135                "{h}The obtained solution is not a minimum.{c}\n\
136                For solution: {solution}.\n\
137                In solver: {solver}."
138            ),
139            Self::SingularMatrix => format!("{h}Matrix is singular."),
140            Self::Upstream(error, solver) => format!(
141                "{error}{c}\n\
142                In solver: {solver}."
143            ),
144        }
145    }
146}
147
148styled_error!(OptimizationError);
149
150impl From<OptimizationError> for String {
151    fn from(error: OptimizationError) -> Self {
152        error.to_string()
153    }
154}
155
156impl From<OptimizationError> for AssertionError {
157    fn from(error: OptimizationError) -> Self {
158        Self {
159            message: error.to_string(),
160        }
161    }
162}
163
164impl From<SquareMatrixError> for OptimizationError {
165    fn from(_error: SquareMatrixError) -> Self {
166        Self::SingularMatrix
167    }
168}
169
170impl From<SparseError> for OptimizationError {
171    fn from(_error: SparseError) -> Self {
172        Self::SingularMatrix
173    }
174}