From 0943d2ecb67f0f685461d8510d792cee7c62a0aa Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 25 Jun 2026 09:00:04 +0200 Subject: [PATCH 01/15] Update tutorial_output.ipynb --- docs/getting_started/tutorial_output.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting_started/tutorial_output.ipynb b/docs/getting_started/tutorial_output.ipynb index a5e70a42e..f468e6edf 100644 --- a/docs/getting_started/tutorial_output.ipynb +++ b/docs/getting_started/tutorial_output.ipynb @@ -145,9 +145,9 @@ "```{note}\n", "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", "\n", - "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", + "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", "\n", - "TODO: Add link to tracking issue on github for this tooling.\n", + "TODO: Add information on conversion functions once https://github.com/Parcels-code/Parcels/issues/2600 is resolved.\n", "```" ] }, @@ -502,7 +502,7 @@ "metadata": { "celltoolbar": "Metagegevens bewerken", "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, From c316e9535ea542bea31ee0a62bcff2ecf8d43f4b Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 25 Jun 2026 09:02:16 +0200 Subject: [PATCH 02/15] Update tutorial_interpolation.ipynb Now that interpolators are functions (since #2646) --- .../examples/tutorial_interpolation.ipynb | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/docs/user_guide/examples/tutorial_interpolation.ipynb b/docs/user_guide/examples/tutorial_interpolation.ipynb index 612903234..982fa640c 100644 --- a/docs/user_guide/examples/tutorial_interpolation.ipynb +++ b/docs/user_guide/examples/tutorial_interpolation.ipynb @@ -5,8 +5,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 🖥️ Using `parcels.interpolators`\n", - "Parcels comes with a number of different interpolation methods for fields on structured (`X`) and unstructured (`Ux`) grids. Here, we will look at a few common {py:obj}`parcels.interpolators` for tracer fiedls, and how to configure them in an idealised example. For more guidance on the sampling of such fields, check out the [sampling tutorial](./tutorial_sampling)." + "# 🖥️ Using the built-in `parcels.interpolators`\n", + "Parcels comes with a number of different interpolation methods for fields on structured (`X`) and unstructured (`Ux`) grids. Here, we will look at a few common {py:obj}`parcels.interpolators` for tracer fields, and how to configure them in an idealised example. For more guidance on the sampling of such fields, check out the [sampling tutorial](./tutorial_sampling)." ] }, { @@ -72,7 +72,7 @@ "source": [ "fieldset = parcels.FieldSet.from_sgrid_conventions(ds, mesh=\"flat\")\n", "\n", - "assert fieldset.P.interp_method == parcels.interpolators.XLinear" + "assert isinstance(fieldset.P.interp_method, parcels.interpolators.XLinear)" ] }, { @@ -124,18 +124,18 @@ " XNearest,\n", ")\n", "\n", - "interp_methods = [XLinear, XLinearInvdistLandTracer, XNearest, CGrid_Tracer]\n", + "interp_methods = [XLinear(), XLinearInvdistLandTracer(), XNearest(), CGrid_Tracer()]\n", "for p_interp in interp_methods:\n", " fieldset.P.interp_method = (\n", " p_interp # setting the interpolation method for fieldset.P\n", " )\n", "\n", - " print(fieldset.P.interp_method.__name__)\n", + " print(fieldset.P.interp_method)\n", " xv, yv = np.meshgrid(np.linspace(0, 1, 8), np.linspace(0, 1, 8))\n", - " pset[p_interp.__name__] = parcels.ParticleSet(\n", + " pset[p_interp.__class__.__name__] = parcels.ParticleSet(\n", " fieldset, pclass=SampleParticle, lon=xv.flatten(), lat=yv.flatten()\n", " )\n", - " pset[p_interp.__name__].execute(\n", + " pset[p_interp.__class__.__name__].execute(\n", " SampleP,\n", " runtime=np.timedelta64(1, \"D\"),\n", " dt=np.timedelta64(1, \"D\"),\n", @@ -188,10 +188,6 @@ "\n", "For `interp_method='XNearest'`, the particle values are the same for all particles in a grid cell. They are also the same for `interp_method='CGrid_Tracer'`, but the grid cells have then shifted. That is because in a C-grid, the tracer is defined on the corners of the velocity grid (black dashed lines in right-most panel).\n", "\n", - "```{note}\n", - "TODO: check C-grid indexing after implementation in v4\n", - "```\n", - "\n", "For `interp_method='XLinearInvdistLandTracer'`, we see that values are the same as `interp_method='XLinear'` for grid cells that don't border the land point. For grid cells that do border the land cell, the `XLinearInvdistLandTracer` interpolation method gives higher values, as also shown in the difference plot below.\n" ] }, @@ -462,7 +458,8 @@ "ax[1].set_aspect(\"equal\", \"box\")\n", "ax[1].set_xlabel(\"x\")\n", "ax[1].set_ylabel(\"y\")\n", - "ax[1].set_title(\"Face Registered Data\")" + "ax[1].set_title(\"Face Registered Data\")\n", + "plt.show()" ] }, { @@ -483,7 +480,7 @@ ], "metadata": { "kernelspec": { - "display_name": "test-notebooks", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -497,7 +494,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.9" + "version": "3.14.6" } }, "nbformat": 4, From 58bedac5b684d6c9394ff06441434b50de3edd97 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Thu, 25 Jun 2026 09:27:02 +0200 Subject: [PATCH 03/15] Update explanation_interpolation.md Adding more information on Interpolator signature --- .../examples/explanation_interpolation.md | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/user_guide/examples/explanation_interpolation.md b/docs/user_guide/examples/explanation_interpolation.md index 94b70c4b3..a154652c8 100644 --- a/docs/user_guide/examples/explanation_interpolation.md +++ b/docs/user_guide/examples/explanation_interpolation.md @@ -3,10 +3,6 @@ Interpolation is an important functionality of Parcels. On this page we will discuss the way it is implemented in **Parcels** and how to write a custom interpolator function. -```{note} -TODO: expand explanation (similar to Kernel loop explanation) -``` - When we want to know the state of particles in an environmental field, such as temperature or velocity, we _evaluate_ the `parcels.Field` at the particles real position in time and space (`t`, `z`, `lat`, `lon`). In Parcels we can do this using square brackets: @@ -26,7 +22,7 @@ If you want to sample at a different location, or time, that is not necessarily ```python particles.temperature = fieldset.temperature[time, depth, lat, lon] ``` -but this could be slower for curvilinear and unstructured because the entire grid needs to be searched. +but this could be slower for curvilinear and unstructured Grids because the entire grid needs to be searched. ```` The values of the `temperature` field at the particles' positions are determined using an interpolation @@ -36,15 +32,23 @@ relate to the value at any point within a grid cell. Each `parcels.Field` is defined on a (structured) `parcels.XGrid` or (unstructured) `parcels.UXGrid`. The interpolation function takes information about the particles position relative to this grid (`grid_positions`), as well as the values of the grid points of the `parcels.Field` in time and space, to calculate -the requested value at the particles location. Note that all grid values are available so that higher-order interpolation is possible. +the requested value at the particles location. ## Interpolator API -The interpolators included in Parcels are designed for common interpolation schemes in Parcels simulations. -If we want to add a custom interpolation method, we need to look at the interpolator API: +The interpolators included in Parcels are designed for common interpolation schemes in Parcels simulations; see the [Using the built-in interpolators tutorial](./tutorial_interpolation.ipynb). -We can write an interpolator function that takes a `parcels.Field` (or `parcels.VectorField`), a dictionary with the `particle_positions` -in real space and time, and a dictionary with the `grid_positions`. +If we want to create a custom interpolation method, we need to look at the interpolator API. Each interpolator is a class that inherits from either the `ScalarInterpolator` or `VectorInterpolator` class. The `ScalarInterpolator` class is used for scalar fields, such as temperature or salinity, while the `VectorInterpolator` class is used for vector fields, such as velocity. An interpolator class than has to have a `.interp()` method with the following signature: + +```python + def interp( + self, + particle_positions: dict[str, float | np.ndarray], + grid_positions: dict[ptyping.XgridAxis, dict[str, int | float | np.ndarray]], + field: Field, + ): + ... +``` The `particle_positions` dictionary contains: @@ -74,3 +78,7 @@ grid_positions = { "FACE": {"index": fi, "bcoord": bcoord} } ``` + +The `.interp()` method should return a float (in the case o a `ScalarInterpolator` or a tuple of three floats `(u, v, w)` in the case of a `VectorInterpolator`). + +Writing custom interpolators is not trivial, so we recommend that you have a look at the built-in interpolators in `parcels.interpolators._xinterpolators` or `parcels.interpolators._uxinterpolators` to see how they are implemented. From fb7afd21ddb315d3368b1e5d1af10c10e3d80420 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Fri, 26 Jun 2026 13:57:14 +0200 Subject: [PATCH 04/15] Update GridID generation in tutorial_nestedgrids --- .../examples/tutorial_nestedgrids.ipynb | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/docs/user_guide/examples/tutorial_nestedgrids.ipynb b/docs/user_guide/examples/tutorial_nestedgrids.ipynb index 48dae7ba8..85326b317 100644 --- a/docs/user_guide/examples/tutorial_nestedgrids.ipynb +++ b/docs/user_guide/examples/tutorial_nestedgrids.ipynb @@ -309,10 +309,10 @@ " \"node_lon\": ((\"n_node\",), points[:, 0]),\n", " \"node_lat\": ((\"n_node\",), points[:, 1]),\n", " \"face_node_connectivity\": ((\"n_face\", \"n_max_face_nodes\"), face_tris),\n", - " \"face_polygon\": (\n", + " \"GridID\": (\n", " (\n", " \"time\",\n", - " \"zf\",\n", + " \"zc\",\n", " \"n_face\",\n", " ),\n", " face_poly[np.newaxis, np.newaxis, :],\n", @@ -325,23 +325,16 @@ " },\n", " coords={\n", " \"time\": np.array([np.timedelta64(0, \"ns\")]),\n", - " \"zf\": np.array([0]),\n", + " \"zf\": np.array([0, 1]),\n", + " \"zc\": np.array([0.5]),\n", " \"n_node\": np.arange(n_node),\n", " \"n_face\": np.arange(n_face),\n", " },\n", " attrs={\"Conventions\": \"UGRID-1.0\"},\n", ")\n", - "\n", - "uxda = ux.UxDataArray(ds_tri[\"face_polygon\"], uxgrid=ux.Grid(ds_tri))\n", - "\n", - "GridID = parcels.Field(\n", - " \"GridID\",\n", - " uxda,\n", - " # TODO note that here we need to use mesh=\"flat\" otherwise the hashing doesn't work. See https://github.com/Parcels-code/Parcels/pull/2439#discussion_r2627664010\n", - " parcels.UxGrid(uxda.uxgrid, z=uxda[\"zf\"], mesh=\"flat\"),\n", - " interp_method=parcels.interpolators.UxConstantFaceConstantZC,\n", - ")\n", - "fieldset = parcels.FieldSet([GridID])" + "uxda = ux.UxDataArray(ds_tri[\"GridID\"], uxgrid=ux.Grid(ds_tri)).to_dataset()\n", + "uxda = uxda.assign_coords({\"zf\": ds_tri.coords[\"zf\"]})\n", + "fieldset = parcels.FieldSet.from_ugrid_conventions(uxda, mesh=\"flat\")" ] }, { @@ -407,7 +400,7 @@ " fieldset.GridID.grid.uxgrid.node_lat.values,\n", " triangles=fieldset.GridID.grid.uxgrid.face_node_connectivity.values,\n", ")\n", - "facecolors = np.squeeze(uxda[0, :].values)\n", + "facecolors = np.squeeze(uxda.GridID[0, :].values)\n", "\n", "ax.tripcolor(triang, facecolors=facecolors, shading=\"flat\", **plot_args)\n", "ax.scatter(pset.lon, pset.lat, c=pset.gridID, **plot_args)\n", @@ -438,14 +431,15 @@ "metadata": {}, "outputs": [], "source": [ - "fields = [GridID]\n", "for i, ds in enumerate(ds_in):\n", " fset = parcels.FieldSet.from_sgrid_conventions(ds, mesh=\"spherical\")\n", "\n", - " for fld in fset.fields.values():\n", - " fld.name = f\"{fld.name}{i}\"\n", - " fields.append(fld)\n", - "fieldset = parcels.FieldSet(fields)" + " # Rename and add fields to the main fieldset\n", + " # TODO This needs a beeter API!\n", + " for old_name, fld in list(fset.fields.items()):\n", + " new_name = f\"{old_name}{i}\"\n", + " fld.name = new_name\n", + " fieldset._fields[new_name] = fld" ] }, { @@ -471,7 +465,7 @@ " unique_ids = np.unique(particles.gridID)\n", " for gid in unique_ids:\n", " mask = particles.gridID == gid\n", - " UVField = getattr(fieldset, f\"UV{gid}\")\n", + " UVField = fieldset._fields[f\"UV{gid}\"]\n", " (u[mask], v[mask]) = UVField[particles[mask]]\n", "\n", " particles.dlon += u * particles.dt\n", @@ -550,7 +544,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -564,7 +558,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, From 4cfb9c9196b3d671c9ca7193b8638cdca9057d84 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 6 Jul 2026 11:54:01 +0200 Subject: [PATCH 05/15] First batch of tutorial cleanups after #2719 --- docs/getting_started/explanation_concepts.md | 22 +++--- docs/getting_started/tutorial_output.ipynb | 26 +++---- docs/getting_started/tutorial_quickstart.md | 16 ++-- .../examples/tutorial_Argofloats.ipynb | 14 ++-- .../examples/tutorial_croco_3D.ipynb | 39 +++++----- .../examples/tutorial_diffusion.ipynb | 76 +++++++++---------- .../examples/tutorial_dt_integrators.ipynb | 48 ++++++------ docs/user_guide/examples/tutorial_fesom.ipynb | 17 +++-- .../examples/tutorial_interpolation.ipynb | 2 +- .../user_guide/examples/tutorial_schism.ipynb | 7 +- 10 files changed, 128 insertions(+), 139 deletions(-) diff --git a/docs/getting_started/explanation_concepts.md b/docs/getting_started/explanation_concepts.md index 0283299ff..748e24afc 100644 --- a/docs/getting_started/explanation_concepts.md +++ b/docs/getting_started/explanation_concepts.md @@ -13,7 +13,7 @@ Here, we will explain the most important classes and functions. This overview ca A Parcels simulation is generally built up from four different components: 1. [**FieldSet**](#1-fieldset). The input dataset of gridded fields (e.g. ocean current velocity, temperature) in which virtual particles are defined. -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. +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. 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). 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. @@ -82,16 +82,16 @@ Once the environment has a `parcels.FieldSet` object, you can start defining you 1. The `parcels.FieldSet` object in which the particles will be released. 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)). -3. Initial conditions for each `Variable` defined in the `Particle`, most notably the release coordinates of `time`, `z`, `lat` and `lon`. +3. Initial conditions for each `Variable` defined in the `Particle`, most notably the release coordinates of `time`, `z`, `y` and `x`. ```python time = np.array([0]) z = np.array([0]) -lat = np.array([0]) -lon = np.array([0]) +y = np.array([0]) +x = np.array([0]) # Create a ParticleSet -pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, lat=lat, lon=lon) +pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, y=y, x=x) ``` ```{admonition} 🖥️ Learn more about how to create ParticleSets @@ -103,7 +103,7 @@ pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, time=time 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. -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: +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: $$ \begin{aligned} @@ -113,20 +113,20 @@ $$ 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$. -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`. +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`. ```python def AdvectionEE(particles, fieldset): """Advection of particles using Explicit Euler (aka Euler Forward) integration.""" (u1, v1) = fieldset.UV[particles] - particles.dlon += u1 * particles.dt - particles.dlat += v1 * particles.dt + particles.dx += u1 * particles.dt + particles.dy += v1 * particles.dt ``` 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. ```{warning} -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. +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](https://docs.oceanparcels.org/en/latest/examples/tutorial_kernelloop.html) to understand why. ``` (custom-kernel)= @@ -186,7 +186,7 @@ pset.execute(kernels=kernels, dt=dt, runtime=runtime) ### Output -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. +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. 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). diff --git a/docs/getting_started/tutorial_output.ipynb b/docs/getting_started/tutorial_output.ipynb index f468e6edf..26627ff56 100644 --- a/docs/getting_started/tutorial_output.ipynb +++ b/docs/getting_started/tutorial_output.ipynb @@ -77,11 +77,11 @@ "npart = 10 # number of particles to be released\n", "lon = 32 * np.ones(npart)\n", "lat = np.linspace(-32.5, -30.5, npart, dtype=np.float32)\n", - "time = ds_fields.time.values[0] + np.arange(0, npart) * np.timedelta64(2, \"h\")\n", "z = np.repeat(ds_fields.depth.values[0], npart)\n", + "time = ds_fields.time.values[0] + np.arange(0, npart) * np.timedelta64(2, \"h\")\n", "\n", "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=parcels.Particle, lon=lon, lat=lat, time=time, z=z\n", + " fieldset=fieldset, pclass=parcels.Particle, x=lon, y=lat, z=z, time=time\n", ")\n", "\n", "output_file = parcels.ParticleFile(\"output.parquet\", outputdt=np.timedelta64(2, \"h\"))" @@ -263,7 +263,7 @@ "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=(5, 3))\n", - "ax.plot(df_particles[\"lon\"], df_particles[\"lat\"], \".-\")\n", + "ax.plot(df_particles[\"x\"], df_particles[\"y\"], \".-\")\n", "plt.show()" ] }, @@ -283,7 +283,7 @@ "fig, ax = plt.subplots(figsize=(5, 3))\n", "for traj in df_particles.partition_by(\"particle_id\", maintain_order=True):\n", " traj_id = traj[\"particle_id\"][0]\n", - " ax.plot(traj[\"lon\"], traj[\"lat\"], \".-\", label=f\"P{traj_id}\")\n", + " ax.plot(traj[\"x\"], traj[\"y\"], \".-\", label=f\"P{traj_id}\")\n", "ax.legend(loc=\"center left\", bbox_to_anchor=(1.02, 0.5), borderaxespad=0.0)\n", "plt.tight_layout()\n", "plt.show()" @@ -307,7 +307,7 @@ "particles = df_particles.filter(pl.col(\"time\") == pl.lit(time_to_plot))\n", "\n", "fig, ax = plt.subplots(figsize=(5, 3))\n", - "ax.plot(particles[\"lon\"], particles[\"lat\"], \"o\")\n", + "ax.plot(particles[\"x\"], particles[\"y\"], \"o\")\n", "title_time = pd.to_datetime(time_to_plot).strftime(\"%Y-%m-%d %H:%M:%S\")\n", "ax.set_title(f\"Particle locations at {title_time}\")\n", "plt.show()" @@ -332,7 +332,7 @@ ")\n", "\n", "fig, ax = plt.subplots(figsize=(5, 3))\n", - "ax.plot(particles[\"lon\"], particles[\"lat\"], \"o\")\n", + "ax.plot(particles[\"x\"], particles[\"y\"], \"o\")\n", "ax.set_title(f\"Particle locations {time_step} after their release\")\n", "plt.show()" ] @@ -354,7 +354,7 @@ "\n", "for traj in df_particles.partition_by(\"particle_id\", maintain_order=True):\n", " distance = np.sqrt(\n", - " (traj[\"lon\"] - traj[\"lon\"][0]) ** 2 + (traj[\"lat\"] - traj[\"lat\"][0]) ** 2\n", + " (traj[\"x\"] - traj[\"x\"][0]) ** 2 + (traj[\"y\"] - traj[\"y\"][0]) ** 2\n", " )\n", " ax[0].plot(traj[\"time\"], distance, \".-\", label=f\"P{traj['particle_id'][0]}\")\n", "\n", @@ -439,8 +439,8 @@ "# --> plot first timestep\n", "particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n", "scatter = ax.scatter(\n", - " particles[\"lon\"],\n", - " particles[\"lat\"],\n", + " particles[\"x\"],\n", + " particles[\"y\"],\n", " s=10,\n", " c=[trajectory_to_color[p] for p in particles[\"particle_id\"]],\n", ")\n", @@ -464,7 +464,7 @@ " particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n", "\n", " if len(particles) > 0:\n", - " scatter.set_offsets(np.c_[particles[\"lon\"], particles[\"lat\"]])\n", + " scatter.set_offsets(np.c_[particles[\"x\"], particles[\"y\"]])\n", " scatter.set_color([trajectory_to_color[p] for p in particles[\"particle_id\"]])\n", "\n", " # --> reset trails\n", @@ -481,8 +481,8 @@ " )\n", " if len(traj_trail) > 1:\n", " (trail,) = ax.plot(\n", - " traj_trail[\"lon\"],\n", - " traj_trail[\"lat\"],\n", + " traj_trail[\"x\"],\n", + " traj_trail[\"y\"],\n", " color=trajectory_to_color[traj],\n", " linewidth=0.6,\n", " alpha=0.6,\n", @@ -516,7 +516,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/getting_started/tutorial_quickstart.md b/docs/getting_started/tutorial_quickstart.md index 12d33f1cf..d68d2cee6 100644 --- a/docs/getting_started/tutorial_quickstart.md +++ b/docs/getting_started/tutorial_quickstart.md @@ -73,7 +73,7 @@ the virtual particles for which we will calculate the trajectories. We need to create a {py:obj}`parcels.ParticleSet` object with the particles' initial time and position. The `parcels.ParticleSet` object also needs to know about the `FieldSet` in which the particles "live". Finally, we need to specify the type of -{py:obj}`parcels.ParticleClass` we want to use. The default particles have `time`, `z`, `lat`, and `lon`, but you can easily add +{py:obj}`parcels.ParticleClass` we want to use. The default particles have `time`, `z`, `y`, and `x`, but you can easily add 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). ```{code-cell} @@ -86,7 +86,7 @@ time = np.repeat(ds_fields.time.values[0], npart) # at initial time of input dat z = np.repeat(ds_fields.depth.values[0], npart) # at the first depth (surface) pset = parcels.ParticleSet( - fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, lat=lat, lon=lon + fieldset=fieldset, pclass=parcels.Particle, time=time, z=z, y=lat, x=lon ) ``` @@ -168,8 +168,8 @@ Let's verify that Parcels has computed the advection of the virtual particles! import matplotlib.pyplot as plt # plot positions and color particles by time -scatter = plt.scatter(df['lon'], df['lat'], c=df['time']) -plt.scatter(df['lon'][:npart], df['lat'][:npart], facecolors="none", edgecolors='r') # starting positions +scatter = plt.scatter(df['x'], df['y'], c=df['time']) +plt.scatter(df['x'][:npart], df['y'][:npart], facecolors="none", edgecolors='r') # starting positions plt.scatter(lon, lat, facecolors="none", edgecolors='r') # starting positions plt.xlim(31,33) 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 ```{code-cell} df_back = parcels.read_particlefile("output-backwards.parquet") -scatter = plt.scatter(df_back['lon'], df_back['lat'], c=df_back['time']) +scatter = plt.scatter(df_back['x'], df_back['y'], c=df_back['time']) particles_at_max_time = df_back.filter(pl.col("time") == df_back["time"].max()) -plt.scatter(particles_at_max_time['lon'], particles_at_max_time['lat'], facecolors="none", edgecolors='r') # starting positions +plt.scatter(particles_at_max_time['x'], particles_at_max_time['y'], facecolors="none", edgecolors='r') # starting positions plt.xlabel("Longitude [deg E]") plt.xlim(31,33) plt.ylabel("Latitude [deg N]") @@ -227,6 +227,6 @@ Using Euler forward advection, the final positions are equal to the original pos ```{code-cell} # testing that final location == original location particles_at_min_time = df_back.filter(pl.col("time") == df_back["time"].min()) -np.testing.assert_almost_equal(particles_at_min_time["lat"], lat, 2) -np.testing.assert_almost_equal(particles_at_min_time['lon'], lon, 2) +np.testing.assert_almost_equal(particles_at_min_time["y"], lat, 2) +np.testing.assert_almost_equal(particles_at_min_time['x'], lon, 2) ``` diff --git a/docs/user_guide/examples/tutorial_Argofloats.ipynb b/docs/user_guide/examples/tutorial_Argofloats.ipynb index 6321d9a87..bd444cdfa 100644 --- a/docs/user_guide/examples/tutorial_Argofloats.ipynb +++ b/docs/user_guide/examples/tutorial_Argofloats.ipynb @@ -63,7 +63,7 @@ "\n", " # Phase 3: Rising with vertical_speed until at surface\n", " ptcls3.dz -= vertical_speed * ptcls3.dt\n", - " ptcls3.temp = fieldset.thetao[ptcls3.time, ptcls3.z, ptcls3.lat, ptcls3.lon]\n", + " ptcls3.temp = fieldset.thetao[ptcls3.time, ptcls3.z, ptcls3.y, ptcls3.x]\n", " next_phase = ptcls3.z + ptcls3.dz <= mindepth\n", " ptcls3.cycle_phase[next_phase] = 4\n", " ptcls3.dz[next_phase] = mindepth - ptcls3.z[next_phase] # avoid overshoot\n", @@ -135,8 +135,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", " pclass=ArgoParticle,\n", - " lon=[32],\n", - " lat=[-31],\n", + " x=[32],\n", + " y=[-31],\n", " z=[mindepth],\n", ")\n", "\n", @@ -210,8 +210,8 @@ "fig = plt.figure(figsize=(13, 8))\n", "ax = plt.axes(projection=\"3d\")\n", "ax.view_init(azim=-145)\n", - "ax.plot3D(df[\"lon\"], df[\"lat\"], df[\"z\"], color=\"gray\")\n", - "cb = ax.scatter(df[\"lon\"], df[\"lat\"], df[\"z\"], c=df[\"temp\"], s=20, marker=\"o\", zorder=2)\n", + "ax.plot3D(df[\"x\"], df[\"y\"], df[\"z\"], color=\"gray\")\n", + "cb = ax.scatter(df[\"x\"], df[\"y\"], df[\"z\"], c=df[\"temp\"], s=20, marker=\"o\", zorder=2)\n", "ax.set_xlabel(\"Longitude\")\n", "ax.set_ylabel(\"Latitude\")\n", "ax.set_zlabel(\"Depth (m)\")\n", @@ -223,7 +223,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Parcels:test (3.14.4)", + "display_name": "Parcels:test (3.14.6)", "language": "python", "name": "python3" }, @@ -237,7 +237,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_croco_3D.ipynb b/docs/user_guide/examples/tutorial_croco_3D.ipynb index 40c8501b2..6f9b88935 100644 --- a/docs/user_guide/examples/tutorial_croco_3D.ipynb +++ b/docs/user_guide/examples/tutorial_croco_3D.ipynb @@ -124,9 +124,7 @@ " particles[any_error].state = parcels.StatusCode.Delete\n", "\n", "\n", - "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=parcels.Particle, lon=X, lat=Y, z=Z\n", - ")\n", + "pset = parcels.ParticleSet(fieldset=fieldset, pclass=parcels.Particle, x=X, y=Y, z=Z)\n", "\n", "outputfile = parcels.ParticleFile(\n", " path=\"croco_particles3D.parquet\",\n", @@ -163,7 +161,7 @@ "\n", "ax.plot(X / 1e3, Z, \"k.\", label=\"Initial positions\")\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " ax.plot(traj[\"lon\"] / 1e3, traj[\"z\"], \".-\")\n", + " ax.plot(traj[\"x\"] / 1e3, traj[\"z\"], \".-\")\n", "\n", "for z in ds_fields.s_w.values:\n", " ax.plot(\n", @@ -215,7 +213,7 @@ "Y = np.ones(X.size) * 100\n", "\n", "pset_noW = parcels.ParticleSet(\n", - " fieldset=fieldset_noW, pclass=parcels.Particle, lon=X, lat=Y, z=Z\n", + " fieldset=fieldset_noW, pclass=parcels.Particle, x=X, y=Y, z=Z\n", ")\n", "\n", "outputfile = parcels.ParticleFile(\n", @@ -234,7 +232,7 @@ "\n", "ax.plot(X / 1e3, Z, \"k.\", label=\"Initial positions\")\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " ax.plot(traj[\"lon\"] / 1e3, traj[\"z\"], \".-\")\n", + " ax.plot(traj[\"x\"] / 1e3, traj[\"z\"], \".-\")\n", "\n", "for z in ds_fields.s_w.values:\n", " ax.plot(\n", @@ -267,44 +265,43 @@ "```python\n", "def SampleTempCroco(particles, fieldset):\n", " \"\"\"Sample temperature field on a CROCO sigma grid by first converting z to sigma levels.\"\"\"\n", - " sigma = convert_z_to_sigma_croco(fieldset, particles.time, particles.z, particles.lat, particles.lon, particles)\n", - " particles.temp = fieldset.T[particles.time, sigma, particles.lat, particles.lon, particles]\n", - "```" + " sigma = convert_z_to_sigma_croco(fieldset, particles.time, particles.z, particles.y, particles.x, particles)\n", + " particles.temp = fieldset.T[particles.time, sigma, particles.y, particles.x, particles]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "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 depth to sigma is done at every time step, using the `convert_z_to_sigma_croco()` function.\n", + "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", "\n", "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", "\n", "```python\n", - "sigma = particles.z / fieldset.h[particles.time, 0, particles.lat, particles.lon]\n", + "sigma = particles.z / fieldset.h[particles.time, 0, particles.y, particles.x]\n", "\n", - "sigma_levels = convert_z_to_sigma_croco(fieldset, particles.time, particles.z, particles.lat, particles.lon, particles)\n", - "(u, v) = fieldset.UV[time, sigma_levels, particle.lat, particle.lon, particle]\n", - "w = fieldset.W[time, sigma_levels, particle.lat, particle.lon, particle]\n", + "sigma_levels = convert_z_to_sigma_croco(fieldset, particles.time, particles.z, particles.y, particles.x, particles)\n", + "(u, v) = fieldset.UV[time, sigma_levels, particles.y, particles.x, particles]\n", + "w = fieldset.W[time, sigma_levels, particles.y, particles.x, particles]\n", "\n", "# scaling the w with the sigma level of the particle\n", - "w_sigma = w * sigma / fieldset.h[particles.time, 0, particles.lat, particles.lon]\n", + "w_sigma = w * sigma / fieldset.h[particles.time, 0, particles.y, particles.x, particles]\n", "\n", - "lon_new = particles.lon + u*particles.dt\n", - "lat_new = particles.lat + v*particles.dt\n", + "x_new = particles.x + u*particles.dt\n", + "y_new = particles.y + v*particles.dt\n", "\n", "# calculating new sigma level\n", "sigma_new = sigma + w_sigma*particles.dt \n", "\n", - "# converting back from sigma to depth, at _new_ location\n", - "depth_new = sigma_new * fieldset.h[particles.time, 0, lat_new, lon_new]\n", + "# converting back from sigma to z, at _new_ location\n", + "z_new = sigma_new * fieldset.h[particles.time, 0, y_new, x_new, particles]\n", "```" ] } ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -318,7 +315,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_diffusion.ipynb b/docs/user_guide/examples/tutorial_diffusion.ipynb index 5aaa094c0..639d63027 100644 --- a/docs/user_guide/examples/tutorial_diffusion.ipynb +++ b/docs/user_guide/examples/tutorial_diffusion.ipynb @@ -227,8 +227,8 @@ " return parcels.ParticleSet(\n", " fieldset,\n", " pclass=parcels.Particle,\n", - " lon=np.zeros(100),\n", - " lat=np.ones(100) * 0.75,\n", + " x=np.zeros(100),\n", + " y=np.ones(100) * 0.75,\n", " time=np.repeat(np.timedelta64(0, \"s\"), 100),\n", " )" ] @@ -290,13 +290,13 @@ "\n", "x = np.arange(0, 0.3, 0.001)\n", "for traj in M1_out.partition_by(\"particle_id\", maintain_order=True):\n", - " ax[0].plot(x, traj[\"lat\"][: len(x)], alpha=0.3)\n", + " ax[0].plot(x, traj[\"y\"][: len(x)], alpha=0.3)\n", "ax[0].scatter(0, 0.75, s=20, c=\"r\", zorder=3)\n", "ax[0].set_ylabel(\"y\")\n", "ax[0].set_ylim(0, 1)\n", "\n", "for traj in M1_out.partition_by(\"particle_id\", maintain_order=True):\n", - " ax[1].plot(x, traj[\"lon\"][: len(x)], alpha=0.3)\n", + " ax[1].plot(x, traj[\"x\"][: len(x)], alpha=0.3)\n", "ax[1].scatter(0, 0, s=20, c=\"r\", zorder=3)\n", "ax[1].set_ylabel(\"x\")\n", "ax[1].set_ylim(-1, 1)\n", @@ -363,13 +363,13 @@ "x = np.arange(0, 0.3, 0.001)\n", "x = np.arange(0, 0.3, 0.001)\n", "for traj in EM_out.partition_by(\"particle_id\", maintain_order=True):\n", - " ax[0].plot(x, traj[\"lat\"][: len(x)], alpha=0.3)\n", + " ax[0].plot(x, traj[\"y\"][: len(x)], alpha=0.3)\n", "ax[0].scatter(0, 0.75, s=20, c=\"r\", zorder=3)\n", "ax[0].set_ylabel(\"y\")\n", "ax[0].set_ylim(0, 1)\n", "\n", "for traj in EM_out.partition_by(\"particle_id\", maintain_order=True):\n", - " ax[1].plot(x, traj[\"lon\"][: len(x)], alpha=0.3)\n", + " ax[1].plot(x, traj[\"x\"][: len(x)], alpha=0.3)\n", "ax[1].scatter(0, 0, s=20, c=\"r\", zorder=3)\n", "ax[1].set_ylabel(\"x\")\n", "ax[1].set_ylim(-1, 1)\n", @@ -422,16 +422,16 @@ " dx = 0.01\n", " # gradients are computed by using a local central difference.\n", " updx, vpdx = fieldset.UV[\n", - " particles.time, particles.z, particles.lat, particles.lon + dx, particles\n", + " particles.time, particles.z, particles.y, particles.x + dx, particles\n", " ]\n", " umdx, vmdx = fieldset.UV[\n", - " particles.time, particles.z, particles.lat, particles.lon - dx, particles\n", + " particles.time, particles.z, particles.y, particles.x - dx, particles\n", " ]\n", " updy, vpdy = fieldset.UV[\n", - " particles.time, particles.z, particles.lat + dx, particles.lon, particles\n", + " particles.time, particles.z, particles.y + dx, particles.x, particles\n", " ]\n", " umdy, vmdy = fieldset.UV[\n", - " particles.time, particles.z, particles.lat - dx, particles.lon, particles\n", + " particles.time, particles.z, particles.y - dx, particles.x, particles\n", " ]\n", "\n", " dudx = (updx - umdx) / (2 * dx)\n", @@ -440,19 +440,19 @@ " dvdx = (vpdx - vmdx) / (2 * dx)\n", " dvdy = (vpdy - vmdy) / (2 * dx)\n", "\n", - " A = fieldset.cell_areas[particles.time, particles.z, particles.lat, particles.lon]\n", - " sq_deg_to_sq_m = (1852 * 60) ** 2 * np.cos(particles.lat * np.pi / 180)\n", + " A = fieldset.cell_areas[particles.time, particles.z, particles.y, particles.x]\n", + " sq_deg_to_sq_m = (1852 * 60) ** 2 * np.cos(particles.y * np.pi / 180)\n", " A = A / sq_deg_to_sq_m\n", " Kh = fieldset.Cs * A * np.sqrt(dudx**2 + 0.5 * (dudy + dvdx) ** 2 + dvdy**2)\n", "\n", - " dlat = np.random.normal(0.0, 1.0, particles.dt.shape) * np.sqrt(\n", + " dy = np.random.normal(0.0, 1.0, particles.dt.shape) * np.sqrt(\n", " 2 * np.fabs(particles.dt) * Kh\n", " )\n", - " dlon = np.random.normal(0.0, 1.0, particles.dt.shape) * np.sqrt(\n", + " dx = np.random.normal(0.0, 1.0, particles.dt.shape) * np.sqrt(\n", " 2 * np.fabs(particles.dt) * Kh\n", " )\n", - " particles.dlat += dlat\n", - " particles.dlon += dlon" + " particles.dy += dy\n", + " particles.dx += dx" ] }, { @@ -490,11 +490,6 @@ "metadata": {}, "outputs": [], "source": [ - "fields = {\"U\": ds_fields[\"uo\"], \"V\": ds_fields[\"vo\"]}\n", - "ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n", - "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", - "\n", - "\n", "def degree_lat_to_meter(d):\n", " return d * 1000.0 * 1.852 * 60.0\n", "\n", @@ -519,24 +514,21 @@ " return cell_areas\n", "\n", "\n", - "da_cell_areas = xr.DataArray(\n", - " data=calc_cell_areas(ds_fields),\n", - " coords=dict(\n", - " latitude=([\"lat\"], ds_fields.latitude.values),\n", - " longitude=([\"lon\"], ds_fields.longitude.values),\n", - " ),\n", - " dims=[\"lat\", \"lon\"],\n", - ")\n", + "fields = {\"U\": ds_fields[\"uo\"], \"V\": ds_fields[\"vo\"]}\n", + "ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n", "\n", - "cell_areas_field = parcels.Field(\n", - " \"cell_areas\",\n", - " da_cell_areas,\n", - " fieldset.U.grid,\n", - " interp_method=parcels.interpolators.XNearest,\n", - ")\n", - "fieldset.add_field(cell_areas_field)\n", + "# TODO implement zero-dimension support on fieldset dimensions (#2727)\n", + "# ds_fset[\"cell_areas\"] = ([\"lat\", \"lon\"], calc_cell_areas(ds_fields))\n", + "\n", + "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", + "fieldset.add_context(\"Cs\", 0.1)\n", + "\n", + "# TODO remove if previous TODO fixed\n", + "ds_cell_areas = ds_fset[[\"grid\", \"lon\", \"lat\"]]\n", + "ds_cell_areas[\"cell_areas\"] = ([\"lat\", \"lon\"], calc_cell_areas(ds_fields))\n", + "fset2 = parcels.FieldSet.from_sgrid_conventions(ds_cell_areas)\n", "\n", - "fieldset.add_context(\"Cs\", 0.1)" + "fieldset += fset2" ] }, { @@ -566,8 +558,8 @@ " pclass=parcels.Particle,\n", " time=time,\n", " z=z,\n", - " lat=lat,\n", - " lon=lon,\n", + " y=lat,\n", + " x=lon,\n", ")\n", "\n", "output_file = parcels.ParticleFile(\"smagdiff.parquet\", outputdt=np.timedelta64(1, \"h\"))\n", @@ -603,7 +595,7 @@ " x=\"longitude\", y=\"latitude\", u=\"uo\", v=\"vo\"\n", ")\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " plt.plot(traj[\"lon\"], traj[\"lat\"], color=\"blue\")\n", + " plt.plot(traj[\"x\"], traj[\"y\"], color=\"blue\")\n", "plt.ylim(-31, -30)\n", "plt.xlim(31, 32.1)\n", "plt.show()" @@ -635,7 +627,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -649,7 +641,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_dt_integrators.ipynb b/docs/user_guide/examples/tutorial_dt_integrators.ipynb index 2acd3ebda..f92659b44 100644 --- a/docs/user_guide/examples/tutorial_dt_integrators.ipynb +++ b/docs/user_guide/examples/tutorial_dt_integrators.ipynb @@ -33,7 +33,7 @@ "\n", "In this example, we will estimate the appropriate timestep to compute advection of a virtual particle through an ocean current field. \n", "\n", - "Advection, the change in position $\\mathbf{x}(t) = (lon(t), lat(t))$ at time $t$, can be described by the equation:\n", + "Advection, the change in position $\\mathbf{x}(t) = (x(t), y(t))$ at time $t$, can be described by the equation:\n", "\n", "$$\n", "\\begin{aligned}\n", @@ -81,8 +81,8 @@ "print(\n", " f\"dt = {np.unique(np.diff(ds_fields.time.values).astype('timedelta64[h]'))} hours\"\n", ")\n", - "print(f\"dlat = {np.unique(np.diff(ds_fields.latitude.values))} degrees\")\n", - "print(f\"dlon = {np.unique(np.diff(ds_fields.longitude.values))} degrees\")" + "print(f\"dy = {np.unique(np.diff(ds_fields.latitude.values))} degrees\")\n", + "print(f\"dx = {np.unique(np.diff(ds_fields.longitude.values))} degrees\")" ] }, { @@ -92,13 +92,13 @@ "source": [ "In our Parcels simulation, we must ensure that:\n", "- `dt` < 24 hours\n", - "- `particles.dlon`/`particles.dlat` < 1/12th degree. \n", + "- `particles.dx`/`particles.dy` < 1/12th degree. \n", "\n", "We can rewrite the second condition using the scale relation:\n", "\n", "$$\n", "\\begin{aligned}\n", - "\\text{d}lon \\propto U \\text{d}t,\n", + "\\text{dx} \\propto U \\text{d}t,\n", "\\end{aligned}\n", "$$\n", "\n", @@ -126,7 +126,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Use the maximum velocity at the surface to scale dlon/dlat\n", + "# Use the maximum velocity at the surface to scale dx/dy\n", "U_max_surface = np.nanmax(np.hypot(ds_fields.uo, ds_fields.vo))\n", "print(f\"U_max = {str(np.round(U_max_surface, 2))} m s-1\")\n", "\n", @@ -238,8 +238,8 @@ " pclass=parcels.Particle,\n", " time=initial_release_times,\n", " z=initial_release_zs,\n", - " lat=initial_release_lats,\n", - " lon=initial_release_lons,\n", + " y=initial_release_lats,\n", + " x=initial_release_lons,\n", " )\n", " outputdt = dt\n", "\n", @@ -301,16 +301,14 @@ " )\n", " for i, traj in enumerate(df.partition_by(\"particle_id\", maintain_order=True)):\n", " ax.plot(\n", - " traj[\"lon\"],\n", - " traj[\"lat\"],\n", + " traj[\"x\"],\n", + " traj[\"y\"],\n", " alpha=0.75,\n", " color=plt.cm.viridis(dt_colours[j]),\n", " label=f\"dt = {dt}\" if i == 0 else None,\n", " )\n", "df_start = df.filter(pl.col(\"time\") == df[\"time\"].min())\n", - "ax.scatter(\n", - " df_start[\"lon\"], df_start[\"lat\"], c=\"r\", marker=\"s\", label=\"starting locations\"\n", - ")\n", + "ax.scatter(df_start[\"x\"], df_start[\"y\"], c=\"r\", marker=\"s\", label=\"starting locations\")\n", "ax.legend()\n", "ax.set_ylim(-32.7, -31.3)\n", "ax.set_xlim(31, 32.4)\n", @@ -389,11 +387,11 @@ " )\n", "\n", " # subset 5 minute data to match dt\n", - " lon_5min_sub = df_5min.filter(pl.col(\"time\").is_in(df[\"time\"].implode()))[\"lon\"]\n", - " lat_5min_sub = df_5min.filter(pl.col(\"time\").is_in(df[\"time\"].implode()))[\"lat\"]\n", + " x_5min_sub = df_5min.filter(pl.col(\"time\").is_in(df[\"time\"].implode()))[\"x\"]\n", + " y_5min_sub = df_5min.filter(pl.col(\"time\").is_in(df[\"time\"].implode()))[\"y\"]\n", "\n", " # compute separation distance between each particle in km\n", - " dist = dist_km(df[\"lon\"], lon_5min_sub, df[\"lat\"], lat_5min_sub)\n", + " dist = dist_km(df[\"x\"], x_5min_sub, df[\"y\"], y_5min_sub)\n", " df = df.with_columns(pl.Series(\"dist\", dist))\n", "\n", " # plot\n", @@ -542,7 +540,7 @@ "id": "26", "metadata": {}, "source": [ - "The higher-order methods use weighted intermediate steps in time and space to obtain a more accurate estimate of `dlat` and `dlon` for a given timestep.\n", + "The higher-order methods use weighted intermediate steps in time and space to obtain a more accurate estimate of `dy` and `dx` for a given timestep.\n", "\n", "Now let's see how the different advection schemes affect our simulation. As before, there is no exact solution to the simulated trajectories, so we will use the highest order method as the benchmark." ] @@ -581,8 +579,8 @@ " pclass=parcels.Particle,\n", " time=initial_release_times,\n", " z=initial_release_zs,\n", - " lat=initial_release_lats,\n", - " lon=initial_release_lons,\n", + " y=initial_release_lats,\n", + " x=initial_release_lons,\n", " )\n", " outputdt = dt\n", "\n", @@ -632,15 +630,15 @@ " )\n", " for i, traj in enumerate(df.partition_by(\"particle_id\", maintain_order=True)):\n", " axs[m, n].plot(\n", - " traj[\"lon\"],\n", - " traj[\"lat\"],\n", + " traj[\"x\"],\n", + " traj[\"y\"],\n", " alpha=0.75,\n", " color=plt.cm.viridis(scheme_colours[j]),\n", " label=f\"{advection_scheme.__name__}\" if i == 0 else None,\n", " )\n", " df_start = df.filter(pl.col(\"time\") == df[\"time\"].min())\n", " axs[m, n].scatter(\n", - " df_start[\"lon\"], df_start[\"lat\"], c=\"r\", marker=\"s\", label=\"starting locations\"\n", + " df_start[\"x\"], df_start[\"y\"], c=\"r\", marker=\"s\", label=\"starting locations\"\n", " )\n", " axs[m, n].grid()\n", "axs[-1, -1].axis(\"off\")\n", @@ -687,7 +685,7 @@ " / f\"KernelCompare_{advection_scheme.__name__}_dt_{int(dt / np.timedelta64(1, 's'))}s.parquet\"\n", " )\n", "\n", - " dist = dist_km(df[\"lon\"], df_RK4[\"lon\"], df[\"lat\"], df_RK4[\"lat\"])\n", + " dist = dist_km(df[\"x\"], df_RK4[\"x\"], df[\"y\"], df_RK4[\"y\"])\n", " df = df.with_columns(pl.Series(\"dist\", dist))\n", " for k, traj in enumerate(df.partition_by(\"particle_id\", maintain_order=True)):\n", " axs[m, n].plot(\n", @@ -812,7 +810,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -826,7 +824,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_fesom.ipynb b/docs/user_guide/examples/tutorial_fesom.ipynb index cc0828316..4f9d17e91 100644 --- a/docs/user_guide/examples/tutorial_fesom.ipynb +++ b/docs/user_guide/examples/tutorial_fesom.ipynb @@ -149,8 +149,9 @@ "fieldset = parcels.FieldSet.from_ugrid_conventions(ds, mesh=\"spherical\")\n", "\n", "for name, field in fieldset.fields.items():\n", + " # TODO clean this up when #2683 is implemented\n", " print(\n", - " f\"{name:>4s} -> {type(field).__name__:<11s} interp={field.interp_method.__name__}\"\n", + " f\"{name:>4s} -> {type(field).__name__:<11s} interp={field.interp_method.__class__.__name__}\"\n", " )" ] }, @@ -187,8 +188,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", " pclass=parcels.Particle,\n", - " lon=lon,\n", - " lat=lat,\n", + " x=lon,\n", + " y=lat,\n", " z=z,\n", ")\n", "\n", @@ -263,11 +264,11 @@ "\n", "# Particle paths (grey lines) and positions coloured by time.\n", "for traj in df.sort(\"time\").partition_by(\"particle_id\"):\n", - " ax.plot(traj[\"lon\"], traj[\"lat\"], color=\"0.4\", linewidth=0.6, alpha=0.7, zorder=2)\n", + " ax.plot(traj[\"x\"], traj[\"y\"], color=\"0.4\", linewidth=0.6, alpha=0.7, zorder=2)\n", "ax.scatter(lon, lat, facecolors=\"none\", edgecolors=\"k\", s=60, label=\"release\", zorder=3)\n", "sc = ax.scatter(\n", - " df[\"lon\"],\n", - " df[\"lat\"],\n", + " df[\"x\"],\n", + " df[\"y\"],\n", " c=df[\"time\"].dt.total_seconds(),\n", " s=6,\n", " cmap=\"viridis\",\n", @@ -294,7 +295,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Parcels:docs (3.14.4)", + "display_name": "Parcels:test (3.14.6)", "language": "python", "name": "python3" }, @@ -308,7 +309,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_interpolation.ipynb b/docs/user_guide/examples/tutorial_interpolation.ipynb index 982fa640c..6f27c3afd 100644 --- a/docs/user_guide/examples/tutorial_interpolation.ipynb +++ b/docs/user_guide/examples/tutorial_interpolation.ipynb @@ -130,7 +130,7 @@ " p_interp # setting the interpolation method for fieldset.P\n", " )\n", "\n", - " print(fieldset.P.interp_method)\n", + " print(fieldset.P.interp_method.__class__.__name__)\n", " xv, yv = np.meshgrid(np.linspace(0, 1, 8), np.linspace(0, 1, 8))\n", " pset[p_interp.__class__.__name__] = parcels.ParticleSet(\n", " fieldset, pclass=SampleParticle, lon=xv.flatten(), lat=yv.flatten()\n", diff --git a/docs/user_guide/examples/tutorial_schism.ipynb b/docs/user_guide/examples/tutorial_schism.ipynb index 879ec72e7..03af30e0b 100644 --- a/docs/user_guide/examples/tutorial_schism.ipynb +++ b/docs/user_guide/examples/tutorial_schism.ipynb @@ -216,8 +216,9 @@ "fieldset = parcels.FieldSet.from_ugrid_conventions(uxds, mesh=\"flat\")\n", "\n", "for name, field in fieldset.fields.items():\n", + " # TODO clean this up when #2683 is implemented\n", " print(\n", - " f\"{name:>4s} -> {type(field).__name__:<11s} interp={field.interp_method.__name__}\"\n", + " f\"{name:>4s} -> {type(field).__name__:<11s} interp={field.interp_method.__class__.__name__}\"\n", " )\n", "print(\"time interval:\", fieldset.time_interval)" ] @@ -413,7 +414,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Parcels:test (3.14.6)", "language": "python", "name": "python3" }, @@ -427,7 +428,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.3" + "version": "3.14.6" } }, "nbformat": 4, From 0afd4d435c045101ad68f8316f1ab6b76d44457e Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 6 Jul 2026 12:44:01 +0200 Subject: [PATCH 06/15] Second batch of tutorial cleanups after #2719 --- .../examples/tutorial_interaction.ipynb | 52 +++++++++---------- .../examples/tutorial_interpolation.ipynb | 20 +++---- docs/user_guide/examples/tutorial_nemo.ipynb | 38 +++++++------- .../examples/tutorial_nestedgrids.ipynb | 34 ++++++------ .../examples/tutorial_sampling.ipynb | 18 +++---- .../user_guide/examples/tutorial_schism.ipynb | 24 ++++----- .../examples/tutorial_write_in_kernel.ipynb | 8 +-- 7 files changed, 93 insertions(+), 101 deletions(-) diff --git a/docs/user_guide/examples/tutorial_interaction.ipynb b/docs/user_guide/examples/tutorial_interaction.ipynb index 97fe828e7..dc3d73e87 100644 --- a/docs/user_guide/examples/tutorial_interaction.ipynb +++ b/docs/user_guide/examples/tutorial_interaction.ipynb @@ -63,12 +63,12 @@ "\n", " # Boolean mask and coordinates of attractors\n", " attractor_mask = particles.attractor.astype(bool)\n", - " lon_a = particles.lon[attractor_mask]\n", - " lat_a = particles.lat[attractor_mask]\n", + " x_a = particles.x[attractor_mask]\n", + " y_a = particles.y[attractor_mask]\n", "\n", " # Pairwise differences and distances (n_attractors × n_particles)\n", - " dx = particles.lon - lon_a[:, None]\n", - " dy = particles.lat - lat_a[:, None]\n", + " dx = particles.x - x_a[:, None]\n", + " dy = particles.y - y_a[:, None]\n", " distances = np.sqrt(dx**2 + dy**2)\n", "\n", " # Mask dx, dy by interaction range\n", @@ -81,8 +81,8 @@ " dx_norm = dx * inv_dist\n", " dy_norm = dy * inv_dist\n", "\n", - " particles.dlon += np.sum(dx_norm, axis=0) * velocity * particles.dt\n", - " particles.dlat += np.sum(dy_norm, axis=0) * velocity * particles.dt" + " particles.dx += np.sum(dx_norm, axis=0) * velocity * particles.dt\n", + " particles.dy += np.sum(dy_norm, axis=0) * velocity * particles.dt" ] }, { @@ -124,8 +124,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=DiffusionFieldSet(),\n", " pclass=InteractingParticle,\n", - " lon=X,\n", - " lat=Y,\n", + " x=X,\n", + " y=Y,\n", " attractor=attractor,\n", ")\n", "\n", @@ -170,17 +170,17 @@ "ax.set_ylim(-1.1, 1.1)\n", "\n", "particles = df_other.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n", - "scatter = ax.scatter(particles[\"lon\"], particles[\"lat\"], c=\"b\", s=5, zorder=1)\n", + "scatter = ax.scatter(particles[\"x\"], particles[\"y\"], c=\"b\", s=5, zorder=1)\n", "particles_attr = df_attr.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n", "scatter_attr = ax.scatter(\n", - " particles_attr[\"lon\"], particles_attr[\"lat\"], c=\"r\", s=40, zorder=2\n", + " particles_attr[\"x\"], particles_attr[\"y\"], c=\"r\", s=40, zorder=2\n", ")\n", "circs = []\n", - "for lon_a, lat_a in zip(particles_attr[\"lon\"], particles_attr[\"lat\"], strict=True):\n", + "for x_a, y_a in zip(particles_attr[\"x\"], particles_attr[\"y\"], strict=True):\n", " circs.append(\n", " ax.add_patch(\n", " plt.Circle(\n", - " (lon_a, lat_a), 0.25, facecolor=\"None\", edgecolor=\"r\", linestyle=\"--\"\n", + " (x_a, y_a), 0.25, facecolor=\"None\", edgecolor=\"r\", linestyle=\"--\"\n", " )\n", " )\n", " )\n", @@ -192,13 +192,13 @@ "\n", "def animate(i):\n", " particles = df_other.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n", - " scatter.set_offsets(np.c_[particles[\"lon\"], particles[\"lat\"]])\n", + " scatter.set_offsets(np.c_[particles[\"x\"], particles[\"y\"]])\n", " particles_attr = df_attr.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n", - " scatter_attr.set_offsets(np.c_[particles_attr[\"lon\"], particles_attr[\"lat\"]])\n", - " for c, lon_a, lat_a in zip(\n", - " circs, particles_attr[\"lon\"], particles_attr[\"lat\"], strict=True\n", + " scatter_attr.set_offsets(np.c_[particles_attr[\"x\"], particles_attr[\"y\"]])\n", + " for c, x_a, y_a in zip(\n", + " circs, particles_attr[\"x\"], particles_attr[\"y\"], strict=True\n", " ):\n", - " c.center = (lon_a, lat_a)\n", + " c.center = (x_a, y_a)\n", " title.set_text(\n", " f\"Particles at t = {timerange[i].total_seconds()}s\\n(Red particles are attractors)\"\n", " )\n", @@ -235,11 +235,11 @@ " \"\"\"\n", " interaction_distance = 0.05\n", "\n", - " N = len(particles.lon)\n", + " N = len(particles.x)\n", "\n", " # calculate pairwise distances (n_particles × n_particles)\n", - " dx = particles.lon[None, :] - particles.lon[:, None]\n", - " dy = particles.lat[None, :] - particles.lat[:, None]\n", + " dx = particles.x[None, :] - particles.x[:, None]\n", + " dy = particles.y[None, :] - particles.y[:, None]\n", " distances = np.sqrt(dx**2 + dy**2)\n", "\n", " # mask distances by interaction range\n", @@ -291,8 +291,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=DiffusionFieldSet(),\n", " pclass=MergeParticle,\n", - " lon=np.random.uniform(-1, 1, size=npart),\n", - " lat=np.random.uniform(-1, 1, size=npart),\n", + " x=np.random.uniform(-1, 1, size=npart),\n", + " y=np.random.uniform(-1, 1, size=npart),\n", " mass=np.random.uniform(0.5, 1.5, size=npart),\n", ")\n", "\n", @@ -335,14 +335,14 @@ "\n", "particles = df.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n", "scatter = ax.scatter(\n", - " particles[\"lon\"], particles[\"lat\"], c=\"b\", s=particles[\"mass\"], zorder=1\n", + " particles[\"x\"], particles[\"y\"], c=\"b\", s=particles[\"mass\"], zorder=1\n", ")\n", "title = ax.set_title(f\"Particles at t = {timerange[0].total_seconds()}s\")\n", "\n", "\n", "def animate(i):\n", " particles = df.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n", - " scatter.set_offsets(np.c_[particles[\"lon\"], particles[\"lat\"]])\n", + " scatter.set_offsets(np.c_[particles[\"x\"], particles[\"y\"]])\n", " scatter.set_sizes(particles[\"mass\"])\n", " title.set_text(f\"Particles at t = {timerange[i].total_seconds()}s\")\n", "\n", @@ -355,7 +355,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -369,7 +369,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_interpolation.ipynb b/docs/user_guide/examples/tutorial_interpolation.ipynb index 6f27c3afd..8edefd70d 100644 --- a/docs/user_guide/examples/tutorial_interpolation.ipynb +++ b/docs/user_guide/examples/tutorial_interpolation.ipynb @@ -133,7 +133,7 @@ " print(fieldset.P.interp_method.__class__.__name__)\n", " xv, yv = np.meshgrid(np.linspace(0, 1, 8), np.linspace(0, 1, 8))\n", " pset[p_interp.__class__.__name__] = parcels.ParticleSet(\n", - " fieldset, pclass=SampleParticle, lon=xv.flatten(), lat=yv.flatten()\n", + " fieldset, pclass=SampleParticle, x=xv.flatten(), y=yv.flatten()\n", " )\n", " pset[p_interp.__class__.__name__].execute(\n", " SampleP,\n", @@ -170,7 +170,7 @@ " ax[i].axvline(lon, color=\"k\", linestyle=\"--\")\n", " pc = ax[i].pcolormesh(x, y, data, vmin=0.1, vmax=1.1)\n", " ax[i].scatter(\n", - " pset[p].lon, pset[p].lat, c=pset[p].p, edgecolors=\"k\", s=50, vmin=0.1, vmax=1.1\n", + " pset[p].x, pset[p].y, c=pset[p].p, edgecolors=\"k\", s=50, vmin=0.1, vmax=1.1\n", " )\n", " xp, yp = np.meshgrid(fieldset.P.grid.lon, fieldset.P.grid.lat)\n", " ax[i].plot(xp, yp, \"kx\")\n", @@ -198,8 +198,8 @@ "outputs": [], "source": [ "plt.scatter(\n", - " pset[\"XLinear\"].lon,\n", - " pset[\"XLinear\"].lat,\n", + " pset[\"XLinear\"].x,\n", + " pset[\"XLinear\"].y,\n", " c=pset[\"XLinearInvdistLandTracer\"].p - pset[\"XLinear\"].p,\n", " edgecolors=\"k\",\n", " s=50,\n", @@ -339,7 +339,7 @@ "xv, yv = np.meshgrid(np.linspace(0.2, 0.8, 8), np.linspace(0.2, 0.9, 8))\n", "\n", "pset[\"node\"] = parcels.ParticleSet(\n", - " fieldset, pclass=SampleParticle, lon=xv.flatten(), lat=yv.flatten()\n", + " fieldset, pclass=SampleParticle, x=xv.flatten(), y=yv.flatten()\n", ")\n", "pset[\"node\"].execute(\n", " SampleTracer_Node,\n", @@ -349,7 +349,7 @@ ")\n", "\n", "pset[\"face\"] = parcels.ParticleSet(\n", - " fieldset, pclass=SampleParticle, lon=xv.flatten(), lat=yv.flatten()\n", + " fieldset, pclass=SampleParticle, x=xv.flatten(), y=yv.flatten()\n", ")\n", "pset[\"face\"].execute(\n", " SampleTracer_Face,\n", @@ -403,8 +403,8 @@ ")\n", "\n", "ax[0].scatter(\n", - " pset[\"node\"].lon,\n", - " pset[\"node\"].lat,\n", + " pset[\"node\"].x,\n", + " pset[\"node\"].y,\n", " c=pset[\"node\"].tracer,\n", " cmap=\"viridis\",\n", " edgecolors=\"k\",\n", @@ -442,8 +442,8 @@ "yf = ds.uxgrid.face_lat.values\n", "\n", "ax[1].scatter(\n", - " pset[\"face\"].lon,\n", - " pset[\"face\"].lat,\n", + " pset[\"face\"].x,\n", + " pset[\"face\"].y,\n", " c=pset[\"face\"].tracer,\n", " cmap=\"viridis\",\n", " edgecolors=\"k\",\n", diff --git a/docs/user_guide/examples/tutorial_nemo.ipynb b/docs/user_guide/examples/tutorial_nemo.ipynb index 01838cfff..27522d784 100644 --- a/docs/user_guide/examples/tutorial_nemo.ipynb +++ b/docs/user_guide/examples/tutorial_nemo.ipynb @@ -149,7 +149,7 @@ "latp = np.linspace(-70, 88, npart)\n", "runtime = np.timedelta64(40, \"D\")\n", "\n", - "pset = parcels.ParticleSet(fieldset, lon=lonp, lat=latp)\n", + "pset = parcels.ParticleSet(fieldset, x=lonp, y=latp)\n", "pfile = parcels.ParticleFile(\n", " \"output_curvilinear.parquet\", outputdt=np.timedelta64(1, \"D\")\n", ")\n", @@ -160,7 +160,7 @@ " dt=np.timedelta64(1, \"D\"),\n", " output_file=pfile,\n", ")\n", - "np.testing.assert_allclose(pset.lat, latp, atol=1e-1)" + "np.testing.assert_allclose(pset.y, latp, atol=1e-1)" ] }, { @@ -179,7 +179,7 @@ "df = parcels.read_particlefile(\"output_curvilinear.parquet\")\n", "\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " plt.plot(traj[\"lon\"], traj[\"lat\"], \".-\")\n", + " plt.plot(traj[\"x\"], traj[\"y\"], \".-\")\n", "\n", "plt.vlines(np.arange(-180, 901, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", "plt.ylabel(\"Latitude [deg N]\")\n", @@ -204,12 +204,12 @@ "outputs": [], "source": [ "# post processing\n", - "df = df.with_columns((pl.col(\"lon\") % 360).alias(\"lon\"))\n", + "df = df.with_columns((pl.col(\"x\") % 360).alias(\"x\"))\n", "df = df.with_columns(\n", - " pl.when(pl.col(\"lon\") <= 180)\n", - " .then(pl.col(\"lon\"))\n", - " .otherwise(pl.col(\"lon\") - 360)\n", - " .alias(\"lon\")\n", + " pl.when(pl.col(\"x\") <= 180)\n", + " .then(pl.col(\"x\"))\n", + " .otherwise(pl.col(\"x\") - 360)\n", + " .alias(\"x\")\n", ")" ] }, @@ -225,12 +225,12 @@ "source": [ "# with a Kernel\n", "def periodicBC(particles, fieldset): # pragma: no cover\n", - " particles.dlon = np.where(\n", - " particles.lon + particles.dlon > 180, particles.dlon - 360, particles.dlon\n", + " particles.dx = np.where(\n", + " particles.x + particles.dx > 180, particles.dx - 360, particles.dx\n", " )\n", "\n", "\n", - "pset = parcels.ParticleSet(fieldset, lon=lonp, lat=latp)\n", + "pset = parcels.ParticleSet(fieldset, x=lonp, y=latp)\n", "pfile = parcels.ParticleFile(\n", " \"output_curvilinear_periodic.parquet\", outputdt=np.timedelta64(1, \"D\")\n", ")\n", @@ -251,7 +251,7 @@ "source": [ "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " ax[0].plot(traj[\"lon\"], traj[\"lat\"], \".-\")\n", + " ax[0].plot(traj[\"x\"], traj[\"y\"], \".-\")\n", "ax[0].vlines(np.arange(-180, 360, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", "ax[0].set_ylabel(\"Latitude [deg N]\")\n", "ax[0].set_xlabel(\"Longitude [deg E]\")\n", @@ -262,7 +262,7 @@ "\n", "df_periodic = parcels.read_particlefile(\"output_curvilinear_periodic.parquet\")\n", "for traj in df_periodic.partition_by(\"particle_id\", maintain_order=True):\n", - " ax[1].plot(traj[\"lon\"], traj[\"lat\"], \".-\")\n", + " ax[1].plot(traj[\"x\"], traj[\"y\"], \".-\")\n", "\n", "ax[1].vlines(np.arange(-180, 360, 360), -90, 90, color=\"r\", label=\"antimeridian\")\n", "ax[1].set_ylabel(\"Latitude [deg N]\")\n", @@ -330,8 +330,8 @@ "npart = 10\n", "pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", - " lon=np.linspace(1.9, 3.4, npart),\n", - " lat=np.linspace(65, 51.6, npart),\n", + " x=np.linspace(1.9, 3.4, npart),\n", + " y=np.linspace(65, 51.6, npart),\n", " z=100 * np.ones(npart),\n", ")\n", "\n", @@ -366,9 +366,7 @@ "vmin = df[\"z\"].min()\n", "vmax = df[\"z\"].max()\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " plt.scatter(\n", - " traj[\"lon\"], traj[\"lat\"], c=-traj[\"z\"], marker=\".\", vmin=-vmax, vmax=-vmin\n", - " )\n", + " plt.scatter(traj[\"x\"], traj[\"y\"], c=-traj[\"z\"], marker=\".\", vmin=-vmax, vmax=-vmin)\n", "plt.colorbar(label=\"Depth (m)\")\n", "plt.show()" ] @@ -376,7 +374,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -390,7 +388,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_nestedgrids.ipynb b/docs/user_guide/examples/tutorial_nestedgrids.ipynb index 85326b317..b6baf6e9e 100644 --- a/docs/user_guide/examples/tutorial_nestedgrids.ipynb +++ b/docs/user_guide/examples/tutorial_nestedgrids.ipynb @@ -361,7 +361,7 @@ " parcels.Variable(\"gridID\", dtype=np.int32)\n", ")\n", "pset = parcels.ParticleSet(\n", - " fieldset, pclass=NestedGridParticle, lon=X.flatten(), lat=Y.flatten()\n", + " fieldset, pclass=NestedGridParticle, x=X.flatten(), y=Y.flatten()\n", ")\n", "\n", "\n", @@ -403,7 +403,7 @@ "facecolors = np.squeeze(uxda.GridID[0, :].values)\n", "\n", "ax.tripcolor(triang, facecolors=facecolors, shading=\"flat\", **plot_args)\n", - "ax.scatter(pset.lon, pset.lat, c=pset.gridID, **plot_args)\n", + "ax.scatter(pset.x, pset.y, c=pset.gridID, **plot_args)\n", "ax.set_aspect(\"equal\")\n", "ax.set_title(\n", " \"Nested Grids visualisation (triangulation and interpolated particle values)\"\n", @@ -431,15 +431,11 @@ "metadata": {}, "outputs": [], "source": [ - "for i, ds in enumerate(ds_in):\n", - " fset = parcels.FieldSet.from_sgrid_conventions(ds, mesh=\"spherical\")\n", - "\n", - " # Rename and add fields to the main fieldset\n", - " # TODO This needs a beeter API!\n", - " for old_name, fld in list(fset.fields.items()):\n", - " new_name = f\"{old_name}{i}\"\n", - " fld.name = new_name\n", - " fieldset._fields[new_name] = fld" + "for i, ds_i in enumerate(ds_in):\n", + " ds_i = ds_i.rename({\"U\": f\"U{i}\", \"V\": f\"V{i}\"})\n", + " fieldset += parcels.FieldSet.from_sgrid_conventions(\n", + " ds_i, mesh=\"spherical\", vector_fields={f\"UV{i}\": (f\"U{i}\", f\"V{i}\")}\n", + " )" ] }, { @@ -458,8 +454,8 @@ "outputs": [], "source": [ "def AdvectEE_NestedGrids(particles, fieldset):\n", - " u = np.zeros_like(particles.lon)\n", - " v = np.zeros_like(particles.lat)\n", + " u = np.zeros_like(particles.x)\n", + " v = np.zeros_like(particles.y)\n", "\n", " particles.gridID = fieldset.GridID[particles]\n", " unique_ids = np.unique(particles.gridID)\n", @@ -468,14 +464,14 @@ " UVField = fieldset._fields[f\"UV{gid}\"]\n", " (u[mask], v[mask]) = UVField[particles[mask]]\n", "\n", - " particles.dlon += u * particles.dt\n", - " particles.dlat += v * particles.dt\n", + " particles.dx += u * particles.dt\n", + " particles.dy += v * particles.dt\n", "\n", "\n", "lat = np.linspace(-17, 35, 10)\n", "lon = np.full(len(lat), -5)\n", "\n", - "pset = parcels.ParticleSet(fieldset, pclass=NestedGridParticle, lon=lon, lat=lat)\n", + "pset = parcels.ParticleSet(fieldset, pclass=NestedGridParticle, x=lon, y=lat)\n", "ofile = parcels.ParticleFile(\n", " \"nestedgrid_particles.parquet\", outputdt=np.timedelta64(1, \"D\")\n", ")\n", @@ -500,10 +496,10 @@ "df = parcels.read_particlefile(\"nestedgrid_particles.parquet\")\n", "\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " plt.plot(traj[\"lon\"], traj[\"lat\"], \"k\")\n", + " plt.plot(traj[\"x\"], traj[\"y\"], \"k\")\n", " sc = ax.scatter(\n", - " traj[\"lon\"],\n", - " traj[\"lat\"],\n", + " traj[\"x\"],\n", + " traj[\"y\"],\n", " c=traj[\"gridID\"],\n", " s=4,\n", " cmap=cmap,\n", diff --git a/docs/user_guide/examples/tutorial_sampling.ipynb b/docs/user_guide/examples/tutorial_sampling.ipynb index a9ded57b3..91d8cfe1f 100644 --- a/docs/user_guide/examples/tutorial_sampling.ipynb +++ b/docs/user_guide/examples/tutorial_sampling.ipynb @@ -127,7 +127,7 @@ "\n", "def SampleT(particles, fieldset):\n", " particles.temperature = fieldset.thetao[\n", - " particles.time, particles.z, particles.lat, particles.lon\n", + " particles.time, particles.z, particles.y, particles.x\n", " ]" ] }, @@ -150,7 +150,7 @@ "outputs": [], "source": [ "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=SampleParticle, lon=lon, lat=lat, time=time, z=z\n", + " fieldset=fieldset, pclass=SampleParticle, x=lon, y=lat, time=time, z=z\n", ")\n", "\n", "output_file = parcels.ParticleFile(\"sampletemp.parquet\", outputdt=timedelta(hours=1))\n", @@ -184,10 +184,10 @@ "ax.set_ylabel(\"Latitude\")\n", "ax.set_xlabel(\"Longitude\")\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " ax.plot(traj[\"lon\"], traj[\"lat\"], c=\"k\", zorder=1)\n", + " ax.plot(traj[\"x\"], traj[\"y\"], c=\"k\", zorder=1)\n", " T_scatter = ax.scatter(\n", - " traj[\"lon\"],\n", - " traj[\"lat\"],\n", + " traj[\"x\"],\n", + " traj[\"y\"],\n", " c=traj[\"temperature\"],\n", " cmap=plt.cm.inferno,\n", " norm=mpl.colors.Normalize(vmin=22.0, vmax=24.0),\n", @@ -222,7 +222,7 @@ "\n", "\n", "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=parcels.Particle, lon=lon, lat=lat, time=time, z=z\n", + " fieldset=fieldset, pclass=parcels.Particle, x=lon, y=lat, time=time, z=z\n", ")\n", "\n", "pset.execute(\n", @@ -255,7 +255,7 @@ "\n", "\n", "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=parcels.Particle, lon=lon, lat=lat, time=time, z=z\n", + " fieldset=fieldset, pclass=parcels.Particle, x=lon, y=lat, time=time, z=z\n", ")\n", "\n", "pset.execute(\n", @@ -306,7 +306,7 @@ "metadata": { "celltoolbar": "Raw-celnotatie", "kernelspec": { - "display_name": "Parcels:docs (3.14.4)", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -320,7 +320,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" }, "pycharm": { "stem_cell": { diff --git a/docs/user_guide/examples/tutorial_schism.ipynb b/docs/user_guide/examples/tutorial_schism.ipynb index 03af30e0b..63c477400 100644 --- a/docs/user_guide/examples/tutorial_schism.ipynb +++ b/docs/user_guide/examples/tutorial_schism.ipynb @@ -295,13 +295,11 @@ "idx = np.where(deep)[0]\n", "idx = idx[:: max(1, idx.size // n_max)][:n_max]\n", "\n", - "lon, lat = cx[idx], cy[idx]\n", - "z = np.full(lon.size, 2.0) # release at 2 m depth\n", - "print(f\"releasing {lon.size} particles at z = 2 m in the deep basin\")\n", + "x, y = cx[idx], cy[idx]\n", + "z = np.full(x.size, 2.0) # release at 2 m depth\n", + "print(f\"releasing {x.size} particles at z = 2 m in the deep basin\")\n", "\n", - "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=SchismParticle, lon=lon, lat=lat, z=z\n", - ")\n", + "pset = parcels.ParticleSet(fieldset=fieldset, pclass=SchismParticle, x=x, y=y, z=z)\n", "output_file = parcels.ParticleFile(\n", " \"output-schism.parquet\", outputdt=np.timedelta64(30, \"m\")\n", ")\n", @@ -313,7 +311,7 @@ " output_file=output_file,\n", " verbose_progress=False,\n", ")\n", - "print(f\"{len(pset.lon)} of {lon.size} particles still active at the end of the run\")" + "print(f\"{len(pset.x)} of {x.size} particles still active at the end of the run\")" ] }, { @@ -357,16 +355,16 @@ "\n", "for traj in df.sort(\"time\").partition_by(\"particle_id\"):\n", " ax.plot(\n", - " np.array(traj[\"lon\"]) / 1e3,\n", - " np.array(traj[\"lat\"]) / 1e3,\n", + " np.array(traj[\"x\"]) / 1e3,\n", + " np.array(traj[\"y\"]) / 1e3,\n", " color=\"0.3\",\n", " lw=0.6,\n", " alpha=0.7,\n", " zorder=2,\n", " )\n", "ax.scatter(\n", - " lon / 1e3,\n", - " lat / 1e3,\n", + " x / 1e3,\n", + " y / 1e3,\n", " facecolors=\"none\",\n", " edgecolors=\"k\",\n", " s=20,\n", @@ -376,8 +374,8 @@ "\n", "elapsed_h = (df[\"time\"] - df[\"time\"].min()).dt.total_seconds() / 3600\n", "sc = ax.scatter(\n", - " np.array(df[\"lon\"]) / 1e3,\n", - " np.array(df[\"lat\"]) / 1e3,\n", + " np.array(df[\"x\"]) / 1e3,\n", + " np.array(df[\"y\"]) / 1e3,\n", " c=elapsed_h,\n", " s=4,\n", " cmap=\"viridis\",\n", diff --git a/docs/user_guide/examples/tutorial_write_in_kernel.ipynb b/docs/user_guide/examples/tutorial_write_in_kernel.ipynb index f548dedb1..447747d7c 100644 --- a/docs/user_guide/examples/tutorial_write_in_kernel.ipynb +++ b/docs/user_guide/examples/tutorial_write_in_kernel.ipynb @@ -129,7 +129,7 @@ "ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields)\n", "fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset)\n", "\n", - "pset = parcels.ParticleSet(fieldset=fieldset, lon=[31, 32], lat=[-32, -31], z=[1, 1])\n", + "pset = parcels.ParticleSet(fieldset=fieldset, x=[31, 32], y=[-32, -31], z=[1, 1])\n", "\n", "pset.execute(\n", " kernels=[\n", @@ -215,7 +215,7 @@ " )\n", "\n", "\n", - "pset = parcels.ParticleSet(fieldset=fieldset, lon=[31, 32], lat=[-32, -31], z=[1, 1])\n", + "pset = parcels.ParticleSet(fieldset=fieldset, x=[31, 32], y=[-32, -31], z=[1, 1])\n", "\n", "pset.execute(\n", " kernels=[\n", @@ -253,7 +253,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Parcels:docs (3.14.4)", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -267,7 +267,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, From 7432089863240ecdc034e3661d9cc78b04264f34 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 6 Jul 2026 14:00:13 +0200 Subject: [PATCH 07/15] Third batch of tutorial cleanups after #2719 --- docs/conf.py | 1 - docs/getting_started/explanation_concepts.md | 2 +- .../examples/explanation_kernelloop.md | 49 +++++++++---------- .../examples/tutorial_delaystart.ipynb | 24 ++++----- 4 files changed, 36 insertions(+), 40 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7dab1c785..5d0af1b40 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -123,7 +123,6 @@ "**.ipynb_checkpoints", "user_guide/examples_v3", ".jupyter_cache", - "user_guide/examples/explanation_kernelloop.md", # TODO v4: https://github.com/Parcels-code/Parcels/issues/2695 ] # The reST default role (used for this markup: `text`) to use for all diff --git a/docs/getting_started/explanation_concepts.md b/docs/getting_started/explanation_concepts.md index 748e24afc..0234af316 100644 --- a/docs/getting_started/explanation_concepts.md +++ b/docs/getting_started/explanation_concepts.md @@ -126,7 +126,7 @@ def AdvectionEE(particles, fieldset): 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. ```{warning} -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](https://docs.oceanparcels.org/en/latest/examples/tutorial_kernelloop.html) to understand why. +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. ``` (custom-kernel)= diff --git a/docs/user_guide/examples/explanation_kernelloop.md b/docs/user_guide/examples/explanation_kernelloop.md index abb293990..e6f1cccb0 100644 --- a/docs/user_guide/examples/explanation_kernelloop.md +++ b/docs/user_guide/examples/explanation_kernelloop.md @@ -14,29 +14,29 @@ This is not very relevant when you only use the built-in Advection Kernels, but When you run a Parcels simulation (i.e. a call to `pset.execute()`), the Kernel loop is the main part of the code that is executed. This part of the code loops through time and executes the Kernels for all particle. -In order to make sure that the displacements of a particle in the different Kernels can be summed, all Kernels add to a _change_ in position (`particles.dlon`, `particles.dlat`, and `particles.dz`). This is important, because there are situations where movement Kernels would otherwise not commute. Take the example of advecting particles by currents _and_ winds. If the particle would first be moved by the currents and then by the winds, the result could be different from first moving by the winds and then by the currents. Instead, by summing the _changes_ in position, the ordering of the Kernels has no consequence on the particle displacement. +In order to make sure that the displacements of a particle in the different Kernels can be summed, all Kernels add to a _change_ in position (`particles.dx`, `particles.dy`, and `particles.dz`). This is important, because there are situations where movement Kernels would otherwise not commute. Take the example of advecting particles by currents _and_ winds. If the particle would first be moved by the currents and then by the winds, the result could be different from first moving by the winds and then by the currents. Instead, by summing the _changes_ in position, the ordering of the Kernels has no consequence on the particle displacement. ## Basic implementation -Below is a structured overview of how the Kernel loop is implemented. Note that this is for `time` and `lon` only, but the process for `lon` is also applied to `lat` and `z`. +Below is a structured overview of how the Kernel loop is implemented. Note that this is for `time` and `x` only, but the process for `y` and `z` is also applied to `y` and `z`. -1. Initialise an extra Variable `particles.dlon=0` +1. Initialise an extra Variable `particles.dx=0` 2. Within the Kernel loop, for each particle: - 1. Update `particles.lon += particles.dlon` + 1. Update `particles.x += particles.dx` 2. Update `particles.time += particles.dt` (except for on the first iteration of the Kernel loop)
- 3. Set variable `particles.dlon = 0` + 3. Set variable `particles.dx = 0` 4. For each Kernel in the list of Kernels: 1. Execute the Kernel - 2. Update `particles.dlon` by adding the change in longitude, if needed + 2. Update `particles.dx` by adding the change in x, if needed - 5. If `outputdt` is a multiple of `particles.time`, write `particles.lon` and `particles.time` to zarr output file + 5. If `outputdt` is a multiple of `particles.time`, write `particles.x` and `particles.time` to zarr output file -Besides having commutable Kernels, the main advantage of this implementation is that, when using Field Sampling with e.g. `particles.temp = fieldset.Temp[particles.time, particles.z, particles.lat, particles.lon]`, the particle location stays the same throughout the entire Kernel loop. Additionally, this implementation ensures that the particle location is the same as the location of the sampled field in the output file. +Besides having commutable Kernels, the main advantage of this implementation is that, when using Field Sampling with e.g. `particles.temp = fieldset.Temp[particles.time, particles.z, particles.y, particles.x]`, the particle location stays the same throughout the entire Kernel loop. Additionally, this implementation ensures that the particle location is the same as the location of the sampled field in the output file. ## Example with currents and winds @@ -76,25 +76,22 @@ fields = { "VWind": ds_fields["VWind"], } ds_fset = parcels.convert.copernicusmarine_to_sgrid(fields=fields) -fieldset = parcels.FieldSet.from_sgrid_conventions(ds_fset) - -# Create a vecorfield for the wind -windvector = parcels.VectorField( - "Wind", - fieldset.UWind, - fieldset.VWind, - interp_method=parcels.interpolators.XLinear_Velocity +fieldset = parcels.FieldSet.from_sgrid_conventions( + ds_fset, + vector_fields={ + "UV": ("U", "V"), + "Wind": ("UWind", "VWind") + } ) -fieldset.add_field(windvector) ``` -Now we define a wind Kernel that uses a forward Euler method to apply the wind forcing. Note that we update the `particles.dlon` and `particles.dlat` variables, rather than `particles.lon` and `particles.lat` directly. +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. ```{code-cell} def wind_kernel(particles, fieldset): uwind, vwind = fieldset.Wind[particles] - particles.dlon += uwind * particles.dt - particles.dlat += vwind * particles.dt + particles.dx += uwind * particles.dt + particles.dy += vwind * particles.dt ``` First run a simulation where we apply Kernels as `[AdvectionRK2, wind_kernel]` @@ -106,7 +103,7 @@ z = np.repeat(ds_fields.depth[0].values, npart) lons = np.repeat(31, npart) lats = np.linspace(-32.5, -30.5, npart) -pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, z=z, lat=lats, lon=lons) +pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, z=z, y=lats, x=lons) output_file = parcels.ParticleFile( path="advection_then_wind.parquet", outputdt=np.timedelta64(6,'h') ) @@ -123,7 +120,7 @@ Then also run a simulation where we apply the Kernels in the reverse order as `[ ```{code-cell} :tags: [hide-output] pset_reverse = parcels.ParticleSet( - fieldset, pclass=parcels.Particle, z=z, lat=lats, lon=lons + fieldset, pclass=parcels.Particle, z=z, y=lats, x=lons ) output_file_reverse = parcels.ParticleFile( path="wind_then_advection.parquet", outputdt=np.timedelta64(6,"h") @@ -145,14 +142,14 @@ wind_then_advection = parcels.read_particlefile("wind_then_advection.parquet") fig, ax = plt.subplots(figsize=(5, 3)) for traj in wind_then_advection.partition_by("particle_id", maintain_order=True): - ax.plot(traj["lon"], traj["lat"], "-") + ax.plot(traj["x"], traj["y"], "-") for traj in advection_then_wind.partition_by("particle_id", maintain_order=True): - ax.plot(traj["lon"], traj["lat"], "--", c="k", alpha=0.7) + ax.plot(traj["x"], traj["y"], "--", c="k", alpha=0.7) plt.show() ``` ```{warning} -It is better not to update `particles.lon` directly in a Kernel, as it can interfere with the loop above. Assigning a value to `particles.lon` in a Kernel will throw a warning. +It is better not to update `particles.x` directly in a Kernel, as it can interfere with the loop above. Assigning a value to `particles.x` in a Kernel will throw a warning. -Instead, update the local variable `particles.dlon`. +Instead, update the local variable `particles.dx`. ``` diff --git a/docs/user_guide/examples/tutorial_delaystart.ipynb b/docs/user_guide/examples/tutorial_delaystart.ipynb index 7678e2a17..e693a364b 100644 --- a/docs/user_guide/examples/tutorial_delaystart.ipynb +++ b/docs/user_guide/examples/tutorial_delaystart.ipynb @@ -94,7 +94,7 @@ "z = np.repeat(ds_fields.depth.values[0], npart)\n", "\n", "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=parcels.Particle, lon=lon, lat=lat, time=time, z=z\n", + " fieldset=fieldset, pclass=parcels.Particle, x=lon, y=lat, time=time, z=z\n", ")" ] }, @@ -155,13 +155,13 @@ "timerange = df_particles[\"time\"].unique()\n", "\n", "particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n", - "sc = ax.scatter(particles[\"lon\"], particles[\"lat\"])\n", + "sc = ax.scatter(particles[\"x\"], particles[\"y\"])\n", "title = ax.set_title(f\"Particles at t = {timerange[0]}\")\n", "\n", "\n", "def animate(i):\n", " particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n", - " sc.set_offsets(np.c_[particles[\"lon\"], particles[\"lat\"]])\n", + " sc.set_offsets(np.c_[particles[\"x\"], particles[\"y\"]])\n", " title.set_text(f\"Particles at t = {timerange[i]}\")\n", "\n", "\n", @@ -223,7 +223,7 @@ "z = np.broadcast_to(z_i, (nrepeat, npart))\n", "\n", "pset = parcels.ParticleSet(\n", - " fieldset=fieldset, pclass=parcels.Particle, lon=lon, lat=lat, time=time, z=z\n", + " fieldset=fieldset, pclass=parcels.Particle, x=lon, y=lat, time=time, z=z\n", ")" ] }, @@ -284,13 +284,13 @@ "timerange = df_particles[\"time\"].unique()\n", "\n", "particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[0]))\n", - "sc = ax.scatter(particles[\"lon\"], particles[\"lat\"])\n", + "sc = ax.scatter(particles[\"x\"], particles[\"y\"])\n", "title = ax.set_title(f\"Particles at t = {timerange[0]}\")\n", "\n", "\n", "def animate(i):\n", " particles = df_particles.filter(pl.col(\"time\") == pl.lit(timerange[i]))\n", - " sc.set_offsets(np.c_[particles[\"lon\"], particles[\"lat\"]])\n", + " sc.set_offsets(np.c_[particles[\"x\"], particles[\"y\"]])\n", " title.set_text(f\"Particles at t = {timerange[i]}\")\n", "\n", "\n", @@ -326,8 +326,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", " pclass=parcels.Particle,\n", - " lat=[-31] * 3,\n", - " lon=[32] * 3,\n", + " y=[-31] * 3,\n", + " x=[32] * 3,\n", " time=ds_fields.time.values[0] + np.arange(3) * np.timedelta64(1, \"h\"),\n", " z=[0.5] * 3,\n", ")\n", @@ -377,8 +377,8 @@ " pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", " pclass=parcels.Particle,\n", - " lat=[-31] * len(times),\n", - " lon=[32] * len(times),\n", + " y=[-31] * len(times),\n", + " x=[32] * len(times),\n", " time=ds_fields.time.values[0] + times,\n", " z=[0.5] * len(times),\n", " )\n", @@ -398,7 +398,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -412,7 +412,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, From b7faa2f883416bc8fc3ffbcca0cd2b3ee50fbc4d Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 6 Jul 2026 14:26:07 +0200 Subject: [PATCH 08/15] Fourth batch of tutorial cleanups after #2719 --- docs/user_guide/examples/tutorial_gsw_density.ipynb | 6 +++--- .../examples/tutorial_manipulating_field_data.ipynb | 8 ++++---- docs/user_guide/examples/tutorial_mitgcm.ipynb | 8 ++++---- docs/user_guide/examples/tutorial_statuscodes.md | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/user_guide/examples/tutorial_gsw_density.ipynb b/docs/user_guide/examples/tutorial_gsw_density.ipynb index fe089bbe1..5eda1c993 100644 --- a/docs/user_guide/examples/tutorial_gsw_density.ipynb +++ b/docs/user_guide/examples/tutorial_gsw_density.ipynb @@ -84,8 +84,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", " pclass=GSWParticle,\n", - " lon=[32],\n", - " lat=[-31],\n", + " x=[32],\n", + " y=[-31],\n", " z=[200],\n", ")" ] @@ -108,7 +108,7 @@ "def ParcelsGSW(particles, fieldset):\n", " particles.temp = fieldset.thetao[particles]\n", " particles.salt = fieldset.so[particles]\n", - " pressure = gsw.p_from_z(-particles.z, particles.lat)\n", + " pressure = gsw.p_from_z(-particles.z, particles.y)\n", " particles.density = gsw.density.rho(particles.salt, particles.temp, pressure)" ] }, diff --git a/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb index cf02db372..3d473ecd8 100644 --- a/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb +++ b/docs/user_guide/examples/tutorial_manipulating_field_data.ipynb @@ -129,7 +129,7 @@ "lons = np.repeat(31, npart)\n", "lats = np.linspace(-32.5, -30.5, npart)\n", "\n", - "pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, z=z, lat=lats, lon=lons)\n", + "pset = parcels.ParticleSet(fieldset, pclass=parcels.Particle, z=z, y=lats, x=lons)\n", "output_file = parcels.ParticleFile(\n", " path=\"summed_advection_wind.parquet\", outputdt=np.timedelta64(6, \"h\")\n", ")\n", @@ -159,14 +159,14 @@ "# Plot the resulting particle trajectories overlapped for both cases\n", "summed_advection_wind = parcels.read_particlefile(\"summed_advection_wind.parquet\")\n", "for traj in summed_advection_wind.partition_by(\"particle_id\", maintain_order=True):\n", - " plt.plot(traj[\"lon\"], traj[\"lat\"], \"-\")\n", + " plt.plot(traj[\"x\"], traj[\"y\"], \"-\")\n", "plt.show()" ] } ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -180,7 +180,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_mitgcm.ipynb b/docs/user_guide/examples/tutorial_mitgcm.ipynb index 92258c6ca..4135d0546 100644 --- a/docs/user_guide/examples/tutorial_mitgcm.ipynb +++ b/docs/user_guide/examples/tutorial_mitgcm.ipynb @@ -90,7 +90,7 @@ " particles[any_error].state = parcels.StatusCode.Delete\n", "\n", "\n", - "pset = parcels.ParticleSet(fieldset=fieldset, lon=X, lat=Y, z=Z)\n", + "pset = parcels.ParticleSet(fieldset=fieldset, x=X, y=Y, z=Z)\n", "\n", "outputfile = parcels.ParticleFile(\n", " path=\"mitgcm_particles.parquet\",\n", @@ -123,14 +123,14 @@ "df = parcels.read_particlefile(\"mitgcm_particles.parquet\")\n", "\n", "for traj in df.partition_by(\"particle_id\", maintain_order=True):\n", - " plt.plot(traj[\"lon\"], traj[\"lat\"], \".-\")\n", + " plt.plot(traj[\"x\"], traj[\"y\"], \".-\")\n", "plt.show()" ] } ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -144,7 +144,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.4" + "version": "3.14.6" } }, "nbformat": 4, diff --git a/docs/user_guide/examples/tutorial_statuscodes.md b/docs/user_guide/examples/tutorial_statuscodes.md index 557cb8063..461992b7e 100644 --- a/docs/user_guide/examples/tutorial_statuscodes.md +++ b/docs/user_guide/examples/tutorial_statuscodes.md @@ -37,7 +37,7 @@ But of course, you can also write code for more sophisticated behaviour than jus ``` def Move1DegreeWest(particles, fieldset): out_of_bounds = particles.state == parcels.StatusCode.ErrorOutOfBounds - particles[out_of_bounds].dlon -= 1.0 + particles[out_of_bounds].dx -= 1.0 particles[out_of_bounds].state = parcels.StatusCode.Evaluate ``` @@ -77,7 +77,7 @@ If we advect particles with the `AdvectionRK2_3D` kernel, Parcels will raise a ` ```{code-cell} :tags: [raises-exception] -pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], lat=[2], lon=[1.5]) +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], y=[2], x=[1.5]) kernels = [parcels.kernels.AdvectionRK2_3D] pset.execute(kernels, runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), verbose_progress=False) ``` @@ -85,7 +85,7 @@ pset.execute(kernels, runtime=np.timedelta64(1, "m"), dt=np.timedelta64(1, "s"), When we add the `KeepInOcean` Kernel, particles will stay at the surface: ```{code-cell} -pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], lat=[2], lon=[1.5]) +pset = parcels.ParticleSet(fieldset, parcels.Particle, z=[0.5], y=[2], x=[1.5]) kernels = [parcels.kernels.AdvectionRK2_3D, KeepInOcean] @@ -95,5 +95,5 @@ print(f"particle z at end of run = {pset.z}") ``` ```{note} -Kernels that control what to do with `particles.state` should typically be added at the _end_ of the Kernel list, because otherwise later Kernels may overwrite the `particles.state` or the `particles.dlon` variables (see [Kernel loop explanation](explanation_kernelloop.md)). +Kernels that control what to do with `particles.state` should typically be added at the _end_ of the Kernel list, because otherwise later Kernels may overwrite the `particles.state` or the `particles.dx` variables (see [Kernel loop explanation](explanation_kernelloop.md)). ``` From 244518d14316ad26934a13736ad60199206db2dd Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:07:30 +0200 Subject: [PATCH 09/15] Update tutorial_gsw_density --- docs/user_guide/examples/tutorial_gsw_density.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user_guide/examples/tutorial_gsw_density.ipynb b/docs/user_guide/examples/tutorial_gsw_density.ipynb index fe089bbe1..c94ce0549 100644 --- a/docs/user_guide/examples/tutorial_gsw_density.ipynb +++ b/docs/user_guide/examples/tutorial_gsw_density.ipynb @@ -84,8 +84,8 @@ "pset = parcels.ParticleSet(\n", " fieldset=fieldset,\n", " pclass=GSWParticle,\n", - " lon=[32],\n", - " lat=[-31],\n", + " x=[32],\n", + " y=[-31],\n", " z=[200],\n", ")" ] @@ -108,7 +108,7 @@ "def ParcelsGSW(particles, fieldset):\n", " particles.temp = fieldset.thetao[particles]\n", " particles.salt = fieldset.so[particles]\n", - " pressure = gsw.p_from_z(-particles.z, particles.lat)\n", + " pressure = gsw.p_from_z(-particles.z, particles.y)\n", " particles.density = gsw.density.rho(particles.salt, particles.temp, pressure)" ] }, @@ -158,7 +158,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.2" + "version": "3.14.4" } }, "nbformat": 4, From 38abb054432a95c589cc9af2b3884d69fbb69d4a Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 6 Jul 2026 16:27:47 +0200 Subject: [PATCH 10/15] Fixing links in documentation --- docs/getting_started/explanation_concepts.md | 2 +- docs/getting_started/tutorial_output.ipynb | 3 +-- docs/user_guide/examples/tutorial_dt_integrators.ipynb | 2 +- docs/user_guide/index.md | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/getting_started/explanation_concepts.md b/docs/getting_started/explanation_concepts.md index 0234af316..3533dac31 100644 --- a/docs/getting_started/explanation_concepts.md +++ b/docs/getting_started/explanation_concepts.md @@ -17,7 +17,7 @@ A Parcels simulation is generally built up from four different components: 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). 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. -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. +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. ```{figure} ../_static/concepts_diagram.png :alt: Parcels concepts diagram diff --git a/docs/getting_started/tutorial_output.ipynb b/docs/getting_started/tutorial_output.ipynb index 26627ff56..b5ddc6d55 100644 --- a/docs/getting_started/tutorial_output.ipynb +++ b/docs/getting_started/tutorial_output.ipynb @@ -17,8 +17,7 @@ "\n", "- [**Reading the output file**](#reading-the-output-file)\n", "- [**Trajectory data structure**](#trajectory-data-structure)\n", - "- [**Analysis**](#analysis)\n", - "- [**Plotting**](#plotting)\n", + "- [**Plotting**](#plotting-trajectories)\n", "- [**Animations**](#animations)\n", "\n", "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." diff --git a/docs/user_guide/examples/tutorial_dt_integrators.ipynb b/docs/user_guide/examples/tutorial_dt_integrators.ipynb index f92659b44..1ec7662d4 100644 --- a/docs/user_guide/examples/tutorial_dt_integrators.ipynb +++ b/docs/user_guide/examples/tutorial_dt_integrators.ipynb @@ -142,7 +142,7 @@ "source": [ "```{admonition} 🖥️ Spherical grids and unit converters\n", ":class: seealso\n", - "Our displacement occurs in units of longitude and latitde, but our velocity field is in m/s. That's why we have to convert the units of velocity from m/s to degrees/s. In Parcels, this is done automatically for `VectorFields` when using a `spherical` grid. See the [UnitConversion guide](./tutorial_unitconverters.ipynb) guide for more information.\n", + "Our displacement occurs in units of longitude and latitde, but our velocity field is in m/s. That's why we have to convert the units of velocity from m/s to degrees/s. In Parcels, this is done automatically for `VectorFields` when using a `spherical` grid. See the [Spherical grids and velocity conversion guide](./tutorial_velocityconversion.ipynb) guide for more information.\n", "```" ] }, diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index 6bc531efb..fea2ca158 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -1,6 +1,6 @@ # User guide -The core of our user guide is a series of Jupyter notebooks which document how to implement specific Lagrangian simulations with the flexibility of **Parcels**. Before diving into these advanced _how-to_ guides (🖥️), we suggest users get started by reading the explanation (📖) of the core concepts and trying the tutorials (🎓). For a description of the specific classes and functions, check out the [API reference](../reference/parcels/index.md). To discover other community resources, check out our [Community](../community/index.md) page. +The core of our user guide is a series of Jupyter notebooks which document how to implement specific Lagrangian simulations with the flexibility of **Parcels**. Before diving into these advanced _how-to_ guides (🖥️), we suggest users get started by reading the explanation (📖) of the core concepts and trying the tutorials (🎓). For a description of the specific classes and functions, check out the [API reference](../reference/parcels/index). To discover other community resources, check out our [Community](../community/index.md) page. ```{note} The tutorials written for Parcels v3 are currently being updated for Parcels v4. Shown below are only the notebooks which have been updated. From 81d29925fe29fef5a9da3f0dea46f0b467f32f24 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Mon, 6 Jul 2026 16:37:00 +0200 Subject: [PATCH 11/15] Fifth batch of tutorial cleanups after #2719 --- docs/getting_started/tutorial_quickstart.md | 2 +- docs/user_guide/examples/explanation_interpolation.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/getting_started/tutorial_quickstart.md b/docs/getting_started/tutorial_quickstart.md index d68d2cee6..5918a9ba3 100644 --- a/docs/getting_started/tutorial_quickstart.md +++ b/docs/getting_started/tutorial_quickstart.md @@ -103,7 +103,7 @@ And you can plot the particles on top of the temperature and velocity field: temperature = ds_fields.isel(time=0, depth=0).thetao.plot(cmap="magma") velocity = ds_fields.isel(time=0, depth=0).plot.quiver(x="longitude", y="latitude", u="uo", v="vo") ax = temperature.axes -ax.scatter(lon,lat,s=40,c='w',edgecolors='r'); +ax.scatter(lon, lat, s=40, c='w', edgecolors='r'); ``` ## Compute: `Kernel` diff --git a/docs/user_guide/examples/explanation_interpolation.md b/docs/user_guide/examples/explanation_interpolation.md index a154652c8..c59eb8529 100644 --- a/docs/user_guide/examples/explanation_interpolation.md +++ b/docs/user_guide/examples/explanation_interpolation.md @@ -4,7 +4,7 @@ Interpolation is an important functionality of Parcels. On this page we will dis implemented in **Parcels** and how to write a custom interpolator function. When we want to know the state of particles in an environmental field, such as temperature or velocity, -we _evaluate_ the `parcels.Field` at the particles real position in time and space (`t`, `z`, `lat`, `lon`). +we _evaluate_ the `parcels.Field` at the particles real position in time and space (`t`, `z`, `y`, `x`). In Parcels we can do this using square brackets: ``` @@ -14,13 +14,13 @@ particles.temperature = fieldset.temperature[particles] ````{note} The statement above is shorthand for ```python -particles.temperature = fieldset.temperature[particles.time, particles.z, particles.lat, particles.lon, particles] +particles.temperature = fieldset.temperature[particles.time, particles.z, particles.y, particles.x, particles] ``` where the `particles` argument at the end provides the grid search algorithm with a first guess for the element indices to interpolate on. If you want to sample at a different location, or time, that is not necessarily close to the particles location, you can use ```python -particles.temperature = fieldset.temperature[time, depth, lat, lon] +particles.temperature = fieldset.temperature[time, z, y, x] ``` but this could be slower for curvilinear and unstructured Grids because the entire grid needs to be searched. ```` @@ -53,7 +53,7 @@ If we want to create a custom interpolation method, we need to look at the inter The `particle_positions` dictionary contains: ``` -particle_positions = {"time", time, "z", z, "lat", lat, "lon", lon} +particle_positions = {"time", time, "z", z, "y", y, "x", x} ``` For structured (`X`) grids, the `grid_positions` dictionary contains: From 888245df8ef00749d25a8df46c687b492bfc9165 Mon Sep 17 00:00:00 2001 From: Vecko <36369090+VeckoTheGecko@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:58:12 +0200 Subject: [PATCH 12/15] Fix RTD --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f2d25e607..af33c564d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -14,6 +14,6 @@ build: build: html: - pixi run -e docs python ci/scripts/download-all-tutorial-datasets.py - - SPHINXOPTS='-T' BUILDDIR=$READTHEDOCS_OUTPUT/html pixi run docs-clean + - pixi run -e docs sphinx-build -T -b html docs $READTHEDOCS_OUTPUT/html sphinx: configuration: docs/conf.py From f4194a54744291c64a25488cad8f850eba1ce9d2 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Tue, 7 Jul 2026 08:12:38 +0200 Subject: [PATCH 13/15] Trying to fix RTD error with logos --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5d0af1b40..470b7b7b9 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -190,8 +190,8 @@ html_theme_options = { "logo": { "alt_text": "Parcels - Home", - "image_light": "logo-horo-transparent.png", - "image_dark": "logo-horo-transparent-dark.png", + "image_light": "_static/logo-horo-transparent.png", + "image_dark": "_static/logo-horo-transparent-dark.png", }, "use_edit_page_button": True, "github_url": "https://github.com/Parcels-code/parcels", From 27a86d111cc2efdda7e4998a6ebca0649cd4ecb6 Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Tue, 7 Jul 2026 09:28:04 +0200 Subject: [PATCH 14/15] Not downloading unused remote files To check if this speeds up RTD --- src/parcels/_datasets/remote.py | 69 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/parcels/_datasets/remote.py b/src/parcels/_datasets/remote.py index c8fb9e0ac..1bdb6b734 100644 --- a/src/parcels/_datasets/remote.py +++ b/src/parcels/_datasets/remote.py @@ -2,7 +2,6 @@ import enum import os from collections.abc import Callable -from datetime import datetime, timedelta from pathlib import Path from typing import Literal @@ -10,8 +9,6 @@ import xarray as xr from zarr.storage import ZipStore -from parcels._v3to4 import patch_dataset_v4_compat - # When modifying existing datasets in a backwards incompatible way, # make a new release in the repo and update the DATA_REPO_TAG to the new tag _DATA_REPO_TAG = "main" @@ -31,32 +28,32 @@ def _get_data_home() -> Path: # See instructions at https://github.com/Parcels-code/parcels-data for adding new datasets _ODIE_REGISTRY_FILES: list[str] = ( # These datasets are from v3 and before of Parcels, where we just used netcdf files - [ - "data/MovingEddies_data/moving_eddiesP.nc", - "data/MovingEddies_data/moving_eddiesU.nc", - "data/MovingEddies_data/moving_eddiesV.nc", - ] - + ["data/MITgcm_example_data/mitgcm_UV_surface_zonally_reentrant.nc"] + # [ + # "data/MovingEddies_data/moving_eddiesP.nc", + # "data/MovingEddies_data/moving_eddiesU.nc", + # "data/MovingEddies_data/moving_eddiesV.nc", + # ] + +["data/MITgcm_example_data/mitgcm_UV_surface_zonally_reentrant.nc"] + ["data/OFAM_example_data/OFAM_simple_U.nc", "data/OFAM_example_data/OFAM_simple_V.nc"] - + [ - "data/Peninsula_data/peninsulaU.nc", - "data/Peninsula_data/peninsulaV.nc", - "data/Peninsula_data/peninsulaP.nc", - "data/Peninsula_data/peninsulaT.nc", - ] - + [ - f"data/GlobCurrent_example_data/{date.strftime('%Y%m%d')}000000-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc" - for date in ([datetime(2002, 1, 1) + timedelta(days=x) for x in range(0, 365)] + [datetime(2003, 1, 1)]) - ] + # + [ + # "data/Peninsula_data/peninsulaU.nc", + # "data/Peninsula_data/peninsulaV.nc", + # "data/Peninsula_data/peninsulaP.nc", + # "data/Peninsula_data/peninsulaT.nc", + # ] + # + [ + # f"data/GlobCurrent_example_data/{date.strftime('%Y%m%d')}000000-GLOBCURRENT-L4-CUReul_hs-ALT_SUM-v02.0-fv01.0.nc" + # for date in ([datetime(2002, 1, 1) + timedelta(days=x) for x in range(0, 365)] + [datetime(2003, 1, 1)]) + # ] + [ "data/CopernicusMarine_data_for_Argo_tutorial/cmems_mod_glo_phy-cur_anfc_0.083deg_P1D-m_uo-vo_31.00E-33.00E_33.00S-30.00S_0.49-2225.08m_2024-01-01-2024-02-01.nc", "data/CopernicusMarine_data_for_Argo_tutorial/cmems_mod_glo_phy-so_anfc_0.083deg_P1D-m_so_31.00E-33.00E_33.00S-30.00S_0.49-2225.08m_2024-01-01-2024-02-01.nc", "data/CopernicusMarine_data_for_Argo_tutorial/cmems_mod_glo_phy-thetao_anfc_0.083deg_P1D-m_thetao_31.00E-33.00E_33.00S-30.00S_0.49-2225.08m_2024-01-01-2024-02-01.nc", ] - + [ - "data/DecayingMovingEddy_data/decaying_moving_eddyU.nc", - "data/DecayingMovingEddy_data/decaying_moving_eddyV.nc", - ] + # + [ + # "data/DecayingMovingEddy_data/decaying_moving_eddyU.nc", + # "data/DecayingMovingEddy_data/decaying_moving_eddyV.nc", + # ] + [ "data/FESOM_periodic_channel/fesom_channel.nc", "data/FESOM_periodic_channel/u.fesom_channel.nc", @@ -211,20 +208,20 @@ class _Purpose(enum.Enum): # The first here is a human readable key used to open datasets, with an object to open the datasets # fmt: off _DATASET_KEYS_AND_CONFIGS: dict[str, tuple[_ParcelsDataset, _Purpose]] = dict([ - ("MovingEddies_data/P", (_V3Dataset(_ODIE,"data/MovingEddies_data/moving_eddiesP.nc"), _Purpose.TUTORIAL)), - ("MovingEddies_data/U", (_V3Dataset(_ODIE,"data/MovingEddies_data/moving_eddiesU.nc"), _Purpose.TUTORIAL)), - ("MovingEddies_data/V", (_V3Dataset(_ODIE,"data/MovingEddies_data/moving_eddiesV.nc"), _Purpose.TUTORIAL)), + # ("MovingEddies_data/P", (_V3Dataset(_ODIE,"data/MovingEddies_data/moving_eddiesP.nc"), _Purpose.TUTORIAL)), + # ("MovingEddies_data/U", (_V3Dataset(_ODIE,"data/MovingEddies_data/moving_eddiesU.nc"), _Purpose.TUTORIAL)), + # ("MovingEddies_data/V", (_V3Dataset(_ODIE,"data/MovingEddies_data/moving_eddiesV.nc"), _Purpose.TUTORIAL)), ("MITgcm_example_data/mitgcm_UV_surface_zonally_reentrant", (_V3Dataset(_ODIE,"data/MITgcm_example_data/mitgcm_UV_surface_zonally_reentrant.nc"), _Purpose.TUTORIAL)), - ("OFAM_example_data/U", (_V3Dataset(_ODIE,"data/OFAM_example_data/OFAM_simple_U.nc"), _Purpose.TUTORIAL)), - ("OFAM_example_data/V", (_V3Dataset(_ODIE,"data/OFAM_example_data/OFAM_simple_V.nc"), _Purpose.TUTORIAL)), - ("Peninsula_data/U", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaU.nc"), _Purpose.TUTORIAL)), - ("Peninsula_data/V", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaV.nc"), _Purpose.TUTORIAL)), - ("Peninsula_data/P", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaP.nc"), _Purpose.TUTORIAL)), - ("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)), + # ("OFAM_example_data/U", (_V3Dataset(_ODIE,"data/OFAM_example_data/OFAM_simple_U.nc"), _Purpose.TUTORIAL)), + # ("OFAM_example_data/V", (_V3Dataset(_ODIE,"data/OFAM_example_data/OFAM_simple_V.nc"), _Purpose.TUTORIAL)), + # ("Peninsula_data/U", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaU.nc"), _Purpose.TUTORIAL)), + # ("Peninsula_data/V", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaV.nc"), _Purpose.TUTORIAL)), + # ("Peninsula_data/P", (_V3Dataset(_ODIE,"data/Peninsula_data/peninsulaP.nc"), _Purpose.TUTORIAL)), + # ("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)), - ("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)), + # ("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)), ("FESOM_periodic_channel/u.fesom_channel", (_V3Dataset(_ODIE,"data/FESOM_periodic_channel/u.fesom_channel.nc"), _Purpose.TUTORIAL)), ("FESOM_periodic_channel/v.fesom_channel", (_V3Dataset(_ODIE,"data/FESOM_periodic_channel/v.fesom_channel.nc"), _Purpose.TUTORIAL)), @@ -240,7 +237,7 @@ class _Purpose(enum.Enum): ("NemoNorthSeaORCA025-N006_data/W", (_V3Dataset(_ODIE,"data/NemoNorthSeaORCA025-N006_data/ORCA025-N06_200001*05W.nc"), _Purpose.TUTORIAL)), ("NemoNorthSeaORCA025-N006_data/mesh_mask", (_V3Dataset(_ODIE,"data/NemoNorthSeaORCA025-N006_data/coordinates.nc", _preprocess_drop_time_from_mesh1), _Purpose.TUTORIAL)), # "POPSouthernOcean_data/t.x1_SAMOC_flux.16900*.nc", # TODO v4: In v3 but should not be in v4 https://github.com/Parcels-code/Parcels/issues/2571#issuecomment-4214476973 - ("SWASH_data/data", (_V3Dataset(_ODIE,"data/SWASH_data/field_00655*.nc"), _Purpose.TUTORIAL)), + # ("SWASH_data/data", (_V3Dataset(_ODIE,"data/SWASH_data/field_00655*.nc"), _Purpose.TUTORIAL)), ("WOA_data/data", (_V3Dataset(_ODIE,"data/WOA_data/woa18_decav_t*_04.nc", _preprocess_set_cf_calendar_360_day), _Purpose.TUTORIAL)), ("CROCOidealized_data/data", (_V3Dataset(_ODIE,"data/CROCOidealized_data/CROCO_idealized.nc"), _Purpose.TUTORIAL)), ] + [ From 31cbf380afdce0888d34e958aeabc4dade028a6f Mon Sep 17 00:00:00 2001 From: Erik van Sebille Date: Tue, 7 Jul 2026 09:48:20 +0200 Subject: [PATCH 15/15] Fixing error in remote --- src/parcels/_datasets/remote.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parcels/_datasets/remote.py b/src/parcels/_datasets/remote.py index 1bdb6b734..f8fd1e2fe 100644 --- a/src/parcels/_datasets/remote.py +++ b/src/parcels/_datasets/remote.py @@ -33,7 +33,7 @@ def _get_data_home() -> Path: # "data/MovingEddies_data/moving_eddiesU.nc", # "data/MovingEddies_data/moving_eddiesV.nc", # ] - +["data/MITgcm_example_data/mitgcm_UV_surface_zonally_reentrant.nc"] + ["data/MITgcm_example_data/mitgcm_UV_surface_zonally_reentrant.nc"] + ["data/OFAM_example_data/OFAM_simple_U.nc", "data/OFAM_example_data/OFAM_simple_V.nc"] # + [ # "data/Peninsula_data/peninsulaU.nc",