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::{
15    defeat_message,
16    math::{
17        Jacobian, Scalar, Solution, TestError,
18        matrix::square::{Banded, SquareMatrixError},
19    },
20};
21use std::{
22    fmt::{self, Debug, Display, Formatter},
23    ops::Mul,
24};
25
26/// Zeroth-order root-finding algorithms.
27pub trait ZerothOrderRootFinding<X> {
28    fn root(
29        &self,
30        function: impl FnMut(&X) -> Result<X, String>,
31        initial_guess: X,
32        equality_constraint: EqualityConstraint,
33    ) -> Result<X, OptimizationError>;
34}
35
36/// First-order root-finding algorithms.
37pub trait FirstOrderRootFinding<F, J, X> {
38    fn root(
39        &self,
40        function: impl FnMut(&X) -> Result<F, String>,
41        jacobian: impl FnMut(&X) -> Result<J, String>,
42        initial_guess: X,
43        equality_constraint: EqualityConstraint,
44    ) -> Result<X, OptimizationError>;
45}
46
47/// First-order optimization algorithms.
48pub trait FirstOrderOptimization<F, X> {
49    fn minimize(
50        &self,
51        function: impl FnMut(&X) -> Result<F, String>,
52        jacobian: impl FnMut(&X) -> Result<X, String>,
53        initial_guess: X,
54        equality_constraint: EqualityConstraint,
55    ) -> Result<X, OptimizationError>;
56}
57
58/// Second-order optimization algorithms.
59pub trait SecondOrderOptimization<F, J, H, X> {
60    fn minimize(
61        &self,
62        function: impl FnMut(&X) -> Result<F, String>,
63        jacobian: impl FnMut(&X) -> Result<J, String>,
64        hessian: impl FnMut(&X) -> Result<H, String>,
65        initial_guess: X,
66        equality_constraint: EqualityConstraint,
67        banded: Option<Banded>,
68    ) -> Result<X, OptimizationError>;
69}
70
71trait BacktrackingLineSearch<J, X>
72where
73    Self: Debug,
74{
75    fn backtracking_line_search(
76        &self,
77        mut function: impl FnMut(&X) -> Result<Scalar, String>,
78        mut jacobian: impl FnMut(&X) -> Result<J, String>,
79        argument: &X,
80        jacobian0: &J,
81        decrement: &X,
82        step_size: Scalar,
83    ) -> Result<Scalar, OptimizationError>
84    where
85        J: Jacobian,
86        for<'a> &'a J: From<&'a X>,
87        X: Solution,
88        for<'a> &'a X: Mul<Scalar, Output = X>,
89    {
90        if matches!(self.get_line_search(), LineSearch::None) {
91            Ok(step_size)
92        } else {
93            match self.get_line_search().backtrack(
94                &mut function,
95                &mut jacobian,
96                argument,
97                jacobian0,
98                decrement,
99                step_size,
100            ) {
101                Ok(step_size) => Ok(step_size),
102                Err(error) => Err(OptimizationError::Upstream(
103                    format!("{error}"),
104                    format!("{self:?}"),
105                )),
106            }
107        }
108    }
109    fn get_line_search(&self) -> &LineSearch;
110}
111
112/// Possible errors encountered during optimization.
113pub enum OptimizationError {
114    Intermediate(String),
115    MaximumStepsReached(usize, String),
116    NotMinimum(String, String),
117    Upstream(String, String),
118    SingularMatrix,
119}
120
121impl From<String> for OptimizationError {
122    fn from(error: String) -> Self {
123        Self::Intermediate(error)
124    }
125}
126
127impl Debug for OptimizationError {
128    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
129        let error = match self {
130            Self::Intermediate(message) => message.to_string(),
131            Self::MaximumStepsReached(steps, solver) => {
132                format!(
133                    "\x1b[1;91mMaximum number of steps ({steps}) reached.\x1b[0;91m\n\
134                     In solver: {solver}."
135                )
136            }
137            Self::NotMinimum(solution, solver) => {
138                format!(
139                    "\x1b[1;91mThe obtained solution is not a minimum.\x1b[0;91m\n\
140                     For solution: {solution}.\n\
141                     In solver: {solver}."
142                )
143            }
144            Self::SingularMatrix => "\x1b[1;91mMatrix is singular.".to_string(),
145            Self::Upstream(error, solver) => {
146                format!(
147                    "{error}\x1b[0;91m\n\
148                    In solver: {solver}."
149                )
150            }
151        };
152        write!(f, "\n{error}\n\x1b[0;2;31m{}\x1b[0m\n", defeat_message())
153    }
154}
155
156impl Display for OptimizationError {
157    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
158        let error = match self {
159            Self::Intermediate(message) => message.to_string(),
160            Self::MaximumStepsReached(steps, solver) => {
161                format!(
162                    "\x1b[1;91mMaximum number of steps ({steps}) reached.\x1b[0;91m\n\
163                     In solver: {solver}."
164                )
165            }
166            Self::NotMinimum(solution, solver) => {
167                format!(
168                    "\x1b[1;91mThe obtained solution is not a minimum.\x1b[0;91m\n\
169                     For solution: {solution}.\n\
170                     In solver: {solver}."
171                )
172            }
173            Self::SingularMatrix => "\x1b[1;91mMatrix is singular.".to_string(),
174            Self::Upstream(error, solver) => {
175                format!(
176                    "{error}\x1b[0;91m\n\
177                    In solver: {solver}."
178                )
179            }
180        };
181        write!(f, "{error}\x1b[0m")
182    }
183}
184
185impl From<OptimizationError> for String {
186    fn from(error: OptimizationError) -> Self {
187        error.to_string()
188    }
189}
190
191impl From<OptimizationError> for TestError {
192    fn from(error: OptimizationError) -> Self {
193        Self {
194            message: error.to_string(),
195        }
196    }
197}
198
199impl From<SquareMatrixError> for OptimizationError {
200    fn from(_error: SquareMatrixError) -> Self {
201        Self::SingularMatrix
202    }
203}