Skip to content

Commit be87206

Browse files
Converting timedelta64 to float for dt in Advection Kernels
This is a pragmatic/temporary fix of #2078 so that Advection kernels can be tested; while we consider the API for particle.dt
1 parent b6f650d commit be87206

1 file changed

Lines changed: 84 additions & 67 deletions

File tree

parcels/application_kernels/advection.py

Lines changed: 84 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,73 +16,82 @@
1616

1717
def AdvectionRK4(particle, fieldset, time): # pragma: no cover
1818
"""Advection of particles using fourth-order Runge-Kutta integration."""
19+
import numpy as np
20+
21+
dt = particle.dt / np.timedelta64(1, "s") # TODO improve API for converting dt to seconds
1922
(u1, v1) = fieldset.UV[particle]
20-
lon1, lat1 = (particle.lon + u1 * 0.5 * particle.dt, particle.lat + v1 * 0.5 * particle.dt)
21-
(u2, v2) = fieldset.UV[time + 0.5 * particle.dt, particle.depth, lat1, lon1, particle]
22-
lon2, lat2 = (particle.lon + u2 * 0.5 * particle.dt, particle.lat + v2 * 0.5 * particle.dt)
23-
(u3, v3) = fieldset.UV[time + 0.5 * particle.dt, particle.depth, lat2, lon2, particle]
24-
lon3, lat3 = (particle.lon + u3 * particle.dt, particle.lat + v3 * particle.dt)
25-
(u4, v4) = fieldset.UV[time + particle.dt, particle.depth, lat3, lon3, particle]
26-
particle_dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6.0 * particle.dt # noqa
27-
particle_dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6.0 * particle.dt # noqa
23+
lon1, lat1 = (particle.lon + u1 * 0.5 * dt, particle.lat + v1 * 0.5 * dt)
24+
(u2, v2) = fieldset.UV[time + 0.5 * dt, particle.depth, lat1, lon1, particle]
25+
lon2, lat2 = (particle.lon + u2 * 0.5 * dt, particle.lat + v2 * 0.5 * dt)
26+
(u3, v3) = fieldset.UV[time + 0.5 * dt, particle.depth, lat2, lon2, particle]
27+
lon3, lat3 = (particle.lon + u3 * dt, particle.lat + v3 * dt)
28+
(u4, v4) = fieldset.UV[time + dt, particle.depth, lat3, lon3, particle]
29+
particle_dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6.0 * dt # noqa
30+
particle_dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6.0 * dt # noqa
2831

2932

3033
def AdvectionRK4_3D(particle, fieldset, time): # pragma: no cover
3134
"""Advection of particles using fourth-order Runge-Kutta integration including vertical velocity."""
35+
import numpy as np
36+
37+
dt = particle.dt / np.timedelta64(1, "s") # TODO improve API for converting dt to seconds
3238
(u1, v1, w1) = fieldset.UVW[particle]
33-
lon1 = particle.lon + u1 * 0.5 * particle.dt
34-
lat1 = particle.lat + v1 * 0.5 * particle.dt
35-
dep1 = particle.depth + w1 * 0.5 * particle.dt
36-
(u2, v2, w2) = fieldset.UVW[time + 0.5 * particle.dt, dep1, lat1, lon1, particle]
37-
lon2 = particle.lon + u2 * 0.5 * particle.dt
38-
lat2 = particle.lat + v2 * 0.5 * particle.dt
39-
dep2 = particle.depth + w2 * 0.5 * particle.dt
40-
(u3, v3, w3) = fieldset.UVW[time + 0.5 * particle.dt, dep2, lat2, lon2, particle]
41-
lon3 = particle.lon + u3 * particle.dt
42-
lat3 = particle.lat + v3 * particle.dt
43-
dep3 = particle.depth + w3 * particle.dt
44-
(u4, v4, w4) = fieldset.UVW[time + particle.dt, dep3, lat3, lon3, particle]
45-
particle_dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * particle.dt # noqa
46-
particle_dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * particle.dt # noqa
47-
particle_ddepth += (w1 + 2 * w2 + 2 * w3 + w4) / 6 * particle.dt # noqa
39+
lon1 = particle.lon + u1 * 0.5 * dt
40+
lat1 = particle.lat + v1 * 0.5 * dt
41+
dep1 = particle.depth + w1 * 0.5 * dt
42+
(u2, v2, w2) = fieldset.UVW[time + 0.5 * dt, dep1, lat1, lon1, particle]
43+
lon2 = particle.lon + u2 * 0.5 * dt
44+
lat2 = particle.lat + v2 * 0.5 * dt
45+
dep2 = particle.depth + w2 * 0.5 * dt
46+
(u3, v3, w3) = fieldset.UVW[time + 0.5 * dt, dep2, lat2, lon2, particle]
47+
lon3 = particle.lon + u3 * dt
48+
lat3 = particle.lat + v3 * dt
49+
dep3 = particle.depth + w3 * dt
50+
(u4, v4, w4) = fieldset.UVW[time + dt, dep3, lat3, lon3, particle]
51+
particle_dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * dt # noqa
52+
particle_dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * dt # noqa
53+
particle_ddepth += (w1 + 2 * w2 + 2 * w3 + w4) / 6 * dt # noqa
4854

