Skip to content

Commit 075cc7c

Browse files
Fold RK46NL into the generic LowStorageRK2N dispatch
RK46NL was the last plain-2N method still carrying a bespoke constant cache struct, mutable cache struct, and four hand-unrolled initialize!/perform_step! methods. Its recurrence is the same Williamson form the shared LowStorageRK2N kernels already run, so the tableau moves into a RK46NLConstantCache function returning a LowStorageRK2NConstantCache with unchanged coefficients, RK46NL joins the LowStorageRK2NAlgorithm union, and the bespoke caches and kernels are deleted. The struct gains the williamson_condition keyword (default false) that the shared alg_cache reads, and isfsal/uses_uprev are declared false to match the rest of the family. The generic in-place kernel re-evaluates f at the start of each step instead of using FSAL storage, keeping the per-step f-call count at six. The new field is fourth, so positional construction now takes four arguments, the same break the nine sibling 2N methods already took when they gained the keyword. Fixed-dt end states are bit-identical to the removed implementation for in-place problems and for out-of-place problems whose state is an array. Out-of-place problems with a scalar state differ in the last bits: 3 ulp after 128 steps of u' = 1.01u, 1 ulp after 256 steps of a nonlinear scalar problem, roughly four thousand times below the method's own discretization error there. Both kernels expand to the same muladd(dt, k, A*tmp), but the straight-line code folds the other product into the fused multiply-add, and only scalar states are affected because muladd over arrays never fuses. RK46NL gains the williamson_condition testset the nine siblings already have. Version goes to 3.4.0 for the new public keyword.
1 parent 0be95df commit 075cc7c

6 files changed

Lines changed: 89 additions & 165 deletions

File tree

lib/OrdinaryDiffEqLowStorageRK/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "OrdinaryDiffEqLowStorageRK"
22
uuid = "b0944070-b475-4768-8dec-fb6eb410534d"
33
authors = ["ParamThakkar123 <paramthakkar864@gmail.com>"]
4-
version = "3.3.0"
4+
version = "3.4.0"
55

66
[deps]
77
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"

lib/OrdinaryDiffEqLowStorageRK/src/alg_utils.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ alg_order(alg::SHLDDRK_2N) = 4
6060
alg_order(alg::SHLDDRK52) = 2
6161

6262
isfsal(alg::ORK256) = false
63+
isfsal(alg::RK46NL) = false
6364
isfsal(alg::CarpenterKennedy2N54) = false
6465
isfsal(alg::DGLDDRK84_F) = false
6566
isfsal(alg::DGLDDRK73_C) = false
@@ -73,6 +74,7 @@ isfsal(alg::NDBLSRK124) = false
7374
isfsal(alg::NDBLSRK144) = false
7475

7576
uses_uprev(alg::ORK256, adaptive::Bool) = false
77+
uses_uprev(alg::RK46NL, adaptive::Bool) = false
7678
uses_uprev(alg::SHLDDRK64, adaptive::Bool) = false
7779
uses_uprev(alg::CarpenterKennedy2N54, adaptive::Bool) = false
7880
uses_uprev(alg::NDBLSRK124, adaptive::Bool) = false

lib/OrdinaryDiffEqLowStorageRK/src/algorithms.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,16 @@ end
195195
@doc explicit_rk_docstring(
196196
"6-stage, fourth order low-stage, low-dissipation, low-dispersion scheme.
197197
Fixed timestep only.", "RK46NL",
198-
references = "Julien Berland, Christophe Bogey, Christophe Bailly. Low-Dissipation and Low-Dispersion Fourth-Order Runge-Kutta Algorithm. Computers & Fluids, 35(10), pp 1459-1463, 2006. doi: https://doi.org/10.1016/j.compfluid.2005.04.003"
198+
references = "Julien Berland, Christophe Bogey, Christophe Bailly. Low-Dissipation and Low-Dispersion Fourth-Order Runge-Kutta Algorithm. Computers & Fluids, 35(10), pp 1459-1463, 2006. doi: https://doi.org/10.1016/j.compfluid.2005.04.003",
199+
extra_keyword_description = """- `williamson_condition`: allows for an optimization that allows fusing broadcast expressions with the function call `f`. However, it only works for `Array` types.
200+
""",
201+
extra_keyword_default = "williamson_condition = false"
199202
)
200203
Base.@kwdef struct RK46NL{StageLimiter, StepLimiter, Thread} <: OrdinaryDiffEqAlgorithm
201204
stage_limiter!::StageLimiter = trivial_limiter!
202205
step_limiter!::StepLimiter = trivial_limiter!
203206
thread::Thread = Serial()
207+
williamson_condition::Bool = false
204208
end
205209

