From e2a218d20d6eb44ae1b17ed3ff2cbb56944c7497 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 07:37:05 +0100 Subject: [PATCH 01/11] Cleaning up the documentation - capitalising Kernels --- docs/getting_started/explanation_concepts.md | 4 ++-- docs/user_guide/examples/explanation_kernelloop.md | 10 +++++----- docs/user_guide/examples/tutorial_Argofloats.ipynb | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/getting_started/explanation_concepts.md b/docs/getting_started/explanation_concepts.md index bce1b30eb6..d71e3fce4a 100644 --- a/docs/getting_started/explanation_concepts.md +++ b/docs/getting_started/explanation_concepts.md @@ -149,7 +149,7 @@ Every Kernel must be a function with the following (and only those) arguments: ` ``` ```{warning} -We have to be careful with writing kernels for vector fields on Curvilinear grids. While Parcels automatically rotates the "U" and "V" field when necessary, this is not the case for other fields such as Stokes drift. [This guide](../user_guide/examples/tutorial_nemo_curvilinear.ipynb) describes how to use a curvilinear grid in Parcels. +We have to be careful with kernels that sample velocities on "spherical" grids (so with longitude and latitude in degrees). Parcels can automatically convert velocities from m s-1 to degrees s-1, but only when using `VectorFields`. [This guide](../user_guide/examples/tutorial_velocityconversion.ipynb) describes how to use velocities on a "spherical" grid in Parcels. ``` ```{admonition} 📖 Read more about the Kernel loop @@ -157,7 +157,7 @@ We have to be careful with writing kernels for vector fields on Curvilinear grid - [The Kernel loop](../user_guide/examples/explanation_kernelloop.md) ``` -```{admonition} 🖥️ Learn how to write kernels +```{admonition} 🖥️ Learn how to write Kernels :class: seealso - [Sample fields like temperature](../user_guide/examples/tutorial_sampling.ipynb). - [Mimic the behaviour of ARGO floats](../user_guide/examples/tutorial_Argofloats.ipynb). diff --git a/docs/user_guide/examples/explanation_kernelloop.md b/docs/user_guide/examples/explanation_kernelloop.md index 025305ce32..0e5c011ee1 100644 --- a/docs/user_guide/examples/explanation_kernelloop.md +++ b/docs/user_guide/examples/explanation_kernelloop.md @@ -8,13 +8,13 @@ kernelspec: On this page we discuss Parcels' execution loop, and what happens under the hood when you combine multiple Kernels. -This is not very relevant when you only use the built-in Advection kernels, but can be important when you are writing and combining your own Kernels! +This is not very relevant when you only use the built-in Advection Kernels, but can be important when you are writing and combining your own Kernels! ## Background 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 time and executes the Kernels for all particle. -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 summing the _changes_ in position, the ordering of the Kernels has no consequence on the particle displacement. +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 summing the _changes_ in position, the ordering of the Kernels has no consequence on the particle displacement. ## Basic implementation @@ -80,7 +80,7 @@ windvector = parcels.VectorField( fieldset.add_field(windvector) ``` -Now we define a wind kernel that uses a forward Euler method to apply the wind forcing. Note that we update the `particles.dlon` and `particles.dlat` variables, rather than `particles.lon` and `particles.lat` directly. +Now we define a wind Kernel that uses a forward Euler method to apply the wind forcing. Note that we update the `particles.dlon` and `particles.dlat` variables, rather than `particles.lon` and `particles.lat` directly. ```{code-cell} def wind_kernel(particles, fieldset): @@ -89,7 +89,7 @@ def wind_kernel(particles, fieldset): particles.dlat += vwind * particles.dt ``` -First run a simulation where we apply kernels as `[AdvectionRK4, wind_kernel]` +First run a simulation where we apply Kernels as `[AdvectionRK4, wind_kernel]` ```{code-cell} :tags: [hide-output] @@ -110,7 +110,7 @@ pset.execute( ) ``` -Then also run a simulation where we apply the kernels in the reverse order as `[wind_kernel, AdvectionRK4]` +Then also run a simulation where we apply the Kernels in the reverse order as `[wind_kernel, AdvectionRK4]` ```{code-cell} :tags: [hide-output] diff --git a/docs/user_guide/examples/tutorial_Argofloats.ipynb b/docs/user_guide/examples/tutorial_Argofloats.ipynb index 94cb9808ce..02b45e3c59 100644 --- a/docs/user_guide/examples/tutorial_Argofloats.ipynb +++ b/docs/user_guide/examples/tutorial_Argofloats.ipynb @@ -15,7 +15,7 @@ "source": [ "This tutorial shows how simple it is to construct a Kernel in Parcels that mimics the [vertical movement of Argo floats](https://www.aoml.noaa.gov/phod/argo/images/argo_float_mission.jpg).\n", "\n", - "We first define the kernels for each phase of the Argo cycle." + "We first define the Kernel for the vertical movement cycle of the Argo float." ] }, { @@ -90,7 +90,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "And then we can run Parcels with this 'custom kernel'.\n", + "And then we can run Parcels with this `ArgoVerticalMovement` Kernel.\n", "\n", "Below we use the horizontal velocity fields of CopernicusMarine, which are provided as example_data with Parcels.\n" ] @@ -143,7 +143,7 @@ " z=[fieldset.mindepth],\n", ")\n", "\n", - "# combine Argo vertical movement kernel with built-in Advection kernel\n", + "# combine Argo vertical movement Kernel with built-in Advection Kernel\n", "kernels = [\n", " ArgoVerticalMovement,\n", " parcels.kernels.AdvectionRK4,\n", @@ -156,7 +156,7 @@ " chunks=(1, 500), # setting to write in chunks of 500 observations\n", ")\n", "\n", - "# Now execute the kernels for 30 days, saving data every 30 minutes\n", + "# Now execute the Kernels for 30 days, saving data every 30 minutes\n", "pset.execute(\n", " kernels,\n", " runtime=timedelta(days=30),\n", @@ -239,7 +239,7 @@ ], "metadata": { "kernelspec": { - "display_name": "test-notebooks", + "display_name": "docs", "language": "python", "name": "python3" }, @@ -253,7 +253,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.14.2" } }, "nbformat": 4, From fe70ecd2b4c97fa3651d48ead9cd0668ba3d0775 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 07:49:49 +0100 Subject: [PATCH 02/11] Changing from RK4 to RK2 in tutorials As RK2 is now advised as standard advection kernel --- docs/getting_started/explanation_concepts.md | 2 +- docs/getting_started/tutorial_output.ipynb | 6 +++--- docs/user_guide/examples/explanation_kernelloop.md | 8 ++++---- docs/user_guide/examples/tutorial_Argofloats.ipynb | 2 +- docs/user_guide/examples/tutorial_delaystart.ipynb | 8 ++++---- docs/user_guide/examples/tutorial_diffusion.ipynb | 4 ++-- docs/user_guide/examples/tutorial_sampling.ipynb | 6 +++--- docs/user_guide/v4-migration.md | 1 + 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/getting_started/explanation_concepts.md b/docs/getting_started/explanation_concepts.md index d71e3fce4a..560770bcd2 100644 --- a/docs/getting_started/explanation_concepts.md +++ b/docs/getting_started/explanation_concepts.md @@ -141,7 +141,7 @@ def Age(particles, fieldset): particles.age += particles.dt # define all kernels to be executed on particles using an (ordered) list -kernels = [Age, parcels.kernels.AdvectionRK4] +kernels = [Age, parcels.kernels.AdvectionRK2] ``` ```{note} diff --git a/docs/getting_started/tutorial_output.ipynb b/docs/getting_started/tutorial_output.ipynb index dfd1c44581..b8d8e844de 100644 --- a/docs/getting_started/tutorial_output.ipynb +++ b/docs/getting_started/tutorial_output.ipynb @@ -117,7 +117,7 @@ "outputs": [], "source": [ "pset.execute(\n", - " parcels.kernels.AdvectionRK4,\n", + " parcels.kernels.AdvectionRK2,\n", " runtime=np.timedelta64(48, \"h\"),\n", " dt=np.timedelta64(5, \"m\"),\n", " output_file=output_file,\n", @@ -557,7 +557,7 @@ "metadata": { "celltoolbar": "Metagegevens bewerken", "kernelspec": { - "display_name": "test-notebooks", + "display_name": "docs", "language": "python", "name": "python3" }, @@ -571,7 +571,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.0" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/explanation_kernelloop.md b/docs/user_guide/examples/explanation_kernelloop.md index 0e5c011ee1..a9d0fc122a 100644 --- a/docs/user_guide/examples/explanation_kernelloop.md +++ b/docs/user_guide/examples/explanation_kernelloop.md @@ -89,7 +89,7 @@ def wind_kernel(particles, fieldset): particles.dlat += vwind * particles.dt ``` -First run a simulation where we apply Kernels as `[AdvectionRK4, wind_kernel]` +First run a simulation where we apply Kernels as `[AdvectionRK2, wind_kernel]` ```{code-cell} :tags: [hide-output] @@ -103,14 +103,14 @@ output_file = parcels.ParticleFile( store="advection_then_wind.zarr", outputdt=np.timedelta64(6,'h') ) pset.execute( - [parcels.kernels.AdvectionRK4, wind_kernel], + [parcels.kernels.AdvectionRK2, wind_kernel], runtime=np.timedelta64(5,'D'), dt=np.timedelta64(1,'h'), output_file=output_file, ) ``` -Then also run a simulation where we apply the Kernels in the reverse order as `[wind_kernel, AdvectionRK4]` +Then also run a simulation where we apply the Kernels in the reverse order as `[wind_kernel, AdvectionRK2]` ```{code-cell} :tags: [hide-output] @@ -121,7 +121,7 @@ output_file_reverse = parcels.ParticleFile( store="wind_then_advection.zarr", outputdt=np.timedelta64(6,"h") ) pset_reverse.execute( - [wind_kernel, parcels.kernels.AdvectionRK4], + [wind_kernel, parcels.kernels.AdvectionRK2], runtime=np.timedelta64(5,"D"), dt=np.timedelta64(1,"h"), output_file=output_file_reverse, diff --git a/docs/user_guide/examples/tutorial_Argofloats.ipynb b/docs/user_guide/examples/tutorial_Argofloats.ipynb index 02b45e3c59..06b6491c06 100644 --- a/docs/user_guide/examples/tutorial_Argofloats.ipynb +++ b/docs/user_guide/examples/tutorial_Argofloats.ipynb @@ -146,7 +146,7 @@ "# combine Argo vertical movement Kernel with built-in Advection Kernel\n", "kernels = [\n", " ArgoVerticalMovement,\n", - " parcels.kernels.AdvectionRK4,\n", + " parcels.kernels.AdvectionRK2,\n", "]\n", "\n", "# Create a ParticleFile object to store the output\n", diff --git a/docs/user_guide/examples/tutorial_delaystart.ipynb b/docs/user_guide/examples/tutorial_delaystart.ipynb index 365a2ed28d..a997533e49 100644 --- a/docs/user_guide/examples/tutorial_delaystart.ipynb +++ b/docs/user_guide/examples/tutorial_delaystart.ipynb @@ -117,7 +117,7 @@ ")\n", "\n", "pset.execute(\n", - " parcels.kernels.AdvectionRK4,\n", + " parcels.kernels.AdvectionRK2,\n", " runtime=np.timedelta64(24, \"h\"),\n", " dt=np.timedelta64(5, \"m\"),\n", " output_file=output_file,\n", @@ -256,7 +256,7 @@ ")\n", "\n", "pset.execute(\n", - " parcels.kernels.AdvectionRK4,\n", + " parcels.kernels.AdvectionRK2,\n", " runtime=np.timedelta64(24, \"h\"),\n", " dt=np.timedelta64(5, \"h\"),\n", " output_file=output_file,\n", @@ -363,7 +363,7 @@ "\n", "output_file = parcels.ParticleFile(outfilepath, outputdt=np.timedelta64(2, \"h\"))\n", "pset.execute(\n", - " parcels.kernels.AdvectionRK4,\n", + " parcels.kernels.AdvectionRK2,\n", " endtime=ds_fields.time.values[0] + np.timedelta64(4, \"h\"),\n", " dt=np.timedelta64(1, \"h\"),\n", " output_file=output_file,\n", @@ -420,7 +420,7 @@ " )\n", " output_file = parcels.ParticleFile(outfilepath, outputdt=np.timedelta64(2, \"h\"))\n", " pset.execute(\n", - " parcels.kernels.AdvectionRK4,\n", + " parcels.kernels.AdvectionRK2,\n", " runtime=np.timedelta64(4, \"h\"),\n", " dt=np.timedelta64(1, \"h\"),\n", " output_file=output_file,\n", diff --git a/docs/user_guide/examples/tutorial_diffusion.ipynb b/docs/user_guide/examples/tutorial_diffusion.ipynb index a0aeac6b87..a95e50580a 100644 --- a/docs/user_guide/examples/tutorial_diffusion.ipynb +++ b/docs/user_guide/examples/tutorial_diffusion.ipynb @@ -543,7 +543,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In the example, particles are released at one location and simulated for 2 days using advection (`AdvectionRK4`) and diffusion (`smagdiff`) kernels." + "In the example, particles are released at one location and simulated for 2 days using advection (`AdvectionRK2`) and diffusion (`smagdiff`) kernels." ] }, { @@ -576,7 +576,7 @@ "np.random.seed(1636) # Random seed for reproducibility\n", "\n", "pset.execute(\n", - " [parcels.kernels.AdvectionRK4, smagdiff],\n", + " [parcels.kernels.AdvectionRK2, smagdiff],\n", " runtime=np.timedelta64(2, \"D\"),\n", " dt=np.timedelta64(5, \"m\"),\n", " output_file=output_file,\n", diff --git a/docs/user_guide/examples/tutorial_sampling.ipynb b/docs/user_guide/examples/tutorial_sampling.ipynb index 4fc1ad7e16..10d0a9321f 100644 --- a/docs/user_guide/examples/tutorial_sampling.ipynb +++ b/docs/user_guide/examples/tutorial_sampling.ipynb @@ -137,7 +137,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can then sample and Advect by combining the `SampleT` and `AdvectionRK4` kernels in a list. Note that the order does not matter.\n" + "We can then sample and advect by combining the `SampleT` and `AdvectionRK2` kernels in a list. Note that the order does not matter.\n" ] }, { @@ -157,7 +157,7 @@ "output_file = parcels.ParticleFile(\"sampletemp.zarr\", outputdt=timedelta(hours=1))\n", "\n", "pset.execute(\n", - " [parcels.kernels.AdvectionRK4, SampleT],\n", + " [parcels.kernels.AdvectionRK2, SampleT],\n", " runtime=timedelta(hours=30),\n", " dt=timedelta(minutes=5),\n", " output_file=output_file,\n", @@ -350,7 +350,7 @@ "output_file = parcels.ParticleFile(\"writeonce.zarr\", outputdt=timedelta(hours=1))\n", "\n", "pset.execute(\n", - " [parcels.kernels.AdvectionRK4, SampleT],\n", + " [parcels.kernels.AdvectionRK2, SampleT],\n", " runtime=timedelta(hours=24),\n", " dt=timedelta(minutes=5),\n", " output_file=output_file,\n", diff --git a/docs/user_guide/v4-migration.md b/docs/user_guide/v4-migration.md index 6e2f4743df..263e006e8c 100644 --- a/docs/user_guide/v4-migration.md +++ b/docs/user_guide/v4-migration.md @@ -15,6 +15,7 @@ Version 4 of Parcels is unreleased at the moment. The information in this migrat - `math` functions should be replaced with array compatible equivalents (e.g., `math.sin` -> `np.sin`). Instead of `ParcelsRandom` you should use numpy's random functions. - `particle.depth` has been changed to `particles.z` to be consistent with the [CF conventions for trajectory data](https://cfconventions.org/cf-conventions/cf-conventions.html#trajectory-data), and to make Parcels also generalizable to atmospheric contexts. - The `InteractionKernel` class has been removed. Since normal Kernels now have access to _all_ particles, particle-particle interaction can be performed within normal Kernels. +- We added a new AdvectionRK2 Kernel. The AdvectionRK4 kernel is still available, but RK2 is now the recommended default advection scheme as it is faster while the accuracy is comparable for most applications. See also the Choosing an integration method tutorial. ## FieldSet From cb4dff1e34c6b0250583bd04980f22b51d776bb5 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 11:57:30 +0100 Subject: [PATCH 03/11] Removing units from repr Since units has been removed as part of #2459 --- src/parcels/_reprs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parcels/_reprs.py b/src/parcels/_reprs.py index 07f58b1532..dc0ef6948e 100644 --- a/src/parcels/_reprs.py +++ b/src/parcels/_reprs.py @@ -39,7 +39,6 @@ def field_repr(field: Field, level: int = 0) -> str: name : {field.name!r} interp_method : {field.interp_method!r} time_interval : {field.time_interval!r} - units : {field.units!r} igrid : {field.igrid!r} DataArray: {textwrap.indent(repr(field.data), 8 * " ")} From 05b6803b3b3db5de103395753d1c775f7bead006 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 13:29:44 +0100 Subject: [PATCH 04/11] Adding print statements to tutorial_quickstart But with [hide-output tag] so they don't flush the screen --- docs/getting_started/tutorial_quickstart.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/getting_started/tutorial_quickstart.md b/docs/getting_started/tutorial_quickstart.md index b843ea9df5..81297e3c3e 100644 --- a/docs/getting_started/tutorial_quickstart.md +++ b/docs/getting_started/tutorial_quickstart.md @@ -49,6 +49,13 @@ recognizes the standard names of a velocity field: fieldset = parcels.FieldSet.from_copernicusmarine(ds_fields) ``` +You can inspect the `fieldset` by simply printing it: + +```{code-cell} +:tags: [hide-output] +print(fieldset) +``` + The subset contains a region of the Agulhas current along the southeastern coast of Africa: ```{code-cell} @@ -80,6 +87,15 @@ pset = parcels.ParticleSet( ) ``` +Again, you can inspect the `pset` by printing it: + +```{code-cell} +:tags: [hide-output] +print(pset) +``` + +And you can plot the particles on top of the temperature and velocity field: + ```{code-cell} temperature = ds_fields.isel(time=0, depth=0).thetao.plot(cmap="magma") velocity = ds_fields.isel(time=0, depth=0).plot.quiver(x="longitude", y="latitude", u="uo", v="vo") From 41bc7499cb2483b737c2f2708bf1a714496e3c25 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 07:27:30 +0100 Subject: [PATCH 05/11] Using RK2 for the manipulating field data tutorial too --- docs/user_guide/examples/tutorial_manipulating_field_data.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb index f7b61b6f72..f5dc55571b 100644 --- a/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb +++ b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb @@ -135,7 +135,7 @@ " store=\"summed_advection_wind.zarr\", outputdt=np.timedelta64(6, \"h\")\n", ")\n", "pset.execute(\n", - " [parcels.kernels.AdvectionRK4],\n", + " [parcels.kernels.AdvectionRK2],\n", " runtime=np.timedelta64(5, \"D\"),\n", " dt=np.timedelta64(1, \"h\"),\n", " output_file=output_file,\n", From 26a24d8310fadd987139ff699631d7fa108c7475 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 07:39:49 +0100 Subject: [PATCH 06/11] Combining Nemo2D and Nemo3D tutorials into one --- .../examples/tutorial_nemo_3D.ipynb | 246 +++++++++++++++++- 1 file changed, 241 insertions(+), 5 deletions(-) diff --git a/docs/user_guide/examples/tutorial_nemo_3D.ipynb b/docs/user_guide/examples/tutorial_nemo_3D.ipynb index 38d79a5bab..f297110754 100644 --- a/docs/user_guide/examples/tutorial_nemo_3D.ipynb +++ b/docs/user_guide/examples/tutorial_nemo_3D.ipynb @@ -5,7 +5,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 🖥️ Nemo 3D tutorial\n" + "# 🖥️ Nemo tutorial\n" ] }, { @@ -20,11 +20,22 @@ "\n", "```{note}\n", "_How to know if your data is discretised on a C grid?_ The best way is to read the documentation that comes with the data. Alternatively, an easy check is to assess the coordinates of the U, V and W fields: for an A grid, U, V and W are distributed on the same nodes, such that the coordinates are the same. For a C grid, there is a shift of half a cell between the different variables.\n", - "```\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Curvilinear C-Grids\n", + "\n", + "Parcels supports [curvilinear grids](https://www.nemo-ocean.eu/doc/node108.html) such as those used in the [NEMO models](https://www.nemo-ocean.eu/).\n", "\n", - "Here, we focus on 3D fields. Basically, it is a straightforward extension of the 2D example, but it is very easy to make a mistake in the setup of the vertical discretisation that would affect the interpolation scheme.\n", + "```{note}\n", + "TODO: make explicit how Parcels determines rotation\n", + "```\n", "\n", - "For the C-grid interpolation in Parcels to work properly, it is important that `U`, `V` and `W` are on the same grid. All other tracers (e.g. `temperature`, `salinity`) should also be on this same grid. So even though these tracers are computed by NEMO on the T-points, Parcels expects them on the f-points (`glamf`, `gphif` and `depthw`). Parcels then under the hood makes sure the interpolation of these tracers is done correctly." + "We will be using the example dataset `NemoCurvilinear_data`. These fields are a purely zonal flow on an aqua-planet (so zonal-velocity is 1 m s-1 and meridional-velocity is 0 m s-1 everywhere, and no land). However, because of the curvilinear grid, the `U` and `V` fields vary for the rotated grid cells north of 20N." ] }, { @@ -44,7 +55,232 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Parcels v4 comes with a `convert.nemo_to_sgrid` method that automatically sets up the correct `Grid` information for both 2D and 3D NEMO data." + "We use the `parcels.convert.nemo_to_sgrid()` function to convert the NEMO curvilinear grid to a dataset that is SGrid-compliant. We then pass this new dataset into `parcels.FieldSet.from_sgrid_conventions()`. This function automatically detects the C-grid structure and sets up the grid accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_folder = parcels.download_example_dataset(\"NemoCurvilinear_data\")\n", + "ds_fields = xr.open_mfdataset(\n", + " data_folder.glob(\"*.nc4\"),\n", + " data_vars=\"minimal\",\n", + " coords=\"minimal\",\n", + " compat=\"override\",\n", + ")\n", + "\n", + "ds_coords = xr.open_dataset(data_folder / \"mesh_mask.nc4\", decode_times=False)\n", + "ds_fset = parcels.convert.nemo_to_sgrid(\n", + " fields=dict(U=ds_fields[\"U\"], V=ds_fields[\"V\"]), coords=ds_coords\n", + ")\n", + "\n", + "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And we can plot the `U` field." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 2, figsize=(10, 4))\n", + "pc1 = fieldset.U.data.plot(cmap=\"viridis\", ax=ax[0], vmin=0)\n", + "pc2 = ax[1].pcolormesh(\n", + " fieldset.U.grid.lon,\n", + " fieldset.U.grid.lat,\n", + " fieldset.U.data[0, 0, :, :],\n", + " vmin=0,\n", + " vmax=1,\n", + ")\n", + "ax[1].set_ylabel(\"Latitude [deg N]\")\n", + "ax[1].set_xlabel(\"Longitude [deg E]\")\n", + "plt.colorbar(pc2, label=\"U\", ax=ax[1])\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you see above, the `U` field indeed is 1 m s-1 south of 20N, but varies with longitude and latitude north of that. We can confirm that Parcels will take care to rotate the `U` and `V` fields by doing a field evaluation at (60N, 50E):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "u, v = fieldset.UV.eval(np.array([0]), np.array([0]), np.array([60]), np.array([50]))\n", + "u *= 1852 * 60 * np.cos(np.deg2rad(60)) # convert from degrees s^-1 to m s^-1\n", + "v *= 1852 * 60 # convert from degrees s^-1 to m s\n", + "print(f\"(u, v) = ({u[0]:.3f}, {v[0]:.3f})\")\n", + "assert np.isclose(u, 1.0, atol=1e-3)\n", + "assert np.isclose(v, 0.0, atol=1e-3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can run particles as normal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "hide-output" + ] + }, + "outputs": [], + "source": [ + "npart = 20\n", + "lonp = 30 * np.ones(npart)\n", + "latp = np.linspace(-70, 88, npart)\n", + "runtime = np.timedelta64(40, \"D\")\n", + "\n", + "pset = parcels.ParticleSet(fieldset, lon=lonp, lat=latp)\n", + "pfile = parcels.ParticleFile(\n", + " store=\"output_curvilinear.zarr\", outputdt=np.timedelta64(1, \"D\")\n", + ")\n", + "\n", + "pset.execute(\n", + " [parcels.kernels.AdvectionEE],\n", + " runtime=runtime,\n", + " dt=np.timedelta64(1, \"D\"),\n", + " output_file=pfile,\n", + ")\n", + "np.testing.assert_allclose(pset.lat, latp, atol=1e-1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And then we can plot these trajectories. As expected, all trajectories go exactly zonal and due to the curvature of the earth, ones at higher latitude move more degrees eastward (even though the distance in km is equal for all particles). These particles at high latitudes cross the antimeridian (180 deg E) and keep going east." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds = xr.open_zarr(\"output_curvilinear.zarr\")\n", + "\n", + "plt.plot(ds.lon.T, ds.lat.T, \".-\")\n", + "plt.vlines(np.arange(-180, 901, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", + "plt.ylabel(\"Latitude [deg N]\")\n", + "plt.xlabel(\"Longitude [deg E]\")\n", + "plt.xticks(np.arange(-180, 901, 90))\n", + "plt.legend(loc=\"lower right\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Handling longitude wrapping\n", + "If we want the `particles.lon` to stay within `[-180,180]` (or `[0,360]`), we can either do this in post-processing, or add a periodic boundary Kernel:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# post processing\n", + "ds[\"lon\"] = ds[\"lon\"] % 360\n", + "ds[\"lon\"] = ds[\"lon\"].where(ds[\"lon\"] <= 180, ds[\"lon\"] - 360)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "hide-output" + ] + }, + "outputs": [], + "source": [ + "# with a Kernel\n", + "def periodicBC(particles, fieldset): # pragma: no cover\n", + " particles.dlon = np.where(\n", + " particles.lon + particles.dlon > 180, particles.dlon - 360, particles.dlon\n", + " )\n", + "\n", + "\n", + "pset = parcels.ParticleSet(fieldset, lon=lonp, lat=latp)\n", + "pfile = parcels.ParticleFile(\n", + " store=\"output_curvilinear_periodic.zarr\", outputdt=np.timedelta64(1, \"D\")\n", + ")\n", + "\n", + "pset.execute(\n", + " [parcels.kernels.AdvectionEE, periodicBC],\n", + " runtime=runtime,\n", + " dt=np.timedelta64(1, \"D\"),\n", + " output_file=pfile,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds_periodic = xr.open_zarr(\"output_curvilinear_periodic.zarr\")\n", + "\n", + "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n", + "ax[0].plot(ds.lon.T, ds.lat.T, \".-\")\n", + "ax[0].vlines(np.arange(-180, 360, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", + "ax[0].set_ylabel(\"Latitude [deg N]\")\n", + "ax[0].set_xlabel(\"Longitude [deg E]\")\n", + "ax[0].set_xticks(np.arange(-180, 181, 45))\n", + "ax[0].set_title(\"in post processing\")\n", + "ax[0].legend(loc=\"lower center\")\n", + "\n", + "ax[1].plot(ds_periodic.lon.T, ds_periodic.lat.T, \".-\")\n", + "ax[1].vlines(np.arange(-180, 360, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", + "ax[1].set_ylabel(\"Latitude [deg N]\")\n", + "ax[1].set_xlabel(\"Longitude [deg E]\")\n", + "ax[1].set_xticks(np.arange(-180, 181, 45))\n", + "ax[1].set_title(\"with periodic Kernel\")\n", + "ax[1].legend(loc=\"lower center\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running 3D Nemo simulations\n", + "\n", + "Here, we focus on 3D fields. Basically, it is a straightforward extension of the 2D example above." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We again use the combination of the `parcels.convert.nemo_to_sgrid` function and the `parcels.FieldSet.from_sgrid_conventions()` to create a FieldSet object with the correct `Grid` information for the NEMO data." ] }, { From ac08626a77550df2d15353227acd729ea735373d Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 07:41:26 +0100 Subject: [PATCH 07/11] Renaming nemo tutorials --- ...rial_nemo_3D.ipynb => tutorial_nemo.ipynb} | 0 .../examples/tutorial_nemo_curvilinear.ipynb | 293 ------------------ docs/user_guide/index.md | 3 +- 3 files changed, 1 insertion(+), 295 deletions(-) rename docs/user_guide/examples/{tutorial_nemo_3D.ipynb => tutorial_nemo.ipynb} (100%) delete mode 100644 docs/user_guide/examples/tutorial_nemo_curvilinear.ipynb diff --git a/docs/user_guide/examples/tutorial_nemo_3D.ipynb b/docs/user_guide/examples/tutorial_nemo.ipynb similarity index 100% rename from docs/user_guide/examples/tutorial_nemo_3D.ipynb rename to docs/user_guide/examples/tutorial_nemo.ipynb diff --git a/docs/user_guide/examples/tutorial_nemo_curvilinear.ipynb b/docs/user_guide/examples/tutorial_nemo_curvilinear.ipynb deleted file mode 100644 index ba26f3558e..0000000000 --- a/docs/user_guide/examples/tutorial_nemo_curvilinear.ipynb +++ /dev/null @@ -1,293 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 🖥️ Curvilinear grids" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Parcels supports [curvilinear grids](https://www.nemo-ocean.eu/doc/node108.html) such as those used in the [NEMO models](https://www.nemo-ocean.eu/).\n", - "\n", - "```{note}\n", - "TODO: make explicit how Parcels determines rotation\n", - "```\n", - "\n", - "We will be using the example dataset `NemoCurvilinear_data`. These fields are a purely zonal flow on an aqua-planet (so zonal-velocity is 1 m/s and meridional-velocity is 0 m/s everywhere, and no land). However, because of the curvilinear grid, the `U` and `V` fields vary for the rotated gridcells north of 20N.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import xarray as xr\n", - "\n", - "import parcels" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can create a `FieldSet` just like we do for normal grids.\n", - "Note that NEMO is discretised on a C-grid. U and V velocities are not located on the same nodes (see https://www.nemo-ocean.eu/doc/node19.html ).\n", - "\n", - "```\n", - " __V1__\n", - "| |\n", - "U0 U1\n", - "|__V0__|\n", - "```\n", - "\n", - "To interpolate U, V velocities on the C-grid, Parcels needs to read the f-nodes, which are located on the corners of the cells (for indexing details: https://www.nemo-ocean.eu/doc/img360.png).\n", - "\n", - "```{note}\n", - "TODO: add link to grid indexing explanation once implemented in v4\n", - "```\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_folder = parcels.download_example_dataset(\"NemoCurvilinear_data\")\n", - "ds_fields = xr.open_mfdataset(\n", - " data_folder.glob(\"*.nc4\"),\n", - " data_vars=\"minimal\",\n", - " coords=\"minimal\",\n", - " compat=\"override\",\n", - ")\n", - "\n", - "ds_coords = xr.open_dataset(data_folder / \"mesh_mask.nc4\", decode_times=False)\n", - "ds_fset = parcels.convert.nemo_to_sgrid(\n", - " fields=dict(U=ds_fields[\"U\"], V=ds_fields[\"V\"]), coords=ds_coords\n", - ")\n", - "\n", - "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And we can plot the `U` field.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 2, figsize=(10, 4))\n", - "pc1 = fieldset.U.data.plot(cmap=\"viridis\", ax=ax[0], vmin=0)\n", - "pc2 = ax[1].pcolormesh(\n", - " fieldset.U.grid.lon,\n", - " fieldset.U.grid.lat,\n", - " fieldset.U.data[0, 0, :, :],\n", - " vmin=0,\n", - " vmax=1,\n", - ")\n", - "ax[1].set_ylabel(\"Latitude [deg N]\")\n", - "ax[1].set_xlabel(\"Longitude [deg E]\")\n", - "plt.colorbar(pc2, label=\"U\", ax=ax[1])\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you see above, the `U` field indeed is 1 m/s south of 20N, but varies with longitude and latitude north of that. We can confirm that Parcels will take care to rotate the `U` and `V` fields by doing a field evaluation at (60N, 50E):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "u, v = fieldset.UV.eval(np.array([0]), np.array([0]), np.array([60]), np.array([50]))\n", - "u *= 1852 * 60 * np.cos(np.deg2rad(60)) # convert from degrees s^-1 to m s^-1\n", - "v *= 1852 * 60 # convert from degrees s^-1 to m s\n", - "print(f\"(u, v) = ({u[0]:.3f}, {v[0]:.3f})\")\n", - "assert np.isclose(u, 1.0, atol=1e-3)\n", - "assert np.isclose(v, 0.0, atol=1e-3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can run particles as normal." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-output" - ] - }, - "outputs": [], - "source": [ - "npart = 20\n", - "lonp = 30 * np.ones(npart)\n", - "latp = np.linspace(-70, 88, npart)\n", - "runtime = np.timedelta64(40, \"D\")\n", - "\n", - "pset = parcels.ParticleSet(fieldset, lon=lonp, lat=latp)\n", - "pfile = parcels.ParticleFile(\n", - " store=\"output_curvilinear.zarr\", outputdt=np.timedelta64(1, \"D\")\n", - ")\n", - "\n", - "pset.execute(\n", - " [parcels.kernels.AdvectionEE],\n", - " runtime=runtime,\n", - " dt=np.timedelta64(1, \"D\"),\n", - " output_file=pfile,\n", - ")\n", - "np.testing.assert_allclose(pset.lat, latp, atol=1e-1)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And then we can plot these trajectories. As expected, all trajectories go exactly zonal and due to the curvature of the earth, ones at higher latitude move more degrees eastward (even though the distance in km is equal for all particles). These particles at high latitudes cross the antimeridian (180 deg E) and keep going east.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds = xr.open_zarr(\"output_curvilinear.zarr\")\n", - "\n", - "plt.plot(ds.lon.T, ds.lat.T, \".-\")\n", - "plt.vlines(np.arange(-180, 901, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", - "plt.ylabel(\"Latitude [deg N]\")\n", - "plt.xlabel(\"Longitude [deg E]\")\n", - "plt.xticks(np.arange(-180, 901, 90))\n", - "plt.legend(loc=\"lower right\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we want the `particles.lon` to stay within `[-180,180]` (or `[0,360]`), we can either do this in post-processing, or add a periodic boundary Kernel:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# post processing\n", - "ds[\"lon\"] = ds[\"lon\"] % 360\n", - "ds[\"lon\"] = ds[\"lon\"].where(ds[\"lon\"] <= 180, ds[\"lon\"] - 360)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-output" - ] - }, - "outputs": [], - "source": [ - "# with a Kernel\n", - "def periodicBC(particles, fieldset): # pragma: no cover\n", - " particles.dlon = np.where(\n", - " particles.lon + particles.dlon > 180, particles.dlon - 360, particles.dlon\n", - " )\n", - "\n", - "\n", - "pset = parcels.ParticleSet(fieldset, lon=lonp, lat=latp)\n", - "pfile = parcels.ParticleFile(\n", - " store=\"output_curvilinear_periodic.zarr\", outputdt=np.timedelta64(1, \"D\")\n", - ")\n", - "\n", - "pset.execute(\n", - " [parcels.kernels.AdvectionEE, periodicBC],\n", - " runtime=runtime,\n", - " dt=np.timedelta64(1, \"D\"),\n", - " output_file=pfile,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds_periodic = xr.open_zarr(\"output_curvilinear_periodic.zarr\")\n", - "\n", - "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n", - "ax[0].plot(ds.lon.T, ds.lat.T, \".-\")\n", - "ax[0].vlines(np.arange(-180, 360, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", - "ax[0].set_ylabel(\"Latitude [deg N]\")\n", - "ax[0].set_xlabel(\"Longitude [deg E]\")\n", - "ax[0].set_xticks(np.arange(-180, 181, 45))\n", - "ax[0].set_title(\"in post processing\")\n", - "ax[0].legend(loc=\"lower center\")\n", - "\n", - "ax[1].plot(ds_periodic.lon.T, ds_periodic.lat.T, \".-\")\n", - "ax[1].vlines(np.arange(-180, 360, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", - "ax[1].set_ylabel(\"Latitude [deg N]\")\n", - "ax[1].set_xlabel(\"Longitude [deg E]\")\n", - "ax[1].set_xticks(np.arange(-180, 181, 45))\n", - "ax[1].set_title(\"with periodic Kernel\")\n", - "ax[1].legend(loc=\"lower center\")\n", - "\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "test-notebooks-latest", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index 3e924ab6b1..b56da92761 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -25,8 +25,7 @@ The tutorials written for Parcels v3 are currently being updated for Parcels v4. :caption: Set up FieldSets :titlesonly: examples/explanation_grids.md -examples/tutorial_nemo_curvilinear.ipynb -examples/tutorial_nemo_3D.ipynb +examples/tutorial_nemo.ipynb examples/tutorial_velocityconversion.ipynb examples/tutorial_nestedgrids.ipynb examples/tutorial_summingfields.ipynb From 0a77770ba9f43f75d6598be8f5a710618001283a Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 07:56:22 +0100 Subject: [PATCH 08/11] Add tutorial_manipulating_field_data to index --- docs/user_guide/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index b56da92761..3783363e80 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -28,7 +28,7 @@ examples/explanation_grids.md examples/tutorial_nemo.ipynb examples/tutorial_velocityconversion.ipynb examples/tutorial_nestedgrids.ipynb -examples/tutorial_summingfields.ipynb +examples/tutorial_manipulating_field_data.ipynb ``` From 43aad4eaca9f46c912d8cda713b732dd214888bc Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 08:05:47 +0100 Subject: [PATCH 09/11] Update tutorial_nemo.ipynb --- docs/user_guide/examples/tutorial_nemo.ipynb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user_guide/examples/tutorial_nemo.ipynb b/docs/user_guide/examples/tutorial_nemo.ipynb index f297110754..d56aa5194f 100644 --- a/docs/user_guide/examples/tutorial_nemo.ipynb +++ b/docs/user_guide/examples/tutorial_nemo.ipynb @@ -16,10 +16,12 @@ "One of the features of Parcels is that it can directly and natively work with `Field` data discretised on C-grids. These C grids are very popular in OGCMs, so velocity fields outputted by OGCMs are often provided on such grids, except if they have been firstly re-interpolated on a A grid.\n", "\n", "More information about C-grid interpolation can be found in [Delandmeter et al., 2019](https://www.geosci-model-dev-discuss.net/gmd-2018-339/).\n", - "An example of such a discretisation is the NEMO model, which is one of the models supported in Parcels. A tutorial teaching how to [interpolate 2D data on a NEMO grid](https://docs.oceanparcels.org/en/latest/examples/tutorial_nemo_curvilinear.html) is available within Parcels.\n", + "An example of such a discretisation is the NEMO model, which is one of the models for which we provide support in Parcels. Other models are CROCO, ROMS, and MITgcm.\n", "\n", "```{note}\n", - "_How to know if your data is discretised on a C grid?_ The best way is to read the documentation that comes with the data. Alternatively, an easy check is to assess the coordinates of the U, V and W fields: for an A grid, U, V and W are distributed on the same nodes, such that the coordinates are the same. For a C grid, there is a shift of half a cell between the different variables.\n", + "_How to know if your data is discretised on a C grid?_ The best way is to read the documentation that comes with the data. Alternatively, an easy check is to assess the coordinates of the U, V and W fields: \n", + "- for an **A grid**, U, V and W are distributed on the same nodes, such that the coordinates are the same. \n", + "- for a **C grid**, there is a shift of half a cell between the different variables.\n", "```" ] }, From d10d53c8576b976b0d26dff42a58aed1fc9d3c60 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 08:10:45 +0100 Subject: [PATCH 10/11] creating separate section Example Kernels --- docs/user_guide/index.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index 3783363e80..35ecd9a7a8 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -48,10 +48,6 @@ examples/tutorial_delaystart.ipynb examples/explanation_kernelloop.md examples/tutorial_sampling.ipynb examples/tutorial_statuscodes.ipynb -examples/tutorial_gsw_density.ipynb -examples/tutorial_Argofloats.ipynb -examples/tutorial_diffusion.ipynb -examples/tutorial_interaction.ipynb ``` ```{toctree} @@ -89,7 +85,12 @@ examples/tutorial_dt_integrators.ipynb ```{toctree} -:caption: Worked examples +:caption: Example Kernels +:titlesonly: +examples/tutorial_gsw_density.ipynb +examples/tutorial_Argofloats.ipynb +examples/tutorial_diffusion.ipynb +examples/tutorial_interaction.ipynb ``` From 52ed981c6bbc7fe0333be06831a2377ce4754ff7 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 16 Jan 2026 08:10:48 +0100 Subject: [PATCH 11/11] Update explanation_kernelloop.md --- docs/user_guide/examples/explanation_kernelloop.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user_guide/examples/explanation_kernelloop.md b/docs/user_guide/examples/explanation_kernelloop.md index 884de08b93..bb2d20743b 100644 --- a/docs/user_guide/examples/explanation_kernelloop.md +++ b/docs/user_guide/examples/explanation_kernelloop.md @@ -4,9 +4,9 @@ kernelspec: name: python3 --- -# 📖 The Parcels Kernel loop +# 📖 Kernel loop -On this page we discuss Parcels' execution loop, and what happens under the hood when you combine multiple Kernels. +On this page we discuss how Parcels executes the Kernel loop, and what happens under the hood when you combine multiple Kernels. This is not very relevant when you only use the built-in Advection Kernels, but can be important when you are writing and combining your own Kernels!