qfall_math/integer/mat_z/
forms.rs

1// Copyright © 2025 Niklas Siemer
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 file contains implementations of special forms and transforms of
10//! matrices, e.g. (Hermite and Smith) normal forms, echelon form, ...
11
12use super::MatZ;
13use crate::traits::MatrixDimensions;
14use flint_sys::fmpz_mat::{
15    fmpz_mat_hnf_transform, fmpz_mat_is_in_hnf, fmpz_mat_is_in_snf, fmpz_mat_snf,
16};
17
18impl MatZ {
19    /// Computes the Hermite normal form of `self` along with
20    /// the transformation matrix `U` s.t. `U * A = H`.
21    ///
22    /// # Examples
23    /// ```
24    /// use qfall_math::integer::MatZ;
25    /// use std::str::FromStr;
26    /// let matrix = MatZ::from_str("[[1, 2, 12],[2, 4, 5]]").unwrap();
27    /// let h_cmp = MatZ::from_str("[[1, 2, 12],[0, 0, 19]]").unwrap();
28    /// let u_cmp = MatZ::from_str("[[1, 0],[2, -1]]").unwrap();
29    ///
30    /// let (h, u) = matrix.hermite_nf();
31    ///
32    /// assert_eq!(h_cmp, h);
33    /// assert_eq!(u_cmp, u);
34    /// ```
35    pub fn hermite_nf(&self) -> (MatZ, MatZ) {
36        let nr_rows = self.get_num_rows();
37        let nr_cols = self.get_num_columns();
38
39        let mut hnf = MatZ::new(nr_rows, nr_cols);
40        let mut transform = MatZ::new(nr_rows, nr_rows);
41
42        // FLINT doc-comment
43        // Computes an integer matrix H such that H is the unique (row) Hermite normal form of A
44        // along with the transformation matrix U such that UA = H.
45        // The algorithm used is selected from the implementations in FLINT as per fmpz_mat_hnf.
46        unsafe { fmpz_mat_hnf_transform(&mut hnf.matrix, &mut transform.matrix, &self.matrix) };
47
48        (hnf, transform)
49    }
50
51    /// Checks if `self` is a matrix in Hermite normal form.
52    ///
53    /// # Examples
54    /// ```
55    /// use qfall_math::integer::MatZ;
56    /// use std::str::FromStr;
57    /// let non_hnf = MatZ::from_str("[[1, 2, 12],[2, 4, 5]]").unwrap();
58    /// let hnf = MatZ::from_str("[[1, 2, 12],[0, 0, 19]]").unwrap();
59    ///
60    /// assert!(!non_hnf.is_in_hermite_nf());
61    /// assert!(hnf.is_in_hermite_nf());
62    /// ```
63    pub fn is_in_hermite_nf(&self) -> bool {
64        1 == unsafe { fmpz_mat_is_in_hnf(&self.matrix) }
65    }
66
67    /// Computes the unique Smith normal form of the matrix `self`.
68    ///
69    /// # Examples
70    /// ```
71    /// use qfall_math::integer::MatZ;
72    /// use std::str::FromStr;
73    /// let matrix = MatZ::from_str("[[1, 2, 12],[2, 4, 5]]").unwrap();
74    /// let snf_cmp = MatZ::from_str("[[1, 0, 0],[0, 19, 0]]").unwrap();
75    ///
76    /// let snf = matrix.smith_nf();
77    ///
78    /// assert_eq!(snf_cmp, snf);
79    /// ```
80    pub fn smith_nf(&self) -> Self {
81        let mut snf: MatZ = MatZ::new(self.get_num_rows(), self.get_num_columns());
82
83        unsafe { fmpz_mat_snf(&mut snf.matrix, &self.matrix) };
84
85        snf
86    }
87
88    /// Checks if `self` is a matrix in Smith normal form.
89    ///
90    /// # Examples
91    /// ```
92    /// use qfall_math::integer::MatZ;
93    /// use std::str::FromStr;
94    /// let non_snf = MatZ::from_str("[[1, 2, 12],[2, 4, 5]]").unwrap();
95    /// let snf = MatZ::from_str("[[1, 0, 0],[0, 19, 0]]").unwrap();
96    ///
97    /// assert!(!non_snf.is_in_smith_nf());
98    /// assert!(snf.is_in_smith_nf());
99    /// ```
100    pub fn is_in_smith_nf(&self) -> bool {
101        1 == unsafe { fmpz_mat_is_in_snf(&self.matrix) }
102    }
103}
104
105#[cfg(test)]
106mod test_hermite_nf {
107    use super::MatZ;
108    use std::str::FromStr;
109
110    /// Ensures that [`MatZ::hermite_nf`] and [`MatZ::is_in_hermite_nf`] works for small entries.
111    #[test]
112    fn small_entries() {
113        let matrix = MatZ::from_str("[[2,3,6,2],[5,6,1,6],[8,3,1,1]]").unwrap();
114        let h_cmp = MatZ::from_str("[[1, 0, 50, -11],[0, 3, 28, -2],[0, 0, 61, -13]]").unwrap();
115        let u_cmp = MatZ::from_str("[[9, -5, 1],[5, -2, 0],[11, -6, 1]]").unwrap();
116
117        let (h, u) = matrix.hermite_nf();
118
119        assert_eq!(h_cmp, h);
120        assert_eq!(u_cmp, u);
121        assert_eq!(h, &u * &matrix);
122
123        assert!(!matrix.is_in_hermite_nf());
124        assert!(h.is_in_hermite_nf());
125        assert!(!u.is_in_hermite_nf());
126    }
127
128    /// Ensures that [`MatZ::hermite_nf`] and [`MatZ::is_in_hermite_nf`] works for large entries.
129    #[test]
130    fn large_entries() {
131        let matrix = MatZ::from_str(&format!(
132            "[[{}, 0],[{}, {}]]",
133            i64::MAX,
134            2 * (i64::MAX as u64),
135            u64::MAX
136        ))
137        .unwrap();
138        let h_cmp = MatZ::from_str(&format!("[[{}, 0],[0, {}]]", i64::MAX, u64::MAX)).unwrap();
139        let u_cmp = MatZ::from_str("[[1, 0],[-2, 1]]").unwrap();
140
141        let (h, u) = matrix.hermite_nf();
142
143        assert_eq!(h_cmp, h);
144        assert_eq!(u_cmp, u);
145        assert_eq!(h, &u * &matrix);
146
147        assert!(!matrix.is_in_hermite_nf());
148        assert!(h.is_in_hermite_nf());
149        assert!(!u.is_in_hermite_nf());
150    }
151
152    /// Ensures that [`MatZ::hermite_nf`] and [`MatZ::is_in_hermite_nf`] work
153    /// for any matrix dimensions.
154    #[test]
155    fn dimensions() {
156        let mat_0 = MatZ::sample_uniform(4, 1, 0, 16).unwrap();
157        let mat_1 = MatZ::sample_uniform(5, 5, 0, 16).unwrap();
158        let mat_2 = MatZ::sample_uniform(2, 4, 0, 16).unwrap();
159
160        let (hnf_0, u_0) = mat_0.hermite_nf();
161        let (hnf_1, u_1) = mat_1.hermite_nf();
162        let (hnf_2, u_2) = mat_2.hermite_nf();
163
164        assert!(hnf_0.is_in_hermite_nf());
165        assert!(hnf_1.is_in_hermite_nf());
166        assert!(hnf_2.is_in_hermite_nf());
167
168        assert_eq!(hnf_0, u_0 * mat_0);
169        assert_eq!(hnf_1, u_1 * mat_1);
170        assert_eq!(hnf_2, u_2 * mat_2);
171    }
172}
173
174#[cfg(test)]
175mod test_smith_nf {
176    use super::MatZ;
177    use std::str::FromStr;
178
179    /// Ensures that [`MatZ::smith_nf`] and [`MatZ::is_in_smith_nf`] works for small entries.
180    #[test]
181    fn small_entries() {
182        let matrix = MatZ::from_str("[[2,3,6,2],[5,6,1,6],[8,3,1,1]]").unwrap();
183        let s_cmp = MatZ::from_str("[[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 1, 0]]").unwrap();
184
185        let s = matrix.smith_nf();
186
187        assert_eq!(s_cmp, s);
188
189        assert!(!matrix.is_in_smith_nf());
190        assert!(s.is_in_smith_nf());
191    }
192
193    /// Ensures that [`MatZ::smith_nf`] and [`MatZ::is_in_smith_nf`] works for large entries.
194    #[test]
195    fn large_entries() {
196        let matrix = MatZ::from_str(&format!(
197            "[[{}, 0],[{}, {}]]",
198            i64::MAX,
199            2 * (i64::MAX as u64),
200            u64::MAX
201        ))
202        .unwrap();
203        let s_cmp =
204            MatZ::from_str("[[1, 0],[0, 170141183460469231704017187605319778305]]").unwrap();
205
206        let s = matrix.smith_nf();
207
208        assert_eq!(s_cmp, s);
209
210        assert!(!matrix.is_in_smith_nf());
211        assert!(s.is_in_smith_nf());
212    }
213
214    /// Ensures that [`MatZ::smith_nf`] and [`MatZ::is_in_smith_nf`] work
215    /// for any matrix dimensions.
216    #[test]
217    fn dimensions() {
218        let mat_0 = MatZ::sample_uniform(4, 1, 0, 16).unwrap();
219        let mat_1 = MatZ::sample_uniform(5, 5, 0, 16).unwrap();
220        let mat_2 = MatZ::sample_uniform(2, 4, 0, 16).unwrap();
221
222        let snf_0 = mat_0.smith_nf();
223        let snf_1 = mat_1.smith_nf();
224        let snf_2 = mat_2.smith_nf();
225
226        assert!(snf_0.is_in_smith_nf());
227        assert!(snf_1.is_in_smith_nf());
228        assert!(snf_2.is_in_smith_nf());
229    }
230}