From 2c0e6b55124a79d6bb008aac3ce5e164a4c45698 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 15:11:47 +0100 Subject: [PATCH 1/4] Adding a tutorial on how to sum fields using xarray operations --- .../examples/tutorial_summingfields.ipynb | 179 ++++++++++++++++++ docs/user_guide/index.md | 1 + 2 files changed, 180 insertions(+) create mode 100644 docs/user_guide/examples/tutorial_summingfields.ipynb diff --git a/docs/user_guide/examples/tutorial_summingfields.ipynb b/docs/user_guide/examples/tutorial_summingfields.ipynb new file mode 100644 index 000000000..9d67d3df3 --- /dev/null +++ b/docs/user_guide/examples/tutorial_summingfields.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# 🖥️ Summing Fields" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "In some applications, you may want to sum multiple fields together to create a combined effect on particle movement. For example, you might want to combine ocean currents with wind-driven surface drift. This tutorial demonstrates how to sum multiple fields in Parcels and visualize the resulting particle trajectories.\n", + "\n", + "We base this tutorial on the example provided in the [Parcels Kernel loop explanation](explanation_kernelloop.md). There, we combined two kernels to simulate the joint effect of currents and winds.\n", + "\n", + "However, we can also do that in one kernel, by combining the fields directly, using `xarray` operations.\n", + "\n", + "We start with loading the necessary libraries and data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import xarray as xr\n", + "\n", + "import parcels\n", + "\n", + "# Load the CopernicusMarine data in the Agulhas region from the example_datasets\n", + "example_dataset_folder = parcels.download_example_dataset(\n", + " \"CopernicusMarine_data_for_Argo_tutorial\"\n", + ")\n", + "\n", + "ds_fields = xr.open_mfdataset(f\"{example_dataset_folder}/*.nc\", combine=\"by_coords\")\n", + "ds_fields.load() # load the dataset into memory\n", + "\n", + "# Create an idealised wind field and add it to the dataset\n", + "tdim, ydim, xdim = (\n", + " len(ds_fields.time),\n", + " len(ds_fields.latitude),\n", + " len(ds_fields.longitude),\n", + ")\n", + "ds_fields[\"UWind\"] = xr.DataArray(\n", + " data=np.ones((tdim, ydim, xdim)) * np.sin(ds_fields.latitude.values)[None, :, None],\n", + " coords=[ds_fields.time, ds_fields.latitude, ds_fields.longitude],\n", + ")\n", + "\n", + "ds_fields[\"VWind\"] = xr.DataArray(\n", + " data=np.zeros((tdim, ydim, xdim)),\n", + " coords=[ds_fields.time, ds_fields.latitude, ds_fields.longitude],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "Now here comes the trick: we can simply sum the fields together using `xarray` operations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# Combine ocean currents and wind fields\n", + "ds_fields[\"U\"] = ds_fields[\"uo\"] + ds_fields[\"UWind\"]\n", + "ds_fields[\"V\"] = ds_fields[\"vo\"] + ds_fields[\"VWind\"]" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "```{note}\n", + "Combining fields in this way assumes that the fields are defined on the same grid and have compatible dimensions. Ensure that the fields you are summing are aligned correctly to avoid unexpected results.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "We can then run the same simulation as in the [Kernel loop explanation tutorial](explanation_kernelloop.md), but now using the combined fields. We only need the default advection kernel now, since the effects of both currents and winds are already included in the summed fields." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": { + "tags": [ + "hide-output" + ] + }, + "outputs": [], + "source": [ + "fields = {\n", + " \"U\": ds_fields[\"U\"],\n", + " \"V\": ds_fields[\"V\"],\n", + "}\n", + "ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n", + "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", + "\n", + "npart = 10\n", + "z = np.repeat(ds_fields.depth[0].values, npart)\n", + "lons = np.repeat(31, npart)\n", + "lats = np.linspace(-32.5, -30.5, npart)\n", + "\n", + "pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, z=z, lat=lats, lon=lons)\n", + "output_file = parcels.ParticleFile(\n", + " store=\"summed_advection_wind.zarr\", outputdt=np.timedelta64(6, \"h\")\n", + ")\n", + "pset.execute(\n", + " [parcels.kernels.AdvectionRK4],\n", + " runtime=np.timedelta64(5, \"D\"),\n", + " dt=np.timedelta64(1, \"h\"),\n", + " output_file=output_file,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "We can then plot the trajectories, and confirm that they particles move the same as in the [Kernel loop explanation tutorial](explanation_kernelloop.md), where we combined the effects of currents and winds using two separate kernels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the resulting particle trajectories overlapped for both cases\n", + "summed_advection_wind = xr.open_zarr(\"summed_advection_wind.zarr\")\n", + "plt.plot(summed_advection_wind.lon.T, summed_advection_wind.lat.T, \"-\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "docs", + "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.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index 1729f4af3..3e924ab6b 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -29,6 +29,7 @@ examples/tutorial_nemo_curvilinear.ipynb examples/tutorial_nemo_3D.ipynb examples/tutorial_velocityconversion.ipynb examples/tutorial_nestedgrids.ipynb +examples/tutorial_summingfields.ipynb ``` From c5126a46f38ba7158b1e3fa3be7da3348d31f1f3 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 15:21:54 +0100 Subject: [PATCH 2/4] Adding note to summing fields --- docs/user_guide/examples/explanation_kernelloop.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/user_guide/examples/explanation_kernelloop.md b/docs/user_guide/examples/explanation_kernelloop.md index 4cd2adb0f..8b9360d54 100644 --- a/docs/user_guide/examples/explanation_kernelloop.md +++ b/docs/user_guide/examples/explanation_kernelloop.md @@ -42,6 +42,10 @@ Besides having commutable Kernels, the main advantage of this implementation is Below is a simple example of some particles at the surface of the ocean. We create an idealised zonal wind flow that will "push" a particle that is already affected by the surface currents. The Kernel loop ensures that these two forces act at the same time and location. +```{note} +Advecting particles by a combination of flow fields can be done with two separate kernels, as is shown below. However, it can also be done by summing the fields directly using `xarray` operations - provided the fields are defined on the same grid and have compatible dimensions. See the [summing fields tutorial](tutorial_summingfields.ipynb) for an example of that approach. +``` + ```{code-cell} :tags: [hide-output] import matplotlib.pyplot as plt From 210bec632e314a14b5ab1780781e28bcc4c686d2 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 16:12:00 +0100 Subject: [PATCH 3/4] Renaming to tutorial_manipulating_field_data --- ...summingfields.ipynb => tutorial_manipulating_field_data.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/user_guide/examples/{tutorial_summingfields.ipynb => tutorial_manipulating_field_data.ipynb} (100%) diff --git a/docs/user_guide/examples/tutorial_summingfields.ipynb b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb similarity index 100% rename from docs/user_guide/examples/tutorial_summingfields.ipynb rename to docs/user_guide/examples/tutorial_manipulating_field_data.ipynb From 2801d59140731132e9e5d3169fe2860f79d186e1 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 15 Jan 2026 16:14:01 +0100 Subject: [PATCH 4/4] Changing tutorial name to Manipulating Field data And added short description --- .../examples/explanation_kernelloop.md | 2 +- .../tutorial_manipulating_field_data.ipynb | 27 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/user_guide/examples/explanation_kernelloop.md b/docs/user_guide/examples/explanation_kernelloop.md index 8b9360d54..607566c4c 100644 --- a/docs/user_guide/examples/explanation_kernelloop.md +++ b/docs/user_guide/examples/explanation_kernelloop.md @@ -43,7 +43,7 @@ Besides having commutable Kernels, the main advantage of this implementation is Below is a simple example of some particles at the surface of the ocean. We create an idealised zonal wind flow that will "push" a particle that is already affected by the surface currents. The Kernel loop ensures that these two forces act at the same time and location. ```{note} -Advecting particles by a combination of flow fields can be done with two separate kernels, as is shown below. However, it can also be done by summing the fields directly using `xarray` operations - provided the fields are defined on the same grid and have compatible dimensions. See the [summing fields tutorial](tutorial_summingfields.ipynb) for an example of that approach. +Advecting particles by a combination of flow fields can be done with two separate kernels, as is shown below. However, it can also be done by summing the fields directly using `xarray` operations - provided the fields are defined on the same grid and have compatible dimensions. See the [manipulating field data tutorial](tutorial_manipulating_field_data.ipynb) for an example of that approach. ``` ```{code-cell} diff --git a/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb index 9d67d3df3..f7b61b6f7 100644 --- a/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb +++ b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb @@ -5,7 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# 🖥️ Summing Fields" + "# 🖥️ Manipulating Field data" ] }, { @@ -13,6 +13,15 @@ "id": "1", "metadata": {}, "source": [ + "Because Parcels `Field` objects are built on top of `xarray` DataArrays, you can leverage the powerful data manipulation capabilities of `xarray` to preprocess or modify your field data before using it in particle simulations. This tutorial provides some examples of how to leverage that power." + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "## Summing Fields\n", "In some applications, you may want to sum multiple fields together to create a combined effect on particle movement. For example, you might want to combine ocean currents with wind-driven surface drift. This tutorial demonstrates how to sum multiple fields in Parcels and visualize the resulting particle trajectories.\n", "\n", "We base this tutorial on the example provided in the [Parcels Kernel loop explanation](explanation_kernelloop.md). There, we combined two kernels to simulate the joint effect of currents and winds.\n", @@ -25,7 +34,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2", + "id": "3", "metadata": {}, "outputs": [], "source": [ @@ -62,7 +71,7 @@ }, { "cell_type": "markdown", - "id": "3", + "id": "4", "metadata": {}, "source": [ "Now here comes the trick: we can simply sum the fields together using `xarray` operations" @@ -71,7 +80,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4", + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -82,7 +91,7 @@ }, { "cell_type": "markdown", - "id": "5", + "id": "6", "metadata": {}, "source": [ "```{note}\n", @@ -92,7 +101,7 @@ }, { "cell_type": "markdown", - "id": "6", + "id": "7", "metadata": {}, "source": [ "We can then run the same simulation as in the [Kernel loop explanation tutorial](explanation_kernelloop.md), but now using the combined fields. We only need the default advection kernel now, since the effects of both currents and winds are already included in the summed fields." @@ -101,7 +110,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7", + "id": "8", "metadata": { "tags": [ "hide-output" @@ -135,7 +144,7 @@ }, { "cell_type": "markdown", - "id": "8", + "id": "9", "metadata": {}, "source": [ "We can then plot the trajectories, and confirm that they particles move the same as in the [Kernel loop explanation tutorial](explanation_kernelloop.md), where we combined the effects of currents and winds using two separate kernels." @@ -144,7 +153,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9", + "id": "10", "metadata": {}, "outputs": [], "source": [