Skip to content

Commit c261d79

Browse files
Updating helper function to return local variable dt
Also expanding docstring for clarity
1 parent 9027987 commit c261d79

1 file changed

Lines changed: 91 additions & 85 deletions

File tree

src/parcels/kernels/advection.py

Lines changed: 91 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@
1818
]
1919

2020

21-
def _adjust_RK_dt(time_interval, time, dt):
22-
"""Helper function to make sure dt does not go outside time_interval for RK methods"""
21+
def _constrain_dt_to_within_time_interval(time_interval, time, dt):
22+
"""Helper function to make sure dt does not go outside time_interval.
23+
24+
This is especially relevant for higher-order RK methods (RK2, RK4, RK45),
25+
which require interpolations at time + dt. If time is at the edges of the
26+
time_interval (typically the last integration step), such an operation would
27+
lead to an OutofTimeError.
28+
"""
2329
if time_interval:
2430
dt = np.where(time + dt <= time_interval.right, dt, time_interval.right - time)
2531
dt = np.where(time + dt >= time_interval.left, dt, time - time_interval.left)
@@ -28,104 +34,104 @@ def _adjust_RK_dt(time_interval, time, dt):
2834

2935
def AdvectionRK2(particles, fieldset): # pragma: no cover
3036
"""Advection of particles using second-order Runge-Kutta integration."""
31-
particles.dt = _adjust_RK_dt(fieldset.time_interval, particles.time, particles.dt)
32-
dt = particles.dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
37+
dt = _constrain_dt_to_within_time_interval(fieldset.time_interval, particles.time, particles.dt)
38+
dt_flt = dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
3339
(u1, v1) = fieldset.UV[particles]
34-
lon1, lat1 = (particles.lon + u1 * 0.5 * dt, particles.lat + v1 * 0.5 * dt)
35-
(u2, v2) = fieldset.UV[particles.time + 0.5 * particles.dt, particles.z, lat1, lon1, particles]
36-
particles.dlon += u2 * dt
37-
particles.dlat += v2 * dt
40+
lon1, lat1 = (particles.lon + u1 * 0.5 * dt_flt, particles.lat + v1 * 0.5 * dt_flt)
41+
(u2, v2) = fieldset.UV[particles.time + 0.5 * dt, particles.z, lat1, lon1, particles]
42+
particles.dlon += u2 * dt_flt
43+
particles.dlat += v2 * dt_flt
3844

3945

4046
def AdvectionRK2_3D(particles, fieldset): # pragma: no cover
4147
"""Advection of particles using second-order Runge-Kutta integration including vertical velocity."""
42-
particles.dt = _adjust_RK_dt(fieldset.time_interval, particles.time, particles.dt)
43-
dt = particles.dt / np.timedelta64(1, "s")
48+
dt = _constrain_dt_to_within_time_interval(fieldset.time_interval, particles.time, particles.dt)
49+
dt_flt = dt / np.timedelta64(1, "s")
4450
(u1, v1, w1) = fieldset.UVW[particles]
45-
lon1 = particles.lon + u1 * 0.5 * dt
46-
lat1 = particles.lat + v1 * 0.5 * dt
47-
z1 = particles.z + w1 * 0.5 * dt
48-
(u2, v2, w2) = fieldset.UVW[particles.time + 0.5 * particles.dt, z1, lat1, lon1, particles]
49-
particles.dlon += u2 * dt
50-
particles.dlat += v2 * dt
51-
particles.dz += w2 * dt
51+
lon1 = particles.lon + u1 * 0.5 * dt_flt
52+
lat1 = particles.lat + v1 * 0.5 * dt_flt
53+
z1 = particles.z + w1 * 0.5 * dt_flt
54+
(u2, v2, w2) = fieldset.UVW[particles.time + 0.5 * dt, z1, lat1, lon1, particles]
55+
particles.dlon += u2 * dt_flt
56+
particles.dlat += v2 * dt_flt
57+
particles.dz += w2 * dt_flt
5258

5359

