From e641f33110585b7f335af7d270c358e543764322 Mon Sep 17 00:00:00 2001 From: reint-fischer Date: Mon, 24 Nov 2025 19:58:37 +0100 Subject: [PATCH 1/3] add statuscodes tutorial --- .../examples/tutorial_statuscodes.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/user_guide/examples/tutorial_statuscodes.md diff --git a/docs/user_guide/examples/tutorial_statuscodes.md b/docs/user_guide/examples/tutorial_statuscodes.md new file mode 100644 index 000000000..ca0a6e75c --- /dev/null +++ b/docs/user_guide/examples/tutorial_statuscodes.md @@ -0,0 +1,101 @@ +--- +file_format: mystnb +kernelspec: + name: python3 +--- + +# Working with Status Codes + +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. + +```{code-cell} +import parcels + +for statuscode, val in parcels.StatusCode.__dict__.items(): + if statuscode.startswith("__"): + continue + print(f"{statuscode} = {val}") +``` + +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. + +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()`. + +``` +def DeleteOutOfBounds(particles, fieldset): + out_of_bounds = particles.state == parcels.StatusCode.ErrorOutOfBounds + particles[out_of_bounds].state = parcels.StatusCode.Delete + + +def DeleteAnyError(particles, fieldset): + any_error = particles.state >= 50 # This captures all Errors + particles[any_error].state = parcels.StatusCode.Delete +``` + +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: + +``` +def Move1DegreeWest(particles, fieldset): + out_of_bounds = particles.state == parcels.StatusCode.ErrorOutOfBounds + particles[out_of_bounds].dlon -= 1.0 + particles[out_of_bounds].state = parcels.StatusCode.Evaluate +``` + +Or, if you want to make sure that particles don't escape through the water surface + +```{code-cell} +def KeepInOcean(particles, fieldset): + # find particles that move through the surface + through_surface = particles.state == parcels.StatusCode.ErrorThroughSurface + + # move particles to surface + particles[through_surface].dz = fieldset.W.grid.depth[0] - particles[through_surface].z + + # change state from error to evaluate + particles[through_surface].state = parcels.StatusCode.Evaluate +``` + +Kernel functions such as the ones above can then be added to the list of kernels in `pset.execute()`. + +Let's add the `KeepInOcean` Kernel to an particle simulation where particles move through the surface: + +```{code-cell} +import numpy as np +from parcels._datasets.structured.generated import simple_UV_dataset + +ds = simple_UV_dataset(dims=(1, 2, 5, 4), mesh="flat").isel(time=0) + +dx, dy = 1.0 / len(ds.XG), 1.0 / len(ds.YG) + +# Add W velocity that pushes through surface +ds["W"] = ds["U"] - 0.1 # 0.1 m/s towards the surface + +grid = parcels.XGrid.from_dataset(ds, mesh="flat") +U = parcels.Field("U", ds["U"], grid, interp_method=parcels.interpolators.XLinear) +V = parcels.Field("V", ds["V"], grid, interp_method=parcels.interpolators.XLinear) +W = parcels.Field("W", ds["W"], grid, interp_method=parcels.interpolators.XLinear) +UVW = parcels.VectorField("UVW", U, V, W) +fieldset = parcels.FieldSet([U, V, W, UVW]) +``` + +If we advect particles with the `AdvectionRK2_3D` kernel, Parcels will raise a `FieldOutOfBoundSurfaceError`: + +```{code-cell} +:tags: [raises-exception] +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0],lat=[2], lon=[1.5]) +pset.execute([parcels.kernels.AdvectionRK2_3D],runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), verbose_progress=False) +``` + +When we add the `KeepInOcean` Kernel, particles will stay at the surface: + +```{code-cell} +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5],lat=[2], lon=[1.5]) + +kernels = [parcels.kernels.AdvectionRK2_3D, KeepInOcean] + +pset.execute(kernels,runtime=np.timedelta64(20, "s"), dt=np.timedelta64(1, "s"), verbose_progress=False) +``` + +```{note} +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. +``` From 217f1cb973c550723eaec1b146c4d1e6863f4886 Mon Sep 17 00:00:00 2001 From: reint-fischer Date: Tue, 25 Nov 2025 10:14:11 +0100 Subject: [PATCH 2/3] write statuscodes tutorial --- docs/user_guide/examples/tutorial_statuscodes.md | 4 +++- docs/user_guide/index.md | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/user_guide/examples/tutorial_statuscodes.md b/docs/user_guide/examples/tutorial_statuscodes.md index ca0a6e75c..e7345e578 100644 --- a/docs/user_guide/examples/tutorial_statuscodes.md +++ b/docs/user_guide/examples/tutorial_statuscodes.md @@ -82,7 +82,7 @@ If we advect particles with the `AdvectionRK2_3D` kernel, Parcels will raise a ` ```{code-cell} :tags: [raises-exception] -pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0],lat=[2], lon=[1.5]) +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5],lat=[2], lon=[1.5]) pset.execute([parcels.kernels.AdvectionRK2_3D],runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), verbose_progress=False) ``` @@ -94,6 +94,8 @@ pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5],lat=[2], lon=[1.5 kernels = [parcels.kernels.AdvectionRK2_3D, KeepInOcean] pset.execute(kernels,runtime=np.timedelta64(20, "s"), dt=np.timedelta64(1, "s"), verbose_progress=False) + +print(f"particle z at end of run = {pset.z}") ``` ```{note} diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index c3fe1a3cb..1166c3f28 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -45,6 +45,7 @@ examples/tutorial_delaystart.ipynb :titlesonly: examples/tutorial_sampling.ipynb +examples/tutorial_statuscodes.ipynb examples/tutorial_gsw_density.ipynb examples/tutorial_Argofloats.ipynb ``` From c09bd55e1e2854f308d96702511dc477e650bfb9 Mon Sep 17 00:00:00 2001 From: Reint Date: Tue, 25 Nov 2025 11:09:00 +0100 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Erik van Sebille --- docs/user_guide/examples/tutorial_statuscodes.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/user_guide/examples/tutorial_statuscodes.md b/docs/user_guide/examples/tutorial_statuscodes.md index e7345e578..9cde0df49 100644 --- a/docs/user_guide/examples/tutorial_statuscodes.md +++ b/docs/user_guide/examples/tutorial_statuscodes.md @@ -82,14 +82,15 @@ If we advect particles with the `AdvectionRK2_3D` kernel, Parcels will raise a ` ```{code-cell} :tags: [raises-exception] -pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5],lat=[2], lon=[1.5]) -pset.execute([parcels.kernels.AdvectionRK2_3D],runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), verbose_progress=False) +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], lat=[2], lon=[1.5]) +kernels = [parcels.kernels.AdvectionRK2_3D] +pset.execute(kernels, runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), verbose_progress=False) ``` When we add the `KeepInOcean` Kernel, particles will stay at the surface: ```{code-cell} -pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5],lat=[2], lon=[1.5]) +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], lat=[2], lon=[1.5]) kernels = [parcels.kernels.AdvectionRK2_3D, KeepInOcean] @@ -99,5 +100,5 @@ print(f"particle z at end of run = {pset.z}") ``` ```{note} -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. +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)). ```