4955

5056
def AdvectionRK4_3D_CROCO(particle, fieldset, time): # pragma: no cover
5157
"""Advection of particles using fourth-order Runge-Kutta integration including vertical velocity.
5258
This kernel assumes the vertical velocity is the 'w' field from CROCO output and works on sigma-layers.
5359
"""
60+
import numpy as np
61+
62+
dt = particle.dt / np.timedelta64(1, "s") # TODO improve API for converting dt to seconds
5463
sig_dep = particle.depth / fieldset.H[time, 0, particle.lat, particle.lon]
5564

5665
(u1, v1, w1) = fieldset.UVW[time, particle.depth, particle.lat, particle.lon, particle]
5766
w1 *= sig_dep / fieldset.H[time, 0, particle.lat, particle.lon]
58-
lon1 = particle.lon + u1 * 0.5 * particle.dt
59-
lat1 = particle.lat + v1 * 0.5 * particle.dt
60-
sig_dep1 = sig_dep + w1 * 0.5 * particle.dt
67+
lon1 = particle.lon + u1 * 0.5 * dt
68+
lat1 = particle.lat + v1 * 0.5 * dt
69+
sig_dep1 = sig_dep + w1 * 0.5 * dt
6170
dep1 = sig_dep1 * fieldset.H[time, 0, lat1, lon1]
6271

63-
(u2, v2, w2) = fieldset.UVW[time + 0.5 * particle.dt, dep1, lat1, lon1, particle]
72+
(u2, v2, w2) = fieldset.UVW[time + 0.5 * dt, dep1, lat1, lon1, particle]
6473
w2 *= sig_dep1 / fieldset.H[time, 0, lat1, lon1]
65-
lon2 = particle.lon + u2 * 0.5 * particle.dt
66-
lat2 = particle.lat + v2 * 0.5 * particle.dt
67-
sig_dep2 = sig_dep + w2 * 0.5 * particle.dt
74+
lon2 = particle.lon + u2 * 0.5 * dt
75+
lat2 = particle.lat + v2 * 0.5 * dt
76+
sig_dep2 = sig_dep + w2 * 0.5 * dt
6877
dep2 = sig_dep2 * fieldset.H[time, 0, lat2, lon2]
6978

