qfall_math/integer/mat_z/
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::MatZ;
12use crate::{rational::MatQ, traits::MatrixDimensions};
13use flint_sys::fmpq_mat::fmpq_mat_inv;
14
15impl MatZ {
16    /// Returns the inverse of the matrix if it exists (is square and
17    /// has a determinant unequal to `0`) and `None` otherwise.
18    ///
19    /// # Examples
20    /// ```
21    /// use qfall_math::integer::MatZ;
22    /// use qfall_math::traits::*;
23    /// use std::str::FromStr;
24    ///
25    /// let mut matrix = MatZ::from_str("[[1, 2],[3, 4]]").unwrap();
26    /// let matrix_invert = matrix.inverse().unwrap();
27    /// ```
28    pub fn inverse(&self) -> Option<MatQ> {
29        // check if matrix is square
30        if self.get_num_rows() != self.get_num_columns() {
31            return None;
32        }
33
34        // check if determinant is not `0`, create new matrix to store inverted result in
35        let mut out = MatQ::new(self.get_num_rows(), self.get_num_columns());
36        match unsafe { fmpq_mat_inv(&mut out.matrix, &MatQ::from(self).matrix) } {
37            0 => None,
38            _ => Some(out),
39        }
40    }
41}
42
43#[cfg(test)]
44mod test_inverse {
45    use crate::{integer::MatZ, rational::MatQ};
46    use std::str::FromStr;
47
48    /// Test whether `inverse` correctly calculates an inverse matrix
49    #[test]
50    fn inverse_works() {
51        let mat_1 = MatZ::from_str("[[5, 2, 0],[2, 1, 0],[0, 0, 1]]").unwrap();
52        let mat_2 = MatZ::from_str(&format!("[[{}]]", i64::MAX)).unwrap();
53        let mat_3 = MatZ::from_str("[[-1, 0],[0, 1]]").unwrap();
54
55        let cmp_inv_1 = MatQ::from_str("[[1, -2, 0],[-2, 5, 0],[0, 0, 1]]").unwrap();
56        let cmp_inv_2 = MatQ::from_str(&format!("[[1/{}]]", i64::MAX)).unwrap();
57        let cmp_inv_3 = MatQ::from_str("[[-1, 0],[0, 1]]").unwrap();
58
59        let inv_1 = mat_1.inverse().unwrap();
60        let inv_2 = mat_2.inverse().unwrap();
61        let inv_3 = mat_3.inverse().unwrap();
62
63        assert_eq!(cmp_inv_1, inv_1);
64        assert_eq!(cmp_inv_2, inv_2);
65        assert_eq!(cmp_inv_3, inv_3);
66    }
67
68    /// Check if the multiplication of inverse and matrix result in an identity matrix
69    #[test]
70    fn inverse_correct() {
71        let mat = MatZ::from_str("[[5, 2],[2, 1]]").unwrap();
72        let mat_q = MatQ::from(&mat);
73        let cmp = MatQ::identity(2, 2);
74
75        let inv = mat.inverse().unwrap();
76        let diag = &mat_q * &inv;
77
78        assert_eq!(cmp, diag);
79    }
80
81    /// Ensure that a matrix that is not square yields `None` on inversion.
82    #[test]
83    fn inv_none_not_squared() {
84        let mat_1 = MatZ::from_str("[[1, 0, 1],[0, 1, 1]]").unwrap();
85        let mat_2 = MatZ::from_str("[[1, 0],[0, 1],[1, 0]]").unwrap();
86
87        assert!(mat_1.inverse().is_none());
88        assert!(mat_2.inverse().is_none());
89    }
90
91    /// Ensure that a matrix that has a determinant of `0` yields `None` on inversion.
92    #[test]
93    fn inv_none_det_zero() {
94        let mat = MatZ::from_str("[[2, 0],[0, 0]]").unwrap();
95
96        assert!(mat.inverse().is_none());
97    }
98}