scirs2_interpolate/
error.rs

1//! Error types for the SciRS2 interpolation module
2
3use thiserror::Error;
4
5/// Interpolation error type with specific context
6#[derive(Error, Debug)]
7pub enum InterpolateError {
8    /// Invalid input data with specific details
9    #[error("Invalid input data: {message}")]
10    InvalidInput { message: String },
11
12    /// Domain error with specific point and bounds
13    #[error("Point {point:?} is outside domain [{min}, {max}] in {context}")]
14    OutOfDomain {
15        point: String,
16        min: String,
17        max: String,
18        context: String,
19    },
20
21    /// Invalid parameter value with expected range
22    #[error("Invalid {parameter}: expected {expected}, got {actual} in {context}")]
23    InvalidParameter {
24        parameter: String,
25        expected: String,
26        actual: String,
27        context: String,
28    },
29
30    /// Shape mismatch with specific details
31    #[error("Shape mismatch: expected {expected}, got {actual} for {object}")]
32    ShapeMismatch {
33        expected: String,
34        actual: String,
35        object: String,
36    },
37
38    /// Computation error (generic error)
39    #[error("Computation error: {0}")]
40    ComputationError(String),
41
42    /// Shape error (ndarray shape mismatch)
43    #[error("Shape error: {0}")]
44    ShapeError(String),
45
46    /// Not implemented error
47    #[error("Not implemented: {0}")]
48    NotImplemented(String),
49
50    /// Invalid value provided
51    #[error("Invalid value: {0}")]
52    InvalidValue(String),
53
54    /// Dimension mismatch between arrays
55    #[error("Dimension mismatch: {0}")]
56    DimensionMismatch(String),
57
58    /// Point is outside the interpolation range
59    #[error("Out of bounds: {0}")]
60    OutOfBounds(String),
61
62    /// Invalid interpolator state
63    #[error("Invalid state: {0}")]
64    InvalidState(String),
65
66    /// Invalid operation attempted
67    #[error("Invalid operation: {0}")]
68    InvalidOperation(String),
69
70    /// Special case for boundary handling: point was mapped to an equivalent point
71    /// This is a special case that's not really an error, but used for control flow
72    #[error("Point was mapped to {0}")]
73    MappedPoint(f64),
74
75    /// Generic version of MappedPoint that can handle any numeric type
76    /// Used for control flow in generic interpolation functions
77    #[error("Point was mapped to equivalent")]
78    MappedPointGeneric(Box<dyn std::any::Any + Send + Sync>),
79
80    /// Index out of bounds error
81    #[error("Index error: {0}")]
82    IndexError(String),
83
84    /// I/O error
85    #[error("IO error: {0}")]
86    IoError(String),
87
88    /// Linear algebra error
89    #[error("Linear algebra error: {0}")]
90    LinalgError(String),
91
92    /// Numerical error (e.g., division by zero, overflow)
93    #[error("Numerical error: {0}")]
94    NumericalError(String),
95
96    /// Operation is not supported
97    #[error("Unsupported operation: {0}")]
98    UnsupportedOperation(String),
99
100    /// Insufficient data for the operation
101    #[error("Insufficient data: {0}")]
102    InsufficientData(String),
103
104    /// Interpolation failed
105    #[error("Interpolation failed: {0}")]
106    InterpolationFailed(String),
107
108    /// Missing points data
109    #[error("Missing points data: interpolator requires training points")]
110    MissingPoints,
111
112    /// Missing values data
113    #[error("Missing values data: interpolator requires training values")]
114    MissingValues,
115
116    /// Numerical instability detected
117    #[error("Numerical instability: {message}")]
118    NumericalInstability { message: String },
119}
120
121impl From<scirs2_core::ndarray::ShapeError> for InterpolateError {
122    fn from(err: scirs2_core::ndarray::ShapeError) -> Self {
123        InterpolateError::ShapeError(err.to_string())
124    }
125}
126
127impl From<scirs2_core::CoreError> for InterpolateError {
128    fn from(err: scirs2_core::CoreError) -> Self {
129        InterpolateError::ComputationError(err.to_string())
130    }
131}
132
133/// Result type for interpolation operations
134pub type InterpolateResult<T> = Result<T, InterpolateError>;
135
136impl InterpolateError {
137    /// Create an InvalidInput error with a descriptive message
138    pub fn invalid_input(message: impl Into<String>) -> Self {
139        Self::InvalidInput {
140            message: message.into(),
141        }
142    }
143
144    /// Create an OutOfDomain error with specific context
145    pub fn out_of_domain<T: std::fmt::Display>(
146        point: T,
147        min: T,
148        max: T,
149        context: impl Into<String>,
150    ) -> Self {
151        Self::OutOfDomain {
152            point: point.to_string(),
153            min: min.to_string(),
154            max: max.to_string(),
155            context: context.into(),
156        }
157    }
158
159    /// Create an InvalidParameter error with specific context  
160    pub fn invalid_parameter<T: std::fmt::Display>(
161        parameter: impl Into<String>,
162        expected: impl Into<String>,
163        actual: T,
164        context: impl Into<String>,
165    ) -> Self {
166        Self::InvalidParameter {
167            parameter: parameter.into(),
168            expected: expected.into(),
169            actual: actual.to_string(),
170            context: context.into(),
171        }
172    }
173
174    /// Create a ShapeMismatch error with specific details
175    pub fn shape_mismatch(
176        expected: impl Into<String>,
177        actual: impl Into<String>,
178        object: impl Into<String>,
179    ) -> Self {
180        Self::ShapeMismatch {
181            expected: expected.into(),
182            actual: actual.into(),
183            object: object.into(),
184        }
185    }
186
187    /// Create a standard dimension mismatch error
188    pub fn dimension_mismatch(expected: usize, actual: usize, context: &str) -> Self {
189        Self::DimensionMismatch(format!(
190            "Dimension mismatch in {context}: expected {expected}, got {actual}"
191        ))
192    }
193
194    /// Create a standard empty data error
195    pub fn empty_data(context: &str) -> Self {
196        Self::InsufficientData(format!("Empty input data provided to {context}"))
197    }
198
199    /// Create a standard convergence failure error
200    pub fn convergence_failure(method: &str, iterations: usize) -> Self {
201        Self::ComputationError(format!(
202            "{method} failed to converge after {iterations} iterations"
203        ))
204    }
205
206    /// Create a numerical stability error
207    pub fn numerical_instability(context: &str, details: &str) -> Self {
208        Self::NumericalError(format!("Numerical instability in {context}: {details}"))
209    }
210
211    /// Create a numerical error
212    pub fn numerical_error(message: impl Into<String>) -> Self {
213        Self::NumericalError(message.into())
214    }
215
216    /// Create an insufficient data points error
217    pub fn insufficient_points(required: usize, provided: usize, method: &str) -> Self {
218        Self::InsufficientData(format!(
219            "{method} requires at least {required} points, but only {provided} provided"
220        ))
221    }
222
223    /// Create an actionable parameter error with suggestions
224    pub fn invalid_parameter_with_suggestion<T: std::fmt::Display>(
225        parameter: impl Into<String>,
226        value: T,
227        context: impl Into<String>,
228        suggestion: impl Into<String>,
229    ) -> Self {
230        Self::InvalidParameter {
231            parameter: parameter.into(),
232            expected: suggestion.into(),
233            actual: value.to_string(),
234            context: context.into(),
235        }
236    }
237
238    /// Create an actionable domain error with recovery suggestions
239    pub fn out_of_domain_with_suggestion<T: std::fmt::Display>(
240        point: T,
241        min: T,
242        max: T,
243        context: impl Into<String>,
244        suggestion: impl Into<String>,
245    ) -> Self {
246        let context_str = context.into();
247        let suggestion_str = suggestion.into();
248        Self::OutOfDomain {
249            point: point.to_string(),
250            min: min.to_string(),
251            max: max.to_string(),
252            context: format!("{context_str} - Suggestion: {suggestion_str}"),
253        }
254    }
255
256    /// Create a numerical stability error with actionable advice
257    pub fn numerical_instability_with_advice(context: &str, details: &str, advice: &str) -> Self {
258        Self::NumericalError(format!(
259            "Numerical instability in {context}: {details} - ADVICE: {advice}"
260        ))
261    }
262
263    /// Create a convergence failure with actionable recommendations
264    pub fn convergence_failure_with_advice(method: &str, iterations: usize, advice: &str) -> Self {
265        Self::ComputationError(format!(
266            "{method} failed to converge after {iterations} iterations - RECOMMENDATION: {advice}"
267        ))
268    }
269
270    /// Create a matrix conditioning error with specific recommendations
271    pub fn matrix_conditioning_error(
272        condition_number: f64,
273        context: &str,
274        recommended_regularization: Option<f64>,
275    ) -> Self {
276        let advice = if let Some(reg) = recommended_regularization {
277            format!(
278                "Matrix is ill-conditioned (condition number: {condition_number:.2e}). Try regularization parameter ≥ {reg:.2e}"
279            )
280        } else {
281            format!(
282                "Matrix is ill-conditioned (condition number: {condition_number:.2e}). Consider data preprocessing or regularization"
283            )
284        };
285
286        Self::LinalgError(format!(
287            "{context}: {} - SOLUTION: {advice}",
288            if condition_number > 1e16 {
289                "Severe numerical instability"
290            } else {
291                "Poor numerical conditioning"
292            }
293        ))
294    }
295
296    /// Create a data quality error with preprocessing suggestions
297    pub fn data_quality_error(issue: &str, context: &str, preprocessingadvice: &str) -> Self {
298        Self::InvalidInput {
299            message: format!(
300                "{issue} detected in {context}: Data may be unsuitable for interpolation - DATA PREPROCESSING: {preprocessingadvice}"
301            ),
302        }
303    }
304
305    /// Create a method selection error with alternative suggestions
306    pub fn method_selection_error(
307        attempted_method: &str,
308        data_characteristics: &str,
309        recommended_alternatives: &[&str],
310    ) -> Self {
311        let alternatives = recommended_alternatives.join(", ");
312        Self::UnsupportedOperation(format!(
313            "{attempted_method} is not suitable for data with {data_characteristics}: Consider using a different interpolation method - ALTERNATIVES: Try {alternatives}"
314        ))
315    }
316
317    /// Create a performance warning with optimization suggestions
318    pub fn performance_warning(
319        operation: &str,
320        data_size: usize,
321        optimization_advice: &str,
322    ) -> Self {
323        Self::ComputationError(format!(
324            "{operation} may be slow for {data_size} data points - OPTIMIZATION: {optimization_advice}"
325        ))
326    }
327}