Skip to main content

qfall_math/integer_mod_q/mat_zq/
concat.rs

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