1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3#![doc = include_str!("../README.md")]
4
5extern crate alloc;
6
7use alloc::vec::Vec;
8use core::fmt;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum LinearError {
12 DimensionMismatch {
13 expected_rows: usize,
14 expected_columns: usize,
15 actual_rows: usize,
16 actual_columns: usize,
17 },
18 IndexOutOfBounds {
19 axis: &'static str,
20 index: usize,
21 bound: usize,
22 },
23 StorageLengthMismatch {
24 expected: usize,
25 actual: usize,
26 },
27 CapacityOverflow {
28 requested: usize,
29 },
30 AllocationFailure {
31 requested: usize,
32 },
33 InvalidStorage {
34 reason: &'static str,
35 },
36}
37
38impl fmt::Display for LinearError {
39 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::DimensionMismatch {
42 expected_rows,
43 expected_columns,
44 actual_rows,
45 actual_columns,
46 } => write!(
47 formatter,
48 "dimension mismatch: expected {expected_rows}x{expected_columns}, got {actual_rows}x{actual_columns}"
49 ),
50 Self::IndexOutOfBounds { axis, index, bound } => {
51 write!(formatter, "{axis} index {index} is out of bounds for {bound}")
52 }
53 Self::StorageLengthMismatch { expected, actual } => {
54 write!(formatter, "storage length mismatch: expected {expected}, got {actual}")
55 }
56 Self::CapacityOverflow { requested } => {
57 write!(formatter, "capacity overflow for {requested} elements")
58 }
59 Self::AllocationFailure { requested } => {
60 write!(formatter, "allocation failed for {requested} elements")
61 }
62 Self::InvalidStorage { reason } => write!(formatter, "invalid storage: {reason}"),
63 }
64 }
65}
66
67#[cfg(feature = "std")]
68impl std::error::Error for LinearError {}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct Shape {
72 rows: usize,
73 columns: usize,
74}
75
76impl Shape {
77 pub const fn new(rows: usize, columns: usize) -> Self {
78 Self { rows, columns }
79 }
80
81 pub const fn rows(self) -> usize {
82 self.rows
83 }
84
85 pub const fn columns(self) -> usize {
86 self.columns
87 }
88
89 pub fn element_count(self) -> Result<usize, LinearError> {
90 self.rows
91 .checked_mul(self.columns)
92 .ok_or(LinearError::CapacityOverflow {
93 requested: usize::MAX,
94 })
95 }
96
97 pub fn row_index(self, index: usize) -> Result<RowIndex, LinearError> {
98 if index < self.rows {
99 Ok(RowIndex(index))
100 } else {
101 Err(LinearError::IndexOutOfBounds {
102 axis: "row",
103 index,
104 bound: self.rows,
105 })
106 }
107 }
108
109 pub fn column_index(self, index: usize) -> Result<ColumnIndex, LinearError> {
110 if index < self.columns {
111 Ok(ColumnIndex(index))
112 } else {
113 Err(LinearError::IndexOutOfBounds {
114 axis: "column",
115 index,
116 bound: self.columns,
117 })
118 }
119 }
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
123pub struct RowIndex(usize);
124
125impl RowIndex {
126 pub const fn value(self) -> usize {
127 self.0
128 }
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
132pub struct ColumnIndex(usize);
133
134impl ColumnIndex {
135 pub const fn value(self) -> usize {
136 self.0
137 }
138}
139
140#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct Vector<T> {
142 values: Vec<T>,
143}
144
145impl<T> Vector<T> {
146 pub fn try_from_vec(values: Vec<T>) -> Result<Self, LinearError> {
147 if values.len() > isize::MAX as usize {
148 return Err(LinearError::InvalidStorage {
149 reason: "vector length exceeds the representable allocation limit",
150 });
151 }
152 Ok(Self { values })
153 }
154
155 pub fn try_zeros(length: usize, value: T) -> Result<Self, LinearError>
156 where
157 T: Clone,
158 {
159 let mut values = Vec::new();
160 values
161 .try_reserve_exact(length)
162 .map_err(|_| LinearError::AllocationFailure { requested: length })?;
163 values.resize(length, value);
164 Self::try_from_vec(values)
165 }
166
167 pub fn len(&self) -> usize {
168 self.values.len()
169 }
170
171 pub fn is_empty(&self) -> bool {
172 self.values.is_empty()
173 }
174
175 pub fn value(&self, index: usize) -> Result<&T, LinearError> {
176 self.values.get(index).ok_or(LinearError::IndexOutOfBounds {
177 axis: "vector",
178 index,
179 bound: self.values.len(),
180 })
181 }
182
183 pub fn values(&self) -> &[T] {
184 &self.values
185 }
186
187 pub fn into_values(self) -> Vec<T> {
188 self.values
189 }
190}
191
192pub trait LinearOperator<T> {
193 fn domain(&self) -> usize;
194
195 fn codomain(&self) -> usize;
196
197 fn apply(&self, input: &Vector<T>) -> Result<Vector<T>, LinearError>;
198}
199
200#[cfg(test)]
201mod tests {
202 use alloc::vec;
203
204 use super::{LinearError, LinearOperator, Shape, Vector};
205
206 #[test]
207 fn shape_rejects_invalid_indices_and_detects_size_overflow() {
208 let shape = Shape::new(2, 3);
209 assert_eq!(shape.element_count(), Ok(6));
210 assert!(matches!(
211 shape.row_index(2),
212 Err(LinearError::IndexOutOfBounds { axis: "row", .. })
213 ));
214 assert!(matches!(
215 shape.column_index(3),
216 Err(LinearError::IndexOutOfBounds { axis: "column", .. })
217 ));
218 assert!(matches!(
219 Shape::new(usize::MAX, 2).element_count(),
220 Err(LinearError::CapacityOverflow { .. })
221 ));
222 }
223
224 #[test]
225 fn vector_preserves_length_and_values() {
226 let vector = Vector::try_zeros(3, 7_u8).expect("small allocation succeeds");
227 assert_eq!(vector.len(), 3);
228 assert_eq!(vector.values(), &[7, 7, 7]);
229 assert_eq!(vector.into_values(), vec![7, 7, 7]);
230 }
231
232 struct TestOperator;
233
234 impl LinearOperator<i32> for TestOperator {
235 fn domain(&self) -> usize {
236 2
237 }
238
239 fn codomain(&self) -> usize {
240 1
241 }
242
243 fn apply(&self, input: &Vector<i32>) -> Result<Vector<i32>, LinearError> {
244 if input.len() != self.domain() {
245 return Err(LinearError::StorageLengthMismatch {
246 expected: self.domain(),
247 actual: input.len(),
248 });
249 }
250 let output = Vector::try_from_vec(vec![*input.value(0)?])?;
251 if output.len() != self.codomain() {
252 return Err(LinearError::StorageLengthMismatch {
253 expected: self.codomain(),
254 actual: output.len(),
255 });
256 }
257 Ok(output)
258 }
259 }
260
261 #[test]
262 fn operator_trait_checks_input_and_output_storage_lengths() {
263 let operator = TestOperator;
264 let input = Vector::try_from_vec(vec![1_i32, 2]).expect("valid storage succeeds");
265 assert_eq!(operator.domain(), 2);
266 assert_eq!(operator.codomain(), 1);
267 assert_eq!(operator.apply(&input).expect("valid dimensions").len(), 1);
268
269 let short = Vector::try_from_vec(vec![1_i32]).expect("valid storage succeeds");
270 assert!(matches!(
271 operator.apply(&short),
272 Err(LinearError::StorageLengthMismatch {
273 expected: 2,
274 actual: 1
275 })
276 ));
277 }
278}