Hierarchy, smoother and Krylov solve

\(u \leftarrow u + \omega\, D^{-1}(f - Au)\)

The update above is a damped Jacobi sweep, with omega=2/3. This smoother quickly reduces the oscillatory part of the error, and leaves the smooth part, which is cheaper to correct on the coarser level. build_hierarchy_structured builds a sequence of the same discretization at coarser and coarser resolutions, together with the operators that transfer a solution or a residual between two consecutive levels. MG combines this hierarchy with a smoother (here DampedJacobiSmoother) into a single object, which is given to LinearKrylov like any other preconditioner.

from scimba_jax.linear_approximation.transfer.hierarchy import (
    build_hierarchy_structured,
)
from scimba_jax.linear_approximation.solvers.multigrid import MG
from scimba_jax.linear_approximation.solvers.smoothers import DampedJacobiSmoother
from scimba_jax.linear_approximation.solvers.newton import LinearKrylov
def make_scheme(n_cells):
    # FEM scheme from the FEM page, at the resolution requested by the
    # hierarchy (one call per level, coarsest first).
    ...
    return EllipticFEscheme(model, variables)
# 4 levels, 64x64 down to 8x8, same discretization at each resolution.
hierarchy = build_hierarchy_structured(make_scheme, (64, 64), n_levels=4)
mg = MG(hierarchy, DampedJacobiSmoother(omega=2.0 / 3.0), nu_pre=2, nu_post=2)
# The multigrid is used as a preconditioner of the usual Krylov solver.
scheme = hierarchy.finest.scheme
solver = LinearKrylov(tol=1e-10, cg_solver="lax", preconditioner=mg)
scheme, report = EllipticFEscheme.solve(scheme, solver=solver, return_report=True)
# 10 CG iterations to 1e-10, independently of the mesh size (an
# unpreconditioned CG needs O(1/h) iterations).

Shifted Laplacian preconditioner for the Helmholtz equation

\(M = -\Delta - (k^2 + i\,\varepsilon),\quad \varepsilon = 2k\)

For an indefinite operator, multigrid cannot efficiently correct the smooth part of the error. The cycle is therefore applied to a shifted operator, damped by i ε, and is used to precondition BiCGSTAB on the original problem. shifted_laplacian_solver builds this preconditioner from a single factory of the problem, with an additive shift of order k, two levels and a damped Jacobi smoother. The classical shift βk² stalls at k = 100. At k = 100, three levels amplify the error instead of reducing it, since the coarsest grid no longer resolves the wave. The coarse level is then a quarter of the fine one, and its dense LU factorization dominates the cost: 9 s to factorise, then 100 ms per cycle. This cost is amortised over a batch of right-hand sides: four sources under jax.vmap cost 1.15 times as much as one.

from scimba_jax.linear_approximation.solvers import shifted_laplacian_solver
from scimba_jax.physical_models.classical_weakform.helmholtz_weak_form import (
    HelmholtzWeakForm,
)
from scimba_jax.physical_models.weak_boundary_conditions import Sommerfeld
def scheme_factory(n_cells, shift=0.0):
    # A single factory: the problem for shift=0, the preconditioner operator
    # -Delta - (k^2 + i shift) for shift=eps (kappa and shift may be x -> float).
    model = AbstractPhysicalWeakModel(dim=2)
    model.add_weak_form("main", HelmholtzWeakForm(2, K**2, f, kappa_im=shift))
    for side in ("west", "east", "south", "north"):
        model.add_boundary_condition(side, Sommerfeld(K))  # d_n u - i k u = 0
    return EllipticFEscheme(model, make_variables(n_cells))
# Two levels, Jacobi omega=0.5, V(1,1), BiCGSTAB and shift 2k, as in the paper.
solver = shifted_laplacian_solver(scheme_factory, (160, 160), shift=2 * K)
scheme, report = EllipticFEscheme.solve(
    scheme_factory((160, 160)), solver=solver, return_report=True
)
# k = 100, 51 842 dofs: 35 iterations to 1e-8 (plain BiCGSTAB stalls).
# For a batch of sources, use solve_pure under jax.vmap with a shared multigrid.

The multigrid presented here is linear: it preconditions a fixed operator. When the operator changes at every iteration, two other methods build on it. The Nonlinear solvers page presents Newton preconditioned by a multigrid that follows the Jacobian, and FAS, a nonlinear multigrid that does not require the Jacobian.