faer_qr/
lib.rs

1//! This crate provides utilities for computing and manipulating the QR factorization with and
2//! without pivoting. The QR factorization decomposes a matrix into a product of a unitary matrix
3//! $Q$ (represented using block Householder sequences), and an upper trapezoidal matrix $R$, such
4//! that their product is equal to the original matrix (or a column permutation of it in the case
5//! where column pivoting is used).
6//!
7//! # Example
8//!
9//! Assume we have an overdetermined system $AX = B$ with full rank, and that we wish to find the
10//! solution that minimizes the 2-norm.
11//!
12//! This is equivalent to computing a matrix $X$ that minimizes the value $||AX - B||^2$,
13//! which is given by the solution $$X = (A^H A)^{-1} A^H B.$$
14//!
15//! If we compute the QR decomposition of $A$, such that $A = QR = Q_{\text{thin}} R_{\text{rect}}$,
16//! then we get $$X = R_{\text{rect}}^{-1} Q_{\text{thin}}^H B.$$
17//!
18//! To translate this to code, we can proceed as follows:
19//!
20//! ```
21//! use assert_approx_eq::assert_approx_eq;
22//! use dyn_stack::{PodStack, GlobalPodBuffer, StackReq};
23//! use faer_core::{mat, solve, Conj, Mat, Parallelism};
24//! use reborrow::*;
25//!
26//! // we start by defining matrices A and B that define our least-squares problem.
27//! let a = mat![
28//!     [-1.14920683, -1.67950492],
29//!     [-0.93009756, -0.03885086],
30//!     [1.22579735, 0.88489976],
31//!     [0.70698973, 0.38928314],
32//!     [-1.66293762, 0.38123281],
33//!     [0.27639595, -0.32559289],
34//!     [-0.37506387, -0.13180778],
35//!     [-1.20774962, -0.38635657],
36//!     [0.44373549, 0.84397648],
37//!     [-1.96779374, -1.42751757_f64],
38//! ];
39//!
40//! let b = mat![
41//!     [-0.14689786, -0.52845138, -2.26975669],
42//!     [-1.00844774, -1.38550214, 0.50329459],
43//!     [1.07941646, 0.71514245, -0.73987761],
44//!     [0.1281168, -0.23999022, 1.58776697],
45//!     [-0.49385283, 1.17875407, 2.01019076],
46//!     [0.65117811, -0.60339895, 0.27217694],
47//!     [0.85599951, -0.00699227, 0.93607199],
48//!     [-0.12635444, 0.94945626, 0.86565968],
49//!     [0.02383305, 0.41515805, -1.2816278],
50//!     [0.34158312, -0.07552168, 0.56724015_f64],
51//! ];
52//!
53//! // computed with numpy
54//! let expected_solution = mat![
55//!     [0.33960324, -0.33812452, -0.8458301],
56//!     [-0.25718351, 0.6281214, 1.07071764_f64],
57//! ];
58//!
59//! let rank = a.nrows().min(a.ncols());
60//!
61//! // we choose the recommended block size for the householder factors of our problem.
62//! let blocksize = faer_qr::no_pivoting::compute::recommended_blocksize::<f64>(a.nrows(), a.ncols());
63//!
64//! // we allocate the memory for the operations that we perform
65//! let mut mem =
66//!     GlobalPodBuffer::new(StackReq::any_of(
67//!         [
68//!             faer_qr::no_pivoting::compute::qr_in_place_req::<f64>(
69//!                 a.nrows(),
70//!                 a.ncols(),
71//!                 blocksize,
72//!                 Parallelism::None,
73//!                 Default::default(),
74//!             )
75//!             .unwrap(),
76//!             faer_core::householder::apply_block_householder_sequence_transpose_on_the_left_in_place_req::<
77//!                 f64,
78//!             >(a.nrows(), blocksize, b.ncols())
79//!             .unwrap(),
80//!         ],
81//!     ));
82//! let mut stack = PodStack::new(&mut mem);
83//!
84//! let mut qr = a.clone();
85//! let mut h_factor = Mat::zeros(blocksize, rank);
86//! faer_qr::no_pivoting::compute::qr_in_place(
87//!     qr.as_mut(),
88//!     h_factor.as_mut(),
89//!     Parallelism::None,
90//!     stack.rb_mut(),
91//!     Default::default(),
92//! );
93//!
94//! // now the Householder bases are in the strictly lower trapezoidal part of `a`, and the
95//! // matrix R is in the upper triangular part of `a`.
96//!
97//! let mut solution = b.clone();
98//!
99//! // compute Q^H×B
100//! faer_core::householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
101//!     qr.as_ref(),
102//!     h_factor.as_ref(),
103//!     Conj::Yes,
104//!     solution.as_mut(),
105//!     Parallelism::None,
106//!     stack.rb_mut(),
107//! );
108//!
109//! solution.resize_with(rank, b.ncols(), |_, _| unreachable!());
110//!
111//! // compute R_rect^{-1} Q_thin^H×B
112//! solve::solve_upper_triangular_in_place(
113//!     qr.as_ref().split_at_row(rank).0,
114//!     solution.as_mut(),
115//!     Parallelism::None,
116//! );
117//!
118//! for i in 0..rank {
119//!     for j in 0..b.ncols() {
120//!         assert_approx_eq!(solution.read(i, j), expected_solution.read(i, j));
121//!     }
122//! }
123//! ```
124
125#![allow(clippy::type_complexity)]
126#![allow(clippy::too_many_arguments)]
127#![cfg_attr(not(feature = "std"), no_std)]
128
129pub mod col_pivoting;
130pub mod no_pivoting;
131
132#[cfg(test)]
133mod tests {
134
135    #[test]
136    fn test_example() {
137        use crate::no_pivoting::compute;
138        use assert_approx_eq::assert_approx_eq;
139        use dyn_stack::{GlobalPodBuffer, PodStack, StackReq};
140        use faer_core::{householder, mat, solve, Conj, Mat, Parallelism};
141        use reborrow::*;
142
143        // we start by defining matrices A and B that define our least-squares problem.
144        let a = mat![
145            [-1.14920683, -1.67950492],
146            [-0.93009756, -0.03885086],
147            [1.22579735, 0.88489976],
148            [0.70698973, 0.38928314],
149            [-1.66293762, 0.38123281],
150            [0.27639595, -0.32559289],
151            [-0.37506387, -0.13180778],
152            [-1.20774962, -0.38635657],
153            [0.44373549, 0.84397648],
154            [-1.96779374, -1.42751757_f64],
155        ];
156
157        let b = mat![
158            [-0.14689786, -0.52845138, -2.26975669],
159            [-1.00844774, -1.38550214, 0.50329459],
160            [1.07941646, 0.71514245, -0.73987761],
161            [0.1281168, -0.23999022, 1.58776697],
162            [-0.49385283, 1.17875407, 2.01019076],
163            [0.65117811, -0.60339895, 0.27217694],
164            [0.85599951, -0.00699227, 0.93607199],
165            [-0.12635444, 0.94945626, 0.86565968],
166            [0.02383305, 0.41515805, -1.2816278],
167            [0.34158312, -0.07552168, 0.56724015_f64],
168        ];
169
170        // computed with numpy
171        let expected_solution = mat![
172            [0.33960324, -0.33812452, -0.8458301],
173            [-0.25718351, 0.6281214, 1.07071764_f64],
174        ];
175
176        let rank = a.nrows().min(a.ncols());
177
178        // we choose the recommended block size for the householder factors of our problem.
179        let blocksize = compute::recommended_blocksize::<f64>(a.nrows(), a.ncols());
180
181        // we allocate the memory for the operations that we perform
182        let mut mem =
183            GlobalPodBuffer::new(StackReq::any_of([
184                compute::qr_in_place_req::<f64>(
185                    a.nrows(),
186                    a.ncols(),
187                    blocksize,
188                    Parallelism::None,
189                    Default::default(),
190                )
191                .unwrap(),
192                faer_core::householder::apply_block_householder_sequence_transpose_on_the_left_in_place_req::<
193                    f64,
194                >(a.nrows(), blocksize, b.ncols())
195                .unwrap(),
196            ]));
197        let mut stack = PodStack::new(&mut mem);
198
199        let mut qr = a;
200        let mut h_factor = Mat::zeros(blocksize, rank);
201        compute::qr_in_place(
202            qr.as_mut(),
203            h_factor.as_mut(),
204            Parallelism::None,
205            stack.rb_mut(),
206            Default::default(),
207        );
208
209        // now the Householder bases are in the strictly lower trapezoidal part of `a`, and the
210        // matrix R is in the upper triangular part of `a`.
211
212        let mut solution = b.clone();
213
214        // compute Q^H×B
215        householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
216            qr.as_ref(),
217            h_factor.as_ref(),
218            Conj::Yes,
219            solution.as_mut(),
220            Parallelism::None,
221            stack.rb_mut(),
222        );
223
224        solution.resize_with(rank, b.ncols(), |_, _| unreachable!());
225
226        // compute R_rect^{-1} Q_thin^H×B
227        solve::solve_upper_triangular_in_place(
228            qr.as_ref().split_at_row(rank).0,
229            solution.as_mut(),
230            Parallelism::None,
231        );
232
233        for i in 0..rank {
234            for j in 0..b.ncols() {
235                assert_approx_eq!(solution.read(i, j), expected_solution.read(i, j));
236            }
237        }
238    }
239}