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>;