Linear Algebra  master
Accessible implementations of linear algebra algorithms
RowPivotLU.cpp
Go to the documentation of this file.
1 #include <linalg/RowPivotLU.hpp>
2 
3 #include <cassert>
4 
26  // For the intermediate calculations, we'll be working with LU.
27  // It is initialized to the square n×n matrix to be factored.
28 
29  assert(LU.rows() == LU.cols());
30  assert(P.size() == LU.rows());
31 
32  // The goal of the LU factorization algorithm is to repeatedly apply
33  // transformations Lₖ to the matrix A to eventually end up with an upper-
34  // triangular matrix U. When row pivoting is used, the rows of A are
35  // permuted using a permutation matrix P:
36  //
37  // Lₙ'⋯L₂'L₁'PA = U
38  //
39  // The main steps of the algorithm are exactly the same as the original
40  // LU algorithm explained in LU.cpp, and will not be repeated here.
41  // The only difference is that instead of using the diagonal element as the
42  // pivot, rows are swapped so that the element with the largest magnitude
43  // ends up on the diagonal and can be used as the pivot.
44 
45  // Loop over all columns of A:
46  for (size_t k = 0; k < LU.cols(); ++k) {
47  // In the following comments, k = [1, n], because this is more intuitive
48  // and it follows the usual mathematical convention.
49  // In the code, however, array indices start at zero, so k = [0, n-1].
50 
51  // On each iteration, the largest element on or below the diagonal in
52  // the current (k-th) column will be used as the pivot.
53  // To this end, the k-th row and the row that contains the largest
54  // element are swapped, and the swapping is stored in the permutation
55  // matrix, so that it can later be undone, when solving systems of
56  // equations for example.
57 
58  // Find the largest element (in absolute value)
59  double max_elem = std::abs(LU(k, k));
60  size_t max_index = k;
61  for (size_t i = k + 1; i < LU.rows(); ++i) {
62  double abs_elem = std::abs(LU(i, k));
63  if (abs_elem > max_elem) {
64  max_elem = abs_elem;
65  max_index = i;
66  }
67  }
68 
69  // Select the index of the element that is largest in absolute value as
70  // the new pivot index.
71  // If this index is not the diagonal element, rows have to be swapped:
72  if (max_index != k) {
73  P(k) = max_index; // save the permutation
74  LU.swap_rows(k, max_index); // actually perfrom the permutation
75  }
76 
77  // Note how all columns of the two rows are permuted, not just the
78  // columns greater than k. You might wonder how that'll ever work out
79  // correctly in the end.
80  // Recall that for the LU factorization without pivoting, the result was
81  // Lₙ⋯L₂L₁A = U.
82  // When pivoting is used, however, the matrix is permuted before each
83  // elimination step:
84  //
85  // LₙPₙ⋯L₂P₂L₁P₁A = U
86  //
87  // Luckily, the product can be reordered, and all permutation matrices
88  // Pₖ can be grouped together.
89  // Without loss of generality, consider the pivoted LU factorization of
90  // a 3×3 matrix:
91  //
92  // L₃P₃L₂P₂L₁P₁A = U
93  //
94  // Now introduce the following permuted matrices Lₖ':
95  //
96  // L₃' = L₃
97  // L₂' = P₃L₂P₃⁻¹
98  // L₁' = P₃P₂L₁P₂⁻¹P₃⁻¹
99  //
100  // You can then easily see that the following equation holds:
101  //
102  // L₃'L₂'L₁'P₂P₃P₁A = U
103  // ⇔ L₃(P₃L₂P₃⁻¹)(P₃P₂L₁P₂⁻¹P₃⁻¹)P₃P₂P₁A = U
104  // ⇔ L₃P₃L₂P₂L₁P₁A = U
105  //
106  // Furthermore, the matrices Lₖ' have the same structure as Lₖ, because
107  // only rows below the k-th pivot are permuted.
108  //
109  // All of this allows us to group all row permutations into a single
110  // permutation matrix P, and use the same algorithm and storage format
111  // as for the unpivoted case.
112  //
113  // The factors Lₖ' are computed implicitly by applying the row
114  // permutations to the entire matrix that stores both the U and L
115  // factors, rather than just to the elements of the trailing submatrix.
116 
117  // The rest of the algorithm is identical to the one explained in
118  // NoPivotLU.cpp.
119 
120  double pivot = LU(k, k);
121 
122  // Compute the k-th column of L, the coefficients lᵢₖ:
123  for (size_t i = k + 1; i < LU.rows(); ++i)
124  LU(i, k) /= pivot;
125 
126  // Update the trailing submatrix A'(k+1:n,k+1:n) = LₖA(k+1:n,k+1:n):
127  for (size_t c = k + 1; c < LU.cols(); ++c)
128  // Subtract lᵢₖ times the current pivot row A(k,:):
129  for (size_t i = k + 1; i < LU.rows(); ++i)
130  // A'(i,c) = 1·A(i,c) - lᵢₖ·A(k,c)
131  LU(i, c) -= LU(i, k) * LU(k, c);
132 
133  // Because of the row pivoting, zero pivots are no longer an issue,
134  // since the pivot is always chosen to be the largest possible element.
135  // When the matrix is singular, the algorithm will still fail, of
136  // course.
137  }
138  state = Factored;
139  valid_LU = true;
140  valid_P = true;
141 }
143 
149 void RowPivotLU::back_subs(const Matrix &B, Matrix &X) const {
150  // Solve upper triangular system UX = B by solving each column of B as a
151  // vector system Uxᵢ = bᵢ
152  //
153  // ┌ ┐┌ ┐ ┌ ┐
154  // │ u₁₁ u₁₂ u₁₃ u₁₄ ││ x₁ᵢ │ │ b₁ᵢ │
155  // │ u₂₂ u₂₃ u₂₄ ││ x₂ᵢ │ = │ b₂ᵢ │
156  // │ u₃₃ u₃₄ ││ x₃ᵢ │ │ b₃ᵢ │
157  // │ u₄₄ ││ x₄ᵢ │ │ b₄ᵢ │
158  // └ ┘└ ┘ └ ┘
159  //
160  // b₄ᵢ = u₄₄·x₄ᵢ ⟺ x₄ᵢ = b₄ᵢ/u₄₄
161  // b₃ᵢ = u₃₃·x₃ᵢ + u₃₄·x₄ᵢ ⟺ x₃ᵢ = (b₃ᵢ - u₃₄·x₄ᵢ)/u₃₃
162  // b₂ᵢ = u₂₂·x₂ᵢ + u₂₃·x₃ᵢ + u₂₄·x₄ᵢ ⟺ x₂ᵢ = (b₂ᵢ - u₂₃·x₃ᵢ + u₂₄·x₄ᵢ)/u₂₂
163  // ...
164 
165  for (size_t i = 0; i < B.cols(); ++i) {
166  for (size_t r = LU.rows(); r-- > 0;) {
167  X(r, i) = B(r, i);
168  for (size_t c = r + 1; c < LU.cols(); ++c)
169  X(r, i) -= LU(r, c) * X(c, i);
170  X(r, i) /= LU(r, r);
171  }
172  }
173 }
175 
181 void RowPivotLU::forward_subs(const Matrix &B, Matrix &X) const {
182  // Solve lower triangular system LX = B by solving each column of B as a
183  // vector system Lxᵢ = bᵢ.
184  // The diagonal is always 1, due to the construction of the L matrix in the
185  // LU algorithm.
186  //
187  // ┌ ┐┌ ┐ ┌ ┐
188  // │ 1 ││ x₁ᵢ │ │ b₁ᵢ │
189  // │ l₂₁ 1 ││ x₂ᵢ │ = │ b₂ᵢ │
190  // │ l₃₁ l₃₂ 1 ││ x₃ᵢ │ │ b₃ᵢ │
191  // │ l₄₁ l₄₂ l₄₃ 1 ││ x₄ᵢ │ │ b₄ᵢ │
192  // └ ┘└ ┘ └ ┘
193  //
194  // b₁ᵢ = 1·x₁ᵢ ⟺ x₁ᵢ = b₁ᵢ
195  // b₂ᵢ = l₂₁·x₁ᵢ + 1·x₂ᵢ ⟺ x₂ᵢ = b₂ᵢ - l₂₁·x₁ᵢ
196  // b₃ᵢ = l₃₁·x₁ᵢ + l₃₂·x₂ᵢ + 1·x₃ᵢ ⟺ x₃ᵢ = b₃ᵢ - l₃₂·x₂ᵢ - l₃₁·x₁ᵢ
197  // ...
198 
199  for (size_t i = 0; i < B.cols(); ++i) {
200  for (size_t r = 0; r < LU.rows(); ++r) {
201  X(r, i) = B(r, i);
202  for (size_t c = 0; c < r; ++c)
203  X(r, i) -= LU(r, c) * X(c, i);
204  }
205  }
206 }
208 
215  // Solve the system AX = B, PAX = PB or LUX = PB.
216  //
217  // Let UX = Z, and first solve LZ = PB, which is a simple lower-triangular
218  // system of equations.
219  // Now that Z is known, solve UX = Z, which is a simple upper-triangular
220  // system of equations.
221  assert(is_factored());
222 
223  P.permute_rows(B);
224  forward_subs(B, B); // overwrite B with Z
225  back_subs(B, B); // overwrite B (Z) with X
226 }
228 
229 // All implementations of the less interesting functions can be found here:
General matrix class.
Definition: Matrix.hpp:25
void swap_rows(size_t a, size_t b)
Swap two rows of the matrix.
Definition: Matrix.cpp:181
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 size of the permutation matrix.
void permute_rows(Matrix &A) const
Apply the permutation to the rows of matrix A.
SquareMatrix LU
Result of a LU factorization: stores the upper-triangular matrix U and the strict lower-triangular pa...
Definition: RowPivotLU.hpp:159
void back_subs(const Matrix &B, Matrix &X) const
Back substitution algorithm for solving upper-triangular systems UX = B.
Definition: RowPivotLU.cpp:149
void solve_inplace(Matrix &B) const
Solve the system AX = B or LUX = B.
Definition: RowPivotLU.cpp:214
bool is_factored() const
Check if this object contains a factorization.
Definition: RowPivotLU.hpp:121
void forward_subs(const Matrix &B, Matrix &X) const
Forward substitution algorithm for solving lower-triangular systems LX = B.
Definition: RowPivotLU.cpp:181
void compute_factorization()
The actual LU factorization algorithm.
Definition: RowPivotLU.cpp:25
PermutationMatrix P
The permutation of A that maximizes pivot size.
Definition: RowPivotLU.hpp:161
enum RowPivotLU::State state