5460
def AdvectionRK4(particles, fieldset): # pragma: no cover
5561
"""Advection of particles using fourth-order Runge-Kutta integration."""
56-
particles.dt = _adjust_RK_dt(fieldset.time_interval, particles.time, particles.dt)
57-
dt = particles.dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
62+
dt = _constrain_dt_to_within_time_interval(fieldset.time_interval, particles.time, particles.dt)
63+
dt_flt = dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
5864
(u1, v1) = fieldset.UV[particles]
59-
lon1, lat1 = (particles.lon + u1 * 0.5 * dt, particles.lat + v1 * 0.5 * dt)
60-
(u2, v2) = fieldset.UV[particles.time + 0.5 * particles.dt, particles.z, lat1, lon1, particles]
61-
lon2, lat2 = (particles.lon + u2 * 0.5 * dt, particles.lat + v2 * 0.5 * dt)
62-
(u3, v3) = fieldset.UV[particles.time + 0.5 * particles.dt, particles.z, lat2, lon2, particles]
63-
lon3, lat3 = (particles.lon + u3 * dt, particles.lat + v3 * dt)
64-
(u4, v4) = fieldset.UV[particles.time + particles.dt, particles.z, lat3, lon3, particles]
65-
particles.dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6.0 * dt
66-
particles.dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6.0 * dt
65+
lon1, lat1 = (particles.lon + u1 * 0.5 * dt_flt, particles.lat + v1 * 0.5 * dt_flt)
66+
(u2, v2) = fieldset.UV[particles.time + 0.5 * dt, particles.z, lat1, lon1, particles]
67+
lon2, lat2 = (particles.lon + u2 * 0.5 * dt_flt, particles.lat + v2 * 0.5 * dt_flt)
68+
(u3, v3) = fieldset.UV[particles.time + 0.5 * dt, particles.z, lat2, lon2, particles]
69+
lon3, lat3 = (particles.lon + u3 * dt_flt, particles.lat + v3 * dt_flt)
70+
(u4, v4) = fieldset.UV[particles.time + dt, particles.z, lat3, lon3, particles]
71+
particles.dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6.0 * dt_flt
72+
particles.dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6.0 * dt_flt
6773

6874

6975
def AdvectionRK4_3D(particles, fieldset): # pragma: no cover
7076
"""Advection of particles using fourth-order Runge-Kutta integration including vertical velocity."""
71-
particles.dt = _adjust_RK_dt(fieldset.time_interval, particles.time, particles.dt)
72-
dt = particles.dt / np.timedelta64(1, "s")
77+
dt = _constrain_dt_to_within_time_interval(fieldset.time_interval, particles.time, particles.dt)
78+
dt_flt = dt / np.timedelta64(1, "s")
7379
(u1, v1, w1) = fieldset.UVW[particles]
74-
lon1 = particles.lon + u1 * 0.5 * dt
75-
lat1 = particles.lat + v1 * 0.5 * dt
76-
z1 = particles.z + w1 * 0.5 * dt
77-
(u2, v2, w2) = fieldset.UVW[particles.time + 0.5 * particles.dt, z1, lat1, lon1, particles]
78-
lon2 = particles.lon + u2 * 0.5 * dt
79-
lat2 = particles.lat + v2 * 0.5 * dt
80-
z2 = particles.z + w2 * 0.5 * dt
81-
(u3, v3, w3) = fieldset.UVW[particles.time + 0.5 * particles.dt, z2, lat2, lon2, particles]
82-
lon3 = particles.lon + u3 * dt
83-
lat3 = particles.lat + v3 * dt
84-
z3 = particles.z + w3 * dt
85-
(u4, v4, w4) = fieldset.UVW[particles.time + particles.dt, z3, lat3, lon3, particles]
86-
particles.dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * dt
87-
particles.dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * dt
88-
particles.dz += (w1 + 2 * w2 + 2 * w3 + w4) / 6 * dt
80+
lon1 = particles.lon + u1 * 0.5 * dt_flt
81+
lat1 = particles.lat + v1 * 0.5 * dt_flt
82+
z1 = particles.z + w1 * 0.5 * dt_flt
83+
(u2, v2, w2) = fieldset.UVW[particles.time + 0.5 * dt, z1, lat1, lon1, particles]
84+
lon2 = particles.lon + u2 * 0.5 * dt_flt
85+
lat2 = particles.lat + v2 * 0.5 * dt_flt
86+
z2 = particles.z + w2 * 0.5 * dt_flt
87+
(u3, v3, w3) = fieldset.UVW[particles.time + 0.5 * dt, z2, lat2, lon2, particles]
88+
lon3 = particles.lon + u3 * dt_flt
89+
lat3 = particles.lat + v3 * dt_flt
90+
z3 = particles.z + w3 * dt_flt
91+
(u4, v4, w4) = fieldset.UVW[particles.time + dt, z3, lat3, lon3, particles]
92+
particles.dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * dt_flt
93+
particles.dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * dt_flt
94+
particles.dz += (w1 + 2 * w2 + 2 * w3 + w4) / 6 * dt_flt
8995

