a/math/matrix/exact.rs
1//! Exact algorithms on matrices of numbers: determinants, ranks, characteristic polynomials,
2//! adjugates, inverses and null spaces, without fractions.
3
4use super::Matrix;
5use crate::math::numbers::Number;
6
7impl Matrix<Number> {
8 /// The determinant, exactly, by Bareiss's fraction-free elimination: each step's elements
9 /// are minors of the matrix, `(a_ij·a_kk - a_ik·a_kj) / p` with the previous pivot `p`
10 /// dividing exactly, so the numbers grow no longer than the determinant. About `n³`
11 /// products. The determinant of the `0 × 0` matrix is 1.
12 ///
13 /// # Panics
14 ///
15 /// Panics if the matrix is not square.
16 ///
17 /// # Examples
18 ///
19 /// ```
20 /// use a::math::matrix::Matrix;
21 /// use a::math::numbers::Number;
22 ///
23 /// let a = Matrix::from_rows(&[[2, -1, 0], [-1, 2, -1], [0, -1, 2]]).map(|&x| Number::from(x));
24 /// assert_eq!(a.det(), 4);
25 /// ```
26 #[track_caller]
27 pub fn det(&self) -> Number {
28 assert!(self.is_square(), "the matrix is not square");
29 let n = self.rows();
30 let mut m = self.clone();
31 let (rank, swaps) = bareiss(&mut m);
32 if rank < n {
33 return Number::default();
34 }
35 if n == 0 {
36 return Number::from(1u8);
37 }
38 let d = m[(n - 1, n - 1)].clone();
39 if swaps % 2 == 1 { -d } else { d }
40 }
41
42 /// The rank, exactly: the number of pivots of Bareiss's fraction-free elimination.
43 pub fn rank(&self) -> usize {
44 bareiss(&mut self.clone()).0
45 }
46
47 /// The coefficients of the characteristic polynomial `det(x·I - A)`, from `xⁿ` down,
48 /// exactly, by Berkowitz's method: the polynomial of each leading block from the one before
49 /// by a Toeplitz matrix of `1`, `-a_kk` and `-R·A_k^j·C`, without a division. About `n⁴/2`
50 /// products.
51 ///
52 /// # Panics
53 ///
54 /// Panics if the matrix is not square.
55 ///
56 /// # Examples
57 ///
58 /// ```
59 /// use a::math::matrix::Matrix;
60 /// use a::math::numbers::Number;
61 ///
62 /// let a = Matrix::from_rows(&[[2, 1], [1, 2]]).map(|&x| Number::from(x));
63 /// assert_eq!(a.characteristic_polynomial(), [1, -4, 3].map(Number::from));
64 /// ```
65 #[track_caller]
66 pub fn characteristic_polynomial(&self) -> Vec<Number> {
67 assert!(self.is_square(), "the matrix is not square");
68 let n = self.rows();
69 let mut p = vec![Number::from(1u8)];
70 for r in 0..n {
71 // `t = (1, -a_rr, -R·C, -R·A_r·C, …)` for the row `R` and the column `C` beside the
72 // leading `r × r` block `A_r`.
73 let mut t = Vec::with_capacity(r + 2);
74 t.push(Number::from(1u8));
75 t.push(-&self[(r, r)]);
76 let mut v: Vec<Number> = (0..r).map(|i| self[(i, r)].clone()).collect();
77 for j in 0..r {
78 let mut q = Number::default();
79 for (k, x) in v.iter().enumerate() {
80 if !x.is_zero() {
81 q += &self[(r, k)] * x;
82 }
83 }
84 t.push(-q);
85 if j + 1 < r {
86 v = (0..r)
87 .map(|i| {
88 let mut s = Number::default();
89 for (k, x) in v.iter().enumerate() {
90 if !x.is_zero() {
91 s += &self[(i, k)] * x;
92 }
93 }
94 s
95 })
96 .collect();
97 }
98 }
99 // `p ← T·p`, `T` lower triangular Toeplitz with the first column `t`.
100 let next = (0..r + 2)
101 .map(|i| {
102 let mut s = Number::default();
103 for (j, c) in p.iter().enumerate().take(i + 1) {
104 if !c.is_zero() && !t[i - j].is_zero() {
105 s += &t[i - j] * c;
106 }
107 }
108 s
109 })
110 .collect();
111 p = next;
112 }
113 p
114 }
115
116 /// The adjugate `adj(A)`, the transpose of the matrix of cofactors, with
117 /// `A·adj(A) = adj(A)·A = det(A)·I`, for singular matrices too: by Cayley and Hamilton,
118 /// `(-1)ⁿ⁻¹·(Aⁿ⁻¹ + c₁·Aⁿ⁻² + … + cₙ₋₁·I)` for the characteristic polynomial's `c`.
119 ///
120 /// # Panics
121 ///
122 /// Panics if the matrix is not square.
123 #[track_caller]
124 pub fn adjugate(&self) -> Matrix<Number> {
125 assert!(self.is_square(), "the matrix is not square");
126 let n = self.rows();
127 if n == 0 {
128 return Matrix::zeros(0, 0);
129 }
130 let c = self.characteristic_polynomial();
131 let mut m = Matrix::<Number>::identity(n);
132 for c in &c[1..n] {
133 m = self * &m;
134 for i in 0..n {
135 m[(i, i)] += c;
136 }
137 }
138 if n.is_multiple_of(2) { -m } else { m }
139 }
140
141 /// The inverse as the adjugate and the determinant, `A⁻¹ = adj(A) / det(A)`, exactly, or
142 /// `None` when the determinant is zero.
143 ///
144 /// # Panics
145 ///
146 /// Panics if the matrix is not square.
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// use a::math::matrix::Matrix;
152 /// use a::math::numbers::Number;
153 ///
154 /// let a = Matrix::from_rows(&[[2, 1], [5, 3]]).map(|&x| Number::from(x));
155 /// let (adj, det) = a.inverse().unwrap();
156 /// assert_eq!(det, 1);
157 /// assert_eq!(adj, Matrix::from_rows(&[[3, -1], [-5, 2]]).map(|&x| Number::from(x)));
158 /// ```
159 #[track_caller]
160 pub fn inverse(&self) -> Option<(Matrix<Number>, Number)> {
161 let det = self.det();
162 if det.is_zero() {
163 return None;
164 }
165 Some((self.adjugate(), det))
166 }
167
168 /// A basis of the null space `{x : A·x = 0}` over the rationals, as the columns of an
169 /// `n × (n - rank)` matrix of integers: one vector for each column without a pivot in the
170 /// fraction-free echelon form, its free coordinate positive and its elements without a
171 /// common factor.
172 ///
173 /// # Examples
174 ///
175 /// ```
176 /// use a::math::matrix::Matrix;
177 /// use a::math::numbers::Number;
178 ///
179 /// let a = Matrix::from_rows(&[[1, 2, 3], [2, 4, 6]]).map(|&x| Number::from(x));
180 /// let null = a.null_space();
181 /// assert_eq!(null.shape(), (3, 2));
182 /// assert!((&a * &null).as_slice().iter().all(|x| x.is_zero()));
183 /// ```
184 pub fn null_space(&self) -> Matrix<Number> {
185 let n = self.cols();
186 let mut e = self.clone();
187 let (rank, _) = bareiss(&mut e);
188 // The pivot columns of the echelon form.
189 let mut pivots = Vec::with_capacity(rank);
190 let mut row = 0;
191 for c in 0..n {
192 if row < rank && !e[(row, c)].is_zero() {
193 pivots.push(c);
194 row += 1;
195 }
196 }
197 let free: Vec<usize> = (0..n).filter(|c| !pivots.contains(c)).collect();
198 let mut basis = Matrix::<Number>::zeros(n, free.len());
199 for (b, &f) in free.iter().enumerate() {
200 // `y`, a multiple of the solution with `x_f = 1` and the other free coordinates 0.
201 let mut y = vec![Number::default(); n];
202 y[f] = Number::from(1u8);
203 for i in (0..rank).rev() {
204 let mut s = Number::default();
205 for c in i..n {
206 if c != pivots[i] && !y[c].is_zero() && !e[(i, c)].is_zero() {
207 s -= &e[(i, c)] * &y[c];
208 }
209 }
210 let pivot = &e[(i, pivots[i])];
211 let g = s.gcd(pivot);
212 if !g.is_zero() {
213 let scale = pivot.div_exact(&g).abs();
214 if scale != 1 {
215 for x in &mut y {
216 *x *= &scale;
217 }
218 s *= &scale;
219 }
220 }
221 y[pivots[i]] = s.div_exact(pivot);
222 }
223 let g = y.iter().fold(Number::default(), |g, x| g.gcd(x));
224 for (i, x) in y.into_iter().enumerate() {
225 basis[(i, b)] = if g > 1 { x.div_exact(&g) } else { x };
226 }
227 if basis[(f, b)].is_negative() {
228 for i in 0..n {
229 basis[(i, b)] = -&basis[(i, b)];
230 }
231 }
232 }
233 basis
234 }
235}
236
237/// Bareiss's fraction-free elimination of `m` to an echelon form in place, the pivot of each
238/// row the first non-zero element at or below it in the next column that has one: the rank,
239/// and the number of row swaps.
240fn bareiss(m: &mut Matrix<Number>) -> (usize, usize) {
241 let (rows, cols) = m.shape();
242 let mut previous = Number::from(1u8);
243 let mut r = 0;
244 let mut swaps = 0;
245 for c in 0..cols {
246 if r == rows {
247 break;
248 }
249 let Some(p) = (r..rows).find(|&i| !m[(i, c)].is_zero()) else {
250 continue;
251 };
252 if p != r {
253 m.swap_rows(p, r);
254 swaps += 1;
255 }
256 let pivot = m[(r, c)].clone();
257 for i in r + 1..rows {
258 let below = m[(i, c)].clone();
259 for j in c + 1..cols {
260 let x = &(&m[(i, j)] * &pivot) - &(&below * &m[(r, j)]);
261 m[(i, j)] = if previous == 1 {
262 x
263 } else {
264 x.div_exact(&previous)
265 };
266 }
267 m[(i, c)] = Number::default();
268 }
269 previous = pivot;
270 r += 1;
271 }
272 (r, swaps)
273}