70-
(u3, v3, w3) = fieldset.UVW[time + 0.5 * particle.dt, dep2, lat2, lon2, particle]
79+
(u3, v3, w3) = fieldset.UVW[time + 0.5 * dt, dep2, lat2, lon2, particle]
7180
w3 *= sig_dep2 / fieldset.H[time, 0, lat2, lon2]
72-
lon3 = particle.lon + u3 * particle.dt
73-
lat3 = particle.lat + v3 * particle.dt
74-
sig_dep3 = sig_dep + w3 * particle.dt
81+
lon3 = particle.lon + u3 * dt
82+
lat3 = particle.lat + v3 * dt
83+
sig_dep3 = sig_dep + w3 * dt
7584
dep3 = sig_dep3 * fieldset.H[time, 0, lat3, lon3]
7685

77-
(u4, v4, w4) = fieldset.UVW[time + particle.dt, dep3, lat3, lon3, particle]
86+
(u4, v4, w4) = fieldset.UVW[time + dt, dep3, lat3, lon3, particle]
7887
w4 *= sig_dep3 / fieldset.H[time, 0, lat3, lon3]
79-
lon4 = particle.lon + u4 * particle.dt
80-
lat4 = particle.lat + v4 * particle.dt
81-
sig_dep4 = sig_dep + w4 * particle.dt
88+
lon4 = particle.lon + u4 * dt
89+
lat4 = particle.lat + v4 * dt
90+
sig_dep4 = sig_dep + w4 * dt
8291
dep4 = sig_dep4 * fieldset.H[time, 0, lat4, lon4]
8392

