qfall_math/integer/mat_z/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 [`MatZ`] values.
10
11use super::super::MatZ;
12use crate::error::MathError;
13use crate::macros::arithmetics::{
14    arithmetic_assign_trait_borrowed_to_owned, arithmetic_trait_borrowed_to_owned,
15    arithmetic_trait_mixed_borrowed_owned,
16};
17use crate::traits::MatrixDimensions;
18use flint_sys::fmpz_mat::fmpz_mat_add;
19use std::ops::{Add, AddAssign};
20
21impl AddAssign<&MatZ> for MatZ {
22    /// Computes the addition of `self` and `other` reusing
23    /// the memory of `self`.
24    ///
25    /// Parameters:
26    /// - `other`: specifies the value to add to `self`
27    ///
28    /// # Examples
29    /// ```
30    /// use qfall_math::integer::MatZ;
31    /// let mut a = MatZ::identity(2, 2);
32    /// let b = MatZ::new(2, 2);
33    ///
34    /// a += &b;
35    /// a += b;
36    /// ```
37    ///
38    /// # Panics ...
39    /// - if the matrix dimensions mismatch.
40    fn add_assign(&mut self, other: &Self) {
41        if self.get_num_rows() != other.get_num_rows()
42            || self.get_num_columns() != other.get_num_columns()
43        {
44            panic!(
45                "Tried to add a '{}x{}' matrix and a '{}x{}' matrix.",
46                self.get_num_rows(),
47                self.get_num_columns(),
48                other.get_num_rows(),
49                other.get_num_columns()
50            );
51        }
52
53        unsafe { fmpz_mat_add(&mut self.matrix, &self.matrix, &other.matrix) };
54    }
55}
56
57arithmetic_assign_trait_borrowed_to_owned!(AddAssign, add_assign, MatZ, MatZ);
58
59impl Add for &MatZ {
60    type Output = MatZ;
61    /// Implements the [`Add`] trait for two [`MatZ`] values.
62    /// [`Add`] is implemented for any combination of [`MatZ`] and borrowed [`MatZ`].
63    ///
64    /// Parameters:
65    /// - `other`: specifies the value to add to `self`
66    ///
67    /// Returns the sum of both numbers as a [`MatZ`].
68    ///
69    /// # Examples
70    /// ```
71    /// use qfall_math::integer::MatZ;
72    /// use std::str::FromStr;
73    ///
74    /// let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
75    /// let b: MatZ = MatZ::from_str("[[1, 9, 3],[1, 0, 5]]").unwrap();
76    ///
77    /// let c: MatZ = &a + &b;
78    /// let d: MatZ = a + b;
79    /// let e: MatZ = &c + d;
80    /// let f: MatZ = c + &e;
81    /// ```
82    ///
83    /// # Panics ...
84    /// - if the dimensions of both matrices mismatch.
85    fn add(self, other: Self) -> Self::Output {
86        self.add_safe(other).unwrap()
87    }
88}
89
90impl MatZ {
91    /// Implements addition for two [`MatZ`] matrices.
92    ///
93    ///
94    /// Parameters:
95    /// - `other`: specifies the value to add to `self`
96    ///
97    /// Returns the sum of both matrices as a [`MatZ`] or an
98    /// error if the matrix dimensions mismatch.
99    ///
100    /// # Examples
101    /// ```
102    /// use qfall_math::integer::MatZ;
103    /// use std::str::FromStr;
104    ///
105    /// let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
106    /// let b: MatZ = MatZ::from_str("[[1, 9, 3],[1, 0, 5]]").unwrap();
107    ///
108    /// let c: MatZ = a.add_safe(&b).unwrap();
109    /// ```
110    /// # Errors
111    /// - Returns a [`MathError`] of type
112    ///   [`MathError::MismatchingMatrixDimension`] if the matrix dimensions
113    ///   mismatch.
114    pub fn add_safe(&self, other: &Self) -> Result<MatZ, MathError> {
115        if self.get_num_rows() != other.get_num_rows()
116            || self.get_num_columns() != other.get_num_columns()
117        {
118            return Err(MathError::MismatchingMatrixDimension(format!(
119                "Tried to add a '{}x{}' matrix and a '{}x{}' matrix.",
120                self.get_num_rows(),
121                self.get_num_columns(),
122                other.get_num_rows(),
123                other.get_num_columns()
124            )));
125        }
126        let mut out = MatZ::new(self.get_num_rows(), self.get_num_columns());
127        unsafe {
128            fmpz_mat_add(&mut out.matrix, &self.matrix, &other.matrix);
129        }
130        Ok(out)
131    }
132}
133
134arithmetic_trait_borrowed_to_owned!(Add, add, MatZ, MatZ, MatZ);
135arithmetic_trait_mixed_borrowed_owned!(Add, add, MatZ, MatZ, MatZ);
136
137#[cfg(test)]
138mod test_add_assign {
139    use crate::integer::MatZ;
140    use std::str::FromStr;
141
142    /// Ensure that `add_assign` works for small numbers.
143    #[test]
144    fn correct_small() {
145        let mut a = MatZ::identity(2, 2);
146        let b = MatZ::from_str("[[4, 5],[-6, -1]]").unwrap();
147        let cmp = MatZ::from_str("[[5, 5],[-6, 0]]").unwrap();
148
149        a += b;
150
151        assert_eq!(cmp, a);
152    }
153
154    /// Ensure that `add_assign` works for large numbers.
155    #[test]
156    fn correct_large() {
157        let mut a = MatZ::from_str(&format!("[[{}, 5],[{}, -1]]", i64::MAX, i64::MIN)).unwrap();
158        let b = MatZ::from_str(&format!("[[{}, -6],[6, -1]]", i64::MAX)).unwrap();
159        let cmp = MatZ::from_str(&format!(
160            "[[{}, -1],[{}, -2]]",
161            2 * (i64::MAX as u64),
162            i64::MIN + 6
163        ))
164        .unwrap();
165
166        a += b;
167
168        assert_eq!(cmp, a);
169    }
170
171    /// Ensure that `add_assign` works for different matrix dimensions.
172    #[test]
173    fn matrix_dimensions() {
174        let dimensions = [(3, 3), (5, 1), (1, 4)];
175
176        for (nr_rows, nr_cols) in dimensions {
177            let mut a = MatZ::new(nr_rows, nr_cols);
178            let b = MatZ::identity(nr_rows, nr_cols);
179
180            a += b;
181
182            assert_eq!(MatZ::identity(nr_rows, nr_cols), a);
183        }
184    }
185
186    /// Ensure that `add_assign` is available for all types.
187    #[test]
188    fn availability() {
189        let mut a = MatZ::new(2, 2);
190        let b = MatZ::new(2, 2);
191
192        a += &b;
193        a += b;
194    }
195}
196
197#[cfg(test)]
198mod test_add {
199    use super::MatZ;
200    use std::str::FromStr;
201
202    /// Testing addition for two [`MatZ`]
203    #[test]
204    fn add() {
205        let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
206        let b: MatZ = MatZ::from_str("[[1, 2, 3],[3, -4, 5]]").unwrap();
207        let c: MatZ = a + b;
208        assert_eq!(c, MatZ::from_str("[[2, 4, 6],[6, 0, 10]]").unwrap());
209    }
210
211    /// Testing addition for two borrowed [`MatZ`]
212    #[test]
213    fn add_borrow() {
214        let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
215        let b: MatZ = MatZ::from_str("[[1, 2, 3],[3, -4, 5]]").unwrap();
216        let c: MatZ = &a + &b;
217        assert_eq!(c, MatZ::from_str("[[2, 4, 6],[6, 0, 10]]").unwrap());
218    }
219
220    /// Testing addition for borrowed [`MatZ`] and [`MatZ`]
221    #[test]
222    fn add_first_borrowed() {
223        let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
224        let b: MatZ = MatZ::from_str("[[1, 2, 3],[3, -4, 5]]").unwrap();
225        let c: MatZ = &a + b;
226        assert_eq!(c, MatZ::from_str("[[2, 4, 6],[6, 0, 10]]").unwrap());
227    }
228
229    /// Testing addition for [`MatZ`] and borrowed [`MatZ`]
230    #[test]
231    fn add_second_borrowed() {
232        let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
233        let b: MatZ = MatZ::from_str("[[1, 2, 3],[3, -4, 5]]").unwrap();
234        let c: MatZ = a + &b;
235        assert_eq!(c, MatZ::from_str("[[2, 4, 6],[6, 0, 10]]").unwrap());
236    }
237
238    /// Testing addition for large numbers
239    #[test]
240    fn add_large_numbers() {
241        let a: MatZ =
242            MatZ::from_str(&format!("[[1, 2, {}],[3, -4, {}]]", i64::MIN, i128::MAX)).unwrap();
243        let b: MatZ =
244            MatZ::from_str(&format!("[[1, 2, {}],[3, 9, {}]]", i64::MIN + 1, i128::MAX)).unwrap();
245        let c: MatZ = a + &b;
246        assert_eq!(
247            c,
248            MatZ::from_str(&format!(
249                "[[2, 4, -{}],[6, 5, {}]]",
250                u64::MAX,
251                u128::MAX - 1
252            ))
253            .unwrap()
254        );
255    }
256
257    /// Testing add_safe
258    #[test]
259    fn add_safe() {
260        let a: MatZ = MatZ::from_str("[[1, 2, 3],[3, 4, 5]]").unwrap();
261        let b: MatZ = MatZ::from_str("[[1, 2, 3],[3, -4, 5]]").unwrap();
262        let c = a.add_safe(&b);
263        assert_eq!(
264            c.unwrap(),
265            MatZ::from_str("[[2, 4, 6],[6, 0, 10]]").unwrap()
266        );
267    }
268
269    /// Testing add_safe throws error
270    #[test]
271    fn add_safe_is_err() {
272        let a: MatZ = MatZ::from_str("[[1, 2],[3, 4]]").unwrap();
273        let b: MatZ = MatZ::from_str("[[1, 2, 3],[3, -4, 5]]").unwrap();
274        let c: MatZ = MatZ::from_str("[[1, 2, 3]]").unwrap();
275        assert!(a.add_safe(&b).is_err());
276        assert!(c.add_safe(&b).is_err());
277    }
278}