// This file is part of Eigen, a lightweight C++ template library // for linear algebra. // // Copyright (C) 2020 Chris Schoutrop // Copyright (C) 2020 Jens Wehner // Copyright (C) 2020 Jan van Dijk // Copyright (C) 2020 Adithya Vijaykumar // // This Source Code Form is subject to the terms of the Mozilla // Public License v. 2.0. If a copy of the MPL was not distributed // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. /* This implementation of BiCGStab(L) is based on the papers General algorithm: 1. G.L.G. Sleijpen, D.R. Fokkema. (1993). BiCGstab(l) for linear equations involving unsymmetric matrices with complex spectrum. Electronic Transactions on Numerical Analysis. Polynomial step update: 2. G.L.G. Sleijpen, M.B. Van Gijzen. (2010) Exploiting BiCGstab(l) strategies to induce dimension reduction SIAM Journal on Scientific Computing. 3. Fokkema, Diederik R. Enhanced implementation of BiCGstab (l) for solving linear systems of equations. Universiteit Utrecht. Mathematisch Instituut, 1996 4. Sleijpen, G. L., & van der Vorst, H. A. (1996). Reliable updated residuals in hybrid Bi-CG methods. Computing, 56(2), 141-163. */ #ifndef EIGEN_BICGSTABL_H #define EIGEN_BICGSTABL_H namespace Eigen { namespace internal { /** \internal Low-level bi conjugate gradient stabilized algorithm with L additional residual minimization steps \param mat The matrix A \param rhs The right hand side vector b \param x On input and initial solution, on output the computed solution. \param precond A preconditioner being able to efficiently solve for an approximation of Ax=b (regardless of b) \param iters On input the max number of iteration, on output the number of performed iterations. \param tol_error On input the tolerance error, on output an estimation of the relative error. \param L On input Number of additional GMRES steps to take. If L is too large (~20) instabilities occur. \return false in the case of numerical issue, for example a break down of BiCGSTABL. */ template bool bicgstabl(const MatrixType &mat, const Rhs &rhs, Dest &x, const Preconditioner &precond, Index &iters, typename Dest::RealScalar &tol_error, Index L) { using numext::abs; using numext::sqrt; typedef typename Dest::RealScalar RealScalar; typedef typename Dest::Scalar Scalar; const Index N = rhs.size(); L = L < x.rows() ? L : x.rows(); Index k = 0; const RealScalar tol = tol_error; const Index maxIters = iters; typedef Matrix VectorType; typedef Matrix DenseMatrixType; DenseMatrixType rHat(N, L + 1); DenseMatrixType uHat(N, L + 1); // We start with an initial guess x_0 and let us set r_0 as (residual // calculated from x_0) VectorType x0 = x; rHat.col(0) = rhs - mat * x0; // r_0 x.setZero(); // This will contain the updates to the solution. // rShadow is arbritary, but must never be orthogonal to any residual. VectorType rShadow = VectorType::Random(N); VectorType x_prime = x; // Redundant: x is already set to 0 // x.setZero(); VectorType b_prime = rHat.col(0); // Other vectors and scalars initialization Scalar rho0 = 1.0; Scalar alpha = 0.0; Scalar omega = 1.0; uHat.col(0).setZero(); bool bicg_convergence = false; const RealScalar normb = rhs.stableNorm(); if (internal::isApprox(normb, RealScalar(0))) { x.setZero(); iters = 0; return true; } RealScalar normr = rHat.col(0).stableNorm(); RealScalar Mx = normr; RealScalar Mr = normr; // Keep track of the solution with the lowest residual RealScalar normr_min = normr; VectorType x_min = x_prime + x; // Criterion for when to apply the group-wise update, conform ref 3. const RealScalar delta = 0.01; bool compute_res = false; bool update_app = false; while (normr > tol * normb && k < maxIters) { rho0 *= -omega; for (Index j = 0; j < L; ++j) { const Scalar rho1 = rShadow.dot(rHat.col(j)); if (!(numext::isfinite)(rho1) || rho0 == RealScalar(0.0)) { // We cannot continue computing, return the best solution found. x += x_prime; // Check if x is better than the best stored solution thus far. normr = (rhs - mat * (precond.solve(x) + x0)).stableNorm(); if (normr > normr_min || !(numext::isfinite)(normr)) { // x_min is a better solution than x, return x_min x = x_min; normr = normr_min; } tol_error = normr / normb; iters = k; // x contains the updates to x0, add those back to obtain the solution x = precond.solve(x); x += x0; return (normr < tol * normb); } const Scalar beta = alpha * (rho1 / rho0); rho0 = rho1; // Update search directions uHat.leftCols(j + 1) = rHat.leftCols(j + 1) - beta * uHat.leftCols(j + 1); uHat.col(j + 1) = mat * precond.solve(uHat.col(j)); const Scalar sigma = rShadow.dot(uHat.col(j + 1)); alpha = rho1 / sigma; // Update residuals rHat.leftCols(j + 1) -= alpha * uHat.middleCols(1, j + 1); rHat.col(j + 1) = mat * precond.solve(rHat.col(j)); // Complete BiCG iteration by updating x x += alpha * uHat.col(0); normr = rHat.col(0).stableNorm(); // Check for early exit if (normr < tol * normb) { /* Convergence was achieved during BiCG step. Without this check BiCGStab(L) fails for trivial matrices, such as when the preconditioner already is the inverse, or the input matrix is identity. */ bicg_convergence = true; break; } else if (normr < normr_min) { // We found an x with lower residual, keep this one. x_min = x + x_prime; normr_min = normr; } } if (!bicg_convergence) { /* The polynomial/minimize residual step. QR Householder method for argmin is more stable than (modified) Gram-Schmidt, in the sense that there is less loss of orthogonality. It is more accurate than solving the normal equations, since the normal equations scale with condition number squared. */ const VectorType gamma = rHat.rightCols(L).householderQr().solve(rHat.col(0)); x += rHat.leftCols(L) * gamma; rHat.col(0) -= rHat.rightCols(L) * gamma; uHat.col(0) -= uHat.rightCols(L) * gamma; normr = rHat.col(0).stableNorm(); omega = gamma(L - 1); } if (normr < normr_min) { // We found an x with lower residual, keep this one. x_min = x + x_prime; normr_min = normr; } k++; /* Reliable update part The recursively computed residual can deviate from the actual residual after several iterations. However, computing the residual from the definition costs extra MVs and should not be done at each iteration. The reliable update strategy computes the true residual from the definition: r=b-A*x at strategic intervals. Furthermore a "group wise update" strategy is used to combine updates, which improves accuracy. */ // Maximum norm of residuals since last update of x. Mx = numext::maxi(Mx, normr); // Maximum norm of residuals since last computation of the true residual. Mr = numext::maxi(Mr, normr); if (normr < delta * normb && normb <= Mx) { update_app = true; } if (update_app || (normr < delta * Mr && normb <= Mr)) { compute_res = true; } if (bicg_convergence) { update_app = true; compute_res = true; bicg_convergence = false; } if (compute_res) { // Explicitly compute residual from the definition // This is equivalent to the shifted version of rhs - mat * // (precond.solve(x)+x0) rHat.col(0) = b_prime - mat * precond.solve(x); normr = rHat.col(0).stableNorm(); Mr = normr; if (update_app) { // After the group wise update, the original problem is translated to a // shifted one. x_prime += x; x.setZero(); b_prime = rHat.col(0); Mx = normr; } } if (normr < normr_min) { // We found an x with lower residual, keep this one. x_min = x + x_prime; normr_min = normr; } compute_res = false; update_app = false; } // Convert internal variable to the true solution vector x x += x_prime; normr = (rhs - mat * (precond.solve(x) + x0)).stableNorm(); if (normr > normr_min || !(numext::isfinite)(normr)) { // x_min is a better solution than x, return x_min x = x_min; normr = normr_min; } tol_error = normr / normb; iters = k; // x contains the updates to x0, add those back to obtain the solution x = precond.solve(x); x += x0; return true; } } // namespace internal template > class BiCGSTABL; namespace internal { template struct traits> { typedef MatrixType_ MatrixType; typedef Preconditioner_ Preconditioner; }; } // namespace internal template class BiCGSTABL : public IterativeSolverBase> { typedef IterativeSolverBase Base; using Base::m_error; using Base::m_info; using Base::m_isInitialized; using Base::m_iterations; using Base::matrix; Index m_L; public: typedef MatrixType_ MatrixType; typedef typename MatrixType::Scalar Scalar; typedef typename MatrixType::RealScalar RealScalar; typedef Preconditioner_ Preconditioner; /** Default constructor. */ BiCGSTABL() : m_L(2) {} /** Initialize the solver with matrix \a A for further \c Ax=b solving. This constructor is a shortcut for the default constructor followed by a call to compute(). \warning this class stores a reference to the matrix A as well as some precomputed values that depend on it. Therefore, if \a A is changed this class becomes invalid. Call compute() to update it with the new matrix A, or modify a copy of A. */ template explicit BiCGSTABL(const EigenBase &A) : Base(A.derived()), m_L(2) {} /** \internal */ /** Loops over the number of columns of b and does the following: 1. sets the tolerence and maxIterations 2. Calls the function that has the core solver routine */ template void _solve_vector_with_guess_impl(const Rhs &b, Dest &x) const { m_iterations = Base::maxIterations(); m_error = Base::m_tolerance; bool ret = internal::bicgstabl(matrix(), b, x, Base::m_preconditioner, m_iterations, m_error, m_L); m_info = (!ret) ? NumericalIssue : m_error <= Base::m_tolerance ? Success : NoConvergence; } /** Sets the parameter L, indicating how many minimize residual steps are * used. Default: 2 */ void setL(Index L) { eigen_assert(L >= 1 && "L needs to be positive"); m_L = L; } }; } // namespace Eigen #endif /* EIGEN_BICGSTABL_H */