84-
particle_dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * particle.dt # noqa
85-
particle_dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * particle.dt # noqa
93+
particle_dlon += (u1 + 2 * u2 + 2 * u3 + u4) / 6 * dt # noqa
94+
particle_dlat += (v1 + 2 * v2 + 2 * v3 + v4) / 6 * dt # noqa
8695
particle_ddepth += ( # noqa
8796
(dep1 - particle.depth) * 2
8897
+ 2 * (dep2 - particle.depth) * 2
@@ -94,9 +103,12 @@ def AdvectionRK4_3D_CROCO(particle, fieldset, time): # pragma: no cover
94103

95104
def AdvectionEE(particle, fieldset, time): # pragma: no cover
96105
"""Advection of particles using Explicit Euler (aka Euler Forward) integration."""
106+
import numpy as np
107+
108+
dt = particle.dt / np.timedelta64(1, "s") # TODO improve API for converting dt to seconds
97109
(u1, v1) = fieldset.UV[particle]
98-
particle_dlon += u1 * particle.dt # noqa
99-
particle_dlat += v1 * particle.dt # noqa
110+
particle_dlon += u1 * dt # noqa
111+
particle_dlat += v1 * dt # noqa
100112

101113

102114
def AdvectionRK45(particle, fieldset, time): # pragma: no cover
@@ -109,7 +121,11 @@ def AdvectionRK45(particle, fieldset, time): # pragma: no cover
109121
Time-step dt is halved if error is larger than fieldset.RK45_tol,
110122
and doubled if error is smaller than 1/10th of tolerance.
111123
"""
112-
particle.dt = min(particle.next_dt, fieldset.RK45_max_dt)
124+
import numpy as np
125+
126+
dt = min(particle.next_dt, fieldset.RK45_max_dt) / np.timedelta64(
127+
1, "s"
128+
) # TODO improve API for converting dt to seconds
113129
c = [1.0 / 4.0, 3.0 / 8.0, 12.0 / 13.0, 1.0, 1.0 / 2.0]
114130
A = [
115131
[1.0 / 4.0, 0.0, 0.0, 0.0, 0.0],
@@ -122,39 +138,39 @@ def AdvectionRK45(particle, fieldset, time): # pragma: no cover
122138
b5 = [16.0 / 135.0, 0.0, 6656.0 / 12825.0, 28561.0 / 56430.0, -9.0 / 50.0, 2.0 / 55.0]
123139

124140
(u1, v1) = fieldset.UV[particle]
125-
lon1, lat1 = (particle.lon + u1 * A[0][0] * particle.dt, particle.lat + v1 * A[0][0] * particle.dt)
126-
(u2, v2) = fieldset.UV[time + c[0] * particle.dt, particle.depth, lat1, lon1, particle]
141+
lon1, lat1 = (particle.lon + u1 * A[0][0] * dt, particle.lat + v1 * A[0][0] * dt)
142+
(u2, v2) = fieldset.UV[time + c[0] * dt, particle.depth, lat1, lon1, particle]
127143
lon2, lat2 = (
128-
particle.lon + (u1 * A[1][0] + u2 * A[1][1]) * particle.dt,
129-
particle.lat + (v1 * A[1][0] + v2 * A[1][1]) * particle.dt,
144+
particle.lon + (u1 * A[1][0] + u2 * A[1][1]) * dt,
145+
particle.lat + (v1 * A[1][0] + v2 * A[1][1]) * dt,
130146
)
131-
(u3, v3) = fieldset.UV[time + c[1] * particle.dt, particle.depth, lat2, lon2, particle]
147+
(u3, v3) = fieldset.UV[time + c[1] * dt, particle.depth, lat2, lon2, particle]
132148
lon3, lat3 = (
133-
particle.lon + (u1 * A[2][0] + u2 * A[2][1] + u3 * A[2][2]) * particle.dt,
134-
particle.lat + (v1 * A[2][0] + v2 * A[2][1] + v3 * A[2][2]) * particle.dt,
149+
particle.lon + (u1 * A[2][0] + u2 * A[2][1] + u3 * A[2][2]) * dt,
150+
particle.lat + (v1 * A[2][0] + v2 * A[2][1] + v3 * A[2][2]) * dt,
135151
)
136-
(u4, v4) = fieldset.UV[time + c[2] * particle.dt, particle.depth, lat3, lon3, particle]
152+
(u4, v4) = fieldset.UV[time + c[2] * dt, particle.depth, lat3, lon3, particle]
137153
lon4, lat4 = (
138-
particle.lon + (u1 * A[3][0] + u2 * A[3][1] + u3 * A[3][2] + u4 * A[3][3]) * particle.dt,
139-
particle.lat + (v1 * A[3][0] + v2 * A[3][1] + v3 * A[3][2] + v4 * A[3][3]) * particle.dt,
154+
particle.lon + (u1 * A[3][0] + u2 * A[3][1] + u3 * A[3][2] + u4 * A[3][3]) * dt,
155+
particle.lat + (v1 * A[3][0] + v2 * A[3][1] + v3 * A[3][2] + v4 * A[3][3]) * dt,
140156
)
141-
(u5, v5) = fieldset.UV[time + c[3] * particle.dt, particle.depth, lat4, lon4, particle]
157+
(u5, v5) = fieldset.UV[time + c[3] * dt, particle.depth, lat4, lon4, particle]
142158
lon5, lat5 = (
143-
particle.lon + (u1 * A[4][0] + u2 * A[4][1] + u3 * A[4][2] + u4 * A[4][3] + u5 * A[4][4]) * particle.dt,
144-
particle.lat + (v1 * A[4][0] + v2 * A[4][1] + v3 * A[4][2] + v4 * A[4][3] + v5 * A[4][4]) * particle.dt,
159+
particle.lon + (u1 * A[4][0] + u2 * A[4][1] + u3 * A[4][2] + u4 * A[4][3] + u5 * A[4][4]) * dt,
160+
particle.lat + (v1 * A[4][0] + v2 * A[4][1] + v3 * A[4][2] + v4 * A[4][3] + v5 * A[4][4]) * dt,
145161
)
146-
(u6, v6) = fieldset.UV[time + c[4] * particle.dt, particle.depth, lat5, lon5, particle]
162+
(u6, v6) = fieldset.UV[time + c[4] * dt, particle.depth, lat5, lon5, particle]
147163

148-
lon_4th = (u1 * b4[0] + u2 * b4[1] + u3 * b4[2] + u4 * b4[3] + u5 * b4[4]) * particle.dt
149-
lat_4th = (v1 * b4[0] + v2 * b4[1] + v3 * b4[2] + v4 * b4[3] + v5 * b4[4]) * particle.dt
150-
lon_5th = (u1 * b5[0] + u2 * b5[1] + u3 * b5[2] + u4 * b5[3] + u5 * b5[4] + u6 * b5[5]) * particle.dt
151-
lat_5th = (v1 * b5[0] + v2 * b5[1] + v3 * b5[2] + v4 * b5[3] + v5 * b5[4] + v6 * b5[5]) * particle.dt
164+
lon_4th = (u1 * b4[0] + u2 * b4[1] + u3 * b4[2] + u4 * b4[3] + u5 * b4[4]) * dt
165+
lat_4th = (v1 * b4[0] + v2 * b4[1] + v3 * b4[2] + v4 * b4[3] + v5 * b4[4]) * dt
166+
lon_5th = (u1 * b5[0] + u2 * b5[1] + u3 * b5[2] + u4 * b5[3] + u5 * b5[4] + u6 * b5[5]) * dt
167+
lat_5th = (v1 * b5[0] + v2 * b5[1] + v3 * b5[2] + v4 * b5[3] + v5 * b5[4] + v6 * b5[5]) * dt
152168

153169
kappa = math.sqrt(math.pow(lon_5th - lon_4th, 2) + math.pow(lat_5th - lat_4th, 2))
154-
if (kappa <= fieldset.RK45_tol) or (math.fabs(particle.dt) < math.fabs(fieldset.RK45_min_dt)):
170+
if (kappa <= fieldset.RK45_tol) or (math.fabs(dt) < math.fabs(fieldset.RK45_min_dt)):
155171
particle_dlon += lon_4th # noqa
156172
particle_dlat += lat_4th # noqa
157-
if (kappa <= fieldset.RK45_tol) / 10 and (math.fabs(particle.dt * 2) <= math.fabs(fieldset.RK45_max_dt)):
173+
if (kappa <= fieldset.RK45_tol) / 10 and (math.fabs(dt * 2) <= math.fabs(fieldset.RK45_max_dt)):
158174
particle.next_dt *= 2
159175
else:
160176
particle.next_dt /= 2
@@ -174,13 +190,14 @@ def AdvectionAnalytical(particle, fieldset, time): # pragma: no cover
174190

175191
tol = 1e-10
176192
I_s = 10 # number of intermediate time steps
177-
direction = 1.0 if particle.dt > 0 else -1.0
193+
dt = particle.dt / np.timedelta64(1, "s") # TODO improve API for converting dt to seconds
194+
direction = 1.0 if dt > 0 else -1.0
178195
withW = True if "W" in [f.name for f in fieldset.fields.values()] else False
179196
withTime = True if len(fieldset.U.grid.time) > 1 else False
180197
tau, zeta, eta, xsi, ti, zi, yi, xi = fieldset.U._search_indices(
181198
time, particle.depth, particle.lat, particle.lon, particle=particle
182199
)
183-
ds_t = particle.dt
200+
ds_t = dt
184201
if withTime:
185202
time_i = np.linspace(0, fieldset.U.grid.time[ti + 1] - fieldset.U.grid.time[ti], I_s)
186203
ds_t = min(ds_t, time_i[np.where(time - fieldset.U.grid.time[ti] < time_i)[0][0]])
@@ -329,6 +346,6 @@ def compute_rs(r, B, delta, s_min):
329346
particle_ddepth += (1.0 - rs_z) * pz[0] + rs_z * pz[1] - particle.depth # noqa
330347

331348
if particle.dt > 0:
332-
particle.dt = max(direction * s_min * (dxdy * dz), 1e-7)
349+
particle.dt = max(direction * s_min * (dxdy * dz), 1e-7).astype("timedelta64[s]")
333350
else:
334-
particle.dt = min(direction * s_min * (dxdy * dz), -1e-7)
351+
particle.dt = min(direction * s_min * (dxdy * dz), -1e-7).astype("timedelta64[s]")

0 commit comments

Comments
 (0)