use core::fmt;
use alloc::{string::String, vec::Vec};
pub type Result<T> = core::result::Result<T, SolverError>;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SolverError {
MatrixNotDiagonallyDominant {
row: usize,
diagonal: f64,
off_diagonal_sum: f64,
},
NumericalInstability {
reason: String,
iteration: usize,
residual_norm: f64,
},
ConvergenceFailure {
iterations: usize,
residual_norm: f64,
tolerance: f64,
algorithm: String,
},
InvalidInput {
message: String,
parameter: Option<String>,
},
DimensionMismatch {
expected: usize,
actual: usize,
operation: String,
},
UnsupportedMatrixFormat {
current_format: String,
required_format: String,
operation: String,
},
MemoryAllocationError {
requested_size: usize,
available_memory: Option<usize>,
},
IndexOutOfBounds {
index: usize,
max_index: usize,
context: String,
},
InvalidSparseMatrix {
reason: String,
position: Option<(usize, usize)>,
},
AlgorithmError {
algorithm: String,
message: String,
context: Vec<(String, String)>,
},
#[cfg(feature = "wasm")]
WasmBindingError {
message: String,
js_error: Option<String>,
},
#[cfg(feature = "std")]
IoError {
#[cfg_attr(feature = "serde", serde(skip))]
message: String,
context: String,
},
#[cfg(feature = "serde")]
SerializationError {
message: String,
data_type: String,
},
}
impl SolverError {
pub fn is_recoverable(&self) -> bool {
match self {
SolverError::ConvergenceFailure { .. } => true,
SolverError::NumericalInstability { .. } => true,
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")]
SolverError::WasmBindingError { .. } => false, #[cfg(feature = "std")]
SolverError::IoError { .. } => false, #[cfg(feature = "serde")]
SolverError::SerializationError { .. } => false, }
}
pub fn recovery_strategy(&self) -> Option<RecoveryStrategy> {
match self {
SolverError::ConvergenceFailure { algorithm, .. } => {
Some(match algorithm.as_str() {
"neumann" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
"forward_push" => RecoveryStrategy::SwitchAlgorithm("backward_push".to_string()),
"backward_push" => RecoveryStrategy::SwitchAlgorithm("hybrid".to_string()),
_ => RecoveryStrategy::RelaxTolerance(10.0),
})
},
SolverError::NumericalInstability { .. } => {
Some(RecoveryStrategy::IncreasePrecision)
},
SolverError::UnsupportedMatrixFormat { required_format, .. } => {
Some(RecoveryStrategy::ConvertMatrixFormat(required_format.clone()))
},
SolverError::AlgorithmError { algorithm, .. } => {
Some(RecoveryStrategy::SwitchAlgorithm("neumann".to_string()))
},
_ => None,
}
}
pub fn severity(&self) -> ErrorSeverity {
match self {
SolverError::MemoryAllocationError { .. } => ErrorSeverity::Critical,
SolverError::InvalidSparseMatrix { .. } => ErrorSeverity::Critical,
SolverError::IndexOutOfBounds { .. } => ErrorSeverity::Critical,
SolverError::MatrixNotDiagonallyDominant { .. } => ErrorSeverity::High,
SolverError::ConvergenceFailure { .. } => ErrorSeverity::Medium,
SolverError::NumericalInstability { .. } => ErrorSeverity::Medium,
SolverError::InvalidInput { .. } => ErrorSeverity::Medium,
SolverError::DimensionMismatch { .. } => ErrorSeverity::Medium,
SolverError::UnsupportedMatrixFormat { .. } => ErrorSeverity::Low,
SolverError::AlgorithmError { .. } => ErrorSeverity::Medium,
#[cfg(feature = "wasm")]
SolverError::WasmBindingError { .. } => ErrorSeverity::High,
#[cfg(feature = "std")]
SolverError::IoError { .. } => ErrorSeverity::Medium,
#[cfg(feature = "serde")]
SolverError::SerializationError { .. } => ErrorSeverity::Low,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RecoveryStrategy {
SwitchAlgorithm(String),
IncreasePrecision,
RelaxTolerance(f64),
RestartWithDifferentSeed,
ConvertMatrixFormat(String),
IncreaseIterations(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ErrorSeverity {
Low,
Medium,
High,
Critical,
}
impl fmt::Display for SolverError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SolverError::MatrixNotDiagonallyDominant { row, diagonal, off_diagonal_sum } => {
write!(f, "Matrix is not diagonally dominant at row {}: diagonal = {:.6}, off-diagonal sum = {:.6}",
row, diagonal, off_diagonal_sum)
},
SolverError::NumericalInstability { reason, iteration, residual_norm } => {
write!(f, "Numerical instability at iteration {}: {} (residual = {:.2e})",
iteration, reason, residual_norm)
},
SolverError::ConvergenceFailure { iterations, residual_norm, tolerance, algorithm } => {
write!(f, "Algorithm '{}' failed to converge after {} iterations: residual = {:.2e} > tolerance = {:.2e}",
algorithm, iterations, residual_norm, tolerance)
},
SolverError::InvalidInput { message, parameter } => {
match parameter {
Some(param) => write!(f, "Invalid input for parameter '{}': {}", param, message),
None => write!(f, "Invalid input: {}", message),
}
},
SolverError::DimensionMismatch { expected, actual, operation } => {
write!(f, "Dimension mismatch in {}: expected {}, got {}", operation, expected, actual)
},
SolverError::UnsupportedMatrixFormat { current_format, required_format, operation } => {
write!(f, "Operation '{}' requires {} format, but matrix is in {} format",
operation, required_format, current_format)
},
SolverError::MemoryAllocationError { requested_size, available_memory } => {
match available_memory {
Some(available) => write!(f, "Memory allocation failed: requested {} bytes, {} available",
requested_size, available),
None => write!(f, "Memory allocation failed: requested {} bytes", requested_size),
}
},
SolverError::IndexOutOfBounds { index, max_index, context } => {
write!(f, "Index {} out of bounds in {}: maximum valid index is {}",
index, context, max_index)
},
SolverError::InvalidSparseMatrix { reason, position } => {
match position {
Some((row, col)) => write!(f, "Invalid sparse matrix at ({}, {}): {}", row, col, reason),
None => write!(f, "Invalid sparse matrix: {}", reason),
}
},
SolverError::AlgorithmError { algorithm, message, .. } => {
write!(f, "Algorithm '{}' error: {}", algorithm, message)
},
#[cfg(feature = "wasm")]
SolverError::WasmBindingError { message, js_error } => {
match js_error {
Some(js_err) => write!(f, "WASM binding error: {} (JS: {})", message, js_err),
None => write!(f, "WASM binding error: {}", message),
}
},
#[cfg(feature = "std")]
SolverError::IoError { message, context } => {
write!(f, "I/O error in {}: {}", context, message)
},
#[cfg(feature = "serde")]
SolverError::SerializationError { message, data_type } => {
write!(f, "Serialization error for {}: {}", data_type, message)
},
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SolverError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for SolverError {
fn from(err: std::io::Error) -> Self {
SolverError::IoError {
message: err.to_string(),
context: "File operation".to_string(),
}
}
}
#[cfg(feature = "wasm")]
impl From<wasm_bindgen::JsValue> for SolverError {
fn from(err: wasm_bindgen::JsValue) -> Self {
let message = if let Some(string) = err.as_string() {
string
} else {
"Unknown JavaScript error".to_string()
};
SolverError::WasmBindingError {
message,
js_error: None,
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn test_error_recoverability() {
let convergence_error = SolverError::ConvergenceFailure {
iterations: 100,
residual_norm: 1e-3,
tolerance: 1e-6,
algorithm: "neumann".to_string(),
};
assert!(convergence_error.is_recoverable());
let dimension_error = SolverError::DimensionMismatch {
expected: 100,
actual: 50,
operation: "matrix_vector_multiply".to_string(),
};
assert!(!dimension_error.is_recoverable());
}
#[test]
fn test_recovery_strategies() {
let error = SolverError::ConvergenceFailure {
iterations: 100,
residual_norm: 1e-3,
tolerance: 1e-6,
algorithm: "neumann".to_string(),
};
if let Some(RecoveryStrategy::SwitchAlgorithm(algo)) = error.recovery_strategy() {
assert_eq!(algo, "hybrid");
} else {
panic!("Expected SwitchAlgorithm recovery strategy");
}
}
#[test]
fn test_error_severity() {
let memory_error = SolverError::MemoryAllocationError {
requested_size: 1000000,
available_memory: None,
};
assert_eq!(memory_error.severity(), ErrorSeverity::Critical);
let convergence_error = SolverError::ConvergenceFailure {
iterations: 100,
residual_norm: 1e-3,
tolerance: 1e-6,
algorithm: "neumann".to_string(),
};
assert_eq!(convergence_error.severity(), ErrorSeverity::Medium);
}
}