qfall_math/integer_mod_q/mat_polynomial_ring_zq/
concat.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//! Implementations to concatenate two [`MatPolynomialRingZq`].
10
11use super::MatPolynomialRingZq;
12use crate::{
13    error::MathError,
14    integer::MatPolyOverZ,
15    traits::{CompareBase, Concatenate, MatrixDimensions},
16};
17use flint_sys::fmpz_poly_mat::{fmpz_poly_mat_concat_horizontal, fmpz_poly_mat_concat_vertical};
18
19impl Concatenate for &MatPolynomialRingZq {
20    type Output = MatPolynomialRingZq;
21
22    /// Concatenates `self` with `other` vertically, i.e. `other` is added below.
23    ///
24    /// Parameters:
25    /// - `other`: the other matrix to concatenate with `self`
26    ///
27    /// Returns a vertical concatenation of the two matrices or a
28    /// an error, if the matrices can not be concatenated vertically.
29    ///
30    /// # Examples
31    /// ```
32    /// use crate::qfall_math::traits::*;
33    /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq};
34    /// use std::str::FromStr;
35    ///
36    /// let modulus_str = "3  1 0 1 mod 17";
37    /// let modulus = ModulusPolynomialRingZq::from_str(modulus_str).unwrap();
38    ///
39    /// let mat_1 = MatPolynomialRingZq::new(13, 5, &modulus);
40    /// let mat_2 = MatPolynomialRingZq::new(17, 5, &modulus);
41    ///
42    /// let mat_vert = mat_1.concat_vertical(&mat_2).unwrap();
43    /// ```
44    ///
45    /// # Errors and Failures
46    /// - Returns a [`MathError`] of type
47    ///   [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
48    ///   if the matrices can not be concatenated due to mismatching dimensions.
49    /// - Returns a [`MathError`] of type
50    ///   [`MismatchingModulus`](MathError::MismatchingModulus)
51    ///   if the matrices can not be concatenated due to mismatching moduli.
52    fn concat_vertical(self, other: Self) -> Result<Self::Output, crate::error::MathError> {
53        if self.get_num_columns() != other.get_num_columns() {
54            return Err(MathError::MismatchingMatrixDimension(format!(
55                "Tried to concatenate vertically a '{}x{}' matrix and a '{}x{}' matrix.",
56                self.get_num_rows(),
57                self.get_num_columns(),
58                other.get_num_rows(),
59                other.get_num_columns()
60            )));
61        }
62
63        if !self.compare_base(other) {
64            return Err(self.call_compare_base_error(other).unwrap());
65        }
66
67        let mut matrix = MatPolyOverZ::new(
68            self.get_num_rows() + other.get_num_rows(),
69            self.get_num_columns(),
70        );
71        unsafe {
72            fmpz_poly_mat_concat_vertical(
73                &mut matrix.matrix,
74                &self.matrix.matrix,
75                &other.matrix.matrix,
76            );
77        }
78        Ok(MatPolynomialRingZq {
79            matrix,
80            modulus: self.get_mod(),
81        })
82    }
83
84    /// Concatenates `self` with `other` horizontally, i.e. `other` is added on the right.
85    ///
86    /// Parameters:
87    /// - `other`: the other matrix to concatenate with `self`
88    ///
89    /// Returns a horizontal concatenation of the two matrices or a
90    /// an error, if the matrices can not be concatenated horizontally.
91    ///
92    /// # Examples
93    /// ```
94    /// use crate::qfall_math::traits::*;
95    /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq};
96    /// use std::str::FromStr;
97    ///
98    /// let modulus_str = "3  1 17 1 mod 17";
99    /// let modulus = ModulusPolynomialRingZq::from_str(&modulus_str).unwrap();
100    ///
101    /// let mat_1 = MatPolynomialRingZq::new(17, 5, &modulus);
102    /// let mat_2 = MatPolynomialRingZq::new(17, 7, &modulus);
103    ///
104    /// let mat_vert = mat_1.concat_horizontal(&mat_2).unwrap();
105    /// ```
106    ///
107    /// # Errors and Failures
108    /// - Returns a [`MathError`] of type
109    ///   [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
110    ///   if the matrices can not be concatenated due to mismatching dimensions.
111    /// - Returns a [`MathError`] of type
112    ///   [`MismatchingModulus`](MathError::MismatchingModulus)
113    ///   if the matrices can not be concatenated due to mismatching moduli.
114    fn concat_horizontal(self, other: Self) -> Result<Self::Output, crate::error::MathError> {
115        if self.get_num_rows() != other.get_num_rows() {
116            return Err(MathError::MismatchingMatrixDimension(format!(
117                "Tried to concatenate horizontally a '{}x{}' matrix and a '{}x{}' matrix.",
118                self.get_num_rows(),
119                self.get_num_columns(),
120                other.get_num_rows(),
121                other.get_num_columns()
122            )));
123        }
124
125        if !self.compare_base(other) {
126            return Err(self.call_compare_base_error(other).unwrap());
127        }
128
129        let mut matrix = MatPolyOverZ::new(
130            self.get_num_rows(),
131            self.get_num_columns() + other.get_num_columns(),
132        );
133        unsafe {
134            fmpz_poly_mat_concat_horizontal(
135                &mut matrix.matrix,
136                &self.matrix.matrix,
137                &other.matrix.matrix,
138            );
139        }
140        Ok(MatPolynomialRingZq {
141            matrix,
142            modulus: self.get_mod(),
143        })
144    }
145}
146
147#[cfg(test)]
148mod test_concatenate {
149    use crate::{
150        integer::MatPolyOverZ,
151        integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq},
152        traits::{Concatenate, MatrixDimensions},
153    };
154    use std::str::FromStr;
155
156    const LARGE_PRIME: u64 = u64::MAX - 58;
157
158    /// Ensure that the dimensions are taken over correctly and an error occurs
159    /// if the dimensions mismatch.
160    #[test]
161    fn dimensions_vertical() {
162        let modulus_str = format!("3  1 {} 1 mod {LARGE_PRIME}", i64::MAX);
163        let modulus = ModulusPolynomialRingZq::from_str(&modulus_str).unwrap();
164        let mat_1 = MatPolynomialRingZq::new(13, 5, &modulus);
165        let mat_2 = MatPolynomialRingZq::new(17, 5, &modulus);
166        let mat_3 = MatPolynomialRingZq::new(17, 6, &modulus);
167
168        let mat_vert = mat_1.concat_vertical(&mat_2).unwrap();
169
170        assert!(mat_1.concat_vertical(&mat_3).is_err());
171
172        assert_eq!(5, mat_vert.get_num_columns());
173        assert_eq!(30, mat_vert.get_num_rows());
174    }
175
176    /// Ensure that the dimensions are taken over correctly and an error occurs
177    /// if the dimensions mismatch.
178    #[test]
179    fn dimensions_horizontal() {
180        let modulus_str = format!("3  1 {} 1 mod {LARGE_PRIME}", i64::MAX);
181        let modulus = ModulusPolynomialRingZq::from_str(&modulus_str).unwrap();
182        let mat_1 = MatPolynomialRingZq::new(13, 5, &modulus);
183        let mat_2 = MatPolynomialRingZq::new(17, 5, &modulus);
184        let mat_3 = MatPolynomialRingZq::new(17, 6, &modulus);
185
186        let mat_vert = mat_2.concat_horizontal(&mat_3).unwrap();
187
188        assert!(mat_1.concat_horizontal(&mat_2).is_err());
189
190        assert_eq!(11, mat_vert.get_num_columns());
191        assert_eq!(17, mat_vert.get_num_rows());
192    }
193
194    /// Ensure that concatenation of matrices with mismatching moduli results in
195    /// in an error.
196    #[test]
197    fn mismatching_moduli() {
198        let mat_1 = MatPolynomialRingZq::from_str("[[0, 0],[0, 0]] / 2  1 1 mod 6").unwrap();
199        let mat_2 = MatPolynomialRingZq::from_str("[[0, 0],[0, 0]] / 2  1 1 mod 7").unwrap();
200
201        let mat_hor = mat_1.concat_horizontal(&mat_2);
202        let mat_vert = mat_1.concat_vertical(&mat_2);
203
204        assert!(mat_hor.is_err());
205        assert!(mat_vert.is_err());
206    }
207
208    /// Ensure that vertical concatenation works correctly.
209    #[test]
210    fn vertically_correct() {
211        let modulus_str = format!("3  1 {} 1 mod {LARGE_PRIME}", i64::MAX);
212        let modulus = ModulusPolynomialRingZq::from_str(&modulus_str).unwrap();
213        let poly_mat_1 =
214            MatPolyOverZ::from_str(&format!("[[4  2 {} 1 1, 1  42],[0, 2  1 2]]", u64::MAX))
215                .unwrap();
216        let poly_mat_2 = MatPolyOverZ::from_str("[[1  27, 2  10 5]]").unwrap();
217        let poly_ring_mat_1 = MatPolynomialRingZq::from((&poly_mat_1, &modulus));
218        let poly_ring_mat_2 = MatPolynomialRingZq::from((&poly_mat_2, &modulus));
219
220        let mat_vertical = poly_ring_mat_1.concat_vertical(&poly_ring_mat_2).unwrap();
221
222        let poly_mat_cmp =
223            MatPolyOverZ::from_str("[[4  2 58 1 1, 1  42],[0, 2  1 2],[1  27, 2  10 5]]").unwrap();
224        let poly_ring_mat_cmp = MatPolynomialRingZq::from((&poly_mat_cmp, &modulus));
225
226        assert_eq!(poly_ring_mat_cmp, mat_vertical);
227    }
228
229    /// Ensure that horizontal concatenation works correctly.
230    #[test]
231    fn horizontally_correct() {
232        let modulus_str = format!("3  1 {} 1 mod {LARGE_PRIME}", i64::MAX);
233        let modulus = ModulusPolynomialRingZq::from_str(&modulus_str).unwrap();
234        let poly_mat_1 = MatPolyOverZ::from_str(&format!(
235            "[[4  {} {} 1 1, 1  42],[0, 2  1 2]]",
236            LARGE_PRIME + 2,
237            u64::MAX
238        ))
239        .unwrap();
240        let poly_mat_2 = MatPolyOverZ::from_str("[[1  27],[2  10 5]]").unwrap();
241        let poly_ring_mat_1 = MatPolynomialRingZq::from((&poly_mat_1, &modulus));
242        let poly_ring_mat_2 = MatPolynomialRingZq::from((&poly_mat_2, &modulus));
243
244        let mat_vertical = poly_ring_mat_1.concat_horizontal(&poly_ring_mat_2).unwrap();
245
246        let poly_mat_cmp =
247            MatPolyOverZ::from_str("[[4  2 58 1 1, 1  42, 1  27],[0, 2  1 2, 2  10 5]]").unwrap();
248        let poly_ring_mat_cmp = MatPolynomialRingZq::from((&poly_mat_cmp, &modulus));
249
250        assert_eq!(poly_ring_mat_cmp, mat_vertical);
251    }
252}