qfall_math/integer_mod_q/mat_zq/
solve.rs

1// Copyright © 2023 Marcel Luca Schmidt, Marvin Beckmann
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 implementations to solve systems of linear equations
10//! over [`MatZq`] with arbitrary moduli.
11
12use super::MatZq;
13use crate::{integer::Z, integer_mod_q::Zq, traits::*, utils::Factorization};
14
15impl MatZq {
16    /// Computes a solution for a system of linear equations under a certain modulus.
17    /// It solves `Ax = y` for `x` with `A` being a [`MatZq`] value.
18    /// If no solution is found, `None` is returned.
19    /// The function uses Gaussian elimination together with Factor refinement
20    /// to split the modulus and the Chinese remainder theorem and Hensel lifting
21    /// to combine solutions under the split modulus.
22    /// For Hensel lifting we use the method from [\[1\]].
23    ///
24    /// Note that this function does not compute a solution whenever there is one.
25    /// If the matrix has not full rank under a modulus that divides the given one,
26    /// `None` may be returned even if the system may be solvable.
27    /// If the number of columns exceeds the number of rows by a factor of 2,
28    /// this is very unlikely to happen.
29    ///
30    /// Parameters:
31    /// - `y`: the syndrome for which a solution has to be computed.
32    ///
33    /// Returns a solution for the linear system or `None`, if none could be computed.
34    ///
35    /// # Examples
36    /// ```
37    /// use qfall_math::integer_mod_q::MatZq;
38    /// use std::str::FromStr;
39    ///
40    /// let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 8").unwrap();
41    /// let y = MatZq::from_str("[[3],[5]] mod 8").unwrap();
42    /// let x = mat.solve_gaussian_elimination(&y).unwrap();
43    ///
44    /// assert_eq!(y, mat*x);
45    /// ```
46    ///
47    /// # Panics ...
48    /// - if the the number of rows of the matrix and the syndrome are different.
49    /// - if the syndrome is not a column vector.
50    /// - if the moduli mismatch.
51    ///
52    /// # Reference
53    /// - \[1\] John D. Dixon.
54    ///   "Exact Solution of Linear Equations Using P-Adic Expansions"
55    ///   <https://link.springer.com/article/10.1007/BF01459082>
56    pub fn solve_gaussian_elimination(&self, y: &MatZq) -> Option<MatZq> {
57        assert!(y.is_column_vector(), "The syndrome is not a column vector.");
58        assert_eq!(
59            y.get_num_rows(),
60            self.get_num_rows(),
61            "The syndrome and the matrix have a different number of rows."
62        );
63        assert_eq!(
64            y.get_mod(),
65            self.get_mod(),
66            "The syndrome and the matrix have a different modulus"
67        );
68
69        // Append the solution vector to easily perform gaussian elimination.
70        let mut matrix = self.concat_horizontal(y).unwrap();
71
72        // Saves the indices of row and column, where we created a 1 entry
73        // such that we do not have to go through the matrix afterwards.
74        let mut indices = Vec::with_capacity(self.get_num_columns() as usize);
75
76        for column_nr in 0..self.get_num_columns() {
77            let used_rows: Vec<i64> = indices.iter().map(|(row, _)| *row).collect();
78            // Try to solve the system with the current modulus or
79            // try to find a not invertible entry to split the modulus.
80            if let Some((row_nr, inv)) =
81                find_invertible_entry_column(&matrix, column_nr, &used_rows)
82            {
83                // Save the position of `1` for that column.
84                indices.push((row_nr, column_nr));
85
86                unsafe { matrix.gauss_row_reduction(row_nr, column_nr, inv) };
87            } else if let Some(row_nr) =
88                find_not_invertible_entry_column(&matrix, column_nr, &used_rows)
89            {
90                let entry: Z = unsafe { matrix.get_entry_unchecked(row_nr, column_nr) };
91
92                // Factorize the modulus with the found entry, compute solutions
93                // for the system under the split modulus and use the
94                // Chinese Remainder Theorem to compute a solution based on these
95                // sub-solutions.
96                return self.factorization_and_crt(y, entry);
97            }
98        }
99
100        // Set the entries of the output vector using the indices vector.
101        let mut out = MatZq::new(self.get_num_columns(), 1, matrix.get_mod());
102        for (i, j) in indices.iter() {
103            let entry: Z = unsafe { matrix.get_entry_unchecked(*i, matrix.get_num_columns() - 1) };
104            unsafe { out.set_entry_unchecked(*j, 0, entry) };
105        }
106
107        Some(out)
108    }
109
110    /// Performs a row reduction from gaussian elimination, given an entry of
111    /// the matrix and its inverse.
112    /// Multiplies the given row of a matrix by the given inverse and reduces all
113    /// other rows such that they have zeros in the given column.
114    ///
115    /// Parameters:
116    /// - `row_nr`: the row where the entry is located
117    /// - `column_nr`: the column where the entry is located
118    /// - `inverse`: the inverse of the entry located at (row_nr, column_nr)
119    ///
120    /// # Safety
121    /// The user must ensure that `row_nr` and `col_nr` refer to entries in `self`.
122    /// Otherwise, unintended behavior can occur.
123    unsafe fn gauss_row_reduction(&mut self, row_nr: i64, column_nr: i64, inverse: Zq) {
124        let row = inverse * self.get_row(row_nr).unwrap();
125        unsafe { self.set_row_unchecked(row_nr, &row, 0) };
126
127        // Set all other entries in that column to `0` (gaussian elimination).
128        for row_nr in (0..self.get_num_rows()).filter(|x| *x != row_nr) {
129            let old_row = unsafe { self.get_row_unchecked(row_nr) };
130            let entry: Z = unsafe { old_row.get_entry_unchecked(0, column_nr) };
131            if !entry.is_zero() {
132                let new_row = &old_row - entry * &row;
133                unsafe { self.set_row_unchecked(row_nr, &new_row, 0) };
134            }
135        }
136    }
137
138    /// Computes a solution for a system of linear equations under a certain modulus.
139    /// It solves `Ax = y` for `x` with `A` being a [`MatZq`] value by first computing a
140    /// factorization of the modulus, given an entry of the matrix that is not co-prime
141    /// to the modulus.
142    /// After that, it computes solutions for the system under the new factors and
143    /// combines these solutions using the Chinese Remainder Theorem.
144    /// If no solution is found, `None` is returned.
145    ///
146    /// Note that this function does not compute a solution whenever there is one.
147    /// If the matrix has not full rank under a modulus that divides the given one,
148    /// `None` may be returned even if the system may be solvable.
149    /// If the number of columns exceeds the number of rows by a factor of 2,
150    /// this is very unlikely to happen.
151    ///
152    /// Note that this function is a part of `solve_gaussian_elimination` and
153    /// does not check for the correctness of the given parameters.
154    ///
155    /// Parameters:
156    /// - `y`: the syndrome for which a solution has to be computed.
157    /// - `entry`: a [`Z`] value that is not co-prime to the modulus.
158    ///
159    /// Returns a solution for the linear system or `None`, if none could be computed.
160    fn factorization_and_crt(&self, y: &MatZq, entry: Z) -> Option<MatZq> {
161        let modulus = Z::from(self.get_mod());
162        let gcd = modulus.gcd(entry);
163
164        let mut fac = Factorization::from((&gcd, &(modulus / &gcd).get_numerator()));
165        fac.refine();
166        let fac_vec = Vec::<(Z, u64)>::from(&fac);
167
168        // Solve the equation under the different moduli.
169        let mut solutions: Vec<MatZq> = Vec::with_capacity(fac_vec.len());
170        for factor in fac_vec.iter() {
171            let mut matrix = self.clone();
172            let mut y = y.clone();
173            matrix.change_modulus(factor.0.pow(factor.1).unwrap());
174            y.change_modulus(factor.0.pow(factor.1).unwrap());
175
176            // Compute a solution under the modulus z^a.
177            if factor.1 > 1 {
178                solutions.push(matrix.solve_hensel(&y, &factor.0, &factor.1)?);
179            // Compute a solution under the new modulus.
180            } else {
181                solutions.push(matrix.solve_gaussian_elimination(&y)?);
182            }
183        }
184        // Connect the solutions via the Chinese Remainder Theorem.
185        self.crt_mat_zq(solutions, fac_vec)
186    }
187
188    /// Given a system of linear equations `Ax = y` with `A` being a [`MatZq`] value,
189    /// this function combines solutions `x`for this system under co-prime moduli
190    /// with the Chinese remainder theorem.
191    /// If no solution exists, `None` is returned.
192    ///
193    /// Note that this function does not check for the correctness of the given
194    /// parameters.
195    ///
196    /// Parameters:
197    /// - `solutions`: the solutions under the co-prime moduli.
198    /// - `moduli`: the moduli of the solutions in the form `z^a`.
199    ///
200    /// Returns a solution for the linear system or `None`, if none could be computed.
201    ///
202    /// # Panics ...
203    /// - if the the number of elements in `solutions` is greater than the number
204    ///   of elements in `moduli`.
205    fn crt_mat_zq(&self, mut solutions: Vec<MatZq>, mut moduli: Vec<(Z, u64)>) -> Option<MatZq> {
206        while solutions.len() > 1 {
207            // Compute Bézout’s identity: a x_1 + b x_2 = 1
208            // by computing xgcd(x_1, x_2).
209            let x_2_exponent = moduli.pop().unwrap();
210            let x_1_exponent = moduli.pop().unwrap();
211            let x_1 = x_1_exponent.0.pow(x_1_exponent.1).unwrap();
212            let x_2 = x_2_exponent.0.pow(x_2_exponent.1).unwrap();
213            let (_gcd, a, b) = x_1.xgcd(&x_2);
214            let mut s_2 = solutions.pop().unwrap();
215            let mut s_1 = solutions.pop().unwrap();
216            s_2.change_modulus(Z::from(s_2.get_mod()) * Z::from(s_1.get_mod()));
217            s_1.change_modulus(s_2.get_mod());
218            solutions.push(s_2 * a * &x_1 + s_1 * b * &x_2);
219            moduli.push((x_1 * x_2, 1));
220        }
221        solutions.pop()
222    }
223
224    /// Computes a solution for a system of linear equations under a modulus
225    /// of the form `z^a` with the help of [\[1\]].
226    /// It solves `Ax = y` for `x` with `A` being a [`MatZq`] value.
227    /// If no solution is found, `None` is returned.
228    ///
229    /// Note that this function does not compute a solution whenever there is one.
230    /// If the matrix has not full rank under a modulus that divides the given one,
231    /// `None` may be returned even if the system may be solvable.
232    /// If the number of columns exceeds the number of rows by a factor of 2,
233    /// this is very unlikely to happen.
234    ///
235    /// Note that this function is a part of `solve_gaussian_elimination` and
236    /// does not check for the correctness of the given parameters.
237    ///
238    /// Parameters:
239    /// - `y`: the syndrome for which a solution has to be computed.
240    /// - `base`: the base of the modulus.
241    /// - `power`: the power of the modulus.
242    ///
243    /// Returns a solution for the linear system or `None`, if none could be computed.
244    fn solve_hensel(&self, y: &MatZq, base: &Z, power: &u64) -> Option<MatZq> {
245        // Set `matrix_base` to the given matrix under `base` as the modulus to
246        // compute a solution for the system under `base` as modulus.
247        let mut matrix_base = self.clone();
248        matrix_base.change_modulus(base);
249
250        // Concatenate the matrix with the identity matrix under `base`
251        // as its modulus to apply gaussian elimination on it.
252        let mut matrix_identity_base_gauss = matrix_base
253            .concat_horizontal(&MatZq::identity(
254                self.get_num_rows(),
255                self.get_num_rows(),
256                base,
257            ))
258            .unwrap();
259
260        // Saves the indices of row and column, where we created a 1 entry
261        // such that we do not have to go through the matrix afterwards.
262        let mut indices: Vec<(i64, i64)> = Vec::with_capacity(self.get_num_columns() as usize);
263        let mut used_rows: Vec<i64> = Vec::with_capacity(self.get_num_columns() as usize);
264        let mut row_count = 0;
265
266        // Saves the permutation of the gaussian elimination.
267        let mut permutation: Vec<i64> = Vec::with_capacity(self.get_num_rows() as usize);
268        for i in 0..self.get_num_rows() {
269            permutation.push(i);
270        }
271        for column_nr in 0..self.get_num_columns() {
272            if !indices.is_empty() && indices[indices.len() - 1].0 >= self.get_num_rows() {
273                break;
274            }
275
276            // Try to solve the system under the current modulus.
277            if let Some((row_nr, inv)) =
278                find_invertible_entry_column(&matrix_identity_base_gauss, column_nr, &used_rows)
279            {
280                unsafe { matrix_identity_base_gauss.gauss_row_reduction(row_nr, column_nr, inv) };
281
282                if row_count != row_nr {
283                    matrix_identity_base_gauss
284                        .swap_rows(row_count, row_nr)
285                        .unwrap();
286
287                    permutation.swap(row_count.try_into().unwrap(), row_nr.try_into().unwrap());
288                }
289
290                // Save the position of `1` for that row.
291                indices.push((row_count, column_nr));
292                used_rows.push(indices[indices.len() - 1].0);
293                row_count += 1;
294            // Search for a not invertible entry to divide the modulus.
295            } else if let Some(row_nr) =
296                find_not_invertible_entry_column(&matrix_identity_base_gauss, column_nr, &used_rows)
297            {
298                // Factorize the modulus with the found entry, compute solutions
299                // for the system under the split modulus and use the
300                // Chinese Remainder Theorem to compute a solution based on these
301                // sub-solutions.
302                let entry: Z =
303                    unsafe { matrix_identity_base_gauss.get_entry_unchecked(row_nr, column_nr) };
304                self.factorization_and_crt(y, entry);
305            }
306        }
307
308        // Return `None` if the matrix has no full rank.
309        if indices.is_empty()
310            || indices[indices.len() - 1].0 + 1 < matrix_identity_base_gauss.get_num_rows()
311        {
312            return None;
313        }
314
315        // Pick the first columns out of the original matrix that form an invertible matrix.
316        // Those columns exist, since the matrix was checked to be full rank.
317        let mut invertible_matrix = MatZq::new(
318            matrix_identity_base_gauss.get_num_rows(),
319            matrix_identity_base_gauss.get_num_rows(),
320            self.get_mod(),
321        );
322        for (current_column, (_row_nr, column_nr)) in indices.iter().enumerate() {
323            unsafe {
324                invertible_matrix.set_column_unchecked(current_column as i64, self, *column_nr)
325            };
326        }
327
328        // The inverse of the previously picked square matrix consists of the last
329        // columns of `matrix_identity_base_gauss`, since we concatenated an identity matrix.
330        let mut matrix_identity_gauss = matrix_identity_base_gauss;
331        matrix_identity_gauss.change_modulus(self.get_mod());
332        let mut matrix_base_inv = MatZq::new(
333            matrix_identity_gauss.get_num_rows(),
334            matrix_identity_gauss.get_num_rows(),
335            self.get_mod(),
336        );
337        for row_nr in 0..matrix_identity_gauss.get_num_rows() {
338            unsafe {
339                matrix_base_inv.set_column_unchecked(
340                    row_nr,
341                    &matrix_identity_gauss,
342                    row_nr + self.get_num_columns(),
343                )
344            };
345        }
346
347        // Use the method from [\[1\]]
348        // to compute a solution for the original system.
349        let mut b_i = y.clone();
350        let mut x_i = &matrix_base_inv * &b_i;
351        let mut x = x_i.clone();
352        for i in 1..*power {
353            b_i = MatZq::from((
354                &(unsafe {
355                    (b_i - &invertible_matrix * x_i)
356                        .get_representative_least_nonnegative_residue()
357                        .div_exact(base)
358                }),
359                &self.get_mod(),
360            ));
361            x_i = &matrix_base_inv * &b_i;
362            x += &x_i * &base.pow(i).unwrap();
363        }
364
365        let mut out = MatZq::new(self.get_num_columns(), 1, self.get_mod());
366        for (current_row_x, (_row_nr, column_nr)) in indices.into_iter().enumerate() {
367            let entry: Z = unsafe { x.get_entry_unchecked(current_row_x as i64, 0) };
368            unsafe { out.set_entry_unchecked(column_nr, 0, entry) };
369        }
370
371        Some(out)
372    }
373}
374
375/// Returns the row of the first invertible entry of that column
376/// together with that specific invertible element.
377/// If there is no invertible element, `None` is returned.
378/// The rows specified in `used_rows` will be ignored.
379///
380/// Parameters:
381/// - `matrix`: the matrix in which entries are searched for
382/// - `column`: the column for which we are trying to find an invertible element
383/// - `used_rows`: the rows which are not scanned for invertible elements
384///
385/// Returns the row and the entry of the first invertible element in that column, and
386/// `None` if there is no such element
387fn find_invertible_entry_column(
388    matrix: &MatZq,
389    column: i64,
390    used_rows: &[i64],
391) -> Option<(i64, Zq)> {
392    for i in (0..matrix.get_num_rows()).filter(|x| !used_rows.contains(x)) {
393        let entry: Zq = unsafe { matrix.get_entry_unchecked(i, column) };
394        if let Ok(inv) = entry.pow(-1) {
395            return Some((i, inv));
396        }
397    }
398    None
399}
400
401/// Returns the row of the first not invertible entry of that column, that is not 0.
402/// If there is no not invertible element unequal to 0, `None` is returned.
403/// The rows specified in `used_rows` will be ignored.
404///
405/// Parameters:
406/// - `matrix`: the matrix in which entries are searched for
407/// - `column`: the column for which we are trying to find an invertible element
408/// - `used_rows`: the rows which are not scanned for invertible elements
409///
410/// Returns the row and the entry of the first not invertible element in that column,
411/// that is not 0, and `None` if there is no such element
412fn find_not_invertible_entry_column(matrix: &MatZq, column: i64, used_rows: &[i64]) -> Option<i64> {
413    for i in (0..matrix.get_num_rows()).filter(|x| !used_rows.contains(x)) {
414        let entry: Zq = unsafe { matrix.get_entry_unchecked(i, column) };
415        if !entry.is_zero() {
416            if let Err(_inv) = entry.pow(-1) {
417                return Some(i);
418            }
419        }
420    }
421    None
422}
423
424#[cfg(test)]
425mod test_solve_gauss {
426    use crate::{
427        integer::Z,
428        integer_mod_q::{MatZq, Modulus},
429        traits::Pow,
430    };
431    use std::str::FromStr;
432
433    /// Ensure that a solution is found with prime modulus.
434    #[test]
435    fn solution_prime_modulus() {
436        let mat = MatZq::from_str("[[5, 6],[11, 12]] mod 13").unwrap();
437        let y = MatZq::from_str("[[5],[2]] mod 13").unwrap();
438
439        let x = mat.solve_gaussian_elimination(&y).unwrap();
440
441        let cmp_sol = MatZq::from_str("[[5],[1]] mod 13").unwrap();
442        assert_eq!(cmp_sol, x);
443    }
444
445    /// Ensure that a solution is found with prime modulus and more rows than columns.
446    #[test]
447    fn solution_more_rows_than_columns_prime() {
448        let mat = MatZq::from_str("[[5, 6],[11, 12],[11, 12]] mod 13").unwrap();
449        let y = MatZq::from_str("[[5],[2],[2]] mod 13").unwrap();
450
451        let x = mat.solve_gaussian_elimination(&y).unwrap();
452
453        let cmp_sol = MatZq::from_str("[[5],[1]] mod 13").unwrap();
454        assert_eq!(cmp_sol, x);
455    }
456
457    /// Ensure that a solution is found with invertible columns.
458    #[test]
459    fn solution_invertible_columns() {
460        let mat = MatZq::from_str("[[3, 1],[11, 13]] mod 20").unwrap();
461        let y = MatZq::from_str("[[5],[2]] mod 20").unwrap();
462
463        let x = mat.solve_gaussian_elimination(&y).unwrap();
464
465        let cmp_sol = MatZq::from_str("[[11],[12]] mod 20").unwrap();
466        assert_eq!(cmp_sol, x);
467    }
468
469    /// Ensure that a solution is found, even if the matrix contains a
470    /// column that is not invertible.
471    #[test]
472    fn solution_with_one_uninvertible_column() {
473        let mat = MatZq::from_str("[[2, 1],[3, 1]] mod 210").unwrap();
474        let y = MatZq::from_str("[[5],[2]] mod 210").unwrap();
475
476        let x = mat.solve_gaussian_elimination(&y).unwrap();
477
478        let cmp_sol = MatZq::from_str("[[207],[11]] mod 210").unwrap();
479        assert_eq!(cmp_sol, x);
480    }
481
482    /// Ensure that a solution is found, even if the matrix contains no
483    /// column that is invertible.
484    #[test]
485    fn solution_without_invertible_columns() {
486        let mat = MatZq::from_str("[[2, 1],[6, 2]] mod 6").unwrap();
487        let y = MatZq::from_str("[[5],[2]] mod 6").unwrap();
488
489        let x = mat.solve_gaussian_elimination(&y).unwrap();
490
491        let cmp_sol = MatZq::from_str("[[2],[1]] mod 6").unwrap();
492        assert_eq!(cmp_sol, x);
493    }
494
495    /// Ensure that a solution is found, even if the modulus is a power of a prime.
496    #[test]
497    fn solution_prime_power() {
498        let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 8").unwrap();
499        let y = MatZq::from_str("[[0],[1]] mod 8").unwrap();
500
501        let x = mat.solve_gaussian_elimination(&y).unwrap();
502
503        assert_eq!(MatZq::from_str("[[0],[3],[6]] mod 8").unwrap(), x)
504    }
505
506    /// Ensure that the trivial solution can always be computed.
507    #[test]
508    fn trivial() {
509        let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 8").unwrap();
510        let y = MatZq::from_str("[[0],[0]] mod 8").unwrap();
511
512        let x = mat.solve_gaussian_elimination(&y).unwrap();
513
514        assert_eq!(MatZq::new(3, 1, mat.get_mod()), x);
515    }
516
517    /// Ensure that a solution containing only one vector of the matrix is found.
518    #[test]
519    fn simple() {
520        let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 1048576").unwrap();
521        let y = MatZq::from_str("[[0],[1]] mod 1048576").unwrap();
522
523        let x = mat.solve_gaussian_elimination(&y).unwrap();
524
525        assert_eq!(y, mat * x);
526    }
527
528    /// Ensure that a solution is found, even if the modulus is a large power of a prime.
529    #[test]
530    fn large_modulus() {
531        let modulus = Modulus::from(Z::from(2).pow(50).unwrap());
532
533        // matrix of size `n x 2n * log q`, hence almost always invertible
534        let mat = MatZq::sample_uniform(10, 10 * 2 * 50, &modulus);
535        let y = MatZq::sample_uniform(10, 1, &modulus);
536
537        let x = mat.solve_gaussian_elimination(&y).unwrap();
538
539        assert_eq!(y, mat * x)
540    }
541
542    /// Ensure that a solution is found, even if a matrix in `solve_hensel` has
543    /// rows containing only zeros after gaussian elimination.
544    #[test]
545    #[ignore]
546    fn solution_zero_rows() {
547        let mat = MatZq::from_str("[[6, 1],[3, 1]] mod 36").unwrap();
548        let y = MatZq::from_str("[[6],[3]] mod 36").unwrap();
549
550        let x = mat.solve_gaussian_elimination(&y).unwrap();
551
552        let cmp_sol = MatZq::from_str("[[1],[0]] mod 6").unwrap();
553        assert_eq!(cmp_sol, x);
554    }
555
556    /// Ensure that a solution is found with prime modulus and more rows than columns
557    /// (This test does not work with the current implementation).
558    #[test]
559    #[ignore]
560    fn solution_more_rows() {
561        let mat = MatZq::from_str("[[5, 6],[11, 12],[11, 12]] mod 20").unwrap();
562        let y = MatZq::from_str("[[5],[2],[2]] mod 20").unwrap();
563
564        let x = mat.solve_gaussian_elimination(&y).unwrap();
565
566        let cmp_sol = MatZq::from_str("[[5],[1]] mod 20").unwrap();
567        assert_eq!(cmp_sol, x);
568    }
569
570    /// Ensure that a solution is found in random matrices (for testing purposes).
571    #[test]
572    #[ignore]
573    fn random_matrix_modulus() {
574        // modulus: 2:100,      rows: 1:10,     columns: 1:10    => <7% false Nones
575        // modulus: 2:10000,    rows: 1:10,     columns: 1:10    => <7% false Nones
576        // modulus: 2:10000,    rows: 1:10,     columns: 10:20   => <0.5% false Nones
577        // modulus: 2:10000,    rows: 50:100,   columns: 100:200 => not measurable
578
579        let mut none_count = 0;
580        let mut correct_count = 0;
581        let mut false_count = 0;
582
583        for i in 0..1000 {
584            let modulus_sample = Z::sample_uniform(2, 10000).unwrap();
585            let row_sample = &Z::sample_uniform(50, 100).unwrap();
586            let column_sample = &Z::sample_uniform(100, 200).unwrap();
587
588            let modulus = Modulus::from(modulus_sample);
589            let mat = MatZq::sample_uniform(row_sample, column_sample, &modulus);
590            let x = MatZq::sample_uniform(column_sample, 1, &modulus);
591            let y = &mat * &x;
592
593            if let Some(solution) = mat.solve_gaussian_elimination(&y) {
594                if &mat * &solution == y {
595                    correct_count += 1;
596                    println!("{i}: Correct!");
597                } else {
598                    false_count += 1;
599                    println!("{i}: False");
600                    println!("\t Matrix: {mat} \n\t y: {y} \n\t x: {x}");
601                }
602            } else {
603                none_count += 1;
604                println!("{i}: None");
605                println!("\t Matrix: {mat} \n\t y: {y} \n\t x: {x}");
606            }
607        }
608
609        println!("Nones: {none_count}");
610        println!("Corrects: {correct_count}");
611        println!("False: {false_count}");
612    }
613
614    /// Ensure that for different moduli the function panics.
615    #[test]
616    #[should_panic]
617    fn different_moduli() {
618        let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 8").unwrap();
619        let y = MatZq::from_str("[[0],[0]] mod 7").unwrap();
620        let _ = mat.solve_gaussian_elimination(&y).unwrap();
621    }
622
623    /// Ensure that for different number of rows the function panics.
624    #[test]
625    #[should_panic]
626    fn different_nr_rows() {
627        let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 8").unwrap();
628        let y = MatZq::from_str("[[0],[0],[0]] mod 8").unwrap();
629        let _ = mat.solve_gaussian_elimination(&y).unwrap();
630    }
631
632    /// Ensure that for non-column vectors, the function panics.
633    #[test]
634    #[should_panic]
635    fn not_column_vector() {
636        let mat = MatZq::from_str("[[2, 2, 3],[2, 5, 7]] mod 8").unwrap();
637        let y = MatZq::from_str("[[0, 1],[0, 1]] mod 8").unwrap();
638        let _ = mat.solve_gaussian_elimination(&y).unwrap();
639    }
640}
641
642#[cfg(test)]
643mod test_find_invertible_entry_column {
644    use crate::{
645        integer::Z,
646        integer_mod_q::{MatZq, mat_zq::solve::find_invertible_entry_column},
647    };
648    use std::str::FromStr;
649
650    /// Ensure that the inverse of the first element is returned with the correct entry
651    /// if it has an inverse.
652    #[test]
653    fn find_correct_entry() {
654        let mat = MatZq::from_str("[[7],[5]] mod 8").unwrap();
655
656        let (i, entry) = find_invertible_entry_column(&mat, 0, &Vec::new()).unwrap();
657        assert_eq!(0, i);
658        assert_eq!(
659            Z::from(7),
660            entry.get_representative_least_nonnegative_residue()
661        );
662
663        let (i, entry) = find_invertible_entry_column(&mat, 0, [0].as_ref()).unwrap();
664        assert_eq!(1, i);
665        assert_eq!(
666            Z::from(5),
667            entry.get_representative_least_nonnegative_residue()
668        );
669
670        let invert = find_invertible_entry_column(&mat, 0, [0, 1].as_ref());
671        assert!(invert.is_none())
672    }
673}
674
675#[cfg(test)]
676mod test_find_uninvertible_entry_column {
677    use crate::integer_mod_q::{MatZq, mat_zq::solve::find_not_invertible_entry_column};
678    use std::str::FromStr;
679
680    /// Ensure that the first element is returned, that is not invertible
681    /// (if no entries are invertible in a column).
682    #[test]
683    fn find_correct_entry() {
684        let mat = MatZq::from_str("[[4],[2]] mod 8").unwrap();
685
686        let i = find_not_invertible_entry_column(&mat, 0, &Vec::new()).unwrap();
687        assert_eq!(0, i);
688
689        let i = find_not_invertible_entry_column(&mat, 0, [0].as_ref()).unwrap();
690        assert_eq!(1, i);
691
692        let invert = find_not_invertible_entry_column(&mat, 0, [0, 1].as_ref());
693        assert!(invert.is_none())
694    }
695}