Skip to main content

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