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    /// A backend refused to open an execution session; no contraction ran.
117    #[error("backend session entry failed: {0}")]
118    SessionEntry(#[from] tenferro_tensor::SessionEntryError),
119
120    /// Graph construction or extension execution failed in the runtime.
121    #[error(transparent)]
122    Runtime(#[from] tenferro_runtime::Error),
123}
124
125impl Error {
126    /// Construct a shared validation error.
127    ///
128    /// # Examples
129    ///
130    /// ```rust
131    /// use tenferro_einsum::Error;
132    /// use tenferro_tensor::ValidationError;
133    ///
134    /// let error = Error::validation("einsum", ValidationError::RankMismatch {
135    ///     expected: 2,
136    ///     actual: 1,
137    /// });
138    /// assert!(matches!(error, Error::Validation { .. }));
139    /// ```
140    pub fn validation(op: &'static str, source: ValidationError) -> Self {
141        Self::Validation { op, source }
142    }
143
144    /// Construct an invalid-argument validation error.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use tenferro_einsum::Error;
150    ///
151    /// let error = Error::invalid_argument("einsum", "inputs", "at least one input is required");
152    /// assert!(matches!(error, Error::Validation { .. }));
153    /// ```
154    pub fn invalid_argument(
155        op: &'static str,
156        argument: &'static str,
157        message: impl Into<String>,
158    ) -> Self {
159        Self::validation(
160            op,
161            ValidationError::InvalidArgument {
162                argument,
163                message: message.into(),
164            },
165        )
166    }
167
168    /// Construct a shape-mismatch validation error.
169    ///
170    /// # Examples
171    ///
172    /// ```rust
173    /// use tenferro_einsum::Error;
174    ///
175    /// let error = Error::shape_mismatch("einsum", [2, 3], [2, 4]);
176    /// assert!(matches!(error, Error::Validation { .. }));
177    /// ```
178    pub fn shape_mismatch(
179        op: &'static str,
180        expected: impl Into<Vec<usize>>,
181        actual: impl Into<Vec<usize>>,
182    ) -> Self {
183        Self::validation(
184            op,
185            ShapeMismatch::ExpectedActual {
186                expected: ShapeVec::from_vec(expected.into()),
187                actual: ShapeVec::from_vec(actual.into()),
188            }
189            .into(),
190        )
191    }
192
193    /// Construct a dtype-mismatch validation error.
194    ///
195    /// # Examples
196    ///
197    /// ```rust
198    /// use tenferro_einsum::Error;
199    /// use tenferro_tensor::DType;
200    ///
201    /// let error = Error::dtype_mismatch("einsum", DType::F32, DType::F64);
202    /// assert!(matches!(error, Error::Tensor(_)));
203    /// ```
204    pub fn dtype_mismatch(op: &'static str, expected: DType, actual: DType) -> Self {
205        Self::Tensor(tenferro_tensor::Error::dtype_mismatch(op, expected, actual))
206    }
207
208    /// Construct a rank-mismatch validation error.
209    ///
210    /// # Examples
211    ///
212    /// ```rust
213    /// use tenferro_einsum::Error;
214    ///
215    /// let error = Error::rank_mismatch("einsum", 2, 1);
216    /// assert!(matches!(error, Error::Validation { .. }));
217    /// ```
218    pub fn rank_mismatch(op: &'static str, expected: usize, actual: usize) -> Self {
219        Self::validation(op, ValidationError::RankMismatch { expected, actual })
220    }
221
222    /// Construct an invalid-notation error.
223    ///
224    /// # Examples
225    ///
226    /// ```rust
227    /// use tenferro_einsum::Error;
228    ///
229    /// let error = Error::invalid_subscripts("missing `->`");
230    /// assert!(matches!(error, Error::InvalidSubscripts { .. }));
231    /// ```
232    pub fn invalid_subscripts(message: impl Into<String>) -> Self {
233        Self::InvalidSubscripts {
234            message: message.into(),
235        }
236    }
237
238    /// Construct a planning failure.
239    ///
240    /// # Examples
241    ///
242    /// ```rust
243    /// use tenferro_einsum::Error;
244    ///
245    /// let error = Error::planning("no contraction path");
246    /// assert!(matches!(error, Error::Planning { .. }));
247    /// ```
248    pub fn planning(message: impl Into<String>) -> Self {
249        Self::Planning {
250            source: PlanningError::InvalidConfiguration {
251                message: message.into(),
252            },
253        }
254    }
255
256    /// Construct a planning failure caused by unavailable planner state.
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// use tenferro_einsum::{Error, PlanningError};
262    /// use tenferro_tensor::ErrorKind;
263    ///
264    /// let error = Error::planning_runtime_state("planner lock is poisoned");
265    /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
266    /// assert!(matches!(
267    ///     error,
268    ///     Error::Planning {
269    ///         source: PlanningError::RuntimeState { .. }
270    ///     }
271    /// ));
272    /// ```
273    pub fn planning_runtime_state(message: impl Into<String>) -> Self {
274        Self::Planning {
275            source: PlanningError::RuntimeState {
276                message: message.into(),
277            },
278        }
279    }
280
281    /// Construct a numerical failure.
282    ///
283    /// # Examples
284    ///
285    /// ```rust
286    /// use tenferro_einsum::Error;
287    ///
288    /// let error = Error::numerical("contraction did not converge");
289    /// assert!(matches!(error, Error::Numerical { .. }));
290    /// ```
291    pub fn numerical(message: impl Into<String>) -> Self {
292        Self::Numerical {
293            message: message.into(),
294        }
295    }
296
297    /// Return the stable coarse classification of this einsum failure.
298    ///
299    /// # Examples
300    ///
301    /// ```rust
302    /// use tenferro_einsum::Error;
303    /// use tenferro_tensor::{ErrorKind, ValidationKind};
304    ///
305    /// assert_eq!(
306    ///     Error::invalid_subscripts("bad").kind(),
307    ///     ErrorKind::Validation(ValidationKind::InvalidArgument),
308    /// );
309    /// ```
310    #[must_use]
311    pub fn kind(&self) -> ErrorKind {
312        match self {
313            Self::Validation { source, .. } => ErrorKind::Validation(source.kind()),
314            Self::InvalidSubscripts { .. } => {
315                ErrorKind::Validation(ValidationKind::InvalidArgument)
316            }
317            Self::Planning { source } => match source {
318                PlanningError::InvalidConfiguration { .. } => {
319                    ErrorKind::Validation(ValidationKind::InvalidArgument)
320                }
321                PlanningError::RuntimeState { .. } => ErrorKind::RuntimeState,
322            },
323            Self::Numerical { .. } => ErrorKind::NumericalFailure,
324            Self::Tensor(error) => error.kind(),
325            Self::SessionEntry(error) => error.kind(),
326            Self::Runtime(error) => error.kind(),
327        }
328    }
329
330    /// Promote this error to the tensor error used by a type-erased extension
331    /// boundary without formatting away its typed source.
332    ///
333    /// Shared validation is promoted directly. All crate-local and nested
334    /// errors remain a boxed source under the einsum extension family.
335    ///
336    /// # Examples
337    ///
338    /// ```rust
339    /// use std::error::Error as _;
340    /// use tenferro_einsum::Error;
341    /// use tenferro_tensor::{Error as TensorError, ErrorKind, ValidationKind};
342    ///
343    /// let tensor_error = Error::planning("no valid contraction path")
344    ///     .into_tensor_error("einsum_extension");
345    /// assert_eq!(
346    ///     tensor_error.kind(),
347    ///     ErrorKind::Validation(ValidationKind::InvalidArgument)
348    /// );
349    /// assert!(matches!(tensor_error, TensorError::Extension { .. }));
350    /// assert!(tensor_error.source().is_some());
351    /// ```
352    #[must_use]
353    pub fn into_tensor_error(self, op: &'static str) -> tenferro_tensor::Error {
354        match self {
355            Self::Validation { op, source } => tenferro_tensor::Error::validation(op, source),
356            Self::Tensor(error) => error,
357            Self::SessionEntry(error) => tenferro_tensor::Error::from(error),
358            error => {
359                let kind = error.kind();
360                tenferro_tensor::Error::extension(op, EINSUM_EXTENSION_FAMILY_ID, kind, error)
361            }
362        }
363    }
364}
365
366/// Result type alias for einsum parsing, planning, and all public extension
367/// APIs.
368pub type Result<T> = std::result::Result<T, Error>;