From bbe40b1a1ce4022bc59cf6950ba4e58530deb4bf Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 24 Jul 2026 16:33:44 +0200 Subject: [PATCH 1/9] First attempt at convert_delft3d_to_sgrid --- src/parcels/_core/xgrid.py | 3 +- src/parcels/_datasets/remote.py | 4 ++ src/parcels/convert.py | 78 +++++++++++++++++++++++++++++++++ tests/test_convert.py | 11 +++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/parcels/_core/xgrid.py b/src/parcels/_core/xgrid.py index 91a48af5d..2b5b7bbb1 100644 --- a/src/parcels/_core/xgrid.py +++ b/src/parcels/_core/xgrid.py @@ -88,8 +88,9 @@ def _transpose_xfield_data_to_tzyx(da: xr.DataArray, sgrid_metadata: sgrid.SGrid # All dimensions must be associated with an axis in the grid if set(dim_to_axis) != set(da.dims): + dims_not_on_grid = set(da.dims) - set(dim_to_axis) raise ValueError( - f"DataArray {da.name!r} with dims {da.dims} has dimensions that are not associated with a direction on the provided grid." + f"DataArray {da.name!r} with dims {da.dims} has dimensions {dims_not_on_grid} that are not associated with a direction on the provided grid." ) axes_not_in_field = set(_FIELD_DATA_ORDERING).difference(set(dim_to_axis.values())) diff --git a/src/parcels/_datasets/remote.py b/src/parcels/_datasets/remote.py index 5fc93868a..ceac0b75f 100644 --- a/src/parcels/_datasets/remote.py +++ b/src/parcels/_datasets/remote.py @@ -54,6 +54,9 @@ def _get_data_home() -> Path: # "data/DecayingMovingEddy_data/decaying_moving_eddyU.nc", # "data/DecayingMovingEddy_data/decaying_moving_eddyV.nc", # ] + + [ + "data/Delft3D_data/Rotterdam_tiny.nc", + ] + [ "data/FESOM_periodic_channel/fesom_channel.nc", "data/FESOM_periodic_channel/u.fesom_channel.nc", @@ -220,6 +223,7 @@ class _Purpose(enum.Enum): # ("Peninsula_data/T", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaT.nc"), _Purpose.TUTORIAL)), # ("GlobCurrent_example_data/data", (_V3Dataset(_ODIE,"data/GlobCurrent_example_data/*000000-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc", pre_decode_cf_callable=patch_dataset_v4_compat), _Purpose.TUTORIAL)), ("CopernicusMarine_data_for_Argo_tutorial/data", (_V3Dataset(_ODIE,"data/CopernicusMarine_data_for_Argo_tutorial/cmems_mod_glo_phy-*.nc"), _Purpose.TUTORIAL)), + ("Delft3D_data/Rotterdam_tiny", (_V3Dataset(_ODIE,"data/Delft3D_data/Rotterdam_tiny.nc"), _Purpose.TUTORIAL)), # ("DecayingMovingEddy_data/U", (_V3Dataset(_ODIE,"data/DecayingMovingEddy_data/decaying_moving_eddyU.nc"), _Purpose.TUTORIAL)), # ("DecayingMovingEddy_data/V", (_V3Dataset(_ODIE,"data/DecayingMovingEddy_data/decaying_moving_eddyV.nc"), _Purpose.TUTORIAL)), ("FESOM_periodic_channel/fesom_channel", (_V3Dataset(_ODIE,"data/FESOM_periodic_channel/fesom_channel.nc"), _Purpose.TUTORIAL)), diff --git a/src/parcels/convert.py b/src/parcels/convert.py index c8cea9ce3..4b1f1fc73 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -102,6 +102,23 @@ class _Status(enum.Enum): "T": "time", } +_DELFT3D_EXPECTED_COORDS: list[tuple[str, _Status]] = [(name, _Status.REQUIRED) for name in ["XZETA", "YZETA"]] + +_DELFT3D_VARNAMES_MAPPING: dict[str, str] = { + "XZETA": "lon", + "YZETA": "lat", + "SIGMA_C": "depth", + "TIME": "time", +} + +_DELFT3D_AXIS_VARNAMES: dict[str, XgcmAxisDirection] = { + "M": "X", + "N": "Y", + "LAYER": "Z", + "time": "T", +} + + _CROCO_EXPECTED_COORDS: list[tuple[str, _Status]] = [ (name, _Status.REQUIRED) for name in ["x_rho", "y_rho", "s_w", "time"] ] @@ -567,6 +584,67 @@ def copernicusmarine_to_sgrid( return ds +def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr.Dataset) -> xr.Dataset: + """Create an sgrid-compliant xarray.Dataset from a dataset of Delft3D netcdf files. + + Parameters + ---------- + fields : dict[str, xr.Dataset | xr.DataArray] + Dictionary of xarray.DataArray objects as obtained from a set of Delft3D netcdf files. + coords : xarray.Dataset, optional + xarray.Dataset containing coordinate variables. + + Returns + ------- + xarray.Dataset + Dataset object following SGRID conventions to be (optionally) modified and passed to a FieldSet constructor. + + See the Delft3D documentation for more information on how to use Delft3D model outputs in Parcels + + """ + warnings.warn( + "The delft3d_to_sgrid function is experimental and may not work for all Delft3D datasets. " + "Furthermore, we are not entirely confident that the SGrid layout for Delft3D is implemented correctly. " + "Please report any issues to the Parcels GitHub repository.", + UserWarning, + stacklevel=2, + ) + + fields = fields.copy() + coords = _pick_expected_coords(coords, _DELFT3D_EXPECTED_COORDS) + + for name, field_da in fields.items(): + if isinstance(field_da, xr.Dataset): + field_da = field_da[name] + # TODO: logging message, warn if multiple fields are in this dataset + else: + field_da = field_da.rename(name) + fields[name] = field_da + + ds = xr.merge(list(fields.values()) + [coords]) + + ds = _maybe_rename_variables(ds, _DELFT3D_VARNAMES_MAPPING) + ds = _set_coords(ds, _DELFT3D_AXIS_VARNAMES.keys()) + ds = _set_axis_attrs(ds, _DELFT3D_AXIS_VARNAMES) + + ds["grid"] = xr.DataArray( + 0, + attrs=sgrid.SGrid2DMetadata( + cf_role="grid_topology", + topology_dimension=2, + node_dimensions=("M", "N"), + node_coordinates=("lon", "lat"), + face_dimensions=( # TODO check these paddings + sgrid.FaceNodePadding("X", "M", sgrid.Padding.HIGH), + sgrid.FaceNodePadding("Y", "N", sgrid.Padding.HIGH), + ), + vertical_dimensions=(sgrid.FaceNodePadding("Z", "LAYER", sgrid.Padding.HIGH),), + ).to_attrs(), + ) + + return ds + + # Known vertical dimension mappings by model _FESOM2_VERTICAL_DIMS = {"interface": "nz", "center": "nz1"} _ICON_VERTICAL_DIMS = {"interface": "depth_2", "center": "depth"} diff --git a/tests/test_convert.py b/tests/test_convert.py index 6a05bc960..30ea5c515 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -159,6 +159,17 @@ def test_convert_copernicusmarine_no_currents(caplog): assert caplog.text == "" +def test_convert_delft3d(): + ds = open_remote_dataset("Delft3D_data/Rotterdam_tiny") + coords = ds[["XZETA", "YZETA", "SIGMA_C"]] + ds_fset = convert.delft3d_to_sgrid(fields={"U": ds["VELU"], "V": ds["VELV"]}, coords=coords) + fieldset = FieldSet.from_sgrid_conventions(ds_fset) + fieldset.describe() + assert "U" in fieldset.fields + assert "V" in fieldset.fields + assert "UV" in fieldset.fields + + @pytest.mark.parametrize("ds", _COPERNICUS_DATASETS) def test_convert_copernicusmarine_no_logs(ds, caplog): ds = ds.copy() From ddc31ab18c6240a0e767eb440e0798f5fa5366d9 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 09:04:39 +0200 Subject: [PATCH 2/9] Adding Delft3D tutorial notebook --- .../examples/tutorial-delft3d.ipynb | 175 ++++++++++++++++++ pixi.toml | 1 + 2 files changed, 176 insertions(+) create mode 100644 docs/user_guide/examples/tutorial-delft3d.ipynb diff --git a/docs/user_guide/examples/tutorial-delft3d.ipynb b/docs/user_guide/examples/tutorial-delft3d.ipynb new file mode 100644 index 000000000..ad0d43862 --- /dev/null +++ b/docs/user_guide/examples/tutorial-delft3d.ipynb @@ -0,0 +1,175 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# 🖥️ Delft3D tutorial" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "This tutorial shows how to load in files from the [Delft3D](https://www.deltares.nl/en/software/delft3d-4-suite/) model into Parcels. \n", + "\n", + "## Structured Grids\n", + "Special about Delft3D is that its structured grid contains NaN values for points that don't exist in the model domain. The hashtable approach in Parcels v4 supports these NaN grid cells out of the box, so that you can use the model output directly without any preprocessing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import cmocean as cmo\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import parcels\n", + "import parcels.tutorial" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "The example below is of a small domain in the Port of Rotterdam. We first pick the coorindates and the velocity fields from the dataset, and then convert them to the sgrid conventions. Finally, we create a FieldSet from the converted dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "ds = parcels.tutorial.open_dataset(\"Delft3D_data/Rotterdam_tiny\")\n", + "coords = ds[[\"XZETA\", \"YZETA\", \"SIGMA_C\"]]\n", + "ds_fset = parcels.convert.delft3d_to_sgrid(\n", + " fields={\"U\": ds[\"VELU\"], \"V\": ds[\"VELV\"]}, coords=coords\n", + ")\n", + "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", + "fieldset = fieldset.to_windowed_arrays()\n", + "fieldset.describe()" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "Now we define a grid of a few particles to release in the domain, and run a simple advection simulation. The particles are advected by the Delft3D velocity fields, and we can visualize their trajectories." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "# Define a set of starting points\n", + "points_x, points_y = np.meshgrid(\n", + " np.linspace(93000, 93300, 5), np.linspace(436300, 436600, 5)\n", + ")\n", + "z = np.full(points_x.shape, 0.06) # Depth of 0.06 m\n", + "pset = parcels.ParticleSet(fieldset, x=points_x, y=points_y, z=z)\n", + "\n", + "# St up an output file to save particle trajectories\n", + "outputfile = parcels.ParticleFile(\n", + " \"Delft3D_structured.parquet\",\n", + " outputdt=np.timedelta64(60, \"s\"),\n", + " mode=\"w\",\n", + ")\n", + "\n", + "\n", + "# Define a custom error handling kernel to delete particles on any error\n", + "def DeleteOnAnyError(particles, fieldset):\n", + " any_error = particles.state >= 50 # This captures all Errors\n", + " particles[any_error].state = parcels.StatusCode.Delete\n", + "\n", + "\n", + "# Run the particle set with the advection kernel and the custom error handling kernel\n", + "pset.execute(\n", + " [parcels.kernels.AdvectionRK2, DeleteOnAnyError],\n", + " endtime=fieldset.time_interval.right,\n", + " dt=np.timedelta64(60, \"s\"),\n", + " output_file=outputfile,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "df = parcels.read_particlefile(\"Delft3D_structured.parquet\")\n", + "\n", + "fig, ax = plt.subplots(figsize=(8, 6))\n", + "\n", + "# Plot background velocity field\n", + "time_idx = 0\n", + "layer_idx = 0\n", + "speed = (\n", + " np.sqrt(ds[\"VELV\"] ** 2 + ds[\"VELU\"] ** 2)\n", + " .isel(TIME=time_idx, LAYER=layer_idx)\n", + " .compute()\n", + ")\n", + "tpc = plt.pcolor(\n", + " speed.XZETA.values,\n", + " speed.YZETA.values,\n", + " speed,\n", + " edgecolors=\"black\",\n", + " cmap=\"cmo.speed\",\n", + " vmin=0,\n", + " vmax=np.max(np.abs(speed)),\n", + ")\n", + "time_str = pd.to_datetime(speed.TIME.values).strftime(\"%Y-%m-%d %H:%M:%S\")\n", + "fig.colorbar(\n", + " tpc,\n", + " ax=ax,\n", + " label=f\"Flow speed on {time_str} and {speed.SIGMA_C.values:.2f}m depth [m/s]\",\n", + ")\n", + "\n", + "for traj in df.partition_by(\"particle_id\"):\n", + " ax.plot(traj[\"x\"], traj[\"y\"], \"b\", alpha=0.8)\n", + "\n", + "ax.plot(points_x, points_y, \"m.\", label=\"initial particle positions\")\n", + "ax.set_aspect(\"equal\", adjustable=\"box\")\n", + "ax.set_xlabel(ds.XZETA.long_name)\n", + "ax.set_ylabel(ds.YZETA.long_name)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Parcels:docs (3.14.6)", + "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.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pixi.toml b/pixi.toml index 71035f952..09c7a4ef5 100644 --- a/pixi.toml +++ b/pixi.toml @@ -93,6 +93,7 @@ trajan = "*" matplotlib-base = ">=2.0.2" gsw = "*" py-triangle = "*" +cmocean = ">=4.0.3,<5" [feature.devtools.dependencies] pdbpp = "*" From 41ed6f77eba307921320c3c6cd8c84e8837e40e8 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 09:12:28 +0200 Subject: [PATCH 3/9] Making explicit that delft3d_to_sgrid is for structured grids --- src/parcels/convert.py | 22 ++++++++++------------ tests/test_convert.py | 3 +-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/parcels/convert.py b/src/parcels/convert.py index 9d46184e8..552dc51ff 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -102,16 +102,16 @@ class _Status(enum.Enum): "T": "time", } -_DELFT3D_EXPECTED_COORDS: list[tuple[str, _Status]] = [(name, _Status.REQUIRED) for name in ["XZETA", "YZETA"]] +_DELFT3D_X_EXPECTED_COORDS: list[tuple[str, _Status]] = [(name, _Status.REQUIRED) for name in ["XZETA", "YZETA"]] -_DELFT3D_VARNAMES_MAPPING: dict[str, str] = { +_DELFT3D_X_VARNAMES_MAPPING: dict[str, str] = { "XZETA": "lon", "YZETA": "lat", "SIGMA_C": "depth", "TIME": "time", } -_DELFT3D_AXIS_VARNAMES: dict[str, XgcmAxisDirection] = { +_DELFT3D_X_AXIS_VARNAMES: dict[str, XgcmAxisDirection] = { "M": "X", "N": "Y", "LAYER": "Z", @@ -594,13 +594,13 @@ def copernicusmarine_to_sgrid( def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr.Dataset) -> xr.Dataset: - """Create an sgrid-compliant xarray.Dataset from a dataset of Delft3D netcdf files. + """Create an sgrid-compliant xarray.Dataset from a dataset of structured-grid Delft3D netcdf files. Parameters ---------- fields : dict[str, xr.Dataset | xr.DataArray] - Dictionary of xarray.DataArray objects as obtained from a set of Delft3D netcdf files. - coords : xarray.Dataset, optional + Dictionary of xarray.DataArray objects as obtained from a set of structured-grid Delft3D netcdf files. + coords : xarray.Dataset xarray.Dataset containing coordinate variables. Returns @@ -608,8 +608,6 @@ def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr xarray.Dataset Dataset object following SGRID conventions to be (optionally) modified and passed to a FieldSet constructor. - See the Delft3D documentation for more information on how to use Delft3D model outputs in Parcels - """ warnings.warn( "The delft3d_to_sgrid function is experimental and may not work for all Delft3D datasets. " @@ -620,7 +618,7 @@ def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr ) fields = fields.copy() - coords = _pick_expected_coords(coords, _DELFT3D_EXPECTED_COORDS) + coords = _pick_expected_coords(coords, _DELFT3D_X_EXPECTED_COORDS) for name, field_da in fields.items(): if isinstance(field_da, xr.Dataset): @@ -632,9 +630,9 @@ def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr ds = xr.merge(list(fields.values()) + [coords]) - ds = _maybe_rename_variables(ds, _DELFT3D_VARNAMES_MAPPING) - ds = _set_coords(ds, _DELFT3D_AXIS_VARNAMES.keys()) - ds = _set_axis_attrs(ds, _DELFT3D_AXIS_VARNAMES) + ds = _maybe_rename_variables(ds, _DELFT3D_X_VARNAMES_MAPPING) + ds = _set_coords(ds, _DELFT3D_X_AXIS_VARNAMES.keys()) + ds = _set_axis_attrs(ds, _DELFT3D_X_AXIS_VARNAMES) ds["grid"] = xr.DataArray( 0, diff --git a/tests/test_convert.py b/tests/test_convert.py index a2b68d650..2fdc277b2 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -159,12 +159,11 @@ def test_convert_copernicusmarine_no_currents(caplog): assert caplog.text == "" -def test_convert_delft3d(): +def test_convert_structured_delft3d(): ds = open_remote_dataset("Delft3D_data/Rotterdam_tiny") coords = ds[["XZETA", "YZETA", "SIGMA_C"]] ds_fset = convert.delft3d_to_sgrid(fields={"U": ds["VELU"], "V": ds["VELV"]}, coords=coords) fieldset = FieldSet.from_sgrid_conventions(ds_fset) - fieldset.describe() assert "U" in fieldset.fields assert "V" in fieldset.fields assert "UV" in fieldset.fields From a7e2e6c461b0722742deec3f587e117215f40d33 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 10:16:56 +0200 Subject: [PATCH 4/9] Fixing padding Delft3D padding is similar to NEMO, according to Figure 4.6 of https://content.oss.deltares.nl/delft3d4/Delft3D-FLOW_User_Manual.pdf --- src/parcels/convert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parcels/convert.py b/src/parcels/convert.py index 552dc51ff..abefd595d 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -641,9 +641,9 @@ def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr topology_dimension=2, node_dimensions=("M", "N"), node_coordinates=("lon", "lat"), - face_dimensions=( # TODO check these paddings - sgrid.FaceNodePadding("X", "M", sgrid.Padding.HIGH), - sgrid.FaceNodePadding("Y", "N", sgrid.Padding.HIGH), + face_dimensions=( + sgrid.FaceNodePadding("X", "M", sgrid.Padding.LOW), + sgrid.FaceNodePadding("Y", "N", sgrid.Padding.LOW), ), vertical_dimensions=(sgrid.FaceNodePadding("Z", "LAYER", sgrid.Padding.HIGH),), ).to_attrs(), From aff4b769d6f479a12e0277fe82faf4ed8c45a3e5 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 10:17:21 +0200 Subject: [PATCH 5/9] Fixing warning --- src/parcels/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parcels/convert.py b/src/parcels/convert.py index abefd595d..eed6a5852 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -628,7 +628,7 @@ def delft3d_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr field_da = field_da.rename(name) fields[name] = field_da - ds = xr.merge(list(fields.values()) + [coords]) + ds = xr.merge(list(fields.values()) + [coords], compat="override") ds = _maybe_rename_variables(ds, _DELFT3D_X_VARNAMES_MAPPING) ds = _set_coords(ds, _DELFT3D_X_AXIS_VARNAMES.keys()) From f1d26a6b0899136eb3f70775becc38a7187e1851 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 10:35:02 +0200 Subject: [PATCH 6/9] Add interpolation method --- .../examples/tutorial-delft3d.ipynb | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/user_guide/examples/tutorial-delft3d.ipynb b/docs/user_guide/examples/tutorial-delft3d.ipynb index ad0d43862..9c3cff468 100644 --- a/docs/user_guide/examples/tutorial-delft3d.ipynb +++ b/docs/user_guide/examples/tutorial-delft3d.ipynb @@ -43,10 +43,20 @@ "The example below is of a small domain in the Port of Rotterdam. We first pick the coorindates and the velocity fields from the dataset, and then convert them to the sgrid conventions. Finally, we create a FieldSet from the converted dataset." ] }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "```{note}\n", + "While Delft3D provides velocities on a CGrid, we currently can't use the `CGrid_Velocity` interpolator on the velocity fields. This is because the velocities have been rotated to east and north in the output - but the Parcels interpolator expects them in the `M` and `N` (along-grid) directions. Therefore, we use the `XFreeslip` interpolator instead.\n", + "```" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "4", + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -56,13 +66,17 @@ " fields={\"U\": ds[\"VELU\"], \"V\": ds[\"VELV\"]}, coords=coords\n", ")\n", "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", + "\n", + "# Set the interpolation method for the UV field to XFreeslip (see note above)\n", + "fieldset.UV.interp_method = parcels.interpolators.XFreeslip()\n", + "\n", "fieldset = fieldset.to_windowed_arrays()\n", "fieldset.describe()" ] }, { "cell_type": "markdown", - "id": "5", + "id": "6", "metadata": {}, "source": [ "Now we define a grid of a few particles to release in the domain, and run a simple advection simulation. The particles are advected by the Delft3D velocity fields, and we can visualize their trajectories." @@ -71,7 +85,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6", + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -108,7 +122,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7", + "id": "8", "metadata": {}, "outputs": [], "source": [ From 882b46909d1d5a016205e23b7b14a8232fd103a3 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 10:48:36 +0200 Subject: [PATCH 7/9] Adding tutorial to docs page --- docs/user_guide/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index 06a578d79..c1df2a149 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -40,6 +40,7 @@ examples/explanation_grids.md examples/tutorial_nemo.ipynb examples/tutorial_croco_3D.ipynb examples/tutorial_mitgcm.ipynb +examples/tutorial_delft3d.ipynb examples/tutorial_fesom.ipynb examples/tutorial_schism.ipynb examples/tutorial_velocityconversion.ipynb From baa3579cf8854580c2b37c5d1b8bcfd87c159f36 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 10:51:38 +0200 Subject: [PATCH 8/9] Rename tutorial_delft3d --- .../examples/{tutorial-delft3d.ipynb => tutorial_delft3d.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/user_guide/examples/{tutorial-delft3d.ipynb => tutorial_delft3d.ipynb} (100%) diff --git a/docs/user_guide/examples/tutorial-delft3d.ipynb b/docs/user_guide/examples/tutorial_delft3d.ipynb similarity index 100% rename from docs/user_guide/examples/tutorial-delft3d.ipynb rename to docs/user_guide/examples/tutorial_delft3d.ipynb From d957916f8e5e5f8ae58946f1562c5cb255968753 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 27 Jul 2026 10:52:35 +0200 Subject: [PATCH 9/9] Suppress progressbar in notebook --- docs/user_guide/examples/tutorial_delft3d.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/user_guide/examples/tutorial_delft3d.ipynb b/docs/user_guide/examples/tutorial_delft3d.ipynb index 9c3cff468..f01708899 100644 --- a/docs/user_guide/examples/tutorial_delft3d.ipynb +++ b/docs/user_guide/examples/tutorial_delft3d.ipynb @@ -116,6 +116,7 @@ " endtime=fieldset.time_interval.right,\n", " dt=np.timedelta64(60, \"s\"),\n", " output_file=outputfile,\n", + " verbose_progress=False,\n", ")" ] },