blob: 90d20fad4858c2497bde0625b55686bda6a83c36 [file] [log] [blame]
Austin Schuhc55b0172022-02-20 17:52:35 -08001// 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 Jens Wehner <j.wehner@esciencecenter.nl>
6// Copyright (C) 2020 Jan van Dijk <j.v.dijk@tue.nl>
7//
8// This Source Code Form is subject to the terms of the Mozilla
9// Public License v. 2.0. If a copy of the MPL was not distributed
10// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
11
12
13#ifndef EIGEN_IDRS_H
14#define EIGEN_IDRS_H
15
16namespace Eigen
17{
18
19 namespace internal
20 {
21 /** \internal Low-level Induced Dimension Reduction algoritm
22 \param A The matrix A
23 \param b The right hand side vector b
24 \param x On input and initial solution, on output the computed solution.
25 \param precond A preconditioner being able to efficiently solve for an
26 approximation of Ax=b (regardless of b)
27 \param iter On input the max number of iteration, on output the number of performed iterations.
28 \param relres On input the tolerance error, on output an estimation of the relative error.
29 \param S On input Number of the dimension of the shadow space.
30 \param smoothing switches residual smoothing on.
31 \param angle small omega lead to faster convergence at the expense of numerical stability
32 \param replacement switches on a residual replacement strategy to increase accuracy of residual at the expense of more Mat*vec products
33 \return false in the case of numerical issue, for example a break down of IDRS.
34 */
35 template<typename Vector, typename RealScalar>
36 typename Vector::Scalar omega(const Vector& t, const Vector& s, RealScalar angle)
37 {
38 using numext::abs;
39 typedef typename Vector::Scalar Scalar;
40 const RealScalar ns = s.norm();
41 const RealScalar nt = t.norm();
42 const Scalar ts = t.dot(s);
43 const RealScalar rho = abs(ts / (nt * ns));
44
45 if (rho < angle) {
46 if (ts == Scalar(0)) {
47 return Scalar(0);
48 }
49 // Original relation for om is given by
50 // om = om * angle / rho;
51 // To alleviate potential (near) division by zero this can be rewritten as
52 // om = angle * (ns / nt) * (ts / abs(ts)) = angle * (ns / nt) * sgn(ts)
53 return angle * (ns / nt) * (ts / abs(ts));
54 }
55 return ts / (nt * nt);
56 }
57
58 template <typename MatrixType, typename Rhs, typename Dest, typename Preconditioner>
59 bool idrs(const MatrixType& A, const Rhs& b, Dest& x, const Preconditioner& precond,
60 Index& iter,
61 typename Dest::RealScalar& relres, Index S, bool smoothing, typename Dest::RealScalar angle, bool replacement)
62 {
63 typedef typename Dest::RealScalar RealScalar;
64 typedef typename Dest::Scalar Scalar;
65 typedef Matrix<Scalar, Dynamic, 1> VectorType;
66 typedef Matrix<Scalar, Dynamic, Dynamic, ColMajor> DenseMatrixType;
67 const Index N = b.size();
68 S = S < x.rows() ? S : x.rows();
69 const RealScalar tol = relres;
70 const Index maxit = iter;
71
72 Index replacements = 0;
73 bool trueres = false;
74
75 FullPivLU<DenseMatrixType> lu_solver;
76
77 DenseMatrixType P;
78 {
79 HouseholderQR<DenseMatrixType> qr(DenseMatrixType::Random(N, S));
80 P = (qr.householderQ() * DenseMatrixType::Identity(N, S));
81 }
82
83 const RealScalar normb = b.norm();
84
85 if (internal::isApprox(normb, RealScalar(0)))
86 {
87 //Solution is the zero vector
88 x.setZero();
89 iter = 0;
90 relres = 0;
91 return true;
92 }
93 // from http://homepage.tudelft.nl/1w5b5/IDRS/manual.pdf
94 // A peak in the residual is considered dangerously high if‖ri‖/‖b‖> C(tol/epsilon).
95 // With epsilon the
96 // relative machine precision. The factor tol/epsilon corresponds to the size of a
97 // finite precision number that is so large that the absolute round-off error in
98 // this number, when propagated through the process, makes it impossible to
99 // achieve the required accuracy.The factor C accounts for the accumulation of
100 // round-off errors. This parameter has beenset to 10−3.
101 // mp is epsilon/C
102 // 10^3 * eps is very conservative, so normally no residual replacements will take place.
103 // It only happens if things go very wrong. Too many restarts may ruin the convergence.
104 const RealScalar mp = RealScalar(1e3) * NumTraits<Scalar>::epsilon();
105
106
107
108 //Compute initial residual
109 const RealScalar tolb = tol * normb; //Relative tolerance
110 VectorType r = b - A * x;
111
112 VectorType x_s, r_s;
113
114 if (smoothing)
115 {
116 x_s = x;
117 r_s = r;
118 }
119
120 RealScalar normr = r.norm();
121
122 if (normr <= tolb)
123 {
124 //Initial guess is a good enough solution
125 iter = 0;
126 relres = normr / normb;
127 return true;
128 }
129
130 DenseMatrixType G = DenseMatrixType::Zero(N, S);
131 DenseMatrixType U = DenseMatrixType::Zero(N, S);
132 DenseMatrixType M = DenseMatrixType::Identity(S, S);
133 VectorType t(N), v(N);
134 Scalar om = 1.;
135
136 //Main iteration loop, guild G-spaces:
137 iter = 0;
138
139 while (normr > tolb && iter < maxit)
140 {
141 //New right hand size for small system:
142 VectorType f = (r.adjoint() * P).adjoint();
143
144 for (Index k = 0; k < S; ++k)
145 {
146 //Solve small system and make v orthogonal to P:
147 //c = M(k:s,k:s)\f(k:s);
148 lu_solver.compute(M.block(k , k , S -k, S - k ));
149 VectorType c = lu_solver.solve(f.segment(k , S - k ));
150 //v = r - G(:,k:s)*c;
151 v = r - G.rightCols(S - k ) * c;
152 //Preconditioning
153 v = precond.solve(v);
154
155 //Compute new U(:,k) and G(:,k), G(:,k) is in space G_j
156 U.col(k) = U.rightCols(S - k ) * c + om * v;
157 G.col(k) = A * U.col(k );
158
159 //Bi-Orthogonalise the new basis vectors:
160 for (Index i = 0; i < k-1 ; ++i)
161 {
162 //alpha = ( P(:,i)'*G(:,k) )/M(i,i);
163 Scalar alpha = P.col(i ).dot(G.col(k )) / M(i, i );
164 G.col(k ) = G.col(k ) - alpha * G.col(i );
165 U.col(k ) = U.col(k ) - alpha * U.col(i );
166 }
167
168 //New column of M = P'*G (first k-1 entries are zero)
169 //M(k:s,k) = (G(:,k)'*P(:,k:s))';
170 M.block(k , k , S - k , 1) = (G.col(k ).adjoint() * P.rightCols(S - k )).adjoint();
171
172 if (internal::isApprox(M(k,k), Scalar(0)))
173 {
174 return false;
175 }
176
177 //Make r orthogonal to q_i, i = 0..k-1
178 Scalar beta = f(k ) / M(k , k );
179 r = r - beta * G.col(k );
180 x = x + beta * U.col(k );
181 normr = r.norm();
182
183 if (replacement && normr > tolb / mp)
184 {
185 trueres = true;
186 }
187
188 //Smoothing:
189 if (smoothing)
190 {
191 t = r_s - r;
192 //gamma is a Scalar, but the conversion is not allowed
193 Scalar gamma = t.dot(r_s) / t.norm();
194 r_s = r_s - gamma * t;
195 x_s = x_s - gamma * (x_s - x);
196 normr = r_s.norm();
197 }
198
199 if (normr < tolb || iter == maxit)
200 {
201 break;
202 }
203
204 //New f = P'*r (first k components are zero)
205 if (k < S-1)
206 {
207 f.segment(k + 1, S - (k + 1) ) = f.segment(k + 1 , S - (k + 1)) - beta * M.block(k + 1 , k , S - (k + 1), 1);
208 }
209 }//end for
210
211 if (normr < tolb || iter == maxit)
212 {
213 break;
214 }
215
216 //Now we have sufficient vectors in G_j to compute residual in G_j+1
217 //Note: r is already perpendicular to P so v = r
218 //Preconditioning
219 v = r;
220 v = precond.solve(v);
221
222 //Matrix-vector multiplication:
223 t = A * v;
224
225 //Computation of a new omega
226 om = internal::omega(t, r, angle);
227
228 if (om == RealScalar(0.0))
229 {
230 return false;
231 }
232
233 r = r - om * t;
234 x = x + om * v;
235 normr = r.norm();
236
237 if (replacement && normr > tolb / mp)
238 {
239 trueres = true;
240 }
241
242 //Residual replacement?
243 if (trueres && normr < normb)
244 {
245 r = b - A * x;
246 trueres = false;
247 replacements++;
248 }
249
250 //Smoothing:
251 if (smoothing)
252 {
253 t = r_s - r;
254 Scalar gamma = t.dot(r_s) /t.norm();
255 r_s = r_s - gamma * t;
256 x_s = x_s - gamma * (x_s - x);
257 normr = r_s.norm();
258 }
259
260 iter++;
261
262 }//end while
263
264 if (smoothing)
265 {
266 x = x_s;
267 }
268 relres=normr/normb;
269 return true;
270 }
271
272 } // namespace internal
273
274 template <typename _MatrixType, typename _Preconditioner = DiagonalPreconditioner<typename _MatrixType::Scalar> >
275 class IDRS;
276
277 namespace internal
278 {
279
280 template <typename _MatrixType, typename _Preconditioner>
281 struct traits<Eigen::IDRS<_MatrixType, _Preconditioner> >
282 {
283 typedef _MatrixType MatrixType;
284 typedef _Preconditioner Preconditioner;
285 };
286
287 } // namespace internal
288
289
290/** \ingroup IterativeLinearSolvers_Module
291 * \brief The Induced Dimension Reduction method (IDR(s)) is a short-recurrences Krylov method for sparse square problems.
292 *
293 * This class allows to solve for A.x = b sparse linear problems. The vectors x and b can be either dense or sparse.
294 * he Induced Dimension Reduction method, IDR(), is a robust and efficient short-recurrence Krylov subspace method for
295 * solving large nonsymmetric systems of linear equations.
296 *
297 * For indefinite systems IDR(S) outperforms both BiCGStab and BiCGStab(L). Additionally, IDR(S) can handle matrices
298 * with complex eigenvalues more efficiently than BiCGStab.
299 *
300 * Many problems that do not converge for BiCGSTAB converge for IDR(s) (for larger values of s). And if both methods
301 * converge the convergence for IDR(s) is typically much faster for difficult systems (for example indefinite problems).
302 *
303 * IDR(s) is a limited memory finite termination method. In exact arithmetic it converges in at most N+N/s iterations,
304 * with N the system size. It uses a fixed number of 4+3s vector. In comparison, BiCGSTAB terminates in 2N iterations
305 * and uses 7 vectors. GMRES terminates in at most N iterations, and uses I+3 vectors, with I the number of iterations.
306 * Restarting GMRES limits the memory consumption, but destroys the finite termination property.
307 *
308 * \tparam _MatrixType the type of the sparse matrix A, can be a dense or a sparse matrix.
309 * \tparam _Preconditioner the type of the preconditioner. Default is DiagonalPreconditioner
310 *
311 * \implsparsesolverconcept
312 *
313 * The maximal number of iterations and tolerance value can be controlled via the setMaxIterations()
314 * and setTolerance() methods. The defaults are the size of the problem for the maximal number of iterations
315 * and NumTraits<Scalar>::epsilon() for the tolerance.
316 *
317 * The tolerance corresponds to the relative residual error: |Ax-b|/|b|
318 *
319 * \b Performance: when using sparse matrices, best performance is achied for a row-major sparse matrix format.
320 * Moreover, in this case multi-threading can be exploited if the user code is compiled with OpenMP enabled.
321 * See \ref TopicMultiThreading for details.
322 *
323 * By default the iterations start with x=0 as an initial guess of the solution.
324 * One can control the start using the solveWithGuess() method.
325 *
326 * IDR(s) can also be used in a matrix-free context, see the following \link MatrixfreeSolverExample example \endlink.
327 *
328 * \sa class SimplicialCholesky, DiagonalPreconditioner, IdentityPreconditioner
329 */
330 template <typename _MatrixType, typename _Preconditioner>
331 class IDRS : public IterativeSolverBase<IDRS<_MatrixType, _Preconditioner> >
332 {
333
334 public:
335 typedef _MatrixType MatrixType;
336 typedef typename MatrixType::Scalar Scalar;
337 typedef typename MatrixType::RealScalar RealScalar;
338 typedef _Preconditioner Preconditioner;
339
340 private:
341 typedef IterativeSolverBase<IDRS> Base;
342 using Base::m_error;
343 using Base::m_info;
344 using Base::m_isInitialized;
345 using Base::m_iterations;
346 using Base::matrix;
347 Index m_S;
348 bool m_smoothing;
349 RealScalar m_angle;
350 bool m_residual;
351
352 public:
353 /** Default constructor. */
354 IDRS(): m_S(4), m_smoothing(false), m_angle(RealScalar(0.7)), m_residual(false) {}
355
356 /** Initialize the solver with matrix \a A for further \c Ax=b solving.
357
358 This constructor is a shortcut for the default constructor followed
359 by a call to compute().
360
361 \warning this class stores a reference to the matrix A as well as some
362 precomputed values that depend on it. Therefore, if \a A is changed
363 this class becomes invalid. Call compute() to update it with the new
364 matrix A, or modify a copy of A.
365 */
366 template <typename MatrixDerived>
367 explicit IDRS(const EigenBase<MatrixDerived>& A) : Base(A.derived()), m_S(4), m_smoothing(false),
368 m_angle(RealScalar(0.7)), m_residual(false) {}
369
370
371 /** \internal */
372 /** Loops over the number of columns of b and does the following:
373 1. sets the tolerence and maxIterations
374 2. Calls the function that has the core solver routine
375 */
376 template <typename Rhs, typename Dest>
377 void _solve_vector_with_guess_impl(const Rhs& b, Dest& x) const
378 {
379 m_iterations = Base::maxIterations();
380 m_error = Base::m_tolerance;
381
382 bool ret = internal::idrs(matrix(), b, x, Base::m_preconditioner, m_iterations, m_error, m_S,m_smoothing,m_angle,m_residual);
383
384 m_info = (!ret) ? NumericalIssue : m_error <= Base::m_tolerance ? Success : NoConvergence;
385 }
386
387 /** Sets the parameter S, indicating the dimension of the shadow space. Default is 4*/
388 void setS(Index S)
389 {
390 if (S < 1)
391 {
392 S = 4;
393 }
394
395 m_S = S;
396 }
397
398 /** Switches off and on smoothing.
399 Residual smoothing results in monotonically decreasing residual norms at
400 the expense of two extra vectors of storage and a few extra vector
401 operations. Although monotonic decrease of the residual norms is a
402 desirable property, the rate of convergence of the unsmoothed process and
403 the smoothed process is basically the same. Default is off */
404 void setSmoothing(bool smoothing)
405 {
406 m_smoothing=smoothing;
407 }
408
409 /** The angle must be a real scalar. In IDR(s), a value for the
410 iteration parameter omega must be chosen in every s+1th step. The most
411 natural choice is to select a value to minimize the norm of the next residual.
412 This corresponds to the parameter omega = 0. In practice, this may lead to
413 values of omega that are so small that the other iteration parameters
414 cannot be computed with sufficient accuracy. In such cases it is better to
415 increase the value of omega sufficiently such that a compromise is reached
416 between accurate computations and reduction of the residual norm. The
417 parameter angle =0.7 (”maintaining the convergence strategy”)
418 results in such a compromise. */
419 void setAngle(RealScalar angle)
420 {
421 m_angle=angle;
422 }
423
424 /** The parameter replace is a logical that determines whether a
425 residual replacement strategy is employed to increase the accuracy of the
426 solution. */
427 void setResidualUpdate(bool update)
428 {
429 m_residual=update;
430 }
431
432 };
433
434} // namespace Eigen
435
436#endif /* EIGEN_IDRS_H */