πŸ–₯️ AIFS tutorial#

Atmospheric dispersion studies are frequently driven by reanalysis or forecast products such as ERA5 or the (A)IFS. These products are often interpolated towards an A-grid, on which the U, V and W velocity components are defined on the same nodes. In this tutorial we advect particles with data from the AIFS forecast, a data-driven forecast product delivered by the European Centre for Medium-Range Weather Forecasts (ECMWF) and openly available from the ECMWF open data portal. Here we use a version that has been regridded onto an A-grid and provided by the MeteoCloud of the JΓΌlich Supercomputing Centre. The workflow described below also applies to reanalysis products such as ERA5.

import matplotlib.pyplot as plt
import numpy as np
import xarray as xr

import parcels
import parcels.tutorial

Building a meteorological dataset#

Below, we use the pre-computed AIFS data to build a meteorological dataset for Parcels. AIFS output is provided every six hours in separate NetCDF files. Note that in this dataset, we have already stacked these files along a new time dimension and assign each one the corresponding timestamp. You may need to do this yourself if you are using a different dataset. The xarray library can help you with this task.

ds = parcels.tutorial.open_dataset("AIFSmeteo/data")

Adapting the dataset dimensions#

The raw NetCDF dimensions (latitude, longitude and pressure) need a few adjustments before they can be used with Parcels. First, the latitude axis is stored in descending order, so we reverse both the coordinate and the data along it. Second, the pressure levels are stored in descending order as well; we reverse them so that pressure increases monotonically from the top of the atmosphere down to the surface. Pressure is also converted from Pa to hPa.

In 1644 Torricelli wrote β€œWe live submerged at the bottom of an ocean of air”. Because pressure decreases with height and increases as we descend towards the surface, it plays a similiar role as depth in an ocean model. Therefore we use pressure as the vertical (β€œdepth”) coordinate of the atmosphere. Concretely, this simply means renaming the plev coordinate to depth.

With pressure as the vertical coordinate, the vertical velocity is the pressure tendency \(\omega\).

Note

The AIFS data used here is provided on pressure levels, but reanalysis and forecast products are also available on other vertical coordinates with other vertical velocities. This has to be taken into account when preparing other datasets for atmospheric Parcels simulations.

# Reverse the latitude axis (and its data) to ascending order
ds = ds.isel(lat=slice(None, None, -1))

# Reverse the pressure levels so that pressure increases downwards
ds = ds.isel(plev=slice(None, None, -1))

# Convert pressure from Pa to hPa and the pressure tendency from Pa/s to hPa/s
ds["w"] = ds["w"] / 100.0
ds["plev"] = ds["plev"] / 100.0

# Rename plev to depth, since pressure acts as the vertical coordinate
ds = ds.rename({"plev": "depth"})

# Rename u, v, w to match Parcels' expected variable names
ds = ds.rename({"u": "U", "v": "V", "w": "W"})

Adding grid metadata#

Next we add the grid faces and the remaining properties that Parcels needs to interpret the AIFS data. The pressure, longitude and latitude faces are computed as the midpoints between neighbouring full levels.

We then declare the dataset conventions as CF-1.6, SGRID-0.3 and tag each coordinate with the axis it represents (X, Y, Z or T), so that Parcels knows how the coordinates map onto the model dimensions. From this information we build the grid topology, attach it to the u, v and w variables, and mark those velocities as being located on the nodes.

# Compute grid faces as the midpoints between neighbouring full levels
lon_face = 0.5 * (ds["lon"].values[:-1] + ds["lon"].values[1:])
ds["lon_face"] = xr.DataArray(lon_face, dims=["lon_face"])
lat_face = 0.5 * (ds["lat"].values[:-1] + ds["lat"].values[1:])
ds["lat_face"] = xr.DataArray(lat_face, dims=["lat_face"])
depth_face = 0.5 * (ds["depth"].values[:-1] + ds["depth"].values[1:])
ds["depth_face"] = xr.DataArray(depth_face, dims=["depth_face"])

# Declare the dataset conventions and the axis of each coordinate
ds.attrs["Conventions"] = "CF-1.6, SGRID-0.3"

ds["lon"].attrs["axis"] = "X"
ds["lat"].attrs["axis"] = "Y"
ds["depth"].attrs["axis"] = "Z"
ds["time"].attrs["axis"] = "T"

# Describe the grid topology
ds["grid"] = xr.DataArray(
    data=np.int32(0),
    attrs={
        "cf_role": "grid_topology",
        "topology_dimension": 2,
        "node_dimensions": "lon lat",
        "node_coordinates": "lon lat",
        "face_dimensions": "lon_face: lon (padding: none) lat_face: lat (padding: none)",
        "face_coordinates": "lon_face lat_face",
        "vertical_dimensions": "depth_face: depth (padding: none)",
    },
)

