1use 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 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 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 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 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 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 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 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 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]
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 #[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]
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]
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 #[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 #[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 #[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 #[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 #[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]
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 #[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 #[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 #[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 #[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 #[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 #[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]
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]
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}