From cea0b91bfc268c6c93f8ca60640dcc115e7207fa Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 21 Jan 2026 08:44:16 +0100 Subject: [PATCH 1/4] Updating convert_croco to pick coords from full dataset --- docs/user_guide/examples/tutorial_croco_3D.ipynb | 5 ++--- src/parcels/convert.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/user_guide/examples/tutorial_croco_3D.ipynb b/docs/user_guide/examples/tutorial_croco_3D.ipynb index 652ede607..a84b0cc74 100644 --- a/docs/user_guide/examples/tutorial_croco_3D.ipynb +++ b/docs/user_guide/examples/tutorial_croco_3D.ipynb @@ -59,7 +59,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we create a FieldSet object using the `FieldSet.from_croco()` method. Note that CROCO is a C-grid (with similar indexing at MITgcm)." + "Now we create a FieldSet object using the `convert.croco_to_sgrid()` function to first create an S-Grid compliant datatset, and then use that in `FieldSet.from_sgrid_conventions()` to create the FieldSet." ] }, { @@ -86,8 +86,7 @@ " \"Cs_w\": ds_fields[\"Cs_w\"],\n", "}\n", "\n", - "coords = ds_fields[[\"x_rho\", \"y_rho\", \"s_w\", \"time\"]]\n", - "ds_fset = parcels.convert.croco_to_sgrid(fields=fields, coords=coords)\n", + "ds_fset = parcels.convert.croco_to_sgrid(fields=fields, coords=ds_fields)\n", "\n", "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", "\n", diff --git a/src/parcels/convert.py b/src/parcels/convert.py index 0fdc9fd51..1428e8c62 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -70,6 +70,8 @@ "T": "time", } +_CROCO_EXPECTED_COORDS = ["x_rho", "y_rho", "s_w", "time"] + _CROCO_VARNAMES_MAPPING = { "x_rho": "lon", "y_rho": "lat", @@ -77,6 +79,16 @@ } +def _pick_expected_coords(coords: xr.Dataset, expected_coord_names: list[str]) -> xr.Dataset: + coords_to_use = {} + for name in expected_coord_names: + if name in coords: + coords_to_use[name] = coords[name] + else: + raise ValueError(f"Expected coordinate '{name}' not found in provided coords dataset.") + return xr.Dataset(coords_to_use) + + def _maybe_bring_UV_depths_to_depth(ds): if "U" in ds.variables and "depthu" in ds.U.coords and "depth" in ds.coords: ds["U"] = ds["U"].assign_coords(depthu=ds["depth"].values).rename({"depthu": "depth"}) @@ -417,6 +429,8 @@ def croco_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr.D field_da = field_da.rename(name) fields[name] = field_da + coords = _pick_expected_coords(coords, _CROCO_EXPECTED_COORDS) + ds = xr.merge(list(fields.values()) + [coords]) ds.attrs.clear() # Clear global attributes from the merging From 533b59b00a83616eaed1f9d7784002f1ea9a3c24 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 21 Jan 2026 08:53:56 +0100 Subject: [PATCH 2/4] pick expected coordinates in convert.nemo --- docs/user_guide/examples/tutorial_nemo.ipynb | 4 ++-- src/parcels/convert.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/user_guide/examples/tutorial_nemo.ipynb b/docs/user_guide/examples/tutorial_nemo.ipynb index d56aa5194..fd2f50ab6 100644 --- a/docs/user_guide/examples/tutorial_nemo.ipynb +++ b/docs/user_guide/examples/tutorial_nemo.ipynb @@ -369,7 +369,7 @@ ], "metadata": { "kernelspec": { - "display_name": "parcels", + "display_name": "docs", "language": "python", "name": "python3" }, @@ -383,7 +383,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/src/parcels/convert.py b/src/parcels/convert.py index 1428e8c62..b6b93bfe0 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -23,6 +23,8 @@ if typing.TYPE_CHECKING: import uxarray as ux +_NEMO_EXPECTED_COORDS = ["glamf", "gphif"] + _NEMO_DIMENSION_COORD_NAMES = ["x", "y", "time", "x", "x_center", "y", "y_center", "depth", "glamf", "gphif"] _NEMO_AXIS_VARNAMES = { @@ -257,7 +259,7 @@ def nemo_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr.Da """ fields = fields.copy() - coords = coords[["gphif", "glamf"]] + coords = _pick_expected_coords(coords, _NEMO_EXPECTED_COORDS) for name, field_da in fields.items(): if isinstance(field_da, xr.Dataset): From 36a4508ee891328df65b96f2d72aef1a7720120c Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 21 Jan 2026 09:15:12 +0100 Subject: [PATCH 3/4] pick expected coords from dataset in mitgcm --- docs/user_guide/examples/tutorial_mitgcm.ipynb | 9 ++------- src/parcels/convert.py | 4 ++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/user_guide/examples/tutorial_mitgcm.ipynb b/docs/user_guide/examples/tutorial_mitgcm.ipynb index ce91dc7f3..e94a0ea88 100644 --- a/docs/user_guide/examples/tutorial_mitgcm.ipynb +++ b/docs/user_guide/examples/tutorial_mitgcm.ipynb @@ -38,11 +38,7 @@ "id": "3", "metadata": {}, "source": [ - "We can use a combination of `parcels.convert.mitgcm_to_sgrid` and `FieldSet.from_sgrid_conventions` to read in the data. See below for an example.\n", - "\n", - "```{note}\n", - "It is very important that you provide the corner nodes as coordinates when converting MITgcm data to S-grid conventions. These corner nodes are typically called `XG` and `YG` in MITgcm output. Failing to do so will lead to incorrect interpolation of the velocity fields.\n", - "```" + "We can use a combination of `parcels.convert.mitgcm_to_sgrid` and `FieldSet.from_sgrid_conventions` to read in the data. See below for an example." ] }, { @@ -52,9 +48,8 @@ "metadata": {}, "outputs": [], "source": [ - "coords = ds_fields[[\"XG\", \"YG\", \"Zl\", \"time\"]]\n", "ds_fset = parcels.convert.mitgcm_to_sgrid(\n", - " fields={\"U\": ds_fields.UVEL, \"V\": ds_fields.VVEL}, coords=coords\n", + " fields={\"U\": ds_fields.UVEL, \"V\": ds_fields.VVEL}, coords=ds_fields\n", ")\n", "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)" ] diff --git a/src/parcels/convert.py b/src/parcels/convert.py index b6b93bfe0..70beaf0d5 100644 --- a/src/parcels/convert.py +++ b/src/parcels/convert.py @@ -44,6 +44,8 @@ "wo": "W", } +_MITGCM_EXPECTED_COORDS = ["XG", "YG", "Zl"] + _MITGCM_AXIS_VARNAMES = { "XC": "X", "XG": "X", @@ -371,6 +373,8 @@ def mitgcm_to_sgrid(*, fields: dict[str, xr.Dataset | xr.DataArray], coords: xr. field_da = field_da.rename(name) fields[name] = field_da + coords = _pick_expected_coords(coords, _MITGCM_EXPECTED_COORDS) + ds = xr.merge(list(fields.values()) + [coords]) ds.attrs.clear() # Clear global attributes from the merging From 0d4a526270a3340b8727e59b282daaead7781009 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Wed, 21 Jan 2026 09:25:45 +0100 Subject: [PATCH 4/4] updating unit tests --- tests/test_advection.py | 3 +-- tests/test_sigmagrids.py | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_advection.py b/tests/test_advection.py index 622e05aca..c5d6a9ebf 100644 --- a/tests/test_advection.py +++ b/tests/test_advection.py @@ -508,8 +508,7 @@ def test_mitgcm(): data_folder = parcels.download_example_dataset("MITgcm_example_data") ds_fields = xr.open_dataset(data_folder / "mitgcm_UV_surface_zonally_reentrant.nc") - coords = ds_fields[["XG", "YG", "Zl", "time"]] - ds_fset = convert.mitgcm_to_sgrid(fields={"U": ds_fields.UVEL, "V": ds_fields.VVEL}, coords=coords) + ds_fset = convert.mitgcm_to_sgrid(fields={"U": ds_fields.UVEL, "V": ds_fields.VVEL}, coords=ds_fields) fieldset = FieldSet.from_sgrid_conventions(ds_fset) npart = 10 diff --git a/tests/test_sigmagrids.py b/tests/test_sigmagrids.py index e67a802cd..de437c8fb 100644 --- a/tests/test_sigmagrids.py +++ b/tests/test_sigmagrids.py @@ -28,8 +28,7 @@ def test_conversion_3DCROCO(): "Cs_w": ds_fields["Cs_w"], } - coords = ds_fields[["x_rho", "y_rho", "s_w", "time"]] - ds_fset = parcels.convert.croco_to_sgrid(fields=fields, coords=coords) + ds_fset = parcels.convert.croco_to_sgrid(fields=fields, coords=ds_fields) fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset) fieldset.add_constant("hc", ds_fields.hc.item()) @@ -61,8 +60,7 @@ def test_advection_3DCROCO(): "omega": ds_fields["omega"], } - coords = ds_fields[["x_rho", "y_rho", "s_w", "time"]] - ds_fset = parcels.convert.croco_to_sgrid(fields=fields, coords=coords) + ds_fset = parcels.convert.croco_to_sgrid(fields=fields, coords=ds_fields) fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset) fieldset.add_constant("hc", ds_fields.hc.item())