Skip to content

Commit 44014a7

Browse files
Merge pull request #2566 from efaulhaber/drift-kick-drift
Implement the drift-kick-drift form of the Leapfrog method
2 parents 771266b + bb946ee commit 44014a7

File tree

10 files changed

+170
-23
lines changed

10 files changed

+170
-23
lines changed

docs/src/dynamicalodeexplicit/SymplecticRK.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ sol = solve(prob, KahanLi8(), dt = 1 / 10)
4747
SymplecticEuler
4848
VelocityVerlet
4949
VerletLeapfrog
50+
LeapfrogDriftKickDrift
5051
PseudoVerletLeapfrog
5152
McAte2
5253
Ruth3

lib/OrdinaryDiffEqSymplecticRK/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Random = "<0.0.1, 1"
2626
RecursiveArrayTools = "3.27.0"
2727
Reexport = "1.2.2"
2828
SafeTestsets = "0.1.0"
29-
Statistics = "1.11.1"
29+
Statistics = "<0.0.1, 1"
3030
Test = "<0.0.1, 1"
3131
julia = "1.10"
3232

lib/OrdinaryDiffEqSymplecticRK/src/OrdinaryDiffEqSymplecticRK.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ include("symplectic_caches.jl")
2525
include("symplectic_tableaus.jl")
2626
include("symplectic_perform_step.jl")
2727

28-
export SymplecticEuler, VelocityVerlet, VerletLeapfrog, PseudoVerletLeapfrog,
29-
McAte2, Ruth3, McAte3, CandyRoz4, McAte4, McAte42, McAte5,
28+
export SymplecticEuler, VelocityVerlet, VerletLeapfrog, LeapfrogDriftKickDrift,
29+
PseudoVerletLeapfrog, McAte2, Ruth3, McAte3, CandyRoz4, McAte4, McAte42, McAte5,
3030
CalvoSanz4, Yoshida6, KahanLi6, McAte8, KahanLi8, SofSpa10
3131

3232
end

lib/OrdinaryDiffEqSymplecticRK/src/alg_utils.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
alg_order(alg::SymplecticEuler) = 1
22
alg_order(alg::VelocityVerlet) = 2
33
alg_order(alg::VerletLeapfrog) = 2
4+
alg_order(alg::LeapfrogDriftKickDrift) = 2
45
alg_order(alg::PseudoVerletLeapfrog) = 2
56
alg_order(alg::McAte2) = 2
67
alg_order(alg::Ruth3) = 3

lib/OrdinaryDiffEqSymplecticRK/src/algorithms.jl

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,38 @@ publisher={APS}
2424
verlet1967, "", "")
2525
struct VelocityVerlet <: OrdinaryDiffEqPartitionedAlgorithm end
2626

27-
@doc generic_solver_docstring("2nd order explicit symplectic integrator.",
27+
monaghan2005 = """
28+
@article{monaghan2005,
29+
title = {Smoothed particle hydrodynamics},
30+
author = {Monaghan, Joseph J.},
31+
year = {2005},
32+
journal = {Reports on Progress in Physics},
33+
volume = {68},
34+
number = {8},
35+
pages = {1703--1759},
36+
doi = {10.1088/0034-4885/68/8/R01},
37+
}
38+
"""
39+
40+
@doc generic_solver_docstring(
41+
"2nd order explicit symplectic integrator. Kick-drift-kick form. Requires only one evaluation of `f1` per step.",
2842
"VerletLeapfrog",
2943
"Symplectic Runge-Kutta Methods",
30-
verlet1967, "", "")
44+
monaghan2005, "", "")
3145
struct VerletLeapfrog <: OrdinaryDiffEqPartitionedAlgorithm end
3246

3347
OrdinaryDiffEqCore.default_linear_interpolation(alg::VerletLeapfrog, prob) = true
3448

