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