sim_lib_discrete_algebra/
matrix.rs1use crate::error::AlgebraError;
9use crate::semiring::Semiring;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct AlgebraLimits {
14 pub max_dim: usize,
16}
17
18impl Default for AlgebraLimits {
19 fn default() -> Self {
20 AlgebraLimits { max_dim: 1024 }
21 }
22}
23
24impl AlgebraLimits {
25 pub fn unlimited() -> Self {
27 AlgebraLimits {
28 max_dim: usize::MAX,
29 }
30 }
31
32 fn check_matrix_dims(&self, rows: usize, cols: usize, op: &str) -> Result<(), AlgebraError> {
33 let max_dim = rows.max(cols);
34 if max_dim > self.max_dim {
35 return Err(AlgebraError::LimitExceeded(format!(
36 "{op}: dimension {max_dim} exceeds max_dim {}",
37 self.max_dim
38 )));
39 }
40 Ok(())
41 }
42}
43
44#[derive(Debug, Clone, PartialEq)]
65pub struct Matrix<S: Semiring> {
66 pub rows: usize,
68 pub cols: usize,
70 pub data: Vec<S>,
72}
73
74impl<S: Semiring> Matrix<S> {
75 pub fn new(rows: usize, cols: usize) -> Self {
77 Self::filled(rows, cols, S::zero())
78 }
79
80 pub fn try_new(rows: usize, cols: usize) -> Result<Self, AlgebraError> {
82 Self::try_filled(rows, cols, S::zero())
83 }
84
85 pub fn try_new_with_limits(
88 rows: usize,
89 cols: usize,
90 limits: AlgebraLimits,
91 ) -> Result<Self, AlgebraError> {
92 Self::try_filled_with_limits(rows, cols, S::zero(), limits)
93 }
94
95 pub fn filled(rows: usize, cols: usize, value: S) -> Self {
97 let len = checked_len(rows, cols).expect("matrix dimensions must fit in usize");
98 Matrix {
99 rows,
100 cols,
101 data: vec![value; len],
102 }
103 }
104
105 pub fn try_filled(rows: usize, cols: usize, value: S) -> Result<Self, AlgebraError> {
107 let len = checked_len(rows, cols)?;
108 Ok(Matrix {
109 rows,
110 cols,
111 data: vec![value; len],
112 })
113 }
114
115 pub fn try_filled_with_limits(
118 rows: usize,
119 cols: usize,
120 value: S,
121 limits: AlgebraLimits,
122 ) -> Result<Self, AlgebraError> {
123 limits.check_matrix_dims(rows, cols, "matrix construction")?;
124 Self::try_filled(rows, cols, value)
125 }
126
127 pub fn identity(n: usize) -> Self {
129 let mut m = Self::new(n, n);
130 for i in 0..n {
131 m.data[i * n + i] = S::one();
132 }
133 m
134 }
135
136 pub fn try_identity(n: usize) -> Result<Self, AlgebraError> {
138 let mut m = Self::try_new(n, n)?;
139 for i in 0..n {
140 m.data[i * n + i] = S::one();
141 }
142 Ok(m)
143 }
144
145 pub fn try_identity_with_limits(n: usize, limits: AlgebraLimits) -> Result<Self, AlgebraError> {
147 limits.check_matrix_dims(n, n, "identity construction")?;
148 Self::try_identity(n)
149 }
150
151 pub fn from_rows(rows: Vec<Vec<S>>) -> Result<Self, AlgebraError> {
153 let nrows = rows.len();
154 let ncols = rows.first().map_or(0, Vec::len);
155 let expected = checked_len(nrows, ncols)?;
156 let mut data = Vec::with_capacity(expected);
157 for row in rows {
158 if row.len() != ncols {
159 return Err(AlgebraError::Ragged);
160 }
161 data.extend(row);
162 }
163 Ok(Matrix {
164 rows: nrows,
165 cols: ncols,
166 data,
167 })
168 }
169
170 pub fn is_square(&self) -> bool {
172 self.rows == self.cols
173 }
174
175 pub fn row_count(&self) -> usize {
177 self.rows
178 }
179
180 pub fn col_count(&self) -> usize {
182 self.cols
183 }
184
185 pub fn data(&self) -> &[S] {
187 &self.data
188 }
189
190 pub fn validate(&self) -> Result<(), AlgebraError> {
192 let expected = checked_len(self.rows, self.cols)?;
193 if self.data.len() != expected {
194 return Err(AlgebraError::InvalidMatrix {
195 rows: self.rows,
196 cols: self.cols,
197 expected,
198 actual: self.data.len(),
199 });
200 }
201 Ok(())
202 }
203
204 pub fn get(&self, r: usize, c: usize) -> Result<&S, AlgebraError> {
206 self.validate()?;
207 if r >= self.rows || c >= self.cols {
208 return Err(AlgebraError::IndexOutOfBounds {
209 index: r.saturating_mul(self.cols).saturating_add(c),
210 len: self.data.len(),
211 });
212 }
213 Ok(&self.data[offset(self.cols, r, c)?])
214 }
215
216 pub fn set(&mut self, r: usize, c: usize, value: S) -> Result<(), AlgebraError> {
218 self.validate()?;
219 if r >= self.rows || c >= self.cols {
220 return Err(AlgebraError::IndexOutOfBounds {
221 index: r.saturating_mul(self.cols).saturating_add(c),
222 len: self.data.len(),
223 });
224 }
225 let index = offset(self.cols, r, c)?;
226 self.data[index] = value;
227 Ok(())
228 }
229
230 pub fn row(&self, r: usize) -> Result<&[S], AlgebraError> {
232 self.validate()?;
233 if r >= self.rows {
234 return Err(AlgebraError::IndexOutOfBounds {
235 index: r,
236 len: self.rows,
237 });
238 }
239 let start = r
240 .checked_mul(self.cols)
241 .ok_or(AlgebraError::DimensionOverflow {
242 rows: r,
243 cols: self.cols,
244 })?;
245 let end = start
246 .checked_add(self.cols)
247 .ok_or(AlgebraError::DimensionOverflow {
248 rows: r + 1,
249 cols: self.cols,
250 })?;
251 Ok(&self.data[start..end])
252 }
253
254 pub fn transpose(&self) -> Result<Self, AlgebraError> {
256 self.validate()?;
257 let len = checked_len(self.cols, self.rows)?;
258 let mut data = Vec::with_capacity(len);
259 for c in 0..self.cols {
260 for r in 0..self.rows {
261 data.push(self.data[offset(self.cols, r, c)?].clone());
262 }
263 }
264 Ok(Matrix {
265 rows: self.cols,
266 cols: self.rows,
267 data,
268 })
269 }
270
271 pub fn matmul(&self, other: &Self) -> Result<Self, AlgebraError> {
273 self.validate()?;
274 other.validate()?;
275 if self.cols != other.rows {
276 return Err(AlgebraError::ShapeMismatch(format!(
277 "matmul: {}x{} by {}x{}",
278 self.rows, self.cols, other.rows, other.cols
279 )));
280 }
281 let len = checked_len(self.rows, other.cols)?;
282 let mut data = Vec::with_capacity(len);
283 for i in 0..self.rows {
284 for j in 0..other.cols {
285 let mut acc = S::zero();
286 for k in 0..self.cols {
287 let term = self.data[offset(self.cols, i, k)?]
288 .mul(&other.data[offset(other.cols, k, j)?]);
289 acc = acc.add(&term);
290 }
291 data.push(acc);
292 }
293 }
294 Ok(Matrix {
295 rows: self.rows,
296 cols: other.cols,
297 data,
298 })
299 }
300}
301
302fn checked_len(rows: usize, cols: usize) -> Result<usize, AlgebraError> {
303 rows.checked_mul(cols)
304 .ok_or(AlgebraError::DimensionOverflow { rows, cols })
305}
306
307fn offset(cols: usize, row: usize, col: usize) -> Result<usize, AlgebraError> {
308 row.checked_mul(cols)
309 .and_then(|base| base.checked_add(col))
310 .ok_or(AlgebraError::DimensionOverflow { rows: row, cols })
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::Counting;
317 use crate::tropical_min::MinPlus;
318
319 #[test]
320 fn from_rows_rejects_ragged() {
321 let r = Matrix::from_rows(vec![
322 vec![Counting::from_u64(1)],
323 vec![Counting::from_u64(1), Counting::from_u64(2)],
324 ]);
325 assert_eq!(r.unwrap_err(), AlgebraError::Ragged);
326 }
327
328 #[test]
329 fn matmul_known_result_over_counting() {
330 let a = Matrix::from_rows(vec![
332 vec![Counting::from_u64(1), Counting::from_u64(2)],
333 vec![Counting::from_u64(3), Counting::from_u64(4)],
334 ])
335 .unwrap();
336 let b = Matrix::from_rows(vec![
337 vec![Counting::from_u64(5), Counting::from_u64(6)],
338 vec![Counting::from_u64(7), Counting::from_u64(8)],
339 ])
340 .unwrap();
341 let c = a.matmul(&b).unwrap();
342 assert_eq!(c.data[0], Counting::from_u64(19));
343 assert_eq!(c.data[1], Counting::from_u64(22));
344 assert_eq!(c.data[2], Counting::from_u64(43));
345 assert_eq!(c.data[3], Counting::from_u64(50));
346 }
347
348 #[test]
349 fn matmul_shape_mismatch() {
350 let a: Matrix<MinPlus> = Matrix::new(2, 3);
351 let b: Matrix<MinPlus> = Matrix::new(2, 2);
352 assert!(matches!(a.matmul(&b), Err(AlgebraError::ShapeMismatch(_))));
353 }
354
355 #[test]
356 fn invalid_public_matrix_fails_before_indexing() {
357 let bad = Matrix {
358 rows: 2,
359 cols: 2,
360 data: vec![Counting::from_u64(1), Counting::from_u64(2)],
361 };
362
363 assert!(matches!(
364 bad.validate(),
365 Err(AlgebraError::InvalidMatrix { .. })
366 ));
367 assert!(matches!(
368 bad.get(0, 0),
369 Err(AlgebraError::InvalidMatrix { .. })
370 ));
371 assert!(matches!(
372 bad.row(0),
373 Err(AlgebraError::InvalidMatrix { .. })
374 ));
375 assert!(matches!(
376 bad.transpose(),
377 Err(AlgebraError::InvalidMatrix { .. })
378 ));
379 assert!(matches!(
380 bad.matmul(&Matrix::identity(2)),
381 Err(AlgebraError::InvalidMatrix { .. })
382 ));
383 }
384
385 #[test]
386 fn checked_constructors_reject_dimension_overflow() {
387 assert!(matches!(
388 Matrix::<Counting>::try_new(usize::MAX, 2),
389 Err(AlgebraError::DimensionOverflow { .. })
390 ));
391 assert!(matches!(
392 Matrix::<Counting>::try_filled(2, usize::MAX, Counting::from_u64(1)),
393 Err(AlgebraError::DimensionOverflow { .. })
394 ));
395 }
396
397 #[test]
398 fn identity_is_multiplicative_unit() {
399 let a = Matrix::from_rows(vec![
400 vec![Counting::from_u64(1), Counting::from_u64(2)],
401 vec![Counting::from_u64(3), Counting::from_u64(4)],
402 ])
403 .unwrap();
404 let id = Matrix::identity(2);
405 assert_eq!(a.matmul(&id).unwrap(), a);
406 assert_eq!(id.matmul(&a).unwrap(), a);
407 }
408}