Skip to content

Commit 6b05c32

Browse files
Merge branch 'main' into support_some_fields_without_time_in_fieldset
2 parents 84463aa + 4b622d3 commit 6b05c32

30 files changed

Lines changed: 607 additions & 390 deletions

docs/development/unstructured_grid_search.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ The algorithm has two phases: **initialisation** (once, when the grid is first u
4242

4343
The hash grid is three-dimensional regardless of the source grid:
4444

45-
| Source grid | Mesh type | Hash-grid space |
46-
| ----------- | --------- | ------------------------------------- |
47-
| `XGrid` | spherical | Cartesian unit cube (lon/lat → x,y,z) |
48-
| `XGrid` | flat | 2-D lon/lat bounding box (z set to 0) |
49-
| `UxGrid` | spherical | Cartesian unit cube |
50-
| `UxGrid` | flat | 2-D lon/lat bounding box |
45+
| Source grid | Mesh type | Hash-grid space |
46+
| ----------- | --------- | ---------------------------------------- |
47+
| `XGrid` | spherical | Cartesian bounding box (lon/lat → x,y,z) |
48+
| `XGrid` | flat | 2-D lon/lat bounding box (z set to 0) |
49+
| `UxGrid` | spherical | Cartesian bounding box (lon/lat → x,y,z) |
50+
| `UxGrid` | flat | 2-D lon/lat bounding box |
5151

5252
For spherical grids, node coordinates are converted from degrees to radians and then to Cartesian (x, y, z) on the unit sphere using `parcels._core.index_search._latlon_rad_to_xyz`:
5353

@@ -57,7 +57,7 @@ y = sin(lon) * cos(lat)
5757
z = sin(lat)
5858
```
5959

60-
The hash grid then spans the unit cube `[-1, 1]³`. Working in Cartesian space avoids the longitude wrap-around discontinuity that would otherwise cause the bounding boxes of cells crossing the antimeridian to erroneously span the entire domain.
60+
The hash grid then spans the axis-aligned Cartesian bounding box of the transformed grid nodes. For a global grid this is (nearly) the unit cube `[-1, 1]³`; for a regional grid it is much tighter, so the full quantization resolution (1024 bins per axis) is spent on the region actually covered by the grid rather than the whole sphere. Working in Cartesian space avoids the longitude wrap-around discontinuity that would otherwise cause the bounding boxes of cells crossing the antimeridian to erroneously span the entire domain.
6161

6262
For flat meshes the hash grid simply spans `[lon_min, lon_max] × [lat_min, lat_max]`, with z fixed at 0.
6363

docs/user_guide/additional_examples.md

Lines changed: 0 additions & 75 deletions
This file was deleted.

docs/user_guide/examples/explanation_kernelloop.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import parcels.tutorial
5757
5858
# Load the CopernicusMarine data in the Agulhas region from the example_datasets
5959
ds_fields = parcels.tutorial.open_dataset("CopernicusMarine_data_for_Argo_tutorial/data")
60-
ds_fields.load() # load the dataset into memory
6160
6261
# Create an idealised wind field and add it to the dataset
6362
ydim, xdim = len(ds_fields.latitude), len(ds_fields.longitude)
@@ -83,6 +82,9 @@ fieldset = parcels.FieldSet.from_sgrid_conventions(
8382
"Wind": ("UWind", "VWind")
8483
}
8584
)
85+
86+
# Convert the FieldSet to windowed arrays for better performance
87+
fieldset = fieldset.to_windowed_arrays()
8688
```
8789

8890
Now we define a wind Kernel that uses a forward Euler method to apply the wind forcing. Note that we update the `particles.dx` and `particles.dy` variables, rather than `particles.x` and `particles.y` directly.

docs/user_guide/examples/tutorial_croco_3D.ipynb

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,11 @@
3737
"import matplotlib.pyplot as plt\n",
3838
"import numpy as np\n",
3939
"import polars as pl\n",
40-
"import xarray as xr\n",
4140
"\n",
4241
"import parcels\n",
4342
"import parcels.tutorial\n",
4443
"\n",
45-
"ds_fields = parcels.tutorial.open_dataset(\"CROCOidealized_data/data\")\n",
46-
"\n",
47-
"ds_fields.load(); # Preload data to speed up access"
44+
"ds_fields = parcels.tutorial.open_dataset(\"CROCOidealized_data/data\")"
4845
]
4946
},
5047
{
@@ -92,7 +89,10 @@
9289
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
9390
"\n",
9491
"# Add the critical depth (`hc`) as context to the fieldset\n",
95-
"fieldset.add_context(\"hc\", ds_fields.hc.item())"
92+
"fieldset.add_context(\"hc\", ds_fields.hc.item())\n",
93+
"\n",
94+
"# Convert the FieldSet to windowed arrays for better performance\n",
95+
"fieldset = fieldset.to_windowed_arrays()"
9696
]
9797
},
9898
{
@@ -132,7 +132,7 @@
132132
")\n",
133133
"\n",
134134
"pset.execute(\n",
135-
" [parcels.kernels.AdvectionRK4_3D_CROCO, DeleteParticle],\n",
135+
" [parcels.kernels.AdvectionRK2_3D_CROCO, DeleteParticle],\n",
136136
" runtime=np.timedelta64(50_000, \"s\"),\n",
137137
" dt=np.timedelta64(100, \"s\"),\n",
138138
" output_file=outputfile,\n",
@@ -173,7 +173,7 @@
173173
"ax.set_xlabel(\"X [km]\")\n",
174174
"ax.set_xlim(30, 170)\n",
175175
"ax.set_ylabel(\"Depth [m]\")\n",
176-
"ax.set_title(\"Particles in idealized CROCO velocity field using AdvectionRK4_3D_CROCO\")\n",
176+
"ax.set_title(\"Particles in idealized CROCO velocity field using AdvectionRK2_3D_CROCO\")\n",
177177
"plt.tight_layout()\n",
178178
"plt.show()"
179179
]
@@ -189,7 +189,7 @@
189189
"cell_type": "markdown",
190190
"metadata": {},
191191
"source": [
192-
"It may be insightful to compare this 3D run with the `AdvectionRK4_3D_CROCO` kernel with a run where the vertical velocity (`W`) is set to zero. In that case, the particles will not stay on their initial depth levels but instead follow sigma-layers."
192+
"It may be insightful to compare this 3D run with the `AdvectionRK2_3D_CROCO` kernel with a run where the vertical velocity (`W`) is set to zero. In that case, the particles will not stay on their initial depth levels but instead follow sigma-layers."
193193
]
194194
},
195195
{
@@ -205,6 +205,7 @@
205205
"fieldset_noW = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
206206
"fieldset_noW.W.data[:] = 0.0\n",
207207
"fieldset_noW.add_context(\"hc\", ds_fields.hc.item())\n",
208+
"fieldset_noW = fieldset_noW.to_windowed_arrays()\n",
208209
"\n",
209210
"X, Z = np.meshgrid(\n",
210211
" [40e3, 80e3, 120e3],\n",
@@ -221,7 +222,7 @@
221222
")\n",
222223
"\n",
223224
"pset_noW.execute(\n",
224-
" [parcels.kernels.AdvectionRK4_3D_CROCO, DeleteParticle],\n",
225+
" [parcels.kernels.AdvectionRK2_3D_CROCO, DeleteParticle],\n",
225226
" runtime=np.timedelta64(50_000, \"s\"),\n",
226227
" dt=np.timedelta64(100, \"s\"),\n",
227228
" output_file=outputfile,\n",
@@ -273,9 +274,9 @@
273274
"cell_type": "markdown",
274275
"metadata": {},
275276
"source": [
276-
"For Advection, you will need to use the `AdvectionRK4_3D_CROCO` kernel, which works slightly different from the normal 3D advection kernel because it converts the vertical velocity in sigma-units. The conversion from z to sigma is done at every time step, using the `convert_z_to_sigma_croco()` function.\n",
277+
"For Advection, you will need to use the `AdvectionRK2_3D_CROCO` kernel, which works slightly different from the normal 3D advection kernel because it converts the vertical velocity in sigma-units. The conversion from z to sigma is done at every time step, using the `convert_z_to_sigma_croco()` function.\n",
277278
"\n",
278-
"In particular, the following algorithm is used (note that the RK4 version is slightly more complex than this Euler-Forward version, but the idea is identical). Also note that the vertical velocity is linearly interpolated here, which gives much better results than the default C-grid interpolation.\n",
279+
"In particular, the following algorithm is used (note that the RK2 version is slightly more complex than this Euler-Forward version, but the idea is identical). Also note that the vertical velocity is linearly interpolated here, which gives much better results than the default C-grid interpolation.\n",
279280
"\n",
280281
"```python\n",
281282
"sigma = particles.z / fieldset.h[particles.t, 0, particles.y, particles.x]\n",
@@ -297,6 +298,11 @@
297298
"z_new = sigma_new * fieldset.h[particles.t, 0, y_new, x_new, particles]\n",
298299
"```"
299300
]
301+
},
302+
{
303+
"cell_type": "markdown",
304+
"metadata": {},
305+
"source": []
300306
}
301307
],
302308
"metadata": {

docs/user_guide/examples/tutorial_delaystart.ipynb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,14 @@
5656
"ds_fields = parcels.tutorial.open_dataset(\n",
5757
" \"CopernicusMarine_data_for_Argo_tutorial/data\"\n",
5858
")\n",
59-
"ds_fields.load() # load the dataset into memory\n",
6059
"\n",
6160
"# Convert to SGRID-compliant dataset and create FieldSet\n",
6261
"fields = {\"U\": ds_fields[\"uo\"], \"V\": ds_fields[\"vo\"]}\n",
6362
"ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n",
64-
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)"
63+
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
64+
"\n",
65+
"# Convert the FieldSet to windowed arrays for better performance\n",
66+
"fieldset = fieldset.to_windowed_arrays()"
6567
]
6668
},
6769
{
@@ -398,7 +400,7 @@
398400
],
399401
"metadata": {
400402
"kernelspec": {
401-
"display_name": "Parcels:docs (3.14.6)",
403+
"display_name": "Python 3",
402404
"language": "python",
403405
"name": "python3"
404406
},

docs/user_guide/examples/tutorial_diffusion.ipynb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,7 @@
472472
"# Load the CopernicusMarine data in the Agulhas region from the example_datasets\n",
473473
"ds_fields = parcels.tutorial.open_dataset(\n",
474474
" \"CopernicusMarine_data_for_Argo_tutorial/data\"\n",
475-
").isel(depth=slice(0, 1))\n",
476-
"ds_fields.load() # load the dataset into memory"
475+
").isel(depth=slice(0, 1))"
477476
]
478477
},
479478
{
@@ -528,7 +527,10 @@
528527
"ds_cell_areas[\"cell_areas\"] = ([\"lat\", \"lon\"], calc_cell_areas(ds_fields))\n",
529528
"fset2 = parcels.FieldSet.from_sgrid_conventions(ds_cell_areas)\n",
530529
"\n",
531-
"fieldset += fset2"
530+
"fieldset += fset2\n",
531+
"\n",
532+
"# Convert the FieldSet to windowed arrays for better performance\n",
533+
"fieldset = fieldset.to_windowed_arrays()"
532534
]
533535
},
534536
{

docs/user_guide/examples/tutorial_dt_integrators.ipynb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
"ds_fields = parcels.tutorial.open_dataset(\n",
6868
" \"CopernicusMarine_data_for_Argo_tutorial/data\"\n",
6969
")\n",
70-
"ds_fields.load() # load the dataset into memory\n",
7170
"\n",
7271
"OUTPUT_FOLDER = Path(\"output\")\n",
7372
"OUTPUT_FOLDER.mkdir(exist_ok=True)\n",
@@ -77,6 +76,9 @@
7776
"ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n",
7877
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
7978
"\n",
79+
"# Convert the FieldSet to windowed arrays for better performance\n",
80+
"fieldset = fieldset.to_windowed_arrays()\n",
81+
"\n",
8082
"# Check field resolution in time and space\n",
8183
"print(\n",
8284
" f\"dt = {np.unique(np.diff(ds_fields.time.values).astype('timedelta64[h]'))} hours\"\n",

docs/user_guide/examples/tutorial_gsw_density.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@
4343
" \"CopernicusMarine_data_for_Argo_tutorial/data\"\n",
4444
")\n",
4545
"\n",
46-
"# TODO check how we can get good performance without loading full dataset in memory\n",
47-
"ds_fields.load() # load the dataset into memory\n",
48-
"\n",
4946
"# Convert to SGRID-compliant dataset and create FieldSet\n",
5047
"fields = {\n",
5148
" \"U\": ds_fields[\"uo\"],\n",
@@ -54,7 +51,10 @@
5451
" \"so\": ds_fields[\"so\"],\n",
5552
"}\n",
5653
"ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n",
57-
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)"
54+
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
55+
"\n",
56+
"# Convert the FieldSet to windowed arrays for better performance\n",
57+
"fieldset = fieldset.to_windowed_arrays()"
5858
]
5959
},
6060
{

docs/user_guide/examples/tutorial_manipulating_field_data.ipynb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
"ds_fields = parcels.tutorial.open_dataset(\n",
5050
" \"CopernicusMarine_data_for_Argo_tutorial/data\"\n",
5151
")\n",
52-
"ds_fields.load() # load the dataset into memory\n",
5352
"\n",
5453
"# Create an idealised wind field and add it to the dataset\n",
5554
"ydim, xdim = len(ds_fields.latitude), len(ds_fields.longitude)\n",
@@ -122,6 +121,9 @@
122121
"ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n",
123122
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
124123
"\n",
124+
"# Convert the FieldSet to windowed arrays for better performance\n",
125+
"fieldset = fieldset.to_windowed_arrays()\n",
126+
"\n",
125127
"npart = 10\n",
126128
"z = np.repeat(ds_fields.depth[0].values, npart)\n",
127129
"lons = np.repeat(32.2, npart)\n",

docs/user_guide/examples/tutorial_nemo.ipynb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,10 @@
307307
" fields={\"U\": ds_u[\"uo\"], \"V\": ds_v[\"vo\"], \"W\": ds_w[\"wo\"]},\n",
308308
" coords=ds_coords,\n",
309309
")\n",
310-
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)"
310+
"fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n",
311+
"\n",
312+
"fieldset = fieldset.to_windowed_arrays()\n",
313+
"fieldset.describe()"
311314
]
312315
},
313316
{
@@ -327,12 +330,12 @@
327330
},
328331
"outputs": [],
329332
"source": [
330-
"npart = 10\n",
333+
"npart = 3\n",
331334
"pset = parcels.ParticleSet(\n",
332335
" fieldset=fieldset,\n",
333336
" x=np.linspace(1.9, 3.4, npart),\n",
334-
" y=np.linspace(65, 51.6, npart),\n",
335-
" z=100 * np.ones(npart),\n",
337+
" y=np.linspace(60, 58, npart),\n",
338+
" z=20 * np.ones(npart),\n",
336339
")\n",
337340
"\n",
338341
"pfile = parcels.ParticleFile(\"output_nemo3D.parquet\", outputdt=np.timedelta64(1, \"D\"))\n",
@@ -358,16 +361,30 @@
358361
"metadata": {},
359362
"outputs": [],
360363
"source": [
361-
"field = fieldset.U.data[0, 0, :, :]\n",
364+
"fig, ax = plt.subplots(figsize=(8, 4))\n",
365+
"\n",
366+
"field = fieldset.U.data.isel(time=0, depth_center=0).compute()\n",
362367
"field = field.where(field != 0, np.nan) # Mask land values for better plotting\n",
363-
"plt.pcolormesh(fieldset.U.grid.lon, fieldset.U.grid.lat, field, cmap=\"RdBu\")\n",
368+
"ax.pcolormesh(fieldset.U.grid.lon, fieldset.U.grid.lat, field, cmap=\"RdBu\")\n",
364369
"\n",
365370
"df = parcels.read_particlefile(\"output_nemo3D.parquet\")\n",
366371
"vmin = df[\"z\"].min()\n",
367372
"vmax = df[\"z\"].max()\n",
368373
"for traj in df.partition_by(\"particle_id\", maintain_order=True):\n",
369-
" plt.scatter(traj[\"x\"], traj[\"y\"], c=-traj[\"z\"], marker=\".\", vmin=-vmax, vmax=-vmin)\n",
370-
"plt.colorbar(label=\"Depth (m)\")\n",
374+
" ax.plot(traj[\"x\"], traj[\"y\"], \"-\", color=\"k\", alpha=0.5)\n",
375+
" scat = ax.scatter(\n",
376+
" traj[\"x\"],\n",
377+
" traj[\"y\"],\n",
378+
" c=-traj[\"z\"],\n",
379+
" marker=\".\",\n",
380+
" vmin=-vmax,\n",
381+
" vmax=-vmin,\n",
382+
" zorder=10,\n",
383+
" )\n",
384+
"plt.colorbar(scat, label=\"Depth (m)\", ax=ax)\n",
385+
"ax.set_xlim([1, 9])\n",
386+
"ax.set_ylim([56, 61])\n",
387+
"ax.set_aspect(\"equal\")\n",
371388
"plt.show()"
372389
]
373390
}

0 commit comments

Comments
 (0)