Skip to main content

feanor_math/algorithms/
qr.rs

1use std::cmp::min;
2
3use crate::algorithms::matmul::ComputeInnerProduct;
4use crate::field::{Field, FieldStore};
5use crate::integer::*;
6use crate::matrix::*;
7use crate::ring::*;
8use crate::rings::approx_real::{ApproxRealField, SqrtRing};
9use crate::rings::rational::*;
10
11#[stability::unstable(feature = "enable")]
12pub trait QRDecompositionField: Field {
13    /// Given a matrix `A`, computes an orthogonal matrix `Q` and an upper triangular
14    /// matrix `R` with `A = Q R`. The function writes `Q diag(x_1, ..., x_n)` to `q` and
15    /// `diag(1/x_1, ..., 1/x_n) R` to `matrix`, and returns `x_1^2, ..., x_n^2`, where
16    /// `x_1, ..., x_n` are the elements on the diagonal of `R`.
17    ///
18    /// Returning the values as given above instead of just `Q` and `R` is done
19    /// to avoid the computation of square-roots, which may not be supported by the
20    /// underlying ring. If it is supported, you can use
21    /// [`QRDecompositionField::qr_decomposition()`] instead. Note that this means that
22    /// `diag(x_1^2, ..., x_n^2)` and `R` are the LDL-decomposition of `A^T A`.
23    ///
24    /// # Rank-deficient matrices
25    ///
26    /// Do not use this for matrices that do not have full rank. If the underlying ring
27    /// is exact, this will panic. For approximate rings (in particular floating-point numbers),
28    /// matrices that don't have full rank, or are very badly conditioned, will give inaccurate
29    /// results.
30    ///
31    /// Clearly, rank-deficient matrices cannot be supported, since for those the value
32    /// `diag(1/x_1, ..., 1/x_n)` is not defined.
33    fn scaled_qr_decomposition<V1, V2>(
34        &self,
35        matrix: SubmatrixMut<V1, Self::Element>,
36        q: SubmatrixMut<V2, Self::Element>,
37    ) -> Vec<Self::Element>
38    where
39        V1: AsPointerToSlice<Self::Element>,
40        V2: AsPointerToSlice<Self::Element>;
41
42    /// Given a square symmetric matrix `A`, computes a strict lower triangular matrix `L` and
43    /// a diagonal matrix `D` such that `A = L D L^T`. The function writes `L` to `matrix`
44    /// and returns the diagonal elements of `D`.
45    ///
46    /// # Singular matrices
47    ///
48    /// Do not use this for matrices that are singular. If the underlying ring is exact,
49    /// this will panic. For approximate rings (in particular floating-point numbers),
50    /// matrices that don't have full rank, or are very badly conditioned, will give inaccurate
51    /// results. Note however that the matrix is not required to be positive definite, it may
52    /// have both positive and negative eigenvalues (but no zero eigenvalues).
53    ///
54    /// Why don't we support singular matrices? Because many singular matrices don't have
55    /// an LDL decomposition. For example, the matrix `[[ 0, 1 ], [ 1, 1 ]]` doesn't.
56    fn ldl_decomposition<V>(&self, matrix: SubmatrixMut<V, Self::Element>) -> Vec<Self::Element>
57    where
58        V: AsPointerToSlice<Self::Element>,
59    {
60        ldl_decomposition_impl(RingRef::new(self), matrix)
61    }
62
63    /// Given a matrix `A`, computes an orthogonal matrix `Q` and an upper triangular
64    /// matrix `R` with `A = Q R`. These are returned in `matrix` and `q`, respectively.
65    ///
66    /// Note that if the ring is not a [`SqrtRing`], you can still use
67    /// [`QRDecompositionField::scaled_qr_decomposition()`].
68    ///
69    /// This function supports non-full-rank matrices as well.
70    fn qr_decomposition<V1, V2>(
71        &self,
72        mut matrix: SubmatrixMut<V1, Self::Element>,
73        mut q: SubmatrixMut<V2, Self::Element>,
74    ) where
75        V1: AsPointerToSlice<Self::Element>,
76        V2: AsPointerToSlice<Self::Element>,
77        Self: SqrtRing,
78    {
79        let d = self.scaled_qr_decomposition(matrix.reborrow(), q.reborrow());
80        for (i, scale_sqr) in d.into_iter().enumerate() {
81            let scale = self.sqrt(scale_sqr);
82            let scale_inv = self.div(&self.one(), &scale);
83            for j in 0..matrix.col_count() {
84                self.mul_assign_ref(matrix.at_mut(i, j), &scale);
85            }
86            for k in 0..q.row_count() {
87                self.mul_assign_ref(q.at_mut(k, i), &scale_inv);
88            }
89        }
90    }
91}
92
93impl<I> QRDecompositionField for RationalFieldBase<I>
94where
95    I: RingStore,
96    I::Type: IntegerRing,
97{
98    fn scaled_qr_decomposition<V1, V2>(
99        &self,
100        mut matrix: SubmatrixMut<V1, Self::Element>,
101        mut q: SubmatrixMut<V2, Self::Element>,
102    ) -> Vec<Self::Element>
103    where
104        V1: AsPointerToSlice<Self::Element>,
105        V2: AsPointerToSlice<Self::Element>,
106    {
107        // since there is no issue with numerical stability, we can do Gram-Schmidt
108        let ring = RingValue::from_ref(self);
109        let m = matrix.row_count();
110        let n = matrix.col_count();
111        assert_eq!(m, q.row_count());
112        assert_eq!(m, q.col_count());
113
114        let mut result = Vec::with_capacity(n);
115        let mut mus = Vec::with_capacity(n);
116        for i in 0..n {
117            mus.clear();
118            for j in 0..i {
119                mus.push(self.div(
120                    &<_ as ComputeInnerProduct>::inner_product_ref(self, (0..m).map(|k| (matrix.at(k, i), q.at(k, j)))),
121                    &result[j],
122                ));
123            }
124            let (mut target, orthogonalized) = q.reborrow().split_cols(i..(i + 1), 0..i);
125            for k in 0..m {
126                *target.at_mut(k, 0) = self.sub_ref_fst(
127                    matrix.at(k, i),
128                    <_ as ComputeInnerProduct>::inner_product_ref(
129                        self,
130                        (0..i).map(|j| (&mus[j], orthogonalized.at(k, j))),
131                    ),
132                );
133            }
134            result.push(<_ as RingStore>::sum(
135                ring,
136                (0..m).map(|k| ring.pow(ring.clone_el(target.at(k, 0)), 2)),
137            ));
138            for (k, c) in mus.drain(..).enumerate() {
139                *matrix.at_mut(k, i) = c;
140            }
141            *matrix.at_mut(i, i) = self.one();
142            for k in (i + 1)..m {
143                *matrix.at_mut(k, i) = self.zero();
144            }
145        }
146
147        return result;
148    }
149}
150
151fn ldl_decomposition_impl<R, V>(ring: R, mut matrix: SubmatrixMut<V, El<R>>) -> Vec<El<R>>
152where
153    R: RingStore,
154    R::Type: Field,
155    V: AsPointerToSlice<El<R>>,
156{
157    assert_eq!(matrix.row_count(), matrix.col_count());
158    let n = matrix.row_count();
159    let mut result = Vec::with_capacity(n);
160    for i in 0..n {
161        let pivot = ring.clone_el(matrix.at(i, i));
162        if !ring.get_ring().is_approximate() && ring.is_zero(&pivot) {
163            panic!("matrix is singular")
164        }
165        let pivot_inv = ring.div(&ring.one(), matrix.at(i, i));
166        for j in i..n {
167            ring.mul_assign_ref(matrix.at_mut(j, i), &pivot_inv);
168        }
169        for k in (i + 1)..n {
170            for l in k..n {
171                let subtract = ring.mul_ref_snd(
172                    ring.mul_ref(matrix.as_const().at(k, i), matrix.as_const().at(l, i)),
173                    &pivot,
174                );
175                ring.sub_assign(matrix.at_mut(l, k), subtract);
176            }
177        }
178        result.push(pivot);
179    }
180    for i in 0..n {
181        for j in (i + 1)..n {
182            *matrix.at_mut(i, j) = ring.zero();
183        }
184    }
185    return result;
186}
187
188impl<R: ApproxRealField + SqrtRing> QRDecompositionField for R {
189    default fn scaled_qr_decomposition<V1, V2>(
190        &self,
191        mut matrix: SubmatrixMut<V1, Self::Element>,
192        mut q: SubmatrixMut<V2, Self::Element>,
193    ) -> Vec<Self::Element>
194    where
195        V1: AsPointerToSlice<Self::Element>,
196        V2: AsPointerToSlice<Self::Element>,
197    {
198        self.qr_decomposition(matrix.reborrow(), q.reborrow());
199        let mut result = Vec::with_capacity(matrix.row_count());
200        for i in 0..matrix.row_count() {
201            let mut scale = self.clone_el(matrix.at(i, i));
202            let scale_inv = self.div(&self.one(), &scale);
203            for j in i..matrix.col_count() {
204                self.mul_assign_ref(matrix.at_mut(i, j), &scale_inv);
205            }
206            for j in 0..q.row_count() {
207                self.mul_assign_ref(q.at_mut(j, i), &scale);
208            }
209            self.square(&mut scale);
210            result.push(scale);
211        }
212        return result;
213    }
214
215    default fn ldl_decomposition<V>(&self, matrix: SubmatrixMut<V, Self::Element>) -> Vec<Self::Element>
216    where
217        V: AsPointerToSlice<Self::Element>,
218    {
219        ldl_decomposition_impl(RingRef::new(self), matrix)
220    }
221
222    default fn qr_decomposition<V1, V2>(
223        &self,
224        mut matrix: SubmatrixMut<V1, Self::Element>,
225        mut q: SubmatrixMut<V2, Self::Element>,
226    ) where
227        V1: AsPointerToSlice<Self::Element>,
228        V2: AsPointerToSlice<Self::Element>,
229    {
230        let ring = RingRef::new(self);
231        let m = matrix.row_count();
232        let n = matrix.col_count();
233        assert_eq!(m, q.row_count());
234        assert_eq!(m, q.col_count());
235        for i in 0..m {
236            for j in 0..m {
237                *q.at_mut(i, j) = if i == j { self.one() } else { self.zero() };
238            }
239        }
240
241        let mut householder_vector = Vec::with_capacity(m);
242        for i in 0..min(n, m) {
243            let norm_sqr = <_ as RingStore>::sum(&ring, (i..m).map(|k| ring.pow(ring.clone_el(matrix.at(k, i)), 2)));
244            let norm = self.sqrt(self.clone_el(&norm_sqr));
245            let alpha = if self.is_neg(matrix.at(i, i)) {
246                self.clone_el(&norm)
247            } else {
248                self.negate(self.clone_el(&norm))
249            };
250            // | x - alpha * e1 | / sqrt(2)
251            let scale = self.sqrt(self.sub(norm_sqr, self.mul_ref(&alpha, matrix.at(i, i))));
252            householder_vector.clear();
253            householder_vector.extend((i..m).map(|k| ring.clone_el(matrix.at(k, i))));
254            ring.sub_assign_ref(&mut householder_vector[0], &alpha);
255            for x in &mut householder_vector {
256                *x = self.div(x, &scale);
257            }
258
259            // update matrix
260            let mut rest = matrix.reborrow().submatrix(i..m, (i + 1)..n);
261            for j in 0..(n - i - 1) {
262                let inner_product = <_ as ComputeInnerProduct>::inner_product_ref(
263                    self,
264                    (0..(m - i)).map(|k| (&householder_vector[k], rest.at(k, j))),
265                );
266                for k in 0..(m - i) {
267                    ring.sub_assign(rest.at_mut(k, j), ring.mul_ref(&inner_product, &householder_vector[k]));
268                }
269            }
270
271            // update q
272            let mut rest = q.reborrow().restrict_cols(i..m);
273            for j in 0..m {
274                let inner_product = <_ as ComputeInnerProduct>::inner_product_ref(
275                    self,
276                    (0..(m - i)).map(|k| (&householder_vector[k], rest.at(j, k))),
277                );
278                for k in 0..(m - i) {
279                    ring.sub_assign(rest.at_mut(j, k), ring.mul_ref(&inner_product, &householder_vector[k]));
280                }
281            }
282
283            // update pivot
284            let mut pivot_col = matrix.reborrow().submatrix(i..m, i..(i + 1));
285            for k in 1..(m - i) {
286                *pivot_col.at_mut(k, 0) = self.zero();
287            }
288            *pivot_col.at_mut(0, 0) = alpha;
289        }
290    }
291}
292
293#[cfg(test)]
294use crate::algorithms::matmul::MatmulAlgorithm;
295#[cfg(test)]
296use crate::algorithms::matmul::STANDARD_MATMUL;
297#[cfg(test)]
298use crate::assert_matrix_eq;
299#[cfg(test)]
300use crate::homomorphism::Homomorphism;
301#[cfg(test)]
302use crate::matrix::format_matrix;
303#[cfg(test)]
304use crate::matrix::{TransposableSubmatrix, TransposableSubmatrixMut};
305#[cfg(test)]
306use crate::primitive_int::StaticRing;
307#[cfg(test)]
308use crate::rings::approx_real::float::Real64;
309#[cfg(test)]
310use crate::rings::fraction::FractionFieldStore;
311
312#[cfg(test)]
313fn assert_is_correct_qr<V1, V2, V3>(original: Submatrix<V1, f64>, q: Submatrix<V2, f64>, r: Submatrix<V3, f64>)
314where
315    V1: AsPointerToSlice<f64>,
316    V2: AsPointerToSlice<f64>,
317    V3: AsPointerToSlice<f64>,
318{
319    let m = q.row_count();
320    let n = r.col_count();
321    assert_eq!(m, original.row_count());
322    assert_eq!(n, original.col_count());
323    assert_eq!(m, r.row_count());
324    let mut product = OwnedMatrix::zero(m, n, Real64::RING);
325    STANDARD_MATMUL.matmul(
326        TransposableSubmatrix::from(q),
327        TransposableSubmatrix::from(r),
328        TransposableSubmatrixMut::from(product.data_mut()),
329        Real64::RING,
330    );
331    for i in 0..m {
332        for j in 0..n {
333            if !(Real64::RING
334                .get_ring()
335                .is_approx_eq(*original.at(i, j), *product.at(i, j), 100))
336            {
337                println!("product does not match; Q, R are");
338                println!("{}", format_matrix(m, m, |i, j| q.at(i, j), Real64::RING));
339                println!("and");
340                println!("{}", format_matrix(m, n, |i, j| r.at(i, j), Real64::RING));
341                println!("the product is");
342                println!("{}", format_matrix(m, n, |i, j| product.at(i, j), Real64::RING));
343                panic!();
344            }
345        }
346    }
347    let mut product = OwnedMatrix::zero(m, m, Real64::RING);
348    STANDARD_MATMUL.matmul(
349        TransposableSubmatrix::from(q).transpose(),
350        TransposableSubmatrix::from(q),
351        TransposableSubmatrixMut::from(product.data_mut()),
352        Real64::RING,
353    );
354    for i in 0..m {
355        for j in 0..m {
356            let expected = if i == j { 1.0 } else { 0.0 };
357            if !(Real64::RING.get_ring().is_approx_eq(expected, *product.at(i, j), 100)) {
358                println!("Q is not orthogonal");
359                println!("{}", format_matrix(m, m, |i, j| q.at(i, j), Real64::RING));
360                panic!();
361            }
362        }
363    }
364
365    for j in 0..n {
366        for i in (j + 1)..m {
367            if !(Real64::RING.get_ring().is_approx_eq(0.0, *r.at(i, j), 100)) {
368                println!("R is not upper triangular");
369                println!("{}", format_matrix(m, n, |i, j| r.at(i, j), Real64::RING));
370                panic!();
371            }
372        }
373    }
374}
375
376#[cfg(test)]
377fn assert_is_correct_ldl<V1, V2>(original: Submatrix<V1, f64>, l: Submatrix<V2, f64>, d: &[f64])
378where
379    V1: AsPointerToSlice<f64>,
380    V2: AsPointerToSlice<f64>,
381{
382    let n = l.col_count();
383    assert_eq!(n, l.row_count());
384    assert_eq!(n, original.col_count());
385    assert_eq!(n, original.row_count());
386    let l_scaled = OwnedMatrix::from_fn(n, n, |i, j| *l.at(i, j) * d[j]);
387    let mut product = OwnedMatrix::zero(n, n, Real64::RING);
388    STANDARD_MATMUL.matmul(
389        TransposableSubmatrix::from(l_scaled.data()),
390        TransposableSubmatrix::from(l).transpose(),
391        TransposableSubmatrixMut::from(product.data_mut()),
392        Real64::RING,
393    );
394    for i in 0..n {
395        for j in 0..n {
396            if !(Real64::RING
397                .get_ring()
398                .is_approx_eq(*original.at(i, j), *product.at(i, j), 100))
399            {
400                println!("product does not match; L is");
401                println!("{}", format_matrix(n, n, |i, j| l.at(i, j), Real64::RING));
402                println!("D is diag{:?} and the product LDL^T is", d);
403                println!("{}", format_matrix(n, n, |i, j| product.at(i, j), Real64::RING));
404                panic!();
405            }
406        }
407    }
408    for i in 0..n {
409        for j in (i + 1)..n {
410            if !(Real64::RING.get_ring().is_approx_eq(0.0, *l.at(i, j), 100)) {
411                println!("L is not lower triangular");
412                println!("{}", format_matrix(n, n, |i, j| l.at(i, j), Real64::RING));
413                panic!();
414            }
415        }
416    }
417}
418
419#[test]
420fn test_float_qr() {
421    let RR = Real64::RING;
422    let a = OwnedMatrix::new_with_shape(vec![0.0, 1.0, 1.0, 0.0], 2, 2);
423    let mut r = a.clone_matrix(RR);
424    let mut q = OwnedMatrix::zero(2, 2, RR);
425    RR.get_ring().qr_decomposition(r.data_mut(), q.data_mut());
426    assert_is_correct_qr(a.data(), q.data(), r.data());
427
428    let a = OwnedMatrix::new_with_shape(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 2);
429    let mut r = a.clone_matrix(RR);
430    let mut q = OwnedMatrix::zero(3, 3, RR);
431    RR.get_ring().qr_decomposition(r.data_mut(), q.data_mut());
432    assert_is_correct_qr(a.data(), q.data(), r.data());
433
434    let a = OwnedMatrix::new_with_shape(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2, 3);
435    let mut r = a.clone_matrix(RR);
436    let mut q = OwnedMatrix::zero(2, 2, RR);
437    RR.get_ring().qr_decomposition(r.data_mut(), q.data_mut());
438    assert_is_correct_qr(a.data(), q.data(), r.data());
439
440    let a = OwnedMatrix::new_with_shape(vec![1.0, 1.0, 1.0, 2.0, 2.0, 3.0, 0.0, 0.0, 1.0], 3, 3);
441    let mut r = a.clone_matrix(RR);
442    let mut q = OwnedMatrix::zero(3, 3, RR);
443    RR.get_ring().qr_decomposition(r.data_mut(), q.data_mut());
444    assert_is_correct_qr(a.data(), q.data(), r.data());
445
446    let a = OwnedMatrix::new_with_shape(
447        (1..31)
448            .map(|x| x as f64 * if x % 2 == 0 { -1.0 } else { 1.0 })
449            .collect::<Vec<_>>(),
450        6,
451        5,
452    );
453    let mut r = a.clone_matrix(RR);
454    let mut q = OwnedMatrix::zero(6, 6, RR);
455    RR.get_ring().qr_decomposition(r.data_mut(), q.data_mut());
456    assert_is_correct_qr(a.data(), q.data(), r.data());
457}
458
459#[test]
460fn test_float_qdr() {
461    let RR = Real64::RING;
462    let a = OwnedMatrix::new_with_shape((1..10).map(|c| c as f64).collect(), 3, 3);
463    let mut r = a.clone_matrix(RR);
464    let mut q = OwnedMatrix::zero(3, 3, RR);
465    let diags = RR.get_ring().scaled_qr_decomposition(r.data_mut(), q.data_mut());
466    for i in 0..3 {
467        for j in 0..3 {
468            if i == j {
469                assert!(RR.get_ring().is_approx_eq(1.0, *r.at(i, j), 100));
470            }
471            RR.mul_assign(r.at_mut(i, j), diags[i].sqrt());
472            RR.mul_assign(q.at_mut(i, j), 1.0 / diags[j].sqrt());
473        }
474    }
475    assert_is_correct_qr(a.data(), q.data(), r.data());
476}
477
478#[test]
479fn test_float_ldl() {
480    let RR = Real64::RING;
481    let a = OwnedMatrix::new_with_shape(vec![5.0, 1.0, 1.0, 5.0], 2, 2);
482    let mut l = a.clone_matrix(RR);
483    let d = RR.get_ring().ldl_decomposition(l.data_mut());
484    assert_is_correct_ldl(a.data(), l.data(), &d);
485
486    let a = OwnedMatrix::new_with_shape(vec![1.0, 2.0, 3.0, 2.0, 6.0, 5.0, 3.0, 5.0, 20.0], 3, 3);
487    let mut l = a.clone_matrix(RR);
488    let d = RR.get_ring().ldl_decomposition(l.data_mut());
489    assert_is_correct_ldl(a.data(), l.data(), &d);
490
491    let mut a = OwnedMatrix::zero(5, 5, RR);
492    let factor = OwnedMatrix::new((0..25).map(|c| (c as f64).powi(2)).collect(), 5);
493    STANDARD_MATMUL.matmul(
494        TransposableSubmatrix::from(factor.data()),
495        TransposableSubmatrix::from(factor.data()).transpose(),
496        TransposableSubmatrixMut::from(a.data_mut()),
497        RR,
498    );
499    let mut l = a.clone_matrix(RR);
500    let d = RR.get_ring().ldl_decomposition(l.data_mut());
501    assert_is_correct_ldl(a.data(), l.data(), &d);
502
503    let a = OwnedMatrix::new_with_shape(vec![1.0, 2.0, 3.0, 2.0, 6.0, 5.0, 3.0, 5.0, -20.0], 3, 3);
504    let mut l = a.clone_matrix(RR);
505    let d = RR.get_ring().ldl_decomposition(l.data_mut());
506    assert_is_correct_ldl(a.data(), l.data(), &d);
507}
508
509#[test]
510fn test_rational_qdr() {
511    let QQ = RationalField::new(StaticRing::<i64>::RING);
512    let mut actual_r = OwnedMatrix::new_with_shape((1..10).map(|x| QQ.pow(QQ.int_hom().map(x), 2)).collect(), 3, 3);
513    let mut actual_q = OwnedMatrix::zero(3, 3, &QQ);
514    let diags = QQ
515        .get_ring()
516        .scaled_qr_decomposition(actual_r.data_mut(), actual_q.data_mut());
517    assert_el_eq!(&QQ, QQ.from_fraction(2658, 1), &diags[0]);
518    assert_el_eq!(&QQ, QQ.from_fraction(9891, 443), &diags[1]);
519    assert_el_eq!(&QQ, QQ.from_fraction(864, 1099), &diags[2]);
520
521    let mut expected_r = OwnedMatrix::identity(3, 3, &QQ);
522    *expected_r.at_mut(0, 1) = QQ.from_fraction(590, 443);
523    *expected_r.at_mut(0, 2) = QQ.from_fraction(759, 443);
524    *expected_r.at_mut(1, 2) = QQ.from_fraction(2700, 1099);
525    assert_matrix_eq!(&QQ, expected_r, actual_r);
526
527    let expected_q_num = [
528        [486857, 1299018, 356172],
529        [7789712, 1796865, -233904],
530        [23855993, -613242, 69108],
531    ];
532    let expected_q_den = 443 * 1099;
533    let expected_q = OwnedMatrix::from_fn(3, 3, |i, j| QQ.from_fraction(expected_q_num[i][j], expected_q_den));
534    assert_matrix_eq!(&QQ, expected_q, actual_q);
535}