qfall_math/integer_mod_q/mat_zq/arithmetic/
add.rs

1// Copyright © 2023 Phil Milewski
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 [`Add`] trait for [`MatZq`] values.
10
11use super::super::MatZq;
12use crate::error::MathError;
13use crate::integer::MatZ;
14use crate::macros::arithmetics::{
15    arithmetic_assign_trait_borrowed_to_owned, arithmetic_trait_borrowed_to_owned,
16    arithmetic_trait_mixed_borrowed_owned, arithmetic_trait_reverse,
17};
18use crate::traits::{CompareBase, MatrixDimensions};
19use flint_sys::fmpz_mat::fmpz_mat_add;
20use flint_sys::fmpz_mod_mat::{_fmpz_mod_mat_reduce, fmpz_mod_mat_add};
21use std::ops::{Add, AddAssign};
22
23impl AddAssign<&MatZq> for MatZq {
24    /// Computes the addition of `self` and `other` reusing
25    /// the memory of `self`.
26    /// [`AddAssign`] can be used on [`MatZq`] in combination with
27    /// [`MatZq`] and [`MatZ`].
28    ///
29    /// Parameters:
30    /// - `other`: specifies the value to add to `self`
31    ///
32    /// # Examples
33    /// ```
34    /// use qfall_math::{integer_mod_q::MatZq, integer::MatZ};
35    /// let mut a = MatZq::identity(2, 2, 7);
36    /// let b = MatZq::new(2, 2, 7);
37    /// let c = MatZ::new(2, 2);
38    ///
39    /// a += &b;
40    /// a += b;
41    /// a += &c;
42    /// a += c;
43    /// ```
44    ///
45    /// # Panics ...
46    /// - if the matrix dimensions mismatch.
47    /// - if the moduli of the matrices mismatch.
48    fn add_assign(&mut self, other: &Self) {
49        if self.get_num_rows() != other.get_num_rows()
50            || self.get_num_columns() != other.get_num_columns()
51        {
52            panic!(
53                "Tried to add a '{}x{}' matrix and a '{}x{}' matrix.",
54                self.get_num_rows(),
55                self.get_num_columns(),
56                other.get_num_rows(),
57                other.get_num_columns()
58            );
59        }
60        if !self.compare_base(other) {
61            panic!("{}", self.call_compare_base_error(other).unwrap());
62        }
63
64        unsafe { fmpz_mod_mat_add(&mut self.matrix, &self.matrix, &other.matrix) };
65    }
66}
67impl AddAssign<&MatZ> for MatZq {
68    /// Documentation at [`MatZq::add_assign`].
69    fn add_assign(&mut self, other: &MatZ) {
70        if self.get_num_rows() != other.get_num_rows()
71            || self.get_num_columns() != other.get_num_columns()
72        {
73            panic!(
74                "Tried to add a '{}x{}' matrix and a '{}x{}' matrix.",
75                self.get_num_rows(),
76                self.get_num_columns(),
77                other.get_num_rows(),
78                other.get_num_columns()
79            );
80        }
81
82        unsafe {
83            fmpz_mat_add(&mut self.matrix.mat[0], &self.matrix.mat[0], &other.matrix);
84            _fmpz_mod_mat_reduce(&mut self.matrix);
85        };
86    }
87}
88
89arithmetic_assign_trait_borrowed_to_owned!(AddAssign, add_assign, MatZq, MatZq);
90arithmetic_assign_trait_borrowed_to_owned!(AddAssign, add_assign, MatZq, MatZ);
91
92impl Add for &MatZq {
93    type Output = MatZq;
94    /// Implements the [`Add`] trait for two [`MatZq`] values.
95    /// [`Add`] is implemented for any combination of [`MatZq`] and borrowed [`MatZq`].
96    ///
97    /// Parameters:
98    /// - `other`: specifies the value to add to `self`
99    ///
100    /// Returns the sum of both numbers as a [`MatZq`].
101    ///
102    /// # Examples
103    /// ```
104    /// use qfall_math::integer_mod_q::MatZq;
105    /// use std::str::FromStr;
106    ///
107    /// let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 7").unwrap();
108    /// let b: MatZq = MatZq::from_str("[[1, 9, 3],[1, 0, 5]] mod 7").unwrap();
109    ///
110    /// let c: MatZq = &a + &b;
111    /// let d: MatZq = a + b;
112    /// let e: MatZq = &c + d;
113    /// let f: MatZq = c + &e;
114    /// ```
115    ///
116    /// # Panics ...
117    /// - if the dimensions of both matrices mismatch.
118    /// - if the moduli mismatch.
119    fn add(self, other: Self) -> Self::Output {
120        self.add_safe(other).unwrap()
121    }
122}
123
124impl Add<&MatZ> for &MatZq {
125    type Output = MatZq;
126
127    /// Implements the [`Add`] trait for a [`MatZ`] and a [`MatZq`] matrix.
128    /// [`Add`] is implemented for any combination of [`MatZ`] and [`MatZq`] and vice versa.
129    ///
130    /// Parameters:
131    /// - `other`: specifies the value to add to `self`
132    ///
133    /// Returns the sum of both numbers as a [`MatZq`].
134    ///
135    /// # Examples
136    /// ```
137    /// use qfall_math::{integer::MatZ, integer_mod_q::MatZq};
138    /// use std::str::FromStr;
139    ///
140    /// let a = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
141    /// let b = MatZq::from_str("[[1, 9, 3],[1, 0, 5]] mod 7").unwrap();
142    ///
143    /// let c = &a + &b;
144    /// let d = a.clone() + b.clone();
145    /// let e = &b + &a;
146    /// let f = b + a;
147    /// ```
148    ///
149    /// # Panics ...
150    /// - if the dimensions of both matrices mismatch.
151    fn add(self, other: &MatZ) -> Self::Output {
152        if self.get_num_rows() != other.get_num_rows()
153            || self.get_num_columns() != other.get_num_columns()
154        {
155            panic!(
156                "Tried to add a '{}x{}' matrix and a '{}x{}' matrix.",
157                self.get_num_rows(),
158                self.get_num_columns(),
159                other.get_num_rows(),
160                other.get_num_columns()
161            );
162        }
163
164        let mut out = MatZq::new(self.get_num_rows(), self.get_num_columns(), self.get_mod());
165        unsafe {
166            fmpz_mat_add(&mut out.matrix.mat[0], &self.matrix.mat[0], &other.matrix);
167            _fmpz_mod_mat_reduce(&mut out.matrix);
168        }
169        out
170    }
171}
172
173arithmetic_trait_borrowed_to_owned!(Add, add, MatZq, MatZ, MatZq);
174arithmetic_trait_mixed_borrowed_owned!(Add, add, MatZq, MatZ, MatZq);
175arithmetic_trait_reverse!(Add, add, MatZ, MatZq, MatZq);
176arithmetic_trait_borrowed_to_owned!(Add, add, MatZ, MatZq, MatZq);
177arithmetic_trait_mixed_borrowed_owned!(Add, add, MatZ, MatZq, MatZq);
178
179impl MatZq {
180    /// Implements addition for two [`MatZq`] matrices.
181    ///
182    /// Parameters:
183    /// - `other`: specifies the value to add to `self`
184    ///
185    /// Returns the sum of both matrices as a [`MatZq`] or an
186    /// error if the matrix dimensions mismatch.
187    ///
188    /// # Examples
189    /// ```
190    /// use qfall_math::integer_mod_q::MatZq;
191    /// use std::str::FromStr;
192    ///
193    /// let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 7").unwrap();
194    /// let b: MatZq = MatZq::from_str("[[1, 9, 3],[1, 0, 5]] mod 7").unwrap();
195    ///
196    /// let c: MatZq = a.add_safe(&b).unwrap();
197    /// ```
198    /// # Errors and Failures
199    /// - Returns a [`MathError`] of type
200    ///   [`MathError::MismatchingMatrixDimension`] if the matrix dimensions
201    ///   mismatch.
202    /// - Returns a [`MathError`] of type
203    ///   [`MathError::MismatchingModulus`] if the moduli mismatch.
204    pub fn add_safe(&self, other: &Self) -> Result<MatZq, MathError> {
205        if !self.compare_base(other) {
206            return Err(self.call_compare_base_error(other).unwrap());
207        }
208        if self.get_num_rows() != other.get_num_rows()
209            || self.get_num_columns() != other.get_num_columns()
210        {
211            return Err(MathError::MismatchingMatrixDimension(format!(
212                "Tried to add a '{}x{}' matrix and a '{}x{}' matrix.",
213                self.get_num_rows(),
214                self.get_num_columns(),
215                other.get_num_rows(),
216                other.get_num_columns()
217            )));
218        }
219        let mut out = MatZq::new(self.get_num_rows(), self.get_num_columns(), self.get_mod());
220        unsafe {
221            fmpz_mod_mat_add(&mut out.matrix, &self.matrix, &other.matrix);
222        }
223        Ok(out)
224    }
225}
226
227arithmetic_trait_borrowed_to_owned!(Add, add, MatZq, MatZq, MatZq);
228arithmetic_trait_mixed_borrowed_owned!(Add, add, MatZq, MatZq, MatZq);
229
230#[cfg(test)]
231mod test_add_assign {
232    use crate::{integer::MatZ, integer_mod_q::MatZq};
233    use std::str::FromStr;
234
235    /// Ensure that `add_assign` works for small numbers.
236    #[test]
237    fn correct_small() {
238        let mut a = MatZq::identity(2, 2, 7);
239        let b = MatZq::from_str("[[4, 5],[-6, 6]] mod 7").unwrap();
240        let mut c = a.clone();
241        let d = b.get_representative_least_nonnegative_residue();
242        let cmp = MatZq::from_str("[[5, 5],[1, 0]] mod 7").unwrap();
243
244        a += b;
245        c += d;
246
247        assert_eq!(cmp, a);
248        assert_eq!(cmp, c);
249    }
250
251    /// Ensure that `add_assign` works for large numbers.
252    #[test]
253    fn correct_large() {
254        let mut a = MatZq::from_str(&format!(
255            "[[{}, 5],[{}, -1]] mod {}",
256            i64::MAX,
257            i64::MIN,
258            u64::MAX
259        ))
260        .unwrap();
261        let b = MatZq::from_str(&format!("[[{}, -6],[6, -1]] mod {}", i64::MAX, u64::MAX)).unwrap();
262        let cmp = MatZq::from_str(&format!(
263            "[[{}, -1],[{}, -2]] mod {}",
264            2 * (i64::MAX as u64),
265            i64::MIN + 6,
266            u64::MAX
267        ))
268        .unwrap();
269
270        a += b;
271
272        assert_eq!(cmp, a);
273    }
274
275    /// Ensure that `add_assign` works for different matrix dimensions.
276    #[test]
277    fn matrix_dimensions() {
278        let dimensions = [(3, 3), (5, 1), (1, 4)];
279
280        for (nr_rows, nr_cols) in dimensions {
281            let mut a = MatZq::new(nr_rows, nr_cols, 7);
282            let b = MatZq::identity(nr_rows, nr_cols, 7);
283
284            a += b;
285
286            assert_eq!(MatZq::identity(nr_rows, nr_cols, 7), a);
287        }
288    }
289
290    /// Ensures that mismatching moduli will result in a panic.
291    #[test]
292    #[should_panic]
293    fn mismatching_moduli() {
294        let mut a = MatZq::new(1, 1, 5);
295        let b = MatZq::new(1, 1, 6);
296
297        a += b;
298    }
299
300    /// Ensure that `add_assign` is available for all types.
301    #[test]
302    fn availability() {
303        let mut a = MatZq::new(2, 2, 7);
304        let b = MatZq::new(2, 2, 7);
305        let c = MatZ::new(2, 2);
306
307        a += &b;
308        a += b;
309        a += &c;
310        a += c;
311    }
312}
313
314#[cfg(test)]
315mod test_add {
316    use super::MatZq;
317    use std::str::FromStr;
318
319    /// Testing addition for two [`MatZq`]
320    #[test]
321    fn add() {
322        let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 7").unwrap();
323        let b: MatZq = MatZq::from_str("[[1, 2, 3],[3, -5, 5]] mod 7").unwrap();
324        let c: MatZq = a + b;
325        assert_eq!(c, MatZq::from_str("[[2, 4, 6],[6, 6, 3]] mod 7").unwrap());
326    }
327
328    /// Testing addition for two borrowed [`MatZq`]
329    #[test]
330    fn add_borrow() {
331        let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 7").unwrap();
332        let b: MatZq = MatZq::from_str("[[1, 2, 3],[3, -5, 5]] mod 7").unwrap();
333        let c: MatZq = &a + &b;
334        assert_eq!(c, MatZq::from_str("[[2, 4, 6],[6, 6, 3]] mod 7").unwrap());
335    }
336
337    /// Testing addition for borrowed [`MatZq`] and [`MatZq`]
338    #[test]
339    fn add_first_borrowed() {
340        let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 7").unwrap();
341        let b: MatZq = MatZq::from_str("[[1, 2, 3],[3, -5, 5]] mod 7").unwrap();
342        let c: MatZq = &a + b;
343        assert_eq!(c, MatZq::from_str("[[2, 4, 6],[6, 6, 3]] mod 7").unwrap());
344    }
345
346    /// Testing addition for [`MatZq`] and borrowed [`MatZq`]
347    #[test]
348    fn add_second_borrowed() {
349        let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 7").unwrap();
350        let b: MatZq = MatZq::from_str("[[1, 2, 3],[3, -5, 5]] mod 7").unwrap();
351        let c: MatZq = a + &b;
352        assert_eq!(c, MatZq::from_str("[[2, 4, 6],[6, 6, 3]] mod 7").unwrap());
353    }
354
355    /// Testing addition for large numbers
356    #[test]
357    fn add_large_numbers() {
358        let a: MatZq = MatZq::from_str(&format!(
359            "[[1, 2, {}],[3, -4, {}]] mod {}",
360            i64::MIN,
361            i128::MAX,
362            u64::MAX - 58
363        ))
364        .unwrap();
365        let b: MatZq = MatZq::from_str(&format!(
366            "[[1, 2, {}],[3, 9, {}]] mod {}",
367            i64::MIN + 1,
368            i128::MAX,
369            u64::MAX - 58
370        ))
371        .unwrap();
372        let c: MatZq = a + &b;
373        assert_eq!(
374            c,
375            MatZq::from_str(&format!(
376                "[[2, 4, -{}],[6, 5, {}]] mod {}",
377                u64::MAX,
378                u128::MAX - 1,
379                u64::MAX - 58
380            ))
381            .unwrap()
382        );
383    }
384
385    /// Testing add_safe
386    #[test]
387    fn add_safe() {
388        let a: MatZq = MatZq::from_str("[[1, 2, 3],[3, 4, 5]] mod 4").unwrap();
389        let b: MatZq = MatZq::from_str("[[1, 2, 3],[3, -5, 5]] mod 4").unwrap();
390        let c = a.add_safe(&b);
391        assert_eq!(
392            c.unwrap(),
393            MatZq::from_str("[[2, 0, 2],[2, 3, 2]] mod 4").unwrap()
394        );
395    }
396
397    /// Testing add_safe throws errors
398    #[test]
399    fn add_safe_is_err() {
400        let a: MatZq = MatZq::from_str("[[1, 2],[3, 4]] mod 4").unwrap();
401        let b: MatZq = MatZq::from_str("[[1, 2, 3],[3, -4, 5]] mod 4").unwrap();
402        let c: MatZq = MatZq::from_str("[[1, 2, 3]] mod 4").unwrap();
403        let d: MatZq = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
404        assert!(a.add_safe(&b).is_err());
405        assert!(c.add_safe(&b).is_err());
406        assert!(a.add_safe(&d).is_err());
407    }
408}
409
410#[cfg(test)]
411mod test_add_matz {
412    use super::MatZq;
413    use crate::integer::MatZ;
414    use std::str::FromStr;
415
416    /// Ensures that addition between [`MatZ`] and [`MatZq`] works properly incl. reduction mod q.
417    #[test]
418    fn small_numbers() {
419        let a = MatZ::from_str("[[1, 2],[3, 4]]").unwrap();
420        let b = MatZq::from_str("[[5, 6],[9, 10]] mod 11").unwrap();
421        let cmp = MatZq::from_str("[[6, 8],[1, 3]] mod 11").unwrap();
422
423        let res_0 = &a + &b;
424        let res_1 = &b + &a;
425
426        assert_eq!(res_0, res_1);
427        assert_eq!(cmp, res_0);
428    }
429
430    /// Testing addition for large numbers
431    #[test]
432    fn large_numbers() {
433        let a: MatZ =
434            MatZ::from_str(&format!("[[1, 2, {}],[3, -4, {}]]", i64::MIN, i128::MAX,)).unwrap();
435        let b: MatZq = MatZq::from_str(&format!(
436            "[[1, 2, {}],[3, 9, {}]] mod {}",
437            i64::MIN + 1,
438            i128::MAX,
439            u64::MAX - 58
440        ))
441        .unwrap();
442
443        let c = a + &b;
444
445        assert_eq!(
446            c,
447            MatZq::from_str(&format!(
448                "[[2, 4, -{}],[6, 5, {}]] mod {}",
449                u64::MAX,
450                u128::MAX - 1,
451                u64::MAX - 58
452            ))
453            .unwrap()
454        );
455    }
456
457    /// Ensures that addition between [`MatZ`] and [`MatZq`] is available for any combination.
458    #[test]
459    fn available() {
460        let a = MatZ::new(2, 2);
461        let b = MatZq::new(2, 2, 7);
462
463        let _ = &b + &a;
464        let _ = &b + a.clone();
465        let _ = b.clone() + &a;
466        let _ = b.clone() + a.clone();
467        let _ = &a + &b;
468        let _ = &a + b.clone();
469        let _ = a.clone() + &b;
470    }
471
472    /// Ensures that mismatching rows results in a panic.
473    #[test]
474    #[should_panic]
475    fn mismatching_rows() {
476        let a = MatZ::new(3, 2);
477        let b = MatZq::new(2, 2, 7);
478
479        let _ = &b + &a;
480    }
481
482    /// Ensures that mismatching columns results in a panic.
483    #[test]
484    #[should_panic]
485    fn mismatching_column() {
486        let a = MatZ::new(2, 3);
487        let b = MatZq::new(2, 2, 7);
488
489        let _ = &b + &a;
490    }
491}