Multigrid#
gko::solver::Multigrid builds a hierarchy of progressively coarser representations
of a system matrix and uses cheap smoother iterations on each level combined with
exact (or near-exact) solves on the coarsest level to attack errors at all
frequencies. It is most effective on elliptic and diagonally-dominant problems where
the low-frequency error modes (the ones Krylov methods struggle with) can be
captured by a coarse-grid representation. In practice, Ginkgo’s Multigrid is most
often used as a preconditioner inside a Krylov solver, not as the outermost
iteration.
The architecture#
A multigrid solve is a recursive structure of four ingredients per level:
finest level (level 0) A_0 <-- system matrix
|
smooth (pre)
|
restrict --> A_1 = R A_0 P
|
smooth (pre)
|
restrict --> A_2 ...
...
coarsest level
(direct solve, by default)
...
prolong <-- correction
|
smooth (post)
|
prolong <-- correction
|
smooth (post)
finest level (output)
Smoothers (
pre_smoother,post_smoother, optionalmid_smoother) attack high-frequency errors that the coarse grid cannot represent. Defaults to one step of iterative refinement with a scalar Jacobi preconditioner if not specified (in distributed runs the scalar Jacobi acts as the local solver of aSchwarzpreconditioner). A smoother is applied assmoother->apply(b, x)and has to updatex, so it is a solver such as the default smoother or one made withgko::solver::build_smoother(...)(see Smoothers below).Restriction (
R) maps the residual from a fine level to a coarse level.Prolongation (
P) maps a correction from a coarse level back to the fine level.Coarsening (the
mg_levelfactories) computesR,P, andA_{i+1} = R A_i Pfor every level. This is the algebraic component that makes Ginkgo an algebraic multigrid (AMG) library; see Coarsening methods below.Coarsest solver (
coarsest_solver) handles the smallest-level system. The default is a direct LU solver in single-process runs, GMRES with Jacobi on DPC++ and GMRES with a Schwarz preconditioner (Jacobi local solver) in distributed runs.
Each pair of consecutive levels is represented by a gko::multigrid::MultigridLevel
implementation. The interface exposes four operators:
auto fine = level->get_fine_op(); // A_i
auto restrict = level->get_restrict_op(); // R
auto coarse = level->get_coarse_op(); // A_{i+1}
auto prolong = level->get_prolong_op(); // P
This is the contract every coarsening method satisfies. A custom coarsening is a
LinOp (for example through EnableLinOp) that also inherits
gko::multigrid::EnableMultigridLevel<ValueType> and calls
set_multigrid_level(prolong, coarse, restrict) from generate().
Cycle types#
The cycle parameter controls how the recursion descends and ascends through the
hierarchy. At every non-coarsest level, the cycle does pre-smooth → restrict → one
or more coarse-grid sub-cycles → prolong → post-smooth. The three available cycle
types differ only in the number and shape of the coarse-grid sub-cycles:
|
Coarse-grid sub-cycles per level |
Cost per outer iteration |
|---|---|---|
|
One V-recursive sub-cycle. |
Cheapest, typical default. |
|
Two sub-cycles: first an F-recursive call, then a V-recursive call. |
Between V and W. |
|
Two W-recursive sub-cycles. |
Most expensive but often the most effective per iteration on stiff problems. |
The exact branching is in MultigridState::run_cycle: the V case takes the early-exit end_of_cycle path after a single recursive call, while F and W make a second recursive call before prolongation.
Key factory parameters#
The Multigrid::build() interface exposes a sizeable parameter surface. Most users
set mg_level and criteria; the remaining parameters have defaults that are
intended to work on standard elliptic problems.
Parameter |
Type |
Purpose |
|---|---|---|
|
vector of |
Coarsening factories; see Coarsening methods. At least one factory is required. |
|
vector of |
Stopping criteria of the multigrid iteration. At least one criterion is required, also when |
|
vector of |
Smoother factory per level (or one shared). Empty vector means “use the default smoother”: one step of iterative refinement with scalar Jacobi (with scalar Jacobi as the local solver of |
|
|
When |
|
|
Behaviour of the mid-smoother in F and W cycles: |
|
|
Maximum number of coarsening steps (excluding the coarsest solver). |
|
|
Stop coarsening once the matrix has at most this many rows. |
|
vector of |
Solver factory for the coarsest level. Defaults to a direct LU solver in single-process runs, GMRES with Jacobi on DPC++ and GMRES with a Schwarz preconditioner (Jacobi local solver) in distributed runs. |
|
|
V, W, or F cycle. |
|
scalar |
Tune the default smoother (relaxation factor, default 0.9, and number of iterations, default 1); they apply wherever no smoother factory is given. |
|
|
What to assume about the initial |
|
|
Pick which |
The smoother vectors (pre_smoother, post_smoother, mid_smoother) and
coarsest_solver fall back to their defaults when empty, and a single entry serves
every level. With several entries, the smoother and mg_level vectors are indexed
through level_selector (or its default), and coarsest_solver through
solver_selector.
This gives fine-grained per-level control without forcing the user to write a selector function for the common cases.
Coarsening methods#
The coarsening method is the algebraic component that turns a fine matrix into a
coarse one. It is what makes a multigrid solver algebraic: pure geometric multigrid
would expect the user to supply the hierarchy from a known mesh. Ginkgo ships two coarsening implementations, both deriving from
gko::multigrid::EnableMultigridLevel:
Class |
Approach |
|---|---|
|
Aggregation by parallel graph matching (the AmgX algorithm). The standard AMG-style coarsening; works on most algebraic problems. Supports distributed matrices; see the Pgm page for the cross-rank aggregation protocol. |
|
User-specified coarse rows. Useful when you have external knowledge of which DOFs should survive coarsening (e.g., from a hierarchical mesh). Single-rank only. |
Both produce four operators at each level (fine, coarse, restrict, prolong)
satisfying the MultigridLevel contract. They are passed to Multigrid through the
mg_level parameter.
Coarsening methods
Construction#
A typical multigrid-as-preconditioner setup. with_mg_level takes the Pgm builder
as is, and the Multigrid factory from .on(exec) moves into with_preconditioner:
auto mg = gko::solver::Multigrid::build()
.with_mg_level(gko::multigrid::Pgm<double, gko::int32>::build()
.with_deterministic(true))
.with_max_levels(10u)
.with_min_coarse_rows(64u)
.with_cycle(gko::solver::multigrid::cycle::v)
.with_criteria(gko::stop::Iteration::build().with_max_iters(1u))
.on(exec);
auto cg_factory = gko::solver::Cg<double>::build()
.with_preconditioner(std::move(mg))
.with_criteria(
gko::stop::Iteration::build().with_max_iters(100u),
gko::stop::ResidualNorm<double>::build()
.with_reduction_factor(1e-8))
.on(exec);
auto cg_solver = cg_factory->generate(system_matrix);
cg_solver->apply(b, x);
The single Iteration criterion makes each preconditioner application one multigrid
cycle. For a stand-alone multigrid solve, give Multigrid the stopping criteria of
the outer iteration and skip the Krylov solver:
auto solver_factory =
gko::solver::Multigrid::build()
.with_mg_level(gko::multigrid::Pgm<double, gko::int32>::build())
.with_criteria(gko::stop::Iteration::build().with_max_iters(50u),
gko::stop::ResidualNorm<double>::build()
.with_reduction_factor(1e-8))
.on(exec);
auto solver = solver_factory->generate(system_matrix);
solver->apply(b, x);
Smoothers#
The cycle applies a smoother as smoother->apply(b, x), so a smoother is a solver
that improves the current x. gko::solver::build_smoother wraps a preconditioner
factory in iterative refinement, which gives it that form (the default smoother is
built the same way); see
Multigrid smoother on the IR page for a complete
example. A bare preconditioner such as Jacobi in a smoother slot would overwrite
x with M^{-1} b and discard the coarse-grid correction. With the default
post_uses_pre = true, the pre-smoother also serves as post-smoother.
When multigrid helps (and when it does not)#
Multigrid shines when:
The problem is elliptic or strongly diagonally dominant (Poisson, diffusion, structural mechanics with reasonable conditioning).
The matrix has natural locality: neighbours in the graph correspond to spatially nearby unknowns.
You have time to tune cycle type, smoother, and coarsening for the specific PDE and discretisation; a poorly-tuned multigrid can be slower than CG-Jacobi.
Multigrid struggles when:
The matrix has long-range coupling or no natural mesh structure (e.g., problems derived from highly irregular graphs).
The problem is strongly non-symmetric or indefinite: the smoother and coarse solver assumptions break down.
In Ginkgo specifically, Multigrid is most often used as a preconditioner inside
CG, FCG, or GMRES. Used that way, even an imperfectly-tuned multigrid can outperform
simpler preconditioners on the right class of problems.
Publications#
See also
Solver taxonomy: multigrid in context of the other families.
Preconditioner taxonomy: the preconditioners that
build_smootherwraps for the smoother slots.LinOp and composition: the factory pattern multigrid uses.
API reference:
gko::solver::Multigrid,gko::multigrid::MultigridLevel