206210
@doc explicit_rk_docstring(

lib/OrdinaryDiffEqLowStorageRK/src/low_storage_rk_caches.jl

Lines changed: 23 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -47,81 +47,30 @@ function ORK256ConstantCache(::Type{T}, ::Type{T2}) where {T, T2}
4747
return LowStorageRK2NConstantCache(A2end, B1, B2end, c2end)
4848
end
4949

50-
function alg_cache(
51-
alg::RK46NL, u, rate_prototype, ::Type{uEltypeNoUnits},
52-
::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t,
53-
dt, reltol, p, calck,
54-
::Val{false}, verbose
55-
) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits}
56-
return RK46NLConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
57-
end
50+
function RK46NLConstantCache(::Type{T}, ::Type{T2}) where {T, T2}
51+
A2 = convert(T, -0.737101392796)
52+
A3 = convert(T, -1.634740794343)
53+
A4 = convert(T, -0.74473900378)
54+
A5 = convert(T, -1.469897351522)
55+
A6 = convert(T, -2.813971388035)
56+
A2end = (A2, A3, A4, A5, A6)
5857

59-
struct RK46NLConstantCache{T, T2} <: OrdinaryDiffEqConstantCache
60-
α2::T
61-
α3::T
62-
α4::T
63-
α5::T
64-
α6::T
65-
β1::T
66-
β2::T
67-
β3::T
68-
β4::T
69-
β5::T
70-
β6::T
71-
c2::T2
72-
c3::T2
73-
c4::T2
74-
c5::T2
75-
c6::T2
76-
77-
function RK46NLConstantCache(::Type{T}, ::Type{T2}) where {T, T2}
78-
α2 = T(-0.737101392796)
79-
α3 = T(-1.634740794343)
80-
α4 = T(-0.74473900378)
81-
α5 = T(-1.469897351522)
82-
α6 = T(-2.813971388035)
83-
β1 = T(0.032918605146)
84-
β2 = T(0.8232569982)
85-
β3 = T(0.3815309489)
86-
β4 = T(0.200092213184)
87-
β5 = T(1.718581042715)
88-
β6 = T(0.27)
89-
c2 = T2(0.032918605146)
90-
c3 = T2(0.249351723343)
91-
c4 = T2(0.466911705055)
92-
c5 = T2(0.582030414044)
93-
c6 = T2(0.847252983783)
94-
return new{T, T2}(α2, α3, α4, α5, α6, β1, β2, β3, β4, β5, β6, c2, c3, c4, c5, c6)
95-
end
96-
end
58+
B1 = convert(T, 0.032918605146)
59+
B2 = convert(T, 0.8232569982)
60+
B3 = convert(T, 0.3815309489)
61+
B4 = convert(T, 0.200092213184)
62+
B5 = convert(T, 1.718581042715)
63+
B6 = convert(T, 0.27)
64+
B2end = (B2, B3, B4, B5, B6)
9765

