Development#
Contributions are welcome: bug reports, documentation fixes, and code. See CONTRIBUTING.md for conventions, and open issues on GitHub.
Use of AI coding agents#
This project is developed with the help of AI coding agents, directed and reviewed by the maintainer, who owns the design and the science.
If you contribute with an agent, AGENTS.md at the repository root is the
orientation file it should read.
Set up a development environment#
PYSTILT uses uv and just. From a clone of the repository:
uv sync --group dev
Common tasks:
just test # run the test suite
just quality-check # lint, type-check, and test
just build-docs # build this documentation into docs/_build/html
STILT-R parity#
PYSTILT is tested against a fixed commit of STILT-R. This section says what “matches STILT-R” covers and how to move to a newer STILT-R commit.
Pinned upstream commit#
Field |
Value |
|---|---|
Upstream |
|
Pinned SHA |
|
Pin location |
|
Last verified |
2026-06-25 |
When the fidelity tests pass, PYSTILT matches STILT-R at this commit. It
says nothing about STILT-R’s current main branch.
What matches#
PYSTILT’s footprints match STILT-R’s for the 20 fidelity scenarios in tests/fixtures/r_stilt_reference.py, within these tolerances:
Quantity |
Tolerance |
|---|---|
Footprint, per cell |
|
Footprint total |
|
Footprint peak |
|
Number of nonzero cells |
identical |
Intermediate tables (interpolation, rtime, gridding) |
|
Trajectory positions and |
|
Grid coordinates, longitude/latitude |
|
Grid coordinates, projected |
|
The scenarios cover:
point, column, and three-location multipoint receptors
6 h and 24 h backward runs, and a 6 h forward run
the hyper-near-field (HNF) plume correction on (12 scenarios) and off
smooth_factorof 0, 0.5, 1, and 20.01° and 0.05° longitude/latitude grids and a UTM grid
a 2° box, a receptor in the domain’s corner, a receptor at the edge of the met grid, and a small 0.1° domain that particles leave early
receptor heights above ground, above sea level (
kmsl=1), and at 0.5 mwinter (stable) and summer (convective) HRRR
hourly footprints and
time_integrate=Truea wind-error run (
siguverr=1m/s)
The synthetic tests in tests/r_stilt/test_footprint_synth.py feed hand-made particle tables to both implementations to test single code paths: one Gaussian particle, cells on the grid boundary, the dateline, a global grid, and the latitude scaling of the kernel width.
What does not match#
The NetCDF layout. STILT-R writes dimensions
(x, y, time)with a fill value of-1. PYSTILT writes CF-1.8 NetCDF with dimensions(time, lat, lon). Read PYSTILT files as ordinary CF NetCDF. Tools that expect STILT-R’s layout will not read them.Forward runs with the HNF correction. STILT-R accumulates the plume from the far end of a forward trajectory back toward the release point, so its plume is widest at release. PYSTILT accumulates outward from the release point. The two
footvalues differ on purpose, andtest_forward_hnf_foot_intentionally_differs_from_rguards the difference.Untested inputs. Runs longer than 24 h backward, latitudes above 80°, and grids finer than 0.001° have not been compared.
Other STILT-R commits. Nothing detects upstream changes to
calc_footprint.r,permute.f90, orcalc_trajectory.r. Bump the pinned SHA by hand (below).
Large-scale comparison#
Outside CI, PYSTILT was compared with STILT-R on 200 receptors at the WBB
tower in the Salt Lake Valley, 2016 to 2024 (35 m above ground, 1000
particles, 24 h backward, HRRR, krand=2 and seed=42, the same v5.1.0
hycs_std on both sides). For each receptor, the PYSTILT trajectory was
compared with a separate STILT-R calc_trajectory run, and the PYSTILT
footprint with STILT-R’s calc_footprint of the same particles, at 0.01°,
0.05°, and 0.1°. All 200 receptors matched.
Quantity |
Agreement (worst of 200 receptors) |
|---|---|
Trajectory, per particle |
absolute |
Footprint, per-cell relative difference |
median |
Footprint nonzero cells |
identical (for example 250,644 cells at 0.01°) |
Footprint total |
relative |
The trajectories are identical, so the footprint differences come only from
the order in which floating-point sums are added. The relative difference is
the same for small cells (under 0.01 % of the peak) as for large ones.
Footprints are stored as float32, whose precision is about 1.2e-7, so
the two outputs agree to the precision the file can hold. The mass-weighted
relative difference, which is what an inversion sums over, is also about
2e-8.
Reading the same met files on both sides over the full date range needs the
find_met_files fix in the pinned STILT-R commit. HRRR archives can hold
the same file both at the top level (as a symlink) and in a YYYY/MM/
subdirectory, and older STILT-R listed it twice.
Moving to a newer STILT-R commit#
Change
STILT_R_SHAin.github/workflows/tests.yml.Run the fidelity tests locally:
git clone https://github.com/uataq/stilt stilt-r-src git -C stilt-r-src checkout <new-sha> STILT_R_DIR=$PWD/stilt-r-src uv run pytest tests/r_stilt/ -v -m integration
If every scenario passes at the current tolerances, commit the change and update “Last verified” above.
If a scenario fails, diff
calc_footprint.r,permute.f90, andcalc_trajectory.rbetween the two commits to find the upstream change. Then do one of the following:make the same change in PYSTILT,
loosen that scenario’s tolerance, with a comment saying why, or
stay on the old commit.
Files to diff on every bump#
These STILT-R files determine the numbers PYSTILT is compared against:
r/src/calc_footprint.r: footprint kernel and griddingr/src/calc_trajectory.r: the HYSPLIT wrapper, random seed, andkrandr/src/permute.f90: the Fortran loop that adds each particle’s kernel to the grid
The R helper scripts in tests/fixtures/r_helpers/ call these files for
the synthetic tests. They may need updating if a function signature changes
upstream.