Skip to main content

oxicuda_solver/
error.rs

1//! Error types for OxiCUDA Solver operations.
2//!
3//! Provides [`SolverError`] covering all failure modes for GPU-accelerated
4//! matrix decompositions and linear solvers — singular matrices, convergence
5//! failures, workspace issues, and underlying CUDA/BLAS/PTX errors.
6
7use oxicuda_blas::BlasError;
8use oxicuda_driver::CudaError;
9use oxicuda_ptx::PtxGenError;
10use thiserror::Error;
11
12/// Solver-specific error type.
13///
14/// Every fallible solver operation returns [`SolverResult<T>`] which uses this
15/// enum as its error variant. The variants are ordered by failure mode:
16/// upstream errors first, then solver-specific conditions.
17#[derive(Debug, Error)]
18pub enum SolverError {
19    /// A CUDA driver call failed.
20    #[error("CUDA driver error: {0}")]
21    Cuda(#[from] CudaError),
22
23    /// A BLAS operation failed.
24    #[error("BLAS error: {0}")]
25    Blas(#[from] BlasError),
26
27    /// PTX kernel source generation failed.
28    #[error("PTX generation error: {0}")]
29    PtxGeneration(#[from] PtxGenError),
30
31    /// The matrix is singular (pivot is exactly zero or numerically zero).
32    #[error("singular matrix detected")]
33    SingularMatrix,
34
35    /// The matrix is not positive definite (Cholesky decomposition failed).
36    #[error("matrix is not positive definite")]
37    NotPositiveDefinite,
38
39    /// Operand dimensions are incompatible.
40    #[error("dimension mismatch: {0}")]
41    DimensionMismatch(String),
42
43    /// An iterative solver failed to converge within the allowed iterations.
44    #[error("convergence failure after {iterations} iterations (residual = {residual:.6e})")]
45    ConvergenceFailure {
46        /// Number of iterations performed before giving up.
47        iterations: u32,
48        /// The residual norm at termination.
49        residual: f64,
50    },
51
52    /// The operation requires a workspace of at least the specified size.
53    #[error("workspace of at least {0} bytes required")]
54    WorkspaceRequired(usize),
55
56    /// An internal logic error that should not occur under normal conditions.
57    #[error("internal solver error: {0}")]
58    InternalError(String),
59}
60
61/// Convenience alias for solver operations.
62pub type SolverResult<T> = Result<T, SolverError>;
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn display_singular_matrix() {
70        let err = SolverError::SingularMatrix;
71        assert!(err.to_string().contains("singular"));
72    }
73
74    #[test]
75    fn display_convergence_failure() {
76        let err = SolverError::ConvergenceFailure {
77            iterations: 100,
78            residual: 1e-3,
79        };
80        let msg = err.to_string();
81        assert!(msg.contains("100"));
82        assert!(msg.contains("1.0"));
83    }
84
85    #[test]
86    fn from_cuda_error() {
87        let cuda_err = CudaError::NotInitialized;
88        let solver_err: SolverError = cuda_err.into();
89        assert!(matches!(solver_err, SolverError::Cuda(_)));
90    }
91
92    #[test]
93    fn from_blas_error() {
94        let blas_err = BlasError::InvalidDimension("test".into());
95        let solver_err: SolverError = blas_err.into();
96        assert!(matches!(solver_err, SolverError::Blas(_)));
97    }
98
99    #[test]
100    fn display_workspace_required() {
101        let err = SolverError::WorkspaceRequired(4096);
102        assert!(err.to_string().contains("4096"));
103    }
104}