49+
@doc generic_solver_docstring(
50+
"2nd order explicit symplectic integrator. Drift-kick-drift form of `VerletLeapfrog`
51+
designed to work when `f1` depends on `v`. Requires two evaluation of `f1` per step.",
52+
"LeapfrogDriftKickDrift",
53+
"Symplectic Runge-Kutta Methods",
54+
monaghan2005, "", "")
55+
struct LeapfrogDriftKickDrift <: OrdinaryDiffEqPartitionedAlgorithm end
56+
57+
OrdinaryDiffEqCore.default_linear_interpolation(alg::LeapfrogDriftKickDrift, prob) = true
58+
3559
@doc generic_solver_docstring("2nd order explicit symplectic integrator.",
3660
"PseudoVerletLeapfrog",
3761
"Symplectic Runge-Kutta Methods",

lib/OrdinaryDiffEqSymplecticRK/src/symplectic_caches.jl

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,38 @@ function alg_cache(alg::VelocityVerlet, u, rate_prototype, ::Type{uEltypeNoUnits
5757
VelocityVerletConstantCache(uEltypeNoUnits(1 // 2))
5858
end
5959

60+
@cache struct LeapfrogDriftKickDriftCache{uType, rateType, uEltypeNoUnits} <:
61+
OrdinaryDiffEqMutableCache
62+
u::uType
63+
uprev::uType
64+
tmp::uType
65+
k::rateType
66+
fsalfirst::rateType
67+
half::uEltypeNoUnits
68+
end
69+
70+
struct LeapfrogDriftKickDriftConstantCache{uEltypeNoUnits} <: HamiltonConstantCache
71+
half::uEltypeNoUnits
72+
end
73+
74+
function alg_cache(alg::LeapfrogDriftKickDrift, u, rate_prototype, ::Type{uEltypeNoUnits},
75+
::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t,
76+
dt, reltol, p, calck,
77+
::Val{true}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits}
78+
tmp = zero(rate_prototype)
79+
k = zero(rate_prototype)
80+
fsalfirst = zero(rate_prototype)
81+
half = uEltypeNoUnits(1 // 2)
82+
LeapfrogDriftKickDriftCache(u, uprev, k, tmp, fsalfirst, half)
83+
end
84+
85+
function alg_cache(alg::LeapfrogDriftKickDrift, u, rate_prototype, ::Type{uEltypeNoUnits},
86+
::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, uprev, uprev2, f, t,
87+
dt, reltol, p, calck,
88+
::Val{false}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits}
89+
LeapfrogDriftKickDriftConstantCache(uEltypeNoUnits(1 // 2))
90+
end
91+
6092
@cache struct VerletLeapfrogCache{uType, rateType, uEltypeNoUnits} <:
6193
OrdinaryDiffEqMutableCache
6294
u::uType
@@ -436,6 +468,6 @@ end
436468

437469
function get_fsalfirstlast(
438470
cache::Union{HamiltonMutableCache, VelocityVerletCache, VerletLeapfrogCache,
439-
SymplecticEulerCache}, u)
471+
SymplecticEulerCache, LeapfrogDriftKickDriftCache}, u)
440472
(cache.fsalfirst, cache.k)
441473
end

lib/OrdinaryDiffEqSymplecticRK/src/symplectic_perform_step.jl

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,13 @@ end
7878
# If called with different functions (which are possible in the Hamiltonian case)
7979
# an exception is thrown to avoid silently calculate wrong results.
8080
function verify_f2(f, p, q, pa, t, ::Any,
81-
::C) where {C <: Union{HamiltonConstantCache, VerletLeapfrogConstantCache}}
81+
::C) where {C <: Union{HamiltonConstantCache, VerletLeapfrogConstantCache,
82+
LeapfrogDriftKickDriftConstantCache}}
8283
f(p, q, pa, t)
8384
end
8485
function verify_f2(f, res, p, q, pa, t, ::Any,
85-
::C) where {C <: Union{HamiltonMutableCache, VerletLeapfrogCache}}
86+
::C) where {C <: Union{HamiltonMutableCache, VerletLeapfrogCache,
87+
LeapfrogDriftKickDriftCache}}
8688
f(res, p, q, pa, t)
8789
end
8890

@@ -128,8 +130,8 @@ function store_symp_state!(integrator, ::OrdinaryDiffEqMutableCache, kdu, ku)
128130
end
129131

130132
function initialize!(integrator,
131-
cache::C) where {C <: Union{
132-
HamiltonMutableCache, VelocityVerletCache, VerletLeapfrogCache}}
133+
cache::C) where {C <: Union{HamiltonMutableCache, VelocityVerletCache,
134+
VerletLeapfrogCache, LeapfrogDriftKickDriftCache}}
133135
integrator.kshortsize = 2
134136
resize!(integrator.k, integrator.kshortsize)
135137
integrator.k[1] = integrator.fsalfirst
@@ -144,8 +146,8 @@ function initialize!(integrator,
144146
end
145147

146148
function initialize!(integrator,
147-
cache::C) where {C <: Union{
148-
HamiltonConstantCache, VelocityVerletConstantCache, VerletLeapfrogConstantCache}}
149+
cache::C) where {C <: Union{HamiltonConstantCache, VelocityVerletConstantCache,
150+
VerletLeapfrogConstantCache, LeapfrogDriftKickDriftConstantCache}}
149151
integrator.kshortsize = 2
150152
integrator.k = typeof(integrator.k)(undef, integrator.kshortsize)
151153

@@ -207,13 +209,11 @@ end
207209
du = duprev + dt * half * kduprev
208210

209211
# update position
210-
tnew = t + half * dt
211-
ku = f.f2(du, uprev, p, tnew)
212+
ku = f.f2(du, uprev, p, t + half * dt)
212213
u = uprev + dt * ku
213214

214215
# update velocity
215-
tnew = tnew + half * dt
216-
kdu = f.f1(du, u, p, tnew)
216+
kdu = f.f1(du, u, p, t + dt)
217217
du = du + dt * half * kdu
218218

219219
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 1)
@@ -226,26 +226,83 @@ end
226226
duprev, uprev, kduprev, _ = load_symp_state(integrator)
227227
du, u, kdu, ku = alloc_symp_state(integrator)
228228

229-
# Kick-Drift-Kick scheme of the Verlet Leapfrog method:
229+
# kick-drift-kick scheme of the Leapfrog method:
230230
# update velocity
231231
half = cache.half
232232
@.. broadcast=false du=duprev + dt * half * kduprev
233233

234234
# update position
235-
tnew = t + half * dt
236-
f.f2(ku, du, uprev, p, tnew)
235+
f.f2(ku, du, uprev, p, t + half * dt)
237236
@.. broadcast=false u=uprev + dt * ku
238237

239238
# update velocity
240-
tnew = tnew + half * dt
241-
f.f1(kdu, du, u, p, tnew)
239+
f.f1(kdu, du, u, p, t + dt)
242240
@.. broadcast=false du=du + dt * half * kdu
243241

244242
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 1)
245243
integrator.stats.nf2 += 1
246244
store_symp_state!(integrator, cache, kdu, ku)
247245
end
248246

247+
@muladd function perform_step!(integrator, cache::LeapfrogDriftKickDriftConstantCache,
248+
repeat_step = false)
249+
@unpack t, dt, f, p = integrator
250+
duprev, uprev, _, _ = load_symp_state(integrator)
251+
252+
# drift-kick-drift scheme of the Leapfrog method, allowing for f1 to depend on v:
253+
# update position half step
254+
half = cache.half
255+
ku = f.f2(duprev, uprev, p, t)
256+
u = uprev + dt * half * ku
257+
258+
# update velocity half step
259+
kdu = f.f1(duprev, uprev, p, t)
260+
du = duprev + dt * half * kdu
261+
262+
# update velocity (add to previous full step velocity)
263+
# note that this extra step is only necessary if f1 depends on v/du (or t)
264+
kdu = f.f1(du, u, p, t + half * dt)
265+
du = duprev + dt * kdu
266+
267+
# update position (add to half step position)
268+
ku = f.f2(du, u, p, t + dt)
269+
u = u + dt * half * ku
270+
271+
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 2)
272+
integrator.stats.nf2 += 2
273+
store_symp_state!(integrator, cache, du, u, kdu, ku)
274+
end
275+
276+
@muladd function perform_step!(integrator, cache::LeapfrogDriftKickDriftCache,
277+
repeat_step = false)
278+
@unpack t, dt, f, p = integrator
279+
duprev, uprev, _, _ = load_symp_state(integrator)
280+
du, u, kdu, ku = alloc_symp_state(integrator)
281+
282+
# drift-kick-drift scheme of the Leapfrog method, allowing for f1 to depend on v:
283+
# update position half step
284+
half = cache.half
285+
f.f2(ku, duprev, uprev, p, t)
286+
@.. broadcast=false u=uprev + dt * half * ku
287+
288+
# update velocity half step
289+
f.f1(kdu, duprev, uprev, p, t)
290+
@.. broadcast=false du=duprev + dt * half * kdu
291+
292+
# update velocity (add to previous full step velocity)
293+
# note that this extra step is only necessary if f1 depends on v/du (or t)
294+
f.f1(kdu, du, u, p, t + half * dt)
295+
@.. broadcast=false du=duprev + dt * kdu
296+
297+
# update position (add to half step position)
298+
f.f2(ku, du, u, p, t + dt)
299+
@.. broadcast=false u=u + dt * half * ku
300+
301+
OrdinaryDiffEqCore.increment_nf!(integrator.stats, 2)
302+
integrator.stats.nf2 += 2
303+
store_symp_state!(integrator, cache, kdu, ku)
304+
end
305+
249306
@muladd function perform_step!(integrator, cache::Symplectic2ConstantCache,
250307
repeat_step = false)
251308
@unpack t, dt, f, p = integrator

