sublinear 0.3.3

High-performance sublinear-time solver for asymmetric diagonally dominant systems
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
//! 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 alloc::{string::String, vec::Vec};
use core::fmt;

/// 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,
    },

    /// Coherence gate refused the solve: the matrix's diagonal-dominance
    /// margin dropped below the configured threshold, so the solver would
    /// have spent polynomial-time work on a near-singular system to
    /// produce an ε-quality answer. See ADR-001 (Complexity as Architecture)
    /// roadmap item #3 and `src/coherence.rs` for the gate implementation.
    Incoherent {
        /// Computed coherence score in [-∞, 1]: 1.0 = perfectly diagonal,
        /// > 0 = strictly diagonally dominant, ≤ 0 = at or past the
        /// diagonal-dominance boundary.
        coherence: f64,
        /// Threshold the caller configured via
        /// `SolverOptions::coherence_threshold`.
        threshold: 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,
            // Incoherent is recoverable in the same sense ConvergenceFailure
            // is: the caller can lower the coherence_threshold, fall back to
            // a cached answer, or refuse the solve. The matrix itself is not
            // broken — it's just below the budget the caller asked for.
            SolverError::Incoherent { .. } => 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,
            // Incoherent is a *budget* refusal, not a data corruption — Low.
            // The caller asked us to refuse, we refused; nothing's broken.
            SolverError::Incoherent { .. } => ErrorSeverity::Low,
            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::Incoherent {
                coherence,
                threshold,
            } => {
                write!(
                    f,
                    "Coherence gate refused solve: matrix coherence = {:.6} < threshold = {:.6} \
                     (ADR-001 item #3 — set SolverOptions::coherence_threshold to 0.0 to disable)",
                    coherence, threshold,
                )
            }
            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);
    }
}