libeigen/eigen!2814 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
151 lines
5.4 KiB
C++
151 lines
5.4 KiB
C++
// This file is part of Eigen, a lightweight C++ template library
|
|
// for linear algebra.
|
|
//
|
|
// Copyright (C) 2011 Gael Guennebaud <g.gael@free.fr>
|
|
// Copyright (C) 2012 desire Nuentsa <desire.nuentsa_wakam@inria.fr
|
|
//
|
|
// 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/.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
#include "sparse_solver.h"
|
|
#include <Eigen/IterativeLinearSolvers>
|
|
|
|
template <typename T>
|
|
void test_dgmres_T() {
|
|
DGMRES<SparseMatrix<T>, DiagonalPreconditioner<T> > dgmres_colmajor_diag;
|
|
DGMRES<SparseMatrix<T>, IdentityPreconditioner> dgmres_colmajor_I;
|
|
DGMRES<SparseMatrix<T>, IncompleteLUT<T> > dgmres_colmajor_ilut;
|
|
// GMRES<SparseMatrix<T>, SSORPreconditioner<T> > dgmres_colmajor_ssor;
|
|
|
|
CALL_SUBTEST(check_sparse_square_solving(dgmres_colmajor_diag));
|
|
// CALL_SUBTEST( check_sparse_square_solving(dgmres_colmajor_I) );
|
|
CALL_SUBTEST(check_sparse_square_solving(dgmres_colmajor_ilut));
|
|
// CALL_SUBTEST( check_sparse_square_solving(dgmres_colmajor_ssor) );
|
|
}
|
|
|
|
// Regression: Arnoldi breakdown used to divide by zero (producing NaN in the
|
|
// Krylov basis) and solve a singular triangular system, silently returning
|
|
// Inf with info() == Success. Exercise both the pathological (rank-deficient
|
|
// pivot) and benign (exact Krylov subspace) breakdown paths.
|
|
template <typename T>
|
|
void test_dgmres_breakdown_T() {
|
|
typedef SparseMatrix<T> Mat;
|
|
typedef Matrix<T, 2, 1> Vec;
|
|
|
|
// Nilpotent A with singular Hessenberg pivot on the first step.
|
|
Mat A(2, 2);
|
|
A.insert(0, 1) = T(1);
|
|
A.makeCompressed();
|
|
Vec b;
|
|
b << T(1), T(0);
|
|
|
|
DGMRES<Mat, IdentityPreconditioner> solver;
|
|
solver.compute(A);
|
|
Vec x = solver.solve(b);
|
|
VERIFY(x.allFinite());
|
|
VERIFY(solver.info() != Success);
|
|
|
|
// Diagonal A with b in an eigenspace: Arnoldi converges after one step.
|
|
Mat D(2, 2);
|
|
D.insert(0, 0) = T(2);
|
|
D.insert(1, 1) = T(2);
|
|
D.makeCompressed();
|
|
Vec d;
|
|
d << T(2), T(2);
|
|
|
|
DGMRES<Mat, DiagonalPreconditioner<T> > solver2;
|
|
solver2.compute(D);
|
|
Vec y = solver2.solve(d);
|
|
VERIFY_IS_EQUAL(solver2.info(), Success);
|
|
VERIFY_IS_APPROX(y, (Vec() << T(1), T(1)).finished());
|
|
}
|
|
|
|
// Regression: dgmres() used m_iterations only as the iteration cap and never
|
|
// wrote the performed count back, so iterations() returned maxIterations()
|
|
// after every solve, however quickly it converged.
|
|
template <typename T>
|
|
void test_dgmres_iterations_T() {
|
|
using Mat = SparseMatrix<T>;
|
|
using DenseMat = Matrix<T, Dynamic, Dynamic>;
|
|
using Vec = Matrix<T, Dynamic, 1>;
|
|
using RealScalar = typename NumTraits<T>::Real;
|
|
|
|
// Well-conditioned tridiagonal system. Its size stays below the default
|
|
// restart length of 30, so a converged solve ends inside the first cycle.
|
|
const Index n = 20;
|
|
Mat A(n, n);
|
|
A.reserve(3 * n);
|
|
for (Index i = 0; i < n; ++i) {
|
|
if (i > 0) A.insert(i, i - 1) = T(-1);
|
|
A.insert(i, i) = T(4);
|
|
if (i + 1 < n) A.insert(i, i + 1) = T(-1);
|
|
}
|
|
A.makeCompressed();
|
|
|
|
const Index max_iters = 500;
|
|
// The system is well conditioned, so the true residual tracks the tolerance
|
|
// the solver converged to, which defaults to NumTraits<Scalar>::epsilon().
|
|
const RealScalar res_bound = RealScalar(64) * NumTraits<RealScalar>::epsilon();
|
|
|
|
Vec b = Vec::Constant(n, T(1));
|
|
DGMRES<Mat, DiagonalPreconditioner<T> > solver;
|
|
solver.setMaxIterations(max_iters);
|
|
solver.compute(A);
|
|
Vec x = solver.solve(b);
|
|
VERIFY_IS_EQUAL(solver.info(), Success);
|
|
VERIFY(solver.iterations() > 0);
|
|
VERIFY(solver.iterations() < solver.maxIterations());
|
|
VERIFY((A * x - b).norm() <= res_bound * b.norm());
|
|
|
|
// Zero right hand side: the early return reports no iteration at all.
|
|
Vec zero = Vec::Zero(n);
|
|
Vec x0 = solver.solve(zero);
|
|
VERIFY(x0.isZero());
|
|
VERIFY_IS_EQUAL(solver.iterations(), Index(0));
|
|
|
|
// Several right hand sides: the cap is restored for every column, so a cheap
|
|
// first column must not throttle a costlier second one.
|
|
DenseMat B = DenseMat::Zero(n, 2);
|
|
B.col(0).setConstant(T(1));
|
|
B(0, 1) = T(1);
|
|
DGMRES<Mat, DiagonalPreconditioner<T> > multi;
|
|
multi.setMaxIterations(max_iters);
|
|
multi.compute(A);
|
|
DenseMat X = multi.solve(B);
|
|
VERIFY_IS_EQUAL(multi.info(), Success);
|
|
VERIFY(multi.iterations() > 0);
|
|
VERIFY(multi.iterations() < multi.maxIterations());
|
|
VERIFY((A * X - B).norm() <= res_bound * B.norm());
|
|
|
|
// Non-converging direction: GMRES stagnates on a cyclic shift matrix, so the
|
|
// reported count saturates at the cap and never exceeds it.
|
|
const Index m = 16;
|
|
Mat S(m, m);
|
|
S.reserve(m);
|
|
for (Index i = 0; i < m; ++i) S.insert((i + 1) % m, i) = T(1);
|
|
S.makeCompressed();
|
|
Vec e = Vec::Zero(m);
|
|
e(0) = T(1);
|
|
for (Index k = 1; k <= 3; ++k) {
|
|
DGMRES<Mat, IdentityPreconditioner> stalled;
|
|
stalled.setMaxIterations(k);
|
|
stalled.compute(S);
|
|
Vec xs = stalled.solve(e);
|
|
VERIFY(xs.allFinite());
|
|
VERIFY_IS_EQUAL(stalled.info(), NoConvergence);
|
|
VERIFY(stalled.iterations() <= stalled.maxIterations());
|
|
VERIFY_IS_EQUAL(stalled.iterations(), k);
|
|
}
|
|
}
|
|
|
|
EIGEN_DECLARE_TEST(dgmres) {
|
|
CALL_SUBTEST_1(test_dgmres_T<double>());
|
|
CALL_SUBTEST_2(test_dgmres_T<std::complex<double> >());
|
|
CALL_SUBTEST_3(test_dgmres_breakdown_T<double>());
|
|
CALL_SUBTEST_4(test_dgmres_breakdown_T<std::complex<double> >());
|
|
CALL_SUBTEST_5(test_dgmres_iterations_T<double>());
|
|
CALL_SUBTEST_6(test_dgmres_iterations_T<std::complex<double> >());
|
|
}
|