Skip to content

Commit 59a6430

Browse files
Automatic build\nPublished by build of: SciML/SciMLBenchmarks.jl@6f3a15e
1 parent 9dec865 commit 59a6430

42 files changed

Lines changed: 3860 additions & 6548 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

markdown/NonlinearProblem/bruss.md

Lines changed: 315 additions & 5392 deletions
Large diffs are not rendered by default.
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
---
2+
priority: 10000000
3+
author: "Avik Pal"
4+
title: "Ill-Conditioned Nonlinear System Work-Precision Diagrams (Krylov Methods)"
5+
---
6+
7+
8+
# Setup
9+
10+
Fetch required packages
11+
12+
```julia
13+
using NonlinearSolve, LinearAlgebra, SparseArrays, DiffEqDevTools,
14+
CairoMakie, Symbolics, BenchmarkTools, PolyesterForwardDiff, LinearSolve, Sundials,
15+
Enzyme, SparseConnectivityTracer, DifferentiationInterface, SparseMatrixColorings
16+
import NLsolve, MINPACK, PETSc, RecursiveFactorization
17+
18+
const RUS = RadiusUpdateSchemes;
19+
BenchmarkTools.DEFAULT_PARAMETERS.seconds = 0.2;
20+
```
21+
22+
23+
24+
25+
Define a utility to timeout the benchmark after a certain time.
26+
27+
```julia
28+
# Taken from ReTestItems.jl
29+
function timeout(f, timeout)
30+
cond = Threads.Condition()
31+
timer = Timer(timeout) do tm
32+
close(tm)
33+
ex = ErrorException("timed out after $timeout seconds")
34+
@lock cond notify(cond, ex; error=false)
35+
end
36+
Threads.@spawn begin
37+
try
38+
ret = $f()
39+
isopen(timer) && @lock cond notify(cond, ret)
40+
catch e
41+
isopen(timer) && @lock cond notify(cond, CapturedException(e, catch_backtrace()); error=true)
42+
finally
43+
close(timer)
44+
end
45+
end
46+
return @lock cond wait(cond) # will throw if we timeout
47+
end
48+
49+
function get_ordering(x::AbstractMatrix)
50+
idxs = Vector{Int}(undef, size(x, 1))
51+
placed = zeros(Bool, size(x, 1))
52+
idx = 1
53+
for j in size(x, 2):-1:1
54+
row = view(x, :, j)
55+
idxs_row = sortperm(row; by = x -> isnan(x) ? Inf : (x == -1 ? Inf : x))
56+
for i in idxs_row
57+
if !placed[i] && !isnan(row[i]) && row[i] -1
58+
idxs[idx] = i
59+
placed[i] = true
60+
idx += 1
61+
idx > length(idxs) && break
62+
end
63+
end
64+
idx > length(idxs) && break
65+
end
66+
return idxs
67+
end
68+
```
69+
70+
```
71+
get_ordering (generic function with 1 method)
72+
```
73+
74+
75+
76+
77+
78+
# Brusselator
79+
80+
Define the Brussletor problem.
81+
82+
```julia
83+
brusselator_f(x, y) = (((x - 3 // 10) ^ 2 + (y - 6 // 10) ^ 2) 0.01) * 5
84+
85+
limit(a, N) = ifelse(a == N + 1, 1, ifelse(a == 0, N, a))
86+
87+
function init_brusselator_2d(xyd, N)
88+
N = length(xyd)
89+
u = zeros(N, N, 2)
90+
for I in CartesianIndices((N, N))
91+
x = xyd[I[1]]
92+
y = xyd[I[2]]
93+
u[I, 1] = 22 * (y * (1 - y))^(3 / 2)
94+
u[I, 2] = 27 * (x * (1 - x))^(3 / 2)
95+
end
96+
return u
97+
end
98+
99+
function generate_brusselator_problem(N::Int; sparsity = nothing, kwargs...)
100+
xyd_brusselator = range(0; stop = 1, length = N)
101+
102+
function brusselator_2d_loop(du_, u_, p)
103+
A, B, α, δx = p
104+
α = α / δx ^ 2
105+
106+
du = reshape(du_, N, N, 2)
107+
u = reshape(u_, N, N, 2)
108+
109+
@inbounds @simd for I in CartesianIndices((N, N))
110+
i, j = Tuple(I)
111+
x, y = xyd_brusselator[I[1]], xyd_brusselator[I[2]]
112+
ip1, im1 = limit(i + 1, N), limit(i - 1, N)
113+
jp1, jm1 = limit(j + 1, N), limit(j - 1, N)
114+
115+
du[i, j, 1] = α * (u[im1, j, 1] + u[ip1, j, 1] + u[i, jp1, 1] + u[i, jm1, 1] -
116+
4u[i, j, 1]) +
117+
B + u[i, j, 1] ^ 2 * u[i, j, 2] - (A + 1) * u[i, j, 1] +
118+
brusselator_f(x, y)
119+
120+
du[i, j, 2] = α * (u[im1, j, 2] + u[ip1, j, 2] + u[i, jp1, 2] + u[i, jm1, 2] -
121+
4u[i, j, 2]) +
122+
A * u[i, j, 1] - u[i, j, 1] ^ 2 * u[i, j, 2]
123+
end
124+
return nothing
125+
end
126+
127+
return NonlinearProblem(
128+
NonlinearFunction(brusselator_2d_loop; sparsity),
129+
vec(init_brusselator_2d(xyd_brusselator, N)),
130+
(3.4, 1.0, 10.0, step(xyd_brusselator));
131+
kwargs...
132+
)
133+
end
134+
```
135+
136+
```
137+
generate_brusselator_problem (generic function with 1 method)
138+
```
139+
140+
141+
142+
143+
144+
145+
# Jacobian-Free Newton / TR Krylov Methods
146+
147+
In this section, we will benchmark jacobian-free nonlinear solvers with Krylov methods. We
148+
will use preconditioning from `AlgebraicMultigrid.jl` and `IncompleteLU.jl`. Unfortunately,
149+
our ability to use 3rd party software is limited here, since only `Sundials.jl` supports
150+
jacobian-free methods via `:GMRES`.
151+
152+
```julia
153+
using AlgebraicMultigrid, IncompleteLU
154+
155+
incompletelu(W, p = nothing) = ilu(W, τ = 50.0), LinearAlgebra.I
156+
157+
function algebraicmultigrid(W, p = nothing)
158+
return aspreconditioner(ruge_stuben(convert(AbstractMatrix, W))), LinearAlgebra.I
159+
end
160+
161+
function algebraicmultigrid_jacobi(W, p = nothing)
162+
A = convert(AbstractMatrix, W)
163+
Dinv = 1.0 ./ diag(A) # PETSc-style Jacobi: inverse of diagonal
164+
smoother = AlgebraicMultigrid.Jacobi(Dinv)
165+
166+
Pl = aspreconditioner(AlgebraicMultigrid.ruge_stuben(
167+
A,
168+
presmoother = smoother,
169+
postsmoother = smoother
170+
))
171+
return Pl, LinearAlgebra.I
172+
end
173+
174+
Ns = 2 .^ (2:7)
175+
krylov_dim = 1000
176+
177+
solvers_scaling_jacobian_free = [
178+
(; pkg = :nonlinearsolve, name = "Newton Krylov", alg = NewtonRaphson(; linsolve = KrylovJL_GMRES())),
179+
(; pkg = :nonlinearsolve, name = "Newton Krylov (ILU)", alg = NewtonRaphson(; linsolve = KrylovJL_GMRES(; precs = incompletelu), concrete_jac = true)),
180+
(; pkg = :nonlinearsolve, name = "Newton Krylov (AMG)", alg = NewtonRaphson(; linsolve = KrylovJL_GMRES(; precs = algebraicmultigrid), concrete_jac = true)),
181+
(; pkg = :nonlinearsolve, name = "Newton Krylov (AMG Jacobi)", alg = NewtonRaphson(; linsolve = KrylovJL_GMRES(; precs = algebraicmultigrid_jacobi), concrete_jac = true)),
182+
183+
(; pkg = :nonlinearsolve, name = "TR Krylov", alg = TrustRegion(; linsolve = KrylovJL_GMRES())),
184+
(; pkg = :nonlinearsolve, name = "TR Krylov (ILU)", alg = TrustRegion(; linsolve = KrylovJL_GMRES(; precs = incompletelu), concrete_jac = true)),
185+
(; pkg = :nonlinearsolve, name = "TR Krylov (AMG)", alg = TrustRegion(; linsolve = KrylovJL_GMRES(; precs = algebraicmultigrid), concrete_jac = true)),
186+
(; pkg = :nonlinearsolve, name = "TR Krylov (AMG Jacobi)", alg = TrustRegion(; linsolve = KrylovJL_GMRES(; precs = algebraicmultigrid_jacobi), concrete_jac = true)),
187+
188+
(; pkg = :wrapper, name = "Newton Krylov [Sundials]", alg = KINSOL(; linear_solver = :GMRES, maxsetupcalls=1, krylov_dim)),
189+
190+
(; pkg = :wrapper, name = "Newton Krylov [PETSc]", alg = PETScSNES(; snes_type = "newtonls", snes_linesearch_type = "basic", ksp_type = "gmres", snes_mf = true, ksp_gmres_restart = krylov_dim)),
191+
(; pkg = :wrapper, name = "Newton Krylov (ILU) [PETSc]", alg = PETScSNES(; snes_type = "newtonls", snes_linesearch_type = "basic", ksp_type = "gmres", pc_type = "ilu", ksp_gmres_restart = krylov_dim, pc_factor_levels = 0, pc_factor_drop_tolerance = 50.0)),
192+
(; pkg = :wrapper, name = "Newton Krylov (AMG) [PETSc]", alg = PETScSNES(; snes_type = "newtonls", snes_linesearch_type = "basic", ksp_type = "gmres", pc_type = "gamg", ksp_gmres_restart = krylov_dim)),
193+
(; pkg = :wrapper, name = "Newton Krylov (AMG Jacobi) [PETSc]", alg = PETScSNES(; snes_type = "newtonls", snes_linesearch_type = "basic", ksp_type = "gmres", pc_type = "gamg", mg_levels_ksp_type = "richardson", mg_levels_pc_type = "jacobi", ksp_gmres_restart = krylov_dim)),
194+
195+
(; pkg = :wrapper, name = "TR Krylov (Not Matrix Free) [PETSc]", alg = PETScSNES(; snes_type = "newtontr", ksp_type = "gmres", ksp_gmres_restart = krylov_dim)),
196+
(; pkg = :wrapper, name = "TR Krylov (ILU) [PETSc]", alg = PETScSNES(; snes_type = "newtontr", ksp_type = "gmres", pc_type = "ilu", ksp_gmres_restart = krylov_dim, pc_factor_levels = 0, pc_factor_drop_tolerance = 50.0)),
197+
(; pkg = :wrapper, name = "TR Krylov (AMG) [PETSc]", alg = PETScSNES(; snes_type = "newtontr", ksp_type = "gmres", pc_type = "gamg", ksp_gmres_restart = krylov_dim)),
198+
(; pkg = :wrapper, name = "TR Krylov (AMG Jacobi) [PETSc]", alg = PETScSNES(; snes_type = "newtontr", ksp_type = "gmres", pc_type = "gamg", mg_levels_ksp_type = "richardson", mg_levels_pc_type = "jacobi", ksp_gmres_restart = krylov_dim)),
199+
]
200+
201+
gc_disabled = false
202+
runtimes_scaling = fill(-1.0, length(solvers_scaling_jacobian_free), length(Ns))
203+
204+
for (j, solver) in enumerate(solvers_scaling_jacobian_free)
205+
alg = solver.alg
206+
name = solver.name
207+
208+
if !gc_disabled && alg isa PETScSNES
209+
GC.enable(false)
210+
global gc_disabled = true
211+
@info "Disabling GC for $(name)"
212+
end
213+
214+
for (i, N) in enumerate(Ns)
215+
prob = generate_brusselator_problem(N; sparsity = TracerSparsityDetector())
216+
217+
if (j > 1 && runtimes_scaling[j - 1, i] == -1)
218+
# The last benchmark failed so skip this too
219+
runtimes_scaling[j, i] = NaN
220+
@warn "$(name): Would Have Timed out"
221+
else
222+
function benchmark_function()
223+
termination_condition = (alg isa PETScSNES || alg isa KINSOL) ?
224+
nothing :
225+
NonlinearSolveBase.AbsNormTerminationMode(Base.Fix1(maximum, abs))
226+
# PETSc doesn't converge properly
227+
tol = alg isa PETScSNES ? 1e-6 : 1e-4
228+
sol = solve(prob, alg; abstol=tol, reltol=tol,
229+
linsolve_kwargs = (; abstol = 1e-8, reltol = 1e-8),
230+
termination_condition)
231+
if SciMLBase.successful_retcode(sol) || norm(sol.resid, Inf) 1e-4
232+
runtimes_scaling[j, i] = @belapsed solve($prob, $alg;
233+
abstol=$tol, reltol=$tol,
234+
linsolve_kwargs = (; abstol = 1e-8, reltol = 1e-8),
235+
termination_condition=$termination_condition)
236+
else
237+
runtimes_scaling[j, i] = NaN
238+
end
239+
@info "$(name): $(runtimes_scaling[j, i]) | $(norm(sol.resid, Inf)) | $(sol.retcode)"
240+
end
241+
242+
timeout(benchmark_function, 600)
243+
244+
if runtimes_scaling[j, i] == -1
245+
@warn "$(name): Timed out"
246+
runtimes_scaling[j, i] = NaN
247+
end
248+
end
249+
end
250+
251+
println()
252+
end
253+
```
254+
255+
256+
257+
258+
Plot the results.
259+
260+
```julia
261+
fig = begin
262+
ASPECT_RATIO = 0.7
263+
WIDTH = 1200
264+
HEIGHT = round(Int, WIDTH * ASPECT_RATIO)
265+
STROKEWIDTH = 2.5
266+
267+
successful_solvers = map(x -> any(isfinite, x), eachrow(runtimes_scaling))
268+
solvers_scaling_jacobian_free = solvers_scaling_jacobian_free[successful_solvers]
269+
runtimes_scaling = runtimes_scaling[successful_solvers, :]
270+
271+
cycle = Cycle([:marker], covary = true)
272+
colors = cgrad(:tableau_20, length(solvers_scaling_jacobian_free); categorical = true)
273+
theme = Theme(Lines = (cycle = cycle,), Scatter = (cycle = cycle,))
274+
LINESTYLES = Dict(
275+
:none => :solid,
276+
:amg => :dot,
277+
:amg_jacobi => :dash,
278+
:ilu => :dashdot,
279+
)
280+
281+
Ns_ = Ns .^ 2 .* 2
282+
283+
with_theme(theme) do
284+
fig = Figure(; size = (WIDTH, HEIGHT))
285+
286+
ax = Axis(fig[1, 1:2], ylabel = L"Time ($s$)", xlabel = L"Problem Size ($N$)",
287+
xscale = log2, yscale = log2, xlabelsize = 22, ylabelsize = 22,
288+
xticklabelsize = 20, yticklabelsize = 20, xtickwidth = STROKEWIDTH,
289+
ytickwidth = STROKEWIDTH, spinewidth = STROKEWIDTH)
290+
291+
idxs = get_ordering(runtimes_scaling)
292+
293+
ls, scs, labels = [], [], []
294+
for (i, solver) in zip(idxs, solvers_scaling_jacobian_free[idxs])
295+
all(isnan, runtimes_scaling[i, :]) && continue
296+
precon = occursin("AMG Jacobi", solver.name) ? :amg_jacobi : occursin("AMG", solver.name) ? :amg : occursin("ILU", solver.name) ? :ilu : :none
297+
linestyle = LINESTYLES[precon]
298+
l = lines!(Ns_, runtimes_scaling[i, :]; linewidth = 5, color = colors[i],
299+
linestyle)
300+
sc = scatter!(Ns_, runtimes_scaling[i, :]; markersize = 16, strokewidth = 2,
301+
color = colors[i])
302+
push!(ls, l)
303+
push!(scs, sc)
304+
push!(labels, solver.name)
305+
end
306+
307+
axislegend(ax, [[l, sc] for (l, sc) in zip(ls, scs)], labels,
308+
"Successful Solvers";
309+
framevisible=true, framewidth = STROKEWIDTH, orientation = :vertical,
310+
titlesize = 20, labelsize = 16, position = :lt, nbanks = 2,
311+
tellheight = true, tellwidth = false, patchsize = (40.0f0, 20.0f0))
312+
313+
axislegend(ax, [
314+
LineElement(; linestyle = :solid, linewidth = 5),
315+
LineElement(; linestyle = :dot, linewidth = 5),
316+
LineElement(; linestyle = :dash, linewidth = 5),
317+
LineElement(; linestyle = :dashdot, linewidth = 5),
318+
], ["No Preconditioning", "AMG", "AMG Jacobi", "Incomplete LU"],
319+
"Preconditioning"; framevisible=true, framewidth = STROKEWIDTH,
320+
orientation = :vertical, titlesize = 20, labelsize = 16,
321+
tellheight = true, tellwidth = true, patchsize = (40.0f0, 20.0f0),
322+
position = :rb)
323+
324+
fig[0, :] = Label(fig,
325+
"Brusselator 2D: Scaling of Jacobian-Free Nonlinear Solvers with Problem Size",
326+
fontsize = 24, tellwidth = false, font = :bold)
327+
328+
return fig
329+
end
330+
end
331+
```
332+
333+
![](figures/bruss_krylov_5_1.png)
334+
335+
```julia
336+
save("brusselator_krylov_methods_scaling.svg", fig)
337+
```
338+
339+
```
340+
CairoMakie.Screen{SVG}
341+
```
342+
343+
272 KB
Loading
51.4 KB
Loading
129 KB
Loading
492 KB
Loading
64.2 KB
Loading
153 KB
Loading
150 KB
Loading
142 KB
Loading

0 commit comments

Comments
 (0)