A Python implementation of the STILT Lagrangian atmospheric transport model.
Project description
PYSTILT
PYSTILT is a Python implementation of the STILT Lagrangian atmospheric transport model. It runs backward trajectories with HYSPLIT and computes receptor footprints that map where upwind surface fluxes influence a measurement.
The project is in alpha and focused on a unified execution model that works for one-off runs, large batch runs, and streaming queue workers.
Status
PYSTILT is alpha software (v0.1.0a1). No backward compatibility guarantees before v1.0.
The core transport is stable: HYSPLIT execution, trajectory and footprint generation, numerical R-STILT parity, and the local and SLURM execution paths are all exercised by the test suite. The public API may change while the package settles.
Choose a workflow
- One-off transport runs for local analysis and notebooks:
use
Model.run()orstilt run. - Queue-backed batch or service runs for HPC/cloud execution:
use
Model.register_pending(),stilt register,stilt pull-worker, andstilt servewith a PostgreSQL-backed queue index configured viaPYSTILT_DB_URL. - Observation-driven workflows for science-facing code:
use
stilt.observationsto turn normalized observations intoReceptorobjects before feeding them into the same runtime.
Roadmap
PYSTILT draws design and science inspiration from two sister projects: X-STILT for column and satellite science workflows, and stiltctl for cloud-native execution patterns. The tables below track what has been absorbed and what remains in scope. See the full roadmap for more details.
Execution and orchestration (from stiltctl)
| Feature | Status |
|---|---|
Pull-mode queue workers (stilt pull-worker) |
Implemented |
Long-lived streaming mode (stilt serve) |
Implemented |
| PostgreSQL-backed simulation registry | Implemented |
| Scene-based submission grouping | Implemented |
| Thin CLI → Model → worker call path | Implemented |
| Kubernetes worker deployment | Partial |
| Cloud object store outputs (GCS, S3) | In scope |
Column and satellite science (from X-STILT)
Full X-STILT feature parity is not a goal. PYSTILT absorbs X-STILT's observation-layer design and column-weighting concepts without trying to replicate every script.
| Feature | Status |
|---|---|
stilt.observations layer (Observation, Scene, sensor families) |
Implemented |
| Column receptor support | Implemented |
| Vertical operator particle transforms (AK / pressure weighting) | Implemented |
| First-order lifetime decay transform | Implemented |
| Declarative per-footprint transforms in config | Implemented |
| Slant-column receptor support | In scope (pending HYSPLIT validation) |
| Additional transform types | In scope |
| Specific sensor adapters (OCO-2/3, TROPOMI, TCCON) | Deferred |
| Inventory coupling and background estimation | Deferred |
Installation
pip install pystilt
For Slurm, Kubernetes, projections, plotting, and cloud object stores:
pip install "pystilt[complete]"
Quickstart: one-off run
Define a receptor, configure meteorology and footprint grid, then run:
import pandas as pd
import stilt
receptor = stilt.Receptor(
time=pd.Timestamp("2023-07-15 18:00", tz="UTC"),
latitude=40.766,
longitude=-111.848,
altitude=10,
)
model = stilt.Model(
project="./my_project",
receptors=[receptor],
config=stilt.ModelConfig(
n_hours=-24,
numpar=100,
mets={
"hrrr": stilt.MetConfig(
directory="/data/hrrr",
file_format="hrrr_%Y%m%d.arl",
file_tres="1h",
)
},
footprints={
"default": stilt.FootprintConfig(
grid=stilt.Grid(
xmin=-113.0,
xmax=-110.5,
ymin=40.0,
ymax=42.0,
xres=0.01,
yres=0.01,
)
)
},
),
)
handle = model.run()
handle.wait()
sim = list(model.simulations.values())[0]
traj = sim.trajectories
foot = sim.get_footprint("default")
Quickstart: queue/service runtime
# Queue workers require a PostgreSQL-backed queue index.
export PYSTILT_DB_URL=postgresql://user:pass@host:5432/pystilt
# Initialize project files (config.yaml and receptors.csv)
stilt init ./my_project
# Run with local workers (blocks until complete)
stilt run ./my_project --backend local --n-workers 8
# Register one grouped scene submission
stilt register ./my_project --scene-id daily_2026_04_13
# Drain queue from worker processes (batch mode)
stilt pull-worker ./my_project
# Long-lived queue workers (streaming mode)
stilt serve ./my_project
# Check project status
stilt status ./my_project
The same queue model is available in Python:
import stilt
from stilt.execution import pull_simulations
model = stilt.Model(project="./my_project")
model.register_pending(scene_id="daily_2026_04_14")
pull_simulations(model, follow=False) # batch mode
print(model.status(scene_id="daily_2026_04_14"))
In all modes, workers claim simulations from the PostgreSQL-backed index and write terminal state directly back to the same registry.
Quickstart: observation layer
PYSTILT also includes a narrow science-facing layer in stilt.observations.
It is designed to sit above Receptor, not replace the transport/runtime core.
import stilt
from stilt.observations import PointSensor
sensor = PointSensor(name="tower", supported_species=("co2",))
observations = [
sensor.make_observation(
time="2023-01-01 12:00:00",
latitude=40.77,
longitude=-111.85,
altitude=30.0,
observation_id="tower-001",
)
]
[scene] = sensor.group_scenes(observations)
receptors = [sensor.build_receptor(obs) for obs in scene.observations]
model = stilt.Model(project="./my_project") # existing project config on disk
model.register_pending(receptors=receptors, scene_id=scene.id)
Direct Observation(...) construction is still available when you already
have a separate product-specific normalization layer. The sensor helper just
keeps the common path less repetitive.
This layer currently focuses on:
- normalized
ObservationandSceneobjects - geometry/operator metadata
- generic point/column sensor families
- observation-to-receptor conversion
See docs/advanced/observations.rst for the intended workflow boundary.
Declarative transforms
Per-footprint transforms can be declared in config instead of embedded as ad hoc callbacks.
footprints:
column:
grid: slv
transforms:
- kind: vertical_operator
mode: ak_pwf
levels: [0.0, 1000.0, 2000.0]
values: [0.2, 0.5, 0.3]
coordinate: xhgt
- kind: first_order_lifetime
lifetime_hours: 4.0
time_column: time
time_unit: min
The built-in transform interface is intentionally small:
- vertical operator weighting
- first-order lifetime decay
- runtime typed transforms for more advanced Python workflows
Accessing results
import pandas as pd
for sim in model.simulations.values():
traj = sim.trajectories
foot = sim.get_footprint("default")
# Load footprints across all matching simulations
footprints = model.footprints["default"].load(
time_range=("2023-01-01", "2023-01-31")
)
coords = [(-111.9, 40.7), (-111.8, 40.8)]
time_bins = pd.interval_range(
start=pd.Timestamp("2023-01-01 00:00", tz="UTC"),
end=pd.Timestamp("2023-01-02 00:00", tz="UTC"),
freq="1h",
)
for footprint in footprints:
hourly = footprint.aggregate(coords=coords, time_bins=time_bins)
If a footprint is tracked as complete-empty, no NetCDF file is expected for that footprint.
The model APIs treat it as a successful terminal outcome while skipping missing file loads.
R-STILT parity
PYSTILT footprints match the uataq/stilt R
implementation on numerical values at rtol=1e-7 per cell, validated by
end-to-end fidelity scenarios against a pinned upstream commit.
NetCDF output is not byte-compatible with R-STILT;
it should be read as generic CF-1.8 NetCDF.
See STILT-R.md for more details.
Documentation
Full documentation is available at https://jmineau.github.io/PYSTILT/
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
James Mineau - jmineau
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pystilt-0.1.0a1.tar.gz.
File metadata
- Download URL: pystilt-0.1.0a1.tar.gz
- Upload date:
- Size: 1.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0e6aecf320ecc6756701535098fda9ccef144310d0cd2739f795aea16e79188
|
|
| MD5 |
5c67e4af7665d09cfcea13747d628239
|
|
| BLAKE2b-256 |
c6f44be88fbf101a7ea6e08b1c6b4a4c4afb4ce069a15f967acc7e4fca520aef
|
Provenance
The following attestation bundles were made for pystilt-0.1.0a1.tar.gz:
Publisher:
publish.yml on jmineau/PYSTILT
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pystilt-0.1.0a1.tar.gz -
Subject digest:
f0e6aecf320ecc6756701535098fda9ccef144310d0cd2739f795aea16e79188 - Sigstore transparency entry: 1518660851
- Sigstore integration time:
-
Permalink:
jmineau/PYSTILT@86397075f663cd483466b9a0862e900c81553429 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/jmineau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@86397075f663cd483466b9a0862e900c81553429 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pystilt-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: pystilt-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 1.8 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cba59f2c1a5e64511ae2259ba0881a8222cd52a458a3e23c0eb7e6c61aebf6ea
|
|
| MD5 |
3c9c76addcf4c2aa64cf0b7592f0b83f
|
|
| BLAKE2b-256 |
3d01dca11c8e2181335295d9f90081c1b99076d1a6008842d5998f32df9089ac
|
Provenance
The following attestation bundles were made for pystilt-0.1.0a1-py3-none-any.whl:
Publisher:
publish.yml on jmineau/PYSTILT
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pystilt-0.1.0a1-py3-none-any.whl -
Subject digest:
cba59f2c1a5e64511ae2259ba0881a8222cd52a458a3e23c0eb7e6c61aebf6ea - Sigstore transparency entry: 1518660893
- Sigstore integration time:
-
Permalink:
jmineau/PYSTILT@86397075f663cd483466b9a0862e900c81553429 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/jmineau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@86397075f663cd483466b9a0862e900c81553429 -
Trigger Event:
push
-
Statement type: