|
26 | 26 | "\n", |
27 | 27 | "When you run a Parcels simulation (i.e. a call to `pset.execute()`), the Kernel loop is the main part of the code that is executed. This part of the code loops through all particles and executes the Kernels that are defined for each particle.\n", |
28 | 28 | "\n", |
29 | | - "In order to make sure that the displacements of a particle in the different Kernels can be summed, all Kernels add to a _change_ in position (`particles.dlon`, `particles.dlat`, and `particles.ddepth`). This is important, because there are situations where movement kernels would otherwise not commute. Take the example of advecting particles by currents _and_ winds. If the particle would first be moved by the currents and then by the winds, the result could be different from first moving by the winds and then by the currents. Instead, by adding the changes in position, the ordering of the Kernels has no consequence on the particle displacement." |
| 29 | + "In order to make sure that the displacements of a particle in the different Kernels can be summed, all Kernels add to a _change_ in position (`particles.dlon`, `particles.dlat`, and `particles.dz`). This is important, because there are situations where movement kernels would otherwise not commute. Take the example of advecting particles by currents _and_ winds. If the particle would first be moved by the currents and then by the winds, the result could be different from first moving by the winds and then by the currents. Instead, by adding the changes in position, the ordering of the Kernels has no consequence on the particle displacement." |
30 | 30 | ] |
31 | 31 | }, |
32 | 32 | { |
|
40 | 40 | "cell_type": "markdown", |
41 | 41 | "metadata": {}, |
42 | 42 | "source": [ |
43 | | - "Below is a structured overview of the Kernel loop is implemented. Note that this is for longitude only, but the same process is applied for latitude and depth.\n", |
| 43 | + "Below is a structured overview of the Kernel loop is implemented. Note that this is for `lon` only, but the same process is applied for `lat` and `z`.\n", |
44 | 44 | "\n", |
45 | | - "1. Initialise an extra Variable `particles.lon=0` and `particles.time_nextloop = particles.time`\n", |
| 45 | + "1. Initialise an extra Variable `particles.dlon=0`\n", |
46 | 46 | "\n", |
47 | 47 | "2. Within the Kernel loop, for each particle:<br>\n", |
48 | 48 | "\n", |
49 | 49 | " 1. Update `particles.lon += particles.dlon`<br>\n", |
50 | 50 | "\n", |
51 | | - " 2. Set variable `particles.dlon = 0`<br>\n", |
| 51 | + " 2. Update `particles.time += particles.dt` (except for on the first iteration of the Kernel loop)<br>\n", |
52 | 52 | "\n", |
53 | | - " 3. Update `particles.time = particles.time_nextloop`\n", |
| 53 | + " 3. Set variable `particles.dlon = 0`<br>\n", |
54 | 54 | "\n", |
55 | 55 | " 4. For each Kernel in the list of Kernels:\n", |
56 | 56 | " \n", |
57 | 57 | " 1. Execute the Kernel\n", |
58 | 58 | " \n", |
59 | 59 | " 2. Update `particles.dlon` by adding the change in longitude, if needed<br>\n", |
60 | 60 | "\n", |
61 | | - " 5. Update `particles.time_nextloop += particles.dt`<br>\n", |
| 61 | + " 5. If `outputdt` is a multiple of `particle.time`, write `particle.lon` and `particle.time` to zarr output file<br>\n", |
62 | 62 | "\n", |
63 | | - " 6. If `outputdt` is a multiple of `particle.time`, write `particle.lon` and `particle.time` to zarr output file<br>\n", |
64 | | - "\n", |
65 | | - "Besides having commutable Kernels, the main advantage of this implementation is that, when using Field Sampling with e.g. `particle.temp = fieldset.Temp[particle.time, particle.depth, particle.lat, particle.lon]`, the particle location stays the same throughout the entire Kernel loop. Additionally, this implementation ensures that the particle location is the same as the location of the sampled field in the output file." |
| 63 | + "Besides having commutable Kernels, the main advantage of this implementation is that, when using Field Sampling with e.g. `particle.temp = fieldset.Temp[particle.time, particle.z, particle.lat, particle.lon]`, the particle location stays the same throughout the entire Kernel loop. Additionally, this implementation ensures that the particle location is the same as the location of the sampled field in the output file." |
66 | 64 | ] |
67 | 65 | }, |
68 | 66 | { |
|
155 | 153 | "source": [ |
156 | 154 | "def wind_kernel(particle, fieldset, time):\n", |
157 | 155 | " particle_dlon += (\n", |
158 | | - " fieldset.UWind[time, particle.depth, particle.lat, particle.lon] * particle.dt\n", |
| 156 | + " fieldset.UWind[time, particle.z, particle.lat, particle.lon] * particle.dt\n", |
159 | 157 | " )\n", |
160 | 158 | " particle_dlat += (\n", |
161 | | - " fieldset.VWind[time, particle.depth, particle.lat, particle.lon] * particle.dt\n", |
| 159 | + " fieldset.VWind[time, particle.z, particle.lat, particle.lon] * particle.dt\n", |
162 | 160 | " )" |
163 | 161 | ] |
164 | 162 | }, |
|
242 | 240 | "cell_type": "markdown", |
243 | 241 | "metadata": {}, |
244 | 242 | "source": [ |
245 | | - "## Caveats" |
| 243 | + "## Caveat: Avoid updating particle locations directly in Kernels" |
246 | 244 | ] |
247 | 245 | }, |
248 | 246 | { |
249 | 247 | "cell_type": "markdown", |
250 | 248 | "metadata": {}, |
251 | 249 | "source": [ |
252 | | - "There are a few important considerations to take into account when writing Kernels\n", |
253 | | - "\n", |
254 | | - "### 1. Avoid updating particle locations directly in Kernels\n", |
255 | 250 | "It is better not to update `particle.lon` directly in a Kernel, as it can interfere with the loop above. Assigning a value to `particle.lon` in a Kernel will throw a warning. \n", |
256 | 251 | "\n", |
257 | | - "Instead, update the local variable `particle.dlon`.\n", |
258 | | - "\n", |
259 | | - "### 2. Be careful with updating particle variables that do not depend on Fields.\n", |
260 | | - "While assigning the interpolated value of a `Field` to a Particle goes well in the loop above, this is not necessarily so for assigning other attributes. For example, a line like `particle.age += particle.dt` is executed directly so may result in the age being `dt` at `time = 0` in the output file. \n", |
261 | | - "\n", |
262 | | - "A workaround is to either initialise the age to `-dt`, or to increase the `age` only when `particle.time > 0` (using an `np.where` statement).\n", |
263 | | - "\n", |
264 | | - "\n", |
265 | | - "### 3. The last time is not written to file\n", |
266 | | - "Because the location at the start of the loop is written at the end of the Kernel loop, the last `particle.time` of the particle is not written to file. This is similar behaviour to e.g. `np.arange(start, stop)`, which also doesn't include the `stop` value itself. \n", |
267 | | - "\n", |
268 | | - "If you do want to write the last time to file, you can increase the `runtime` or `endtime` by `dt` (although this may cause an OutsideTimeInterval if your run was to the end of the available hydrodynamic data), or you can call `pfile.write_latest_locations(pset, time=pset[0].time_nextloop)`. Note that in the latter case, the particle locations (longitude, latitude and depth) will be updated, but other variables will _not_ be updated as the Kernels are not run again." |
| 252 | + "Instead, update the local variable `particle.dlon`." |
269 | 253 | ] |
270 | 254 | }, |
271 | 255 | { |
|
355 | 339 | "source": [ |
356 | 340 | "def KeepInOcean(particle, fieldset, time):\n", |
357 | 341 | " if particle.state == StatusCode.ErrorThroughSurface:\n", |
358 | | - " particle_ddepth = 0.0\n", |
| 342 | + " particle_dz = 0.0\n", |
359 | 343 | " particle.state = StatusCode.Success" |
360 | 344 | ] |
361 | 345 | }, |
|
0 commit comments