Skip to main content

tenferro_einsum/
error.rs

1//! Error types owned by the einsum crate.
2//!
3//! Parsing and planning remain einsum-domain concerns, while shared tensor
4//! validation is represented by the common validation vocabulary. At erased
5//! runtime boundaries [`Error::into_tensor_error`] preserves this distinction
6//! and keeps the original einsum error as a typed source.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use tenferro_einsum::Error;
12//! use tenferro_tensor::{ErrorKind, ValidationKind};
13//!
14//! let err = Error::invalid_subscripts("missing output arrow");
15//! assert_eq!(err.kind(), ErrorKind::Validation(ValidationKind::InvalidArgument));
16//! ```
17
18use tenferro_tensor::{DType, ErrorKind, ShapeMismatch, ShapeVec, ValidationError, ValidationKind};
19
20use crate::EINSUM_EXTENSION_FAMILY_ID;
21
22/// Domain-specific cause of an einsum planning failure.
23///
24/// Caller-controlled expressions, shapes, and optimizer options use
25/// [`PlanningError::InvalidConfiguration`]. Runtime-state classification is
26/// reserved for an unavailable or poisoned planner state.
27///
28/// # Examples
29///
30/// ```rust
31/// use tenferro_einsum::{Error, PlanningError};
32///
33/// let error = Error::planning("the requested path is invalid");
34/// assert!(matches!(
35///     error,
36///     Error::Planning {
37///         source: PlanningError::InvalidConfiguration { .. }
38///     }
39/// ));
40/// ```
41#[derive(Debug, thiserror::Error)]
42pub enum PlanningError {
43    /// The requested expression, path, or planner option is invalid.
44    #[error("invalid einsum planning configuration: {message}")]
45    InvalidConfiguration {
46        /// Human-readable configuration detail.
47        message: String,
48    },
49    /// Planner state required by a valid request is unavailable.
50    #[error("einsum planning runtime state unavailable: {message}")]
51    RuntimeState {
52        /// Human-readable state detail.
53        message: String,
54    },
55}
56
57/// Errors produced while parsing, planning, lowering, or executing einsum
58/// expressions.
59///
60/// # Examples
61///
62/// ```rust
63/// use tenferro_einsum::Error;
64/// use tenferro_tensor::{ErrorKind, ShapeMismatch, ShapeVec, ValidationKind};
65///
66/// let err = Error::validation(
67///     "einsum",
68///     ShapeMismatch::ExpectedActual {
69///         expected: ShapeVec::from_vec(vec![2, 3]),
70///         actual: ShapeVec::from_vec(vec![2, 4]),
71///     }
72///     .into(),
73/// );
74/// assert_eq!(err.kind(), ErrorKind::Validation(ValidationKind::ShapeMismatch));
75/// ```
76#[derive(Debug, thiserror::Error)]
77#[non_exhaustive]
78pub enum Error {
79    /// A shared tensor validation fact discovered by an einsum operation.
80    #[error("{op}: {source}")]
81    Validation {
82        /// Public operation name.
83        op: &'static str,
84        /// Machine-readable validation payload.
85        #[source]
86        source: ValidationError,
87    },
88
89    /// Einsum notation is malformed or cannot be parsed.
90    #[error("invalid einsum subscripts: {message}")]
91    InvalidSubscripts {
92        /// Human-readable parser detail.
93        message: String,
94    },
95
96    /// No valid contraction plan could be constructed for the supplied
97    /// expression or optimizer configuration.
98    #[error("einsum planning failed: {source}")]
99    Planning {
100        /// Typed planning-domain cause.
101        #[source]
102        source: PlanningError,
103    },
104
105    /// A numerical contraction or backend accumulation failed to converge.
106    #[error("einsum numerical failure: {message}")]
107    Numerical {
108        /// Human-readable numerical detail.
109        message: String,
110    },
111
112    /// A concrete tensor/backend operation failed.
113    #[error(transparent)]
114    Tensor(#[from] tenferro_tensor::Error),
115
116    /// Graph construction or extension execution failed in the runtime.
117    #[error(transparent)]
118    Runtime(#[from] tenferro_runtime::Error),
119}
120
121impl Error {
122    /// Construct a shared validation error.
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use tenferro_einsum::Error;
128    /// use tenferro_tensor::ValidationError;
129    ///
130    /// let error = Error::validation("einsum", ValidationError::RankMismatch {
131    ///     expected: 2,
132    ///     actual: 1,
133    /// });
134    /// assert!(matches!(error, Error::Validation { .. }));
135    /// ```
136    pub fn validation(op: &'static str, source: ValidationError) -> Self {
137        Self::Validation { op, source }
138    }
139
140    /// Construct an invalid-argument validation error.
141    ///
142    /// # Examples
143    ///
144    /// ```rust
145    /// use tenferro_einsum::Error;
146    ///
147    /// let error = Error::invalid_argument("einsum", "inputs", "at least one input is required");
148    /// assert!(matches!(error, Error::Validation { .. }));
149    /// ```
150    pub fn invalid_argument(
151        op: &'static str,
152        argument: &'static str,
153        message: impl Into<String>,
154    ) -> Self {
155        Self::validation(
156            op,
157            ValidationError::InvalidArgument {
158                argument,
159                message: message.into(),
160            },
161        )
162    }
163
164    /// Construct a shape-mismatch validation error.
165    ///
166    /// # Examples
167    ///
168    /// ```rust
169    /// use tenferro_einsum::Error;
170    ///
171    /// let error = Error::shape_mismatch("einsum", [2, 3], [2, 4]);
172    /// assert!(matches!(error, Error::Validation { .. }));
173    /// ```
174    pub fn shape_mismatch(
175        op: &'static str,
176        expected: impl Into<Vec<usize>>,
177        actual: impl Into<Vec<usize>>,
178    ) -> Self {
179        Self::validation(
180            op,
181            ShapeMismatch::ExpectedActual {
182                expected: ShapeVec::from_vec(expected.into()),
183                actual: ShapeVec::from_vec(actual.into()),
184            }
185            .into(),
186        )
187    }
188
189    /// Construct a dtype-mismatch validation error.
190    ///
191    /// # Examples
192    ///
193    /// ```rust
194    /// use tenferro_einsum::Error;
195    /// use tenferro_tensor::DType;
196    ///
197    /// let error = Error::dtype_mismatch("einsum", DType::F32, DType::F64);
198    /// assert!(matches!(error, Error::Tensor(_)));
199    /// ```
200    pub fn dtype_mismatch(op: &'static str, expected: DType, actual: DType) -> Self {
201        Self::Tensor(tenferro_tensor::Error::dtype_mismatch(op, expected, actual))
202    }
203
204    /// Construct a rank-mismatch validation error.
205    ///
206    /// # Examples
207    ///
208    /// ```rust
209    /// use tenferro_einsum::Error;
210    ///
211    /// let error = Error::rank_mismatch("einsum", 2, 1);
212    /// assert!(matches!(error, Error::Validation { .. }));
213    /// ```
214    pub fn rank_mismatch(op: &'static str, expected: usize, actual: usize) -> Self {
215        Self::validation(op, ValidationError::RankMismatch { expected, actual })
216    }
217
218    /// Construct an invalid-notation error.
219    ///
220    /// # Examples
221    ///
222    /// ```rust
223    /// use tenferro_einsum::Error;
224    ///
225    /// let error = Error::invalid_subscripts("missing `->`");
226    /// assert!(matches!(error, Error::InvalidSubscripts { .. }));
227    /// ```
228    pub fn invalid_subscripts(message: impl Into<String>) -> Self {
229        Self::InvalidSubscripts {
230            message: message.into(),
231        }
232    }
233
234    /// Construct a planning failure.
235    ///
236    /// # Examples
237    ///
238    /// ```rust
239    /// use tenferro_einsum::Error;
240    ///
241    /// let error = Error::planning("no contraction path");
242    /// assert!(matches!(error, Error::Planning { .. }));
243    /// ```
244    pub fn planning(message: impl Into<String>) -> Self {
245        Self::Planning {
246            source: PlanningError::InvalidConfiguration {
247                message: message.into(),
248            },
249        }
250    }
251
252    /// Construct a planning failure caused by unavailable planner state.
253    ///
254    /// # Examples
255    ///
256    /// ```rust
257    /// use tenferro_einsum::{Error, PlanningError};
258    /// use tenferro_tensor::ErrorKind;
259    ///
260    /// let error = Error::planning_runtime_state("planner lock is poisoned");
261    /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
262    /// assert!(matches!(
263    ///     error,
264    ///     Error::Planning {
265    ///         source: PlanningError::RuntimeState { .. }
266    ///     }
267    /// ));
268    /// ```
269    pub fn planning_runtime_state(message: impl Into<String>) -> Self {
270        Self::Planning {
271            source: PlanningError::RuntimeState {
272                message: message.into(),
273            },
274        }
275    }
276
277    /// Construct a numerical failure.
278    ///
279    /// # Examples
280    ///
281    /// ```rust
282    /// use tenferro_einsum::Error;
283    ///
284    /// let error = Error::numerical("contraction did not converge");
285    /// assert!(matches!(error, Error::Numerical { .. }));
286    /// ```
287    pub fn numerical(message: impl Into<String>) -> Self {
288        Self::Numerical {
289            message: message.into(),
290        }
291    }
292
293    /// Return the stable coarse classification of this einsum failure.
294    ///
295    /// # Examples
296    ///
297    /// ```rust
298    /// use tenferro_einsum::Error;
299    /// use tenferro_tensor::{ErrorKind, ValidationKind};
300    ///
301    /// assert_eq!(
302    ///     Error::invalid_subscripts("bad").kind(),
303    ///     ErrorKind::Validation(ValidationKind::InvalidArgument),
304    /// );
305    /// ```
306    #[must_use]
307    pub fn kind(&self) -> ErrorKind {
308        match self {
309            Self::Validation { source, .. } => ErrorKind::Validation(source.kind()),
310            Self::InvalidSubscripts { .. } => {
311                ErrorKind::Validation(ValidationKind::InvalidArgument)
312            }
313            Self::Planning { source } => match source {
314                PlanningError::InvalidConfiguration { .. } => {
315                    ErrorKind::Validation(ValidationKind::InvalidArgument)
316                }
317                PlanningError::RuntimeState { .. } => ErrorKind::RuntimeState,
318            },
319            Self::Numerical { .. } => ErrorKind::NumericalFailure,
320            Self::Tensor(error) => error.kind(),
321            Self::Runtime(error) => error.kind(),
322        }
323    }
324
325    /// Promote this error to the tensor error used by a type-erased extension
326    /// boundary without formatting away its typed source.
327    ///
328    /// Shared validation is promoted directly. All crate-local and nested
329    /// errors remain a boxed source under the einsum extension family.
330    ///
331    /// # Examples
332    ///
333    /// ```rust
334    /// use std::error::Error as _;
335    /// use tenferro_einsum::Error;
336    /// use tenferro_tensor::{Error as TensorError, ErrorKind, ValidationKind};
337    ///
338    /// let tensor_error = Error::planning("no valid contraction path")
339    ///     .into_tensor_error("einsum_extension");
340    /// assert_eq!(
341    ///     tensor_error.kind(),
342    ///     ErrorKind::Validation(ValidationKind::InvalidArgument)
343    /// );
344    /// assert!(matches!(tensor_error, TensorError::Extension { .. }));
345    /// assert!(tensor_error.source().is_some());
346    /// ```
347    #[must_use]
348    pub fn into_tensor_error(self, op: &'static str) -> tenferro_tensor::Error {
349        match self {
350            Self::Validation { op, source } => tenferro_tensor::Error::validation(op, source),
351            Self::Tensor(error) => error,
352            error => {
353                let kind = error.kind();
354                tenferro_tensor::Error::extension(op, EINSUM_EXTENSION_FAMILY_ID, kind, error)
355            }
356        }
357    }
358}
359
360/// Result type alias for einsum parsing, planning, and all public extension
361/// APIs.
362pub type Result<T> = std::result::Result<T, Error>;