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 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" => RecoveryStrategy::SwitchAlgorithm("backward_push".to_string()),
196 "backward_push" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
197 _ => RecoveryStrategy::RelaxTolerance(10.0),
198 })
199 },
200 SolverError::NumericalInstability { .. } => {
201 Some(RecoveryStrategy::IncreasePrecision)
202 },
203 SolverError::UnsupportedMatrixFormat { required_format, .. } => {
204 Some(RecoveryStrategy::ConvertMatrixFormat(required_format.clone()))
205 },
206 SolverError::AlgorithmError { algorithm, .. } => {
207 Some(RecoveryStrategy::SwitchAlgorithm("neumann".to_string()))
208 },
209 _ => None,
210 }
211 }
212
213 pub fn severity(&self) -> ErrorSeverity {
215 match self {
216 SolverError::MemoryAllocationError { .. } => ErrorSeverity::Critical,
217 SolverError::InvalidSparseMatrix { .. } => ErrorSeverity::Critical,
218 SolverError::IndexOutOfBounds { .. } => ErrorSeverity::Critical,
219 SolverError::MatrixNotDiagonallyDominant { .. } => ErrorSeverity::High,
220 SolverError::ConvergenceFailure { .. } => ErrorSeverity::Medium,
221 SolverError::NumericalInstability { .. } => ErrorSeverity::Medium,
222 SolverError::Incoherent { .. } => ErrorSeverity::Low,
225 SolverError::InvalidInput { .. } => ErrorSeverity::Medium,
226 SolverError::DimensionMismatch { .. } => ErrorSeverity::Medium,
227 SolverError::UnsupportedMatrixFormat { .. } => ErrorSeverity::Low,
228 SolverError::AlgorithmError { .. } => ErrorSeverity::Medium,
229 #[cfg(feature = "wasm")]
230 SolverError::WasmBindingError { .. } => ErrorSeverity::High,
231 #[cfg(feature = "std")]
232 SolverError::IoError { .. } => ErrorSeverity::Medium,
233 #[cfg(feature = "serde")]
234 SolverError::SerializationError { .. } => ErrorSeverity::Low,
235 }
236 }
237}
238
239#[derive(Debug, Clone, PartialEq)]
241#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
242pub enum RecoveryStrategy {
243 SwitchAlgorithm(String),
245 IncreasePrecision,
247 RelaxTolerance(f64),
249 RestartWithDifferentSeed,
251 ConvertMatrixFormat(String),
253 IncreaseIterations(usize),
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
260pub enum ErrorSeverity {
261 Low,
263 Medium,
265 High,
267 Critical,
269}
270
271impl fmt::Display for SolverError {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 match self {
274 SolverError::MatrixNotDiagonallyDominant { row, diagonal, off_diagonal_sum } => {
275 write!(f, "Matrix is not diagonally dominant at row {}: diagonal = {:.6}, off-diagonal sum = {:.6}",
276 row, diagonal, off_diagonal_sum)
277 },
278 SolverError::NumericalInstability { reason, iteration, residual_norm } => {
279 write!(f, "Numerical instability at iteration {}: {} (residual = {:.2e})",
280 iteration, reason, residual_norm)
281 },
282 SolverError::ConvergenceFailure { iterations, residual_norm, tolerance, algorithm } => {
283 write!(f, "Algorithm '{}' failed to converge after {} iterations: residual = {:.2e} > tolerance = {:.2e}",
284 algorithm, iterations, residual_norm, tolerance)
285 },
286 SolverError::Incoherent { coherence, threshold } => {
287 write!(f,
288 "Coherence gate refused solve: matrix coherence = {:.6} < threshold = {:.6} \
289 (ADR-001 item #3 — set SolverOptions::coherence_threshold to 0.0 to disable)",
290 coherence, threshold,
291 )
292 },
293 SolverError::InvalidInput { message, parameter } => {
294 match parameter {
295 Some(param) => write!(f, "Invalid input for parameter '{}': {}", param, message),
296 None => write!(f, "Invalid input: {}", message),
297 }
298 },
299 SolverError::DimensionMismatch { expected, actual, operation } => {
300 write!(f, "Dimension mismatch in {}: expected {}, got {}", operation, expected, actual)
301 },
302 SolverError::UnsupportedMatrixFormat { current_format, required_format, operation } => {
303 write!(f, "Operation '{}' requires {} format, but matrix is in {} format",
304 operation, required_format, current_format)
305 },
306 SolverError::MemoryAllocationError { requested_size, available_memory } => {
307 match available_memory {
308 Some(available) => write!(f, "Memory allocation failed: requested {} bytes, {} available",
309 requested_size, available),
310 None => write!(f, "Memory allocation failed: requested {} bytes", requested_size),
311 }
312 },
313 SolverError::IndexOutOfBounds { index, max_index, context } => {
314 write!(f, "Index {} out of bounds in {}: maximum valid index is {}",
315 index, context, max_index)
316 },
317 SolverError::InvalidSparseMatrix { reason, position } => {
318 match position {
319 Some((row, col)) => write!(f, "Invalid sparse matrix at ({}, {}): {}", row, col, reason),
320 None => write!(f, "Invalid sparse matrix: {}", reason),
321 }
322 },
323 SolverError::AlgorithmError { algorithm, message, .. } => {
324 write!(f, "Algorithm '{}' error: {}", algorithm, message)
325 },
326 #[cfg(feature = "wasm")]
327 SolverError::WasmBindingError { message, js_error } => {
328 match js_error {
329 Some(js_err) => write!(f, "WASM binding error: {} (JS: {})", message, js_err),
330 None => write!(f, "WASM binding error: {}", message),
331 }
332 },
333 #[cfg(feature = "std")]
334 SolverError::IoError { message, context } => {
335 write!(f, "I/O error in {}: {}", context, message)
336 },
337 #[cfg(feature = "serde")]
338 SolverError::SerializationError { message, data_type } => {
339 write!(f, "Serialization error for {}: {}", data_type, message)
340 },
341 }
342 }
343}
344
345#[cfg(feature = "std")]
346impl std::error::Error for SolverError {
347 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
348 None
349 }
350}
351
352#[cfg(feature = "std")]
354impl From<std::io::Error> for SolverError {
355 fn from(err: std::io::Error) -> Self {
356 SolverError::IoError {
357 message: err.to_string(),
358 context: "File operation".to_string(),
359 }
360 }
361}
362
363#[cfg(feature = "wasm")]
365impl From<wasm_bindgen::JsValue> for SolverError {
366 fn from(err: wasm_bindgen::JsValue) -> Self {
367 let message = if let Some(string) = err.as_string() {
368 string
369 } else {
370 "Unknown JavaScript error".to_string()
371 };
372
373 SolverError::WasmBindingError {
374 message,
375 js_error: None,
376 }
377 }
378}
379
380#[cfg(all(test, feature = "std"))]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn test_error_recoverability() {
386 let convergence_error = SolverError::ConvergenceFailure {
387 iterations: 100,
388 residual_norm: 1e-3,
389 tolerance: 1e-6,
390 algorithm: "neumann".to_string(),
391 };
392 assert!(convergence_error.is_recoverable());
393
394 let dimension_error = SolverError::DimensionMismatch {
395 expected: 100,
396 actual: 50,
397 operation: "matrix_vector_multiply".to_string(),
398 };
399 assert!(!dimension_error.is_recoverable());
400 }
401
402 #[test]
403 fn test_recovery_strategies() {
404 let error = SolverError::ConvergenceFailure {
405 iterations: 100,
406 residual_norm: 1e-3,
407 tolerance: 1e-6,
408 algorithm: "neumann".to_string(),
409 };
410
411 if let Some(RecoveryStrategy::SwitchAlgorithm(algo)) = error.recovery_strategy() {
412 assert_eq!(algo, "hybrid");
413 } else {
414 panic!("Expected SwitchAlgorithm recovery strategy");
415 }
416 }
417
418 #[test]
419 fn test_error_severity() {
420 let memory_error = SolverError::MemoryAllocationError {
421 requested_size: 1000000,
422 available_memory: None,
423 };
424 assert_eq!(memory_error.severity(), ErrorSeverity::Critical);
425
426 let convergence_error = SolverError::ConvergenceFailure {
427 iterations: 100,
428 residual_norm: 1e-3,
429 tolerance: 1e-6,
430 algorithm: "neumann".to_string(),
431 };
432 assert_eq!(convergence_error.severity(), ErrorSeverity::Medium);
433 }
434}