Skip to main content

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