Skip to content

Commit 1578619

Browse files
Merge pull request #254 from fredrikekre/fe/hypre
HYPRE.jl solvers and preconditioners
2 parents 935d2db + 7fbf5aa commit 1578619

File tree

9 files changed

+535
-3
lines changed

9 files changed

+535
-3
lines changed

.github/workflows/CI.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ jobs:
1818
version:
1919
- '1'
2020
- '1.6'
21+
include:
22+
- version: '^1.9.0-0'
23+
group: 'LinearSolveHYPRE'
2124
steps:
2225
- uses: actions/checkout@v2
2326
- uses: julia-actions/setup-julia@v1
@@ -39,7 +42,7 @@ jobs:
3942
GROUP: ${{ matrix.group }}
4043
- uses: julia-actions/julia-processcoverage@v1
4144
with:
42-
directories: src,lib/LinearSolveCUDA/src,lib/LinearSolvePardiso/src
45+
directories: src,lib/LinearSolveCUDA/src,lib/LinearSolvePardiso/src,ext
4346
- uses: codecov/codecov-action@v3
4447
with:
4548
files: lcov.info

Project.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,18 @@ Sparspak = "e56a9233-b9d6-4f03-8d0f-1825330902ac"
2424
SuiteSparse = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
2525
UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
2626

27+
[weakdeps]
28+
HYPRE = "b5ffcf37-a2bd-41ab-a3da-4bd9bc8ad771"
29+
30+
[extensions]
31+
LinearSolveHYPRE = "HYPRE"
32+
2733
[compat]
2834
ArrayInterfaceCore = "0.1.1"
2935
DocStringExtensions = "0.8, 0.9"
3036
FastLapackInterface = "1"
3137
GPUArraysCore = "0.1"
38+
HYPRE = "1.3.1"
3239
IterativeSolvers = "0.9.2"
3340
KLU = "0.3.0, 0.4"
3441
Krylov = "0.9"
@@ -44,12 +51,14 @@ UnPack = "1"
4451
julia = "1.6"
4552

4653
[extras]
47-
MultiFloats = "bdf0d083-296b-4888-a5b6-7498122e68a5"
4854
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
55+
HYPRE = "b5ffcf37-a2bd-41ab-a3da-4bd9bc8ad771"
56+
MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
57+
MultiFloats = "bdf0d083-296b-4888-a5b6-7498122e68a5"
4958
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
5059
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
5160
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
5261
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5362

5463
[targets]
55-
test = ["Test", "Pkg", "Random", "SafeTestsets", "MultiFloats", "ForwardDiff"]
64+
test = ["Test", "Pkg", "Random", "SafeTestsets", "MultiFloats", "ForwardDiff", "HYPRE", "MPI"]

