Linear Algebra  master
Accessible implementations of linear algebra algorithms
HouseholderQR.cpp
Go to the documentation of this file.
2 
3 #include <cassert>
4 
23  // For the intermediate calculations, we'll be working with RW.
24  // It is initialized to the rectangular matrix to be factored.
25  // At the end of this function, RW will contain the strict
26  // upper-triangular part of the matrix R (without the diagonal),
27  // and the complete scaled matrix of reflection vectors W, which is a
28  // lower-triangular matrix. The diagonal of R is stored separately in
29  // R_diag.
30 
31  assert(RW.rows() >= RW.cols());
32  assert(R_diag.size() == RW.cols());
33 
34  // Helper function to square a number
35  auto sq = [](double x) { return x * x; };
36 
37  for (size_t k = 0; k < RW.cols(); ++k) {
38  // Introduce a column vector x = A[k:M,k], it's the lower part of the
39  // k-th column of the matrix.
40  // First compute the norm of x:
41 
42  double sq_norm_x = 0;
43  for (size_t i = k; i < RW.rows(); ++i)
44  sq_norm_x += sq(RW(i, k));
45  double norm_x = std::sqrt(sq_norm_x);
46 
47  // x consists of two parts: its first element, x₀, and the rest, xₛ
48  // x = (x₀, xₛ)
49  // You can express the norm of x in terms of the norms of the two parts:
50  // ‖x‖² = x₀² + ‖xₛ‖²
51  double &x_0 = RW(k, k);
52 
53  // The goal of QR factorization is to introduce zeros below the diagonal
54  // in the R factor by transforming the vector x to a new vector that is
55  // all zero, except for the first component.
56  // Call this vector xₕ, the Householder reflection of x.
57  // Since the transformation has to be unitary (Q is a unitary matrix),
58  // this operation has to preserve the 2-norm. This means that the
59  // nonzero component of xₕ has to have the same 2-norm (energy) as x:
60  // xₕ = (±‖x‖, 0, ..., 0) = ±‖x‖·̅e₁
61  // where ̅e₁ is the first standard basis vector (1, 0, ..., 0).
62  //
63  // There are two (real) vectors that have the same energy as x in their
64  // first component, because the sign doesn't affect the energy.
65  // For numerical reasons, it's best to pick the sign opposite to the
66  // sign of the first component of x, so that x and xₕ are far apart.
67  //
68  // The reflector vector vₖ is the difference between the original
69  // vector x and its Householder reflection xₕ:
70  // vₖ = x - xₕ
71  // = x - (-sign(x₀)·‖x‖·̅e₁)
72  // = x + sign(x₀)·‖x‖·̅e₁
73  // = (x₀ + sign(x₀)·‖x‖, xₛ)
74  //
75  // Since vₖ will later be used to construct a projection matrix, it
76  // should be normalized.
77  // Computing the full norm is not necessary, since
78  // ‖vₖ‖² = (x₀ + sign(x₀)·‖x‖)² + ‖xₛ‖²
79  // = x₀² + 2·x₀·sign(x₀)·‖x‖ + ‖x‖² + ‖xₛ‖²
80  // = 2·x₀·sign(x₀)·‖x‖ + 2·‖x‖²
81  // = 2·|x₀|·‖x‖ + 2·‖x‖²
82  // ‖vₖ‖ = √2·√(|x₀|·‖x‖ + ‖x‖²)
83  //
84  // Normalize vₖ and call this uₖ:
85  // uₖ = vₖ / ‖vₖ‖
86  //
87  // For reasons that will become apparent in a moment, we'll keep the
88  // factor of √2 in our normalization of vₖ, call this vector wₖ:
89  // wₖ = √2·uₖ
90  // = √2·vₖ / ‖vₖ‖
91  // = vₖ / √(|x₀|·‖x‖ + ‖x‖²)
92  // Note how the sum only adds up numbers with the same sign. This
93  // prevents catastrophic cancelations.
94  //
95  // It is clear that if ‖x‖ = 0, this normalization will fail. In that
96  // case, we set uₖ = ̅e₁ or wₖ = √2·̅e₁.
97  //
98  // x will be overwritten by wₖ. The vector xₕ only has a single nonzero
99  // component. It is saved in the R_diag vector.
100 
101  if (norm_x >= std::numeric_limits<double>::min() * 2) {
102  double x_p = -std::copysign(norm_x, x_0); // -sign(x₀)·‖x‖
103  double v_0 = x_0 - x_p;
104  double norm_v_sq2 = std::sqrt(std::abs(x_0) * norm_x + sq_norm_x);
105 
106  // Overwrite x with vₖ:
107  x_0 = v_0;
108  // the other components of x (xₛ) are already equal to the bottom
109  // part of vₖ, so they don't have to be overwritten explicitly.
110 
111  // Then normalize x (= vₖ) to obtain wₖ:
112  for (size_t i = k; i < RW.rows(); ++i)
113  RW(i, k) /= norm_v_sq2;
114 
115  // Save the first component of xₕ:
116  R_diag(k) = x_p;
117  } else {
118  // Overwrite x with wₖ = √2·̅e₁:
119  x_0 = std::sqrt(2);
120  // the other components of x (xₛ) are already equal to zero, since
121  // ‖x‖ = 0.
122 
123  // Save the first component of xₕ:
124  R_diag(k) = 0;
125  }
126 
127  // Now that the reflection vector vₖ (wₖ) is known, the rest of the
128  // matrix A can be updated, that is A[k:m,k:n].
129  //
130  // The reflection was defined in terms of the reflection vector
131  // vₖ = x - xₕ. To reflect x onto xₕ, you can first project x onto the
132  // orthogonal complement of the space spanned by vₖ, call this
133  // projection xₚ, and then add the difference between xₚ and x to xₚ
134  // again, in order to reflect about the orthogonal complement of vₖ,
135  // rather than projecting onto it:
136  // xₕ = xₚ + (xₚ - x)
137  // = 2·xₚ - x
138  // The projection matrix onto the orthogonal complement of the space
139  // spanned by vₖ is:
140  // P⟂ = I - uₖ·uₖᵀ
141  // where I is the identity matrix, and uₖ the unit vector in the
142  // direction of vₖ, as defined above.
143  // This allows us to compute xₚ:
144  // xₚ = P⟂·x
145  // = I·x - uₖ·uₖᵀ·x
146  // = x - uₖ·uₖᵀ·x
147  // xₕ = 2·xₚ - x
148  // = 2·x - 2·uₖ·uₖᵀ·x - x
149  // = x - 2·uₖ·uₖᵀ·x
150  // Because of our choice of wₖ = √2·uₖ earlier, this simplifies to
151  // xₕ = x - wₖ·wₖᵀ·x
152  // The Householder reflector is thus the linear transformation given by
153  // H = I - wₖ·wₖᵀ
154  //
155  // The final step is to apply this reflector to the remaining part of
156  // the matrix A, i.e. A[k:m,k:n].
157  // The first column x = A[k:m,k] has already been updated, that's how
158  // the reflector was computed, so only the submatrix A[k+1:m,k:n] has
159  // to be updated.
160  // A'[k+1:m,k:n] = H·A[k+1:m,k:n]
161  // = A[k+1:m,k:n] - wₖ·wₖᵀ·A[k+1:m,k:n]
162  // This can be computed column-wise:
163  // aᵢ' = aᵢ - wₖ·wₖᵀ·aᵢ
164  // where aᵢ is the i-th column of A.
165 
166  for (size_t c = k + 1; c < RW.cols(); ++c) {
167  // Compute wₖᵀ·aᵢ
168  double dot_product = 0;
169  for (size_t r = k; r < RW.rows(); ++r)
170  dot_product += RW(r, k) * RW(r, c);
171  // Subtract wₖ·wₖᵀ·aᵢ
172  for (size_t r = k; r < RW.rows(); ++r)
173  RW(r, c) -= RW(r, k) * dot_product;
174  }
175  }
176  state = Factored;
177 }
179 
186  assert(is_factored());
187  assert(RW.rows() == B.rows());
188  // Apply the Householder reflectors to each column of B.
189  for (size_t i = 0; i < B.cols(); ++i) {
190  // Recall that the Householder reflector H is applied as follows:
191  // bᵢ'[k+1:m] = H·bᵢ[k+1:m]
192  // = bᵢ[k+1:m] - wₖ·wₖᵀ·bᵢ[k+1:m]
193  for (size_t k = 0; k < RW.cols(); ++k) {
194  // Compute wₖᵀ·bᵢ
195  double dot_product = 0;
196  for (size_t r = k; r < RW.rows(); ++r)
197  dot_product += RW(r, k) * B(r, i);
198  // Subtract wₖ·wₖᵀ·bᵢ
199  for (size_t r = k; r < RW.rows(); ++r)
200  B(r, i) -= RW(r, k) * dot_product;
201  }
202  }
203 }
205 
212  assert(is_factored());
213  assert(RW.rows() == X.rows());
214  // Apply the Householder reflectors in reverse order to each column of X.
215  for (size_t i = 0; i < X.cols(); ++i) {
216  // Recall that the Householder reflector H is applied as follows:
217  // xᵢ'[k+1:m] = H·xᵢ[k+1:m]
218  // = xᵢ[k+1:m] - wₖ·wₖᵀ·xᵢ[k+1:m]
219  for (size_t k = RW.cols(); k-- > 0;) {
220  // Compute wₖᵀ·xᵢ
221  double dot_product = 0;
222  for (size_t r = k; r < RW.rows(); ++r)
223  dot_product += RW(r, k) * X(r, i);
224  // Subtract wₖ·wₖᵀ·xᵢ
225  for (size_t r = k; r < RW.rows(); ++r)
226  X(r, i) -= RW(r, k) * dot_product;
227  }
228  }
229 }
231 
237 void HouseholderQR::back_subs(const Matrix &B, Matrix &X) const {
238  // Solve upper triangular system RX = B by solving each column of B as a
239  // vector system Rxᵢ = bᵢ
240  //
241  // ┌ ┐┌ ┐ ┌ ┐
242  // │ r₁₁ r₁₂ r₁₃ r₁₄ ││ x₁ᵢ │ │ b₁ᵢ │
243  // │ r₂₂ r₂₃ r₂₄ ││ x₂ᵢ │ = │ b₂ᵢ │
244  // │ r₃₃ r₃₄ ││ x₃ᵢ │ │ b₃ᵢ │
245  // │ r₄₄ ││ x₄ᵢ │ │ b₄ᵢ │
246  // └ ┘└ ┘ └ ┘
247  //
248  // b₄ᵢ = r₄₄·x₄ᵢ ⟺ x₄ᵢ = b₄ᵢ/r₄₄
249  // b₃ᵢ = r₃₃·x₃ᵢ + r₃₄·x₄ᵢ ⟺ x₃ᵢ = (b₃ᵢ - r₃₄·x₄ᵢ)/r₃₃
250  // b₂ᵢ = r₂₂·x₂ᵢ + r₂₃·x₃ᵢ + r₂₄·x₄ᵢ ⟺ x₂ᵢ = (b₂ᵢ - r₂₃·x₃ᵢ + r₂₄·x₄ᵢ)/r₂₂
251  // ...
252 
253  for (size_t i = 0; i < B.cols(); ++i) {
254  for (size_t k = RW.cols(); k-- > 0;) {
255  X(k, i) = B(k, i);
256  for (size_t j = k + 1; j < RW.cols(); ++j) {
257  X(k, i) -= RW(k, j) * X(j, i);
258  }
259  X(k, i) /= R_diag(k);
260  }
261  }
262 }
264 
271  // If AX = B, then QRX = B, or RX = QᵀB, so first apply Qᵀ to B:
272  apply_QT_inplace(B);
273 
274  // To solve RX = QᵀB, use back substitution:
275 
276  // If the matrix is square, B and X have the same size, so we can reuse B's
277  // storage if we operate on B directly:
278  if (RW.cols() == RW.rows()) {
279  back_subs(B, B);
280  }
281  // If the matrix is rectangular, the sizes of B and X differ, so use a
282  // separate result variable:
283  else {
284  Matrix X(RW.cols(), B.cols());
285  back_subs(B, X);
286  B = std::move(X);
287  }
288 }
290 
291 // All implementations of the less interesting functions can be found here:
Vector R_diag
Contains the diagonal elements of R.
void apply_QT_inplace(Matrix &B) const
Compute the product QᵀB, overwriting B with the result.
enum HouseholderQR::State state
void back_subs(const Matrix &B, Matrix &X) const
Back substitution algorithm for solving upper-triangular systems RX = B.
Matrix RW
Result of a Householder QR factorization: stores the strict upper-triangular part of matrix R and the...
void solve_inplace(Matrix &B) const
Solve the system AX = B or QRX = B.
bool is_factored() const
Check if this object contains a valid factorization.
void compute_factorization()
The actual QR factorization algorithm.
void apply_Q_inplace(Matrix &X) const
Compute the product QB, overwriting B with the result.
General matrix class.
Definition: Matrix.hpp:25
size_t rows() const
Get the number of rows of the matrix.
Definition: Matrix.hpp:69
size_t cols() const
Get the number of columns of the matrix.
Definition: Matrix.hpp:71
size_t size() const
Get the number of elements in the vector.
Definition: Matrix.hpp:273