newton_rootfinder/solver_n_dimensional/
errors.rs

1//! Solver errors
2//!
3//! The error API exposed to the end user is represented by the enum [SolverError]
4//!
5//! However, to have optimal integration between solver and model,
6//! it is expected to define the potential errors raised by the model
7//! through the associated types:
8//! - [crate::model::Model::InaccurateValuesError]
9//! - [crate::model::Model::UnusableValuesError]
10//!
11//! If such error cannot occur, you can default the values to [std::convert::Infallible]
12//!
13//! The use of such associated types allows the user model
14//! to classify its error into defined categories that the solver can react to.
15//! It also allows the model to define subcategories that the solver don't need to know about,
16//! in order to improve the quality of the error message to ease the debugging experience.
17//!
18//! An explanation behind the rational for the error categories can be found
19//! in the documentation of [crate::model::ModelError]
20//!
21//! Here is a working example implementing the associated types in the model:
22//!
23//! ```
24//! use std::error::Error;
25//! use std::fmt;
26//!
27//! use newton_rootfinder as nrf;
28//! use nrf::iteratives;
29//! use nrf::model::Model;
30//! use nrf::residuals;
31//!
32//! struct MyDummyModel {
33//!     iteratives: nalgebra::DVector<f64>,
34//!     residuals: nalgebra::DVector<f64>,
35//! }
36//!
37//! impl MyDummyModel {
38//!     pub fn new() -> Self {
39//!         let iteratives = nalgebra::DVector::zeros(1);
40//!         let residuals = nalgebra::DVector::zeros(1);
41//!         MyDummyModel {
42//!             iteratives,
43//!             residuals,
44//!         }
45//!     }
46//! }
47//!
48//! #[derive(Debug)]
49//! pub enum MyCustomErrors {
50//!     NotAGoodValue,
51//! }
52//!
53//! impl fmt::Display for MyCustomErrors {
54//!     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55//!         match self {
56//!             _ => write!(f, "{}", "Not a good value"),
57//!         }
58//!     }
59//! }
60//!
61//! impl Error for MyCustomErrors {}
62//!
63//! impl Model<nalgebra::Dyn> for MyDummyModel {
64//!     type InaccurateValuesError = MyCustomErrors;
65//!     type UnusableValuesError = MyCustomErrors;
66//!
67//!     fn len_problem(&self) -> usize {
68//!         1
69//!     }
70//!
71//!     fn get_iteratives(&self) -> nalgebra::DVector<f64> {
72//!         return self.iteratives.clone();
73//!     }
74//!
75//!     fn set_iteratives(&mut self, iteratives: &nalgebra::DVector<f64>) {
76//!         self.iteratives = iteratives.clone();
77//!     }
78//!
79//!     fn get_residuals(&self) -> nrf::residuals::ResidualsValues<nalgebra::Dyn> {
80//!         return nrf::residuals::ResidualsValues::new(
81//!             self.residuals.clone(),
82//!             nalgebra::DVector::zeros(1),
83//!         );
84//!     }
85//!
86//!     fn evaluate(&mut self) -> Result<(), nrf::model::ModelError<Self, nalgebra::Dyn>> {
87//!         self.residuals[0] = self.iteratives[0].powi(2) - 2.0;
88//!         Err(nrf::model::ModelError::InaccurateValuesError(
89//!             MyCustomErrors::NotAGoodValue,
90//!         ))
91//!     }
92//! }
93//!
94//! fn main() {
95//!     let problem_size = 1;
96//!     let mut init = nalgebra::DVector::zeros(problem_size);
97//!     init[0] = 1.0;
98//!
99//!     let damping = false;
100//!
101//!     let vec_iter_params = iteratives::default_vec_iteratives_fd(problem_size);
102//!     let iter_params = iteratives::Iteratives::new(&vec_iter_params);
103//!     let stopping_residuals = vec![residuals::NormalizationMethod::Abs; problem_size];
104//!     let update_methods = vec![residuals::NormalizationMethod::Abs; problem_size];
105//!     let res_config = residuals::ResidualsConfig::new(&stopping_residuals, &update_methods);
106//!     let mut rf = nrf::solver::default_with_guess(
107//!         init,
108//!         &iter_params,
109//!         &res_config,
110//!         nrf::solver::ResolutionMethod::NewtonRaphson,
111//!         damping,
112//!     );
113//!
114//!     let mut my_model = MyDummyModel::new();
115//!
116//!     let result = rf.solve(&mut my_model).unwrap_err();
117//!     let expected: nrf::errors::SolverError<nrf::model::UserModelFromFunction, nalgebra::Dyn> =
118//!         nrf::errors::SolverError::FinalEvaluationError;
119//!     assert_eq!(expected.to_string(), result.to_string());
120//!     assert!(float_cmp::approx_eq!(
121//!         f64,
122//!         my_model.get_iteratives()[0],
123//!         std::f64::consts::SQRT_2,
124//!         epsilon = 1e-6
125//!     ));
126//! }
127//! ```
128
129use std::error::Error;
130use std::fmt;
131
132/// Errors for solver control flow
133///
134/// These error are not exposed directly to the API
135pub enum SolverInternalError<M, D>
136where
137    M: crate::model::Model<D>,
138    D: nalgebra::Dim,
139    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
140    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
141{
142    InvalidJacobianError(crate::model::ModelError<M, D>),
143    InvalidJacobianInverseError,
144}
145
146impl<M, D> fmt::Display for SolverInternalError<M, D>
147where
148    M: crate::model::Model<D>,
149    D: nalgebra::Dim,
150    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
151    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
152{
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154        match self {
155            Self::InvalidJacobianError(error) => write!(f, "Invalid jacobian: {}", error),
156            Self::InvalidJacobianInverseError => write!(f, "Non invertible jacobian"),
157        }
158    }
159}
160
161impl<M, D> fmt::Debug for SolverInternalError<M, D>
162where
163    M: crate::model::Model<D>,
164    D: nalgebra::Dim,
165    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
166    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
167{
168    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169        fmt::Display::fmt(self, f)
170    }
171}
172
173pub struct NonInvertibleJacobian;
174
175/// Error returned by the [crate::solver::RootFinder::solve] method
176///
177/// Exit status:
178/// - [SolverError::NonConvergenceError] : finished all the iterations but didn't find a root
179/// - [SolverError::ModelInitialEvaluationError] : the algorithm must be able to evaluate the model correctly at the begin of the resolution process, it failed in that case
180/// - [SolverError::ModelEvaluationError] : during the iterative process, while performing an update, a model error occured
181/// - [SolverError::JacobianError] : during the jacobian evaluation, an error occured
182/// - [SolverError::FinalEvaluationError] : the algorithm managed to converged but the model returned an error at convergence
183pub enum SolverError<M, D>
184where
185    M: crate::model::Model<D>,
186    D: nalgebra::Dim,
187    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
188    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
189{
190    NonConvergenceError,
191    ModelInitialEvaluationError(String),
192    ModelEvaluationError(crate::model::ModelError<M, D>),
193    JacobianError(SolverInternalError<M, D>),
194    FinalEvaluationError,
195}
196
197impl<M, D> fmt::Display for SolverError<M, D>
198where
199    M: crate::model::Model<D>,
200    D: nalgebra::Dim,
201    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
202    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
203{
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        match self {
206            Self::NonConvergenceError => write!(f, "Convergence not reached"),
207            Self::ModelInitialEvaluationError(error) => {
208                write!(f, "Initial model evaluation failed: {}", error)
209            }
210            Self::ModelEvaluationError(error) => {
211                write!(f, "Model evaluation failed: {}", error)
212            }
213            Self::JacobianError(error) => {
214                write!(f, "Jacobian error: {}", error)
215            }
216            Self::FinalEvaluationError => {
217                write!(f, "Final model evaluation failed")
218            }
219        }
220    }
221}
222
223impl<M, D> fmt::Debug for SolverError<M, D>
224where
225    M: crate::model::Model<D>,
226    D: nalgebra::Dim,
227    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
228    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
229{
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        fmt::Display::fmt(self, f)
232    }
233}
234
235impl<M, D> Error for SolverError<M, D>
236where
237    M: crate::model::Model<D>,
238    D: nalgebra::Dim,
239    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D>,
240    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<D, D>,
241{
242}