|
| 1 | +--- |
| 2 | +file_format: mystnb |
| 3 | +kernelspec: |
| 4 | + name: python3 |
| 5 | +--- |
| 6 | + |
| 7 | +# Working with Status Codes |
| 8 | + |
| 9 | +In order to capture errors in the [Kernel loop](explanation_kernelloop.md), Parcels uses a Status Code system. There are several Status Codes, listed below. |
| 10 | + |
| 11 | +```{code-cell} |
| 12 | +import parcels |
| 13 | +
|
| 14 | +for statuscode, val in parcels.StatusCode.__dict__.items(): |
| 15 | + if statuscode.startswith("__"): |
| 16 | + continue |
| 17 | + print(f"{statuscode} = {val}") |
| 18 | +``` |
| 19 | + |
| 20 | +Once an error is thrown (for example, a Field Interpolation error), then the `particles.state` is updated to the corresponding status code. This gives you the flexibility to write a Kernel that checks for a status code and does something with it. |
| 21 | + |
| 22 | +For example, you can write a Kernel that checks for `particles.state == parcels.StatusCode.ErrorOutOfBounds` and deletes the particle, and then append this custom Kernel to the Kernel list in `pset.execute()`. |
| 23 | + |
| 24 | +``` |
| 25 | +def DeleteOutOfBounds(particles, fieldset): |
| 26 | + out_of_bounds = particles.state == parcels.StatusCode.ErrorOutOfBounds |
| 27 | + particles[out_of_bounds].state = parcels.StatusCode.Delete |
| 28 | +
|
| 29 | +
|
| 30 | +def DeleteAnyError(particles, fieldset): |
| 31 | + any_error = particles.state >= 50 # This captures all Errors |
| 32 | + particles[any_error].state = parcels.StatusCode.Delete |
| 33 | +``` |
| 34 | + |
| 35 | +But of course, you can also write code for more sophisticated behaviour than just deleting the particle. It's up to you! Note that if you don't delete the particle, you will have to update the `particles.state = parcels.StatusCode.Evaluate` yourself. For example: |
| 36 | + |
| 37 | +``` |
| 38 | +def Move1DegreeWest(particles, fieldset): |
| 39 | + out_of_bounds = particles.state == parcels.StatusCode.ErrorOutOfBounds |
| 40 | + particles[out_of_bounds].dlon -= 1.0 |
| 41 | + particles[out_of_bounds].state = parcels.StatusCode.Evaluate |
| 42 | +``` |
| 43 | + |
| 44 | +Or, if you want to make sure that particles don't escape through the water surface |
| 45 | + |
| 46 | +```{code-cell} |
| 47 | +def KeepInOcean(particles, fieldset): |
| 48 | + # find particles that move through the surface |
| 49 | + through_surface = particles.state == parcels.StatusCode.ErrorThroughSurface |
| 50 | +
|
| 51 | + # move particles to surface |
| 52 | + particles[through_surface].dz = fieldset.W.grid.depth[0] - particles[through_surface].z |
| 53 | +
|
| 54 | + # change state from error to evaluate |
| 55 | + particles[through_surface].state = parcels.StatusCode.Evaluate |
| 56 | +``` |
| 57 | + |
| 58 | +Kernel functions such as the ones above can then be added to the list of kernels in `pset.execute()`. |
| 59 | + |
| 60 | +Let's add the `KeepInOcean` Kernel to an particle simulation where particles move through the surface: |
| 61 | + |
| 62 | +```{code-cell} |
| 63 | +import numpy as np |
| 64 | +from parcels._datasets.structured.generated import simple_UV_dataset |
| 65 | +
|
| 66 | +ds = simple_UV_dataset(dims=(1, 2, 5, 4), mesh="flat").isel(time=0) |
| 67 | +
|
| 68 | +dx, dy = 1.0 / len(ds.XG), 1.0 / len(ds.YG) |
| 69 | +
|
| 70 | +# Add W velocity that pushes through surface |
| 71 | +ds["W"] = ds["U"] - 0.1 # 0.1 m/s towards the surface |
| 72 | +
|
| 73 | +grid = parcels.XGrid.from_dataset(ds, mesh="flat") |
| 74 | +U = parcels.Field("U", ds["U"], grid, interp_method=parcels.interpolators.XLinear) |
| 75 | +V = parcels.Field("V", ds["V"], grid, interp_method=parcels.interpolators.XLinear) |
| 76 | +W = parcels.Field("W", ds["W"], grid, interp_method=parcels.interpolators.XLinear) |
| 77 | +UVW = parcels.VectorField("UVW", U, V, W) |
| 78 | +fieldset = parcels.FieldSet([U, V, W, UVW]) |
| 79 | +``` |
| 80 | + |
| 81 | +If we advect particles with the `AdvectionRK2_3D` kernel, Parcels will raise a `FieldOutOfBoundSurfaceError`: |
| 82 | + |
| 83 | +```{code-cell} |
| 84 | +:tags: [raises-exception] |
| 85 | +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], lat=[2], lon=[1.5]) |
| 86 | +kernels = [parcels.kernels.AdvectionRK2_3D] |
| 87 | +pset.execute(kernels, runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), verbose_progress=False) |
| 88 | +``` |
| 89 | + |
| 90 | +When we add the `KeepInOcean` Kernel, particles will stay at the surface: |
| 91 | + |
| 92 | +```{code-cell} |
| 93 | +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], lat=[2], lon=[1.5]) |
| 94 | +
|
| 95 | +kernels = [parcels.kernels.AdvectionRK2_3D, KeepInOcean] |
| 96 | +
|
| 97 | +pset.execute(kernels,runtime=np.timedelta64(20, "s"), dt=np.timedelta64(1, "s"), verbose_progress=False) |
| 98 | +
|
| 99 | +print(f"particle z at end of run = {pset.z}") |
| 100 | +``` |
| 101 | + |
| 102 | +```{note} |
| 103 | +Kernels that control what to do with `particles.state` should typically be added at the _end_ of the Kernel list, because otherwise later Kernels may overwrite the `particles.state` or the `particles.dlon` variables (see [Kernel loop explanation](explanation_kernelloop.md)). |
| 104 | +``` |
0 commit comments