use thiserror::Error;
pub type MathResult<T> = Result<T, MathError>;
#[derive(Error, Debug, Clone)]
pub enum MathError {
#[error("Convergence failed after {iterations} iterations (residual: {residual:.2e})")]
ConvergenceFailed {
iterations: u32,
residual: f64,
},
#[error("Invalid bracket: f({a}) = {fa:.2e} and f({b}) = {fb:.2e} have same sign")]
InvalidBracket {
a: f64,
b: f64,
fa: f64,
fb: f64,
},
#[error("Division by zero or near-zero value: {value:.2e}")]
DivisionByZero {
value: f64,
},
#[error("Singular matrix: cannot invert")]
SingularMatrix,
#[error("Incompatible matrix dimensions: ({rows1}x{cols1}) and ({rows2}x{cols2})")]
DimensionMismatch {
rows1: usize,
cols1: usize,
rows2: usize,
cols2: usize,
},
#[error("Extrapolation not allowed: {x} is outside [{min}, {max}]")]
ExtrapolationNotAllowed {
x: f64,
min: f64,
max: f64,
},
#[error("Insufficient data: need at least {required}, got {actual}")]
InsufficientData {
required: usize,
actual: usize,
},
#[error("Invalid input: {reason}")]
InvalidInput {
reason: String,
},
#[error("Numerical overflow in {operation}")]
Overflow {
operation: String,
},
#[error("Numerical underflow in {operation}")]
Underflow {
operation: String,
},
}
impl MathError {
#[must_use]
pub fn convergence_failed(iterations: u32, residual: f64) -> Self {
Self::ConvergenceFailed {
iterations,
residual,
}
}
#[must_use]
pub fn invalid_input(reason: impl Into<String>) -> Self {
Self::InvalidInput {
reason: reason.into(),
}
}
#[must_use]
pub fn insufficient_data(required: usize, actual: usize) -> Self {
Self::InsufficientData { required, actual }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = MathError::convergence_failed(100, 1e-6);
assert!(err.to_string().contains("100 iterations"));
}
}