lib/OrdinaryDiffEqSymplecticRK/test/symplectic_convergence.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ position_error = :final => [mean(sim[i].u[2].x[1] - sim[i].u_analytic[2].x[1])
6262
sim = test_convergence(dts, prob, VerletLeapfrog(), dense_errors = true)
6363
@test sim.𝒪est[:l2]2 rtol=1e-1
6464
@test sim.𝒪est[:L2]2 rtol=1e-1
65+
sim = test_convergence(dts, prob, LeapfrogDriftKickDrift(), dense_errors = true)
66+
@test sim.𝒪est[:l2]2 rtol=1e-1
67+
@test sim.𝒪est[:L2]2 rtol=1e-1
6568
sim = test_convergence(dts, prob, PseudoVerletLeapfrog(), dense_errors = true)
6669
@test sim.𝒪est[:l2]2 rtol=1e-1
6770
@test sim.𝒪est[:L2]2 rtol=1e-1
@@ -151,6 +154,9 @@ position_error = :final => [mean(sim[i].u[2].x[1] - sim[i].u_analytic[2].x[1])
151154
sim = test_convergence(dts, prob, VerletLeapfrog(), dense_errors = true)
152155
@test sim.𝒪est[:l2]2 rtol=1e-1
153156
@test sim.𝒪est[:L2]2 rtol=1e-1
157+
sim = test_convergence(dts, prob, LeapfrogDriftKickDrift(), dense_errors = true)
158+
@test sim.𝒪est[:l2]2 rtol=1e-1
159+
@test sim.𝒪est[:L2]2 rtol=1e-1
154160
sim = test_convergence(dts, prob, PseudoVerletLeapfrog(), dense_errors = true)
155161
@test sim.𝒪est[:l2]2 rtol=1e-1
156162
@test sim.𝒪est[:L2]2 rtol=1e-1
@@ -202,3 +208,28 @@ dts = 1.0 ./ 2.0 .^ (2:-1:-2)
202208
sim = test_convergence(dts, prob, SofSpa10(), dense_errors = true)
203209
@test sim.𝒪est[:l2]10 rtol=1e-1
204210
@test sim.𝒪est[:L2]4 rtol=1e-1
211+
212+
################# f1 dependent on v
213+
214+
println("f1 dependent on v")
215+
216+
u0 = fill(0.0, 2)
217+
v0 = ones(2)
218+
function f1_v(dv, v, u, p, t)
219+
dv .= v
220+
end
221+
function f2_v(du, v, u, p, t)
222+
du .= v
223+
end
224+
function f_v_analytic(y0, p, x)
225+
v0, u0 = y0.x
226+
ArrayPartition(v0 * exp(x), v0 * exp(x) - v0 + u0)
227+
end
228+
ff_v = DynamicalODEFunction(f1_v, f2_v; analytic = f_v_analytic)
229+
prob = DynamicalODEProblem(ff_v, v0, u0, (0.0, 5.0))
230+
231+
dts = 1 .// 2 .^ (6:-1:3)
232+
# LeapfrogDriftKickDrift
233+
sim = test_convergence(dts, prob, LeapfrogDriftKickDrift(), dense_errors = true)
234+
@test sim.𝒪est[:l2]2 rtol=1e-1
235+
@test sim.𝒪est[:L2]2 rtol=1e-1

lib/OrdinaryDiffEqSymplecticRK/test/symplectic_tests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ using OrdinaryDiffEqRKN
77
const ALGOS = ((SymplecticEuler, true, 1),
88
(VelocityVerlet, false, 2),
99
(VerletLeapfrog, true, 2),
10+
(LeapfrogDriftKickDrift, true, 2),
1011
(PseudoVerletLeapfrog, true, 2),
1112
(McAte2, true, 2),
1213
(Ruth3, true, 3),

src/OrdinaryDiffEq.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ using OrdinaryDiffEqFeagin
113113
export Feagin10, Feagin12, Feagin14
114114

115115
using OrdinaryDiffEqSymplecticRK
116-
export SymplecticEuler, VelocityVerlet, VerletLeapfrog, PseudoVerletLeapfrog,
117-
McAte2, Ruth3, McAte3, CandyRoz4, McAte4, McAte42, McAte5,
116+
export SymplecticEuler, VelocityVerlet, VerletLeapfrog, LeapfrogDriftKickDrift,
117+
PseudoVerletLeapfrog, McAte2, Ruth3, McAte3, CandyRoz4, McAte4, McAte42, McAte5,
118118
CalvoSanz4, Yoshida6, KahanLi6, McAte8, KahanLi8, SofSpa10
119119

120120
using OrdinaryDiffEqRKN

0 commit comments

Comments
 (0)