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>,
{
}