mirror of
https://gitlab.com/libeigen/eigen.git
synced 2026-04-10 11:34:33 +08:00
396 lines
14 KiB
C++
396 lines
14 KiB
C++
// 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 */
|