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

use std::error::Error;
use std::fmt;

/// Errors for solver control flow
///
/// These error are not exposed directly to the API
pub enum SolverInternalError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
    InvalidJacobianError(crate::model::ModelError<M, D>),
    InvalidJacobianInverseError,
}

impl<M, D> fmt::Display for SolverInternalError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::InvalidJacobianError(error) => write!(f, "Invalid jacobian: {}", error),
            Self::InvalidJacobianInverseError => write!(f, "Non invertible jacobian"),
        }
    }
}

impl<M, D> fmt::Debug for SolverInternalError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

pub struct NonInvertibleJacobian;

/// Error returned by the [crate::solver::RootFinder::solve] method
///
/// Exit status:
/// - [SolverError::NonConvergenceError] : finished all the iterations but didn't find a root
/// - [SolverError::ModelInitialEvaluationError] : the algorithm must be able to evaluate the model correctly at the begin of the resolution process, it failed in that case
/// - [SolverError::ModelEvaluationError] : during the iterative process, while performing an update, a model error occured
/// - [SolverError::JacobianError] : during the jacobian evaluation, an error occured
/// - [SolverError::FinalEvaluationError] : the algorithm managed to converged but the model returned an error at convergence
pub enum SolverError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
    NonConvergenceError,
    ModelInitialEvaluationError(String),
    ModelEvaluationError(crate::model::ModelError<M, D>),
    JacobianError(SolverInternalError<M, D>),
    FinalEvaluationError,
}

impl<M, D> fmt::Display for SolverError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::NonConvergenceError => write!(f, "Convergence not reached"),
            Self::ModelInitialEvaluationError(error) => {
                write!(f, "Initial model evaluation failed: {}", error)
            }
            Self::ModelEvaluationError(error) => {
                write!(f, "Model evaluation failed: {}", error)
            }
            Self::JacobianError(error) => {
                write!(f, "Jacobian error: {}", error)
            }
            Self::FinalEvaluationError => {
                write!(f, "Final model evaluation failed")
            }
        }
    }
}

impl<M, D> fmt::Debug for SolverError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

impl<M, D> Error for SolverError<M, D>
where
    M: crate::model::Model<D>,
    D: nalgebra::Dim,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D>,
    nalgebra::DefaultAllocator: nalgebra::base::allocator::Allocator<f64, D, D>,
{
}