Skip to main content

qfall_math/integer_mod_q/mat_zq/
tensor.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//! This module contains the implementation of the `tensor` product.
10
11use super::MatZq;
12use crate::{
13    error::MathError,
14    traits::{CompareBase, MatrixDimensions, Tensor},
15};
16use flint3_sys::{_fmpz_mod_mat_reduce, fmpz_mat_kronecker_product};
17
18impl Tensor for MatZq {
19    /// Computes the tensor product of `self` with `other`.
20    ///
21    /// Parameters:
22    /// - `other`: the value with which the tensor product is computed.
23    ///
24    /// Returns the tensor product of `self` with `other` and panics if the
25    /// moduli of the provided matrices mismatch.
26    ///
27    /// # Examples
28    /// ```
29    /// use qfall_math::integer_mod_q::MatZq;
30    /// use qfall_math::traits::Tensor;
31    /// use std::str::FromStr;
32    ///
33    /// let mat_1 = MatZq::from_str("[[1, 1],[2, 2]] mod 7").unwrap();
34    /// let mat_2 = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
35    ///
36    /// let mat_ab = mat_1.tensor_product(&mat_2);
37    /// let mat_ba = mat_2.tensor_product(&mat_1);
38    ///
39    /// let res_ab = "[[1, 2, 1, 2],[3, 4, 3, 4],[2, 4, 2, 4],[6, 1, 6, 1]] mod 7";
40    /// let res_ba = "[[1, 1, 2, 2],[2, 2, 4, 4],[3, 3, 4, 4],[6, 6, 1, 1]] mod 7";
41    /// assert_eq!(mat_ab, MatZq::from_str(res_ab).unwrap());
42    /// assert_eq!(mat_ba, MatZq::from_str(res_ba).unwrap());
43    /// ```
44    ///
45    /// # Panics ...
46    /// - if the moduli of both matrices mismatch.
47    ///   Use [`tensor_product_safe`](crate::integer_mod_q::MatZq::tensor_product_safe) to get an error instead.
48    fn tensor_product(&self, other: &Self) -> Self {
49        self.tensor_product_safe(other).unwrap()
50    }
51}
52
53impl MatZq {
54    /// Computes the tensor product of `self` with `other`.
55    ///
56    /// Parameters:
57    /// - `other`: the value with which the tensor product is computed.
58    ///
59    /// Returns the tensor product of `self` with `other` or an error if the
60    /// moduli of the provided matrices mismatch.
61    ///
62    /// # Examples
63    /// ```
64    /// use qfall_math::integer_mod_q::MatZq;
65    /// use std::str::FromStr;
66    ///
67    /// let mat_1 = MatZq::from_str("[[1, 1],[2, 2]] mod 7").unwrap();
68    /// let mat_2 = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
69    ///
70    /// let mat_ab = mat_1.tensor_product_safe(&mat_2).unwrap();
71    /// let mat_ba = mat_2.tensor_product_safe(&mat_1).unwrap();
72    ///
73    /// let res_ab = "[[1, 2, 1, 2],[3, 4, 3, 4],[2, 4, 2, 4],[6, 1, 6, 1]] mod 7";
74    /// let res_ba = "[[1, 1, 2, 2],[2, 2, 4, 4],[3, 3, 4, 4],[6, 6, 1, 1]] mod 7";
75    /// assert_eq!(mat_ab, MatZq::from_str(res_ab).unwrap());
76    /// assert_eq!(mat_ba, MatZq::from_str(res_ba).unwrap());
77    /// ```
78    ///
79    /// # Errors and Failures
80    /// - Returns a [`MathError`] of type
81    ///   [`MismatchingModulus`](MathError::MismatchingModulus) if the
82    ///   moduli of the provided matrices mismatch.
83    pub fn tensor_product_safe(&self, other: &Self) -> Result<Self, MathError> {
84        if !self.compare_base(other) {
85            return Err(self.call_compare_base_error(other).unwrap());
86        }
87
88        let mut out = MatZq::new(
89            self.get_num_rows() * other.get_num_rows(),
90            self.get_num_columns() * other.get_num_columns(),
91            self.get_mod(),
92        );
93
94        unsafe { fmpz_mat_kronecker_product(&mut out.matrix, &self.matrix, &other.matrix) };
95
96        unsafe { _fmpz_mod_mat_reduce(&mut out.matrix, self.modulus.get_fmpz_mod_ctx_struct()) }
97
98        Ok(out)
99    }
100}
101
102#[cfg(test)]
103mod test_tensor {
104    use crate::{
105        integer_mod_q::MatZq,
106        traits::{MatrixDimensions, Tensor},
107    };
108    use std::str::FromStr;
109
110    /// Ensure that the dimensions of the tensor product are taken over correctly.
111    #[test]
112    fn dimensions_fit() {
113        let mat_1 = MatZq::new(17, 13, 13);
114        let mat_2 = MatZq::new(3, 4, 13);
115
116        let mat_3 = mat_1.tensor_product(&mat_2);
117        let mat_3_safe = mat_1.tensor_product_safe(&mat_2).unwrap();
118
119        assert_eq!(51, mat_3.get_num_rows());
120        assert_eq!(52, mat_3.get_num_columns());
121        assert_eq!(&mat_3, &mat_3_safe);
122    }
123
124    /// Ensure that the tensor works correctly with identity.
125    #[test]
126    fn identity() {
127        let identity = MatZq::from_str(&format!("[[1, 0],[0, 1]] mod {}", u128::MAX)).unwrap();
128        let mat_1 = MatZq::from_str(&format!(
129            "[[1, {}, 1],[0, {}, -1]] mod {}",
130            u64::MAX,
131            i64::MIN,
132            u128::MAX
133        ))
134        .unwrap();
135
136        let mat_2 = identity.tensor_product(&mat_1);
137        let mat_3 = mat_1.tensor_product(&identity);
138        let mat_2_safe = identity.tensor_product_safe(&mat_1).unwrap();
139        let mat_3_safe = mat_1.tensor_product_safe(&identity).unwrap();
140
141        let cmp_mat_2 = MatZq::from_str(&format!(
142            "[[1, {}, 1, 0, 0, 0], \
143              [0, {}, -1, 0, 0, 0], \
144              [0, 0, 0, 1, {}, 1], \
145              [0, 0, 0, 0, {}, -1]] mod {}",
146            u64::MAX,
147            i64::MIN,
148            u64::MAX,
149            i64::MIN,
150            u128::MAX
151        ))
152        .unwrap();
153        let cmp_mat_3 = MatZq::from_str(&format!(
154            "[[1, 0, {}, 0, 1, 0], \
155              [0, 1, 0, {}, 0, 1], \
156              [0, 0, {}, 0, -1, 0], \
157              [0, 0, 0, {}, 0, -1]] mod {}",
158            u64::MAX,
159            u64::MAX,
160            i64::MIN,
161            i64::MIN,
162            u128::MAX
163        ))
164        .unwrap();
165
166        assert_eq!(cmp_mat_2, mat_2);
167        assert_eq!(cmp_mat_3, mat_3);
168        assert_eq!(cmp_mat_2, mat_2_safe);
169        assert_eq!(cmp_mat_3, mat_3_safe);
170    }
171
172    /// Ensure the tensor product works where one is a vector and the other is a matrix.
173    #[test]
174    fn vector_matrix() {
175        let vector = MatZq::from_str(&format!("[[1],[-1]] mod {}", u128::MAX)).unwrap();
176        let mat_1 = MatZq::from_str(&format!(
177            "[[1, {}, 1],[0, {}, -1]] mod {}",
178            u64::MAX,
179            i64::MAX,
180            u128::MAX
181        ))
182        .unwrap();
183
184        let mat_2 = vector.tensor_product(&mat_1);
185        let mat_3 = mat_1.tensor_product(&vector);
186        let mat_2_safe = vector.tensor_product_safe(&mat_1).unwrap();
187        let mat_3_safe = mat_1.tensor_product_safe(&vector).unwrap();
188
189        let cmp_mat_2 = MatZq::from_str(&format!(
190            "[[1, {}, 1],[0, {}, -1],[-1, -{}, -1],[0, -{}, 1]] mod {}",
191            u64::MAX,
192            i64::MAX,
193            u64::MAX,
194            i64::MAX,
195            u128::MAX
196        ))
197        .unwrap();
198        let cmp_mat_3 = MatZq::from_str(&format!(
199            "[[1, {}, 1],[-1, -{}, -1],[0, {}, -1],[0, -{}, 1]] mod {}",
200            u64::MAX,
201            u64::MAX,
202            i64::MAX,
203            i64::MAX,
204            u128::MAX
205        ))
206        .unwrap();
207
208        assert_eq!(cmp_mat_2, mat_2);
209        assert_eq!(cmp_mat_3, mat_3);
210        assert_eq!(cmp_mat_2, mat_2_safe);
211        assert_eq!(cmp_mat_3, mat_3_safe);
212    }
213
214    /// Ensure that the tensor product works correctly with two vectors.
215    #[test]
216    fn vector_vector() {
217        let vec_1 = MatZq::from_str(&format!("[[2],[1]] mod {}", u128::MAX)).unwrap();
218        let vec_2 = MatZq::from_str(&format!(
219            "[[{}],[{}]] mod {}",
220            (u64::MAX - 1) / 2,
221            i64::MIN / 2,
222            u128::MAX
223        ))
224        .unwrap();
225
226        let vec_3 = vec_1.tensor_product(&vec_2);
227        let vec_4 = vec_2.tensor_product(&vec_1);
228        let vec_3_safe = vec_1.tensor_product_safe(&vec_2).unwrap();
229        let vec_4_safe = vec_2.tensor_product_safe(&vec_1).unwrap();
230
231        let cmp_vec_3 = MatZq::from_str(&format!(
232            "[[{}],[{}],[{}],[{}]] mod {}",
233            u64::MAX - 1,
234            i64::MIN,
235            (u64::MAX - 1) / 2,
236            i64::MIN / 2,
237            u128::MAX
238        ))
239        .unwrap();
240        let cmp_vec_4 = MatZq::from_str(&format!(
241            "[[{}],[{}],[{}],[{}]] mod {}",
242            u64::MAX - 1,
243            (u64::MAX - 1) / 2,
244            i64::MIN,
245            i64::MIN / 2,
246            u128::MAX
247        ))
248        .unwrap();
249
250        assert_eq!(cmp_vec_3, vec_3);
251        assert_eq!(cmp_vec_4, vec_4);
252        assert_eq!(cmp_vec_3, vec_3_safe);
253        assert_eq!(cmp_vec_4, vec_4_safe);
254    }
255
256    /// Ensure that entries are reduced by the modulus.
257    #[test]
258    fn entries_reduced() {
259        let mat_1 = MatZq::from_str(&format!("[[1, 2],[3, 4]] mod {}", u64::MAX - 58)).unwrap();
260        let mat_2 = MatZq::from_str(&format!("[[1, 58],[0, -1]] mod {}", u64::MAX - 58)).unwrap();
261
262        let mat_3 = mat_1.tensor_product(&mat_2);
263        let mat_3_safe = mat_1.tensor_product_safe(&mat_2).unwrap();
264
265        let mat_3_cmp = MatZq::from_str(&format!(
266            "[[1, 58, 2, 116],[0, -1, 0, -2],[3, 174, 4, 232],[0, -3, 0, -4]] mod {}",
267            u64::MAX - 58
268        ))
269        .unwrap();
270        assert_eq!(mat_3_cmp, mat_3);
271        assert_eq!(mat_3_cmp, mat_3_safe);
272    }
273
274    /// Ensure that tensor panics if the moduli mismatch.
275    #[test]
276    #[should_panic]
277    fn mismatching_moduli_tensor_product() {
278        let mat_1 = MatZq::new(1, 2, u64::MAX);
279        let mat_2 = MatZq::new(1, 2, u64::MAX - 58);
280
281        let _ = mat_1.tensor_product(&mat_2);
282    }
283
284    /// Ensure that tensor_product_safe returns an error if the moduli mismatch.
285    #[test]
286    fn mismatching_moduli_tensor_product_safe() {
287        let mat_1 = MatZq::new(1, 2, u64::MAX);
288        let mat_2 = MatZq::new(1, 2, u64::MAX - 58);
289
290        assert!(mat_1.tensor_product_safe(&mat_2).is_err());
291        assert!(mat_2.tensor_product_safe(&mat_1).is_err());
292    }
293}