The resulting dataset looks as follows:

display(ds)
<xarray.Dataset> Size: 42MB
Dimensions:     (time: 5, depth: 13, lat: 201, lon: 201, lon_face: 200,
                 lat_face: 200, depth_face: 12)
Coordinates:
  * time        (time) datetime64[ns] 40B 2025-07-01 ... 2025-07-02
  * depth       (depth) float64 104B 50.0 100.0 150.0 ... 850.0 925.0 1e+03
  * lat         (lat) float64 2kB -70.0 -69.75 -69.5 ... -20.5 -20.25 -20.0
  * lon         (lon) float64 2kB 25.0 25.25 25.5 25.75 ... 74.5 74.75 75.0
  * lon_face    (lon_face) float64 2kB 25.12 25.38 25.62 ... 74.38 74.62 74.88
  * lat_face    (lat_face) float64 2kB -69.88 -69.62 -69.38 ... -20.38 -20.12
  * depth_face  (depth_face) float64 96B 75.0 125.0 175.0 ... 775.0 887.5 962.5
Data variables:
    U           (time, depth, lat, lon) float32 11MB dask.array<chunksize=(1, 13, 201, 201), meta=np.ndarray>
    t           (time, depth, lat, lon) float32 11MB dask.array<chunksize=(1, 13, 201, 201), meta=np.ndarray>
    W           (time, depth, lat, lon) float32 11MB dask.array<chunksize=(1, 13, 201, 201), meta=np.ndarray>
    V           (time, depth, lat, lon) float32 11MB dask.array<chunksize=(1, 13, 201, 201), meta=np.ndarray>
    grid        int32 4B 0
