rivrs_sparse/error.rs
1//! Error types for the sparse solver pipeline.
2//!
3//! Provides [`SparseError`], the unified error enum for all phases of the
4//! sparse solver (analysis, factorization, solve, and I/O). Implements
5//! [`std::error::Error`] and [`Display`](std::fmt::Display) for integration
6//! with standard Rust error handling.
7//!
8//! `PartialEq` is intentionally not derived because the `NumericalSingularity`
9//! variant contains `f64`, where `NaN != NaN` would cause subtle comparison
10//! bugs. Use `matches!()` for pattern-matching assertions in tests.
11
12use std::fmt;
13
14/// Errors that can occur during sparse solver operations.
15///
16/// Note: `PartialEq` is intentionally not derived because the `NumericalSingularity`
17/// variant contains `f64`, where `NaN != NaN` would cause subtle comparison bugs.
18/// Use `matches!()` for pattern-matching assertions in tests.
19#[derive(Debug, Clone)]
20pub enum SparseError {
21 /// Matrix dimensions are incompatible.
22 DimensionMismatch {
23 /// Expected dimensions (rows, cols).
24 expected: (usize, usize),
25 /// Actual dimensions (rows, cols).
26 got: (usize, usize),
27 /// Description of where the mismatch occurred.
28 context: String,
29 },
30
31 /// A matrix that should be square is not.
32 NotSquare {
33 /// Actual dimensions (rows, cols).
34 dims: (usize, usize),
35 },
36
37 /// Input contains NaN, Inf, or other invalid floating-point values.
38 InvalidInput {
39 /// Description of the invalid input.
40 reason: String,
41 },
42
43 /// The matrix is structurally singular (symbolic analysis detected zero diagonal).
44 StructurallySingular {
45 /// Zero-diagonal column index.
46 column: usize,
47 },
48
49 /// Numeric factorization encountered a zero or near-zero pivot.
50 NumericalSingularity {
51 /// Index of the singular pivot.
52 pivot_index: usize,
53 /// The near-zero pivot value.
54 value: f64,
55 },
56
57 /// An ordering or analysis algorithm failed.
58 AnalysisFailure {
59 /// Description of the failure.
60 reason: String,
61 },
62
63 /// An IO operation failed (file not found, permission denied, etc.).
64 IoError {
65 /// Error description.
66 source: String,
67 /// File path that caused the error.
68 path: String,
69 },
70
71 /// A file could not be parsed (malformed Matrix Market, invalid JSON, etc.).
72 ParseError {
73 /// Description of the parse failure.
74 reason: String,
75 /// File path that could not be parsed.
76 path: String,
77 /// Line number where parsing failed, if available.
78 line: Option<usize>,
79 },
80
81 /// A named matrix was not found in the registry.
82 MatrixNotFound {
83 /// Name that was looked up.
84 name: String,
85 },
86
87 /// Solve was called before factor() completed.
88 SolveBeforeFactor {
89 /// Description of the premature solve attempt.
90 context: String,
91 },
92}
93
94impl fmt::Display for SparseError {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self {
97 Self::DimensionMismatch {
98 expected,
99 got,
100 context,
101 } => {
102 write!(
103 f,
104 "Dimension mismatch: expected {:?}, got {:?}. {}",
105 expected, got, context
106 )
107 }
108 Self::NotSquare { dims } => {
109 write!(
110 f,
111 "Matrix must be square, but has dimensions {}x{}",
112 dims.0, dims.1
113 )
114 }
115 Self::InvalidInput { reason } => {
116 write!(f, "Invalid input: {}", reason)
117 }
118 Self::StructurallySingular { column } => {
119 write!(f, "Matrix is structurally singular at column {}", column)
120 }
121 Self::NumericalSingularity { pivot_index, value } => {
122 write!(
123 f,
124 "Numerical singularity at pivot index {} (value: {:.2e})",
125 pivot_index, value
126 )
127 }
128 Self::AnalysisFailure { reason } => {
129 write!(f, "Symbolic analysis failed: {}", reason)
130 }
131 Self::IoError { source, path } => {
132 write!(f, "IO error for '{}': {}", path, source)
133 }
134 Self::ParseError { reason, path, line } => {
135 if let Some(line) = line {
136 write!(f, "Parse error in '{}' at line {}: {}", path, line, reason)
137 } else {
138 write!(f, "Parse error in '{}': {}", path, reason)
139 }
140 }
141 Self::MatrixNotFound { name } => {
142 write!(f, "Matrix '{}' not found in registry", name)
143 }
144 Self::SolveBeforeFactor { context } => {
145 write!(f, "Solve called before factor(): {}", context)
146 }
147 }
148 }
149}
150
151impl std::error::Error for SparseError {}
152
153// Note: blanket `From<std::io::Error>` and `From<serde_json::Error>` impls were
154// intentionally removed. They produced errors with empty `path` fields, losing
155// context. All call sites should construct `SparseError::IoError` or
156// `SparseError::ParseError` explicitly with the relevant file path.