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}