qfall_math/integer/mat_poly_over_z/arithmetic/
mul.rs

1// Copyright © 2023 Phil Milewski, 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//! Implementation of the [`Mul`] trait for [`MatPolyOverZ`] values.
10
11use super::super::MatPolyOverZ;
12use crate::error::MathError;
13use crate::integer_mod_q::MatPolynomialRingZq;
14use crate::macros::arithmetics::{
15    arithmetic_trait_borrowed_to_owned, arithmetic_trait_mixed_borrowed_owned,
16};
17use crate::traits::MatrixDimensions;
18use flint_sys::fmpz_poly_mat::fmpz_poly_mat_mul_KS;
19use std::ops::Mul;
20
21impl Mul for &MatPolyOverZ {
22    type Output = MatPolyOverZ;
23
24    /// Implements the [`Mul`] trait for two [`MatPolyOverZ`] values.
25    /// [`Mul`] is implemented for any combination of [`MatPolyOverZ`] and borrowed [`MatPolyOverZ`].
26    ///
27    /// Parameters:
28    /// - `other`: specifies the value to multiply with `self`
29    ///
30    /// Returns the product of `self` and `other` as a [`MatPolyOverZ`].
31    ///
32    /// # Examples
33    /// ```
34    /// use qfall_math::integer::MatPolyOverZ;
35    /// use std::str::FromStr;
36    ///
37    /// let a: MatPolyOverZ = MatPolyOverZ::from_str("[[0, 1  42, 2  42 24],[3  17 24 42, 1  17, 1  42]]").unwrap();
38    /// let b: MatPolyOverZ = MatPolyOverZ::from_str("[[1  -42, 2  24 42],[1  -1, 1  17],[0, 2  1 42]]").unwrap();
39    ///
40    /// let c = &a * &b;
41    /// let d = a * b;
42    /// let e = &c * d;
43    /// let f = c * &e;
44    /// ```
45    ///
46    /// # Panics ...
47    /// - if the dimensions of `self` and `other` do not match for multiplication.
48    fn mul(self, other: Self) -> Self::Output {
49        self.mul_safe(other).unwrap()
50    }
51}
52
53impl Mul<&MatPolynomialRingZq> for &MatPolyOverZ {
54    type Output = MatPolynomialRingZq;
55    /// Implements the [`Mul`] trait for a [`MatPolyOverZ`] matrix with a [`MatPolynomialRingZq`] matrix.
56    /// [`Mul`] is implemented for any combination of owned and borrowed values.
57    ///
58    /// Parameters:
59    /// - `other`: specifies the value to multiply with `self`
60    ///
61    /// Returns the product of `self` and `other` as a [`MatPolynomialRingZq`].
62    ///
63    /// # Examples
64    /// ```
65    /// use qfall_math::integer_mod_q::MatPolynomialRingZq;
66    /// use qfall_math::integer::MatPolyOverZ;
67    /// use std::str::FromStr;
68    ///
69    /// let mat_1 = MatPolyOverZ::from_str("[[2  1 42, 1  17],[1  8, 2  5 6]]").unwrap();
70    /// let mat_2 = MatPolynomialRingZq::from_str("[[2  1 42, 1  17],[1  8, 2  5 6]] / 3  1 2 3 mod 17").unwrap();
71    ///
72    /// let mat_3 = &mat_1 * &mat_2;
73    /// ```
74    ///
75    /// # Panics ...
76    /// - if the dimensions of `self` and `other` do not match for multiplication.
77    fn mul(self, other: &MatPolynomialRingZq) -> Self::Output {
78        self.mul_mat_poly_ring_zq_safe(other).unwrap()
79    }
80}
81
82arithmetic_trait_borrowed_to_owned!(
83    Mul,
84    mul,
85    MatPolyOverZ,
86    MatPolynomialRingZq,
87    MatPolynomialRingZq
88);
89arithmetic_trait_mixed_borrowed_owned!(
90    Mul,
91    mul,
92    MatPolyOverZ,
93    MatPolynomialRingZq,
94    MatPolynomialRingZq
95);
96
97impl MatPolyOverZ {
98    /// Implements multiplication for two [`MatPolyOverZ`] values.
99    ///
100    /// Parameters:
101    /// - `other`: specifies the value to multiply with `self`
102    ///
103    /// Returns the product of `self` and `other` as a [`MatPolyOverZ`]
104    /// or an error if the dimensions of `self` and `other` do not match for multiplication.
105    ///
106    /// # Examples
107    /// ```
108    /// use qfall_math::integer::MatPolyOverZ;
109    /// use std::str::FromStr;
110    ///
111    /// let a: MatPolyOverZ = MatPolyOverZ::from_str("[[0, 2  42 24],[3  17 24 42, 1  17]]").unwrap();
112    /// let b: MatPolyOverZ = MatPolyOverZ::from_str("[[1  -42, 2  24 42],[3  1 12 4, 1  17]]").unwrap();
113    ///
114    /// let c: MatPolyOverZ = a.mul_safe(&b).unwrap();
115    /// ```
116    ///
117    /// # Errors and Failures
118    /// - Returns a [`MathError`] of type
119    ///   [`MathError::MismatchingMatrixDimension`] if the dimensions of `self`
120    ///   and `other` do not match for multiplication.
121    pub fn mul_safe(&self, other: &Self) -> Result<Self, MathError> {
122        if self.get_num_columns() != other.get_num_rows() {
123            return Err(MathError::MismatchingMatrixDimension(format!(
124                "Tried to multiply a '{}x{}' matrix and a '{}x{}' matrix.",
125                self.get_num_rows(),
126                self.get_num_columns(),
127                other.get_num_rows(),
128                other.get_num_columns()
129            )));
130        }
131
132        let mut new = MatPolyOverZ::new(self.get_num_rows(), other.get_num_columns());
133        // Testing shows that `fmpz_poly_mat_mul_KS` should almost always be more efficient for parameter sets
134        // relevant in lattice-based crypto than `fmpz_poly_mat_mul_classical`. Thus, we can spare ourselves
135        // the evaluation time in `fmpz_poly_mat_mul` to determine which algorithm performs better
136        unsafe { fmpz_poly_mat_mul_KS(&mut new.matrix, &self.matrix, &other.matrix) };
137        Ok(new)
138    }
139
140    /// Implements multiplication for a [`MatPolyOverZ`] matrix with a [`MatPolynomialRingZq`]  matrix.
141    ///
142    /// Parameters:
143    /// - `other`: specifies the value to multiply with `self`
144    ///
145    /// Returns the product of `self` and `other` as a [`MatPolynomialRingZq`].
146    ///
147    /// # Examples
148    /// ```
149    /// use qfall_math::integer_mod_q::MatPolynomialRingZq;
150    /// use qfall_math::integer::MatPolyOverZ;
151    /// use std::str::FromStr;
152    ///
153    /// let mat_1 = MatPolyOverZ::from_str("[[2  1 42, 1  17],[1  8, 2  5 6]]").unwrap();
154    /// let mat_2 = MatPolynomialRingZq::from_str("[[2  1 42, 1  17],[1  8, 2  5 6]] / 3  1 2 3 mod 17").unwrap();
155    ///
156    /// let mat_3 = &mat_1.mul_mat_poly_ring_zq_safe(&mat_2).unwrap();
157    /// ```
158    ///
159    /// # Errors and Failures
160    /// - Returns a [`MathError`] of type
161    ///   [`MathError::MismatchingMatrixDimension`] if the dimensions of `self`
162    ///   and `other` do not match for multiplication.
163    pub fn mul_mat_poly_ring_zq_safe(
164        &self,
165        other: &MatPolynomialRingZq,
166    ) -> Result<MatPolynomialRingZq, MathError> {
167        let mut out =
168            MatPolynomialRingZq::new(self.get_num_rows(), self.get_num_columns(), other.get_mod());
169
170        out.matrix = self.mul_safe(&other.matrix)?;
171        out.reduce();
172
173        Ok(out)
174    }
175}
176
177arithmetic_trait_borrowed_to_owned!(Mul, mul, MatPolyOverZ, MatPolyOverZ, MatPolyOverZ);
178arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatPolyOverZ, MatPolyOverZ, MatPolyOverZ);
179
180#[cfg(test)]
181mod test_mul {
182    use super::MatPolyOverZ;
183    use std::str::FromStr;
184
185    /// Checks if matrix multiplication works fine for squared matrices
186    #[test]
187    fn square_correctness() {
188        let mat_1 = MatPolyOverZ::from_str("[[2  0 1, 1  4],[0, 3  1 2 3]]").unwrap();
189        let mat_2 = MatPolyOverZ::from_str("[[2  0 1, 1  4],[0, 3  1 2 3]]").unwrap();
190        let res = MatPolyOverZ::from_str("[[3  0 0 1, 3  4 12 12],[0, 5  1 4 10 12 9]]").unwrap();
191        assert_eq!(res, &mat_1 * &mat_2);
192    }
193
194    /// Checks if matrix multiplication works fine for matrices of different dimensions
195    #[test]
196    fn different_dimensions_correctness() {
197        let mat = MatPolyOverZ::from_str("[[2  1 4, 1  7],[1  3, 3  12 3 4]]").unwrap();
198        let vec = MatPolyOverZ::from_str("[[1  4],[0]]").unwrap();
199        let cmp = MatPolyOverZ::from_str("[[2  4 16],[1  12]]").unwrap();
200
201        assert_eq!(cmp, &mat * &vec);
202    }
203
204    /// Checks if matrix multiplication works fine for large entries
205    #[test]
206    fn large_entries() {
207        let mat =
208            MatPolyOverZ::from_str(&format!("[[2  3 {}, 1  15],[1  1, 0]]", u64::MAX)).unwrap();
209        let vec = MatPolyOverZ::from_str(&format!("[[2  1 {}],[0]]", u64::MAX)).unwrap();
210        let cmp = MatPolyOverZ::from_str(&format!(
211            "[[3  3 {} {}],[2  1 {}]]",
212            u128::from(u64::MAX) * 4,
213            u128::from(u64::MAX) * u128::from(u64::MAX),
214            u64::MAX
215        ))
216        .unwrap();
217        assert_eq!(cmp, mat * vec);
218    }
219
220    /// Checks if matrix multiplication with incompatible matrix dimensions
221    /// throws an error as expected
222    #[test]
223    fn incompatible_dimensions() {
224        let a: MatPolyOverZ = MatPolyOverZ::from_str("[[2  0 1, 1  4],[0, 3  1 2 3]]").unwrap();
225        let b: MatPolyOverZ = MatPolyOverZ::from_str("[[2  0 1, 1  4]]").unwrap();
226        let c: MatPolyOverZ =
227            MatPolyOverZ::from_str("[[2  0 1, 1  4, 0],[0, 3  1 2 3, 1  3]]").unwrap();
228        assert!(a.mul_safe(&b).is_err());
229        assert!(c.mul_safe(&b).is_err());
230    }
231}
232
233#[cfg(test)]
234mod test_mul_mat_poly_over_z {
235    use super::MatPolynomialRingZq;
236    use crate::{integer::MatPolyOverZ, integer_mod_q::ModulusPolynomialRingZq};
237    use std::str::FromStr;
238
239    /// Checks whether multiplication is available for other types.
240    #[test]
241    fn availability() {
242        let modulus = ModulusPolynomialRingZq::from_str("4  1 0 0 1 mod 17").unwrap();
243        let poly_mat = MatPolyOverZ::from_str("[[3  0 1 1, 1  3],[0, 2  1 2]]").unwrap();
244        let poly_ring_mat = MatPolynomialRingZq::from((&poly_mat, &modulus));
245
246        let _ = &poly_mat * &poly_ring_mat;
247        let _ = &poly_mat * poly_ring_mat.clone();
248        let _ = poly_mat.clone() * &poly_ring_mat;
249        let _ = poly_mat.clone() * poly_ring_mat.clone();
250    }
251
252    /// Checks if matrix multiplication works fine for matrices of different dimensions.
253    #[test]
254    fn different_dimensions_correctness() {
255        let modulus = ModulusPolynomialRingZq::from_str("4  1 0 0 1 mod 17").unwrap();
256        let poly_mat_1 = MatPolyOverZ::from_str("[[1  42],[1  17]]").unwrap();
257        let poly_ring_mat_1 = MatPolynomialRingZq::from((&poly_mat_1, &modulus));
258        let poly_mat_2 = MatPolyOverZ::from_str("[[4  -1 0 1 1, 1  42],[0, 2  1 2]]").unwrap();
259
260        let poly_ring_mat_3 = &poly_mat_2 * &poly_ring_mat_1;
261
262        let poly_mat_cmp = MatPolyOverZ::from_str("[[3  1 0 8],[0]]").unwrap();
263        let poly_ring_mat_cmp = MatPolynomialRingZq::from((&poly_mat_cmp, &modulus));
264
265        assert_eq!(poly_ring_mat_cmp, poly_ring_mat_3);
266    }
267
268    /// Checks if matrix multiplication with incompatible matrix dimensions
269    /// throws an error as expected.
270    #[test]
271    fn errors() {
272        let modulus_1 = ModulusPolynomialRingZq::from_str("4  1 0 0 1 mod 17").unwrap();
273        let poly_mat_1 = MatPolyOverZ::from_str("[[4  -1 0 1 1],[2  1 2]]").unwrap();
274        let poly_ring_mat_1 = MatPolynomialRingZq::from((&poly_mat_1, &modulus_1));
275
276        assert!((poly_mat_1.mul_mat_poly_ring_zq_safe(&poly_ring_mat_1)).is_err());
277    }
278
279    /// Checks if multiplication panics if dimensions mismatch.
280    #[test]
281    #[should_panic]
282    fn mul_panic() {
283        let modulus1 = ModulusPolynomialRingZq::from_str("4  1 0 0 1 mod 17").unwrap();
284        let poly_mat1 = MatPolyOverZ::from_str("[[1  3],[2  1 2]]").unwrap();
285        let poly_ring_mat1 = MatPolynomialRingZq::from((&poly_mat1, &modulus1));
286
287        let _ = &poly_mat1 * &poly_ring_mat1;
288    }
289}