9096

9197
def AdvectionRK4_3D_CROCO(particles, fieldset): # pragma: no cover
9298
"""Advection of particles using fourth-order Runge-Kutta integration including vertical velocity.
9399
This kernel assumes the vertical velocity is the 'w' field from CROCO output and works on sigma-layers.
94100
"""
95-
particles.dt = _adjust_RK_dt(fieldset.time_interval, particles.time, particles.dt)
96-
dt = particles.dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
101+
dt = _constrain_dt_to_within_time_interval(fieldset.time_interval, particles.time, particles.dt)
102+
dt_flt = dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
97103
sig_dep = particles.z / fieldset.H[particles.time, 0, particles.lat, particles.lon]
98104

99105
(u1, v1, w1) = fieldset.UVW[particles.time, particles.z, particles.lat, particles.lon, particles]
100106
w1 *= sig_dep / fieldset.H[particles.time, 0, particles.lat, particles.lon]
101-
lon1 = particles.lon + u1 * 0.5 * dt
102-
lat1 = particles.lat + v1 * 0.5 * dt
103-
sig_dep1 = sig_dep + w1 * 0.5 * dt
107+
lon1 = particles.lon + u1 * 0.5 * dt_flt
108+
lat1 = particles.lat + v1 * 0.5 * dt_flt
109+
sig_dep1 = sig_dep + w1 * 0.5 * dt_flt
104110
dep1 = sig_dep1 * fieldset.H[particles.time, 0, lat1, lon1]
105111

106-
(u2, v2, w2) = fieldset.UVW[particles.time + 0.5 * particles.dt, dep1, lat1, lon1, particles]
112+
(u2, v2, w2) = fieldset.UVW[particles.time + 0.5 * dt, dep1, lat1, lon1, particles]
107113
w2 *= sig_dep1 / fieldset.H[particles.time, 0, lat1, lon1]
108-
lon2 = particles.lon + u2 * 0.5 * dt
109-
lat2 = particles.lat + v2 * 0.5 * dt
110-
sig_dep2 = sig_dep + w2 * 0.5 * dt
114+
lon2 = particles.lon + u2 * 0.5 * dt_flt
115+
lat2 = particles.lat + v2 * 0.5 * dt_flt
116+
sig_dep2 = sig_dep + w2 * 0.5 * dt_flt
111117
dep2 = sig_dep2 * fieldset.H[particles.time, 0, lat2, lon2]
112118

113-
(u3, v3, w3) = fieldset.UVW[particles.time + 0.5 * particles.dt, dep2, lat2, lon2, particles]
119+
(u3, v3, w3) = fieldset.UVW[particles.time + 0.5 * dt, dep2, lat2, lon2, particles]
114120
w3 *= sig_dep2 / fieldset.H[particles.time, 0, lat2, lon2]
115-
lon3 = particles.lon + u3 * dt
116-
lat3 = particles.lat + v3 * dt
117-
sig_dep3 = sig_dep + w3 * dt
121+
lon3 = particles.lon + u3 * dt_flt
122+
lat3 = particles.lat + v3 * dt_flt
123+
sig_dep3 = sig_dep + w3 * dt_flt
118124
dep3 = sig_dep3 * fieldset.H[particles.time, 0, lat3, lon3]
119125

