qfall_math/integer/mat_poly_over_z/
properties.rs

1// Copyright © 2023 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//! Implementations to check certain properties of [`MatPolyOverZ`]
10//! This includes checks such as squareness.
11
12use super::MatPolyOverZ;
13use crate::{
14    integer::Z,
15    traits::{MatrixDimensions, MatrixGetEntry},
16};
17use flint_sys::fmpz_poly_mat::{fmpz_poly_mat_is_one, fmpz_poly_mat_is_zero, fmpz_poly_mat_rank};
18
19impl MatPolyOverZ {
20    /// Checks if a [`MatPolyOverZ`] is a identity matrix, i.e.
21    /// all entries on the diagonal are the constant polynomial `1` and `0` elsewhere.
22    ///
23    /// Returns `true` if the matrix is the identity and `false` otherwise.
24    ///
25    /// # Examples
26    /// ```
27    /// use std::str::FromStr;
28    /// use qfall_math::integer::MatPolyOverZ;
29    ///
30    /// let matrix = MatPolyOverZ::from_str("[[1  1, 0],[0, 1  1]]").unwrap();
31    ///
32    /// assert!(matrix.is_identity());
33    /// ```
34    pub fn is_identity(&self) -> bool {
35        1 == unsafe { fmpz_poly_mat_is_one(&self.matrix) }
36    }
37
38    /// Checks if a [`MatPolyOverZ`] is a square matrix, i.e.
39    /// the number of rows and columns is identical.
40    ///
41    /// Returns `true` if the number of rows and columns is identical.
42    ///
43    /// # Examples
44    /// ```
45    /// use std::str::FromStr;
46    /// use qfall_math::integer::MatPolyOverZ;
47    ///
48    /// let matrix = MatPolyOverZ::from_str("[[1  1, 0],[0, 1  1]]").unwrap();
49    /// let check = matrix.is_square();
50    /// # assert!(check);
51    /// ```
52    pub fn is_square(&self) -> bool {
53        self.get_num_columns() == self.get_num_rows()
54    }
55
56    /// Checks if a [`MatPolyOverZ`] is a zero matrix, i.e.
57    /// all entries are the constant polynomial `0` everywhere.
58    ///
59    /// Returns `true` if the matrix is zero and `false` otherwise.
60    ///
61    /// # Examples
62    /// ```
63    /// use std::str::FromStr;
64    /// use qfall_math::integer::MatPolyOverZ;
65    ///
66    /// let matrix = MatPolyOverZ::from_str("[[0, 0],[0, 0]]").unwrap();
67    /// let check = matrix.is_zero();
68    /// # assert!(check);
69    /// ```
70    pub fn is_zero(&self) -> bool {
71        // we have to test squareness manually, since FLINT does not check this
72        // directly with their method
73        unsafe { 0 != fmpz_poly_mat_is_zero(&self.matrix) }
74    }
75
76    /// Checks if a [`MatPolyOverZ`] is symmetric.
77    ///
78    /// Returns `true` if we have `a_ij == a_ji` for all i,j.
79    ///
80    /// # Examples
81    /// ```
82    /// use qfall_math::integer::MatPolyOverZ;
83    ///
84    /// let value = MatPolyOverZ::identity(2,2);
85    /// assert!(value.is_symmetric());
86    /// ```
87    pub fn is_symmetric(&self) -> bool {
88        if !self.is_square() {
89            return false;
90        }
91        for row in 0..self.get_num_rows() {
92            for column in 0..row {
93                if unsafe {
94                    self.get_entry_unchecked(row, column) != self.get_entry_unchecked(column, row)
95                } {
96                    return false;
97                }
98            }
99        }
100        true
101    }
102
103    /// Returns the rank of the matrix.
104    ///
105    /// # Examples
106    /// ```
107    /// use qfall_math::integer::MatPolyOverZ;
108    /// use std::str::FromStr;
109    ///
110    /// let matrix = MatPolyOverZ::from_str("[[1  1, 0, 0],[0, 0, 1  1]]").unwrap();
111    ///
112    /// let rank = matrix.rank();
113    /// ```
114    pub fn rank(&self) -> Z {
115        Z::from(unsafe { fmpz_poly_mat_rank(&self.matrix) })
116    }
117}
118
119#[cfg(test)]
120mod test_is_identity {
121    use crate::integer::MatPolyOverZ;
122    use std::str::FromStr;
123
124    /// Ensure that true is returned for a 1x1, 2x2, 3x3, 2x3, 3x2 identity matrix.
125    #[test]
126    fn identity_true() {
127        let matrix_1x1 = MatPolyOverZ::from_str("[[1  1]]").unwrap();
128        let matrix_2x2 = MatPolyOverZ::from_str("[[1  1, 0],[0, 1  1]]").unwrap();
129        let matrix_3x3 =
130            MatPolyOverZ::from_str("[[1  1, 0, 0],[0, 1  1, 0],[0, 0, 1  1]]").unwrap();
131        let matrix_2x3 = MatPolyOverZ::from_str("[[1  1, 0, 0],[0, 1  1, 0]]").unwrap();
132        let matrix_3x2 = MatPolyOverZ::from_str("[[1  1, 0],[0, 1  1],[0, 0]]").unwrap();
133
134        assert!(matrix_1x1.is_identity());
135        assert!(matrix_2x2.is_identity());
136        assert!(matrix_3x3.is_identity());
137        assert!(matrix_2x3.is_identity());
138        assert!(matrix_3x2.is_identity());
139    }
140
141    /// Ensure that matrices which are square but are not the identity matrix, return false.
142    #[test]
143    fn not_identity() {
144        let matrix_side_entry = MatPolyOverZ::from_str("[[1  1, 1  1],[0, 1  1]]").unwrap();
145        let matrix_negative_1 = MatPolyOverZ::from_str("[[1  -1, 0],[0, 1  1]]").unwrap();
146        let matrix_negative_2 = MatPolyOverZ::from_str("[[1  -17, 0],[0, 1  1]]").unwrap();
147        let matrix_higher_degree = MatPolyOverZ::from_str("[[1  1, 0],[0, 2  1 42]]").unwrap();
148        let matrix_matrix_positive = MatPolyOverZ::from_str("[[1  17, 0],[0, 1  1]]").unwrap();
149        let matrix_large_negative =
150            MatPolyOverZ::from_str(&format!("[[1  -{}, 0],[0, 1  1]]", u64::MAX)).unwrap();
151        let matrix_large_positive =
152            MatPolyOverZ::from_str(&format!("[[1  {}, 0],[0, 1  1]]", u64::MAX)).unwrap();
153
154        assert!(!matrix_side_entry.is_identity());
155        assert!(!matrix_negative_1.is_identity());
156        assert!(!matrix_negative_2.is_identity());
157        assert!(!matrix_higher_degree.is_identity());
158        assert!(!matrix_matrix_positive.is_identity());
159        assert!(!matrix_large_negative.is_identity());
160        assert!(!matrix_large_positive.is_identity());
161    }
162}
163
164#[cfg(test)]
165mod test_is_square {
166    use crate::integer::MatPolyOverZ;
167    use std::str::FromStr;
168
169    /// Ensure that square matrices return true
170    #[test]
171    fn square_matrix() {
172        let matrix_negative =
173            MatPolyOverZ::from_str("[[1  -17, 0, 0],[0, 1  1, 0],[0, 0, 0]]").unwrap();
174        let matrix_higher_degree = MatPolyOverZ::from_str("[[1  1, 0],[0, 2  1 42]]").unwrap();
175        let matrix_matrix_positive = MatPolyOverZ::from_str("[[1  17]]").unwrap();
176        let matrix_large_negative =
177            MatPolyOverZ::from_str(&format!("[[1  -{}, 0],[0, 1  1]]", u64::MAX)).unwrap();
178        let matrix_large_positive = MatPolyOverZ::from_str(&format!(
179            "[[1  {}, 0, 0, 0],[0, 1  1, 0, 0],[0, 1  1, 0, 0],[0, 1  1, 0, 0]]",
180            u64::MAX
181        ))
182        .unwrap();
183
184        assert!(matrix_negative.is_square());
185        assert!(matrix_matrix_positive.is_square());
186        assert!(matrix_higher_degree.is_square());
187        assert!(matrix_large_negative.is_square());
188        assert!(matrix_large_positive.is_square());
189    }
190
191    /// Ensure that non-square matrices return false
192    #[test]
193    fn not_square_matrix() {
194        let matrix_1x2 = MatPolyOverZ::from_str("[[1  1, 0]]").unwrap();
195        let matrix_2x1 = MatPolyOverZ::from_str("[[1  1],[0]]").unwrap();
196        let matrix_2x3 = MatPolyOverZ::from_str("[[1  1, 0, 0],[0, 1  1, 0]]").unwrap();
197        let matrix_3x2 = MatPolyOverZ::from_str("[[1  1, 0],[0, 1  1],[0, 0]]").unwrap();
198
199        assert!(!matrix_1x2.is_square());
200        assert!(!matrix_2x1.is_square());
201        assert!(!matrix_2x3.is_square());
202        assert!(!matrix_3x2.is_square());
203    }
204}
205
206#[cfg(test)]
207mod test_is_zero {
208    use super::MatPolyOverZ;
209    use std::str::FromStr;
210
211    /// Ensure that is_zero returns `true` for all zero matrices.
212    #[test]
213    fn zero_detection() {
214        let zero = MatPolyOverZ::from_str("[[0, 0],[0, 0]]").unwrap();
215        let zero_2 = MatPolyOverZ::from_str("[[0, 0],[0, 0],[0, 3  0 0 0]]").unwrap();
216
217        assert!(zero.is_zero());
218        assert!(zero_2.is_zero());
219    }
220
221    /// Ensure that is_zero returns `false` for non-zero matrices.
222    #[test]
223    fn zero_rejection() {
224        let small = MatPolyOverZ::from_str("[[0, 0],[4  0 0 0 2, 0]]").unwrap();
225        let large =
226            MatPolyOverZ::from_str(&format!("[[0, 0],[1  {}, 0]]", (u128::MAX - 1) / 2 + 1))
227                .unwrap();
228
229        assert!(!small.is_zero());
230        assert!(!large.is_zero());
231    }
232}
233
234#[cfg(test)]
235mod test_is_symmetric {
236    use super::MatPolyOverZ;
237    use std::str::FromStr;
238
239    /// Ensure that is_symmetric returns `false` for non-symmetric matrices.
240    #[test]
241    fn symmetric_rejection() {
242        let mat_2x3 = MatPolyOverZ::from_str("[[0, 1  6, 2  1 4],[1  2, 0, 2  1 1]]").unwrap();
243        let mat_2x2 = MatPolyOverZ::from_str("[[1  9, 0],[2  1 71, 0]]").unwrap();
244
245        assert!(!mat_2x3.is_symmetric());
246        assert!(!mat_2x2.is_symmetric());
247    }
248
249    /// Ensure that is_symmetric returns `true` for symmetric matrices.
250    #[test]
251    fn symmetric_detection() {
252        let mat_2x2 = MatPolyOverZ::from_str(&format!(
253            "[[2  1 {}, 2  3 {}],[2  3 {}, 3  1 {} 8]]",
254            u64::MIN,
255            u64::MAX,
256            u64::MAX,
257            i64::MAX
258        ))
259        .unwrap();
260
261        assert!(mat_2x2.is_symmetric());
262    }
263}
264
265#[cfg(test)]
266mod test_rank {
267    use crate::integer::{MatPolyOverZ, Z};
268    use std::str::FromStr;
269
270    /// Test whether the rank is correctly computed
271    #[test]
272    fn rank_works() {
273        let mat_1 = MatPolyOverZ::from_str("[[1  5, 1  2],[1  2, 1  1]]").unwrap();
274        let mat_2 = MatPolyOverZ::from_str(&format!(
275            "[[2  {} 3, 0, 0, 0],[0, 0, 1  5, 1  7]]",
276            i64::MIN
277        ))
278        .unwrap();
279        let mat_3 = MatPolyOverZ::from_str("[[0],[0]]").unwrap();
280        let mat_4 = MatPolyOverZ::from_str("[[0, 0],[0, 1  1]]").unwrap();
281        let mat_5 = MatPolyOverZ::from_str("[[0, 1  1],[0, 1  5]]").unwrap();
282        let mat_6 =
283            MatPolyOverZ::from_str("[[1  6, 0, 1  1],[0, 1  1, 0],[2  1 5, 1  2, 0]]").unwrap();
284
285        let rank_1 = mat_1.rank();
286        let rank_2 = mat_2.rank();
287        let rank_3 = mat_3.rank();
288        let rank_4 = mat_4.rank();
289        let rank_5 = mat_5.rank();
290        let rank_6 = mat_6.rank();
291
292        assert_eq!(Z::from(2), rank_1);
293        assert_eq!(Z::from(2), rank_2);
294        assert_eq!(Z::ZERO, rank_3);
295        assert_eq!(Z::ONE, rank_4);
296        assert_eq!(Z::ONE, rank_5);
297        assert_eq!(Z::from(3), rank_6);
298    }
299}