98-
function alg_cache(
99-
alg::RK46NL, u, rate_prototype, ::Type{uEltypeNoUnits},
100-
::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t,
101-
dt, reltol, p, calck,
102-
::Val{true}, verbose
103-
) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits}
104-
tmp = zero(u)
105-
k = zero(rate_prototype)
106-
fsalfirst = zero(rate_prototype)
107-
tab = RK46NLConstantCache(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits))
108-
return RK46NLCache(
109-
u, uprev, k, tmp, fsalfirst, tab, alg.stage_limiter!, alg.step_limiter!,
110-
alg.thread
111-
)
112-
end
66+
c2 = convert(T2, 0.032918605146)
67+
c3 = convert(T2, 0.249351723343)
68+
c4 = convert(T2, 0.466911705055)
69+
c5 = convert(T2, 0.582030414044)
70+
c6 = convert(T2, 0.847252983783)
71+
c2end = (c2, c3, c4, c5, c6)
11372

114-
@cache struct RK46NLCache{uType, rateType, TabType, StageLimiter, StepLimiter, Thread} <:
115-
LowStorageRKMutableCache
116-
u::uType
117-
uprev::uType
118-
k::rateType
119-
tmp::uType
120-
fsalfirst::rateType
121-
tab::TabType
122-
stage_limiter!::StageLimiter
123-
step_limiter!::StepLimiter
124-
thread::Thread
73+
return LowStorageRK2NConstantCache(A2end, B1, B2end, c2end)
12574
end
12675

12776
function CarpenterKennedy2N54ConstantCache(::Type{T}, ::Type{T2}) where {T, T2}
@@ -4357,11 +4306,12 @@ end
43574306

43584307
# --- LowStorageRK2N ---
43594308
const LowStorageRK2NAlgorithm = Union{
4360-
ORK256, CarpenterKennedy2N54, SHLDDRK64, DGLDDRK73_C, DGLDDRK84_C, DGLDDRK84_F,
4309+
ORK256, RK46NL, CarpenterKennedy2N54, SHLDDRK64, DGLDDRK73_C, DGLDDRK84_C, DGLDDRK84_F,
43614310
NDBLSRK124, NDBLSRK134, NDBLSRK144,
43624311
}
43634312

43644313
_lowstorage_2n_tableau(::ORK256, ::Type{T}, ::Type{T2}) where {T, T2} = ORK256ConstantCache(T, T2)
4314+
_lowstorage_2n_tableau(::RK46NL, ::Type{T}, ::Type{T2}) where {T, T2} = RK46NLConstantCache(T, T2)
43654315
_lowstorage_2n_tableau(::CarpenterKennedy2N54, ::Type{T}, ::Type{T2}) where {T, T2} = CarpenterKennedy2N54ConstantCache(T, T2)
43664316
_lowstorage_2n_tableau(::SHLDDRK64, ::Type{T}, ::Type{T2}) where {T, T2} = SHLDDRK64ConstantCache(T, T2)
43674317
_lowstorage_2n_tableau(::DGLDDRK73_C, ::Type{T}, ::Type{T2}) where {T, T2} = DGLDDRK73_CConstantCache(T, T2)

lib/OrdinaryDiffEqLowStorageRK/src/low_storage_rk_perform_step.jl

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -903,96 +903,6 @@ end
903903
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 1)
904904
end
905905

