sublinear_solver/
error.rs

1//! Error types and handling for the sublinear solver.
2//!
3//! This module defines all error conditions that can occur during matrix operations
4//! and solver execution, providing detailed error information for debugging and
5//! recovery strategies.
6
7use core::fmt;
8use alloc::{string::String, vec::Vec};
9
10/// Result type alias for solver operations.
11pub type Result<T> = core::result::Result<T, SolverError>;
12
13/// Comprehensive error type for all solver operations.
14#[derive(Debug, Clone, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum SolverError {
17    /// Matrix is not diagonally dominant, which is required for convergence guarantees.
18    MatrixNotDiagonallyDominant {
19        /// The row where diagonal dominance fails
20        row: usize,
21        /// Diagonal element value
22        diagonal: f64,
23        /// Sum of off-diagonal absolute values
24        off_diagonal_sum: f64,
25    },
26    
27    /// Numerical instability detected during computation.
28    NumericalInstability {
29        /// Description of the instability
30        reason: String,
31        /// Iteration where instability was detected
32        iteration: usize,
33        /// Current residual norm when instability occurred
34        residual_norm: f64,
35    },
36    
37    /// Algorithm failed to converge within specified iterations.
38    ConvergenceFailure {
39        /// Number of iterations performed
40        iterations: usize,
41        /// Final residual norm achieved
42        residual_norm: f64,
43        /// Target tolerance that wasn't reached
44        tolerance: f64,
45        /// Algorithm that failed to converge
46        algorithm: String,
47    },
48    
49    /// Invalid input parameters or data.
50    InvalidInput {
51        /// Description of the invalid input
52        message: String,
53        /// Optional parameter name that was invalid
54        parameter: Option<String>,
55    },
56    
57    /// Dimension mismatch between matrix and vector operations.
58    DimensionMismatch {
59        /// Expected dimension
60        expected: usize,
61        /// Actual dimension found
62        actual: usize,
63        /// Context where mismatch occurred
64        operation: String,
65    },
66    
67    /// Matrix format is not supported for the requested operation.
68    UnsupportedMatrixFormat {
69        /// Current matrix format
70        current_format: String,
71        /// Required format for the operation
72        required_format: String,
73        /// Operation that was attempted
74        operation: String,
75    },
76    
77    /// Memory allocation failure.
78    MemoryAllocationError {
79        /// Requested allocation size in bytes
80        requested_size: usize,
81        /// Available memory at time of failure (if known)
82        available_memory: Option<usize>,
83    },
84    
85    /// Index out of bounds for matrix or vector access.
86    IndexOutOfBounds {
87        /// The invalid index
88        index: usize,
89        /// Maximum valid index
90        max_index: usize,
91        /// Context where out-of-bounds access occurred
92        context: String,
93    },
94    
95    /// Sparse matrix contains invalid data.
96    InvalidSparseMatrix {
97        /// Description of the invalid data
98        reason: String,
99        /// Position where invalid data was found
100        position: Option<(usize, usize)>,
101    },
102    
103    /// Algorithm-specific error conditions.
104    AlgorithmError {
105        /// Name of the algorithm
106        algorithm: String,
107        /// Specific error message
108        message: String,
109        /// Additional context data
110        context: Vec<(String, String)>,
111    },
112    
113    /// WebAssembly binding error (when WASM feature is enabled).
114    #[cfg(feature = "wasm")]
115    WasmBindingError {
116        /// Error message from WASM binding
117        message: String,
118        /// JavaScript error if available
119        js_error: Option<String>,
120    },
121    
122    /// I/O error for file operations (when std feature is enabled).
123    #[cfg(feature = "std")]
124    IoError {
125        /// I/O error description
126        #[cfg_attr(feature = "serde", serde(skip))]
127        message: String,
128        /// Context where I/O error occurred
129        context: String,
130    },
131    
132    /// Serialization/deserialization error.
133    #[cfg(feature = "serde")]
134    SerializationError {
135        /// Error message from serialization
136        message: String,
137        /// Data type being serialized
138        data_type: String,
139    },
140}
141
142impl SolverError {
143    /// Check if this error indicates a recoverable condition.
144    /// 
145    /// Recoverable errors can potentially be resolved by adjusting
146    /// algorithm parameters or switching to a different solver.
147    pub fn is_recoverable(&self) -> bool {
148        match self {
149            SolverError::ConvergenceFailure { .. } => true,
150            SolverError::NumericalInstability { .. } => true,
151            SolverError::MatrixNotDiagonallyDominant { .. } => false, // Fundamental issue
152            SolverError::InvalidInput { .. } => false, // User error
153            SolverError::DimensionMismatch { .. } => false, // User error
154            SolverError::MemoryAllocationError { .. } => false, // System limitation
155            SolverError::IndexOutOfBounds { .. } => false, // Programming error
156            SolverError::InvalidSparseMatrix { .. } => false, // Data corruption
157            SolverError::UnsupportedMatrixFormat { .. } => true, // Can convert format
158            SolverError::AlgorithmError { .. } => true, // Algorithm-specific, might recover
159            #[cfg(feature = "wasm")]
160            SolverError::WasmBindingError { .. } => false, // Runtime environment issue
161            #[cfg(feature = "std")]
162            SolverError::IoError { .. } => false, // External system issue
163            #[cfg(feature = "serde")]
164            SolverError::SerializationError { .. } => false, // Data format issue
165        }
166    }
167    
168    /// Get suggested recovery strategy for recoverable errors.
169    pub fn recovery_strategy(&self) -> Option<RecoveryStrategy> {
170        match self {
171            SolverError::ConvergenceFailure { algorithm, .. } => {
172                // Suggest alternative algorithms
173                Some(match algorithm.as_str() {
174                    "neumann" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
175                    "forward_push" => RecoveryStrategy::SwitchAlgorithm("backward_push".to_string()),
176                    "backward_push" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
177                    _ => RecoveryStrategy::RelaxTolerance(10.0),
178                })
179            },
180            SolverError::NumericalInstability { .. } => {
181                Some(RecoveryStrategy::IncreasePrecision)
182            },
183            SolverError::UnsupportedMatrixFormat { required_format, .. } => {
184                Some(RecoveryStrategy::ConvertMatrixFormat(required_format.clone()))
185            },
186            SolverError::AlgorithmError { algorithm, .. } => {
187                Some(RecoveryStrategy::SwitchAlgorithm("neumann".to_string()))
188            },
189            _ => None,
190        }
191    }
192    
193    /// Get the error severity level.
194    pub fn severity(&self) -> ErrorSeverity {
195        match self {
196            SolverError::MemoryAllocationError { .. } => ErrorSeverity::Critical,
197            SolverError::InvalidSparseMatrix { .. } => ErrorSeverity::Critical,
198            SolverError::IndexOutOfBounds { .. } => ErrorSeverity::Critical,
199            SolverError::MatrixNotDiagonallyDominant { .. } => ErrorSeverity::High,
200            SolverError::ConvergenceFailure { .. } => ErrorSeverity::Medium,
201            SolverError::NumericalInstability { .. } => ErrorSeverity::Medium,
202            SolverError::InvalidInput { .. } => ErrorSeverity::Medium,
203            SolverError::DimensionMismatch { .. } => ErrorSeverity::Medium,
204            SolverError::UnsupportedMatrixFormat { .. } => ErrorSeverity::Low,
205            SolverError::AlgorithmError { .. } => ErrorSeverity::Medium,
206            #[cfg(feature = "wasm")]
207            SolverError::WasmBindingError { .. } => ErrorSeverity::High,
208            #[cfg(feature = "std")]
209            SolverError::IoError { .. } => ErrorSeverity::Medium,
210            #[cfg(feature = "serde")]
211            SolverError::SerializationError { .. } => ErrorSeverity::Low,
212        }
213    }
214}
215
216/// Recovery strategies for recoverable errors.
217#[derive(Debug, Clone, PartialEq)]
218#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219pub enum RecoveryStrategy {
220    /// Switch to a different solver algorithm.
221    SwitchAlgorithm(String),
222    /// Increase numerical precision (f32 -> f64).
223    IncreasePrecision,
224    /// Relax convergence tolerance by the given factor.
225    RelaxTolerance(f64),
226    /// Restart with different random seed.
227    RestartWithDifferentSeed,
228    /// Convert matrix to a different storage format.
229    ConvertMatrixFormat(String),
230    /// Increase maximum iteration count.
231    IncreaseIterations(usize),
232}
233
234/// Error severity levels for logging and monitoring.
235#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
236#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
237pub enum ErrorSeverity {
238    /// Low severity - algorithm can continue with degraded performance
239    Low,
240    /// Medium severity - operation failed but system remains stable
241    Medium,
242    /// High severity - significant failure requiring user intervention
243    High,
244    /// Critical severity - system integrity compromised
245    Critical,
246}
247
248impl fmt::Display for SolverError {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        match self {
251            SolverError::MatrixNotDiagonallyDominant { row, diagonal, off_diagonal_sum } => {
252                write!(f, "Matrix is not diagonally dominant at row {}: diagonal = {:.6}, off-diagonal sum = {:.6}", 
253                       row, diagonal, off_diagonal_sum)
254            },
255            SolverError::NumericalInstability { reason, iteration, residual_norm } => {
256                write!(f, "Numerical instability at iteration {}: {} (residual = {:.2e})", 
257                       iteration, reason, residual_norm)
258            },
259            SolverError::ConvergenceFailure { iterations, residual_norm, tolerance, algorithm } => {
260                write!(f, "Algorithm '{}' failed to converge after {} iterations: residual = {:.2e} > tolerance = {:.2e}", 
261                       algorithm, iterations, residual_norm, tolerance)
262            },
263            SolverError::InvalidInput { message, parameter } => {
264                match parameter {
265                    Some(param) => write!(f, "Invalid input for parameter '{}': {}", param, message),
266                    None => write!(f, "Invalid input: {}", message),
267                }
268            },
269            SolverError::DimensionMismatch { expected, actual, operation } => {
270                write!(f, "Dimension mismatch in {}: expected {}, got {}", operation, expected, actual)
271            },
272            SolverError::UnsupportedMatrixFormat { current_format, required_format, operation } => {
273                write!(f, "Operation '{}' requires {} format, but matrix is in {} format", 
274                       operation, required_format, current_format)
275            },
276            SolverError::MemoryAllocationError { requested_size, available_memory } => {
277                match available_memory {
278                    Some(available) => write!(f, "Memory allocation failed: requested {} bytes, {} available", 
279                                             requested_size, available),
280                    None => write!(f, "Memory allocation failed: requested {} bytes", requested_size),
281                }
282            },
283            SolverError::IndexOutOfBounds { index, max_index, context } => {
284                write!(f, "Index {} out of bounds in {}: maximum valid index is {}", 
285                       index, context, max_index)
286            },
287            SolverError::InvalidSparseMatrix { reason, position } => {
288                match position {
289                    Some((row, col)) => write!(f, "Invalid sparse matrix at ({}, {}): {}", row, col, reason),
290                    None => write!(f, "Invalid sparse matrix: {}", reason),
291                }
292            },
293            SolverError::AlgorithmError { algorithm, message, .. } => {
294                write!(f, "Algorithm '{}' error: {}", algorithm, message)
295            },
296            #[cfg(feature = "wasm")]
297            SolverError::WasmBindingError { message, js_error } => {
298                match js_error {
299                    Some(js_err) => write!(f, "WASM binding error: {} (JS: {})", message, js_err),
300                    None => write!(f, "WASM binding error: {}", message),
301                }
302            },
303            #[cfg(feature = "std")]
304            SolverError::IoError { message, context } => {
305                write!(f, "I/O error in {}: {}", context, message)
306            },
307            #[cfg(feature = "serde")]
308            SolverError::SerializationError { message, data_type } => {
309                write!(f, "Serialization error for {}: {}", data_type, message)
310            },
311        }
312    }
313}
314
315#[cfg(feature = "std")]
316impl std::error::Error for SolverError {
317    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
318        None
319    }
320}
321
322// Conversion from standard library errors
323#[cfg(feature = "std")]
324impl From<std::io::Error> for SolverError {
325    fn from(err: std::io::Error) -> Self {
326        SolverError::IoError {
327            message: err.to_string(),
328            context: "File operation".to_string(),
329        }
330    }
331}
332
333// Conversion for WASM environments
334#[cfg(feature = "wasm")]
335impl From<wasm_bindgen::JsValue> for SolverError {
336    fn from(err: wasm_bindgen::JsValue) -> Self {
337        let message = if let Some(string) = err.as_string() {
338            string
339        } else {
340            "Unknown JavaScript error".to_string()
341        };
342        
343        SolverError::WasmBindingError {
344            message,
345            js_error: None,
346        }
347    }
348}
349
350#[cfg(all(test, feature = "std"))]
351mod tests {
352    use super::*;
353    
354    #[test]
355    fn test_error_recoverability() {
356        let convergence_error = SolverError::ConvergenceFailure {
357            iterations: 100,
358            residual_norm: 1e-3,
359            tolerance: 1e-6,
360            algorithm: "neumann".to_string(),
361        };
362        assert!(convergence_error.is_recoverable());
363        
364        let dimension_error = SolverError::DimensionMismatch {
365            expected: 100,
366            actual: 50,
367            operation: "matrix_vector_multiply".to_string(),
368        };
369        assert!(!dimension_error.is_recoverable());
370    }
371    
372    #[test]
373    fn test_recovery_strategies() {
374        let error = SolverError::ConvergenceFailure {
375            iterations: 100,
376            residual_norm: 1e-3,
377            tolerance: 1e-6,
378            algorithm: "neumann".to_string(),
379        };
380        
381        if let Some(RecoveryStrategy::SwitchAlgorithm(algo)) = error.recovery_strategy() {
382            assert_eq!(algo, "hybrid");
383        } else {
384            panic!("Expected SwitchAlgorithm recovery strategy");
385        }
386    }
387    
388    #[test]
389    fn test_error_severity() {
390        let memory_error = SolverError::MemoryAllocationError {
391            requested_size: 1000000,
392            available_memory: None,
393        };
394        assert_eq!(memory_error.severity(), ErrorSeverity::Critical);
395        
396        let convergence_error = SolverError::ConvergenceFailure {
397            iterations: 100,
398            residual_norm: 1e-3,
399            tolerance: 1e-6,
400            algorithm: "neumann".to_string(),
401        };
402        assert_eq!(convergence_error.severity(), ErrorSeverity::Medium);
403    }
404}