1use 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 pub fn inverse(&self) -> Option<MatZq> {
36 if self.modulus.is_prime() {
37 return self.inverse_prime();
38 }
39
40 if let Ok(det) = self.get_representative_least_nonnegative_residue().det() {
42 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 let mut e_i = MatZq::identity(dimensions, 1, self.get_mod());
51
52 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 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 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 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 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 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 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]
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 #[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]
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]
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 #[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 #[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 #[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 #[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 #[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]
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 #[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 #[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 #[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 #[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 #[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 #[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]
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]
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}