Preconditioners
Many linear solvers can be accelerated by using what is known as a preconditioner, an approximation to the matrix inverse action which is cheap to evaluate. These can improve the numerical conditioning of the solver process and in turn improve the performance. LinearSolve.jl provides an interface for the definition of preconditioners which works with the wrapped iterative solver packages.
Using Preconditioners
Mathematical Definition
A right preconditioner, $P_r$ transforms the linear system $Au = b$ into the form:
\[AP_r^{-1}(P_r u) = AP_r^{-1}y = b\]
which is solved for $y$, and then $P_r u = y$ is solved for $u$. The left preconditioner, $P_l$, transforms the linear system into the form:
\[P_l^{-1}Au = P_l^{-1}b\]
A two-sided preconditioned system is of the form:
\[P_l^{-1}A P_r^{-1} (P_r u) = P_l^{-1}b\]
Specifying Preconditioners
One way to specify preconditioners uses the Pl
and Pr
keyword arguments to init
or solve
: Pl
for left and Pr
for right preconditioner, respectively. By default, if no preconditioner is given, the preconditioner is assumed to be the identity $I$.
In the following, we will use a left sided diagonal (Jacobi) preconditioner.
using LinearSolve, LinearAlgebra
n = 4
A = rand(n, n)
b = rand(n)
Pl = Diagonal(A)
prob = LinearProblem(A, b)
sol = solve(prob, KrylovJL_GMRES(), Pl = Pl)
sol.u
4-element Vector{Float64}:
-0.04116583358366968
0.18648260664554167
0.4992404148063758
0.19765278307658934
Alternatively, preconditioners can be specified via the precs
argument to the constructor of an iterative solver specification. This argument shall deliver a factory method mapping A
and a parameter p
to a tuple (Pl,Pr)
consisting a left and a right preconditioner.
using LinearSolve, LinearAlgebra
n = 4
A = rand(n, n)
b = rand(n)
prob = LinearProblem(A, b)
sol = solve(prob, KrylovJL_GMRES(precs = (A, p) -> (Diagonal(A), I)))
sol.u
4-element Vector{Float64}:
1.480953458317602
-0.4651011807887008
-0.5702874247535148
0.3080071106741705
This approach has the advantage that the specification of the preconditioner is possible without the knowledge of a concrete matrix A
. It also allows to specify the preconditioner via a callable object and to pass parameters to the constructor of the preconditioner instances. The example below also shows how to reuse the preconditioner once constructed for the subsequent solution of a modified problem.
using LinearSolve, LinearAlgebra
Base.@kwdef struct WeightedDiagonalPreconBuilder
w::Float64
end
(builder::WeightedDiagonalPreconBuilder)(A, p) = (builder.w * Diagonal(A), I)
n = 4
A = n * I - rand(n, n)
b = rand(n)
prob = LinearProblem(A, b)
sol = solve(prob, KrylovJL_GMRES(precs = WeightedDiagonalPreconBuilder(w = 0.9)))
sol.u
B = A .+ 0.1
cache = sol.cache
reinit!(cache, A = B, reuse_precs = true)
sol = solve!(cache, KrylovJL_GMRES(precs = WeightedDiagonalPreconBuilder(w = 0.9)))
sol.u
4-element Vector{Float64}:
0.056576701039026976
0.14049254047325732
0.0985424318116175
0.25918196041962366
Preconditioner Interface
To define a new preconditioner you define a Julia type which satisfies the following interface:
Base.eltype(::Preconditioner)
(Required only for Krylov.jl)LinearAlgebra.ldiv!(::AbstractVector,::Preconditioner,::AbstractVector)
andLinearAlgebra.ldiv!(::Preconditioner,::AbstractVector)
Curated List of Pre-Defined Preconditioners
The following preconditioners match the interface of LinearSolve.jl.
LinearSolve.ComposePreconditioner(prec1,prec2)
: composes the preconditioners to applyprec1
beforeprec2
.LinearSolve.InvPreconditioner(prec)
: invertsmul!
andldiv!
in a preconditioner definition as a lazy inverse.LinearAlgera.Diagonal(s::Union{Number,AbstractVector})
: the lazy Diagonal matrix type of Base.LinearAlgebra. Used for efficient construction of a diagonal preconditioner.Other
Base.LinearAlgera
types: all define the full Preconditioner interface.IncompleteLU.ilu: an implementation of the incomplete LU-factorization preconditioner. This requires
A
as aSparseMatrixCSC
.Preconditioners.CholeskyPreconditioner(A, i): An incomplete Cholesky preconditioner with cut-off level
i
. RequiresA
as aAbstractMatrix
and positive semi-definite.AlgebraicMultigrid: Implementations of the algebraic multigrid method. Must be converted to a preconditioner via
AlgebraicMultigrid.aspreconditioner(AlgebraicMultigrid.precmethod(A))
. RequiresA
as aAbstractMatrix
. Provides the following methods:AlgebraicMultigrid.ruge_stuben(A)
AlgebraicMultigrid.smoothed_aggregation(A)
PyAMG: Implementations of the algebraic multigrid method. Must be converted to a preconditioner via
PyAMG.aspreconditioner(PyAMG.precmethod(A))
. RequiresA
as aAbstractMatrix
. Provides the following methods:PyAMG.RugeStubenSolver(A)
PyAMG.SmoothedAggregationSolver(A)
ILUZero.ILU0Precon(A::SparseMatrixCSC{T,N}, b_type = T): An incomplete LU implementation. Requires
A
as aSparseMatrixCSC
.LimitedLDLFactorizations.lldl: A limited-memory LDLᵀ factorization for symmetric matrices. Requires
A
as aSparseMatrixCSC
. ApplyingF = lldl(A); F.D .= abs.(F.D)
before usage as a preconditioner makes the preconditioner symmetric positive definite and thus is required for Krylov methods which are specialized for symmetric linear systems.RandomizedPreconditioners.NystromPreconditioner A randomized sketching method for positive semidefinite matrices
A
. Builds a preconditioner $P ≈ A + μ*I$ for the system $(A + μ*I)x = b$.HYPRE.jl A set of solvers with preconditioners which supports distributed computing via MPI. These can be written using the LinearSolve.jl interface choosing algorithms like
HYPRE.ILU
andHYPRE.BoomerAMG
.KrylovPreconditioners.jl: Provides GPU-ready preconditioners via KernelAbstractions.jl. At the time of writing the package provides the following methods:
- Incomplete Cholesky decomposition
KrylovPreconditioners.kp_ic0(A)
- Incomplete LU decomposition
KrylovPreconditioners.kp_ilu0(A)
- Block Jacobi
KrylovPreconditioners.kp_block_jacobi(A)
- Incomplete Cholesky decomposition