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