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 diff --git a/docs/conf.py b/docs/conf.py index 7dab1c785..470b7b7b9 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 @@ -191,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", diff --git a/docs/getting_started/explanation_concepts.md b/docs/getting_started/explanation_concepts.md index 0283299ff..3533dac31 100644 --- a/docs/getting_started/explanation_concepts.md +++ b/docs/getting_started/explanation_concepts.md @@ -13,11 +13,11 @@ 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. -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 @@ -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](../user_guide/examples/explanation_kernelloop.md) 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 a5e70a42e..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." @@ -77,11 +76,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\"))" @@ -145,9 +144,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", "```" ] }, @@ -263,7 +262,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 +282,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 +306,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 +331,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 +353,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 +438,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 +463,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 +480,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", @@ -502,7 +501,7 @@ "metadata": { "celltoolbar": "Metagegevens bewerken", "kernelspec": { - "display_name": "docs", + "display_name": "Parcels:docs (3.14.6)", "language": "python", "name": "python3" }, @@ -516,7 +515,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..5918a9ba3 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 ) ``` @@ -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` @@ -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/explanation_interpolation.md b/docs/user_guide/examples/explanation_interpolation.md index 94b70c4b3..c59eb8529 100644 --- a/docs/user_guide/examples/explanation_interpolation.md +++ b/docs/user_guide/examples/explanation_interpolation.md @@ -3,12 +3,8 @@ 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`). +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: ``` @@ -18,15 +14,15 @@ 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 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,20 +32,28 @@ 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: ``` -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: @@ -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. 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_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_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, 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..1ec7662d4 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", @@ -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", "```" ] }, @@ -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_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, 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 612903234..8edefd70d 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.__class__.__name__)\n", " xv, yv = np.meshgrid(np.linspace(0, 1, 8), np.linspace(0, 1, 8))\n", - " pset[p_interp.__name__] = parcels.ParticleSet(\n", - " fieldset, pclass=SampleParticle, lon=xv.flatten(), lat=yv.flatten()\n", + " pset[p_interp.__class__.__name__] = parcels.ParticleSet(\n", + " fieldset, pclass=SampleParticle, x=xv.flatten(), y=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", @@ -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", @@ -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" ] }, @@ -202,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", @@ -343,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", @@ -353,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", @@ -407,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", @@ -446,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", @@ -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, 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_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 48dae7ba8..b6baf6e9e 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\")" ] }, { @@ -368,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", @@ -407,10 +400,10 @@ " 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", + "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", @@ -438,14 +431,11 @@ "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)" + "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", + " )" ] }, { @@ -464,24 +454,24 @@ "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", " 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", - " 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", @@ -506,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", @@ -550,7 +540,7 @@ ], "metadata": { "kernelspec": { - "display_name": "docs", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -564,7 +554,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_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 879ec72e7..63c477400 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)" ] @@ -294,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", @@ -312,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\")" ] }, { @@ -356,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", @@ -375,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", @@ -413,7 +412,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Parcels:test (3.14.6)", "language": "python", "name": "python3" }, @@ -427,7 +426,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.3" + "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)). ``` 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, 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. diff --git a/src/parcels/_datasets/remote.py b/src/parcels/_datasets/remote.py index c8fb9e0ac..f8fd1e2fe 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)), ] + [