1use core::fmt;
8use alloc::{string::String, vec::Vec};
9
10pub type Result<T> = core::result::Result<T, SolverError>;
12
13#[derive(Debug, Clone, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum SolverError {
17 MatrixNotDiagonallyDominant {
19 row: usize,
21 diagonal: f64,
23 off_diagonal_sum: f64,
25 },
26
27 NumericalInstability {
29 reason: String,
31 iteration: usize,
33 residual_norm: f64,
35 },
36
37 ConvergenceFailure {
39 iterations: usize,
41 residual_norm: f64,
43 tolerance: f64,
45 algorithm: String,
47 },
48
49 InvalidInput {
51 message: String,
53 parameter: Option<String>,
55 },
56
57 DimensionMismatch {
59 expected: usize,
61 actual: usize,
63 operation: String,
65 },
66
67 UnsupportedMatrixFormat {
69 current_format: String,
71 required_format: String,
73 operation: String,
75 },
76
77 MemoryAllocationError {
79 requested_size: usize,
81 available_memory: Option<usize>,
83 },
84
85 IndexOutOfBounds {
87 index: usize,
89 max_index: usize,
91 context: String,
93 },
94
95 InvalidSparseMatrix {
97 reason: String,
99 position: Option<(usize, usize)>,
101 },
102
103 AlgorithmError {
105 algorithm: String,
107 message: String,
109 context: Vec<(String, String)>,
111 },
112
113 #[cfg(feature = "wasm")]
115 WasmBindingError {
116 message: String,
118 js_error: Option<String>,
120 },
121
122 #[cfg(feature = "std")]
124 IoError {
125 #[cfg_attr(feature = "serde", serde(skip))]
127 message: String,
128 context: String,
130 },
131
132 #[cfg(feature = "serde")]
134 SerializationError {
135 message: String,
137 data_type: String,
139 },
140}
141
142impl SolverError {
143 pub fn is_recoverable(&self) -> bool {
148 match self {
149 SolverError::ConvergenceFailure { .. } => true,
150 SolverError::NumericalInstability { .. } => true,
151 SolverError::MatrixNotDiagonallyDominant { .. } => false, SolverError::InvalidInput { .. } => false, SolverError::DimensionMismatch { .. } => false, SolverError::MemoryAllocationError { .. } => false, SolverError::IndexOutOfBounds { .. } => false, SolverError::InvalidSparseMatrix { .. } => false, SolverError::UnsupportedMatrixFormat { .. } => true, SolverError::AlgorithmError { .. } => true, #[cfg(feature = "wasm")]
160 SolverError::WasmBindingError { .. } => false, #[cfg(feature = "std")]
162 SolverError::IoError { .. } => false, #[cfg(feature = "serde")]
164 SolverError::SerializationError { .. } => false, }
166 }
167
168 pub fn recovery_strategy(&self) -> Option<RecoveryStrategy> {
170 match self {
171 SolverError::ConvergenceFailure { algorithm, .. } => {
172 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 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#[derive(Debug, Clone, PartialEq)]
218#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219pub enum RecoveryStrategy {
220 SwitchAlgorithm(String),
222 IncreasePrecision,
224 RelaxTolerance(f64),
226 RestartWithDifferentSeed,
228 ConvertMatrixFormat(String),
230 IncreaseIterations(usize),
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
236#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
237pub enum ErrorSeverity {
238 Low,
240 Medium,
242 High,
244 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#[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#[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}