Preconditioners¶
A preconditioner is an inexpensive inverse action \(M^{-1}\) chosen so the preconditioned operator has a more favorable spectrum. It need not reproduce \(A^{-1}\) accurately in every direction; it should remove the error components that make the outer iteration slow.
SOLVAX builders return callables suitable for precond=.
Jacobi scaling¶
For \(M=\operatorname{diag}(A)\),
precond = sx.jacobi(diagonal)
Jacobi is cheap, parallel, and useful for scale disparity. It cannot represent strong off-diagonal or within-cell coupling. Zero diagonal entries are a model error and should be addressed explicitly.
Block Jacobi¶
Partition the unknown into independent preconditioning blocks:
precond = sx.block_jacobi(blocks) # (N, m, m)
Each dense block is LU-factored and applied in a batch. This is effective when within-point physics is stiff but inter-point coupling is weaker. It costs more than point Jacobi but often reduces Krylov iterations substantially.
Coarse or simplified operator¶
Let \(A_s\) retain dominant physics and discard expensive weak couplings. Then
If the second term is small in the difficult subspace, eigenvalues cluster near
one. coarse_operator documents and returns an existing solve action:
factors = sx.block_thomas_factor(*local_bands)
solve_local = lambda r: sx.block_thomas_solve(factors, r)
precond = sx.coarse_operator(solve_local)
This is the preferred production pattern for a structured local principal part plus nonlocal, nonlinearized, or dense-tail corrections.
Line smoother¶
For anisotropic operators, point smoothers leave error that varies slowly along the strongly coupled direction. A line solve updates
for each selected direction \(i\) and sweep.
precond = sx.line_smoother(
matvec,
[x_line_inverse, y_line_inverse],
omega=[0.8, 0.8],
sweeps=2,
)
The line inverses often use tridiagonal_solve or banded LU. Alternating
directions treats mixed anisotropy better than a single line family
[TOSchuller01].
Symmetric additive composition¶
For fixed self-adjoint positive-definite inverse actions \(B_i\) and positive
weights \(w_i\), the sum \(B=\sum_i w_iB_i\) remains self-adjoint positive definite
and is therefore suitable for PCG. additive_preconditioner defaults to the
arithmetic mean and accepts arrays or arbitrary matching pytrees:
precond = sx.additive_preconditioner([x_line_inverse, y_line_inverse])
solution = sx.pcg(matvec, rhs, precond=precond)
This composes existing axis, block, or Schwarz inverse actions; geometry and component construction stay with the caller. Use positive custom weights only after verifying that every component is symmetric positive definite.
For structured arrays, SOLVAX can build those components directly from nonperiodic tridiagonal bands and an optional cyclic final axis:
precond = sx.additive_tridiagonal_line_preconditioner(
diagonal,
[(0, lower_x, upper_x), (1, lower_y, upper_y)],
periodic_last_axis=(lower_z, upper_z),
)
solution = sx.pcg(matvec, rhs, precond=precond)
All arrays retain their original layout outside each batched line solve. The builder is JIT- and gradient-transparent; the caller remains responsible for coefficient boundary entries and component positive definiteness.
Multigrid V-cycle¶
Let level \(\ell\) have operator \(A_\ell\), smoother \(S_\ell\), restriction \(R_\ell\), and prolongation \(P_\ell\). A V-cycle performs:
pre-smoothing on \(A_\ell x=b\);
residual restriction \(r_{\ell+1}=R_\ell(b-A_\ell x)\);
recursive coarse correction;
prolongation \(x\leftarrow x+P_\ell e_{\ell+1}\);
post-smoothing.
precond = sx.p_multigrid(
matvecs=[A_fine, A_medium],
restricts=[R_fine, R_medium],
prolongs=[P_fine, P_medium],
coarse_solve=solve_coarse,
smoothers=[fine_diagonal, medium_smoother],
cycles=1,
)
Despite its historical name, the routine is agnostic to whether levels arise from mesh spacing \(h\), polynomial degree \(p\), spectral truncation, or a physics coarsening. The caller owns consistency of shapes and transfer operators.
Arrays supplied as smoothers are interpreted as diagonal smoothers; callables may implement richer applications. Multigrid quality depends on complementary smoothing and coarse correction, not the recursion alone [TOSchuller01].
Randomized Nystrom preconditioning¶
When no grid hierarchy or structured coarse operator exists, a rank-\(\ell\) randomized Nystrom approximation \(A_{\mathrm{nys}}=U\Lambda U^T\) built from \(\ell\) operator applications gives the SPD preconditioner
for the regularized system \((A+\mu I)x=b\). When \(\ell\) exceeds about twice the \(\mu\)-effective dimension of \(A\), the preconditioned condition number is bounded by a small constant in expectation, independently of how the spectrum decays [FTU23].
precond = sx.nystrom_preconditioner(matvec, n, rank, jax.random.PRNGKey(0), mu=mu)
solution = sx.pcg(lambda v: matvec(v) + mu * v, b, precond=precond)
The sketch key is explicit, so construction is deterministic, jit-able, and differentiable through both the sketch and the eigenfactors — gradients flow through the preconditioner exactly like every other SOLVAX component. On a decaying-spectrum SPD operator with a flat tail the test suite pins at least a 2x PCG iteration reduction; the target regime is fast spectral decay ahead of a regularization shift.
Symmetric Galerkin deflation¶
PCG needs a fixed symmetric positive-definite preconditioner. Given a symmetric
smoother \(S\), prolongation \(P\), and Galerkin coarse operator
\(A_c=P^TAP\), galerkin_deflation applies the balanced two-level inverse
coarse_template = jnp.zeros(coarse_shape)
precond = sx.galerkin_deflation(
A_fine,
symmetric_smoother,
prolong,
solve_galerkin_coarse,
coarse_template,
)
Restriction is generated as the exact linear transpose of prolong, avoiding
an inconsistent transfer pair. The caller still owns the symmetry and positive
definiteness of \(A\), \(S\), and the coarse solve. Use the general V-cycle with a
flexible Krylov method when these requirements do not hold.
Kronecker preconditioning¶
For a separable approximation \(A\approx A_1\otimes A_2\),
kronecker_nkp accepts LU factors for the two factors and applies the inverse
through reshaping and two small solves:
precond = sx.kronecker_nkp(lu_factor(A1), lu_factor(A2))
For a small dense matrix, nearest_kronecker(matrix, na, nb) obtains a
rank-one Kronecker approximation from the leading singular triplet of the Van
Loan-Pitsianis rearrangement [VLP93].
The extraction itself requires the dense matrix and is therefore a model or setup tool, not a large matrix-free operation.
Mixed-precision wrapper¶
precond32 = sx.mixed_precision(precond64, dtype=jnp.float32)
Inputs are cast down for the preconditioner and results cast back. Flexible GMRES can tolerate this varying/inexact action. PCG requires additional care: the effective preconditioner must remain positive definite.
Constraint-aware preconditioning¶
For bordered systems, use schur_projected_precond; see Linear operator models.
It incorporates the small constraint Schur complement rather than ignoring
Lagrange multipliers.
How to evaluate a preconditioner¶
Measure:
outer iterations and true residual;
setup/factorization time;
application time per iteration;
memory and compilation cost;
robustness across the full parameter regime.
A preconditioner that halves iterations but costs ten operator applications per use is not an improvement. Benchmark complete solves, including setup reuse.
Compatibility table¶
Preconditioner |
FGMRES |
GCROT |
PCG |
|---|---|---|---|
Jacobi |
yes |
yes |
yes if positive |
Block Jacobi |
yes |
yes |
yes if HPD |
changing/inexact nested solve |
yes |
yes |
generally no |
positive additive composition |
yes |
yes |
yes if components are SPD |
line smoother |
yes |
yes |
only if resulting action is SPD |
V-cycle |
yes |
yes |
only with a symmetric positive cycle |
balanced Galerkin deflation |
yes |
yes |
yes if components are SPD |
mixed precision |
yes |
yes |
validate positivity carefully |
API summary¶
Runnable counterparts: examples 02, 07, 08, 09, 10, 11, and 12.