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 Fn(&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 Fn(&X) -> Result<F, String>,
41        jacobian: impl Fn(&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 Fn(&X) -> Result<F, String>,
52        jacobian: impl Fn(&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 Fn(&X) -> Result<F, String>,
63        jacobian: impl Fn(&X) -> Result<J, String>,
64        hessian: impl Fn(&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        function: impl Fn(&X) -> Result<Scalar, String>,
78        jacobian: impl Fn(&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                &function, &jacobian, argument, jacobian0, decrement, step_size,
95            ) {
96                Ok(step_size) => Ok(step_size),
97                Err(error) => Err(OptimizationError::Upstream(
98                    format!("{error}"),
99                    format!("{self:?}"),
100                )),
101            }
102        }
103    }
104    fn get_line_search(&self) -> &LineSearch;
105}
106
107/// Possible errors encountered during optimization.
108pub enum OptimizationError {
109    Intermediate(String),
110    MaximumStepsReached(usize, String),
111    NotMinimum(String, String),
112    Upstream(String, String),
113    SingularMatrix,
114}
115
116impl From<String> for OptimizationError {
117    fn from(error: String) -> Self {
118        Self::Intermediate(error)
119    }
120}
121
122impl Debug for OptimizationError {
123    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
124        let error = match self {
125            Self::Intermediate(message) => message.to_string(),
126            Self::MaximumStepsReached(steps, solver) => {
127                format!(
128                    "\x1b[1;91mMaximum number of steps ({steps}) reached.\x1b[0;91m\n\
129                     In solver: {solver}."
130                )
131            }
132            Self::NotMinimum(solution, solver) => {
133                format!(
134                    "\x1b[1;91mThe obtained solution is not a minimum.\x1b[0;91m\n\
135                     For solution: {solution}.\n\
136                     In solver: {solver}."
137                )
138            }
139            Self::SingularMatrix => "\x1b[1;91mMatrix is singular.".to_string(),
140            Self::Upstream(error, solver) => {
141                format!(
142                    "{error}\x1b[0;91m\n\
143                    In solver: {solver}."
144                )
145            }
146        };
147        write!(f, "\n{error}\n\x1b[0;2;31m{}\x1b[0m\n", defeat_message())
148    }
149}
150
151impl Display for OptimizationError {
152    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
153        let error = match self {
154            Self::Intermediate(message) => message.to_string(),
155            Self::MaximumStepsReached(steps, solver) => {
156                format!(
157                    "\x1b[1;91mMaximum number of steps ({steps}) reached.\x1b[0;91m\n\
158                     In solver: {solver}."
159                )
160            }
161            Self::NotMinimum(solution, solver) => {
162                format!(
163                    "\x1b[1;91mThe obtained solution is not a minimum.\x1b[0;91m\n\
164                     For solution: {solution}.\n\
165                     In solver: {solver}."
166                )
167            }
168            Self::SingularMatrix => "\x1b[1;91mMatrix is singular.".to_string(),
169            Self::Upstream(error, solver) => {
170                format!(
171                    "{error}\x1b[0;91m\n\
172                    In solver: {solver}."
173                )
174            }
175        };
176        write!(f, "{error}\x1b[0m")
177    }
178}
179
180impl From<OptimizationError> for String {
181    fn from(error: OptimizationError) -> Self {
182        error.to_string()
183    }
184}
185
186impl From<OptimizationError> for TestError {
187    fn from(error: OptimizationError) -> Self {
188        Self {
189            message: error.to_string(),
190        }
191    }
192}
193
194impl From<SquareMatrixError> for OptimizationError {
195    fn from(_error: SquareMatrixError) -> Self {
196        Self::SingularMatrix
197    }
198}