qfall_math/integer_mod_q/mat_zq/
inverse.rs

1// Copyright © 2023 Marcel Luca Schmidt
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! This module contains the implementation of the `inverse` function.
10
11use super::MatZq;
12use crate::{
13    integer::Z,
14    traits::{Concatenate, Gcd, MatrixDimensions, MatrixSetSubmatrix, MatrixSwaps},
15};
16use flint_sys::fmpz_mod_mat::fmpz_mod_mat_rref;
17
18impl MatZq {
19    /// Returns the inverse of the matrix if it exists (is square and
20    /// has a determinant co-prime to the modulus) and `None` otherwise.
21    ///
22    /// # Examples
23    /// ```
24    /// use qfall_math::integer_mod_q::MatZq;
25    /// use std::str::FromStr;
26    ///
27    /// let mut matrix = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
28    ///
29    /// let matrix_invert = matrix.inverse().unwrap();
30    ///
31    /// let id = &matrix_invert * &matrix;
32    /// assert_eq!("[[5, 1],[5, 3]] mod 7", matrix_invert.to_string());
33    /// assert!(id.is_identity());
34    /// ```
35    pub fn inverse(&self) -> Option<MatZq> {
36        if self.modulus.is_prime() {
37            return self.inverse_prime();
38        }
39
40        // Check if the matrix is square and compute the determinant.
41        if let Ok(det) = self.get_representative_least_nonnegative_residue().det() {
42            // Check whether the square matrix is invertible or not.
43            if det.gcd(self.get_mod()) != Z::ONE {
44                None
45            } else {
46                let dimensions = self.get_num_rows();
47                let mut inverse = MatZq::new(dimensions, dimensions, self.get_mod());
48
49                // The i`th unit vector in the for loop.
50                let mut e_i = MatZq::identity(dimensions, 1, self.get_mod());
51
52                // Use solve for all unit vectors.
53                for i in 0..dimensions {
54                    if let Some(column_i) = self.solve_gaussian_elimination(&e_i) {
55                        inverse.set_column(i, &column_i, 0).unwrap();
56                    } else {
57                        return None;
58                    }
59
60                    if i != dimensions - 1 {
61                        e_i.swap_entries(i, 0, i + 1, 0).unwrap();
62                    }
63                }
64
65                Some(inverse)
66            }
67        } else {
68            None
69        }
70    }
71
72    /// Returns the inverse of the matrix if it exists (is square and
73    /// has a determinant co-prime to the modulus) and `None` otherwise.
74    ///
75    /// Note that the modulus is assumed to be prime, otherwise the function panics.
76    ///
77    /// # Examples
78    /// ```
79    /// use qfall_math::integer_mod_q::MatZq;
80    /// use std::str::FromStr;
81    ///
82    /// let mut matrix = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
83    ///
84    /// let matrix_invert = matrix.inverse_prime().unwrap();
85    ///
86    /// let id = &matrix_invert * &matrix;
87    /// assert_eq!("[[5, 1],[5, 3]] mod 7", matrix_invert.to_string());
88    /// assert!(id.is_identity());
89    /// ```
90    ///
91    /// # Panics ...
92    /// - if the modulus is not prime.
93    pub fn inverse_prime(&self) -> Option<MatZq> {
94        assert!(
95            self.get_mod().is_prime(),
96            "The modulus of the matrix is not prime"
97        );
98
99        // Check if the matrix is square and compute the determinant.
100        if let Ok(det) = self.get_representative_least_nonnegative_residue().det() {
101            if det == Z::ZERO || det.gcd(self.get_mod()) != Z::ONE {
102                None
103            } else {
104                let dimensions = self.get_num_rows();
105
106                // Concatenate the matrix with the identity matrix.
107                let matrix_identity = self
108                    .concat_horizontal(&MatZq::identity(dimensions, dimensions, self.get_mod()))
109                    .unwrap();
110
111                let identity_inverse = matrix_identity.gaussian_elimination_prime();
112
113                // The inverse is now the right half of the matrix `identity_inverse`.
114                let mut inverse = MatZq::new(dimensions, dimensions, self.get_mod());
115                unsafe {
116                    inverse.set_submatrix_unchecked(
117                        0,
118                        0,
119                        dimensions,
120                        dimensions,
121                        &identity_inverse,
122                        0,
123                        dimensions,
124                        dimensions,
125                        2 * dimensions,
126                    );
127                }
128
129                Some(inverse)
130            }
131        } else {
132            None
133        }
134    }
135
136    /// Returns the `row echelon form` of the matrix using gaussian elimination.
137    ///
138    /// Note that the modulus is assumed to be prime, otherwise the function panics.
139    ///
140    /// # Examples
141    /// ```
142    /// use qfall_math::integer_mod_q::MatZq;
143    /// use std::str::FromStr;
144    ///
145    /// let mut matrix = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
146    ///
147    /// let matrix_gauss = matrix.gaussian_elimination_prime();
148    ///
149    /// assert_eq!("[[1, 0],[0, 1]] mod 7", matrix_gauss.to_string());
150    /// ```
151    ///
152    /// # Panics ...
153    /// - if the modulus is not prime.
154    pub fn gaussian_elimination_prime(self) -> MatZq {
155        assert!(
156            self.get_mod().is_prime(),
157            "The modulus of the matrix is not prime"
158        );
159
160        // Since we only want the echelon form, the permutation `perm` is not relevant.
161        let _ = unsafe { fmpz_mod_mat_rref(std::ptr::null_mut(), &self.matrix) };
162
163        self
164    }
165}
166
167#[cfg(test)]
168mod test_inverse {
169    use crate::{
170        integer::Z,
171        integer_mod_q::{MatZq, Modulus},
172        traits::Gcd,
173    };
174    use std::str::FromStr;
175
176    /// Test whether `inverse` correctly calculates an inverse matrix.
177    #[test]
178    fn inverse_works() {
179        let mat = MatZq::from_str("[[5, 2],[2, 1]] mod 14").unwrap();
180
181        let inv = mat.inverse().unwrap();
182
183        let cmp_inv = MatZq::from_str("[[1, 12],[12, 5]] mod 14").unwrap();
184        assert_eq!(cmp_inv, inv);
185    }
186
187    /// Check if the multiplication of inverse and matrix result in an identity matrix.
188    #[test]
189    fn inverse_correct() {
190        let mat = MatZq::from_str("[[5, 2],[2, 1]] mod 8").unwrap();
191
192        let inv = mat.inverse().unwrap();
193
194        let diag = mat * inv;
195        assert!(diag.is_identity());
196    }
197
198    /// Test whether `inverse` correctly calculates an inverse matrix of an identity.
199    #[test]
200    fn inverse_identity() {
201        let mat = MatZq::identity(3, 3, 8);
202
203        let inv = mat.inverse().unwrap();
204
205        assert_eq!(mat, inv);
206    }
207
208    /// Test whether `inverse` correctly calculates an inverse matrix for big numbers.
209    #[test]
210    fn inverse_big() {
211        let mat = MatZq::from_str(&format!("[[{}, 0],[0, 1]] mod {}", i64::MAX, u64::MAX)).unwrap();
212
213        let inv = mat.inverse().unwrap();
214
215        let cmp_inv =
216            MatZq::from_str(&format!("[[{}, 0],[0, 1]] mod {}", u64::MAX - 2, u64::MAX)).unwrap();
217        assert_eq!(cmp_inv, inv);
218    }
219
220    /// Check if matrix inversion works for slightly larger dimensions.
221    #[test]
222    fn slightly_larger_dimension() {
223        let n = 30;
224        let q = 6;
225
226        let mut matrix = MatZq::sample_uniform(n, n, q);
227        let mut det_matrix = matrix
228            .get_representative_least_nonnegative_residue()
229            .det()
230            .unwrap();
231        while det_matrix == Z::ZERO || det_matrix.gcd(q) != Z::ONE {
232            matrix = MatZq::sample_uniform(n, n, q);
233            det_matrix = matrix
234                .get_representative_least_nonnegative_residue()
235                .det()
236                .unwrap();
237        }
238
239        let inv = matrix.inverse().unwrap();
240
241        let diag = matrix * inv;
242        assert!(diag.is_identity());
243    }
244
245    /// Ensure that a matrix that is not square yields `None` on inversion.
246    #[test]
247    fn inv_none_not_squared() {
248        let mat_1 = MatZq::from_str("[[1, 0, 1],[0, 1, 1]] mod 4").unwrap();
249        let mat_2 = MatZq::from_str("[[1, 0],[0, 1],[1, 0]] mod 82").unwrap();
250
251        assert!(mat_1.inverse().is_none());
252        assert!(mat_2.inverse().is_none());
253    }
254
255    /// Ensure that a matrix that has a determinant of `0` yields `None` on inversion.
256    #[test]
257    fn inv_none_det_zero() {
258        let mat = MatZq::from_str("[[2, 0],[0, 0]] mod 12").unwrap();
259
260        assert!(mat.inverse().is_none());
261    }
262
263    /// Ensure that a matrix that has a determinant co-prime to the modulus
264    /// and not `0` yields `None` on inversion.
265    #[test]
266    fn inv_none_det_coprime() {
267        let mat = MatZq::from_str("[[2, 0],[0, 2]] mod 4").unwrap();
268
269        assert!(mat.inverse().is_none());
270    }
271
272    /// Ensure that a solution is found in random matrices (for testing purposes).
273    #[test]
274    #[ignore]
275    fn random_matrix_modulus() {
276        let mut none_count = 0;
277        let mut correct_count = 0;
278        let mut false_count = 0;
279
280        for i in 0..10000 {
281            let modulus_sample = Z::sample_uniform(2, 10000).unwrap();
282            let row_sample = &Z::sample_uniform(1, 10).unwrap();
283
284            let modulus = Modulus::from(modulus_sample);
285            let mat = MatZq::sample_uniform(row_sample, row_sample, &modulus);
286            if let Some(inverse) = mat.inverse() {
287                let id = &mat * &inverse;
288
289                if id == MatZq::identity(row_sample, row_sample, modulus) {
290                    correct_count += 1;
291                    println!("{i}: Correct");
292                } else {
293                    false_count += 1;
294                    println!("{i}: False");
295                }
296            } else {
297                none_count += 1;
298                println!("{i}: None");
299            }
300        }
301
302        println!("Nones: {none_count}");
303        println!("Corrects: {correct_count}");
304        println!("False: {false_count}");
305    }
306}
307
308#[cfg(test)]
309mod test_inverse_prime {
310    use crate::{integer_mod_q::MatZq, traits::Gcd};
311    use std::str::FromStr;
312
313    /// Test whether `inverse_prime` correctly calculates an inverse matrix.
314    #[test]
315    fn inverse_works() {
316        let mat = MatZq::from_str("[[5, 2],[2, 1]] mod 7").unwrap();
317
318        let inv = mat.inverse_prime().unwrap();
319
320        let cmp_inv = MatZq::from_str("[[1, 5],[5, 5]] mod 7").unwrap();
321        assert_eq!(cmp_inv, inv);
322    }
323
324    /// Check if the multiplication of inverse and matrix result in an identity matrix.
325    #[test]
326    fn inverse_correct() {
327        let mat = MatZq::from_str("[[5, 2],[2, 1]] mod 11").unwrap();
328
329        let inv = mat.inverse_prime().unwrap();
330
331        let diag = mat * inv;
332        assert!(diag.is_identity());
333    }
334
335    /// Check if the multiplication of inverse and matrix result in an identity matrix.
336    #[test]
337    fn inverse_correct_2() {
338        let mat = MatZq::from_str("[[0, 2],[2, 1]] mod 3").unwrap();
339
340        let inv = mat.inverse_prime().unwrap();
341
342        let diag = mat * inv;
343        assert!(diag.is_identity());
344    }
345
346    /// Check if matrix inversion works for slightly larger dimensions.
347    #[test]
348    fn slightly_larger_dimension() {
349        let n = 30;
350        let q = 7;
351
352        let mut matrix = MatZq::sample_uniform(n, n, q);
353        let mut det_matrix = matrix
354            .get_representative_least_nonnegative_residue()
355            .det()
356            .unwrap();
357        while det_matrix == 0 || det_matrix.gcd(q) != 1 {
358            matrix = MatZq::sample_uniform(n, n, q);
359            det_matrix = matrix
360                .get_representative_least_nonnegative_residue()
361                .det()
362                .unwrap();
363        }
364
365        let inv = matrix.inverse_prime().unwrap();
366
367        let diag = matrix * inv;
368        assert!(diag.is_identity());
369    }
370
371    /// Ensure that a matrix that is not square yields `None` on inversion.
372    #[test]
373    fn inv_none_not_squared() {
374        let mat_1 = MatZq::from_str("[[1, 0, 1],[0, 1, 1]] mod 3").unwrap();
375        let mat_2 = MatZq::from_str("[[1, 0],[0, 1],[1, 0]] mod 17").unwrap();
376
377        assert!(mat_1.inverse_prime().is_none());
378        assert!(mat_2.inverse_prime().is_none());
379    }
380
381    /// Ensure that a matrix that has a determinant of `0` yields `None` on inversion.
382    #[test]
383    fn inv_none_det_zero() {
384        let mat = MatZq::from_str("[[2, 0],[0, 0]] mod 11").unwrap();
385
386        assert!(mat.inverse_prime().is_none());
387    }
388
389    /// Ensure that the function panics if a matrix with a non prime modulus is given as input.
390    #[test]
391    #[should_panic]
392    fn not_prime_error() {
393        let mat = MatZq::from_str("[[2, 0],[0, 2]] mod 10").unwrap();
394
395        let _inv = mat.inverse_prime().unwrap();
396    }
397}
398
399#[cfg(test)]
400mod test_gauss {
401    use crate::integer_mod_q::MatZq;
402    use std::str::FromStr;
403
404    /// Test whether `gaussian_elimination_prime` works correctly.
405    #[test]
406    fn gauss_works() {
407        let mat_1 = MatZq::from_str("[[5, 2, 1, 0],[2, 1, 0, 1]] mod 7").unwrap();
408        let mat_2 =
409            MatZq::from_str("[[1, 3, 1, 9],[1, 1, 130, 1],[3, 11, 5, 35]] mod 131").unwrap();
410        let mat_3 =
411            MatZq::from_str("[[5, 0, 2, 1, 0],[5, 0, 2, 1, 0],[2, 0, 1, 0, 1]] mod 7").unwrap();
412
413        let gauss_1 = mat_1.gaussian_elimination_prime();
414        let gauss_2 = mat_2.gaussian_elimination_prime();
415        let gauss_3 = mat_3.gaussian_elimination_prime();
416
417        assert_eq!(
418            gauss_1,
419            MatZq::from_str("[[1, 0, 1, 5],[0, 1, 5, 5]] mod 7").unwrap()
420        );
421        assert_eq!(
422            gauss_2,
423            MatZq::from_str("[[1, 0, 129, 128],[0, 1, 1, 4],[0, 0, 0, 0]] mod 131").unwrap()
424        );
425        assert_eq!(
426            gauss_3,
427            MatZq::from_str("[[1, 0, 0, 1, 5],[0, 0, 1, 5, 5],[0, 0, 0, 0, 0]] mod 7").unwrap()
428        );
429    }
430
431    /// Test whether `gaussian_elimination_prime` yields an error if the inverse
432    /// of an entry can not be computed because the modulus is not prime.
433    #[test]
434    #[should_panic]
435    fn gauss_error() {
436        let mat_1 = MatZq::from_str("[[5, 2, 1, 0],[2, 1, 0, 1]] mod 10").unwrap();
437        let _ = mat_1.gaussian_elimination_prime();
438    }
439}