Skip to content

Commit 4b73c68

Browse files
Merge pull request #2397 from Parcels-code/fix_RK45
Fixes for the RK45 Kernel
2 parents 9f39bea + eefda47 commit 4b73c68

File tree

2 files changed

+17
-14
lines changed

2 files changed

+17
-14
lines changed

src/parcels/kernels/advection.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,14 @@ def AdvectionEE(particles, fieldset): # pragma: no cover
129129
def AdvectionRK45(particles, fieldset): # pragma: no cover
130130
"""Advection of particles using adaptive Runge-Kutta 4/5 integration.
131131
132-
Note that this kernel requires a Particle Class that has an extra Variable 'next_dt'
133-
and a FieldSet with constants 'RK45_tol' (in meters), 'RK45_min_dt' (in seconds)
134-
and 'RK45_max_dt' (in seconds).
132+
Note that this kernel requires a FieldSet with constants 'RK45_tol' (in meters),
133+
'RK45_min_dt' (in seconds) and 'RK45_max_dt' (in seconds).
135134
136135
Time-step dt is halved if error is larger than fieldset.RK45_tol,
137136
and doubled if error is smaller than 1/10th of tolerance.
138137
"""
139138
dt = particles.dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
139+
sign_dt = np.sign(dt)
140140

141141
c = [1.0 / 4.0, 3.0 / 8.0, 12.0 / 13.0, 1.0, 1.0 / 2.0]
142142
A = [
@@ -189,17 +189,17 @@ def AdvectionRK45(particles, fieldset): # pragma: no cover
189189
)
190190
particles.dt = np.where(increase_dt_particles, particles.dt * 2, particles.dt)
191191
particles.dt = np.where(
192-
particles.dt > fieldset.RK45_max_dt * np.timedelta64(1, "s"),
193-
fieldset.RK45_max_dt * np.timedelta64(1, "s"),
192+
np.abs(particles.dt) > np.abs(fieldset.RK45_max_dt * np.timedelta64(1, "s")),
193+
fieldset.RK45_max_dt * np.timedelta64(1, "s") * sign_dt,
194194
particles.dt,
195195
)
196-
particles.state = np.where(good_particles, StatusCode.Success, particles.state)
196+
particles.state = np.where(good_particles, StatusCode.Evaluate, particles.state)
197197

198198
repeat_particles = np.invert(good_particles)
199199
particles.dt = np.where(repeat_particles, particles.dt / 2, particles.dt)
200200
particles.dt = np.where(
201-
particles.dt < fieldset.RK45_min_dt * np.timedelta64(1, "s"),
202-
fieldset.RK45_min_dt * np.timedelta64(1, "s"),
201+
np.abs(particles.dt) < np.abs(fieldset.RK45_min_dt * np.timedelta64(1, "s")),
202+
fieldset.RK45_min_dt * np.timedelta64(1, "s") * sign_dt,
203203
particles.dt,
204204
)
205205
particles.state = np.where(repeat_particles, StatusCode.Repeat, particles.state)

tests/test_advection.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,12 @@ def test_radialrotation(npart=10):
235235
lon = np.linspace(32, 50, npart)
236236
lat = np.ones(npart) * 30
237237
starttime = np.arange(np.timedelta64(0, "s"), npart * dt, dt)
238+
endtime = np.timedelta64(10, "m")
238239

239240
pset = parcels.ParticleSet(fieldset, lon=lon, lat=lat, time=starttime)
240-
pset.execute(parcels.kernels.AdvectionRK4, endtime=np.timedelta64(10, "m"), dt=dt)
241+
pset.execute(parcels.kernels.AdvectionRK4, endtime=endtime, dt=dt)
241242

242-
theta = 2 * np.pi * (pset.time - starttime) / np.timedelta64(24 * 3600, "s")
243+
theta = 2 * np.pi * (endtime - starttime) / np.timedelta64(24 * 3600, "s")
243244
true_lon = (lon - 30.0) * np.cos(theta) + 30.0
244245
true_lat = -(lon - 30.0) * np.sin(theta) + 30.0
245246

@@ -282,20 +283,21 @@ def test_moving_eddy(kernel, rtol):
282283

283284
start_lon, start_lat, start_z = 12000, 12500, 12500
284285
dt = np.timedelta64(30, "m")
286+
endtime = np.timedelta64(1, "h")
285287

286288
if kernel == AdvectionRK45:
287289
fieldset.add_constant("RK45_tol", rtol)
288290

289291
pset = ParticleSet(fieldset, lon=start_lon, lat=start_lat, z=start_z, time=np.timedelta64(0, "s"))
290-
pset.execute(kernel, dt=dt, endtime=np.timedelta64(1, "h"))
292+
pset.execute(kernel, dt=dt, endtime=endtime)
291293

292294
def truth_moving(x_0, y_0, t):
293295
t /= np.timedelta64(1, "s")
294296
lat = y_0 - (ds.u_0 - ds.u_g) / ds.f * (1 - np.cos(ds.f * t))
295297
lon = x_0 + ds.u_g * t + (ds.u_0 - ds.u_g) / ds.f * np.sin(ds.f * t)
296298
return lon, lat
297299

298-
exp_lon, exp_lat = truth_moving(start_lon, start_lat, pset.time[0])
300+
exp_lon, exp_lat = truth_moving(start_lon, start_lat, endtime)
299301
np.testing.assert_allclose(pset.lon, exp_lon, rtol=rtol)
300302
np.testing.assert_allclose(pset.lat, exp_lat, rtol=rtol)
301303
if kernel == AdvectionRK4_3D:
@@ -321,13 +323,14 @@ def test_decaying_moving_eddy(kernel, rtol):
321323

322324
start_lon, start_lat = 10000, 10000
323325
dt = np.timedelta64(60, "m")
326+
endtime = np.timedelta64(23, "h")
324327

325328
if kernel == AdvectionRK45:
326329
fieldset.add_constant("RK45_tol", rtol)
327330
fieldset.add_constant("RK45_min_dt", 10 * 60)
328331

329332
pset = ParticleSet(fieldset, lon=start_lon, lat=start_lat, time=np.timedelta64(0, "s"))
330-
pset.execute(kernel, dt=dt, endtime=np.timedelta64(23, "h"))
333+
pset.execute(kernel, dt=dt, endtime=endtime)
331334

332335
def truth_moving(x_0, y_0, t):
333336
t /= np.timedelta64(1, "s")
@@ -343,7 +346,7 @@ def truth_moving(x_0, y_0, t):
343346
)
344347
return lon, lat
345348

346-
exp_lon, exp_lat = truth_moving(start_lon, start_lat, pset.time[0])
349+
exp_lon, exp_lat = truth_moving(start_lon, start_lat, endtime)
347350
np.testing.assert_allclose(pset.lon, exp_lon, rtol=rtol)
348351
np.testing.assert_allclose(pset.lat, exp_lat, rtol=rtol)
349352

0 commit comments

Comments
 (0)