Attributes:
    CDI:          Climate Data Interface version 2.4.4 (https://mpimet.mpg.de...
    Conventions:  CF-1.6, SGRID-0.3
    institution:  European Centre for Medium-Range Weather Forecasts
    history:      Mon Sep 14 16:55:01 2026: cdo -s -O -f nc4 selname,u,v,w,t ...
    CDO:          Climate Data Operators version 2.4.4 (https://mpimet.mpg.de...

Creating the FieldSet#

With the dataset prepared, we can build the parcels.FieldSet using parcels.FieldSet.from_sgrid_conventions(). We pass mesh="spherical" because the velocities are given in local (spherical) coordinates. For the interpolation of the 2D and 3D fields we choose linear interpolation, which is the method most commonly used in atmospheric Lagrangian transport models.

We also speed up the I/O by using the ChunkCachedArray backend - see the squeezing performance out of Parcels tutorial for more details.

# Build the FieldSet and use linear interpolation
fieldset = parcels.FieldSet.from_sgrid_conventions(ds)
fieldset.to_chunk_cached_arrays()
fieldset.describe()
| Name   | Type        |   Grid number | Interp method / value   | Parcels backend   |
|:-------|:------------|--------------:|:------------------------|:------------------|
| U      | Field       |             0 | XLinear(...)            | ChunkCachedArray  |
| V      | Field       |             0 | XLinear(...)            | ChunkCachedArray  |
| W      | Field       |             0 | XLinear(...)            | ChunkCachedArray  |
| t      | Field       |             0 | XLinear(...)            | ChunkCachedArray  |
| UV     | VectorField |             0 | XLinear_Velocity(...)   | -                 |
| UVW    | VectorField |             0 | XLinear_Velocity(...)   | -                 |

mesh: SphericalMesh(radius=6366707.019493707)
time interval: (np.datetime64('2025-07-01T00:00:00.000000000'), np.datetime64('2025-07-02T00:00:00.000000000'))

Creating the initial particles#

We initialize the particles on a three-dimensional grid with NumPy’s meshgrid, releasing them at three pressure levels (900 hPa, 500 hPa and 100 hPa) over a small region of interest. The parcels.ParticleSet is then created with Parcels’ own constructor.

pressure, lat, lon = np.meshgrid(
    np.array([900, 500, 100]),
    np.linspace(-65, -25, 10),
    np.linspace(30, 70, 10),
    indexing="ij",
)
pset = parcels.ParticleSet(fieldset=fieldset, x=lon, y=lat, z=pressure)

Running the simulation#

The simulation is run with ParticleSet.execute, which applies a list of kernels over a given runtime (here one day) with a fixed integration time step (here 900 s).

First we need a three-dimensional advection kernel; we use Runge-Kutta 2 through parcels.kernels.AdvectionRK2_3D. Second, because we simulate trajectories within a regional lat-lon box, we define a kernel that removes particles leaving the domain. The DeleteOutOfBounds kernel below deletes any parcel whose status is ErrorOutOfBounds. For global simulations one would instead add a periodic boundary kernel that wraps longitude at the dateline and handle the lower and upper pressure boundaries, for example by reflecting particles or letting them slide along the maximum and minimum pressure levels.

We store the output in a ParticleFile; here we write a Parquet file with an output frequency of 15 minutes.

# Define a kernel that deletes particles that leave the domain
def DeleteOutOfBounds(particles, fieldset):
    """Delete particles that have left the domain boundaries."""
    out_of_bounds = particles.state == parcels.StatusCode.ErrorOutOfBounds
    particles[out_of_bounds].state = parcels.StatusCode.Delete


outfile = parcels.ParticleFile(
    "atmosphere_parcels.parquet", outputdt=np.timedelta64(15, "m"), mode="w"
)

pset.execute(
    [parcels.kernels.AdvectionRK2_3D, DeleteOutOfBounds],
    runtime=np.timedelta64(1, "D"),
    dt=np.timedelta64(900, "s"),
    output_file=outfile,
)

Hide code cell output

INFO: Output files are stored in atmosphere_parcels.parquet
   0%|          | [00:00<?, ?it/s]
Integration time: 2025-07-01T00:00:00   0%|          | [00:00<?, ?it/s]
Integration time: 2025-07-01T00:00:00   0%|          | [00:00<?, ?it/s]
Integration time: 2025-07-01T00:00:00   1%|          | [00:00<00:19, 4302.22it/s]
Integration time: 2025-07-01T00:15:00   1%|          | [00:00<00:19, 4302.22it/s]
Integration time: 2025-07-01T00:30:00   2%|▏         | [00:00<00:19, 4302.22it/s]
Integration time: 2025-07-01T00:45:00   3%|β–Ž         | [00:00<00:19, 4302.22it/s]
Integration time: 2025-07-01T01:00:00   4%|▍         | [00:00<00:19, 4302.22it/s]
Integration time: 2025-07-01T01:15:00   5%|β–Œ         | [00:00<00:19, 4302.22it/s]
Integration time: 2025-07-01T01:30:00   6%|β–‹         | [00:00<00:18, 4302.22it/s]
Integration time: 2025-07-01T01:45:00   7%|β–‹         | [00:00<00:18, 4302.22it/s]
Integration time: 2025-07-01T02:00:00   8%|β–Š         | [00:00<00:18, 4302.22it/s]
Integration time: 2025-07-01T02:15:00   9%|β–‰         | [00:00<00:18, 4302.22it/s]
Integration time: 2025-07-01T02:15:00  10%|β–ˆ         | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T02:30:00  10%|β–ˆ         | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T02:45:00  11%|β–ˆβ–        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T03:00:00  12%|β–ˆβ–Ž        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T03:15:00  14%|β–ˆβ–Ž        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T03:30:00  15%|β–ˆβ–        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T03:45:00  16%|β–ˆβ–Œ        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T04:00:00  17%|β–ˆβ–‹        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T04:15:00  18%|β–ˆβ–Š        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T04:30:00  19%|β–ˆβ–‰        | [00:00<00:02, 33928.12it/s]
Integration time: 2025-07-01T04:30:00  20%|β–ˆβ–‰        | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T04:45:00  20%|β–ˆβ–‰        | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T05:00:00  21%|β–ˆβ–ˆ        | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T05:15:00  22%|β–ˆβ–ˆβ–       | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T05:30:00  23%|β–ˆβ–ˆβ–Ž       | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T05:45:00  24%|β–ˆβ–ˆβ–       | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T06:00:00  25%|β–ˆβ–ˆβ–Œ       | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T06:15:00  26%|β–ˆβ–ˆβ–Œ       | [00:00<00:01, 49326.93it/s]
Integration time: 2025-07-01T06:15:00  27%|β–ˆβ–ˆβ–‹       | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T06:30:00  27%|β–ˆβ–ˆβ–‹       | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T06:45:00  28%|β–ˆβ–ˆβ–Š       | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T07:00:00  29%|β–ˆβ–ˆβ–‰       | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T07:15:00  30%|β–ˆβ–ˆβ–ˆ       | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T07:30:00  31%|β–ˆβ–ˆβ–ˆβ–      | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T07:45:00  32%|β–ˆβ–ˆβ–ˆβ–      | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T08:00:00  33%|β–ˆβ–ˆβ–ˆβ–Ž      | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T08:15:00  34%|β–ˆβ–ˆβ–ˆβ–      | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T08:30:00  35%|β–ˆβ–ˆβ–ˆβ–Œ      | [00:00<00:01, 50496.97it/s]
Integration time: 2025-07-01T08:30:00  36%|β–ˆβ–ˆβ–ˆβ–‹      | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T08:45:00  36%|β–ˆβ–ˆβ–ˆβ–‹      | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T09:00:00  38%|β–ˆβ–ˆβ–ˆβ–Š      | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T09:15:00  39%|β–ˆβ–ˆβ–ˆβ–Š      | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T09:30:00  40%|β–ˆβ–ˆβ–ˆβ–‰      | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T09:45:00  41%|β–ˆβ–ˆβ–ˆβ–ˆ      | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T10:00:00  42%|β–ˆβ–ˆβ–ˆβ–ˆβ–     | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T10:15:00  43%|β–ˆβ–ˆβ–ˆβ–ˆβ–Ž     | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T10:30:00  44%|β–ˆβ–ˆβ–ˆβ–ˆβ–     | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T10:45:00  45%|β–ˆβ–ˆβ–ˆβ–ˆβ–     | [00:00<00:00, 57914.18it/s]
Integration time: 2025-07-01T10:45:00  46%|β–ˆβ–ˆβ–ˆβ–ˆβ–Œ     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T11:00:00  46%|β–ˆβ–ˆβ–ˆβ–ˆβ–Œ     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T11:15:00  47%|β–ˆβ–ˆβ–ˆβ–ˆβ–‹     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T11:30:00  48%|β–ˆβ–ˆβ–ˆβ–ˆβ–Š     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T11:45:00  49%|β–ˆβ–ˆβ–ˆβ–ˆβ–‰     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T12:00:00  50%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T12:15:00  51%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ     | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T12:30:00  52%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–    | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T12:45:00  53%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž    | [00:00<00:00, 63217.29it/s]
Integration time: 2025-07-01T12:45:00  54%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T13:00:00  54%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T13:15:00  55%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T13:30:00  56%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T13:45:00  57%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T14:00:00  58%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T14:15:00  59%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T14:30:00  60%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ    | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T14:45:00  61%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–   | [00:00<00:00, 60838.67it/s]
Integration time: 2025-07-01T15:00:00  62%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž   | [00:01<00:00, 60838.67it/s]
Integration time: 2025-07-01T15:00:00  64%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T15:15:00  64%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T15:30:00  65%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T15:45:00  66%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T16:00:00  67%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T16:15:00  68%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T16:30:00  69%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T16:45:00  70%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T17:00:00  71%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T17:15:00  72%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–  | [00:01<00:00, 65363.46it/s]
Integration time: 2025-07-01T17:15:00  73%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T17:30:00  73%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T17:45:00  74%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T18:00:00  75%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T18:15:00  76%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T18:30:00  77%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T18:45:00  78%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T19:00:00  79%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T19:15:00  80%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  | [00:01<00:00, 68176.25it/s]
Integration time: 2025-07-01T19:15:00  81%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ– | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T19:30:00  81%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ– | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T19:45:00  82%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ– | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T20:00:00  83%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T20:15:00  84%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ– | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T20:30:00  85%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T20:45:00  86%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹ | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T21:00:00  88%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T21:15:00  89%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T21:30:00  90%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰ | [00:01<00:00, 64276.48it/s]
Integration time: 2025-07-01T21:30:00  91%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T21:45:00  91%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T22:00:00  92%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T22:15:00  93%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T22:30:00  94%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T22:45:00  95%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T23:00:00  96%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T23:15:00  97%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T23:30:00  98%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T23:45:00  99%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰| [00:01<00:00, 68000.31it/s]
Integration time: 2025-07-01T23:45:00 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| [00:01<00:00, 70645.72it/s]
Integration time: 2025-07-01T23:45:00 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| [00:01<00:00, 59664.69it/s]

Plotting the trajectories#

Finally, we study the trajectories with a Matplotlib plotting routine. We use parcels.read_particlefile() to load the output and plot the particle positions in three dimensions for the full day. The trajectories show an anticyclone at the surface and fast zonal winds in the higher layers.

from mpl_toolkits.mplot3d import Axes3D

df = parcels.read_particlefile("atmosphere_parcels.parquet")

fig = plt.figure(figsize=(13, 8))
ax = plt.axes(projection="3d")
ax.view_init(azim=-145, elev=25)
for traj in df.partition_by("particle_id", maintain_order=True):
    ax.plot3D(traj["x"], traj["y"], traj["z"], ".-")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_zlabel("Pressure (mbar)")
ax.set_zlim(df["z"].max(), 0)
plt.show()
../../_images/c12ff33f507a8f366920da845841f3b14ea7acf2b9392c375eb5e1258f634883.png