1use std::fmt;
8use std::ops::{Index, IndexMut};
9
10use ocas_domain::EuclideanDomain;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum MatrixError {
15 ShapeMismatch,
17 RightHandSideIsNotVector,
19 Inconsistent,
21 Underdetermined {
23 rank: usize,
25 },
26 ResultNotInDomain,
28}
29
30impl fmt::Display for MatrixError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 MatrixError::ShapeMismatch => f.write_str("matrix shape mismatch"),
34 MatrixError::RightHandSideIsNotVector => {
35 f.write_str("right-hand side must be a column vector")
36 }
37 MatrixError::Inconsistent => f.write_str("inconsistent linear system"),
38 MatrixError::Underdetermined { rank } => {
39 write!(f, "underdetermined system (rank {})", rank)
40 }
41 MatrixError::ResultNotInDomain => {
42 f.write_str("solution does not lie in the expected domain")
43 }
44 }
45 }
46}
47
48impl std::error::Error for MatrixError {}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct Matrix<D: EuclideanDomain> {
71 data: Vec<D::Element>,
72 nrows: usize,
73 ncols: usize,
74 domain: D,
75}
76
77impl<D: EuclideanDomain> Matrix<D> {
78 pub fn new(nrows: usize, ncols: usize, data: Vec<D::Element>, domain: D) -> Self {
82 assert_eq!(
83 data.len(),
84 nrows * ncols,
85 "data length {} != {} * {}",
86 data.len(),
87 nrows,
88 ncols
89 );
90 Self {
91 data,
92 nrows,
93 ncols,
94 domain,
95 }
96 }
97
98 pub fn zeros(nrows: usize, ncols: usize, domain: D) -> Self {
100 let data = vec![domain.zero(); nrows * ncols];
101 Self {
102 data,
103 nrows,
104 ncols,
105 domain,
106 }
107 }
108
109 pub fn identity(n: usize, domain: D) -> Self {
111 let mut m = Self::zeros(n, n, domain);
112 for i in 0..n {
113 m[(i, i)] = m.domain.one();
114 }
115 m
116 }
117
118 pub fn from_rows(rows: Vec<Vec<D::Element>>, domain: D) -> Self {
122 let nrows = rows.len();
123 let ncols = rows.first().map_or(0, |r| r.len());
124 let mut data = Vec::with_capacity(nrows * ncols);
125 for row in &rows {
126 assert_eq!(
127 row.len(),
128 ncols,
129 "inconsistent row lengths: expected {}, got {}",
130 ncols,
131 row.len()
132 );
133 data.extend(row.iter().cloned());
134 }
135 Self {
136 data,
137 nrows,
138 ncols,
139 domain,
140 }
141 }
142
143 pub fn nrows(&self) -> usize {
145 self.nrows
146 }
147
148 pub fn ncols(&self) -> usize {
150 self.ncols
151 }
152
153 pub fn domain(&self) -> &D {
155 &self.domain
156 }
157
158 pub fn swap_rows(&mut self, i: usize, j: usize, start_col: usize) {
160 if i == j {
161 return;
162 }
163 for col in start_col..self.ncols {
164 self.data.swap(i * self.ncols + col, j * self.ncols + col);
165 }
166 }
167
168 pub fn augment(&self, other: &Matrix<D>) -> Result<Matrix<D>, MatrixError> {
172 if self.nrows != other.nrows {
173 return Err(MatrixError::ShapeMismatch);
174 }
175 let new_ncols = self.ncols + other.ncols;
176 let mut data = Vec::with_capacity(self.nrows * new_ncols);
177 for row in 0..self.nrows {
178 let start = row * self.ncols;
179 data.extend_from_slice(&self.data[start..start + self.ncols]);
180 let ostart = row * other.ncols;
181 data.extend_from_slice(&other.data[ostart..ostart + other.ncols]);
182 }
183 Ok(Matrix {
184 data,
185 nrows: self.nrows,
186 ncols: new_ncols,
187 domain: self.domain.clone(),
188 })
189 }
190
191 pub fn row_echelon(&mut self, max_col: usize) -> usize {
196 let max_col = max_col.min(self.ncols);
197 let mut i = 0;
198
199 for j in 0..max_col {
200 if i >= self.nrows {
201 break;
202 }
203
204 if self.domain.is_zero(&self[(i, j)]) {
205 let mut found = false;
207 for k in i + 1..self.nrows {
208 if !self.domain.is_zero(&self[(k, j)]) {
209 self.swap_rows(i, k, j);
210 found = true;
211 break;
212 }
213 }
214 if !found {
215 continue; }
217 }
218
219 let mut g = self[(i, j)].clone();
221 for l in j + 1..self.ncols {
222 if self.domain.is_one(&g) {
223 break;
224 }
225 g = self.domain.gcd(&g, &self[(i, l)]);
226 }
227 if !self.domain.is_one(&g) {
228 for l in j..self.ncols {
229 self[(i, l)] = self.domain.div(&self[(i, l)], &g).unwrap();
230 }
231 }
232
233 let pivot = self[(i, j)].clone();
235 for k in i + 1..self.nrows {
236 if !self.domain.is_zero(&self[(k, j)]) {
237 let g = self.domain.gcd(&pivot, &self[(k, j)]);
238 let scale_pivot = self.domain.div(&self[(k, j)], &g).unwrap();
239 let scale_row = self.domain.div(&pivot, &g).unwrap();
240
241 self[(k, j)] = self.domain.zero();
242 for l in j + 1..self.ncols {
243 let term1 = self.domain.mul(&self[(k, l)], &scale_row);
244 let term2 = self.domain.mul(&self[(i, l)], &scale_pivot);
245 self[(k, l)] = self.domain.sub(&term1, &term2);
246 }
247 }
248 }
249
250 i += 1;
251 }
252
253 i
254 }
255
256 pub fn back_substitution(&mut self, max_col: usize) {
259 let max_col = max_col.min(self.ncols);
260 for i in (0..self.nrows).rev() {
261 if let Some(j) = (0..max_col).find(|&j| !self.domain.is_zero(&self[(i, j)])) {
262 let mut g = self[(i, j)].clone();
264 for l in j + 1..self.ncols {
265 if self.domain.is_one(&g) {
266 break;
267 }
268 g = self.domain.gcd(&g, &self[(i, l)]);
269 }
270 if !self.domain.is_one(&g) {
271 for l in j..self.ncols {
272 self[(i, l)] = self.domain.div(&self[(i, l)], &g).unwrap();
273 }
274 }
275
276 for k in 0..i {
278 if !self.domain.is_zero(&self[(k, j)]) {
279 let g = self.domain.gcd(&self[(i, j)], &self[(k, j)]);
280 let scale_pivot = self.domain.div(&self[(k, j)], &g).unwrap();
281 let scale_row = self.domain.div(&self[(i, j)], &g).unwrap();
282
283 if !self.domain.is_one(&scale_row) {
284 for l in 0..self.ncols {
285 if !self.domain.is_zero(&self[(k, l)]) {
286 self[(k, l)] = self.domain.mul(&self[(k, l)], &scale_row);
287 }
288 }
289 }
290
291 self[(k, j)] = self.domain.zero();
292 for l in j + 1..self.ncols {
293 let term1 = self[(k, l)].clone();
294 let term2 = self.domain.mul(&self[(i, l)], &scale_pivot);
295 self[(k, l)] = self.domain.sub(&term1, &term2);
296 }
297 }
298 }
299 }
300 }
301 }
302
303 pub fn solve(&self, b: &[D::Element]) -> Result<Vec<D::Element>, MatrixError> {
308 if self.nrows != b.len() {
309 return Err(MatrixError::ShapeMismatch);
310 }
311
312 let b_matrix = Matrix::new(b.len(), 1, b.to_vec(), self.domain.clone());
313
314 let mut augmented = self.augment(&b_matrix)?;
315 let nvars = self.ncols;
316
317 let rank = augmented.row_echelon(nvars);
318
319 for k in rank..self.nrows {
321 if !self.domain.is_zero(&augmented[(k, nvars)]) {
322 return Err(MatrixError::Inconsistent);
323 }
324 }
325
326 augmented.back_substitution(nvars);
327
328 if rank < nvars {
329 return Err(MatrixError::Underdetermined { rank });
330 }
331
332 let mut solution = Vec::with_capacity(nvars);
334 for i in 0..nvars {
335 match self.domain.div(&augmented[(i, nvars)], &augmented[(i, i)]) {
336 Some(val) => solution.push(val),
337 None => return Err(MatrixError::ResultNotInDomain),
338 }
339 }
340
341 Ok(solution)
342 }
343
344 pub fn into_rows(self) -> Vec<Vec<D::Element>> {
346 let mut rows = Vec::with_capacity(self.nrows);
347 for i in 0..self.nrows {
348 let start = i * self.ncols;
349 rows.push(self.data[start..start + self.ncols].to_vec());
350 }
351 rows
352 }
353
354 pub fn data(&self) -> &[D::Element] {
356 &self.data
357 }
358
359 pub fn row(&self, i: usize) -> Vec<D::Element> {
361 let start = i * self.ncols;
362 self.data[start..start + self.ncols].to_vec()
363 }
364
365 pub fn column(&self, j: usize) -> Vec<D::Element> {
367 (0..self.nrows).map(|i| self[(i, j)].clone()).collect()
368 }
369
370 pub fn transpose(&self) -> Matrix<D> {
387 let mut data = Vec::with_capacity(self.nrows * self.ncols);
388 for j in 0..self.ncols {
389 for i in 0..self.nrows {
390 data.push(self[(i, j)].clone());
391 }
392 }
393 Matrix {
394 data,
395 nrows: self.ncols,
396 ncols: self.nrows,
397 domain: self.domain.clone(),
398 }
399 }
400
401 pub fn trace(&self) -> Result<D::Element, MatrixError> {
419 if self.nrows != self.ncols {
420 return Err(MatrixError::ShapeMismatch);
421 }
422 let mut sum = self.domain.zero();
423 for i in 0..self.nrows {
424 sum = self.domain.add(&sum, &self[(i, i)]);
425 }
426 Ok(sum)
427 }
428
429 pub fn matmul(&self, other: &Matrix<D>) -> Result<Matrix<D>, MatrixError> {
446 if self.ncols != other.nrows {
447 return Err(MatrixError::ShapeMismatch);
448 }
449 let mut data = Vec::with_capacity(self.nrows * other.ncols);
450 for i in 0..self.nrows {
451 for j in 0..other.ncols {
452 let mut acc = self.domain.zero();
453 for k in 0..self.ncols {
454 let term = self.domain.mul(&self[(i, k)], &other[(k, j)]);
455 acc = self.domain.add(&acc, &term);
456 }
457 data.push(acc);
458 }
459 }
460 Ok(Matrix {
461 data,
462 nrows: self.nrows,
463 ncols: other.ncols,
464 domain: self.domain.clone(),
465 })
466 }
467
468 pub fn rank(&self) -> usize {
484 let mut copy = self.clone();
485 copy.row_echelon(self.ncols)
486 }
487
488 pub fn determinant(&self) -> Result<D::Element, MatrixError> {
507 if self.nrows != self.ncols {
508 return Err(MatrixError::ShapeMismatch);
509 }
510 let n = self.nrows;
511 if n == 0 {
512 return Ok(self.domain.one());
513 }
514 if n == 1 {
515 return Ok(self.data[0].clone());
516 }
517 let mut m = self.data.clone();
519 let mut sign_pos = true;
520 let mut prev = self.domain.one();
521 for k in 0..n - 1 {
522 let pivot = m[k * n + k].clone();
523 if self.domain.is_zero(&pivot) {
524 let mut swap_row = None;
526 for i in k + 1..n {
527 if !self.domain.is_zero(&m[i * n + k]) {
528 swap_row = Some(i);
529 break;
530 }
531 }
532 match swap_row {
533 Some(i) => {
534 for j in 0..n {
535 m.swap(k * n + j, i * n + j);
536 }
537 sign_pos = !sign_pos;
538 }
539 None => return Ok(self.domain.zero()),
540 }
541 }
542 let pivot = m[k * n + k].clone();
543 for i in k + 1..n {
544 for j in k + 1..n {
545 let term1 = self.domain.mul(&m[i * n + j], &pivot);
546 let term2 = self.domain.mul(&m[i * n + k], &m[k * n + j]);
547 let diff = self.domain.sub(&term1, &term2);
548 m[i * n + j] = self.domain.div(&diff, &prev).unwrap_or(diff);
550 }
551 }
552 prev = pivot;
553 }
554 let det = m[(n - 1) * n + (n - 1)].clone();
555 if sign_pos {
556 Ok(det)
557 } else {
558 Ok(self.domain.neg(&det))
559 }
560 }
561
562 pub fn inverse(&self) -> Result<Matrix<D>, MatrixError> {
586 if self.nrows != self.ncols {
587 return Err(MatrixError::ShapeMismatch);
588 }
589 let n = self.nrows;
590 let identity = Matrix::identity(n, self.domain.clone());
591 let mut inv_data = vec![self.domain.zero(); n * n];
594 for j in 0..n {
595 let b = identity.column(j);
596 let col = self.solve(&b)?;
597 if col.len() != n {
598 return Err(MatrixError::Underdetermined { rank: col.len() });
599 }
600 for i in 0..n {
601 inv_data[i * n + j] = col[i].clone();
602 }
603 }
604 Ok(Matrix {
605 data: inv_data,
606 nrows: n,
607 ncols: n,
608 domain: self.domain.clone(),
609 })
610 }
611}
612
613impl<D: EuclideanDomain> Index<(usize, usize)> for Matrix<D> {
614 type Output = D::Element;
615
616 fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
617 &self.data[row * self.ncols + col]
618 }
619}
620
621impl<D: EuclideanDomain> IndexMut<(usize, usize)> for Matrix<D> {
622 fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
623 &mut self.data[row * self.ncols + col]
624 }
625}
626
627impl<D: EuclideanDomain + fmt::Display> fmt::Display for Matrix<D>
628where
629 D::Element: fmt::Display,
630{
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 for i in 0..self.nrows {
633 if i > 0 {
634 writeln!(f)?;
635 }
636 for j in 0..self.ncols {
637 if j > 0 {
638 write!(f, " ")?;
639 }
640 write!(f, "{}", self[(i, j)])?;
641 }
642 }
643 Ok(())
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use ocas_domain::{Integer, IntegerDomain};
651
652 fn i(n: i64) -> Integer {
653 Integer::from(n)
654 }
655
656 #[test]
657 fn identity_solve() {
658 let d = IntegerDomain;
659 let a = Matrix::identity(3, d);
660 let b = vec![i(1), i(2), i(3)];
661 let x = a.solve(&b).unwrap();
662 assert_eq!(x, b);
663 }
664
665 #[test]
666 fn solve_2x2() {
667 let d = IntegerDomain;
668 let a = Matrix::from_rows(vec![vec![i(2), i(1)], vec![i(1), i(1)]], d);
674 let b = vec![i(4), i(3)];
675 let x = a.solve(&b).unwrap();
676 assert_eq!(x, vec![i(1), i(2)]);
677 }
678
679 #[test]
680 fn solve_3x3() {
681 let d = IntegerDomain;
682 let a = Matrix::from_rows(
686 vec![
687 vec![i(1), i(1), i(1)],
688 vec![i(2), i(-1), i(1)],
689 vec![i(1), i(2), i(-1)],
690 ],
691 d,
692 );
693 let b = vec![i(6), i(3), i(2)];
694 let x = a.solve(&b).unwrap();
695 assert_eq!(x, vec![i(1), i(2), i(3)]);
696 }
697
698 #[test]
699 fn inconsistent_system() {
700 let d = IntegerDomain;
701 let a = Matrix::from_rows(vec![vec![i(1), i(1)], vec![i(1), i(1)]], d);
702 let b = vec![i(1), i(2)];
703 assert_eq!(a.solve(&b), Err(MatrixError::Inconsistent));
704 }
705
706 #[test]
707 fn underdetermined_system() {
708 let d = IntegerDomain;
709 let a = Matrix::from_rows(vec![vec![i(1), i(1), i(1)]], d);
710 let b = vec![i(3)];
711 assert!(matches!(
712 a.solve(&b),
713 Err(MatrixError::Underdetermined { .. })
714 ));
715 }
716
717 #[test]
718 fn row_echelon_rank() {
719 let d = IntegerDomain;
720 let mut a = Matrix::from_rows(
721 vec![
722 vec![i(1), i(2), i(3)],
723 vec![i(2), i(4), i(6)],
724 vec![i(0), i(1), i(1)],
725 ],
726 d,
727 );
728 let rank = a.row_echelon(3);
729 assert_eq!(rank, 2);
730 }
731
732 #[test]
733 fn augment_and_solve() {
734 let d = IntegerDomain;
735 let a = Matrix::from_rows(vec![vec![i(3), i(2)], vec![i(1), i(1)]], d);
736 let b = Matrix::new(2, 1, vec![i(5), i(2)], d);
737 let aug = a.augment(&b).unwrap();
738 assert_eq!(aug.nrows, 2);
739 assert_eq!(aug.ncols, 3);
740 assert_eq!(aug[(0, 0)], i(3));
741 assert_eq!(aug[(0, 2)], i(5));
742 assert_eq!(aug[(1, 0)], i(1));
743 assert_eq!(aug[(1, 2)], i(2));
744 }
745
746 #[test]
747 fn transpose_rect() {
748 let d = IntegerDomain;
749 let a = Matrix::from_rows(vec![vec![i(1), i(2), i(3)], vec![i(4), i(5), i(6)]], d);
750 let t = a.transpose();
751 assert_eq!(t.nrows(), 3);
752 assert_eq!(t.ncols(), 2);
753 assert_eq!(t[(0, 0)], i(1));
754 assert_eq!(t[(0, 1)], i(4));
755 assert_eq!(t[(2, 1)], i(6));
756 }
757
758 #[test]
759 fn transpose_square() {
760 let d = IntegerDomain;
761 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(3), i(4)]], d);
762 let t = a.transpose();
763 assert_eq!(t[(0, 1)], i(3));
764 assert_eq!(t[(1, 0)], i(2));
765 }
766
767 #[test]
768 fn trace_square() {
769 let d = IntegerDomain;
770 let a = Matrix::from_rows(
771 vec![
772 vec![i(1), i(2), i(3)],
773 vec![i(4), i(5), i(6)],
774 vec![i(7), i(8), i(9)],
775 ],
776 d,
777 );
778 assert_eq!(a.trace().unwrap(), i(15));
779 }
780
781 #[test]
782 fn trace_nonsquare_errors() {
783 let d = IntegerDomain;
784 let a = Matrix::from_rows(vec![vec![i(1), i(2)]], d);
785 assert_eq!(a.trace(), Err(MatrixError::ShapeMismatch));
786 }
787
788 #[test]
789 fn matmul_basic() {
790 let d = IntegerDomain;
791 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(3), i(4)]], d);
792 let b = Matrix::from_rows(vec![vec![i(5), i(6)], vec![i(7), i(8)]], d);
793 let c = a.matmul(&b).unwrap();
794 assert_eq!(c[(0, 0)], i(19));
795 assert_eq!(c[(0, 1)], i(22));
796 assert_eq!(c[(1, 0)], i(43));
797 assert_eq!(c[(1, 1)], i(50));
798 }
799
800 #[test]
801 fn matmul_identity() {
802 let d = IntegerDomain;
803 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(3), i(4)]], d);
804 let id = Matrix::identity(2, d);
805 let c = a.matmul(&id).unwrap();
806 assert_eq!(c, a);
807 }
808
809 #[test]
810 fn matmul_shape_mismatch() {
811 let d = IntegerDomain;
812 let a = Matrix::from_rows(vec![vec![i(1), i(2)]], d);
813 let b = Matrix::from_rows(vec![vec![i(3), i(4)]], d);
814 assert_eq!(a.matmul(&b), Err(MatrixError::ShapeMismatch));
815 }
816
817 #[test]
818 fn rank_full() {
819 let d = IntegerDomain;
820 let a = Matrix::from_rows(vec![vec![i(1), i(0)], vec![i(0), i(1)]], d);
821 assert_eq!(a.rank(), 2);
822 }
823
824 #[test]
825 fn rank_deficient() {
826 let d = IntegerDomain;
827 let a = Matrix::from_rows(
828 vec![
829 vec![i(1), i(2), i(3)],
830 vec![i(2), i(4), i(6)],
831 vec![i(1), i(1), i(1)],
832 ],
833 d,
834 );
835 assert_eq!(a.rank(), 2);
836 }
837
838 #[test]
839 fn determinant_2x2() {
840 let d = IntegerDomain;
841 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(3), i(4)]], d);
842 assert_eq!(a.determinant().unwrap(), i(-2));
843 }
844
845 #[test]
846 fn determinant_3x3() {
847 let d = IntegerDomain;
848 let a = Matrix::from_rows(
852 vec![
853 vec![i(1), i(2), i(3)],
854 vec![i(4), i(5), i(6)],
855 vec![i(5), i(6), i(7)],
856 ],
857 d,
858 );
859 assert_eq!(a.determinant().unwrap(), i(0));
863 }
864
865 #[test]
866 fn determinant_singular() {
867 let d = IntegerDomain;
868 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(2), i(4)]], d);
870 assert_eq!(a.determinant().unwrap(), i(0));
871 }
872
873 #[test]
874 fn determinant_nonsquare_errors() {
875 let d = IntegerDomain;
876 let a = Matrix::from_rows(vec![vec![i(1), i(2)]], d);
877 assert_eq!(a.determinant(), Err(MatrixError::ShapeMismatch));
878 }
879
880 #[test]
881 fn inverse_unimodular() {
882 let d = IntegerDomain;
883 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(3), i(5)]], d);
884 let inv = a.inverse().unwrap();
886 assert_eq!(inv[(0, 0)], i(-5));
888 assert_eq!(inv[(0, 1)], i(2));
889 assert_eq!(inv[(1, 0)], i(3));
890 assert_eq!(inv[(1, 1)], i(-1));
891 let prod = a.matmul(&inv).unwrap();
893 assert_eq!(prod, Matrix::identity(2, IntegerDomain));
894 }
895
896 #[test]
897 fn inverse_singular_errors() {
898 let d = IntegerDomain;
899 let a = Matrix::from_rows(vec![vec![i(1), i(2)], vec![i(2), i(4)]], d);
900 assert!(a.inverse().is_err());
901 }
902}