906-
function initialize!(integrator, cache::RK46NLCache)
907-
(; k, fsalfirst) = cache
908-
909-
integrator.kshortsize = 1
910-
resize!(integrator.k, integrator.kshortsize)
911-
integrator.k[1] = integrator.fsalfirst
912-
integrator.f(integrator.fsalfirst, integrator.uprev, integrator.p, integrator.t) # FSAL for interpolation
913-
return OrdinaryDiffEqCore.increment_nf!(integrator.stats, 1)
914-
end
915-
916-
@muladd function perform_step!(integrator, cache::RK46NLCache, repeat_step = false)
917-
(; t, dt, uprev, u, f, p) = integrator
918-
(; k, fsalfirst, tmp, thread) = cache
919-
stage_limiter! = integrator.opts.stage_limiter!
920-
(; α2, α3, α4, α5, α6, β1, β2, β3, β4, β5, β6, c2, c3, c4, c5, c6) = cache.tab
921-
922-
# u1
923-
@.. broadcast = false thread = thread tmp = dt * fsalfirst
924-
@.. broadcast = false thread = thread u = uprev + β1 * tmp
925-
stage_limiter!(u, integrator, p, t + c2 * dt)
926-
# u2
927-
f(k, u, p, t + c2 * dt)
928-
@.. broadcast = false thread = thread tmp = α2 * tmp + dt * k
929-
@.. broadcast = false thread = thread u = u + β2 * tmp
930-
stage_limiter!(u, integrator, p, t + c3 * dt)
931-
# u3
932-
f(k, u, p, t + c3 * dt)
933-
@.. broadcast = false thread = thread tmp = α3 * tmp + dt * k
934-
@.. broadcast = false thread = thread u = u + β3 * tmp
935-
stage_limiter!(u, integrator, p, t + c4 * dt)
936-
# u4
937-
f(k, u, p, t + c4 * dt)
938-
@.. broadcast = false thread = thread tmp = α4 * tmp + dt * k
939-
@.. broadcast = false thread = thread u = u + β4 * tmp
940-
stage_limiter!(u, integrator, p, t + c5 * dt)
941-
# u5 = u
942-
f(k, u, p, t + c5 * dt)
943-
@.. broadcast = false thread = thread tmp = α5 * tmp + dt * k
944-
@.. broadcast = false thread = thread u = u + β5 * tmp
945-
stage_limiter!(u, integrator, p, t + c6 * dt)
946-
947-
f(k, u, p, t + c6 * dt)
948-
@.. broadcast = false thread = thread tmp = α6 * tmp + dt * k
949-
@.. broadcast = false thread = thread u = u + β6 * tmp
950-
stage_limiter!(u, integrator, p, t + dt)
951-
952-
f(k, u, p, t + dt)
953-
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 6)
954-
end
955-
956-
function initialize!(integrator, cache::RK46NLConstantCache)
957-
integrator.fsalfirst = integrator.f(integrator.uprev, integrator.p, integrator.t) # Pre-start fsal
958-
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 1)
959-
integrator.kshortsize = 1
960-
integrator.k = typeof(integrator.k)(undef, integrator.kshortsize)
961-
962-
# Avoid undefined entries if k is an array of arrays
963-
integrator.fsallast = zero(integrator.fsalfirst)
964-
return integrator.k[1] = integrator.fsalfirst
965-
end
966-
967-
@muladd function perform_step!(integrator, cache::RK46NLConstantCache, repeat_step = false)
968-
(; t, dt, uprev, u, f, p) = integrator
969-
(; α2, α3, α4, α5, α6, β1, β2, β3, β4, β5, β6, c2, c3, c4, c5, c6) = cache
970-
971-
# u1
972-
tmp = dt * integrator.fsalfirst
973-
u = uprev + β1 * tmp
974-
# u2
975-
tmp = α2 * tmp + dt * f(u, p, t + c2 * dt)
976-
u = u + β2 * tmp
977-
# u3
978-
tmp = α3 * tmp + dt * f(u, p, t + c3 * dt)
979-
u = u + β3 * tmp
980-
# u4
981-
tmp = α4 * tmp + dt * f(u, p, t + c4 * dt)
982-
u = u + β4 * tmp
983-
# u5 = u
984-
tmp = α5 * tmp + dt * f(u, p, t + c5 * dt)
985-
u = u + β5 * tmp
986-
# u6
987-
tmp = α6 * tmp + dt * f(u, p, t + c6 * dt)
988-
u = u + β6 * tmp
989-
990-
integrator.fsallast = f(u, p, t + dt) # For interpolation, then FSAL'd
991-
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 6)
992-
integrator.k[1] = integrator.fsalfirst
993-
integrator.u = u
994-
end
995-
996906
function initialize!(integrator, cache::SHLDDRK52ConstantCache)
997907
integrator.kshortsize = 2
998908
integrator.k = typeof(integrator.k)(undef, integrator.kshortsize)

