1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// ferray-core: Error types (REQ-27)
use core::fmt;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{string::String, string::ToString, vec::Vec};
/// The primary error type for all ferray operations.
///
/// This enum is `#[non_exhaustive]`, so new variants may be added
/// in minor releases without breaking downstream code.
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum FerrayError {
/// Operand shapes are incompatible for the requested operation.
#[error("shape mismatch: {message}")]
ShapeMismatch {
/// Human-readable description of the mismatch.
message: String,
},
/// Broadcasting failed because shapes cannot be reconciled.
#[error("broadcast failure: cannot broadcast shapes {shape_a:?} and {shape_b:?}")]
BroadcastFailure {
/// First shape.
shape_a: Vec<usize>,
/// Second shape.
shape_b: Vec<usize>,
},
/// An axis index exceeded the array's dimensionality.
#[error("axis {axis} is out of bounds for array with {ndim} dimensions")]
AxisOutOfBounds {
/// The invalid axis.
axis: usize,
/// Number of dimensions.
ndim: usize,
},
/// An element index exceeded the array's extent along some axis.
#[error("index {index} is out of bounds for axis {axis} with size {size}")]
IndexOutOfBounds {
/// The invalid index.
index: isize,
/// The axis along which the index was applied.
axis: usize,
/// The size of that axis.
size: usize,
},
/// A matrix was singular when an invertible one was required.
#[error("singular matrix: {message}")]
SingularMatrix {
/// Diagnostic context.
message: String,
},
/// An iterative algorithm did not converge within its budget.
#[error("convergence failure after {iterations} iterations: {message}")]
ConvergenceFailure {
/// Number of iterations attempted.
iterations: usize,
/// Diagnostic context.
message: String,
},
/// The requested dtype is invalid or unsupported for this operation.
#[error("invalid dtype: {message}")]
InvalidDtype {
/// Diagnostic context.
message: String,
},
/// A computation produced NaN / Inf when finite results were required.
#[error("numerical instability: {message}")]
NumericalInstability {
/// Diagnostic context.
message: String,
},
/// An I/O operation failed.
#[error("I/O error: {message}")]
IoError {
/// Diagnostic context.
message: String,
},
/// A function argument was invalid.
#[error("invalid value: {message}")]
InvalidValue {
/// Diagnostic context.
message: String,
},
}
/// Convenience alias used throughout ferray.
pub type FerrayResult<T> = Result<T, FerrayError>;
impl FerrayError {
/// Create a `ShapeMismatch` error with a formatted message.
pub fn shape_mismatch(msg: impl fmt::Display) -> Self {
Self::ShapeMismatch {
message: msg.to_string(),
}
}
/// Create a `BroadcastFailure` error.
#[must_use]
pub fn broadcast_failure(a: &[usize], b: &[usize]) -> Self {
Self::BroadcastFailure {
shape_a: a.to_vec(),
shape_b: b.to_vec(),
}
}
/// Create an `AxisOutOfBounds` error.
#[must_use]
pub const fn axis_out_of_bounds(axis: usize, ndim: usize) -> Self {
Self::AxisOutOfBounds { axis, ndim }
}
/// Create an `IndexOutOfBounds` error.
#[must_use]
pub const fn index_out_of_bounds(index: isize, axis: usize, size: usize) -> Self {
Self::IndexOutOfBounds { index, axis, size }
}
/// Create an `InvalidDtype` error with a formatted message.
pub fn invalid_dtype(msg: impl fmt::Display) -> Self {
Self::InvalidDtype {
message: msg.to_string(),
}
}
/// Create an `InvalidValue` error with a formatted message.
pub fn invalid_value(msg: impl fmt::Display) -> Self {
Self::InvalidValue {
message: msg.to_string(),
}
}
/// Create an `IoError` from a formatted message.
pub fn io_error(msg: impl fmt::Display) -> Self {
Self::IoError {
message: msg.to_string(),
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for FerrayError {
fn from(e: std::io::Error) -> Self {
Self::IoError {
message: e.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display_shape_mismatch() {
let e = FerrayError::shape_mismatch("expected (3,4), got (3,5)");
assert!(e.to_string().contains("expected (3,4), got (3,5)"));
}
#[test]
fn error_display_axis_out_of_bounds() {
let e = FerrayError::axis_out_of_bounds(5, 3);
assert!(e.to_string().contains("axis 5"));
assert!(e.to_string().contains("3 dimensions"));
}
#[test]
fn error_display_broadcast_failure() {
let e = FerrayError::broadcast_failure(&[4, 3], &[2, 5]);
let s = e.to_string();
assert!(s.contains("[4, 3]"));
assert!(s.contains("[2, 5]"));
}
#[test]
fn error_is_non_exhaustive() {
// Verify the enum is non_exhaustive by using a wildcard
// in a match from an "external" perspective. Inside this crate
// the compiler knows all variants, so we just verify construction.
let e = FerrayError::invalid_value("test");
assert!(matches!(e, FerrayError::InvalidValue { .. }));
let e2 = FerrayError::shape_mismatch("bad shape");
assert!(matches!(e2, FerrayError::ShapeMismatch { .. }));
}
#[test]
fn from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let ferray_err: FerrayError = io_err.into();
assert!(ferray_err.to_string().contains("file missing"));
}
}