Preconditioned GMRes terminates preemptively in some cases

Hi all,

When using NGSolve’s GMRESSolver together with a Jacobi preconditioner, I observed that the iteration terminates too early, giving an inaccurate solution. See the following minimal example that reproduces the problem; note that I’m using GMRES rather than CG because my actual use case is “symmetric matrix + small non-symmetric perturbation”.

import ngsolve as ngs
from netgen import occ

mesh = ngs.Mesh(occ.OCCGeometry(occ.Box((0, 0, 0), (1, 1, 1))).GenerateMesh(maxh=0.2))
fes = ngs.H1(mesh, order=1)
u, v = fes.TnT()

m = ngs.BilinearForm(fes)
m += u * v * ngs.dx
m.Assemble()

f = ngs.LinearForm(fes)
f += v * ngs.dx
f.Assemble()

N = 1000
DT = 1 / N
u = ngs.GridFunction(fes)
u.Set(1)

# All commented solvers work, only gmres with jacobi preconditioner fails
pre_jac = m.mat.CreateSmoother(fes.FreeDofs(), GS=False)
pre_gs = m.mat.CreateSmoother(fes.FreeDofs(), GS=True)
# m_inv = ngs.CGSolver(m.mat, pre_jac)
# m_inv = ngs.CGSolver(m.mat, pre_gs)
m_inv = ngs.GMRESSolver(m.mat, pre_jac)
# m_inv = ngs.GMRESSolver(m.mat, pre_gs)

for i in range(N):
    res = DT * f.vec
    u.vec.data += m_inv * res

# The solution should be 2 in the whole domain with very little variance
# Using the solver above, the output of the following lines is 1.823 ± 0.882
sol = u.vec.FV().NumPy()
print(f"Solution: {sol.mean():.3f} ± {sol.std():.3g}")

An explanation might be found in these lecture notes: The method that is used in GMRESSolver is a left-preconditioned GMRES, which minimizes the residual in the preconditioner space \|M^{-1} (b - Ax) \| rather than the actual residual \|b - Ax \|, which might fool the solver into believing that the desired accuracy is already reached.

Am I on the right track with this? Are there any plans to also implement right-preconditioned GMRES in NGSolve? Currently, just using the Gauss-Seidel preconditioner instead seems to solve my problem. Do you have other suggestions for solver/preconditioner pairs I should try for “symmetric matrix + small non-symmetric perturbation”-type matrices arising from time-stepping?

Thanks,
Michael

The C++ implementation GMRESSolver used an unstable implementation, it’s fixed now. You can also use the newer Python implemenation GMResSolver, which is working:

from ngsolve.krylovspace import GMResSolver

Thanks for the reporting this issue!

Hi Joachim,

I didn’t know about krylovspace.GMResSolver, thanks! Also, big thanks for fixing the code so quickly, that’s awesome!

Michael