Receptors#
A receptor is the place and time of a measurement. STILT releases particles there and follows them backward in time. Pick the receptor type that matches how your instrument samples the air.
Receptor type |
Use it for |
You give it |
|---|---|---|
An instrument sampling at one point, such as a tower inlet, a flask, or an aircraft or vehicle measurement |
|
|
An instrument measuring the vertical column above one spot, such as a ground-based spectrometer (TCCON, EM27/SUN) |
|
|
A slanted line of sight, such as a solar-tracking spectrometer or an off-nadir satellite sounding (see Slant Columns), or any set of release points at one time |
|
Every receptor also needs a time in UTC. A timezone-aware time is
converted to UTC. Altitudes are in metres above ground level. Pass
altitude_ref="msl" to give them above mean sea level instead.
PointReceptor#
This is the most common type. Use it for a fixed surface site or an airborne sample at one height.
import stilt
receptor = stilt.PointReceptor(
time="2023-07-15 18:00:00",
longitude=-111.848,
latitude=40.766,
altitude=10.0, # metres above ground
)
ColumnReceptor#
Use it when the measurement covers a range of heights above one spot.
HYSPLIT releases the particles evenly in height between bottom and
top.
receptor = stilt.ColumnReceptor(
time="2023-07-15 18:00:00",
longitude=-111.848,
latitude=40.766,
bottom=50.0,
top=6000.0,
altitude_ref="msl",
)
bottom must be below top. Above-ground altitudes cannot be negative.
A column footprint usually needs weighting by pressure, and often by the
instrument’s averaging kernel. Add the pressure_weighting and
averaging_kernel transforms for that (see Particle Weighting (Transforms)).
MultiPointReceptor#
Use it when the instrument looks along a slanted path, so the air it samples is spread out both horizontally and vertically. OCO-2 soundings and other satellite views with a full line-of-sight geometry are examples.
import numpy as np
receptor = stilt.MultiPointReceptor(
time="2023-07-15 18:00:00",
longitudes=np.linspace(-111.85, -111.80, 10),
latitudes=np.linspace(40.76, 40.80, 10),
altitudes=np.linspace(0.0, 8000.0, 10),
altitude_ref="msl",
)
The three arrays must have the same length. The receptor’s ID is built from a hash of its points, so listing the same points in another order gives the same ID. To build the points from viewing angles instead of by hand, see Slant Columns.
Warning
Every point must have its own horizontal location. HYSPLIT treats
consecutive release points at the same latitude and longitude as one
vertical line source, and releases particles only between the last two
heights. Stacking several heights at one location would silently drop all
but the top segment, so MultiPointReceptor raises
ValueError instead.
For a vertical column, use ColumnReceptor. For several separate
heights at one location, run one PointReceptor per height (a
distinct r_idx in the CSV) and combine the footprints afterward.
Loading receptors from a CSV file#
For more than a handful of receptors, keep them in a CSV file. The simplest form has one row per point receptor:
time,longitude,latitude,altitude
2023-07-15 18:00:00,-111.848,40.766,10
2023-07-15 19:00:00,-111.848,40.766,10
The column names from STILT-R also work. Use long or lon for
longitude, lati or lat for latitude, and zagl or zmsl for
altitude. A zmsl column means the altitudes are above sea level. To mix
the two in one file, add an altitude_ref column with agl or msl
on each row.
To build a column or multipoint receptor, give its rows the same r_idx
value. Rows that share an r_idx become one receptor, so they must also
share the same time:
time,longitude,latitude,altitude,r_idx
2023-07-15 18:00:00,-111.848,40.766,0,1
2023-07-15 18:00:00,-111.848,40.766,3000,1
Two rows at the same location make a ColumnReceptor. Rows at
different locations make a MultiPointReceptor.
Any other column is kept on the receptor in attrs. A scene or
site label you add to the file is then available when you select
results (see Load And Plot Results):
model.simulations.sel(where=lambda r: r.attrs["scene"] == "A")
For a receptor built from several rows, the first row’s labels are used.
A project’s receptors.csv is read automatically. To load another CSV,
use read_receptors(), or pass the path to the model:
receptors = stilt.read_receptors("my_receptors.csv")
model = stilt.Model(project="./my_project", receptors="my_receptors.csv")
Receptors you add to a project later are appended to its receptors.csv
using the file’s own columns (see Project Folders And Reruns).
How particles are released#
numpar is the number of particles released in each simulation.
A
PointReceptorreleases all of them from its one location.A
ColumnReceptorspreads them evenly frombottomtotop.A
MultiPointReceptordivides them among its locations. HYSPLIT rounds the count per location up and stops whennumparruns out, so the last location can get fewer. With 200 particles over 10 locations, the first nine get 21 each and the last gets 11.
Advanced: release heights for multipoint and slant receptors#
Vertical weighting needs each particle’s release height (xhgt). HYSPLIT
does not record which release point a particle came from, so PYSTILT works
it out from the first row HYSPLIT writes for the particle. With the bundled
HYSPLIT build, that row comes one timestep after release. By then the wind
has moved the particle a few hundred metres, which can be more than the
spacing between the points of a slant column.
PYSTILT recovers release heights in this order:
If the HYSPLIT build writes rows at release time (
t = 0), it uses them. The result is exact.If the release altitudes are all different, as they are in a slant column, it matches particles by height. Height changes much less than horizontal position over one timestep, so this is accurate to about 20 m.
Otherwise it matches by horizontal position. It warns when the release points are closer than 1 km, because the match cannot be trusted there.
A HYSPLIT change that writes t = 0 rows has been sent to NOAA ARL. Until
a published build includes it, you can point PYSTILT at your own build:
# config.yaml
exe_dir: /path/to/hysplit/exec # directory containing hycs_std
The setting is saved with each trajectory, so you can tell which build produced it.
Working with receptors in code#
The rest of this page is for code that handles any type of receptor.
Checking the type#
Use isinstance() to branch on the receptor type:
from stilt import ColumnReceptor, MultiPointReceptor, PointReceptor
if isinstance(receptor, PointReceptor):
print(f"Single point at {receptor.altitude} m")
elif isinstance(receptor, ColumnReceptor):
print(f"Column from {receptor.bottom} to {receptor.top} m")
elif isinstance(receptor, MultiPointReceptor):
print(f"Multi-point with {len(receptor)} locations")
All three are subclasses of Receptor.
Building a receptor from a list of points#
Receptor.from_points() takes a list of (longitude, latitude,
altitude) tuples and returns the matching type:
one tuple gives a
PointReceptor;two tuples at the same location give a
ColumnReceptor, withbottomandtopin the right order;anything else gives a
MultiPointReceptor.
r = stilt.Receptor.from_points(
time="2023-07-15 18:00:00",
points=[(-111.848, 40.766, 10.0)],
)
# PointReceptor