Skip to main content

optionstratlib_math/error/
interpolation.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/1/25
5******************************************************************************/
6//! Owned by `optionstratlib-math` (ADR-0001 D6, roadmap M1-14): `InterpolationError`.
7
8use crate::error::{CurveError, SurfaceError};
9use optionstratlib_core::error::PositionError;
10use thiserror::Error;
11
12/// Represents errors that can occur during different interpolation operations.
13///
14/// This enum encapsulates various types of errors that might be encountered when
15/// performing numerical interpolation across different methods. Each variant
16/// corresponds to a specific interpolation technique and contains a descriptive
17/// error message.
18///
19/// Interpolation errors typically arise from invalid input data, mathematical
20/// constraints, or numerical stability issues specific to each interpolation method.
21#[derive(Error, Debug)]
22pub enum InterpolationError {
23    /// Errors that occur during linear interpolation.
24    ///
25    /// These may include insufficient data points, non-monotonic input values,
26    /// or out-of-bounds interpolation attempts.
27    #[error("Linear interpolation error: {0}")]
28    Linear(String),
29
30    /// Errors that occur during bilinear interpolation.
31    ///
32    /// These may include a sample too small to build a cell from (fewer than
33    /// four points on a [`Curve`](crate::curves::Curve)), or a
34    /// checked-arithmetic step leaving the representable range.
35    ///
36    /// A query in the last segments of a
37    /// [`Curve`](crate::curves::Curve) is not one of them. The cell there is
38    /// built by clamping its far edge to the curve's last segment, which is
39    /// described on
40    /// [`BiLinearInterpolation for Curve`](crate::geometrics::BiLinearInterpolation);
41    /// it used to report this error because the cell ran off the end of the
42    /// sample.
43    #[error("Bilinear interpolation error: {0}")]
44    Bilinear(String),
45
46    /// Errors that occur during cubic interpolation.
47    ///
48    /// These may include insufficient data points for cubic polynomial fitting,
49    /// oscillation issues, or numerical instability.
50    #[error("Cubic interpolation error: {0}")]
51    Cubic(String),
52
53    /// Errors that occur during spline interpolation.
54    ///
55    /// These may include insufficient data points for spline construction,
56    /// boundary condition issues, or knot placement problems.
57    #[error("Spline interpolation error: {0}")]
58    Spline(String),
59
60    /// Error from position operations.
61    #[error(transparent)]
62    Position(#[from] PositionError),
63
64    /// Error from curve operations.
65    #[error(transparent)]
66    Curve(CurveError),
67
68    /// Error from surface operations.
69    #[error(transparent)]
70    Surface(#[from] SurfaceError),
71
72    /// Input data set was empty so interpolation could not proceed.
73    #[error("interpolation input has no data points")]
74    EmptyData,
75
76    /// Requested point falls outside the valid interpolation range.
77    #[error("interpolation target {target} is outside the supported range")]
78    OutOfRange {
79        /// The offending coordinate (stringified for flexibility across axes).
80        target: String,
81    },
82
83    /// Interval degenerated into a point (two consecutive knots coincide).
84    #[error("interpolation interval is degenerate: knots collapse to a single point")]
85    DegenerateInterval,
86}
87
88impl From<CurveError> for InterpolationError {
89    fn from(err: CurveError) -> Self {
90        InterpolationError::Curve(err)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use optionstratlib_core::error::position::PositionValidationErrorKind;
98    use std::error::Error as StdError;
99    use std::fmt;
100
101    // Mock implementations of the error types used in From implementations
102    #[derive(Debug)]
103    struct MockPositionError;
104
105    impl fmt::Display for MockPositionError {
106        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107            write!(f, "Mock position error")
108        }
109    }
110
111    impl StdError for MockPositionError {}
112
113    #[derive(Debug)]
114    struct MockCurveError;
115
116    impl fmt::Display for MockCurveError {
117        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118            write!(f, "Mock curve error")
119        }
120    }
121
122    impl StdError for MockCurveError {}
123
124    #[derive(Debug)]
125    struct MockSurfaceError;
126
127    impl fmt::Display for MockSurfaceError {
128        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129            write!(f, "Mock surface error")
130        }
131    }
132
133    impl StdError for MockSurfaceError {}
134
135    impl From<MockSurfaceError> for SurfaceError {
136        fn from(err: MockSurfaceError) -> Self {
137            SurfaceError::AnalysisError(err.to_string())
138        }
139    }
140
141    // Mock implementation of the From trait for testing
142    impl From<MockCurveError> for CurveError {
143        fn from(err: MockCurveError) -> Self {
144            // The actual implementation doesn't matter for this test
145            CurveError::AnalysisError(err.to_string())
146        }
147    }
148
149    // Mock implementation of the From trait for testing
150    impl From<MockPositionError> for PositionError {
151        fn from(err: MockPositionError) -> Self {
152            // The actual implementation doesn't matter for this test
153            PositionError::ValidationError(PositionValidationErrorKind::InvalidPosition {
154                reason: err.to_string(),
155            })
156        }
157    }
158
159    #[test]
160    fn test_create_interpolation_errors() {
161        // Test creating instances of each variant
162        let linear_err = InterpolationError::Linear("insufficient data points".to_string());
163        let bilinear_err = InterpolationError::Bilinear("out of grid boundary".to_string());
164        let cubic_err = InterpolationError::Cubic("numerical instability".to_string());
165        let spline_err = InterpolationError::Spline("invalid knot placement".to_string());
166        let empty_err = InterpolationError::EmptyData;
167        let out_of_range = InterpolationError::OutOfRange {
168            target: "1.23".to_string(),
169        };
170        let degenerate = InterpolationError::DegenerateInterval;
171
172        // Verify the variants are created correctly
173        match linear_err {
174            InterpolationError::Linear(msg) => assert_eq!(msg, "insufficient data points"),
175            _ => panic!("Expected Linear variant"),
176        }
177
178        match bilinear_err {
179            InterpolationError::Bilinear(msg) => assert_eq!(msg, "out of grid boundary"),
180            _ => panic!("Expected Bilinear variant"),
181        }
182
183        match cubic_err {
184            InterpolationError::Cubic(msg) => assert_eq!(msg, "numerical instability"),
185            _ => panic!("Expected Cubic variant"),
186        }
187
188        match spline_err {
189            InterpolationError::Spline(msg) => assert_eq!(msg, "invalid knot placement"),
190            _ => panic!("Expected Spline variant"),
191        }
192
193        assert!(matches!(empty_err, InterpolationError::EmptyData));
194        assert!(matches!(
195            out_of_range,
196            InterpolationError::OutOfRange { .. }
197        ));
198        assert!(matches!(degenerate, InterpolationError::DegenerateInterval));
199    }
200
201    #[test]
202    fn test_display_implementation() {
203        // Test that the Display implementation formats messages correctly
204        let linear_err = InterpolationError::Linear("test error".to_string());
205        let bilinear_err = InterpolationError::Bilinear("test error".to_string());
206        let cubic_err = InterpolationError::Cubic("test error".to_string());
207        let spline_err = InterpolationError::Spline("test error".to_string());
208        let empty_err = InterpolationError::EmptyData;
209
210        assert_eq!(
211            format!("{linear_err}"),
212            "Linear interpolation error: test error"
213        );
214        assert_eq!(
215            format!("{bilinear_err}"),
216            "Bilinear interpolation error: test error"
217        );
218        assert_eq!(
219            format!("{cubic_err}"),
220            "Cubic interpolation error: test error"
221        );
222        assert_eq!(
223            format!("{spline_err}"),
224            "Spline interpolation error: test error"
225        );
226        assert_eq!(
227            format!("{empty_err}"),
228            "interpolation input has no data points"
229        );
230    }
231
232    #[test]
233    fn test_conversion_from_position_error() {
234        let position_err = MockPositionError;
235        let position_err = PositionError::from(position_err);
236        let interpolation_err = InterpolationError::from(position_err);
237
238        match interpolation_err {
239            InterpolationError::Position(_) => {
240                // Conversion successful
241            }
242            _ => panic!("Expected Position variant"),
243        }
244    }
245
246    #[test]
247    fn test_conversion_from_curve_error() {
248        let curve_err = MockCurveError;
249        let curve_err = CurveError::from(curve_err);
250        let interpolation_err = InterpolationError::from(curve_err);
251        match interpolation_err {
252            InterpolationError::Curve(_) => {
253                // Conversion successful
254            }
255            _ => panic!("Expected Curve variant"),
256        }
257    }
258
259    #[test]
260    fn test_conversion_from_surface_error() {
261        let surface_err = MockSurfaceError;
262        let surface_err = SurfaceError::from(surface_err);
263        let interpolation_err = InterpolationError::from(surface_err);
264
265        match interpolation_err {
266            InterpolationError::Surface(_) => {
267                // Conversion successful
268            }
269            _ => panic!("Expected Surface variant"),
270        }
271    }
272
273    #[test]
274    fn test_debug_implementation() {
275        let err = InterpolationError::Linear("test debug".to_string());
276        let debug_str = format!("{err:?}");
277        assert!(
278            debug_str.contains("Linear"),
279            "Debug representation should include the variant"
280        );
281        assert!(
282            debug_str.contains("test debug"),
283            "Debug representation should include the error message"
284        );
285    }
286}