faer/linalg/qr/mod.rs
1//! `faer` 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}}
16//! R_{\text{thin}}$, then we get $$x = R_{\text{thin}}^{-1} Q_{\text{thin}}^H b$$
17//!
18//! to translate this to code, we can proceed as follows:
19//!
20//! ```
21//! use faer::dyn_stack::{MemBuffer, MemStack, StackReq};
22//! use faer::reborrow::*;
23//!
24//! use faer::linalg::qr::no_pivoting::factor;
25//! use faer::linalg::{householder, triangular_solve};
26//!
27//! use faer::{Conj, Mat, Par, mat};
28//!
29//! // we start by defining matrices A and B that define our least-squares problem.
30//! let a = mat![
31//! [-1.14920683, -1.67950492],
32//! [-0.93009756, -0.03885086],
33//! [1.22579735, 0.88489976],
34//! [0.70698973, 0.38928314],
35//! [-1.66293762, 0.38123281],
36//! [0.27639595, -0.32559289],
37//! [-0.37506387, -0.13180778],
38//! [-1.20774962, -0.38635657],
39//! [0.44373549, 0.84397648],
40//! [-1.96779374, -1.42751757_f64],
41//! ];
42//!
43//! let b = mat![
44//! [-0.14689786, -0.52845138, -2.26975669],
45//! [-1.00844774, -1.38550214, 0.50329459],
46//! [1.07941646, 0.71514245, -0.73987761],
47//! [0.1281168, -0.23999022, 1.58776697],
48//! [-0.49385283, 1.17875407, 2.01019076],
49//! [0.65117811, -0.60339895, 0.27217694],
50//! [0.85599951, -0.00699227, 0.93607199],
51//! [-0.12635444, 0.94945626, 0.86565968],
52//! [0.02383305, 0.41515805, -1.2816278],
53//! [0.34158312, -0.07552168, 0.56724015_f64],
54//! ];
55//!
56//! // approximate solution computed with numpy
57//! let expected_solution = mat![
58//! [0.33960324, -0.33812452, -0.8458301], //
59//! [-0.25718351, 0.6281214, 1.07071764_f64],
60//! ];
61//!
62//! let rank = Ord::min(a.nrows(), a.ncols());
63//!
64//! // we choose the recommended block size for the householder factors of our problem.
65//! let blocksize = factor::recommended_blocksize::<f64>(a.nrows(), a.ncols());
66//!
67//! // we allocate the memory for the operations that we perform
68//! let mut mem =
69//! MemBuffer::new(StackReq::any_of(&[
70//! factor::qr_in_place_scratch::<f64>(
71//! a.nrows(),
72//! a.ncols(),
73//! blocksize,
74//! Par::Seq,
75//! Default::default(),
76//! ),
77//! householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<
78//! f64,
79//! >(a.nrows(), blocksize, b.ncols()),
80//! ]));
81//! let mut stack = MemStack::new(&mut mem);
82//!
83//! let mut qr = a;
84//! let mut h_factor = Mat::zeros(blocksize, rank);
85//! factor::qr_in_place(
86//! qr.as_mut(),
87//! h_factor.as_mut(),
88//! Par::Seq,
89//! stack.rb_mut(),
90//! Default::default(),
91//! );
92//!
93//! // now the householder bases are in the strictly lower trapezoidal part of `a`, and the
94//! // matrix R is in the upper triangular part of `a`.
95//!
96//! let mut solution = b.clone();
97//!
98//! // compute Q^H×B
99//! householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
100//! qr.as_ref(),
101//! h_factor.as_ref(),
102//! Conj::Yes,
103//! solution.as_mut(),
104//! Par::Seq,
105//! stack.rb_mut(),
106//! );
107//!
108//! solution.truncate(rank, b.ncols());
109//!
110//! // compute R_thin^{-1} Q_thin^H×B
111//! triangular_solve::solve_upper_triangular_in_place(
112//! qr.as_ref().split_at_row(rank).0,
113//! solution.as_mut(),
114//! Par::Seq,
115//! );
116//!
117//! for i in 0..rank {
118//! for j in 0..b.ncols() {
119//! assert!((solution[(i, j)] - expected_solution[(i, j)]).abs() <= 1e-6);
120//! }
121//! }
122//! ```
123
124pub mod col_pivoting;
125pub mod no_pivoting;
126
127#[cfg(test)]
128mod tests {
129 use crate as faer;
130 use equator::assert;
131
132 #[test]
133 fn test_example() {
134 use faer::dyn_stack::{MemBuffer, MemStack, StackReq};
135 use faer::reborrow::*;
136
137 use faer::linalg::qr::no_pivoting::factor;
138 use faer::linalg::{householder, triangular_solve};
139
140 use faer::{Conj, Mat, Par, mat};
141
142 // we start by defining matrices A and B that define our least-squares problem.
143 let a = mat![
144 [-1.14920683, -1.67950492],
145 [-0.93009756, -0.03885086],
146 [1.22579735, 0.88489976],
147 [0.70698973, 0.38928314],
148 [-1.66293762, 0.38123281],
149 [0.27639595, -0.32559289],
150 [-0.37506387, -0.13180778],
151 [-1.20774962, -0.38635657],
152 [0.44373549, 0.84397648],
153 [-1.96779374, -1.42751757_f64],
154 ];
155
156 let b = mat![
157 [-0.14689786, -0.52845138, -2.26975669],
158 [-1.00844774, -1.38550214, 0.50329459],
159 [1.07941646, 0.71514245, -0.73987761],
160 [0.1281168, -0.23999022, 1.58776697],
161 [-0.49385283, 1.17875407, 2.01019076],
162 [0.65117811, -0.60339895, 0.27217694],
163 [0.85599951, -0.00699227, 0.93607199],
164 [-0.12635444, 0.94945626, 0.86565968],
165 [0.02383305, 0.41515805, -1.2816278],
166 [0.34158312, -0.07552168, 0.56724015_f64],
167 ];
168
169 // approximate solution computed with numpy
170 let expected_solution = mat![
171 [0.33960324, -0.33812452, -0.8458301], //
172 [-0.25718351, 0.6281214, 1.07071764_f64],
173 ];
174
175 let rank = Ord::min(a.nrows(), a.ncols());
176
177 // we choose the recommended block size for the householder factors of our problem.
178 let blocksize = factor::recommended_blocksize::<f64>(a.nrows(), a.ncols());
179
180 // we allocate the memory for the operations that we perform
181 let mut mem = MemBuffer::new(StackReq::any_of(&[
182 factor::qr_in_place_scratch::<f64>(a.nrows(), a.ncols(), blocksize, Par::Seq, Default::default()),
183 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_scratch::<f64>(a.nrows(), blocksize, b.ncols()),
184 ]));
185 let mut stack = MemStack::new(&mut mem);
186
187 let mut qr = a;
188 let mut h_factor = Mat::zeros(blocksize, rank);
189 factor::qr_in_place(qr.as_mut(), h_factor.as_mut(), Par::Seq, stack.rb_mut(), Default::default());
190
191 // now the householder bases are in the strictly lower trapezoidal part of `a`, and the
192 // matrix R is in the upper triangular part of `a`.
193
194 let mut solution = b.clone();
195
196 // compute Q^H×B
197 householder::apply_block_householder_sequence_transpose_on_the_left_in_place_with_conj(
198 qr.as_ref(),
199 h_factor.as_ref(),
200 Conj::Yes,
201 solution.as_mut(),
202 Par::Seq,
203 stack.rb_mut(),
204 );
205
206 solution.truncate(rank, b.ncols());
207
208 // compute R_thin^{-1} Q_thin^H×B
209 triangular_solve::solve_upper_triangular_in_place(qr.as_ref().split_at_row(rank).0, solution.as_mut(), Par::Seq);
210
211 for i in 0..rank {
212 for j in 0..b.ncols() {
213 assert!((solution[(i, j)] - expected_solution[(i, j)]).abs() <= 1e-6);
214 }
215 }
216 }
217}