docs/src/solvers/solvers.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,50 @@ function KrylovKitJL(args...;
226226
KrylovAlg = KrylovKit.GMRES, gmres_restart = 0,
227227
kwargs...)
228228
```
229+
230+
### HYPRE.jl
231+
232+
!!! note
233+
Using HYPRE solvers requires Julia version 1.9 or higher, and that the package HYPRE.jl
234+
is installed.
235+
236+
[HYPRE.jl](https://github.com/fredrikekre/HYPRE.jl) is an interface to
237+
[`hypre`](https://computing.llnl.gov/projects/hypre-scalable-linear-solvers-multigrid-methods)
238+
and provide iterative solvers and preconditioners for sparse linear systems. It is mainly
239+
developed for large multi-process distributed problems (using MPI), but can also be used for
240+
single-process problems with Julias standard sparse matrices.
241+
242+
The algorithm is defined as:
243+
244+
```julia
245+
alg = HYPREAlgorithm(X)
246+
```
247+
248+
where `X` is one of the following supported solvers:
249+
250+
- `HYPRE.BiCGSTAB`
251+
- `HYPRE.BoomerAMG`
252+
- `HYPRE.FlexGMRES`
253+
- `HYPRE.GMRES`
254+
- `HYPRE.Hybrid`
255+
- `HYPRE.ILU`
256+
- `HYPRE.ParaSails` (as preconditioner only)
257+
- `HYPRE.PCG`
258+
259+
Some of the solvers above can also be used as preconditioners by passing via the `Pl`
260+
keyword argument.
261+
262+
For example, to use `HYPRE.PCG` as the solver, with `HYPRE.BoomerAMG` as the preconditioner,
263+
the algorithm should be defined as follows:
264+
265+
```julia
266+
A, b = setup_system(...)
267+
prob = LinearProblem(A, b)
268+
alg = HYPREAlgorithm(HYPRE.PCG)
269+
prec = HYPRE.BoomerAMG
270+
sol = solve(prob, alg; Pl = prec)
271+
```
272+
273+
If you need more fine-grained control over the solver/preconditioner options you can
274+
alternatively pass an already created solver to `HYPREAlgorithm` (and to the `Pl` keyword
275+
argument). See HYPRE.jl docs for how to set up solvers with specific options.

ext/LinearSolveHYPRE.jl

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
module LinearSolveHYPRE
2+
3+
using HYPRE.LibHYPRE: HYPRE_Complex
4+
using HYPRE: HYPRE, HYPREMatrix, HYPRESolver, HYPREVector
5+
using IterativeSolvers: Identity
6+
using LinearSolve: HYPREAlgorithm, LinearCache, LinearProblem, LinearSolve,
7+
OperatorAssumptions, default_tol, init_cacheval, issquare, set_cacheval
8+
using SciMLBase: LinearProblem, SciMLBase
9+
using UnPack: @unpack
10+
using Setfield: @set!
11+
12+
mutable struct HYPRECache
13+
solver::Union{HYPRE.HYPRESolver, Nothing}
14+
A::Union{HYPREMatrix, Nothing}
15+
b::Union{HYPREVector, Nothing}
16+
u::Union{HYPREVector, Nothing}
17+
isfresh_A::Bool
18+
isfresh_b::Bool
19+
isfresh_u::Bool
20+
end
21+
22+
function LinearSolve.init_cacheval(alg::HYPREAlgorithm, A, b, u, Pl, Pr, maxiters::Int,
23+
abstol, reltol,
24+
verbose::Bool, assumptions::OperatorAssumptions)
25+
return HYPRECache(nothing, nothing, nothing, nothing, true, true, true)
26+
end
27+
28+
# Overload set_(A|b|u) in order to keep track of "isfresh" for all of them
29+
const LinearCacheHYPRE = LinearCache{<:Any, <:Any, <:Any, <:Any, <:Any, HYPRECache}
30+
function LinearSolve.set_A(cache::LinearCacheHYPRE, A)
31+
@set! cache.A = A
32+
cache.cacheval.isfresh_A = true
33+
@set! cache.isfresh = true
34+
return cache
35+
end
36+
function LinearSolve.set_b(cache::LinearCacheHYPRE, b)
37+
@set! cache.b = b
38+
cache.cacheval.isfresh_b = true
39+
return cache
40+
end
41+
function LinearSolve.set_u(cache::LinearCacheHYPRE, u)
42+
@set! cache.u = u
43+
cache.cacheval.isfresh_u = true
44+
return cache
45+
end
46+
47+
# Note:
48+
# SciMLBase.init is overloaded here instead of just LinearSolve.init_cacheval for two
49+
# reasons:
50+
# - HYPREArrays can't really be `deepcopy`d, so that is turned off by default
51+
# - The solution vector/initial guess u0 can't be created with
52+
# fill!(similar(b, size(A, 2)), false) since HYPREArrays are not AbstractArrays.
53+
54+
function SciMLBase.init(prob::LinearProblem, alg::HYPREAlgorithm,
55+
args...;
56+
alias_A = false, alias_b = false,
57+
# TODO: Implement eltype for HYPREMatrix in HYPRE.jl? Looks useful
58+
# even if it is not AbstractArray.
59+
abstol = default_tol(prob.A isa HYPREMatrix ? HYPRE_Complex :
60+
eltype(prob.A)),
61+
reltol = default_tol(prob.A isa HYPREMatrix ? HYPRE_Complex :
62+
eltype(prob.A)),
63+
# TODO: Implement length() for HYPREVector in HYPRE.jl?
64+
maxiters::Int = prob.b isa HYPREVector ? 1000 : length(prob.b),
65+
verbose::Bool = false,
66+
Pl = Identity(),
67+
Pr = Identity(),
68+
assumptions = OperatorAssumptions(),
69+
kwargs...)
70+
@unpack A, b, u0, p = prob
71+
72+
# Create solution vector/initial guess
73+
if u0 === nothing
74+
u0 = zero(b)
75+
end
76+
77+
# Initialize internal alg cache
78+
cacheval = init_cacheval(alg, A, b, u0, Pl, Pr, maxiters, abstol, reltol, verbose,
79+
assumptions)
80+
Tc = typeof(cacheval)
81+
isfresh = true
82+
83+
cache = LinearCache{
84+
typeof(A), typeof(b), typeof(u0), typeof(p), typeof(alg), Tc,
85+
typeof(Pl), typeof(Pr), typeof(reltol), issquare(assumptions)
86+
}(A, b, u0, p, alg, cacheval, isfresh, Pl, Pr, abstol, reltol,
87+
maxiters,
88+
verbose, assumptions)
89+
return cache
90+
end
91+
92+
# Solvers whose constructor requires passing the MPI communicator
93+
const COMM_SOLVERS = Union{HYPRE.BiCGSTAB, HYPRE.FlexGMRES, HYPRE.GMRES, HYPRE.ParaSails,
94+
HYPRE.PCG}
95+
create_solver(::Type{S}, comm) where {S <: COMM_SOLVERS} = S(comm)
96+
97+
# Solvers whose constructor should not be passed the MPI communicator
98+
const NO_COMM_SOLVERS = Union{HYPRE.BoomerAMG, HYPRE.Hybrid, HYPRE.ILU}
99+
create_solver(::Type{S}, comm) where {S <: NO_COMM_SOLVERS} = S()
100+
101+
function create_solver(alg::HYPREAlgorithm, cache::LinearCache)
102+
# If the solver is already instantiated, return it directly
103+
if alg.solver isa HYPRE.HYPRESolver
104+
return alg.solver
105+
end
106+
107+
# Otherwise instantiate
108+
if !(alg.solver <: Union{COMM_SOLVERS, NO_COMM_SOLVERS})
109+
throw(ArgumentError("unknown or unsupported HYPRE solver: $(alg.solver)"))
110+
end
111+
comm = cache.cacheval.A.comm # communicator from the matrix
112+
solver = create_solver(alg.solver, comm)
113+
114+
# Construct solver options
115+
solver_options = (;
116+
AbsoluteTol = cache.abstol,
117+
MaxIter = cache.maxiters,
118+
PrintLevel = Int(cache.verbose),
119+
Tol = cache.reltol)
120+
121+
# Preconditioner (uses Pl even though it might not be a *left* preconditioner just *a*
122+
# preconditioner)
123+
if !(cache.Pl isa Identity)
124+
precond = if cache.Pl isa HYPRESolver
125+
cache.Pl
126+
elseif cache.Pl <: HYPRESolver
127+
create_solver(cache.Pl, comm)
128+
else
129+
throw(ArgumentError("unknown HYPRE preconditioner $(cache.Pl)"))
130+
end
131+
solver_options = merge(solver_options, (; Precond = precond))
132+
end
133+
134+
# Filter out some options that are not supported for some solvers
135+
if solver isa HYPRE.Hybrid
136+
# Rename MaxIter to PCGMaxIter
137+
MaxIter = solver_options.MaxIter
138+
ks = filter(x -> x !== :MaxIter, keys(solver_options))
139+
solver_options = NamedTuple{ks}(solver_options)
140+
solver_options = merge(solver_options, (; PCGMaxIter = MaxIter))
141+
elseif solver isa HYPRE.BoomerAMG || solver isa HYPRE.ILU
142+
# Remove AbsoluteTol, Precond
143+
ks = filter(x -> !in(x, (:AbsoluteTol, :Precond)), keys(solver_options))
144+
solver_options = NamedTuple{ks}(solver_options)
145+
end
146+
147+
# Set the options
148+
HYPRE.Internals.set_options(solver, pairs(solver_options))
149+
150+
return solver
151+
end
152+
153+
# TODO: How are args... and kwargs... supposed to be used here?
154+
function SciMLBase.solve(cache::LinearCache, alg::HYPREAlgorithm, args...; kwargs...)
155+
# It is possible to reach here without HYPRE.Init() being called if HYPRE structures are
156+
# only to be created here internally (i.e. when cache.A::SparseMatrixCSC and not a
157+
# ::HYPREMatrix created externally by the user). Be nice to the user and call it :)
158+
if !(cache.A isa HYPREMatrix || cache.b isa HYPREVector || cache.u isa HYPREVector ||
159+
alg.solver isa HYPRESolver)
160+
HYPRE.Init()
161+
end
162+
163+
# Move matrix and vectors to HYPRE, if not already provided as HYPREArrays
164+
hcache = cache.cacheval
165+
if hcache.isfresh_A || hcache.A === nothing
166+
hcache.A = cache.A isa HYPREMatrix ? cache.A : HYPREMatrix(cache.A)
167+
hcache.isfresh_A = false
168+
end
169+
if hcache.isfresh_b || hcache.b === nothing
170+
hcache.b = cache.b isa HYPREVector ? cache.b : HYPREVector(cache.b)
171+
hcache.isfresh_b = false
172+
end
173+
if hcache.isfresh_u || hcache.u === nothing
174+
hcache.u = cache.u isa HYPREVector ? cache.u : HYPREVector(cache.u)
175+
hcache.isfresh_u = false
176+
end
177+
178+
# Create the solver.
179+
if hcache.solver === nothing
180+
hcache.solver = create_solver(alg, cache)
181+
end
182+
183+
# Done with cache updates; set it
184+
cache = set_cacheval(cache, hcache)
185+
186+
# Solve!
187+
HYPRE.solve!(hcache.solver, hcache.u, hcache.A, hcache.b)
188+
189+
# Copy back if the output is not HYPREVector
190+
if cache.u !== hcache.u
191+
@assert !(cache.u isa HYPREVector)
192+
copy!(cache.u, hcache.u)
193+
end
194+
195+
# Note: Inlining SciMLBase.build_linear_solution(alg, u, resid, cache; retcode, iters)
196+
# since some of the functions used in there does not play well with HYPREVector.
197+
198+
T = cache.u isa HYPREVector ? HYPRE_Complex : eltype(cache.u) # eltype(u)
199+
N = 1 # length((size(u)...,))
200+
resid = nothing # TODO: Fetch from solver
201+
iters = 0 # TODO: Fetch from solver
202+
retc = SciMLBase.ReturnCode.Default # TODO: Fetch from solver
203+
204+
ret = SciMLBase.LinearSolution{T, N, typeof(cache.u), typeof(resid), typeof(alg),
205+
typeof(cache)}(cache.u, resid, alg, retc, iters, cache)
206+
207+
return ret
208+
end
209+
210+
# HYPREArrays are not AbstractArrays so perform some type-piracy
211+
function SciMLBase.LinearProblem(A::HYPREMatrix, b::HYPREVector,
212+
p = SciMLBase.NullParameters();
213+
u0::Union{HYPREVector, Nothing} = nothing, kwargs...)
214+
return LinearProblem{true}(A, b, p; u0 = u0, kwargs)
215+
end
216+
217+
end # module LinearSolveHYPRE

src/HYPRE.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# This file only include the algorithm struct to be exported by LinearSolve.jl. The main
2+
# functionality is implemented as a package extension in ext/LinearSolveHYPRE.jl.
3+
4+
struct HYPREAlgorithm <: SciMLLinearSolveAlgorithm
5+
solver::Any
6+
end

src/LinearSolve.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ include("preconditioners.jl")
5050
include("solve_function.jl")
5151
include("default.jl")
5252
include("init.jl")
53+
include("HYPRE.jl")
5354

5455
@static if INCLUDE_SPARSE
5556
include("factorization_sparse.jl")
@@ -101,4 +102,6 @@ export KrylovJL, KrylovJL_CG, KrylovJL_MINRES, KrylovJL_GMRES,
101102
IterativeSolversJL_BICGSTAB, IterativeSolversJL_MINRES,
102103
KrylovKitJL, KrylovKitJL_CG, KrylovKitJL_GMRES
103104

105+
export HYPREAlgorithm
106+
104107
end

0 commit comments

Comments
 (0)