Skip to main content

use_calculus/
error.rs

1use core::fmt;
2use std::error::Error;
3
4/// Errors returned by numerical-calculus helpers.
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum CalculusError {
7    /// A sample point or evaluation point must be finite.
8    NonFinitePoint {
9        /// The name of the point that rejected the value.
10        name: &'static str,
11        /// The invalid value.
12        value: f64,
13    },
14    /// An integration bound must be finite.
15    NonFiniteBound {
16        /// The bound name that rejected the value.
17        bound: &'static str,
18        /// The invalid value.
19        value: f64,
20    },
21    /// A finite-difference or limit step must be finite.
22    NonFiniteStep(f64),
23    /// A finite-difference or limit step must be positive.
24    NonPositiveStep(f64),
25    /// A limit tolerance must be finite.
26    NonFiniteTolerance(f64),
27    /// A limit tolerance must not be negative.
28    NegativeTolerance(f64),
29    /// The number of subintervals must be positive.
30    ZeroSubintervals,
31    /// Simpson integration requires an even number of subintervals.
32    OddSubintervalCount(usize),
33    /// Function evaluation produced `NaN` or infinity.
34    NonFiniteEvaluation {
35        /// The sample point that produced the invalid value.
36        input: f64,
37        /// The invalid evaluation result.
38        value: f64,
39    },
40    /// The left and right limit samples disagreed by more than tolerance.
41    LimitMismatch {
42        /// The left-hand sample.
43        left: f64,
44        /// The right-hand sample.
45        right: f64,
46        /// The accepted absolute tolerance.
47        tolerance: f64,
48    },
49}
50
51impl CalculusError {
52    pub(crate) const fn validate_point(name: &'static str, value: f64) -> Result<f64, Self> {
53        if !value.is_finite() {
54            return Err(Self::NonFinitePoint { name, value });
55        }
56
57        Ok(value)
58    }
59
60    pub(crate) const fn validate_bound(bound: &'static str, value: f64) -> Result<f64, Self> {
61        if !value.is_finite() {
62            return Err(Self::NonFiniteBound { bound, value });
63        }
64
65        Ok(value)
66    }
67
68    pub(crate) const fn validate_step(step: f64) -> Result<f64, Self> {
69        if !step.is_finite() {
70            return Err(Self::NonFiniteStep(step));
71        }
72
73        if step <= 0.0 {
74            return Err(Self::NonPositiveStep(step));
75        }
76
77        Ok(step)
78    }
79
80    pub(crate) const fn validate_tolerance(tolerance: f64) -> Result<f64, Self> {
81        if !tolerance.is_finite() {
82            return Err(Self::NonFiniteTolerance(tolerance));
83        }
84
85        if tolerance < 0.0 {
86            return Err(Self::NegativeTolerance(tolerance));
87        }
88
89        Ok(tolerance)
90    }
91
92    pub(crate) const fn validate_subintervals(subintervals: usize) -> Result<usize, Self> {
93        if subintervals == 0 {
94            return Err(Self::ZeroSubintervals);
95        }
96
97        Ok(subintervals)
98    }
99
100    pub(crate) fn validate_even_subintervals(subintervals: usize) -> Result<usize, Self> {
101        Self::validate_subintervals(subintervals)?;
102
103        if !subintervals.is_multiple_of(2_usize) {
104            return Err(Self::OddSubintervalCount(subintervals));
105        }
106
107        Ok(subintervals)
108    }
109
110    pub(crate) const fn validate_evaluation(input: f64, value: f64) -> Result<f64, Self> {
111        if !value.is_finite() {
112            return Err(Self::NonFiniteEvaluation { input, value });
113        }
114
115        Ok(value)
116    }
117}
118
119impl fmt::Display for CalculusError {
120    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            Self::NonFinitePoint { name, value } => {
123                write!(formatter, "{name} point must be finite, got {value}")
124            },
125            Self::NonFiniteBound { bound, value } => {
126                write!(formatter, "{bound} bound must be finite, got {value}")
127            },
128            Self::NonFiniteStep(value) => {
129                write!(formatter, "step must be finite, got {value}")
130            },
131            Self::NonPositiveStep(value) => {
132                write!(formatter, "step must be positive, got {value}")
133            },
134            Self::NonFiniteTolerance(value) => {
135                write!(formatter, "tolerance must be finite, got {value}")
136            },
137            Self::NegativeTolerance(value) => {
138                write!(formatter, "tolerance must be non-negative, got {value}")
139            },
140            Self::ZeroSubintervals => {
141                write!(formatter, "subinterval count must be greater than zero")
142            },
143            Self::OddSubintervalCount(value) => write!(
144                formatter,
145                "Simpson integration requires an even number of subintervals, got {value}"
146            ),
147            Self::NonFiniteEvaluation { input, value } => write!(
148                formatter,
149                "function evaluation must be finite, got {value} at input {input}"
150            ),
151            Self::LimitMismatch {
152                left,
153                right,
154                tolerance,
155            } => write!(
156                formatter,
157                "left and right limit samples disagree beyond tolerance: left={left}, right={right}, tolerance={tolerance}"
158            ),
159        }
160    }
161}
162
163impl Error for CalculusError {}