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}