Skip to content

Commit 53dea7e

Browse files
Merge branch 'v4-dev' into interpolators_as_directory
2 parents c5f59a0 + 19b6259 commit 53dea7e

12 files changed

Lines changed: 643 additions & 434 deletions

File tree

docs/user_guide/examples/tutorial_nemo_curvilinear.ipynb

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -64,43 +64,19 @@
6464
"outputs": [],
6565
"source": [
6666
"data_folder = parcels.download_example_dataset(\"NemoCurvilinear_data\")\n",
67-
"files = data_folder.glob(\"*.nc4\")\n",
6867
"ds_fields = xr.open_mfdataset(\n",
69-
" files, combine=\"nested\", data_vars=\"minimal\", coords=\"minimal\", compat=\"override\"\n",
68+
" data_folder.glob(\"*.nc4\"),\n",
69+
" data_vars=\"minimal\",\n",
70+
" coords=\"minimal\",\n",
71+
" compat=\"override\",\n",
7072
")\n",
7173
"\n",
72-
"\n",
73-
"# TODO: replace manual fieldset creation with FieldSet.from_nemo() once available\n",
74-
"ds_fields = (\n",
75-
" ds_fields.isel(time_counter=0, drop=True)\n",
76-
" .isel(time=0, drop=True)\n",
77-
" .isel(z_a=0, drop=True)\n",
78-
" .rename({\"glamf\": \"lon\", \"gphif\": \"lat\", \"z\": \"depth\"})\n",
79-
")\n",
80-
"\n",
81-
"import xgcm\n",
82-
"\n",
83-
"xgcm_grid = xgcm.Grid(\n",
84-
" ds_fields,\n",
85-
" coords={\n",
86-
" \"X\": {\"left\": \"x\"},\n",
87-
" \"Y\": {\"left\": \"y\"},\n",
88-
" },\n",
89-
" periodic=False,\n",
90-
" autoparse_metadata=False,\n",
74+
"ds_coords = xr.open_dataset(data_folder / \"mesh_mask.nc4\", decode_times=False)\n",
75+
"ds_fset = parcels.convert.nemo_to_sgrid(\n",
76+
" fields=dict(U=ds_fields[\"U\"], V=ds_fields[\"V\"]), coords=ds_coords\n",
9177
")\n",
92-
"grid = parcels.XGrid(xgcm_grid, mesh=\"spherical\")\n",
9378
"\n",
94-
"U = parcels.Field(\n",
95-
" \"U\", ds_fields[\"U\"], grid, interp_method=parcels.interpolators.XLinear\n",
96-
")\n",
97-
"V = parcels.Field(\n",
98-
" \"V\", ds_fields[\"V\"], grid, interp_method=parcels.interpolators.XLinear\n",
99-
")\n",
100-
"UV = parcels.VectorField(\n",
101-
" \"UV\", U, V, vector_interp_method=parcels.interpolators.CGrid_Velocity\n",
102-
")\n",
103-
"fieldset = parcels.FieldSet([U, V, UV])"
79+
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)"
10480
]
10581
},
10682
{
@@ -118,7 +94,7 @@
11894
"outputs": [],
11995
"source": [
12096
"fig, ax = plt.subplots(1, 2, figsize=(10, 4))\n",
121-
"pc1 = ds_fields.U.plot(cmap=\"viridis\", ax=ax[0], vmin=0)\n",
97+
"pc1 = fieldset.U.data.plot(cmap=\"viridis\", ax=ax[0], vmin=0)\n",
12298
"pc2 = ax[1].pcolormesh(\n",
12399
" fieldset.U.grid.lon,\n",
124100
" fieldset.U.grid.lat,\n",
@@ -295,7 +271,7 @@
295271
],
296272
"metadata": {
297273
"kernelspec": {
298-
"display_name": "default",
274+
"display_name": "test-notebooks-latest",
299275
"language": "python",
300276
"name": "python3"
301277
},
@@ -309,7 +285,7 @@
309285
"name": "python",
310286
"nbconvert_exporter": "python",
311287
"pygments_lexer": "ipython3",
312-
"version": "3.14.2"
288+
"version": "3.13.9"
313289
}
314290
},
315291
"nbformat": 4,

docs/user_guide/examples_v3/tutorial_nemo_3D.ipynb

