Skip to main content

tenferro_linalg/
error.rs

1//! Domain errors owned by the linear-algebra extension.
2//!
3//! Numerical failures and unsupported dtypes remain linalg-owned values until
4//! they cross into the tensor/runtime boundary. That boundary wraps the value
5//! as a typed extension source, so callers can classify the failure without
6//! losing the operation-specific payload.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use tenferro_linalg::Error;
12//! use tenferro_tensor::{ErrorKind, DType};
13//!
14//! let error = Error::UnsupportedDType {
15//!     op: "svd",
16//!     dtype: DType::I32,
17//! };
18//! assert_eq!(error.kind(), ErrorKind::Unsupported);
19//! ```
20
21use tenferro_tensor::{DType, ErrorKind};
22
23/// Typed diagnostics for provider status and workspace contracts.
24#[cfg(any(feature = "cpu-blas", feature = "cuda"))]
25#[derive(Debug, thiserror::Error)]
26pub(crate) enum BackendError {
27    #[cfg(feature = "cuda")]
28    #[error("{library} call {call} returned status {status}")]
29    ProviderStatus {
30        library: &'static str,
31        call: &'static str,
32        status: i32,
33    },
34    #[cfg(feature = "cpu-blas")]
35    #[error("{library} routine {routine} returned an invalid workspace: {detail}")]
36    InvalidWorkspace {
37        library: &'static str,
38        routine: &'static str,
39        detail: String,
40    },
41}
42
43/// Failures owned by a linalg algorithm or provider boundary.
44///
45/// # Examples
46///
47/// ```rust
48/// use tenferro_linalg::Error;
49/// use tenferro_tensor::ErrorKind;
50///
51/// let error = Error::Singular { op: "solve" };
52/// assert_eq!(error.kind(), ErrorKind::NumericalFailure);
53/// ```
54#[derive(Debug, thiserror::Error)]
55#[non_exhaustive]
56pub enum Error {
57    /// The algorithm could not converge for the supplied numeric input.
58    #[error("{op} did not converge")]
59    NonConvergence {
60        /// Linalg operation that failed to converge.
61        op: &'static str,
62    },
63    /// The input or a required computed quantity was non-finite.
64    #[error("{op} encountered non-finite {role}")]
65    NonFinite {
66        /// Linalg operation that encountered the non-finite value.
67        op: &'static str,
68        /// Input or computed quantity that was non-finite.
69        role: &'static str,
70    },
71    /// The input matrix or factorization became singular.
72    #[error("{op} is singular")]
73    Singular {
74        /// Linalg operation that encountered a singular matrix.
75        op: &'static str,
76    },
77    /// The selected linalg operation has no implementation for this dtype.
78    #[error("{op} does not support dtype {dtype:?}")]
79    UnsupportedDType {
80        /// Linalg operation that rejected the dtype.
81        op: &'static str,
82        /// Rejected input dtype.
83        dtype: DType,
84    },
85}
86
87impl Error {
88    /// Return the stable coarse classification for this linalg failure.
89    ///
90    /// # Examples
91    ///
92    /// ```rust
93    /// use tenferro_linalg::Error;
94    /// use tenferro_tensor::ErrorKind;
95    ///
96    /// assert_eq!(
97    ///     Error::NonConvergence { op: "svd" }.kind(),
98    ///     ErrorKind::NumericalFailure
99    /// );
100    /// ```
101    #[must_use]
102    pub fn kind(&self) -> ErrorKind {
103        match self {
104            Self::NonConvergence { .. } | Self::NonFinite { .. } | Self::Singular { .. } => {
105                ErrorKind::NumericalFailure
106            }
107            Self::UnsupportedDType { .. } => ErrorKind::Unsupported,
108        }
109    }
110}
111
112/// Result type for linalg-owned domain operations.
113///
114/// # Examples
115///
116/// ```rust
117/// use tenferro_linalg::{Error, Result};
118///
119/// let result: Result<()> = Err(Error::Singular { op: "solve" });
120/// assert!(result.is_err());
121/// ```
122pub type Result<T> = std::result::Result<T, Error>;
123
124/// Wrap a linalg-owned source at the tensor extension boundary.
125pub(crate) fn into_tensor_error(op: &'static str, source: Error) -> tenferro_tensor::Error {
126    tenferro_tensor::Error::extension(
127        op,
128        crate::extension::LINALG_EXTENSION_FAMILY_ID,
129        source.kind(),
130        source,
131    )
132}
133
134/// Construct a typed unsupported-dtype tensor error for linalg backends.
135pub(crate) fn unsupported_dtype(op: &'static str, dtype: DType) -> tenferro_tensor::Error {
136    into_tensor_error(op, Error::UnsupportedDType { op, dtype })
137}
138
139/// Preserve a provider status as a typed backend source.
140#[cfg(feature = "cuda")]
141pub(crate) fn backend_status(
142    op: &'static str,
143    library: &'static str,
144    call: &'static str,
145    status: i32,
146) -> tenferro_tensor::Error {
147    tenferro_tensor::Error::backend_source(
148        op,
149        BackendError::ProviderStatus {
150            library,
151            call,
152            status,
153        },
154    )
155}
156
157/// Preserve an invalid provider workspace response as a typed backend source.
158#[cfg(feature = "cpu-blas")]
159pub(crate) fn invalid_workspace(
160    op: &'static str,
161    library: &'static str,
162    routine: &'static str,
163    detail: impl Into<String>,
164) -> tenferro_tensor::Error {
165    tenferro_tensor::Error::backend_source(
166        op,
167        BackendError::InvalidWorkspace {
168            library,
169            routine,
170            detail: detail.into(),
171        },
172    )
173}
174
175#[cfg(all(test, any(feature = "cpu-blas", feature = "cuda")))]
176mod tests {
177    use std::error::Error as _;
178
179    use super::*;
180
181    #[cfg(feature = "cuda")]
182    #[test]
183    fn provider_status_keeps_typed_backend_source() {
184        let error = backend_status("svd", "cuSOLVER", "cusolverDnSgesvd", 7);
185
186        assert_eq!(error.kind(), ErrorKind::BackendFailure);
187        assert!(matches!(
188            error.source().and_then(|source| source.downcast_ref()),
189            Some(BackendError::ProviderStatus {
190                library: "cuSOLVER",
191                call: "cusolverDnSgesvd",
192                status: 7,
193            })
194        ));
195    }
196
197    #[cfg(feature = "cpu-blas")]
198    #[test]
199    fn invalid_workspace_keeps_typed_backend_source() {
200        let error = invalid_workspace("eigh", "LAPACK", "dsyevd", "query was zero");
201
202        assert_eq!(error.kind(), ErrorKind::BackendFailure);
203        assert!(matches!(
204            error.source().and_then(|source| source.downcast_ref()),
205            Some(BackendError::InvalidWorkspace {
206                library: "LAPACK",
207                routine: "dsyevd",
208                detail,
209            }) if detail == "query was zero"
210        ));
211    }
212}