qfall_math/rational/mat_q/arithmetic/
div_scalar.rs

1// Copyright © 2025 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//! Implementation of scalar division for [`MatQ`] matrices.
10
11use crate::{
12    integer::Z,
13    macros::{
14        arithmetics::{
15            arithmetic_assign_between_types, arithmetic_assign_trait_borrowed_to_owned,
16            arithmetic_trait_borrowed_to_owned, arithmetic_trait_mixed_borrowed_owned,
17        },
18        for_others::implement_for_others,
19    },
20    rational::{MatQ, Q},
21    traits::MatrixDimensions,
22};
23use flint_sys::fmpq_mat::fmpq_mat_scalar_div_fmpz;
24use std::ops::{Div, DivAssign, MulAssign};
25
26impl Div<&Z> for &MatQ {
27    type Output = MatQ;
28
29    /// Implements the [`Div`] trait for a [`MatQ`] by a [`Z`] integer.
30    /// [`Div`] is implemented for any combination of owned and borrowed values.
31    ///
32    /// Parameters:
33    /// - `scalar`: specifies the scalar by which the matrix is divided
34    ///
35    /// Returns the division of `self` by `scalar` as a [`MatQ`].
36    ///
37    /// # Examples
38    /// ```
39    /// use qfall_math::rational::{MatQ, Q};
40    /// use std::str::FromStr;
41    ///
42    /// let matq_1 = MatQ::from_str("[[1, 2, 3],[4, 5/4, -1]]").unwrap();
43    /// let rational = Q::from((2,3));
44    ///
45    /// let matq_2 = &matq_1 / &rational;
46    /// ```
47    fn div(self, scalar: &Z) -> Self::Output {
48        assert!(!scalar.is_zero(), "Tried to divide {self} by zero.");
49
50        let mut out = MatQ::new(self.get_num_rows(), self.get_num_columns());
51        unsafe {
52            fmpq_mat_scalar_div_fmpz(&mut out.matrix, &self.matrix, &scalar.value);
53        }
54        out
55    }
56}
57
58arithmetic_trait_borrowed_to_owned!(Div, div, MatQ, Z, MatQ);
59arithmetic_trait_mixed_borrowed_owned!(Div, div, MatQ, Z, MatQ);
60
61implement_for_others!(Z, MatQ, MatQ, Div Scalar for i8 i16 i32 i64 u8 u16 u32 u64);
62
63impl Div<&Q> for &MatQ {
64    type Output = MatQ;
65    /// Implements the [`Div`] trait for a [`MatQ`] by a [`Q`] rational.
66    /// [`Div`] is implemented for any combination of owned and borrowed values.
67    ///
68    /// Parameters:
69    /// - `scalar`: specifies the scalar by which the matrix is divided
70    ///
71    /// Returns the division of `self` by `scalar` as a [`MatQ`].
72    ///
73    /// # Examples
74    /// ```
75    /// use qfall_math::rational::{MatQ, Q};
76    /// use std::str::FromStr;
77    ///
78    /// let matq_1 = MatQ::from_str("[[1, 2, 3],[4, 5/4, -1]]").unwrap();
79    /// let rational = Q::from((2,3));
80    ///
81    /// let matq_2 = &matq_1 / &rational;
82    /// ```
83    ///
84    /// # Panics ...
85    /// - if the `scalar` is `0`.
86    #[allow(clippy::suspicious_arithmetic_impl)]
87    fn div(self, scalar: &Q) -> Self::Output {
88        assert!(!scalar.is_zero(), "Tried to divide {self} by zero.");
89
90        let scalar = scalar.inverse().unwrap();
91        self * scalar
92    }
93}
94
95arithmetic_trait_borrowed_to_owned!(Div, div, MatQ, Q, MatQ);
96arithmetic_trait_mixed_borrowed_owned!(Div, div, MatQ, Q, MatQ);
97
98implement_for_others!(Q, MatQ, MatQ, Div Scalar for f32 f64);
99
100impl DivAssign<&Q> for MatQ {
101    /// Computes the scalar multiplication of `self` and `other` reusing
102    /// the memory of `self`.
103    ///
104    /// Parameters:
105    /// - `other`: specifies the value to multiply to `self`
106    ///
107    /// Divides `self` coordinate-wise by `other` returning a [`MatQ`].
108    ///
109    /// # Examples
110    /// ```
111    /// use qfall_math::rational::{Q, MatQ};
112    /// use qfall_math::integer::{Z};
113    /// use std::str::FromStr;
114    ///
115    /// let mut matq = MatQ::from_str(&format!("[[1, 2, 3],[4, 5/4, -1]]")).unwrap();
116    /// let q = Q::from((3, 4));
117    /// let z = Z::from(5);
118    ///
119    /// matq /= &q;
120    /// matq /= q;
121    /// matq /= &z;
122    /// matq /= z;
123    /// matq /= -1;
124    /// matq /= 2;
125    /// ```
126    ///
127    /// # Panics ...
128    /// - if the `scalar` is `0`.
129    fn div_assign(&mut self, scalar: &Q) {
130        assert!(!scalar.is_zero(), "Tried to divide {self} by zero.");
131
132        let scalar = scalar.inverse().unwrap();
133        self.mul_assign(scalar);
134    }
135}
136
137impl DivAssign<&Z> for MatQ {
138    /// Documentation at [`MatQ::div_assign`].
139    fn div_assign(&mut self, scalar: &Z) {
140        assert!(!scalar.is_zero(), "Tried to divide {self} by zero.");
141
142        unsafe { fmpq_mat_scalar_div_fmpz(&mut self.matrix, &self.matrix, &scalar.value) };
143    }
144}
145
146arithmetic_assign_trait_borrowed_to_owned!(DivAssign, div_assign, MatQ, Q);
147arithmetic_assign_trait_borrowed_to_owned!(DivAssign, div_assign, MatQ, Z);
148arithmetic_assign_between_types!(DivAssign, div_assign, MatQ, Z, u64 u32 u16 u8 i64 i32 i16 i8);
149arithmetic_assign_between_types!(DivAssign, div_assign, MatQ, Q, f32 f64);
150
151#[cfg(test)]
152mod test_div_z {
153    use super::MatQ;
154    use crate::integer::Z;
155    use std::str::FromStr;
156
157    /// Checks if scalar division works fine for both borrowed
158    #[test]
159    fn borrowed_correctness() {
160        let cmp = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
161        let mat = MatQ::from_str("[[2, 3],[3/2, 6]]").unwrap();
162        let integer = Z::from(3);
163
164        assert_eq!(cmp, &mat / &integer);
165    }
166
167    /// Checks if scalar division works fine for both owned
168    #[test]
169    fn owned_correctness() {
170        let cmp = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
171        let mat = MatQ::from_str("[[2, 3],[3/2, 6]]").unwrap();
172        let integer = Z::from(3);
173
174        assert_eq!(cmp, mat / integer);
175    }
176
177    /// Checks if scalar division works fine for half owned/borrowed
178    #[test]
179    fn half_correctness() {
180        let cmp = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
181        let mat = MatQ::from_str("[[2, 3],[3/2, 6]]").unwrap();
182        let integer = Z::from(3);
183
184        assert_eq!(cmp, &mat / integer.clone());
185        assert_eq!(cmp, mat / &integer);
186    }
187
188    /// Checks if scalar division works fine for different scalar types
189    #[test]
190    #[allow(clippy::erasing_op)]
191    fn different_types() {
192        let mat_1 = MatQ::from_str("[[1/2],[0],[4]]").unwrap();
193        let mat_2 = MatQ::from_str("[[2, 5, 6],[1, 3, 1]]").unwrap();
194        let mat_3 = MatQ::from_str("[[1],[0],[8]]").unwrap();
195        let mat_5 = MatQ::from_str("[[-1/2],[0],[-4]]").unwrap();
196        let mat_6 = MatQ::from_str("[[6, 15, 18],[3, 9, 3]]").unwrap();
197
198        assert_eq!(&mat_3 / 2u8, mat_1);
199        assert_eq!(&mat_3 / 2i8, mat_1);
200        assert_eq!(&mat_3 / 2u16, mat_1);
201        assert_eq!(&mat_3 / 2i16, mat_1);
202        assert_eq!(&mat_3 / 2u32, mat_1);
203        assert_eq!(&mat_3 / 2i32, mat_1);
204        assert_eq!(&mat_3 / 2u64, mat_1);
205        assert_eq!(&mat_3 / 2i64, mat_1);
206        assert_eq!(mat_5, &mat_1 / -1);
207        assert_eq!(&mat_6 / 3, mat_2);
208    }
209
210    /// Checks if scalar division works fine for matrices of different dimensions
211    #[test]
212    fn different_dimensions_correctness() {
213        let mat_1 = MatQ::from_str("[[1/2],[0],[4]]").unwrap();
214        let mat_2 = MatQ::from_str("[[2, 5/8, 6],[1, 3, 1/7]]").unwrap();
215        let mat_3 = MatQ::from_str("[[3/2],[0],[12]]").unwrap();
216        let mat_4 = MatQ::from_str("[[6, 15/8, 18],[3, 9, 3/7]]").unwrap();
217        let integer = Z::from(3);
218
219        assert_eq!(mat_1, mat_3 / &integer);
220        assert_eq!(mat_2, mat_4 / integer);
221    }
222
223    /// Checks if scalar division works fine for large values
224    #[test]
225    fn large_entries() {
226        let mat_1 = MatQ::from_str(&format!("[[1],[{}],[1/{}]]", i64::MAX, i64::MAX)).unwrap();
227        let mat_2 = MatQ::from_str("[[3]]").unwrap();
228        let mat_3 = MatQ::from_str(&format!(
229            "[[3],[{}],[3/{}]]",
230            3 * i64::MAX as i128,
231            i64::MAX
232        ))
233        .unwrap();
234        let mat_4 = MatQ::from_str(&format!("[[{}]]", 3 * i64::MAX as i128)).unwrap();
235        let integer_1 = Z::from(3);
236        let integer_2 = Z::from(i64::MAX);
237
238        assert_eq!(mat_3 / integer_1, mat_1);
239        assert_eq!(mat_4 / integer_2, mat_2);
240    }
241
242    /// Ensure that `div` panics if a division by `0` occurs
243    #[test]
244    #[should_panic]
245    fn div_0() {
246        let mat = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
247        let integer = Z::from(0);
248
249        let _ = &mat / &integer;
250    }
251}
252
253#[cfg(test)]
254mod test_mul_q {
255    use super::MatQ;
256    use crate::rational::Q;
257    use std::str::FromStr;
258
259    /// Checks if scalar division works fine for both borrowed
260    #[test]
261    fn borrowed_correctness() {
262        let mat = MatQ::from_str("[[1, 3/2],[3/4, 3]]").unwrap();
263        let rational = Q::from((3, 2));
264        let cmp = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
265
266        assert_eq!(&mat / &rational, cmp);
267    }
268
269    /// Checks if scalar division works fine for both owned
270    #[test]
271    fn owned_correctness() {
272        let mat = MatQ::from_str("[[1, 3/2],[3/4, 3]]").unwrap();
273        let rational = Q::from((3, 2));
274        let cmp = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
275
276        assert_eq!(mat / rational, cmp);
277    }
278
279    /// Checks if scalar division works fine for half owned/borrowed
280    #[test]
281    fn half_correctness() {
282        let mat = MatQ::from_str("[[1, 3/2],[3/4, 3]]").unwrap();
283        let rational = Q::from((3, 2));
284        let cmp = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
285
286        assert_eq!(&mat / rational.clone(), cmp);
287        assert_eq!(mat / &rational, cmp);
288    }
289
290    /// Checks if scalar division works fine for different scalar types
291    #[test]
292    #[allow(clippy::erasing_op)]
293    fn different_types() {
294        let mat_1 = MatQ::from_str("[[1/2],[0],[4]]").unwrap();
295        let mat_2 = MatQ::from_str("[[2, 5, 6],[1, 3, 1]]").unwrap();
296        let mat_3 = MatQ::from_str("[[5/4],[0],[10]]").unwrap();
297        let mat_4 = MatQ::from_str("[[-799/8],[0],[-799]]").unwrap();
298        let mat_5 = MatQ::from_str("[[285/4, 1425/8, 855/4],[285/8, 855/8, 285/8]]").unwrap();
299
300        assert_eq!(mat_3 / 2.5f32, mat_1);
301        assert_eq!(mat_4 / -199.75f64, mat_1);
302        assert_eq!(mat_5 / 35.625, mat_2);
303    }
304
305    /// Checks if scalar division works fine for matrices of different dimensions
306    #[test]
307    fn different_dimensions_correctness() {
308        let mat_1 = MatQ::from_str("[[1/2],[0],[4]]").unwrap();
309        let mat_2 = MatQ::from_str("[[2, 5/8, 6],[1, 3, 1/7]]").unwrap();
310        let mat_3 = MatQ::from_str("[[3/4],[0],[6]]").unwrap();
311        let mat_4 = MatQ::from_str("[[3, 15/16, 9],[3/2, 9/2, 3/14]]").unwrap();
312        let rational = Q::from((3, 2));
313
314        assert_eq!(mat_3, &rational * mat_1);
315        assert_eq!(mat_4, rational * mat_2);
316    }
317
318    /// Checks if scalar division works fine for large values
319    #[test]
320    fn large_entries() {
321        let mat_1 = MatQ::from_str(&format!("[[1],[{}],[1/{}]]", i64::MAX, i64::MAX)).unwrap();
322        let mat_2 = MatQ::from_str("[[3]]").unwrap();
323        let mat_3 = MatQ::from_str(&format!(
324            "[[3/2],[{}/2],[3/{}]]",
325            3 * i64::MAX as i128,
326            2 * i64::MAX as i128
327        ))
328        .unwrap();
329        let mat_4 = MatQ::from_str(&format!("[[{}/2]]", 3 * i64::MAX as i128)).unwrap();
330        let mat_5 = MatQ::from_str(&format!("[[6/{}]]", i64::MAX)).unwrap();
331        let rational_1 = Q::from((3, 2));
332        let rational_2 = Q::from((i64::MAX, 2));
333        let rational_3 = Q::from((2, i64::MAX));
334
335        assert_eq!(mat_3 / rational_1, mat_1);
336        assert_eq!(mat_4 / rational_2, mat_2);
337        assert_eq!(mat_5 / rational_3, mat_2);
338    }
339
340    /// Ensure that `div` panics if a division by `0` occurs
341    #[test]
342    #[should_panic]
343    fn div_0() {
344        let mat = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
345        let rational = Q::from(0);
346
347        let _ = &mat / &rational;
348    }
349}
350
351#[cfg(test)]
352mod test_div_assign {
353    use crate::integer::Z;
354    use crate::rational::{MatQ, Q};
355    use std::str::FromStr;
356
357    /// Ensure that `div_assign` produces same output as normal division.
358    #[test]
359    fn consistency() {
360        let mut a = MatQ::from_str("[[2, 1],[-1, 0]]").unwrap();
361        let b = Q::from((1, i32::MAX));
362        let cmp = &a / &b;
363
364        a /= b;
365
366        assert_eq!(cmp, a);
367    }
368
369    /// Ensure that `div_assign` is available for all types.
370    #[test]
371    fn availability() {
372        let mut a = MatQ::from_str("[[2, 1],[1, 2]]").unwrap();
373        let b = Z::from(2);
374        let c = Q::from((2, 3));
375
376        a /= &b;
377        a /= b;
378        a /= &c;
379        a /= c;
380        a /= 1_u8;
381        a /= 1_u16;
382        a /= 1_u32;
383        a /= 1_u64;
384        a /= 1_i8;
385        a /= 1_i16;
386        a /= 1_i32;
387        a /= 1_i64;
388        a /= 1_f32;
389        a /= 1_f64;
390    }
391
392    /// Ensure that `div_assign` panics if a division by 0 would occur.
393    #[test]
394    #[should_panic]
395    fn div_0() {
396        let mut a = MatQ::from_str("[[2, 1],[-1, 0]]").unwrap();
397        let b = Q::from((0, i32::MAX));
398
399        a /= b;
400    }
401}