Skip to main content

tenferro_runtime/
error.rs

1//! Error types for the tenferro runtime crate.
2//!
3//! # Examples
4//!
5//! ```rust
6//! use tenferro_runtime::error::Error;
7//!
8//! let err = Error::InvalidSubscripts("bad label".into());
9//! assert!(err.to_string().contains("bad label"));
10//! ```
11
12use std::sync::atomic::{AtomicUsize, Ordering};
13
14use tenferro_tensor::DType;
15
16static NEXT_CONTEXT_ID: AtomicUsize = AtomicUsize::new(1);
17
18/// Errors produced by einsum, eval, and other tenferro operations.
19///
20/// # Examples
21///
22/// ```rust
23/// use tenferro_runtime::error::Error;
24///
25/// let err = Error::InvalidSubscripts("rank mismatch".into());
26/// ```
27#[derive(Debug, thiserror::Error)]
28pub enum Error {
29    /// Einsum subscript string is invalid or cannot be parsed.
30    #[error("invalid subscripts: {0}")]
31    InvalidSubscripts(String),
32
33    /// Contraction optimization failed (shape mismatch, bad path, etc.).
34    #[error("contraction error: {0}")]
35    ContractionError(String),
36
37    /// A required input tensor is missing from the inputs map.
38    #[error("missing input: {0}")]
39    MissingInput(String),
40
41    /// Reverse-mode gradient requires a scalar output.
42    #[error("grad requires a scalar output, got shape {shape:?}")]
43    NonScalarGrad { shape: Vec<usize> },
44
45    /// Runtime tensor execution failed in the backend layer.
46    #[error(transparent)]
47    TensorRuntime(#[from] tenferro_tensor::Error),
48
49    /// A `TracedTensor` passed to graph-executor input bindings is not a
50    /// placeholder (has attached data).
51    #[error(
52        "binding #{binding_index} is not a placeholder; \
53         only tensors built via input_concrete_shape / input_symbolic_shape \
54         can be bound"
55    )]
56    UnexpectedBinding { binding_index: usize },
57
58    /// A placeholder appearing in the graph has no binding supplied.
59    #[error("placeholder {input_key} has no runtime input binding")]
60    UnboundPlaceholder { input_key: String },
61
62    /// The same placeholder was bound more than once in the `bindings` slice.
63    #[error("placeholder {input_key} was bound more than once")]
64    DuplicateBinding { input_key: String },
65
66    /// A binding tensor's dtype does not match the placeholder's dtype.
67    #[error("binding dtype mismatch for placeholder: expected {expected:?}, got {actual:?}")]
68    PlaceholderDtypeMismatch { expected: DType, actual: DType },
69
70    /// A binding tensor's shape does not match an `input_concrete_shape`
71    /// placeholder's fixed shape.
72    #[error(
73        "binding shape mismatch for concrete-shape placeholder: \
74         expected {expected:?}, got {actual:?}"
75    )]
76    PlaceholderShapeMismatch {
77        expected: Vec<usize>,
78        actual: Vec<usize>,
79    },
80
81    /// A binding tensor's rank does not match an `input_symbolic_shape`
82    /// placeholder's declared rank.
83    #[error(
84        "binding rank mismatch for symbolic-shape placeholder: \
85         expected rank {expected}, got rank {actual}"
86    )]
87    PlaceholderRankMismatch { expected: usize, actual: usize },
88
89    /// Operation attempted to mix tensors from different eager contexts.
90    #[error(
91        "tensors belong to different eager AD contexts ({lhs} vs {rhs}); \
92         detach into the target context before combining them"
93    )]
94    ContextMismatch { lhs: ContextId, rhs: ContextId },
95
96    /// Traced graph construction rejected an invalid operation configuration.
97    #[error("{op}: invalid traced graph build: {message}")]
98    InvalidGraphBuild {
99        /// Public operation name.
100        op: &'static str,
101        /// Validation failure details.
102        message: String,
103    },
104
105    /// An AD transform requires a primitive or extension rule that is not
106    /// registered for the requested operation.
107    #[error("unsupported {transform} AD rule for {op}")]
108    UnsupportedAdRule {
109        /// AD transform that requested the rule, such as `grad` or `backward`.
110        transform: &'static str,
111        /// Operation or extension family identifier that has no applicable rule.
112        op: String,
113    },
114
115    /// Lowering rejected an inconsistent compiled graph.
116    #[error("invalid compiled graph: {message}")]
117    InvalidCompiledGraph {
118        /// Validation failure details.
119        message: String,
120    },
121
122    /// An unexpected internal error.
123    #[error("internal error: {0}")]
124    Internal(String),
125}
126
127/// Opaque identifier for an eager AD runtime, used in [`Error::ContextMismatch`].
128#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub struct ContextId(usize);
130
131impl ContextId {
132    /// Generate a fresh opaque runtime context identifier.
133    ///
134    /// Runtime implementations use this when constructing a new execution
135    /// context. The value is intentionally opaque and is only useful in error
136    /// reporting and equality checks.
137    pub fn fresh() -> Self {
138        let id = NEXT_CONTEXT_ID.fetch_add(1, Ordering::Relaxed);
139        Self(id)
140    }
141}
142
143impl std::fmt::Display for ContextId {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(f, "ctx@{:x}", self.0)
146    }
147}
148
149/// Result type alias for tenferro operations.
150pub type Result<T> = std::result::Result<T, Error>;