Lines changed: 58 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,13 @@
1818
"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",
1919
"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",
2020
"\n",
21-
"Here, we focus on 3D fields. Basically, it is a straightforward extension of the 2D example, but it is very easy to do a mistake in the setup of the vertical discretisation that would affect the interpolation scheme.\n",
22-
"\n",
23-
"## Preliminary comments\n",
24-
"\n",
21+
"```{note}\n",
2522
"_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",
23+
"```\n",
2624
"\n",
27-
"_What about grid indexing?_ Since the C-grid variables are not located on the same nodes, there is not one obvious way to define the indexing, i.e. where is `u[k,j,i]` compared to `v[k,j,i]` and `w[k,j,i]`. In Parcels, we use the same notation as in NEMO: see [horizontal indexing](https://www.nemo-ocean.eu/doc/img360.png) and [vertical indexing](https://www.nemo-ocean.eu/doc/img362.png).\n",
28-
"It is important that you check if your data is following the same notation. Otherwise, you should re-index your data properly (this can be done within Parcels, there is no need to regenerate new netcdf files).\n",
29-
"\n",
30-
"_What about the accuracy?_ By default in Parcels, particle coordinates (i.e. longitude, latitude and depth) are stored using single-precision `np.float32` numbers. The advantage of this is that it saves some memory resources for the computation. In some applications, especially where particles travel very close to the coast, the single precision accuracy can lead to uncontrolled particle beaching, due to numerical rounding errors. In such case, you may want to double the coordinate precision to `np.float64`. This can be done by adding the parameter `lonlatdepth_dtype=np.float64` to the ParticleSet constructor. Note that for C-grid fieldsets such as in NEMO, the coordinates precision is set by default to `np.float64`.\n",
31-
"\n",
32-
"## How to create a 3D NEMO `dimensions` dictionary?\n",
33-
"\n",
34-
"In the following, we will show how to create the `dimensions` dictionary for 3D NEMO simulations. What you require is a 'mesh_mask' file, which in our case is called `coordinates.nc` but in some other versions of NEMO has a different name. In any case, it will have to contain the variables `glamf`, `gphif` and `depthw`, which are the longitude, latitude and depth of the mesh nodes, respectively. Note that `depthw` is not part of the mesh_mask file, but is in the same file as the w data (`wfiles[0]`).\n",
35-
"\n",
36-
"For the C-grid interpolation in Parcels to work properly, it is important that `U`, `V` and `W` are on the same grid. \n",
25+
"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",
3726
"\n",
38-
"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.\n",
39-
"\n",
40-
"The code below is an example of how to create a 3D simulation with particles, starting in the mouth of the river Rhine at 1m depth, and advecting them through the North Sea using the `AdvectionRK4_3D`\n"
27+
"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."
4128
]
4229
},
4330
{
@@ -46,69 +33,18 @@
4633
"metadata": {},
4734
"outputs": [],
4835
"source": [
49-
"import warnings\n",
50-
"from datetime import timedelta\n",
51-
"from glob import glob\n",
52-
"\n",
5336
"import matplotlib.pyplot as plt\n",
37+
"import numpy as np\n",
5438
"import xarray as xr\n",
5539
"\n",
56-
"import parcels\n",
57-
"from parcels import FileWarning\n",
58-
"\n",
59-
"# Add a filter for the xarray decoding warning\n",
60-
"warnings.simplefilter(\"ignore\", FileWarning)\n",
61-
"\n",
62-
"example_dataset_folder = parcels.download_example_dataset(\n",
63-
" \"NemoNorthSeaORCA025-N006_data\"\n",
64-
")\n",
65-
"ufiles = sorted(glob(f\"{example_dataset_folder}/ORCA*U.nc\"))\n",
66-
"vfiles = sorted(glob(f\"{example_dataset_folder}/ORCA*V.nc\"))\n",
67-
"wfiles = sorted(glob(f\"{example_dataset_folder}/ORCA*W.nc\"))\n",
68-
"# tfiles = sorted(glob(f\"{example_dataset_folder}/ORCA*T.nc\")) # Not used in this example\n",
69-
"mesh_mask = f\"{example_dataset_folder}/coordinates.nc\"\n",
70-
"\n",
71-
"filenames = {\n",
72-
" \"U\": {\"lon\": mesh_mask, \"lat\": mesh_mask, \"depth\": wfiles[0], \"data\": ufiles},\n",
73-
" \"V\": {\"lon\": mesh_mask, \"lat\": mesh_mask, \"depth\": wfiles[0], \"data\": vfiles},\n",
74-
" \"W\": {\"lon\": mesh_mask, \"lat\": mesh_mask, \"depth\": wfiles[0], \"data\": wfiles},\n",
75-
" # \"T\": {\"lon\": mesh_mask, \"lat\": mesh_mask, \"depth\": wfiles[0], \"data\": tfiles}, # Not used in this example\n",
76-
"}\n",
77-
"\n",
78-
"variables = {\n",
79-
" \"U\": \"uo\",\n",
80-
" \"V\": \"vo\",\n",
81-
" \"W\": \"wo\",\n",
82-
" # \"T\": \"thetao\", # Not used in this example\n",
83-
"}\n",
84-
"\n",
85-
"# Note that all variables need the same dimensions in a C-Grid\n",
86-
"c_grid_dimensions = {\n",
87-
" \"lon\": \"glamf\",\n",
88-
" \"lat\": \"gphif\",\n",
89-
" \"depth\": \"depthw\",\n",
90-
" \"time\": \"time_counter\",\n",
91-
"}\n",
92-
"dimensions = {\n",
93-
" \"U\": c_grid_dimensions,\n",
94-
" \"V\": c_grid_dimensions,\n",
95-
" \"W\": c_grid_dimensions,\n",
96-
" # \"T\": c_grid_dimensions, # Not used in this example\n",
97-
"}\n",
98-
"\n",
99-
"fieldset = parcels.FieldSet.from_nemo(filenames, variables, dimensions)\n",
100-
"\n",
101-
"pset = parcels.ParticleSet.from_line(\n",
102-
" fieldset=fieldset,\n",
103-
" pclass=parcels.Particle,\n",
104-
" size=10,\n",
105-
" start=(1.9, 52.5),\n",
106-
" finish=(3.4, 51.6),\n",
107-
" depth=1,\n",
108-
")\n",
109-
"\n",
110-
"kernels = pset.Kernel(parcels.kernels.AdvectionRK4_3D)\n",
111-
"pset.execute(kernels, runtime=timedelta(days=4), dt=timedelta(hours=6))"
40+
"import parcels"
41+
]
42+
},
43+
{
44+
"cell_type": "markdown",
45+
"metadata": {},
46+
"source": [
47+
"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."
11248
]
11349
},
11450
{
@@ -117,47 +53,63 @@
11753
"metadata": {},
11854
"outputs": [],
11955
"source": [
120-
"depth_level = 8\n",
121-
"print(\n",
122-
" f\"Level[{int(depth_level)}] depth is: \"\n",
123-
" f\"[{fieldset.W.grid.depth[depth_level]:g} \"\n",
124-
" f\"{fieldset.W.grid.depth[depth_level + 1]:g}]\"\n",
56+
"data_folder = parcels.download_example_dataset(\"NemoNorthSeaORCA025-N006_data\")\n",
57+
"ds_fields = xr.open_mfdataset(\n",
58+
" data_folder.glob(\"ORCA*.nc\"),\n",
59+
" data_vars=\"minimal\",\n",
60+
" coords=\"minimal\",\n",
61+
" compat=\"override\",\n",
12562
")\n",
126-
"\n",
127-
"plt.pcolormesh(\n",
128-
" fieldset.U.grid.lon, fieldset.U.grid.lat, fieldset.U.data[0, depth_level, :, :]\n",
63+
"ds_coords = xr.open_dataset(data_folder / \"coordinates.nc\", decode_times=False)\n",
64+
"ds_fset = parcels.convert.nemo_to_sgrid(\n",
65+
" fields={\"U\": ds_fields[\"uo\"], \"V\": ds_fields[\"vo\"], \"W\": ds_fields[\"wo\"]},\n",
66+
" coords=ds_coords,\n",
12967
")\n",
130-
"plt.show()"
68+
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)"
13169
]
13270
},
13371
{
134-
"attachments": {},
13572
"cell_type": "markdown",
13673
"metadata": {},
13774
"source": [
138-
"## Adding other fields like cell edges\n",
139-
"\n",
140-
"It is quite straightforward to add other gridded data, on the same curvilinear or any other type of grid, to the fieldset. Because it is good practice to make no changes to a `FieldSet` once a `ParticleSet` has been defined in it, we redefine the fieldset and add the fields with the cell edges from the coordinates file using `FieldSet.add_field()`.\n",
141-
"\n",
142-
"Note that including tracers like `temperature` and `salinity` needs to be done at the f-points, so on the same grid as the velocity fields. See also the section above.\n"
75+
"The code below is an example of how to then create a 3D simulation with particles, starting on a meridional line through the North Sea from the mouth of the river Rhine at 100m depth, and advecting them through the North Sea using the `AdvectionRK2_3D`"
14376
]
14477
},
14578
{
14679
"cell_type": "code",
14780
"execution_count": null,
148-
"metadata": {},
81+
"metadata": {
82+
"tags": [
83+
"hide-output"
84+
]
85+
},
14986
"outputs": [],
15087
"source": [
151-
"from parcels import Field"
88+
"npart = 10\n",
89+
"pset = parcels.ParticleSet(\n",
90+
" fieldset=fieldset,\n",
91+
" lon=np.linspace(1.9, 3.4, npart),\n",
92+
" lat=np.linspace(65, 51.6, npart),\n",
93+
" z=100 * np.ones(npart),\n",
94+
")\n",
95+
"\n",
96+
"pfile = parcels.ParticleFile(\n",
97+
" store=\"output_nemo3D.zarr\", outputdt=np.timedelta64(1, \"D\")\n",
98+
")\n",
99+
"\n",
100+
"pset.execute(\n",
101+
" parcels.kernels.AdvectionRK2_3D,\n",
102+
" endtime=fieldset.time_interval.right,\n",
103+
" dt=np.timedelta64(6, \"h\"),\n",
104+
" output_file=pfile,\n",
105+
")"
152106
]
153107
},
154108
{
155-
"cell_type": "code",
156-
"execution_count": null,
109+
"cell_type": "markdown",
157110
"metadata": {},
158-
"outputs": [],
159111
"source": [
160-
"fieldset = parcels.FieldSet.from_nemo(filenames, variables, dimensions)"
112+
"We can then plot the trajectories on top of the surface U field"
161113
]
162114
},
163115
{
@@ -166,34 +118,14 @@
166118
"metadata": {},
167119
"outputs": [],
168120
"source": [
169-
"e1u = Field.from_netcdf(\n",
170-
" filenames=mesh_mask,\n",
171-
" variable=\"e1u\",\n",
172-
" dimensions={\"lon\": \"glamu\", \"lat\": \"gphiu\"},\n",
173-
" interp_method=\"nearest\",\n",
174-
")\n",
175-
"e2u = Field.from_netcdf(\n",
176-
" filenames=mesh_mask,\n",
177-
" variable=\"e2u\",\n",
178-
" dimensions={\"lon\": \"glamu\", \"lat\": \"gphiu\"},\n",
179-
" interp_method=\"nearest\",\n",
180-
")\n",
181-
"e1v = Field.from_netcdf(\n",
182-
" filenames=mesh_mask,\n",
183-
" variable=\"e1v\",\n",
184-
" dimensions={\"lon\": \"glamv\", \"lat\": \"gphiv\"},\n",
185-
" interp_method=\"nearest\",\n",
186-
")\n",
187-
"e2v = Field.from_netcdf(\n",
188-
" filenames=mesh_mask,\n",
189-
" variable=\"e2v\",\n",
190-
" dimensions={\"lon\": \"glamv\", \"lat\": \"gphiv\"},\n",
191-
" interp_method=\"nearest\",\n",
192-
")\n",
193-
"fieldset.add_field(e1u)\n",
194-
"fieldset.add_field(e2u)\n",
195-
"fieldset.add_field(e1v)\n",
196-
"fieldset.add_field(e2v)"
121+
"field = fieldset.U.data[0, 0, :, :]\n",
122+
"field = field.where(field != 0, np.nan) # Mask land values for better plotting\n",
123+
"plt.pcolormesh(fieldset.U.grid.lon, fieldset.U.grid.lat, field, cmap=\"RdBu\")\n",
124+
"\n",
125+
"ds_out = xr.open_zarr(\"output_nemo3D.zarr\")\n",
126+
"plt.scatter(ds_out.lon.T, ds_out.lat.T, c=-ds_out.z.T, marker=\".\")\n",
127+
"plt.colorbar(label=\"Depth (m)\")\n",
128+
"plt.show()"
197129
]
198130
}
199131
],

docs/user_guide/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ The tutorials written for Parcels v3 are currently being updated for Parcels v4.
2626
:titlesonly:
2727
examples/explanation_grids.md
2828
examples/tutorial_nemo_curvilinear.ipynb
29+
examples_v3/tutorial_nemo_3D.ipynb # TODO move to examples folder
2930
examples/tutorial_unitconverters.ipynb
3031
examples/tutorial_nestedgrids.ipynb
3132
```
3233

3334
<!-- examples/documentation_indexing.ipynb -->
34-
<!-- examples/tutorial_nemo_3D.ipynb -->
3535
<!-- examples/tutorial_croco_3D.ipynb -->
3636
<!-- examples/tutorial_timevaryingdepthdimensions.ipynb -->
3737

0 commit comments

Comments
 (0)