wifi-densepose/vendor/sublinear-time-solver/src/error.rs

404 lines
15 KiB
Rust

//! Error types and handling for the sublinear solver.
//!
//! This module defines all error conditions that can occur during matrix operations
//! and solver execution, providing detailed error information for debugging and
//! recovery strategies.
use core::fmt;
use alloc::{string::String, vec::Vec};
/// Result type alias for solver operations.
pub type Result<T> = core::result::Result<T, SolverError>;
/// Comprehensive error type for all solver operations.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SolverError {
/// Matrix is not diagonally dominant, which is required for convergence guarantees.
MatrixNotDiagonallyDominant {
/// The row where diagonal dominance fails
row: usize,
/// Diagonal element value
diagonal: f64,
/// Sum of off-diagonal absolute values
off_diagonal_sum: f64,
},
/// Numerical instability detected during computation.
NumericalInstability {
/// Description of the instability
reason: String,
/// Iteration where instability was detected
iteration: usize,
/// Current residual norm when instability occurred
residual_norm: f64,
},
/// Algorithm failed to converge within specified iterations.
ConvergenceFailure {
/// Number of iterations performed
iterations: usize,
/// Final residual norm achieved
residual_norm: f64,
/// Target tolerance that wasn't reached
tolerance: f64,
/// Algorithm that failed to converge
algorithm: String,
},
/// Invalid input parameters or data.
InvalidInput {
/// Description of the invalid input
message: String,
/// Optional parameter name that was invalid
parameter: Option<String>,
},
/// Dimension mismatch between matrix and vector operations.
DimensionMismatch {
/// Expected dimension
expected: usize,
/// Actual dimension found
actual: usize,
/// Context where mismatch occurred
operation: String,
},
/// Matrix format is not supported for the requested operation.
UnsupportedMatrixFormat {
/// Current matrix format
current_format: String,
/// Required format for the operation
required_format: String,
/// Operation that was attempted
operation: String,
},
/// Memory allocation failure.
MemoryAllocationError {
/// Requested allocation size in bytes
requested_size: usize,
/// Available memory at time of failure (if known)
available_memory: Option<usize>,
},
/// Index out of bounds for matrix or vector access.
IndexOutOfBounds {
/// The invalid index
index: usize,
/// Maximum valid index
max_index: usize,
/// Context where out-of-bounds access occurred
context: String,
},
/// Sparse matrix contains invalid data.
InvalidSparseMatrix {
/// Description of the invalid data
reason: String,
/// Position where invalid data was found
position: Option<(usize, usize)>,
},
/// Algorithm-specific error conditions.
AlgorithmError {
/// Name of the algorithm
algorithm: String,
/// Specific error message
message: String,
/// Additional context data
context: Vec<(String, String)>,
},
/// WebAssembly binding error (when WASM feature is enabled).
#[cfg(feature = "wasm")]
WasmBindingError {
/// Error message from WASM binding
message: String,
/// JavaScript error if available
js_error: Option<String>,
},
/// I/O error for file operations (when std feature is enabled).
#[cfg(feature = "std")]
IoError {
/// I/O error description
#[cfg_attr(feature = "serde", serde(skip))]
message: String,
/// Context where I/O error occurred
context: String,
},
/// Serialization/deserialization error.
#[cfg(feature = "serde")]
SerializationError {
/// Error message from serialization
message: String,
/// Data type being serialized
data_type: String,
},
}
impl SolverError {
/// Check if this error indicates a recoverable condition.
///
/// Recoverable errors can potentially be resolved by adjusting
/// algorithm parameters or switching to a different solver.
pub fn is_recoverable(&self) -> bool {
match self {
SolverError::ConvergenceFailure { .. } => true,
SolverError::NumericalInstability { .. } => true,
SolverError::MatrixNotDiagonallyDominant { .. } => false, // Fundamental issue
SolverError::InvalidInput { .. } => false, // User error
SolverError::DimensionMismatch { .. } => false, // User error
SolverError::MemoryAllocationError { .. } => false, // System limitation
SolverError::IndexOutOfBounds { .. } => false, // Programming error
SolverError::InvalidSparseMatrix { .. } => false, // Data corruption
SolverError::UnsupportedMatrixFormat { .. } => true, // Can convert format
SolverError::AlgorithmError { .. } => true, // Algorithm-specific, might recover
#[cfg(feature = "wasm")]
SolverError::WasmBindingError { .. } => false, // Runtime environment issue
#[cfg(feature = "std")]
SolverError::IoError { .. } => false, // External system issue
#[cfg(feature = "serde")]
SolverError::SerializationError { .. } => false, // Data format issue
}
}
/// Get suggested recovery strategy for recoverable errors.
pub fn recovery_strategy(&self) -> Option<RecoveryStrategy> {
match self {
SolverError::ConvergenceFailure { algorithm, .. } => {
// Suggest alternative algorithms
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,
}
}
/// Get the error severity level.
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,
}
}
}
/// Recovery strategies for recoverable errors.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RecoveryStrategy {
/// Switch to a different solver algorithm.
SwitchAlgorithm(String),
/// Increase numerical precision (f32 -> f64).
IncreasePrecision,
/// Relax convergence tolerance by the given factor.
RelaxTolerance(f64),
/// Restart with different random seed.
RestartWithDifferentSeed,
/// Convert matrix to a different storage format.
ConvertMatrixFormat(String),
/// Increase maximum iteration count.
IncreaseIterations(usize),
}
/// Error severity levels for logging and monitoring.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ErrorSeverity {
/// Low severity - algorithm can continue with degraded performance
Low,
/// Medium severity - operation failed but system remains stable
Medium,
/// High severity - significant failure requiring user intervention
High,
/// Critical severity - system integrity compromised
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
}
}
// Conversion from standard library errors
#[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(),
}
}
}
// Conversion for WASM environments
#[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);
}
}