Jacobi (scalar and block)#
gko::preconditioner::Jacobi<ValueType, IndexType> is the block-Jacobi
preconditioner: it inverts the diagonal blocks of the system matrix and
applies that block-diagonal inverse as the approximate inverse of the full
operator. At block size 1 it degenerates to pointwise (scalar) Jacobi:
element-wise diagonal scaling. At larger block sizes (up to 32 rows /
columns per block on NVIDIA, 64 on AMD) it captures local couplings that scalar Jacobi misses.
The defining property is locality: each block’s inverse is computed and applied independently, so the preconditioner is embarrassingly parallel. Block-Jacobi is the standard cheap default for iterative solvers, and the canonical local solver inside the Schwarz preconditioner for distributed-memory runs.
When to use Jacobi#
As a cheap starting preconditioner for any iterative solver: the identity-preconditioner baseline rarely beats it, and the overhead is minimal.
At block size 1 for diagonally-dominant systems where local couplings are weak and pointwise scaling is enough.
At larger block sizes when the matrix has a natural block structure: finite-element matrices with multiple DOFs per node, vector-PDE discretisations, or any matrix where local couplings within a block are much stronger than couplings between blocks.
As the local solver inside Schwarz for distributed-memory problems; see Schwarz.
For matrices where the diagonal blocks are not a good approximation of the full inverse (e.g., problems with long-range couplings or large condition number), Jacobi’s per-iteration cost is so low that it often still helps, but more aggressive preconditioners (IC, ILU, AMG) will reduce the iteration count much further.
Construction#
auto jacobi = gko::preconditioner::Jacobi<double, gko::int32>::build()
.with_max_block_size(8u)
.on(exec);
auto preconditioner = jacobi->generate(system_matrix);
// or pass directly to a solver
auto cg = gko::solver::Cg<double>::build()
.with_preconditioner(
gko::preconditioner::Jacobi<double, gko::int32>::build()
.with_max_block_size(8u)
.on(exec))
.with_criteria(
gko::stop::Iteration::build().with_max_iters(100u),
gko::stop::ResidualNorm<double>::build()
.with_reduction_factor(1e-8))
.on(exec);
If the block-diagonal structure can be determined from the application
(e.g., DOFs per node in a finite-element problem), pass the explicit block
starting rows via with_block_pointers. Otherwise the implementation runs
a supervariable-agglomeration heuristic on the natural blocks it detects in
the matrix.
Factory parameters#
Parameter |
Type |
Purpose |
|---|---|---|
|
|
Upper bound on the per-block dimension, between 1 and 32 (NVIDIA) / 64 (AMD). Default |
|
|
Stride between two columns of a stored block, in elements; a multiple of the cache line size performs best. Default |
|
|
Skip sorting the input matrix when it is known to be sorted by row, then by column; the preconditioner requires sorted input. Default |
|
|
Starting row of each diagonal block. Omit to auto-detect via the supervariable-agglomeration heuristic; the heuristic is conservative, so explicit pointers from domain knowledge are usually faster and more accurate. |
|
|
Block storage precision. Default |
|
|
Relative accuracy of the adaptive variant. It applies to blocks whose precision is |
|
|
Generate the preconditioner on \(A + \mathrm{diag}(\sum_{k \in \text{off-block}} \lvert A_{ik} \rvert)\) instead of \(A\). Useful as a smoother in multigrid; see the upstream docstring for the row-dominance condition. Default |
The block-detection heuristic and the available adaptive-precision options are documented in detail on the class docstring; see the API reference link below for the full surface.
Implementation notes#
Each diagonal block is inverted explicitly by Gauss-Jordan elimination
with column pivoting and stored in a custom interleaved layout that
preserves coalesced GPU access patterns. The pointwise max_block_size = 1
specialisation skips the block format entirely and stores the inverse
diagonal in a single array, significantly faster for very large problems
where the regularity of the access matters more than the block coupling.
For blocks set to precision_reduction::autodetect(), the adaptive-precision
variant analyses each block’s condition number at generation time and chooses a per-block storage precision (double →
float → half, …) whose rounding error stays below accuracy / cond. This reduces the memory traffic during apply,
the biggest cost on GPUs, without changing the working precision of the
iterative solver around it.
See also
Solver taxonomy: picking the right solver to pair with Jacobi.
Preconditioner taxonomy: alternatives when Jacobi is too weak.
Schwarz: uses block-Jacobi as its per-rank local solver.
API reference:
gko::preconditioner::Jacobi.
References:
Anzt, H., Dongarra, J., Flegar, G., Higham, N. J., Quintana-Ortí, E. S. Adaptive Precision in Block-Jacobi Preconditioning for Iterative Sparse Linear System Solvers. ACM Transactions on Mathematical Software, 47 (2), Article 14, 2021. https://doi.org/10.1145/3441850