Skip to content

Commit f69add7

Browse files
Multirate: add implicit MRI-GARK-ESDIRK46a (order 4) (#961) (#3886)
Co-authored-by: Christopher Rackauckas <accounts@chrisrackauckas.com>
1 parent 0be95df commit f69add7

8 files changed

Lines changed: 230 additions & 15 deletions

File tree

lib/OrdinaryDiffEqMultirate/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "OrdinaryDiffEqMultirate"
22
uuid = "d4b830b4-ac80-426b-8507-16693d424963"
33
authors = ["singhharsh1708 <hs1663531@gmail.com>"]
4-
version = "2.6.1"
4+
version = "2.7.0"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

lib/OrdinaryDiffEqMultirate/src/OrdinaryDiffEqMultirate.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ include("multirate_caches.jl")
2929
include("multirate_perform_step.jl")
3030

3131
export MREEF, MRAB, MRIGARKERK22a, MRIGARKERK22b, MRIGARKERK33a, MRIGARKERK45a,
32-
MRIGARKIRK21a, MRIGARKESDIRK34a, MIS
32+
MRIGARKIRK21a, MRIGARKESDIRK34a, MRIGARKESDIRK46a, MIS
3333

3434
end

lib/OrdinaryDiffEqMultirate/src/alg_utils.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,15 @@ end
2929

3030
alg_order(::MRIGARKIRK21a) = 2
3131
alg_order(::MRIGARKESDIRK34a) = 3
32-
isfsal(::Union{MRIGARKIRK21a, MRIGARKESDIRK34a}) = false
32+
alg_order(::MRIGARKESDIRK46a) = 4
33+
isfsal(::MRIGARKImplicitAlg) = false
3334

34-
function prepare_alg(
35-
alg::Union{MRIGARKIRK21a, MRIGARKESDIRK34a}, u0::AbstractArray, p, prob
36-
)
35+
function prepare_alg(alg::MRIGARKImplicitAlg, u0::AbstractArray, p, prob)
3736
alg.m >= 1 || throw(ArgumentError("$(nameof(typeof(alg))): `m` must be ≥ 1"))
3837
return alg
3938
end
4039

41-
nlsolve_f(f, ::Union{MRIGARKIRK21a, MRIGARKESDIRK34a}) = f isa SplitFunction ? f.f2 : f
40+
nlsolve_f(f, ::MRIGARKImplicitAlg) = f isa SplitFunction ? f.f2 : f
4241

4342
alg_order(::MIS) = 2
4443
isfsal(::MIS) = false

lib/OrdinaryDiffEqMultirate/src/algorithms.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,55 @@ function MRIGARKESDIRK34a(;
267267
return MRIGARKESDIRK34a(m, linsolve, nlsolve, autodiff, _unwrap_val(concrete_jac))
268268
end
269269

270+
@doc generic_solver_docstring(
271+
"Multirate Infinitesimal GARK — 4th-order solve-decoupled implicit (MRI-GARK-ESDIRK46a).
272+
273+
Solves a SplitODE `du/dt = f1(u,t) + f2(u,t)` where `f1` is the fast component
274+
and `f2` is the slow component (SciML convention). 11-stage, 4th-order
275+
solve-decoupled implicit MRI-GARK method of Sandu 2019: the fast component is
276+
integrated over five sub-intervals with `m` explicit-RK4 inner micro-steps, each
277+
followed by a `Δc = 0` stage that applies an ESDIRK slow correction requiring a
278+
nonlinear solve in the slow rate `f2`. The five implicit stages share the common
279+
diagonal coefficient `1/4`, so a single Jacobian/`W` is reused. Unlike the lower
280+
order members of the family this method carries a 3rd-order embedded pair, giving
281+
a proper local error estimate for adaptive stepping.",
282+
"MRIGARKESDIRK46a",
283+
"Multirate infinitesimal GARK solve-decoupled implicit method.",
284+
"""@article{sandu2019class,
285+
title={A class of multirate infinitesimal {GARK} methods},
286+
author={Sandu, Adrian},
287+
journal={SIAM Journal on Numerical Analysis},
288+
volume={57},
289+
number={5},
290+
pages={2300--2327},
291+
year={2019}}""",
292+
"""
293+
- `m`: number of inner RK4 micro-steps per fast stage.
294+
""",
295+
"""
296+
m::Int,
297+
"""
298+
)
299+
struct MRIGARKESDIRK46a{AD, F, F2, CJ} <: OrdinaryDiffEqNewtonAdaptiveAlgorithm
300+
m::Int
301+
linsolve::F
302+
nlsolve::F2
303+
autodiff::AD
304+
concrete_jac::CJ
305+
end
306+
307+
function MRIGARKESDIRK46a(;
308+
m::Int, autodiff = AutoForwardDiff(), concrete_jac = nothing,
309+
linsolve = nothing, nlsolve = NLNewton()
310+
)
311+
autodiff = _fixup_ad(autodiff)
312+
return MRIGARKESDIRK46a(m, linsolve, nlsolve, autodiff, _unwrap_val(concrete_jac))
313+
end
314+
315+
const MRIGARKImplicitAlg = Union{
316+
MRIGARKIRK21a, MRIGARKESDIRK34a, MRIGARKESDIRK46a,
317+
}
318+
270319
@doc generic_solver_docstring(
271320
"Multirate Infinitesimal Step (MIS).
272321

lib/OrdinaryDiffEqMultirate/src/multirate_caches.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ end
168168
kk::Vector{uType}
169169
z::Vector{uType}
170170
fS::Vector{rateType}
171+
zemb::uType
171172
fsalfirst::rateType
172173
k::rateType
173174
nlsolver::N
@@ -178,8 +179,6 @@ get_fsalfirstlast(cache::MRIGARKImplicitCache, u) = (cache.fsalfirst, cache.k)
178179

179180
_mrigark_impl_γ(tab) = tab.γ0[findfirst(!iszero, tab.γ0)]
180181

181-
const MRIGARKImplicitAlg = Union{MRIGARKIRK21a, MRIGARKESDIRK34a}
182-
183182
function alg_cache(
184183
alg::MRIGARKImplicitAlg, u, rate_prototype,
185184
::Type{uEltypeNoUnits}, ::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits},
@@ -203,10 +202,12 @@ function alg_cache(
203202
kk = [zero(u) for _ in 1:(tab.q)]
204203
z = [zero(u) for _ in 1:(s + 1)]
205204
fS = [zero(rate_prototype) for _ in 1:s]
205+
zemb = zero(u)
206206
fsalfirst = zero(rate_prototype)
207207
k = zero(rate_prototype)
208208
return MRIGARKImplicitCache(
209-
u, uprev, tmp, atmp, v, vtmp, f1eval, kk, z, fS, fsalfirst, k, nlsolver, tab
209+
u, uprev, tmp, atmp, v, vtmp, f1eval, kk, z, fS, zemb,
210+
fsalfirst, k, nlsolver, tab
210211
)
211212
end
212213

lib/OrdinaryDiffEqMultirate/src/multirate_perform_step.jl

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,8 @@ end
547547

548548
function perform_step!(integrator, cache::MRIGARKImplicitCache, repeat_step = false)
549549
(; t, dt, uprev, u, f, p) = integrator
550-
(; tmp, atmp, z, fS, nlsolver, tab) = cache
551-
(; Δc, W0, W1, γ0, q) = tab
550+
(; tmp, atmp, z, fS, zemb, nlsolver, tab) = cache
551+
(; Δc, W0, W1, Wemb0, Wemb1, γ0, q) = tab
552552
alg = unwrap_alg(integrator, true)
553553
m = alg.m
554554
s = length(Δc)
@@ -585,7 +585,16 @@ function perform_step!(integrator, cache::MRIGARKImplicitCache, repeat_step = fa
585585
@.. broadcast = false u = z[s + 1]
586586

587587
return if integrator.opts.adaptive
588-
@.. broadcast = false tmp = u - z[s]
588+
if isempty(Wemb0)
589+
@.. broadcast = false tmp = u - z[s]
590+
else
591+
w1emb = isempty(Wemb1) ? nothing : Wemb1
592+
_mrigark_substage!(
593+
zemb, z[s], cache, f, p, t, dt, Δc[s], cprev - Δc[s], m, q,
594+
Wemb0, w1emb, fS, s, stats
595+
)
596+
@.. broadcast = false tmp = u - zemb
597+
end
589598
calculate_residuals!(
590599
atmp, tmp, uprev, u,
591600
integrator.opts.abstol, integrator.opts.reltol,
@@ -600,7 +609,7 @@ end
600609
)
601610
(; t, dt, uprev, f, p) = integrator
602611
(; nlsolver, tab) = cache
603-
(; Δc, W0, W1, γ0, q) = tab
612+
(; Δc, W0, W1, Wemb0, Wemb1, γ0, q) = tab
604613
alg = unwrap_alg(integrator, true)
605614
m = alg.m
606615
s = length(Δc)
@@ -640,7 +649,16 @@ end
640649
integrator.u = z[s + 1]
641650

642651
if integrator.opts.adaptive
643-
utilde = @.. broadcast = false integrator.u - z[s]
652+
utilde = if isempty(Wemb0)
653+
@.. broadcast = false integrator.u - z[s]
654+
else
655+
w1emb = isempty(Wemb1) ? nothing : Wemb1
656+
zemb = _mrigark_substage(
657+
z[s], f, p, t, dt, Δc[s], cprev - Δc[s], m, q,
658+
Wemb0, w1emb, fS, s, stats
659+
)
660+
@.. broadcast = false integrator.u - zemb
661+
end
644662
atmp = calculate_residuals(
645663
utilde, uprev, integrator.u,
646664
integrator.opts.abstol, integrator.opts.reltol,

lib/OrdinaryDiffEqMultirate/src/multirate_tableaus.jl

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,90 @@ function MRIGARKESDIRK34aTableau(::Type{T}) where {T}
109109
return MRIGARKTableau{T}(Δc, W0, W1, T[], T[], γ0, 3)
110110
end
111111

112+
# Stage 11 carries an all-zero coupling row: the method is stiffly accurate, so the
113+
# final stage repeats stage 10. It is kept because the embedding row couples f2 at
114+
# that stage value.
115+
function MRIGARKESDIRK46aTableau(::Type{T}) where {T}
116+
γ = T(1 // 4)
117+
Δc = T[1 // 5, 0, 1 // 5, 0, 1 // 5, 0, 1 // 5, 0, 1 // 5, 0, 0]
118+
W0 = zeros(T, 11, 11)
119+
W0[1, 1] = 1 // 5
120+
W0[2, 1] = -1 // 4
121+
W0[3, 1] = 1771023115159 // 1929363690800
122+
W0[3, 3] = -1385150376999 // 1929363690800
123+
W0[4, 1] = 914009 // 345800
124+
W0[4, 3] = -1000459 // 345800
125+
W0[5, 1] = 18386293581909 // 36657910125200
126+
W0[5, 3] = 5506531089 // 80566835440
127+
W0[5, 5] = -178423463189 // 482340922700
128+
W0[6, 1] = 36036097 // 8299200
129+
W0[6, 3] = 4621 // 118560
130+
W0[6, 5] = -38434367 // 8299200
131+
W0[7, 1] = -247809665162987 // 146631640500800
132+
W0[7, 3] = 10604946373579 // 14663164050080
133+
W0[7, 5] = 10838126175385 // 5865265620032
134+
W0[7, 7] = -24966656214317 // 36657910125200
135+
W0[8, 1] = 38519701 // 11618880
136+
W0[8, 3] = 10517363 // 9682400
137+
W0[8, 5] = -23284701 // 19364800
138+
W0[8, 7] = -10018609 // 2904720
139+
W0[9, 1] = -52907807977903 // 33838070884800
140+
W0[9, 3] = 74846944529257 // 73315820250400
141+
W0[9, 5] = 365022522318171 // 146631640500800
142+
W0[9, 7] = -20513210406809 // 109973730375600
143+
W0[9, 9] = -2918009798 // 1870301537
144+
W0[10, 1] = 19 // 100
145+
W0[10, 3] = -73 // 300
146+
W0[10, 5] = 127 // 300
147+
W0[10, 7] = 127 // 300
148+
W0[10, 9] = -313 // 300
149+
W1 = zeros(T, 11, 11)
150+
W1[3, 1] = -1674554930619 // 964681845400
151+
W1[3, 3] = 1674554930619 // 964681845400
152+
W1[4, 1] = -1007739 // 172900
153+
W1[4, 3] = 1007739 // 172900
154+
W1[5, 1] = -8450070574289 // 18328955062600
155+
W1[5, 3] = -39429409169 // 40283417720
156+
W1[5, 5] = 173621393067 // 120585230675
157+
W1[6, 1] = -122894383 // 16598400
158+
W1[6, 3] = 14501 // 237120
159+
W1[6, 5] = 121879313 // 16598400
160+
W1[7, 1] = 32410002731287 // 15434909526400
161+
W1[7, 3] = -46499276605921 // 29326328100160
162+
W1[7, 5] = -34914135774643 // 11730531240064
163+
W1[7, 7] = 45128506783177 // 18328955062600
164+
W1[8, 1] = -128357303 // 23237760
165+
W1[8, 3] = -35433927 // 19364800
166+
W1[8, 5] = 71038479 // 38729600
167+
W1[8, 7] = 8015933 // 1452360
168+
W1[9, 1] = 136721604296777 // 67676141769600
169+
W1[9, 3] = -349632444539303 // 146631640500800
170+
W1[9, 5] = -1292744859249609 // 293263281001600
171+
W1[9, 7] = 8356250416309 // 54986865187800
172+
W1[9, 9] = 17282943803 // 3740603074
173+
W1[10, 1] = 3 // 25
174+
W1[10, 3] = -29 // 300
175+
W1[10, 5] = 71 // 300
176+
W1[10, 7] = 71 // 300
177+
W1[10, 9] = -149 // 300
178+
Wemb0 = zeros(T, 11)
179+
Wemb0[1] = -1 // 4
180+
Wemb0[3] = 5595 // 8804
181+
Wemb0[5] = -2445 // 8804
182+
Wemb0[7] = -4225 // 8804
183+
Wemb0[9] = 2205 // 4402
184+
Wemb0[11] = -567 // 4402
185+
γ0 = T[0, γ, 0, γ, 0, γ, 0, γ, 0, γ, 0]
186+
return MRIGARKTableau{T}(Δc, W0, W1, Wemb0, T[], γ0, 4)
187+
end
188+
112189
mri_gark_tableau(::MRIGARKERK22a, ::Type{T}) where {T} = MRIGARKERK22aTableau(T)
113190
mri_gark_tableau(::MRIGARKERK22b, ::Type{T}) where {T} = MRIGARKERK22bTableau(T)
114191
mri_gark_tableau(::MRIGARKERK33a, ::Type{T}) where {T} = MRIGARKERK33aTableau(T)
115192
mri_gark_tableau(::MRIGARKERK45a, ::Type{T}) where {T} = MRIGARKERK45aTableau(T)
116193
mri_gark_tableau(::MRIGARKIRK21a, ::Type{T}) where {T} = MRIGARKIRK21aTableau(T)
117194
mri_gark_tableau(::MRIGARKESDIRK34a, ::Type{T}) where {T} = MRIGARKESDIRK34aTableau(T)
195+
mri_gark_tableau(::MRIGARKESDIRK46a, ::Type{T}) where {T} = MRIGARKESDIRK46aTableau(T)
118196

119197
# ── MIS: multirate infinitesimal step ─────────────────────────────────────────
120198

lib/OrdinaryDiffEqMultirate/test/mri_gark_tests.jl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,73 @@ end
294294
@test sim.𝒪est[:l∞] 3 atol = 0.3
295295
end
296296
end
297+
298+
@testset "MRIGARKESDIRK46a" begin
299+
@testset "Construction" begin
300+
@test MRIGARKESDIRK46a(m = 10).m == 10
301+
@test MRIGARKESDIRK46a(m = 20).m == 20
302+
end
303+
304+
@testset "Scalar out-of-place" begin
305+
prob = SplitODEProblem(
306+
(u, p, t) -> -0.9 * u, (u, p, t) -> -0.1 * u, 1.0, (0.0, 1.0)
307+
)
308+
sol = solve(prob, MRIGARKESDIRK46a(m = 10), dt = 0.05, adaptive = false)
309+
@test abs(sol.u[end] - exp(-1.0)) < 1.0e-3
310+
311+
sol_a = solve(prob, MRIGARKESDIRK46a(m = 10), reltol = 1.0e-6, abstol = 1.0e-6)
312+
@test abs(sol_a.u[end] - exp(-1.0)) < 1.0e-3
313+
end
314+
315+
@testset "Vector in-place" begin
316+
f1!(du, u, p, t) = (du .= -0.9 .* u)
317+
f2!(du, u, p, t) = (du .= -0.1 .* u)
318+
u0 = [1.0, 2.0, 3.0]
319+
prob = SplitODEProblem(f1!, f2!, u0, (0.0, 1.0))
320+
321+
sol = solve(prob, MRIGARKESDIRK46a(m = 10), dt = 0.05, adaptive = false)
322+
@test norm(sol.u[end] - u0 .* exp(-1.0)) < 1.0e-2
323+
324+
sol_a = solve(prob, MRIGARKESDIRK46a(m = 10), reltol = 1.0e-6, abstol = 1.0e-6)
325+
@test norm(sol_a.u[end] - u0 .* exp(-1.0)) < 1.0e-3
326+
end
327+
328+
@testset "Stiff slow component" begin
329+
analytic(u0, p, t) = u0 * exp(-51t)
330+
prob = SplitODEProblem(
331+
SplitFunction(
332+
(u, p, t) -> -u, (u, p, t) -> -50u; analytic = analytic
333+
),
334+
1.0, (0.0, 1.0)
335+
)
336+
sol = solve(prob, MRIGARKESDIRK46a(m = 4), dt = 0.02, adaptive = false)
337+
@test abs(sol.u[end] - exp(-51.0)) < 1.0e-3
338+
@test solve(prob, MRIGARKESDIRK46a(m = 4), reltol = 1.0e-6, abstol = 1.0e-8).retcode ==
339+
ReturnCode.Success
340+
end
341+
342+
@testset "Embedded error estimate" begin
343+
# The final coupling row is zero, so `u - z[s]` degenerates to 0 here: the
344+
# step size can only respond to the tolerance via the tableau embedding.
345+
f1!(du, u, p, t) = (du .= -2 .* u)
346+
f2!(du, u, p, t) = (du .= -u)
347+
prob = SplitODEProblem(f1!, f2!, [1.0], (0.0, 1.0))
348+
loose = solve(prob, MRIGARKESDIRK46a(m = 8), reltol = 1.0e-4, abstol = 1.0e-4)
349+
tight = solve(prob, MRIGARKESDIRK46a(m = 8), reltol = 1.0e-8, abstol = 1.0e-8)
350+
@test tight.stats.naccept > loose.stats.naccept
351+
@test abs(tight.u[end][1] - exp(-3.0)) < abs(loose.u[end][1] - exp(-3.0))
352+
end
353+
354+
@testset "Convergence" begin
355+
analytic(u0, p, t) = u0 * exp(-3t)
356+
prob = SplitODEProblem(
357+
SplitFunction(
358+
(u, p, t) -> -2u, (u, p, t) -> -u; analytic = analytic
359+
),
360+
1.0, (0.0, 1.0)
361+
)
362+
dts = 1 ./ 2 .^ (8:-1:4)
363+
sim = test_convergence(dts, prob, MRIGARKESDIRK46a(m = 8))
364+
@test sim.𝒪est[:l∞] 4 atol = 0.3
365+
end
366+
end

0 commit comments

Comments
 (0)