qfall_math/rational/mat_q/arithmetic/mul.rs
1// Copyright © 2023 Marcel Luca Schmidt, 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 [`Mul`] trait for [`MatQ`] values.
10
11use super::super::MatQ;
12use crate::error::MathError;
13use crate::integer::MatZ;
14use crate::macros::arithmetics::{
15 arithmetic_trait_borrowed_to_owned, arithmetic_trait_mixed_borrowed_owned,
16};
17use crate::traits::{MatrixDimensions, MatrixSetEntry};
18use flint_sys::fmpq_mat::{fmpq_mat_mul, fmpq_mat_mul_fmpz_mat};
19use std::ops::Mul;
20
21impl Mul for &MatQ {
22 type Output = MatQ;
23
24 /// Implements the [`Mul`] trait for two [`MatQ`] values.
25 /// [`Mul`] is implemented for any combination of owned and borrowed [`MatQ`].
26 ///
27 /// Parameters:
28 /// - `other`: specifies the value to multiply with `self`
29 ///
30 /// Returns the product of `self` and `other` as a [`MatQ`].
31 ///
32 /// # Examples
33 /// ```
34 /// use qfall_math::rational::MatQ;
35 /// use std::str::FromStr;
36 ///
37 /// let a: MatQ = MatQ::from_str("[[1/2, 2/3],[3/4, 5/7]]").unwrap();
38 /// let b: MatQ = MatQ::from_str("[[1/4, 9/7],[1, 5]]").unwrap();
39 ///
40 /// let c = &a * &b;
41 /// let d = a * b;
42 /// let e = &c * d;
43 /// let f = c * &e;
44 /// ```
45 ///
46 /// # Panics ...
47 /// - if the dimensions of `self` and `other` do not match for multiplication.
48 fn mul(self, other: Self) -> Self::Output {
49 self.mul_safe(other).unwrap()
50 }
51}
52
53arithmetic_trait_borrowed_to_owned!(Mul, mul, MatQ, MatQ, MatQ);
54arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatQ, MatQ, MatQ);
55
56impl Mul<&MatZ> for &MatQ {
57 type Output = MatQ;
58
59 /// Implements the [`Mul`] trait for [`MatQ`] and [`MatZ`].
60 /// [`Mul`] is implemented for any combination of owned and borrowed values.
61 ///
62 /// Parameters:
63 /// - `other`: specifies the value to multiply with `self`
64 ///
65 /// Returns the product of `self` and `other` as a [`MatQ`].
66 ///
67 /// # Examples
68 /// ```
69 /// use qfall_math::integer::MatZ;
70 /// use qfall_math::rational::MatQ;
71 /// use std::str::FromStr;
72 ///
73 /// let a = MatQ::from_str("[[2/3, 1/2],[8/4, 7]]").unwrap();
74 /// let b = MatZ::identity(2, 2);
75 ///
76 /// let c = &a * &b;
77 /// let d = a * b;
78 /// let e = c * &MatZ::identity(2, 2);
79 /// let f = &e * MatZ::identity(2, 2);
80 /// ```
81 ///
82 /// # Panics ...
83 /// - if the dimensions of `self` and `other` do not match for multiplication.
84 fn mul(self, other: &MatZ) -> Self::Output {
85 assert_eq!(
86 self.get_num_columns(),
87 other.get_num_rows(),
88 "Tried to multiply matrices with mismatching matrix dimensions."
89 );
90
91 let mut new = MatQ::new(self.get_num_rows(), other.get_num_columns());
92 unsafe { fmpq_mat_mul_fmpz_mat(&mut new.matrix, &self.matrix, &other.matrix) };
93 new
94 }
95}
96
97impl MatQ {
98 /// Multiplies the matrices `self` and `other` naively with each other
99 /// using their [`f64`] presentation, i.e. with a small loss of precision.
100 ///
101 /// This function can speed up multiplications of [`MatQ`]'s as it allows for
102 /// some loss of precision. The loss of precision depends on the size of the matrices
103 /// and how exact the entries could be represented by a [`f64`].
104 ///
105 /// **WARNING:** This function is less efficient than [`Mul`] for integer values
106 /// or entries with small numerators and denominators. This function becomes more
107 /// efficient once `self` or `other` has entries with large numerators and denominators
108 /// as FLINT's implementation does not allow any loss of precision.
109 ///
110 /// **WARNING:** Please be aware that the deviation of the representation of the matrices' entries as [`f64`]
111 /// will scale with the size of the entries, e.g. an entry within the size of `2^{64}`
112 /// might deviate from the original value by a distance of `1_000`. This loss of precision
113 /// might be aggravated by this matrix multiplication with a factor of `self.get_num_columns()`
114 /// for each entry in the resulting matrix.
115 ///
116 /// **WARNING:** This function is unchecked, i.e. the user is expected to align matrix
117 /// dimensions for multiplication.
118 ///
119 /// # Example
120 /// ```
121 /// use qfall_math::integer::MatZ;
122 /// let mat = MatZ::sample_uniform(3, 3, -256, 256).unwrap().inverse().unwrap();
123 ///
124 /// let mat_inv_sqrd = mat.mul_f64_unchecked(&mat);
125 /// ```
126 ///
127 /// # Panics ...
128 /// - if the dimensions of `self` and `other` do not match for multiplication.
129 /// - if any result during the naive computation of matrix multiplication
130 /// is larger than [`f64::MAX`] or smaller than [`f64::MIN`].
131 #[allow(clippy::needless_range_loop)]
132 pub fn mul_f64_unchecked(&self, other: &Self) -> MatQ {
133 let num_rows = self.get_num_rows() as usize;
134 let num_cols = other.get_num_columns() as usize;
135
136 let mat_self = self.collect_entries_f64();
137 let mat_other = other.collect_entries_f64();
138
139 let mut mat_out = MatQ::new(num_rows, num_cols);
140 for (i, row) in mat_self.iter().enumerate() {
141 for j in 0..num_cols {
142 let mut entry = 0.0;
143 for k in 0..self.get_num_columns() as usize {
144 entry += row[k] * mat_other[k][j];
145 }
146 mat_out.set_entry(i, j, entry).unwrap();
147 }
148 }
149
150 mat_out
151 }
152}
153
154arithmetic_trait_borrowed_to_owned!(Mul, mul, MatQ, MatZ, MatQ);
155arithmetic_trait_mixed_borrowed_owned!(Mul, mul, MatQ, MatZ, MatQ);
156
157impl MatQ {
158 /// Implements multiplication for two [`MatQ`] values.
159 ///
160 /// Parameters:
161 /// - `other`: specifies the value to multiply with `self`
162 ///
163 /// Returns the product of `self` and `other` as a [`MatQ`] or
164 /// an error, if the dimensions of `self` and `other` do not match for multiplication.
165 ///
166 /// # Examples
167 /// ```
168 /// use qfall_math::rational::MatQ;
169 /// use std::str::FromStr;
170 ///
171 /// let a: MatQ = MatQ::from_str("[[1/2, 2/3],[3/4, 4/5]]").unwrap();
172 /// let b: MatQ = MatQ::from_str("[[1/4, 3/7],[1, 0]]").unwrap();
173 ///
174 /// let c: MatQ = a.mul_safe(&b).unwrap();
175 /// ```
176 ///
177 /// # Errors and Failures
178 /// - Returns a [`MathError`] of type
179 /// [`MathError::MismatchingMatrixDimension`] if the dimensions of `self`
180 /// and `other` do not match for multiplication.
181 pub fn mul_safe(&self, other: &Self) -> Result<Self, MathError> {
182 if self.get_num_columns() != other.get_num_rows() {
183 return Err(MathError::MismatchingMatrixDimension(format!(
184 "Tried to multiply a '{}x{}' matrix and a '{}x{}' matrix.",
185 self.get_num_rows(),
186 self.get_num_columns(),
187 other.get_num_rows(),
188 other.get_num_columns()
189 )));
190 }
191
192 let mut new = MatQ::new(self.get_num_rows(), other.get_num_columns());
193 unsafe { fmpq_mat_mul(&mut new.matrix, &self.matrix, &other.matrix) };
194 Ok(new)
195 }
196}
197
198#[cfg(test)]
199mod test_mul {
200 use super::MatQ;
201 use crate::{rational::Q, traits::MatrixSetEntry};
202 use std::str::FromStr;
203
204 /// Checks if matrix multiplication works fine for squared matrices
205 #[test]
206 fn square_correctness() {
207 let mat_1 = MatQ::from_str("[[2/3, 1/3],[1/3, 2/3]]").unwrap();
208 let mat_2 = MatQ::identity(2, 2);
209 let mat_3 = MatQ::from_str("[[1/7, 2/7],[2/7, 1/7]]").unwrap();
210 let cmp = MatQ::from_str("[[4/21, 5/21],[5/21, 4/21]]").unwrap();
211
212 assert_eq!(mat_1, &mat_1 * &mat_2);
213 assert_eq!(cmp, &mat_1 * &mat_3);
214 }
215
216 /// Checks if matrix multiplication works fine for matrices of different dimensions
217 #[test]
218 fn different_dimensions_correctness() {
219 let mat = MatQ::from_str("[[2/3, 1/5],[1/5, 2/19]]").unwrap();
220 let vec = MatQ::from_str("[[1/7],[0]]").unwrap();
221 let cmp = MatQ::from_str("[[2/21],[1/35]]").unwrap();
222
223 assert_eq!(cmp, &mat * &vec);
224 }
225
226 /// Checks if matrix multiplication works fine for large entries
227 #[test]
228 fn large_entries() {
229 let mat = MatQ::from_str(&format!("[[{}, 1],[0, 2]]", i64::MAX)).unwrap();
230 let vec = MatQ::from_str(&format!("[[1/{}],[0]]", i64::MAX)).unwrap();
231 let mut cmp = MatQ::new(2, 1);
232 let max: Q = Q::from(i64::MAX);
233 cmp.set_entry(0, 0, &(&max * Q::from((1, i64::MAX))))
234 .unwrap();
235
236 assert_eq!(cmp, mat * vec);
237 }
238
239 /// Checks if matrix multiplication with incompatible matrix dimensions
240 /// throws an error as expected
241 #[test]
242 fn incompatible_dimensions() {
243 let mat_1 = MatQ::from_str("[[2, 1/9],[1/7, 2]]").unwrap();
244 let mat_2 = MatQ::from_str("[[1/6, 0],[0, 3/8],[0, 0]]").unwrap();
245
246 assert!((mat_1.mul_safe(&mat_2)).is_err());
247 }
248}
249
250#[cfg(test)]
251mod test_mul_matz {
252 use super::MatQ;
253 use crate::integer::MatZ;
254 use crate::rational::Q;
255 use crate::traits::MatrixSetEntry;
256 use std::str::FromStr;
257
258 /// Checks if matrix multiplication works fine for squared matrices
259 #[test]
260 fn square_correctness() {
261 let mat_1 = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
262 let mat_2 = MatZ::identity(2, 2);
263 let mat_3 = MatZ::from_str("[[1, 2],[2, 1]]").unwrap();
264 let cmp = MatQ::from_str("[[8/3, 7/3],[9/2, 3]]").unwrap();
265
266 assert_eq!(mat_1, &mat_1 * &mat_2);
267 assert_eq!(cmp, &mat_1 * &mat_3);
268 }
269
270 /// Checks if matrix multiplication works fine for matrices of different dimensions
271 #[test]
272 fn different_dimensions_correctness() {
273 let mat = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
274 let vec = MatZ::from_str("[[2],[0]]").unwrap();
275 let cmp = MatQ::from_str("[[4/3],[1]]").unwrap();
276
277 assert_eq!(cmp, &mat * &vec);
278 }
279
280 /// Checks if matrix multiplication works fine for large entries
281 #[test]
282 fn large_entries() {
283 let mat = MatQ::from_str(&format!("[[{}, 1],[0, 2/{}]]", u64::MAX, u64::MAX)).unwrap();
284 let vec = MatZ::from_str(&format!("[[{}],[0]]", u64::MAX)).unwrap();
285 let mut cmp = MatQ::new(2, 1);
286 let max: Q = u64::MAX.into();
287 cmp.set_entry(0, 0, &(&max * &max)).unwrap();
288
289 assert_eq!(cmp, &mat * &vec);
290 }
291
292 /// Checks if matrix multiplication with incompatible matrix dimensions
293 /// throws an error as expected
294 #[test]
295 #[should_panic]
296 fn errors() {
297 let mat_1 = MatQ::from_str("[[2/3, 1],[1/2, 2]]").unwrap();
298 let mat_2 = MatZ::from_str("[[1, 0],[0, 1],[0, 0]]").unwrap();
299 let _ = &mat_1 * &mat_2;
300 }
301}
302
303#[cfg(test)]
304mod test_mul_f64_unchecked {
305 use crate::{
306 rational::{MatQ, Q},
307 traits::{Distance, MatrixGetEntry},
308 };
309 use std::str::FromStr;
310
311 /// Ensures that the result of the multiplication is valid.
312 #[test]
313 fn correctness() {
314 // If the entries of the matrix are changed to fractions that can't be represented
315 // exactly by f64, the assertions need to be adapted to check for small losses.
316 let mat_0 = MatQ::from_str("[[1,0],[0,1]]").unwrap();
317 let mat_1 = MatQ::from_str("[[4,5],[5/10,-4/8],[-3,0]]").unwrap();
318 let mat_2 = MatQ::from_str("[[-3/-4],[1/2]]").unwrap();
319
320 assert_eq!(mat_0, mat_0.mul_f64_unchecked(&mat_0));
321 assert_eq!(&mat_1 * &mat_0, mat_1.mul_f64_unchecked(&mat_0));
322 assert_eq!(&mat_0 * &mat_2, mat_0.mul_f64_unchecked(&mat_2));
323 }
324
325 /// Ensures that the loss of precision is reasonable.
326 /// This test just showcases / gives an idea that the loss of precision
327 /// should be fairly irrelevant for most use-cases. Nevertheless,
328 /// the loss of precision depends on the dimensions of the matrices
329 /// and the loss of precision due to transforming to [`f64`].
330 #[test]
331 fn loss_of_precision() {
332 let mat = MatQ::from_str(&format!("[[1/{},0],[0,-1/{}]]", u64::MAX, i64::MAX)).unwrap();
333 let cmp_0 = Q::from((1, u64::MAX));
334 let cmp_1 = Q::from((1, i64::MAX));
335 let max_loss = Q::from((1, i64::MAX));
336
337 let res = mat.mul_f64_unchecked(&mat);
338
339 assert!(res.get_entry(0, 0).unwrap().distance(cmp_0) < max_loss);
340 assert!(res.get_entry(1, 1).unwrap().distance(cmp_1) < max_loss);
341 }
342
343 /// Ensures that the function panics if invalid dimensions are input.
344 #[test]
345 #[should_panic]
346 fn incorrect_dimensions() {
347 let mat_0 = MatQ::identity(2, 3);
348 let mat_1 = MatQ::new(1, 2);
349
350 let _ = mat_0.mul_f64_unchecked(&mat_1);
351 }
352}