Eigen  5.0.1
 
Loading...
Searching...
No Matches
IDRSTABL.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2020 Chris Schoutrop <c.e.m.schoutrop@tue.nl>
5// Copyright (C) 2020 Mischa Senders <m.j.senders@student.tue.nl>
6// Copyright (C) 2020 Lex Kuijpers <l.kuijpers@student.tue.nl>
7// Copyright (C) 2020 Jens Wehner <j.wehner@esciencecenter.nl>
8// Copyright (C) 2020 Jan van Dijk <j.v.dijk@tue.nl>
9// Copyright (C) 2020 Adithya Vijaykumar
10//
11// This Source Code Form is subject to the terms of the Mozilla
12// Public License v. 2.0. If a copy of the MPL was not distributed
13// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
14// SPDX-License-Identifier: MPL-2.0
15/*
16
17The IDR(S)Stab(L) method is a combination of IDR(S) and BiCGStab(L)
18
19This implementation of IDRSTABL is based on
201. Aihara, K., Abe, K., & Ishiwata, E. (2014). A variant of IDRstab with
21reliable update strategies for solving sparse linear systems. Journal of
22Computational and Applied Mathematics, 259, 244-258.
23 doi:10.1016/j.cam.2013.08.028
24 2. Aihara, K., Abe, K., & Ishiwata, E. (2015). Preconditioned
25IDRSTABL Algorithms for Solving Nonsymmetric Linear Systems. International
26Journal of Applied Mathematics, 45(3).
27 3. Saad, Y. (2003). Iterative Methods for Sparse Linear Systems:
28Second Edition. Philadelphia, PA: SIAM.
29 4. Sonneveld, P., & Van Gijzen, M. B. (2009). IDR(s): A Family
30of Simple and Fast Algorithms for Solving Large Nonsymmetric Systems of Linear
31Equations. SIAM Journal on Scientific Computing, 31(2), 1035-1062.
32 doi:10.1137/070685804
33 5. Sonneveld, P. (2012). On the convergence behavior of IDR (s)
34and related methods. SIAM Journal on Scientific Computing, 34(5), A2576-A2598.
35
36 Right-preconditioning based on Ref. 3 is implemented here.
37*/
38
39#ifndef EIGEN_IDRSTABL_H
40#define EIGEN_IDRSTABL_H
41
42// IWYU pragma: private
43#include "./InternalHeaderCheck.h"
44
45namespace Eigen {
46
47namespace internal {
48
49template <typename MatrixType, typename Rhs, typename Dest, typename Preconditioner>
50bool idrstabl(const MatrixType &mat, const Rhs &rhs, Dest &x, const Preconditioner &precond, Index &iters,
51 typename Dest::RealScalar &tol_error, Index L, Index S) {
52 /*
53 Setup and type definitions.
54 */
55 using numext::abs;
56 using Scalar = typename Dest::Scalar;
57 using RealScalar = typename Dest::RealScalar;
58 using VectorType = Matrix<Scalar, Dynamic, 1>;
59 using DenseMatrixType = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
60
61 const Index N = x.rows();
62
63 Index k = 0; // Iteration counter
64 const Index maxIters = iters;
65
66 const RealScalar rhs_norm = rhs.stableNorm();
67 const RealScalar tol = tol_error * rhs_norm;
68
69 if (rhs_norm == 0) {
70 /*
71 If b==0, then the exact solution is x=0.
72 rhs_norm is needed for other calculations anyways, this exit is a freebie.
73 */
74 x.setZero();
75 tol_error = 0.0;
76 return true;
77 }
78 // Construct decomposition objects beforehand.
79 FullPivLU<DenseMatrixType> lu_solver;
80
81 if (S >= N || L >= N) {
82 /*
83 The matrix is very small, or the choice of L and S is very poor
84 in that case solving directly will be best.
85 */
86 lu_solver.compute(DenseMatrixType(mat));
87 x = lu_solver.solve(rhs);
88 tol_error = (rhs - mat * x).stableNorm() / rhs_norm;
89 return true;
90 }
91
92 // Define maximum sizes to prevent any reallocation later on.
93 DenseMatrixType u(N, L + 1);
94 DenseMatrixType r(N, L + 1);
95
96 DenseMatrixType V(N * (L + 1), S);
97
98 VectorType alpha(S);
99 VectorType gamma(L);
100 VectorType update(N);
101
102 /*
103 Main IDRSTABL algorithm
104 */
105 // Set up the initial residual
106 VectorType x0 = x;
107 r.col(0).noalias() = rhs - mat * x;
108 x.setZero(); // The final solution will be x0+x
109
110 tol_error = r.col(0).stableNorm();
111
112 // FOM = Full orthogonalisation method
113 DenseMatrixType h_FOM = DenseMatrixType::Zero(S, S - 1);
114
115 // Construct an initial U matrix of size N x S
116 DenseMatrixType U(N * (L + 1), S);
117 for (Index col_index = 0; col_index < S; ++col_index) {
118 // Arnoldi-like process to generate a set of orthogonal vectors spanning
119 // {u,A*u,A*A*u,...,A^(S-1)*u}. This construction can be combined with the
120 // Full Orthogonalization Method (FOM) from Ref.3 to provide a possible
121 // early exit with no additional MV.
122 if (col_index != 0) {
123 /*
124 Modified Gram-Schmidt strategy:
125 */
126 VectorType w = mat * precond.solve(u.col(0));
127 for (Index i = 0; i < col_index; ++i) {
128 auto v = U.col(i).head(N);
129 h_FOM(i, col_index - 1) = v.dot(w);
130 w -= h_FOM(i, col_index - 1) * v;
131 }
132 u.col(0) = w;
133 h_FOM(col_index, col_index - 1) = u.col(0).stableNorm();
134
135 if (abs(h_FOM(col_index, col_index - 1)) != RealScalar(0)) {
136 /*
137 This only happens if u is NOT exactly zero. In case it is exactly zero
138 it would imply that that this u has no component in the direction of the
139 current residual.
140
141 By then setting u to zero it will not contribute any further (as it
142 should). Whereas attempting to normalize results in division by zero.
143
144 Such cases occur if:
145 1. The basis of dimension <S is sufficient to exactly solve the linear
146 system. I.e. the current residual is in span{r,Ar,...A^{m-1}r}, where
147 (m-1)<=S.
148 2. Two vectors generated from r, Ar,... are (numerically)
149 parallel.
150
151 In case 1, the exact solution to the system can be obtained from the
152 "Full Orthogonalization Method" (Algorithm 6.4 in the book of Saad),
153 without any additional MV.
154
155 Contrary to what one would suspect, the comparison with ==0.0 for
156 floating-point types is intended here. Any arbitrary non-zero u is fine
157 to continue, however if u contains either NaN or Inf the algorithm will
158 break down.
159 */
160 u.col(0) /= h_FOM(col_index, col_index - 1);
161 }
162 } else {
163 u.col(0) = r.col(0);
164 u.col(0).normalize();
165 }
166
167 U.col(col_index).head(N) = u.col(0);
168 }
169
170 if (S > 1) {
171 // Check for early FOM exit.
172 Scalar beta = r.col(0).stableNorm();
173 VectorType e1 = VectorType::Zero(S - 1);
174 e1(0) = beta;
175 lu_solver.compute(h_FOM.topLeftCorner(S - 1, S - 1));
176 VectorType y = lu_solver.solve(e1);
177 VectorType x2 = x + U.topLeftCorner(N, S - 1) * y;
178
179 // Using proposition 6.7 in Saad, one MV can be saved to calculate the
180 // residual
181 RealScalar FOM_residual = (h_FOM(S - 1, S - 2) * y(S - 2) * U.col(S - 1).head(N)).stableNorm();
182
183 if (FOM_residual < tol) {
184 // Exit, the FOM algorithm was already accurate enough
185 iters = k;
186 // Convert back to the unpreconditioned solution
187 x = precond.solve(x2);
188 // x contains the updates to x0, add those back to obtain the solution
189 x += x0;
190 tol_error = FOM_residual / rhs_norm;
191 return true;
192 }
193 }
194
195 /*
196 Select an initial (N x S) matrix R0.
197 1. Generate random R0, orthonormalize the result.
198 2. This results in R0, however to save memory and compute we only need the
199 adjoint of R0. This is given by the matrix R_T. Additionally, the matrix
200 (mat.adjoint()*R_tilde).adjoint()=R_tilde.adjoint()*mat by the
201 anti-distributivity property of the adjoint. This results in AR_T, which is
202 constant if R_T does not have to be regenerated and can be precomputed.
203 Based on reference 4, this has zero probability in exact arithmetic.
204 */
205
206 // Original IDRSTABL and Kensuke choose S random vectors:
207 DenseMatrixType R_T = internal::random_orthonormal_basis<DenseMatrixType>(N, S).adjoint();
208 DenseMatrixType AR_T = DenseMatrixType(R_T * mat);
209
210 // Pre-allocate sigma.
211 DenseMatrixType sigma(S, S);
212
213 bool reset_while = false; // Should the while loop be reset?
214
215 while (k < maxIters) {
216 for (Index j = 1; j <= L; ++j) {
217 /*
218 The IDR Step
219 */
220 // Construction of the sigma-matrix, and the decomposition of sigma.
221 for (Index i = 0; i < S; ++i) {
222 sigma.col(i).noalias() = AR_T * precond.solve(U.block(N * (j - 1), i, N, 1));
223 }
224
225 lu_solver.compute(sigma);
226 // Obtain the update coefficients alpha
227 if (j == 1) {
228 // alpha=inverse(sigma)*(R_T*r_0);
229 alpha = lu_solver.solve(R_T * r.col(0));
230 } else {
231 // alpha=inverse(sigma)*(AR_T*r_{j-2})
232 alpha = lu_solver.solve(AR_T * precond.solve(r.col(j - 2)));
233 }
234
235 // Obtain new solution and residual from this update
236 update.noalias() = U.topRows(N) * alpha;
237 r.col(0).noalias() -= mat * precond.solve(update);
238 x += update;
239
240 for (Index i = 1; i <= j - 2; ++i) {
241 // This only affects the case L>2
242 r.col(i).noalias() -= U.block(N * (i + 1), 0, N, S) * alpha;
243 }
244 if (j > 1) {
245 // r=[r;A*r_{j-2}]
246 r.col(j - 1).noalias() = mat * precond.solve(r.col(j - 2));
247 }
248 tol_error = r.col(0).stableNorm();
249
250 if (tol_error < tol) {
251 // If at this point the algorithm has converged, exit.
252 reset_while = true;
253 break;
254 }
255
256 bool break_normalization = false;
257 for (Index q = 1; q <= S; ++q) {
258 if (q == 1) {
259 // u = r;
260 u.leftCols(j + 1) = r.leftCols(j + 1);
261 } else {
262 // u=[u_1;u_2;...;u_j]
263 u.leftCols(j) = u.middleCols(1, j);
264 }
265
266 // Obtain the update coefficients beta implicitly
267 // beta=lu_sigma.solve(AR_T * u.block(N * (j - 1), 0, N, 1)
268 u.leftCols(j).reshaped() -= U.topRows(N * j) * lu_solver.solve(AR_T * precond.solve(u.col(j - 1)));
269
270 // u=[u;Au_{j-1}]
271 u.col(j).noalias() = mat * precond.solve(u.col(j - 1));
272
273 // Orthonormalize u_j to the columns of V_j(:,1:q-1)
274 if (q > 1) {
275 /*
276 Modified Gram-Schmidt-like procedure to make u orthogonal to the
277 columns of V from Ref. 1.
278
279 The vector mu from Ref. 1 is obtained implicitly:
280 mu=V.block(N * j, 0, N, q - 1).adjoint() * u.block(N * j, 0, N, 1).
281 */
282 for (Index i = 0; i <= q - 2; ++i) {
283 auto v = V.col(i).segment(N * j, N);
284 Scalar h = v.squaredNorm();
285 h = v.dot(u.col(j)) / h;
286 u.leftCols(j + 1).reshaped() -= h * V.col(i).head(N * (j + 1));
287 }
288 }
289 // Normalize u and assign to a column of V
290 Scalar normalization_constant = u.col(j).stableNorm();
291 // If u is exactly zero, this will lead to a NaN. Small, non-zero u is
292 // fine.
293 if (normalization_constant == RealScalar(0.0)) {
294 break_normalization = true;
295 break;
296 } else {
297 u.leftCols(j + 1) /= normalization_constant;
298 }
299
300 V.col(q - 1).head(N * (j + 1)) = u.leftCols(j + 1).reshaped();
301 }
302
303 if (!break_normalization) {
304 U = V;
305 }
306 }
307 if (reset_while) {
308 break;
309 }
310
311 // r=[r;mat*r_{L-1}]
312 r.col(L).noalias() = mat * precond.solve(r.col(L - 1));
313
314 /*
315 The polynomial step
316 */
317 ColPivHouseholderQR<DenseMatrixType> qr_solver(r.rightCols(L));
318 gamma = qr_solver.solve(r.col(0));
319
320 // Update solution and residual using the "minimized residual coefficients"
321 update.noalias() = r.leftCols(L) * gamma;
322 x += update;
323 r.col(0).noalias() -= mat * precond.solve(update);
324
325 // Update iteration info
326 ++k;
327 tol_error = r.col(0).stableNorm();
328
329 if (tol_error < tol) {
330 // Slightly early exit by moving the criterion before the update of U,
331 // after the main while loop the result of that calculation would not be
332 // needed.
333 break;
334 }
335
336 /*
337 U=U0-sum(gamma_j*U_j)
338 Consider the first iteration. Then U only contains U0, so at the start of
339 the while-loop U should be U0. Therefore only the first N rows of U have to
340 be updated.
341 */
342 for (Index i = 1; i <= L; ++i) {
343 U.topRows(N) -= U.block(N * i, 0, N, S) * gamma(i - 1);
344 }
345 }
346
347 /*
348 Exit after the while loop terminated.
349 */
350 iters = k;
351 // Convert back to the unpreconditioned solution
352 x = precond.solve(x);
353 // x contains the updates to x0, add those back to obtain the solution
354 x += x0;
355 tol_error = tol_error / rhs_norm;
356 return true;
357}
358
359} // namespace internal
360
361template <typename MatrixType_, typename Preconditioner_ = DiagonalPreconditioner<typename MatrixType_::Scalar>>
362class IDRSTABL;
363
364namespace internal {
365
366template <typename MatrixType_, typename Preconditioner_>
367struct traits<IDRSTABL<MatrixType_, Preconditioner_>> {
368 using MatrixType = MatrixType_;
369 using Preconditioner = Preconditioner_;
370};
371
372} // namespace internal
373
412
413template <typename MatrixType_, typename Preconditioner_>
414class IDRSTABL : public IterativeSolverBase<IDRSTABL<MatrixType_, Preconditioner_>> {
415 protected:
417 using Base::m_error;
418 using Base::m_info;
419 using Base::m_isInitialized;
420 using Base::m_iterations;
421 using Base::matrix;
422 Index m_L = 2;
423 Index m_S = 4;
424
425 public:
426 using MatrixType = MatrixType_;
427 using Scalar = typename MatrixType::Scalar;
428 using RealScalar = typename MatrixType::RealScalar;
429 using Preconditioner = Preconditioner_;
430
431 public:
433 IDRSTABL() = default;
434
445 template <typename MatrixDerived>
446 explicit IDRSTABL(const EigenBase<MatrixDerived> &A) : Base(A.derived()) {}
447
454 template <typename Rhs, typename Dest>
455 void _solve_vector_with_guess_impl(const Rhs &b, Dest &x) const {
456 m_iterations = Base::maxIterations();
457 m_error = Base::m_tolerance;
458 bool ret = internal::idrstabl(matrix(), b, x, Base::m_preconditioner, m_iterations, m_error, m_L, m_S);
459
460 m_info = (!ret) ? NumericalIssue : m_error <= 10 * Base::m_tolerance ? Success : NoConvergence;
461 }
462
465 void setL(Index L) {
466 eigen_assert(L >= 1 && "L needs to be positive");
467 m_L = L;
468 }
469
471 void setS(Index S) {
472 eigen_assert(S >= 1 && "S needs to be positive");
473 m_S = S;
474 }
475};
476
477} // namespace Eigen
478
479#endif /* EIGEN_IDRSTABL_H */
The IDR(s)STAB(l) is a combination of IDR(s) and BiCGSTAB(l). It is a short-recurrences Krylov method...
Definition IDRSTABL.h:414
void setL(Index L)
Definition IDRSTABL.h:465
void setS(Index S)
Definition IDRSTABL.h:471
IDRSTABL(const EigenBase< MatrixDerived > &A)
Definition IDRSTABL.h:446
void _solve_vector_with_guess_impl(const Rhs &b, Dest &x) const
Definition IDRSTABL.h:455
IDRSTABL()=default
Index maxIterations() const
Definition IterativeSolverBase.h:245
@ NumericalIssue
Definition Constants.h:459
@ Success
Definition Constants.h:457
@ NoConvergence
Definition Constants.h:461
Definition EigenBase.h:34