Skip to main content

tenferro_tensor_core/
error.rs

1use crate::{DType, ShapeVec};
2
3/// Coarse classification for shared tensor validation failures.
4///
5/// # Examples
6///
7/// ```rust
8/// use tenferro_tensor_core::{ValidationError, ValidationKind};
9///
10/// let error = ValidationError::RankMismatch {
11///     expected: 2,
12///     actual: 1,
13/// };
14/// assert_eq!(error.kind(), ValidationKind::RankMismatch);
15/// ```
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17#[non_exhaustive]
18pub enum ValidationKind {
19    ShapeMismatch,
20    RankMismatch,
21    AxisOutOfBounds,
22    DTypeMismatch,
23    InvalidArgument,
24}
25
26/// Coarse classification shared by crate-local error types.
27///
28/// # Examples
29///
30/// ```rust
31/// use tenferro_tensor_core::{ErrorKind, ValidationKind};
32///
33/// assert_eq!(
34///     ErrorKind::Validation(ValidationKind::ShapeMismatch),
35///     ErrorKind::Validation(ValidationKind::ShapeMismatch),
36/// );
37/// ```
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39#[non_exhaustive]
40pub enum ErrorKind {
41    Validation(ValidationKind),
42    Unsupported,
43    NumericalFailure,
44    BackendFailure,
45    Io,
46    RuntimeState,
47    Internal,
48}
49
50/// Structured facts describing why two tensor shapes are incompatible.
51///
52/// # Examples
53///
54/// ```rust
55/// use tenferro_tensor_core::{ShapeMismatch, ShapeVec};
56///
57/// let mismatch = ShapeMismatch::IncompatibleShapes {
58///     lhs: ShapeVec::from_vec(vec![2, 3]),
59///     rhs: ShapeVec::from_vec(vec![2, 4]),
60/// };
61/// assert!(mismatch.to_string().contains("incompatible shapes"));
62/// ```
63#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
64#[non_exhaustive]
65pub enum ShapeMismatch {
66    #[error("incompatible shapes: lhs={lhs:?}, rhs={rhs:?}")]
67    IncompatibleShapes { lhs: ShapeVec, rhs: ShapeVec },
68    #[error("shape mismatch: expected={expected:?}, actual={actual:?}")]
69    ExpectedActual {
70        expected: ShapeVec,
71        actual: ShapeVec,
72    },
73    #[error("reshape element-count mismatch: from {from} to {to}")]
74    ReshapeElementCount { from: usize, to: usize },
75    #[error(
76        "contracted dimensions differ: lhs axis {lhs_axis} ({lhs_size}) vs rhs axis {rhs_axis} ({rhs_size})"
77    )]
78    ContractedDimensions {
79        lhs_axis: usize,
80        lhs_size: usize,
81        rhs_axis: usize,
82        rhs_size: usize,
83    },
84}
85
86/// Structured validation failures owned by the tensor data model.
87///
88/// # Examples
89///
90/// ```rust
91/// use tenferro_tensor_core::{ShapeMismatch, ShapeVec, ValidationError};
92///
93/// let error: ValidationError = ShapeMismatch::ExpectedActual {
94///     expected: ShapeVec::from_vec(vec![2, 3]),
95///     actual: ShapeVec::from_vec(vec![6]),
96/// }
97/// .into();
98/// assert!(error.to_string().contains("shape mismatch"));
99/// ```
100#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
101#[non_exhaustive]
102pub enum ValidationError {
103    #[error("{0}")]
104    ShapeMismatch(#[source] Box<ShapeMismatch>),
105    #[error("shape product {expected} does not match data length {actual}")]
106    ShapeDataLengthMismatch { expected: usize, actual: usize },
107    #[error("rank mismatch: expected {expected}, actual {actual}")]
108    RankMismatch { expected: usize, actual: usize },
109    #[error("axis {axis} out of bounds for rank {rank}")]
110    AxisOutOfBounds { axis: usize, rank: usize },
111    #[error("duplicate {role} axis {axis}")]
112    DuplicateAxis { axis: usize, role: &'static str },
113    #[error("axis {axis} appears in both {first_role} and {second_role}")]
114    AxisRoleConflict {
115        axis: usize,
116        first_role: &'static str,
117        second_role: &'static str,
118    },
119    #[error("invalid permutation length: expected {expected}, actual {actual}")]
120    InvalidPermutationLength { expected: usize, actual: usize },
121    #[error("invalid slice step {step}; zero is invalid")]
122    InvalidSliceStep { step: isize },
123    #[error("invalid slice bounds: start={start}, end={end}, axis_len={axis_len}")]
124    InvalidSliceBounds {
125        start: isize,
126        end: isize,
127        axis_len: usize,
128    },
129    #[error("dtype mismatch: expected {expected:?}, actual {actual:?}")]
130    DTypeMismatch { expected: DType, actual: DType },
131    #[error("invalid argument {argument}: {message}")]
132    InvalidArgument {
133        argument: &'static str,
134        message: String,
135    },
136    #[error("view is not slice-contiguous")]
137    NonContiguousViewAsSlice,
138    #[error("view metadata is out of borrowed-slice bounds")]
139    ViewOutOfBounds,
140    #[error("mutable tensor layout may overlap physical elements")]
141    OverlappingMutableLayout,
142    #[error("integer overflow while validating tensor metadata")]
143    IntegerOverflow,
144}
145
146impl From<ShapeMismatch> for ValidationError {
147    fn from(error: ShapeMismatch) -> Self {
148        Self::ShapeMismatch(Box::new(error))
149    }
150}
151
152impl ValidationError {
153    /// Return the stable coarse classification for this validation failure.
154    ///
155    /// # Examples
156    ///
157    /// ```rust
158    /// use tenferro_tensor_core::{ValidationError, ValidationKind};
159    ///
160    /// let error = ValidationError::AxisOutOfBounds { axis: 3, rank: 2 };
161    /// assert_eq!(error.kind(), ValidationKind::AxisOutOfBounds);
162    /// ```
163    pub fn kind(&self) -> ValidationKind {
164        match self {
165            Self::ShapeMismatch(_) | Self::ShapeDataLengthMismatch { .. } => {
166                ValidationKind::ShapeMismatch
167            }
168            Self::RankMismatch { .. } | Self::InvalidPermutationLength { .. } => {
169                ValidationKind::RankMismatch
170            }
171            Self::AxisOutOfBounds { .. } => ValidationKind::AxisOutOfBounds,
172            Self::DTypeMismatch { .. } => ValidationKind::DTypeMismatch,
173            _ => ValidationKind::InvalidArgument,
174        }
175    }
176}