Skip to content

Commit 6a67411

Browse files
Add next_dt in RK45
This is needed in case dt is signalled for increasing in the RK45 Kernel; otherwise the wrong dt gets added in the add_positionupdate_kernel
1 parent e9d136a commit 6a67411

4 files changed

Lines changed: 42 additions & 13 deletions

File tree

src/parcels/_core/kernel.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ def PositionUpdate(particles, fieldset): # pragma: no cover
123123
particles.dlat = 0
124124
particles.dz = 0
125125

126+
if hasattr(self.fieldset, "RK45_tol"):
127+
# Update dt in case it's increased in RK45 kernel
128+
particles.dt = particles.next_dt
129+
126130
self._pyfuncs = (PositionUpdate + self)._pyfuncs
127131

128132
def check_fieldsets_in_kernels(self, pyfunc): # TODO v4: this can go into another method? assert_is_compatible()?
@@ -138,6 +142,8 @@ def check_fieldsets_in_kernels(self, pyfunc): # TODO v4: this can go into anoth
138142
if self._fieldset.U.grid._gtype not in [GridType.CurvilinearZGrid, GridType.RectilinearZGrid]:
139143
raise NotImplementedError("Analytical Advection only works with Z-grids in the vertical")
140144
elif pyfunc is AdvectionRK45:
145+
if "next_dt" not in [v.name for v in self.ptype.variables]:
146+
raise ValueError('ParticleClass requires a "next_dt" for AdvectionRK45 Kernel.')
141147
if not hasattr(self.fieldset, "RK45_tol"):
142148
warnings.warn(
143149
"Setting RK45 tolerance to 10 m. Use fieldset.add_constant('RK45_tol', [distance]) to change.",
@@ -240,6 +246,9 @@ def execute(self, pset, endtime, dt):
240246
return StatusCode.Success
241247

242248
# adapt dt to end exactly on endtime
249+
if hasattr(self.fieldset, "RK45_tol"):
250+
pset.next_dt = np.maximum(np.minimum(pset.next_dt, time_to_endtime), 0)
251+
243252
if compute_time_direction == 1:
244253
pset.dt = np.maximum(np.minimum(pset.dt, time_to_endtime), 0)
245254
else:

src/parcels/kernels/advection.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,11 @@ def AdvectionRK45(particles, fieldset): # pragma: no cover
188188
increase_dt_particles = (
189189
good_particles & (kappa <= fieldset.RK45_tol / 10) & (np.fabs(dt * 2) <= np.fabs(fieldset.RK45_max_dt))
190190
)
191-
particles.dt = np.where(increase_dt_particles, particles.dt * 2, particles.dt)
192-
particles.dt = np.where(
191+
particles.next_dt = np.where(increase_dt_particles, particles.dt * 2, particles.dt)
192+
particles.next_dt = np.where(
193193
np.abs(particles.next_dt) > np.abs(fieldset.RK45_max_dt * np.timedelta64(1, "s")),
194194
fieldset.RK45_max_dt * np.timedelta64(1, "s") * sign_dt,
195-
particles.dt,
195+
particles.next_dt,
196196
)
197197
particles.state = np.where(good_particles, StatusCode.Evaluate, particles.state)
198198

tests/test_advection.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,13 @@ def test_moving_eddy(kernel, rtol):
285285

286286
if kernel == AdvectionRK45:
287287
fieldset.add_constant("RK45_tol", rtol)
288+
MyParticle = Particle.add_variable(Variable("next_dt", dtype="timedelta64[s]", initial=dt))
289+
else:
290+
MyParticle = Particle
288291

289-
pset = ParticleSet(fieldset, lon=start_lon, lat=start_lat, z=start_z, time=np.timedelta64(0, "s"))
292+
pset = ParticleSet(
293+
fieldset, pclass=MyParticle, lon=start_lon, lat=start_lat, z=start_z, time=np.timedelta64(0, "s")
294+
)
290295
pset.execute(kernel, dt=dt, endtime=np.timedelta64(1, "h"))
291296

292297
def truth_moving(x_0, y_0, t):
@@ -325,8 +330,11 @@ def test_decaying_moving_eddy(kernel, rtol):
325330
if kernel == AdvectionRK45:
326331
fieldset.add_constant("RK45_tol", rtol)
327332
fieldset.add_constant("RK45_min_dt", 10 * 60)
333+
MyParticle = Particle.add_variable(Variable("next_dt", dtype="timedelta64[s]", initial=dt))
334+
else:
335+
MyParticle = Particle
328336

329-
pset = ParticleSet(fieldset, lon=start_lon, lat=start_lat, time=np.timedelta64(0, "s"))
337+
pset = ParticleSet(fieldset, pclass=MyParticle, lon=start_lon, lat=start_lat, time=np.timedelta64(0, "s"))
330338
pset.execute(kernel, dt=dt, endtime=np.timedelta64(23, "h"))
331339

332340
def truth_moving(x_0, y_0, t):
@@ -373,13 +381,15 @@ def test_stommelgyre_fieldset(kernel, rtol, grid_type):
373381
start_lon = np.linspace(10e3, 100e3, npart)
374382
start_lat = np.ones_like(start_lon) * 5000e3
375383

376-
if kernel == AdvectionRK45:
377-
fieldset.add_constant("RK45_tol", rtol)
378-
379384
SampleParticle = Particle.add_variable(
380385
[Variable("p", initial=0.0, dtype=np.float32), Variable("p_start", initial=0.0, dtype=np.float32)]
381386
)
382387

388+
if kernel == AdvectionRK45:
389+
fieldset.add_constant("RK45_tol", rtol)
390+
fieldset.add_constant("RK45_min_dt", 5 * 60)
391+
SampleParticle = SampleParticle.add_variable(Variable("next_dt", dtype="timedelta64[s]", initial=dt))
392+
383393
def UpdateP(particles, fieldset): # pragma: no cover
384394
particles.p = fieldset.P[particles.time, particles.z, particles.lat, particles.lon]
385395
particles.p_start = np.where(particles.time == 0, particles.p, particles.p_start)
@@ -394,7 +404,7 @@ def UpdateP(particles, fieldset): # pragma: no cover
394404
[
395405
(AdvectionRK2, 2e-2),
396406
(AdvectionRK4, 5e-3),
397-
(AdvectionRK45, 1e-4),
407+
(AdvectionRK45, 1e-3),
398408
],
399409
)
400410
@pytest.mark.parametrize("grid_type", ["A"]) # TODO also implement C-grid once available
@@ -413,13 +423,15 @@ def test_peninsula_fieldset(kernel, rtol, grid_type):
413423
start_lat = np.linspace(3e3, 47e3, npart)
414424
start_lon = 3e3 * np.ones_like(start_lat)
415425

416-
if kernel == AdvectionRK45:
417-
fieldset.add_constant("RK45_tol", rtol)
418-
419426
SampleParticle = Particle.add_variable(
420427
[Variable("p", initial=0.0, dtype=np.float32), Variable("p_start", initial=0.0, dtype=np.float32)]
421428
)
422429

430+
if kernel == AdvectionRK45:
431+
fieldset.add_constant("RK45_tol", rtol)
432+
fieldset.add_constant("RK45_min_dt", 5 * 60)
433+
SampleParticle = SampleParticle.add_variable(Variable("next_dt", dtype="timedelta64[s]", initial=dt))
434+
423435
def UpdateP(particles, fieldset): # pragma: no cover
424436
particles.p = fieldset.P[particles.time, particles.z, particles.lat, particles.lon]
425437
particles.p_start = np.where(particles.time == 0, particles.p, particles.p_start)

tests/test_kernel.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
)
1212
from parcels._datasets.structured.generic import datasets as datasets_structured
1313
from parcels.interpolators import XLinear
14-
from parcels.kernels import AdvectionRK4
14+
from parcels.kernels import AdvectionRK4, AdvectionRK45
1515
from tests.common_kernels import MoveEast, MoveNorth
1616

1717

@@ -88,6 +88,14 @@ def test_kernel_from_list_error_checking(fieldset):
8888
assert kernels_mixed.funcname == "AdvectionRK4MoveEastMoveNorth"
8989

9090

91+
def test_RK45Kernel_error_no_next_dt(fieldset):
92+
"""Tests that kernel throws error if Particle class does not have next_dt for RK45"""
93+
pset = ParticleSet(fieldset, lon=[0.5], lat=[0.5])
94+
95+
with pytest.raises(ValueError, match='ParticleClass requires a "next_dt" for AdvectionRK45 Kernel.'):
96+
pset.Kernel(AdvectionRK45)
97+
98+
9199
def test_kernel_signature(fieldset):
92100
pset = ParticleSet(fieldset, lon=[0.5], lat=[0.5])
93101

0 commit comments

Comments
 (0)