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}