120-
(u4, v4, w4) = fieldset.UVW[particles.time + particles.dt, dep3, lat3, lon3, particles]
126+
(u4, v4, w4) = fieldset.UVW[particles.time + dt, dep3, lat3, lon3, particles]
121127
w4 *= sig_dep3 / fieldset.H[particles.time, 0, lat3, lon3]
122-
lon4 = particles.lon + u4 * dt
123-
lat4 = particles.lat + v4 * dt
124-
sig_dep4 = sig_dep + w4 * dt
128+
lon4 = particles.lon + u4 * dt_flt
129+
lat4 = particles.lat + v4 * dt_flt
130+
sig_dep4 = sig_dep + w4 * dt_flt
125131
dep4 = sig_dep4 * fieldset.H[particles.time, 0, lat4, lon4]
126132

127-
particles.dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * dt
128-
particles.dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * dt
133+
particles.dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * dt_flt
134+
particles.dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * dt_flt
129135
particles.dz += (
130136
(dep1 - particles.z) * 2 + 2 * (dep2 - particles.z) * 2 + 2 * (dep3 - particles.z) + dep4 - particles.z
131137
) / 6
@@ -149,8 +155,8 @@ def AdvectionRK45(particles, fieldset): # pragma: no cover
149155
Time-step dt is halved if error is larger than fieldset.RK45_tol,
150156
and doubled if error is smaller than 1/10th of tolerance.
151157
"""
152-
particles.dt = _adjust_RK_dt(fieldset.time_interval, particles.time, particles.dt)
153-
dt = particles.dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
158+
dt = _constrain_dt_to_within_time_interval(fieldset.time_interval, particles.time, particles.dt)
159+
dt_flt = dt / np.timedelta64(1, "s") # TODO: improve API for converting dt to seconds
154160

155161
c = [1.0 / 4.0, 3.0 / 8.0, 12.0 / 13.0, 1.0, 1.0 / 2.0]
156162
A = [
@@ -164,42 +170,42 @@ def AdvectionRK45(particles, fieldset): # pragma: no cover
164170
b5 = [16.0 / 135.0, 0.0, 6656.0 / 12825.0, 28561.0 / 56430.0, -9.0 / 50.0, 2.0 / 55.0]
165171

166172
(u1, v1) = fieldset.UV[particles]
167-
lon1, lat1 = (particles.lon + u1 * A[0][0] * dt, particles.lat + v1 * A[0][0] * dt)
168-
(u2, v2) = fieldset.UV[particles.time + c[0] * particles.dt, particles.z, lat1, lon1, particles]
173+
lon1, lat1 = (particles.lon + u1 * A[0][0] * dt_flt, particles.lat + v1 * A[0][0] * dt_flt)
174+
(u2, v2) = fieldset.UV[particles.time + c[0] * dt, particles.z, lat1, lon1, particles]
169175
lon2, lat2 = (
170-
particles.lon + (u1 * A[1][0] + u2 * A[1][1]) * dt,
171-
particles.lat + (v1 * A[1][0] + v2 * A[1][1]) * dt,
176+
particles.lon + (u1 * A[1][0] + u2 * A[1][1]) * dt_flt,
177+
particles.lat + (v1 * A[1][0] + v2 * A[1][1]) * dt_flt,
172178
)
173-
(u3, v3) = fieldset.UV[particles.time + c[1] * particles.dt, particles.z, lat2, lon2, particles]
179+
(u3, v3) = fieldset.UV[particles.time + c[1] * dt, particles.z, lat2, lon2, particles]
174180
lon3, lat3 = (
175-
particles.lon + (u1 * A[2][0] + u2 * A[2][1] + u3 * A[2][2]) * dt,
176-
particles.lat + (v1 * A[2][0] + v2 * A[2][1] + v3 * A[2][2]) * dt,
181+
particles.lon + (u1 * A[2][0] + u2 * A[2][1] + u3 * A[2][2]) * dt_flt,
182+
particles.lat + (v1 * A[2][0] + v2 * A[2][1] + v3 * A[2][2]) * dt_flt,
177183
)
178-
(u4, v4) = fieldset.UV[particles.time + c[2] * particles.dt, particles.z, lat3, lon3, particles]
184+
(u4, v4) = fieldset.UV[particles.time + c[2] * dt, particles.z, lat3, lon3, particles]
179185
lon4, lat4 = (
180-
particles.lon + (u1 * A[3][0] + u2 * A[3][1] + u3 * A[3][2] + u4 * A[3][3]) * dt,
181-
particles.lat + (v1 * A[3][0] + v2 * A[3][1] + v3 * A[3][2] + v4 * A[3][3]) * dt,
186+
particles.lon + (u1 * A[3][0] + u2 * A[3][1] + u3 * A[3][2] + u4 * A[3][3]) * dt_flt,
187+
particles.lat + (v1 * A[3][0] + v2 * A[3][1] + v3 * A[3][2] + v4 * A[3][3]) * dt_flt,
182188
)
183-
(u5, v5) = fieldset.UV[particles.time + c[3] * particles.dt, particles.z, lat4, lon4, particles]
189+
(u5, v5) = fieldset.UV[particles.time + c[3] * dt, particles.z, lat4, lon4, particles]
184190
lon5, lat5 = (
185-
particles.lon + (u1 * A[4][0] + u2 * A[4][1] + u3 * A[4][2] + u4 * A[4][3] + u5 * A[4][4]) * dt,
186-
particles.lat + (v1 * A[4][0] + v2 * A[4][1] + v3 * A[4][2] + v4 * A[4][3] + v5 * A[4][4]) * dt,
191+
particles.lon + (u1 * A[4][0] + u2 * A[4][1] + u3 * A[4][2] + u4 * A[4][3] + u5 * A[4][4]) * dt_flt,
192+
particles.lat + (v1 * A[4][0] + v2 * A[4][1] + v3 * A[4][2] + v4 * A[4][3] + v5 * A[4][4]) * dt_flt,
187193
)
188-
(u6, v6) = fieldset.UV[particles.time + c[4] * particles.dt, particles.z, lat5, lon5, particles]
194+
(u6, v6) = fieldset.UV[particles.time + c[4] * dt, particles.z, lat5, lon5, particles]
189195

190-
lon_4th = (u1 * b4[0] + u2 * b4[1] + u3 * b4[2] + u4 * b4[3] + u5 * b4[4]) * dt
191-
lat_4th = (v1 * b4[0] + v2 * b4[1] + v3 * b4[2] + v4 * b4[3] + v5 * b4[4]) * dt
192-
lon_5th = (u1 * b5[0] + u2 * b5[1] + u3 * b5[2] + u4 * b5[3] + u5 * b5[4] + u6 * b5[5]) * dt
193-
lat_5th = (v1 * b5[0] + v2 * b5[1] + v3 * b5[2] + v4 * b5[3] + v5 * b5[4] + v6 * b5[5]) * dt
196+
lon_4th = (u1 * b4[0] + u2 * b4[1] + u3 * b4[2] + u4 * b4[3] + u5 * b4[4]) * dt_flt
197+
lat_4th = (v1 * b4[0] + v2 * b4[1] + v3 * b4[2] + v4 * b4[3] + v5 * b4[4]) * dt_flt
198+
lon_5th = (u1 * b5[0] + u2 * b5[1] + u3 * b5[2] + u4 * b5[3] + u5 * b5[4] + u6 * b5[5]) * dt_flt
199+
lat_5th = (v1 * b5[0] + v2 * b5[1] + v3 * b5[2] + v4 * b5[3] + v5 * b5[4] + v6 * b5[5]) * dt_flt
194200

195201
kappa = np.sqrt(np.pow(lon_5th - lon_4th, 2) + np.pow(lat_5th - lat_4th, 2))
196202

197-
good_particles = (kappa <= fieldset.RK45_tol) | (np.fabs(dt) <= np.fabs(fieldset.RK45_min_dt))
203+
good_particles = (kappa <= fieldset.RK45_tol) | (np.fabs(dt_flt) <= np.fabs(fieldset.RK45_min_dt))
198204
particles.dlon += np.where(good_particles, lon_5th, 0)
199205
particles.dlat += np.where(good_particles, lat_5th, 0)
200206

201207
increase_dt_particles = (
202-
good_particles & (kappa <= fieldset.RK45_tol / 10) & (np.fabs(dt * 2) <= np.fabs(fieldset.RK45_max_dt))
208+
good_particles & (kappa <= fieldset.RK45_tol / 10) & (np.fabs(dt_flt * 2) <= np.fabs(fieldset.RK45_max_dt))
203209
)
204210
particles.dt = np.where(increase_dt_particles, particles.dt * 2, particles.dt)
205211
particles.dt = np.where(

0 commit comments

Comments
 (0)