| // This file is part of Eigen, a lightweight C++ template library | 
 | // for linear algebra. | 
 | // | 
 | // Copyright (C) 2020 Chris Schoutrop <c.e.m.schoutrop@tue.nl> | 
 | // Copyright (C) 2020 Jens Wehner <j.wehner@esciencecenter.nl> | 
 | // Copyright (C) 2020 Jan van Dijk <j.v.dijk@tue.nl> | 
 | // | 
 | // 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/. | 
 |  | 
 | #ifndef EIGEN_IDRS_H | 
 | #define EIGEN_IDRS_H | 
 |  | 
 | // IWYU pragma: private | 
 | #include "./InternalHeaderCheck.h" | 
 |  | 
 | namespace Eigen { | 
 |  | 
 | namespace internal { | 
 | /**     \internal Low-level Induced Dimension Reduction algorithm | 
 |         \param A The matrix A | 
 |         \param b 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 iter On input the max number of iteration, on output the number of performed iterations. | 
 |         \param relres On input the tolerance error, on output an estimation of the relative error. | 
 |         \param S On input Number of the dimension of the shadow space. | 
 |                 \param smoothing switches residual smoothing on. | 
 |                 \param angle small omega lead to faster convergence at the expense of numerical stability | 
 |                 \param replacement switches on a residual replacement strategy to increase accuracy of residual at the | 
 |    expense of more Mat*vec products \return false in the case of numerical issue, for example a break down of IDRS. | 
 | */ | 
 | template <typename Vector, typename RealScalar> | 
 | typename Vector::Scalar omega(const Vector& t, const Vector& s, RealScalar angle) { | 
 |   using numext::abs; | 
 |   typedef typename Vector::Scalar Scalar; | 
 |   const RealScalar ns = s.stableNorm(); | 
 |   const RealScalar nt = t.stableNorm(); | 
 |   const Scalar ts = t.dot(s); | 
 |   const RealScalar rho = abs(ts / (nt * ns)); | 
 |  | 
 |   if (rho < angle) { | 
 |     if (ts == Scalar(0)) { | 
 |       return Scalar(0); | 
 |     } | 
 |     // Original relation for om is given by | 
 |     // om = om * angle / rho; | 
 |     // To alleviate potential (near) division by zero this can be rewritten as | 
 |     // om = angle * (ns / nt) * (ts / abs(ts)) = angle * (ns / nt) * sgn(ts) | 
 |     return angle * (ns / nt) * (ts / abs(ts)); | 
 |   } | 
 |   return ts / (nt * nt); | 
 | } | 
 |  | 
 | template <typename MatrixType, typename Rhs, typename Dest, typename Preconditioner> | 
 | bool idrs(const MatrixType& A, const Rhs& b, Dest& x, const Preconditioner& precond, Index& iter, | 
 |           typename Dest::RealScalar& relres, Index S, bool smoothing, typename Dest::RealScalar angle, | 
 |           bool replacement) { | 
 |   typedef typename Dest::RealScalar RealScalar; | 
 |   typedef typename Dest::Scalar Scalar; | 
 |   typedef Matrix<Scalar, Dynamic, 1> VectorType; | 
 |   typedef Matrix<Scalar, Dynamic, Dynamic, ColMajor> DenseMatrixType; | 
 |   const Index N = b.size(); | 
 |   S = S < x.rows() ? S : x.rows(); | 
 |   const RealScalar tol = relres; | 
 |   const Index maxit = iter; | 
 |  | 
 |   bool trueres = false; | 
 |  | 
 |   FullPivLU<DenseMatrixType> lu_solver; | 
 |  | 
 |   DenseMatrixType P; | 
 |   { | 
 |     HouseholderQR<DenseMatrixType> qr(DenseMatrixType::Random(N, S)); | 
 |     P = (qr.householderQ() * DenseMatrixType::Identity(N, S)); | 
 |   } | 
 |  | 
 |   const RealScalar normb = b.stableNorm(); | 
 |  | 
 |   if (internal::isApprox(normb, RealScalar(0))) { | 
 |     // Solution is the zero vector | 
 |     x.setZero(); | 
 |     iter = 0; | 
 |     relres = 0; | 
 |     return true; | 
 |   } | 
 |   // from http://homepage.tudelft.nl/1w5b5/IDRS/manual.pdf | 
 |   // A peak in the residual is considered dangerously high if‖ri‖/‖b‖> C(tol/epsilon). | 
 |   // With epsilon the relative machine precision. The factor tol/epsilon corresponds | 
 |   // to the size of a finite precision number that is so large that the absolute | 
 |   // round-off error in this number, when propagated through the process, makes it | 
 |   // impossible to achieve the required accuracy. The factor C accounts for the | 
 |   // accumulation of round-off errors. This parameter has been set to 10^{-3}. | 
 |   // mp is epsilon/C 10^3 * eps is very conservative, so normally no residual | 
 |   // replacements will take place. It only happens if things go very wrong. Too many | 
 |   // restarts may ruin the convergence. | 
 |   const RealScalar mp = RealScalar(1e3) * NumTraits<Scalar>::epsilon(); | 
 |  | 
 |   // Compute initial residual | 
 |   const RealScalar tolb = tol * normb;  // Relative tolerance | 
 |   VectorType r = b - A * x; | 
 |  | 
 |   VectorType x_s, r_s; | 
 |  | 
 |   if (smoothing) { | 
 |     x_s = x; | 
 |     r_s = r; | 
 |   } | 
 |  | 
 |   RealScalar normr = r.stableNorm(); | 
 |  | 
 |   if (normr <= tolb) { | 
 |     // Initial guess is a good enough solution | 
 |     iter = 0; | 
 |     relres = normr / normb; | 
 |     return true; | 
 |   } | 
 |  | 
 |   DenseMatrixType G = DenseMatrixType::Zero(N, S); | 
 |   DenseMatrixType U = DenseMatrixType::Zero(N, S); | 
 |   DenseMatrixType M = DenseMatrixType::Identity(S, S); | 
 |   VectorType t(N), v(N); | 
 |   Scalar om = 1.; | 
 |  | 
 |   // Main iteration loop, guild G-spaces: | 
 |   iter = 0; | 
 |  | 
 |   while (normr > tolb && iter < maxit) { | 
 |     // New right hand size for small system: | 
 |     VectorType f = (r.adjoint() * P).adjoint(); | 
 |  | 
 |     for (Index k = 0; k < S; ++k) { | 
 |       // Solve small system and make v orthogonal to P: | 
 |       // c = M(k:s,k:s)\f(k:s); | 
 |       lu_solver.compute(M.block(k, k, S - k, S - k)); | 
 |       VectorType c = lu_solver.solve(f.segment(k, S - k)); | 
 |       // v = r - G(:,k:s)*c; | 
 |       v = r - G.rightCols(S - k) * c; | 
 |       // Preconditioning | 
 |       v = precond.solve(v); | 
 |  | 
 |       // Compute new U(:,k) and G(:,k), G(:,k) is in space G_j | 
 |       U.col(k) = U.rightCols(S - k) * c + om * v; | 
 |       G.col(k) = A * U.col(k); | 
 |  | 
 |       // Bi-Orthogonalise the new basis vectors: | 
 |       for (Index i = 0; i < k - 1; ++i) { | 
 |         // alpha =  ( P(:,i)'*G(:,k) )/M(i,i); | 
 |         Scalar alpha = P.col(i).dot(G.col(k)) / M(i, i); | 
 |         G.col(k) = G.col(k) - alpha * G.col(i); | 
 |         U.col(k) = U.col(k) - alpha * U.col(i); | 
 |       } | 
 |  | 
 |       // New column of M = P'*G  (first k-1 entries are zero) | 
 |       // M(k:s,k) = (G(:,k)'*P(:,k:s))'; | 
 |       M.block(k, k, S - k, 1) = (G.col(k).adjoint() * P.rightCols(S - k)).adjoint(); | 
 |  | 
 |       if (internal::isApprox(M(k, k), Scalar(0))) { | 
 |         return false; | 
 |       } | 
 |  | 
 |       // Make r orthogonal to q_i, i = 0..k-1 | 
 |       Scalar beta = f(k) / M(k, k); | 
 |       r = r - beta * G.col(k); | 
 |       x = x + beta * U.col(k); | 
 |       normr = r.stableNorm(); | 
 |  | 
 |       if (replacement && normr > tolb / mp) { | 
 |         trueres = true; | 
 |       } | 
 |  | 
 |       // Smoothing: | 
 |       if (smoothing) { | 
 |         t = r_s - r; | 
 |         // gamma is a Scalar, but the conversion is not allowed | 
 |         Scalar gamma = t.dot(r_s) / t.stableNorm(); | 
 |         r_s = r_s - gamma * t; | 
 |         x_s = x_s - gamma * (x_s - x); | 
 |         normr = r_s.stableNorm(); | 
 |       } | 
 |  | 
 |       if (normr < tolb || iter == maxit) { | 
 |         break; | 
 |       } | 
 |  | 
 |       // New f = P'*r (first k  components are zero) | 
 |       if (k < S - 1) { | 
 |         f.segment(k + 1, S - (k + 1)) = f.segment(k + 1, S - (k + 1)) - beta * M.block(k + 1, k, S - (k + 1), 1); | 
 |       } | 
 |     }  // end for | 
 |  | 
 |     if (normr < tolb || iter == maxit) { | 
 |       break; | 
 |     } | 
 |  | 
 |     // Now we have sufficient vectors in G_j to compute residual in G_j+1 | 
 |     // Note: r is already perpendicular to P so v = r | 
 |     // Preconditioning | 
 |     v = r; | 
 |     v = precond.solve(v); | 
 |  | 
 |     // Matrix-vector multiplication: | 
 |     t = A * v; | 
 |  | 
 |     // Computation of a new omega | 
 |     om = internal::omega(t, r, angle); | 
 |  | 
 |     if (om == RealScalar(0.0)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     r = r - om * t; | 
 |     x = x + om * v; | 
 |     normr = r.stableNorm(); | 
 |  | 
 |     if (replacement && normr > tolb / mp) { | 
 |       trueres = true; | 
 |     } | 
 |  | 
 |     // Residual replacement? | 
 |     if (trueres && normr < normb) { | 
 |       r = b - A * x; | 
 |       trueres = false; | 
 |     } | 
 |  | 
 |     // Smoothing: | 
 |     if (smoothing) { | 
 |       t = r_s - r; | 
 |       Scalar gamma = t.dot(r_s) / t.stableNorm(); | 
 |       r_s = r_s - gamma * t; | 
 |       x_s = x_s - gamma * (x_s - x); | 
 |       normr = r_s.stableNorm(); | 
 |     } | 
 |  | 
 |     iter++; | 
 |  | 
 |   }  // end while | 
 |  | 
 |   if (smoothing) { | 
 |     x = x_s; | 
 |   } | 
 |   relres = normr / normb; | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace internal | 
 |  | 
 | template <typename MatrixType_, typename Preconditioner_ = DiagonalPreconditioner<typename MatrixType_::Scalar> > | 
 | class IDRS; | 
 |  | 
 | namespace internal { | 
 |  | 
 | template <typename MatrixType_, typename Preconditioner_> | 
 | struct traits<Eigen::IDRS<MatrixType_, Preconditioner_> > { | 
 |   typedef MatrixType_ MatrixType; | 
 |   typedef Preconditioner_ Preconditioner; | 
 | }; | 
 |  | 
 | }  // namespace internal | 
 |  | 
 | /** \ingroup IterativeLinearSolvers_Module | 
 |  * \brief The Induced Dimension Reduction method (IDR(s)) is a short-recurrences Krylov method for sparse square | 
 |  * problems. | 
 |  * | 
 |  * This class allows to solve for A.x = b sparse linear problems. The vectors x and b can be either dense or sparse. | 
 |  * he Induced Dimension Reduction method, IDR(), is a robust and efficient short-recurrence Krylov subspace method for | 
 |  * solving large nonsymmetric systems of linear equations. | 
 |  * | 
 |  * For indefinite systems IDR(S) outperforms both BiCGStab and BiCGStab(L). Additionally, IDR(S) can handle matrices | 
 |  * with complex eigenvalues more efficiently than BiCGStab. | 
 |  * | 
 |  * Many problems that do not converge for BiCGSTAB converge for IDR(s) (for larger values of s). And if both methods | 
 |  * converge the convergence for IDR(s) is typically much faster for difficult systems (for example indefinite problems). | 
 |  * | 
 |  * IDR(s) is a limited memory finite termination method. In exact arithmetic it converges in at most N+N/s iterations, | 
 |  * with N the system size.  It uses a fixed number of 4+3s vector. In comparison, BiCGSTAB terminates in 2N iterations | 
 |  * and uses 7 vectors. GMRES terminates in at most N iterations, and uses I+3 vectors, with I the number of iterations. | 
 |  * Restarting GMRES limits the memory consumption, but destroys the finite termination property. | 
 |  * | 
 |  * \tparam MatrixType_ the type of the sparse matrix A, can be a dense or a sparse matrix. | 
 |  * \tparam Preconditioner_ the type of the preconditioner. Default is DiagonalPreconditioner | 
 |  * | 
 |  * \implsparsesolverconcept | 
 |  * | 
 |  * The maximal number of iterations and tolerance value can be controlled via the setMaxIterations() | 
 |  * and setTolerance() methods. The defaults are the size of the problem for the maximal number of iterations | 
 |  * and NumTraits<Scalar>::epsilon() for the tolerance. | 
 |  * | 
 |  * The tolerance corresponds to the relative residual error: |Ax-b|/|b| | 
 |  * | 
 |  * \b Performance: when using sparse matrices, best performance is achied for a row-major sparse matrix format. | 
 |  * Moreover, in this case multi-threading can be exploited if the user code is compiled with OpenMP enabled. | 
 |  * See \ref TopicMultiThreading for details. | 
 |  * | 
 |  * By default the iterations start with x=0 as an initial guess of the solution. | 
 |  * One can control the start using the solveWithGuess() method. | 
 |  * | 
 |  * IDR(s) can also be used in a matrix-free context, see the following \link MatrixfreeSolverExample example \endlink. | 
 |  * | 
 |  * \sa class SimplicialCholesky, DiagonalPreconditioner, IdentityPreconditioner | 
 |  */ | 
 | template <typename MatrixType_, typename Preconditioner_> | 
 | class IDRS : public IterativeSolverBase<IDRS<MatrixType_, Preconditioner_> > { | 
 |  public: | 
 |   typedef MatrixType_ MatrixType; | 
 |   typedef typename MatrixType::Scalar Scalar; | 
 |   typedef typename MatrixType::RealScalar RealScalar; | 
 |   typedef Preconditioner_ Preconditioner; | 
 |  | 
 |  private: | 
 |   typedef IterativeSolverBase<IDRS> Base; | 
 |   using Base::m_error; | 
 |   using Base::m_info; | 
 |   using Base::m_isInitialized; | 
 |   using Base::m_iterations; | 
 |   using Base::matrix; | 
 |   Index m_S; | 
 |   bool m_smoothing; | 
 |   RealScalar m_angle; | 
 |   bool m_residual; | 
 |  | 
 |  public: | 
 |   /** Default constructor. */ | 
 |   IDRS() : m_S(4), m_smoothing(false), m_angle(RealScalar(0.7)), m_residual(false) {} | 
 |  | 
 |   /**     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 <typename MatrixDerived> | 
 |   explicit IDRS(const EigenBase<MatrixDerived>& A) | 
 |       : Base(A.derived()), m_S(4), m_smoothing(false), m_angle(RealScalar(0.7)), m_residual(false) {} | 
 |  | 
 |   /** \internal */ | 
 |   /**     Loops over the number of columns of b and does the following: | 
 |                   1. sets the tolerance and maxIterations | 
 |                   2. Calls the function that has the core solver routine | 
 |   */ | 
 |   template <typename Rhs, typename Dest> | 
 |   void _solve_vector_with_guess_impl(const Rhs& b, Dest& x) const { | 
 |     m_iterations = Base::maxIterations(); | 
 |     m_error = Base::m_tolerance; | 
 |  | 
 |     bool ret = internal::idrs(matrix(), b, x, Base::m_preconditioner, m_iterations, m_error, m_S, m_smoothing, m_angle, | 
 |                               m_residual); | 
 |  | 
 |     m_info = (!ret) ? NumericalIssue : m_error <= Base::m_tolerance ? Success : NoConvergence; | 
 |   } | 
 |  | 
 |   /** Sets the parameter S, indicating the dimension of the shadow space. Default is 4*/ | 
 |   void setS(Index S) { | 
 |     if (S < 1) { | 
 |       S = 4; | 
 |     } | 
 |  | 
 |     m_S = S; | 
 |   } | 
 |  | 
 |   /** Switches off and on smoothing. | 
 |   Residual smoothing results in monotonically decreasing residual norms at | 
 |   the expense of two extra vectors of storage and a few extra vector | 
 |   operations. Although monotonic decrease of the residual norms is a | 
 |   desirable property, the rate of convergence of the unsmoothed process and | 
 |   the smoothed process is basically the same. Default is off */ | 
 |   void setSmoothing(bool smoothing) { m_smoothing = smoothing; } | 
 |  | 
 |   /** The angle must be a real scalar. In IDR(s), a value for the | 
 |   iteration parameter omega must be chosen in every s+1th step. The most | 
 |   natural choice is to select a value to minimize the norm of the next residual. | 
 |   This corresponds to the parameter omega = 0. In practice, this may lead to | 
 |   values of omega that are so small that the other iteration parameters | 
 |   cannot be computed with sufficient accuracy. In such cases it is better to | 
 |   increase the value of omega sufficiently such that a compromise is reached | 
 |   between accurate computations and reduction of the residual norm. The | 
 |   parameter angle =0.7 (”maintaining the convergence strategy”) | 
 |   results in such a compromise. */ | 
 |   void setAngle(RealScalar angle) { m_angle = angle; } | 
 |  | 
 |   /** The parameter replace is a logical that determines whether a | 
 |   residual replacement strategy is employed to increase the accuracy of the | 
 |   solution. */ | 
 |   void setResidualUpdate(bool update) { m_residual = update; } | 
 | }; | 
 |  | 
 | }  // namespace Eigen | 
 |  | 
 | #endif /* EIGEN_IDRS_H */ |