1use alloc::{string::String, vec::Vec};
8use core::fmt;
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 Incoherent {
43 coherence: f64,
47 threshold: f64,
50 },
51
52 ConvergenceFailure {
54 iterations: usize,
56 residual_norm: f64,
58 tolerance: f64,
60 algorithm: String,
62 },
63
64 InvalidInput {
66 message: String,
68 parameter: Option<String>,
70 },
71
72 DimensionMismatch {
74 expected: usize,
76 actual: usize,
78 operation: String,
80 },
81
82 UnsupportedMatrixFormat {
84 current_format: String,
86 required_format: String,
88 operation: String,
90 },
91
92 MemoryAllocationError {
94 requested_size: usize,
96 available_memory: Option<usize>,
98 },
99
100 IndexOutOfBounds {
102 index: usize,
104 max_index: usize,
106 context: String,
108 },
109
110 InvalidSparseMatrix {
112 reason: String,
114 position: Option<(usize, usize)>,
116 },
117
118 AlgorithmError {
120 algorithm: String,
122 message: String,
124 context: Vec<(String, String)>,
126 },
127
128 #[cfg(feature = "wasm")]
130 WasmBindingError {
131 message: String,
133 js_error: Option<String>,
135 },
136
137 #[cfg(feature = "std")]
139 IoError {
140 #[cfg_attr(feature = "serde", serde(skip))]
142 message: String,
143 context: String,
145 },
146
147 #[cfg(feature = "serde")]
149 SerializationError {
150 message: String,
152 data_type: String,
154 },
155}
156
157impl SolverError {
158 pub fn is_recoverable(&self) -> bool {
163 match self {
164 SolverError::ConvergenceFailure { .. } => true,
165 SolverError::NumericalInstability { .. } => true,
166 SolverError::Incoherent { .. } => true,
171 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")]
180 SolverError::WasmBindingError { .. } => false, #[cfg(feature = "std")]
182 SolverError::IoError { .. } => false, #[cfg(feature = "serde")]
184 SolverError::SerializationError { .. } => false, }
186 }
187
188 pub fn recovery_strategy(&self) -> Option<RecoveryStrategy> {
190 match self {
191 SolverError::ConvergenceFailure { algorithm, .. } => {
192 Some(match algorithm.as_str() {
194 "neumann" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
195 "forward_push" => {
196 RecoveryStrategy::SwitchAlgorithm("backward_push".to_string())
197 }
198 "backward_push" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
199 _ => RecoveryStrategy::RelaxTolerance(10.0),
200 })
201 }
202 SolverError::NumericalInstability { .. } => Some(RecoveryStrategy::IncreasePrecision),
203 SolverError::UnsupportedMatrixFormat {
204 required_format, ..
205 } => Some(RecoveryStrategy::ConvertMatrixFormat(
206 required_format.clone(),
207 )),
208 SolverError::AlgorithmError { algorithm, .. } => {
209 Some(RecoveryStrategy::SwitchAlgorithm("neumann".to_string()))
210 }
211 _ => None,
212 }
213 }
214
215 pub fn severity(&self) -> ErrorSeverity {
217 match self {
218 SolverError::MemoryAllocationError { .. } => ErrorSeverity::Critical,
219 SolverError::InvalidSparseMatrix { .. } => ErrorSeverity::Critical,
220 SolverError::IndexOutOfBounds { .. } => ErrorSeverity::Critical,
221 SolverError::MatrixNotDiagonallyDominant { .. } => ErrorSeverity::High,
222 SolverError::ConvergenceFailure { .. } => ErrorSeverity::Medium,
223 SolverError::NumericalInstability { .. } => ErrorSeverity::Medium,
224 SolverError::Incoherent { .. } => ErrorSeverity::Low,
227 SolverError::InvalidInput { .. } => ErrorSeverity::Medium,
228 SolverError::DimensionMismatch { .. } => ErrorSeverity::Medium,
229 SolverError::UnsupportedMatrixFormat { .. } => ErrorSeverity::Low,
230 SolverError::AlgorithmError { .. } => ErrorSeverity::Medium,
231 #[cfg(feature = "wasm")]
232 SolverError::WasmBindingError { .. } => ErrorSeverity::High,
233 #[cfg(feature = "std")]
234 SolverError::IoError { .. } => ErrorSeverity::Medium,
235 #[cfg(feature = "serde")]
236 SolverError::SerializationError { .. } => ErrorSeverity::Low,
237 }
238 }
239}
240
241#[derive(Debug, Clone, PartialEq)]
243#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
244pub enum RecoveryStrategy {
245 SwitchAlgorithm(String),
247 IncreasePrecision,
249 RelaxTolerance(f64),
251 RestartWithDifferentSeed,
253 ConvertMatrixFormat(String),
255 IncreaseIterations(usize),
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
261#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
262pub enum ErrorSeverity {
263 Low,
265 Medium,
267 High,
269 Critical,
271}
272
273impl fmt::Display for SolverError {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 match self {
276 SolverError::MatrixNotDiagonallyDominant {
277 row,
278 diagonal,
279 off_diagonal_sum,
280 } => {
281 write!(f, "Matrix is not diagonally dominant at row {}: diagonal = {:.6}, off-diagonal sum = {:.6}",
282 row, diagonal, off_diagonal_sum)
283 }
284 SolverError::NumericalInstability {
285 reason,
286 iteration,
287 residual_norm,
288 } => {
289 write!(
290 f,
291 "Numerical instability at iteration {}: {} (residual = {:.2e})",
292 iteration, reason, residual_norm
293 )
294 }
295 SolverError::ConvergenceFailure {
296 iterations,
297 residual_norm,
298 tolerance,
299 algorithm,
300 } => {
301 write!(f, "Algorithm '{}' failed to converge after {} iterations: residual = {:.2e} > tolerance = {:.2e}",
302 algorithm, iterations, residual_norm, tolerance)
303 }
304 SolverError::Incoherent {
305 coherence,
306 threshold,
307 } => {
308 write!(
309 f,
310 "Coherence gate refused solve: matrix coherence = {:.6} < threshold = {:.6} \
311 (ADR-001 item #3 — set SolverOptions::coherence_threshold to 0.0 to disable)",
312 coherence, threshold,
313 )
314 }
315 SolverError::InvalidInput { message, parameter } => match parameter {
316 Some(param) => write!(f, "Invalid input for parameter '{}': {}", param, message),
317 None => write!(f, "Invalid input: {}", message),
318 },
319 SolverError::DimensionMismatch {
320 expected,
321 actual,
322 operation,
323 } => {
324 write!(
325 f,
326 "Dimension mismatch in {}: expected {}, got {}",
327 operation, expected, actual
328 )
329 }
330 SolverError::UnsupportedMatrixFormat {
331 current_format,
332 required_format,
333 operation,
334 } => {
335 write!(
336 f,
337 "Operation '{}' requires {} format, but matrix is in {} format",
338 operation, required_format, current_format
339 )
340 }
341 SolverError::MemoryAllocationError {
342 requested_size,
343 available_memory,
344 } => match available_memory {
345 Some(available) => write!(
346 f,
347 "Memory allocation failed: requested {} bytes, {} available",
348 requested_size, available
349 ),
350 None => write!(
351 f,
352 "Memory allocation failed: requested {} bytes",
353 requested_size
354 ),
355 },
356 SolverError::IndexOutOfBounds {
357 index,
358 max_index,
359 context,
360 } => {
361 write!(
362 f,
363 "Index {} out of bounds in {}: maximum valid index is {}",
364 index, context, max_index
365 )
366 }
367 SolverError::InvalidSparseMatrix { reason, position } => match position {
368 Some((row, col)) => {
369 write!(f, "Invalid sparse matrix at ({}, {}): {}", row, col, reason)
370 }
371 None => write!(f, "Invalid sparse matrix: {}", reason),
372 },
373 SolverError::AlgorithmError {
374 algorithm, message, ..
375 } => {
376 write!(f, "Algorithm '{}' error: {}", algorithm, message)
377 }
378 #[cfg(feature = "wasm")]
379 SolverError::WasmBindingError { message, js_error } => match js_error {
380 Some(js_err) => write!(f, "WASM binding error: {} (JS: {})", message, js_err),
381 None => write!(f, "WASM binding error: {}", message),
382 },
383 #[cfg(feature = "std")]
384 SolverError::IoError { message, context } => {
385 write!(f, "I/O error in {}: {}", context, message)
386 }
387 #[cfg(feature = "serde")]
388 SolverError::SerializationError { message, data_type } => {
389 write!(f, "Serialization error for {}: {}", data_type, message)
390 }
391 }
392 }
393}
394
395#[cfg(feature = "std")]
396impl std::error::Error for SolverError {
397 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
398 None
399 }
400}
401
402#[cfg(feature = "std")]
404impl From<std::io::Error> for SolverError {
405 fn from(err: std::io::Error) -> Self {
406 SolverError::IoError {
407 message: err.to_string(),
408 context: "File operation".to_string(),
409 }
410 }
411}
412
413#[cfg(feature = "wasm")]
415impl From<wasm_bindgen::JsValue> for SolverError {
416 fn from(err: wasm_bindgen::JsValue) -> Self {
417 let message = if let Some(string) = err.as_string() {
418 string
419 } else {
420 "Unknown JavaScript error".to_string()
421 };
422
423 SolverError::WasmBindingError {
424 message,
425 js_error: None,
426 }
427 }
428}
429
430#[cfg(all(test, feature = "std"))]
431mod tests {
432 use super::*;
433
434 #[test]
435 fn test_error_recoverability() {
436 let convergence_error = SolverError::ConvergenceFailure {
437 iterations: 100,
438 residual_norm: 1e-3,
439 tolerance: 1e-6,
440 algorithm: "neumann".to_string(),
441 };
442 assert!(convergence_error.is_recoverable());
443
444 let dimension_error = SolverError::DimensionMismatch {
445 expected: 100,
446 actual: 50,
447 operation: "matrix_vector_multiply".to_string(),
448 };
449 assert!(!dimension_error.is_recoverable());
450 }
451
452 #[test]
453 fn test_recovery_strategies() {
454 let error = SolverError::ConvergenceFailure {
455 iterations: 100,
456 residual_norm: 1e-3,
457 tolerance: 1e-6,
458 algorithm: "neumann".to_string(),
459 };
460
461 if let Some(RecoveryStrategy::SwitchAlgorithm(algo)) = error.recovery_strategy() {
462 assert_eq!(algo, "hybrid");
463 } else {
464 panic!("Expected SwitchAlgorithm recovery strategy");
465 }
466 }
467
468 #[test]
469 fn test_error_severity() {
470 let memory_error = SolverError::MemoryAllocationError {
471 requested_size: 1000000,
472 available_memory: None,
473 };
474 assert_eq!(memory_error.severity(), ErrorSeverity::Critical);
475
476 let convergence_error = SolverError::ConvergenceFailure {
477 iterations: 100,
478 residual_norm: 1e-3,
479 tolerance: 1e-6,
480 algorithm: "neumann".to_string(),
481 };
482 assert_eq!(convergence_error.severity(), ErrorSeverity::Medium);
483 }
484}