lib/OrdinaryDiffEqLowStorageRK/test/ode_low_storage_rk_tests.jl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,64 @@ end
143143
@test sol_old.u[end] sol_new.u[end]
144144
end
145145

146+
@testset "RK46NL" begin
147+
alg = RK46NL(; williamson_condition = true)
148+
alg2 = RK46NL(; williamson_condition = false)
149+
dts = 1 ./ 2 .^ (7:-1:3)
150+
for prob in test_problems_only_time
151+
sim = test_convergence(dts, prob, alg)
152+
@test sim.𝒪est[:final] OrdinaryDiffEqLowStorageRK.alg_order(alg) atol = testTol
153+
sim = test_convergence(dts, prob, alg2)
154+
@test sim.𝒪est[:final] OrdinaryDiffEqLowStorageRK.alg_order(alg) atol = testTol
155+
end
156+
for prob in test_problems_linear
157+
sim = test_convergence(dts, prob, alg)
158+
@test sim.𝒪est[:final] OrdinaryDiffEqLowStorageRK.alg_order(alg) atol = testTol
159+
sim = test_convergence(dts, prob, alg2)
160+
@test sim.𝒪est[:final] OrdinaryDiffEqLowStorageRK.alg_order(alg) atol = testTol
161+
end
162+
for prob in test_problems_nonlinear
163+
sim = test_convergence(dts, prob, alg)
164+
@test sim.𝒪est[:final] OrdinaryDiffEqLowStorageRK.alg_order(alg) atol = testTol
165+
sim = test_convergence(dts, prob, alg2)
166+
@test sim.𝒪est[:final] OrdinaryDiffEqLowStorageRK.alg_order(alg) atol = testTol
167+
end
168+
integ = init(
169+
prob_ode_large, alg, dt = 1.0e-2, save_start = false, save_end = false,
170+
save_everystep = false
171+
)
172+
@test Base.summarysize(integ) ÷ Base.summarysize(u0_large) <= 3
173+
integ = init(
174+
prob_ode_large, alg, dt = 1.0e-2, save_start = false, save_end = false,
175+
save_everystep = false, alias = ODEAliasSpecifier(alias_u0 = true)
176+
)
177+
@test Base.summarysize(integ) ÷ Base.summarysize(u0_large) <= 2
178+
integ = init(
179+
prob_ode_large, alg2, dt = 1.0e-2, save_start = false, save_end = false,
180+
save_everystep = false
181+
)
182+
@test Base.summarysize(integ) ÷ Base.summarysize(u0_large) <= 4
183+
integ = init(
184+
prob_ode_large, alg2, dt = 1.0e-2, save_start = false, save_end = false,
185+
save_everystep = false, alias = ODEAliasSpecifier(alias_u0 = true)
186+
)
187+
@test Base.summarysize(integ) ÷ Base.summarysize(u0_large) <= 3
188+
# test whether aliasing u0 is bad
189+
new_prob_ode_nonlinear_inplace = ODEProblem(
190+
prob_ode_nonlinear_inplace.f, [1.0],
191+
(0.0, 0.5)
192+
)
193+
sol_old = solve(
194+
prob_ode_nonlinear_inplace, alg, dt = 1.0e-4, save_everystep = false,
195+
save_start = false
196+
)
197+
sol_new = solve(
198+
new_prob_ode_nonlinear_inplace, alg, dt = 1.0e-4, save_everystep = false,
199+
save_start = false, alias = ODEAliasSpecifier(alias_u0 = true)
200+
)
201+
@test sol_old.u[end] sol_new.u[end]
202+
end
203+
146204
@testset "CarpenterKennedy2N54" begin
147205
alg = CarpenterKennedy2N54(; williamson_condition = true)
148206
alg2 = CarpenterKennedy2N54(; williamson_condition = false)

0 commit comments

Comments
 (0)