Skip to content

Commit 14ed003

Browse files
Update tutorials in preparation for Parcels v4-preview release (#2704)
* Update tutorial_output.ipynb * Update tutorial_interpolation.ipynb Now that interpolators are functions (since #2646) * Update explanation_interpolation.md Adding more information on Interpolator signature * Update GridID generation in tutorial_nestedgrids * First batch of tutorial cleanups after #2719 * Second batch of tutorial cleanups after #2719 * Third batch of tutorial cleanups after #2719 * Fourth batch of tutorial cleanups after #2719 * Update tutorial_gsw_density * Fixing links in documentation * Fifth batch of tutorial cleanups after #2719 * Fix RTD * Trying to fix RTD error with logos * Not downloading unused remote files To check if this speeds up RTD * Fixing error in remote --------- Co-authored-by: Vecko <36369090+VeckoTheGecko@users.noreply.github.com>
1 parent f49e9b2 commit 14ed003

26 files changed

Lines changed: 359 additions & 387 deletions

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ build:
1414
build:
1515
html:
1616
- pixi run -e docs python ci/scripts/download-all-tutorial-datasets.py
17-
- SPHINXOPTS='-T' BUILDDIR=$READTHEDOCS_OUTPUT/html pixi run docs-clean
17+
- pixi run -e docs sphinx-build -T -b html docs $READTHEDOCS_OUTPUT/html
1818
sphinx:
1919
configuration: docs/conf.py

docs/conf.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@
123123
"**.ipynb_checkpoints",
124124
"user_guide/examples_v3",
125125
".jupyter_cache",
126-
"user_guide/examples/explanation_kernelloop.md", # TODO v4: https://github.com/Parcels-code/Parcels/issues/2695
127126
]
128127

129128
# The reST default role (used for this markup: `text`) to use for all
@@ -191,8 +190,8 @@
191190
html_theme_options = {
192191
"logo": {
193192
"alt_text": "Parcels - Home",
194-
"image_light": "logo-horo-transparent.png",
195-
"image_dark": "logo-horo-transparent-dark.png",
193+
"image_light": "_static/logo-horo-transparent.png",
194+
"image_dark": "_static/logo-horo-transparent-dark.png",
196195
},
197196
"use_edit_page_button": True,
198197
"github_url": "https://github.com/Parcels-code/parcels",

docs/getting_started/explanation_concepts.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ Here, we will explain the most important classes and functions. This overview ca
1313
A Parcels simulation is generally built up from four different components:
1414

1515
1. [**FieldSet**](#1-fieldset). The input dataset of gridded fields (e.g. ocean current velocity, temperature) in which virtual particles are defined.
16-
2. [**ParticleSet**](#2-particleset). The dataset of virtual particles. These always contain time, z, lat, and lon, for which initial values must be defined. The ParticleSet may also contain other, custom variables.
16+
2. [**ParticleSet**](#2-particleset). The dataset of virtual particles. These always contain time, z, y, and x, for which initial values must be defined. The ParticleSet may also contain other, custom variables.
1717
3. [**Kernels**](#3-kernels). Kernels perform some specific operation on the particles every time step (e.g. advect the particles with the three-dimensional flow; or interpolate the temperature field to the particle location).
1818
4. [**Execute**](#4-execute). Execute the simulation. The core method which integrates the operations defined in Kernels for a given runtime and timestep, and writes output to a ParticleFile.
1919

20-
We discuss each component in more detail below. The subsections titled **"Learn how to"** link to more detailed [how-to guide notebooks](../user_guide/index.md) and more detailed _explanations_ of Parcels functionality are included under **"Read more about"** subsections. The full list of classes and methods is in the [API reference](../reference/parcels/index.rst). If you want to learn by doing, check out the [quickstart tutorial](./tutorial_quickstart.md) to start creating your first Parcels simulation.
20+
We discuss each component in more detail below. The subsections titled **"Learn how to"** link to more detailed [how-to guide notebooks](../user_guide/index.md) and more detailed _explanations_ of Parcels functionality are included under **"Read more about"** subsections. The full list of classes and methods is in the [API reference](../reference/parcels/index). If you want to learn by doing, check out the [quickstart tutorial](./tutorial_quickstart.md) to start creating your first Parcels simulation.
2121

2222
```{figure} ../_static/concepts_diagram.png
2323
:alt: Parcels concepts diagram
@@ -82,16 +82,16 @@ Once the environment has a `parcels.FieldSet` object, you can start defining you
8282

8383
1. The `parcels.FieldSet` object in which the particles will be released.
8484
2. The type of `parcels.Particle`: A default `Particle` or a custom `Particle`-type with additional `Variable`s (see the [custom kernel example](custom-kernel)).
85-
3. Initial conditions for each `Variable` defined in the `Particle`, most notably the release coordinates of `time`, `z`, `lat` and `lon`.
85+
3. Initial conditions for each `Variable` defined in the `Particle`, most notably the release coordinates of `time`, `z`, `y` and `x`.
8686

8787
```python
8888
time = np.array([0])
8989
z = np.array([0])
90-
lat = np.array([0])
91-
lon = np.array([0])
90+
y = np.array([0])
91+
x = np.array([0])
9292

9393
# Create a ParticleSet
94-
pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, lat=lat, lon=lon)
94+
pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, y=y, x=x)
9595
```
9696

9797
```{admonition} 🖥️ Learn more about how to create ParticleSets
@@ -103,7 +103,7 @@ pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, time=time
103103

104104
A **`parcels.Kernel`** object is a little snippet of code, which is applied to the particles in the `ParticleSet`, for every time step during a simulation. Kernels define the computation or numerical integration done by Parcels, and can represent many processes such as advection, ageing, growth, or simply the sampling of a field.
105105

106-
Advection of a particle by the flow, the change in position $\mathbf{x}(t) = (lon(t), lat(t))$ at time $t$, can be described by the equation:
106+
Advection of a particle by the flow, the change in position $\mathbf{x}(t) = (x(t), y(t))$ at time $t$, can be described by the equation:
107107

108108
$$
109109
\begin{aligned}
@@ -113,20 +113,20 @@ $$
113113

114114
where $\mathbf{v}(\mathbf{x},t) = (u(\mathbf{x},t), v(\mathbf{x},t))$ describes the ocean velocity field at position $\mathbf{x}$ at time $t$.
115115

116-
In Parcels, we can write a kernel function which integrates this equation at each timestep `particles.dt`. To do so, we need the ocean velocity field `fieldset.UV` at the `particles` location, and compute the change in position, `particles.dlon` and `particles.dlat`.
116+
In Parcels, we can write a kernel function which integrates this equation at each timestep `particles.dt`. To do so, we need the ocean velocity field `fieldset.UV` at the `particles` location, and compute the change in position, `particles.dx` and `particles.dy`.
117117

118118
```python
119119
def AdvectionEE(particles, fieldset):
120120
"""Advection of particles using Explicit Euler (aka Euler Forward) integration."""
121121
(u1, v1) = fieldset.UV[particles]
122-
particles.dlon += u1 * particles.dt
123-
particles.dlat += v1 * particles.dt
122+
particles.dx += u1 * particles.dt
123+
particles.dy += v1 * particles.dt
124124
```
125125

126126
Basic kernels are included in Parcels to compute advection and diffusion. The standard advection kernel is `parcels.kernels.AdvectionRK2`, a [second-order Runge-Kutta integrator](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods#The_Runge%E2%80%93Kutta_method) of the advection function.
127127

128128
```{warning}
129-
It is advised _not_ to update the particle coordinates (`particles.time`, `particles.z`, `particles.lat`, or `particles.lon`) directly within a Kernel, as that can negatively interfere with the way that particle movements by different kernels are vectorially added. Use a change in the coordinates: `particles.dlon`, `particles.dlat` and/or `particles.dz`. Read the [kernel loop tutorial](https://docs.oceanparcels.org/en/latest/examples/tutorial_kernelloop.html) to understand why.
129+
It is advised _not_ to update the particle coordinates (`particles.time`, `particles.z`, `particles.y`, or `particles.x`) directly within a Kernel, as that can negatively interfere with the way that particle movements by different kernels are vectorially added. Use a change in the coordinates: `particles.dy`, `particles.dx` and/or `particles.dz`. Read the [kernel loop tutorial](../user_guide/examples/explanation_kernelloop.md) to understand why.
130130
```
131131

132132
(custom-kernel)=
@@ -186,7 +186,7 @@ pset.execute(kernels=kernels, dt=dt, runtime=runtime)
186186

187187
### Output
188188

189-
To analyse the particle data generated in the simulation, we need to define a `parcels.ParticleFile` and add it as an argument to `parcels.ParticleSet.execute()`. The output will be written in a [parquet format](https://parquet.apache.org/), which can be opened as a `polars.DataFrame`. The dataset will contain the particle data with at least `time`, `z`, `lat` and `lon`, for each particle at timesteps defined by the `outputdt` argument.
189+
To analyse the particle data generated in the simulation, we need to define a `parcels.ParticleFile` and add it as an argument to `parcels.ParticleSet.execute()`. The output will be written in a [parquet format](https://parquet.apache.org/), which can be opened as a `polars.DataFrame`. The dataset will contain the particle data with at least `time`, `z`, `y` and `x`, for each particle at timesteps defined by the `outputdt` argument.
190190

191191
There are many ways to analyze particle output, and although we provide [a short tutorial to get started](./tutorial_output.ipynb), we recommend writing your own analysis code and checking out [related Lagrangian analysis projects in our community page](../community/index.md#analysis-code).
192192

docs/getting_started/tutorial_output.ipynb

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
"\n",
1818
"- [**Reading the output file**](#reading-the-output-file)\n",
1919
"- [**Trajectory data structure**](#trajectory-data-structure)\n",
20-
"- [**Analysis**](#analysis)\n",
21-
"- [**Plotting**](#plotting)\n",
20+
"- [**Plotting**](#plotting-trajectories)\n",
2221
"- [**Animations**](#animations)\n",
2322
"\n",
2423
"For more advanced reading and tutorials on the analysis of Lagrangian trajectories, we recommend checking out the [Lagrangian Diagnostics Analysis Cookbook](https://lagrangian-diags.readthedocs.io/en/latest/tutorials.html) and the project in general."
@@ -77,11 +76,11 @@
7776
"npart = 10 # number of particles to be released\n",
7877
"lon = 32 * np.ones(npart)\n",
7978
"lat = np.linspace(-32.5, -30.5, npart, dtype=np.float32)\n",
80-
"time = ds_fields.time.values[0] + np.arange(0, npart) * np.timedelta64(2, \"h\")\n",
8179
"z = np.repeat(ds_fields.depth.values[0], npart)\n",
80+
"time = ds_fields.time.values[0] + np.arange(0, npart) * np.timedelta64(2, \"h\")\n",
8281
"\n",
8382
"pset = parcels.ParticleSet(\n",
84-
" fieldset=fieldset, pclass=parcels.Particle, lon=lon, lat=lat, time=time, z=z\n",
83+
" fieldset=fieldset, pclass=parcels.Particle, x=lon, y=lat, z=z, time=time\n",
8584
")\n",
8685
"\n",
8786
"output_file = parcels.ParticleFile(\"output.parquet\", outputdt=np.timedelta64(2, \"h\"))"
@@ -145,9 +144,9 @@
145144
"```{note}\n",
146145
"As of Parcels v4, the default output format is [`parquet`](https://parquet.apache.org/) (instead of `zarr`). The `parquet` output format is a tabular format, in which every row corresponds to an observation of a particle trajectory. The `zarr` output format is a multidimensional array format, in which the data is stored in a 2D array with dimensions `traj` and `obs`. The `parquet` format is more compact and faster to read.\n",
147146
"\n",
148-
"However, the `parquet` format does not support the [CF-convention for trajectories data](http://cfconventions.org/cf-conventions/v1.6.0/cf-conventions.html#_multidimensional_array_representation_of_trajectories) implemented with the [NCEI trajectory template](https://www.ncei.noaa.gov/data/oceans/ncei/formats/netcdf/v2.0/trajectoryIncomplete.cdl). We are working on efficient tooling to convert the parcels `parquet` output into a CF-compliant format.\n",
147+
"However, the `parquet` format does not support the [CF-convention for trajectories data](http://cfconventions.org/cf-conventions/v1.6.0/cf-conventions.html#_multidimensional_array_representation_of_trajectories) implemented with the [NCEI trajectory template](https://www.ncei.noaa.gov/data/oceans/ncei/formats/netcdf/v2.0/trajectoryIncomplete.cdl). We have implemented a `parcels.read_particlefile()` function to facilitate reading `parquet` output files, see more information below.\n",
149148
"\n",
150-
"TODO: Add link to tracking issue on github for this tooling.\n",
149+
"TODO: Add information on conversion functions once https://github.com/Parcels-code/Parcels/issues/2600 is resolved.\n",
151150
"```"
152151
]
153152
},
@@ -263,7 +262,7 @@
263262
"outputs": [],
264263
"source": [
265264
"fig, ax = plt.subplots(figsize=(5, 3))\n",
266-
"ax.plot(df_particles[\"lon\"], df_particles[\"lat\"], \".-\")\n",
265+
"ax.plot(df_particles[\"x\"], df_particles[\"y\"], \".-\")\n",
267266
"plt.show()"
268267
]
269268
},
@@ -283,7 +282,7 @@
283282
"fig, ax = plt.subplots(figsize=(5, 3))\n",
284283
"for traj in df_particles.partition_by(\"particle_id\", maintain_order=True):\n",
285284
" traj_id = traj[\"particle_id\"][0]\n",
286-
" ax.plot(traj[\"lon\"], traj[\"lat\"], \".-\", label=f\"P{traj_id}\")\n",
285+
" ax.plot(traj[\"x\"], traj[\"y\"], \".-\", label=f\"P{traj_id}\")\n",
287286
"ax.legend(loc=\"center left\", bbox_to_anchor=(1.02, 0.5), borderaxespad=0.0)\n",
288287
"plt.tight_layout()\n",
289288
"plt.show()"
@@ -307,7 +306,7 @@
307306
"particles = df_particles.filter(pl.col(\"time\") == pl.lit(time_to_plot))\n",
308307
"\n",
309308
"fig, ax = plt.subplots(figsize=(5, 3))\n",
310-
"ax.plot(particles[\"lon\"], particles[\"lat\"], \"o\")\n",
309+
"ax.plot(particles[\"x\"], particles[\"y\"], \"o\")\n",
311310
"title_time = pd.to_datetime(time_to_plot).strftime(\"%Y-%m-%d %H:%M:%S\")\n",
312311
"ax.set_title(f\"Particle locations at {title_time}\")\n",
313312
"plt.show()"
@@ -332,7 +331,7 @@
332331
")\n",
333332
"\n",
334333
"fig, ax = plt.subplots(figsize=(5, 3))\n",
335-
"ax.plot(particles[\"lon\"], particles[\"lat\"], \"o\")\n",
334+
"ax.plot(particles[\"x\"], particles[\"y\"], \"o\")\n",
336335
"ax.set_title(f\"Particle locations {time_step} after their release\")\n",
337336
"plt.show()"
338337
]
@@ -354,7 +353,7 @@
354353
"\n",
355354
"for traj in df_particles.partition_by(\"particle_id\", maintain_order=True):\n",
356355
" distance = np.sqrt(\n",
357-
" (traj[\"lon\"] - traj[\"lon\"][0]) ** 2 + (traj[\"lat\"] - traj[\"lat\"][0]) ** 2\n",
356+
" (traj[\"x\"] - traj[\"x\"][0]) ** 2 + (traj[\"y\"] - traj[\"y\"][0]) ** 2\n",
358357
" )\n",
359358
" ax[0].plot(traj[\"time\"], distance, \".-\", label=f\"P{traj['particle_id'][0]}\")\n",
360359
"\n",
@@ -439,8 +438,8 @@
439438
"# --> plot first timestep\n",
440439
"particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n",
441440
"scatter = ax.scatter(\n",
442-
" particles[\"lon\"],\n",
443-
" particles[\"lat\"],\n",
441+
" particles[\"x\"],\n",
442+
" particles[\"y\"],\n",
444443
" s=10,\n",
445444
" c=[trajectory_to_color[p] for p in particles[\"particle_id\"]],\n",
446445
")\n",
@@ -464,7 +463,7 @@
464463
" particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n",
465464
"\n",
466465
" if len(particles) > 0:\n",
467-
" scatter.set_offsets(np.c_[particles[\"lon\"], particles[\"lat\"]])\n",
466+
" scatter.set_offsets(np.c_[particles[\"x\"], particles[\"y\"]])\n",
468467
" scatter.set_color([trajectory_to_color[p] for p in particles[\"particle_id\"]])\n",
469468
"\n",
470469
" # --> reset trails\n",
@@ -481,8 +480,8 @@
481480
" )\n",
482481
" if len(traj_trail) > 1:\n",
483482
" (trail,) = ax.plot(\n",
484-
" traj_trail[\"lon\"],\n",
485-
" traj_trail[\"lat\"],\n",
483+
" traj_trail[\"x\"],\n",
484+
" traj_trail[\"y\"],\n",
486485
" color=trajectory_to_color[traj],\n",
487486
" linewidth=0.6,\n",
488487
" alpha=0.6,\n",
@@ -502,7 +501,7 @@
502501
"metadata": {
503502
"celltoolbar": "Metagegevens bewerken",
504503
"kernelspec": {
505-
"display_name": "docs",
504+
"display_name": "Parcels:docs (3.14.6)",
506505
"language": "python",
507506
"name": "python3"
508507
},
@@ -516,7 +515,7 @@
516515
"name": "python",
517516
"nbconvert_exporter": "python",
518517
"pygments_lexer": "ipython3",
519-
"version": "3.14.4"
518+
"version": "3.14.6"
520519
}
521520
},
522521
"nbformat": 4,

docs/getting_started/tutorial_quickstart.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ the virtual particles for which we will calculate the trajectories.
7373

7474
We need to create a {py:obj}`parcels.ParticleSet` object with the particles' initial time and position. The `parcels.ParticleSet`
7575
object also needs to know about the `FieldSet` in which the particles "live". Finally, we need to specify the type of
76-
{py:obj}`parcels.ParticleClass` we want to use. The default particles have `time`, `z`, `lat`, and `lon`, but you can easily add
76+
{py:obj}`parcels.ParticleClass` we want to use. The default particles have `time`, `z`, `y`, and `x`, but you can easily add
7777
other {py:obj}`parcels.Variable`s such as size, temperature, or age to create your own particles to mimic plastic or an [ARGO float](../user_guide/examples/tutorial_Argofloats.ipynb).
7878

7979
```{code-cell}
@@ -86,7 +86,7 @@ time = np.repeat(ds_fields.time.values[0], npart) # at initial time of input dat
8686
z = np.repeat(ds_fields.depth.values[0], npart) # at the first depth (surface)
8787
8888
pset = parcels.ParticleSet(
89-
fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, lat=lat, lon=lon
89+
fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, y=lat, x=lon
9090
)
9191
```
9292

@@ -103,7 +103,7 @@ And you can plot the particles on top of the temperature and velocity field:
103103
temperature = ds_fields.isel(time=0, depth=0).thetao.plot(cmap="magma")
104104
velocity = ds_fields.isel(time=0, depth=0).plot.quiver(x="longitude", y="latitude", u="uo", v="vo")
105105
ax = temperature.axes
106-
ax.scatter(lon,lat,s=40,c='w',edgecolors='r');
106+
ax.scatter(lon, lat, s=40, c='w', edgecolors='r');
107107
```
108108

109109
## Compute: `Kernel`
@@ -168,8 +168,8 @@ Let's verify that Parcels has computed the advection of the virtual particles!
168168
import matplotlib.pyplot as plt
169169
170170
# plot positions and color particles by time
171-
scatter = plt.scatter(df['lon'], df['lat'], c=df['time'])
172-
plt.scatter(df['lon'][:npart], df['lat'][:npart], facecolors="none", edgecolors='r') # starting positions
171+
scatter = plt.scatter(df['x'], df['y'], c=df['time'])
172+
plt.scatter(df['x'][:npart], df['y'][:npart], facecolors="none", edgecolors='r') # starting positions
173173
plt.scatter(lon, lat, facecolors="none", edgecolors='r') # starting positions
174174
plt.xlim(31,33)
175175
plt.ylabel("Latitude [deg N]")
@@ -211,9 +211,9 @@ When we check the output, we can see that the particles have returned to their o
211211
```{code-cell}
212212
df_back = parcels.read_particlefile("output-backwards.parquet")
213213
214-
scatter = plt.scatter(df_back['lon'], df_back['lat'], c=df_back['time'])
214+
scatter = plt.scatter(df_back['x'], df_back['y'], c=df_back['time'])
215215
particles_at_max_time = df_back.filter(pl.col("time") == df_back["time"].max())
216-
plt.scatter(particles_at_max_time['lon'], particles_at_max_time['lat'], facecolors="none", edgecolors='r') # starting positions
216+
plt.scatter(particles_at_max_time['x'], particles_at_max_time['y'], facecolors="none", edgecolors='r') # starting positions
217217
plt.xlabel("Longitude [deg E]")
218218
plt.xlim(31,33)
219219
plt.ylabel("Latitude [deg N]")
@@ -227,6 +227,6 @@ Using Euler forward advection, the final positions are equal to the original pos
227227
```{code-cell}
228228
# testing that final location == original location
229229
particles_at_min_time = df_back.filter(pl.col("time") == df_back["time"].min())
230-
np.testing.assert_almost_equal(particles_at_min_time["lat"], lat, 2)
231-
np.testing.assert_almost_equal(particles_at_min_time['lon'], lon, 2)
230+
np.testing.assert_almost_equal(particles_at_min_time["y"], lat, 2)
231+
np.testing.assert_almost_equal(particles_at_min_time['x'], lon, 2)
232232
```

0 commit comments

Comments
 (0)