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
2935def 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
4046def 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
5460def 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
6975def 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
9197def 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