PyAERMOD Quick Start Guide¶
Installation¶
For visualization, geospatial export, or the GUI, install extras:
5-Minute Example¶
Create a complete AERMOD input file in just a few lines:
from pyaermod import (
AERMODProject,
ControlPathway,
SourcePathway,
PointSource,
ReceptorPathway,
CartesianGrid,
MeteorologyPathway,
OutputPathway,
PollutantType,
)
# 1. Define the control parameters
control = ControlPathway(
title_one="My First AERMOD Run",
pollutant_id=PollutantType.PM25,
averaging_periods=["ANNUAL", "24"],
terrain_type="FLAT",
)
# 2. Add your emission source(s)
sources = SourcePathway()
sources.add_source(PointSource(
source_id="STACK1",
x_coord=500.0, # meters (UTM)
y_coord=500.0, # meters (UTM)
base_elevation=10.0,
stack_height=50.0, # meters
stack_temp=400.0, # Kelvin
exit_velocity=15.0, # m/s
stack_diameter=2.0, # meters
emission_rate=1.5, # g/s
))
# 3. Define receptor grid
receptors = ReceptorPathway()
receptors.add_cartesian_grid(
CartesianGrid.from_bounds(
x_min=0, x_max=2000,
y_min=0, y_max=2000,
spacing=100, # 100m grid spacing
)
)
# 4. Specify meteorology files
meteorology = MeteorologyPathway(
surface_file="met_data.sfc",
profile_file="met_data.pfl",
)
# 5. Configure output
output = OutputPathway(
receptor_table=True,
max_table=True,
summary_file="results.sum",
)
# 6. Create project and write input file
project = AERMODProject(
control=control,
sources=sources,
receptors=receptors,
meteorology=meteorology,
output=output,
)
project.write("my_first_run.inp")
That's it! You now have a valid AERMOD input file.
Running AERMOD from Python¶
from pyaermod.runner import run_aermod
result = run_aermod("my_first_run.inp")
print(f"Success: {result.success}")
print(f"Runtime: {result.runtime_seconds:.1f}s")
print(f"Output: {result.output_file}")
Note
You need the AERMOD executable installed separately. Download it from EPA SCRAM.
Parsing Output¶
from pyaermod.output_parser import parse_aermod_output
results = parse_aermod_output(result.output_file)
df = results.get_concentrations("ANNUAL")
print(f"Max concentration: {df['concentration'].max():.4g}")
Visualization¶
from pyaermod.visualization import AERMODVisualizer
viz = AERMODVisualizer(results)
fig = viz.plot_contours(averaging_period="ANNUAL")
Common Patterns¶
Multiple Sources¶
from pyaermod import SourcePathway, PointSource
sources = SourcePathway()
stack_data = [
(100.0, 200.0, 1.0),
(300.0, 400.0, 2.5),
(500.0, 600.0, 0.8),
]
for i, (x, y, rate) in enumerate(stack_data):
sources.add_source(PointSource(
source_id=f"STACK{i + 1}",
x_coord=x,
y_coord=y,
stack_height=50.0,
stack_temp=400.0,
exit_velocity=15.0,
stack_diameter=2.0,
emission_rate=rate,
))
Polar Grid Around Facility¶
from pyaermod import PolarGrid, ReceptorPathway
receptors = ReceptorPathway()
receptors.add_polar_grid(PolarGrid(
x_origin=500.0,
y_origin=500.0,
dist_init=100.0, # start 100m from origin
dist_num=20, # 20 distance rings
dist_delta=100.0, # 100m spacing
dir_init=0.0, # start at north
dir_num=36, # 36 directions (every 10 degrees)
dir_delta=10.0,
))
Building Downwash¶
from pyaermod import PointSource
stack = PointSource(
source_id="STACK1",
x_coord=0.0,
y_coord=0.0,
stack_height=30.0,
stack_temp=400.0,
exit_velocity=15.0,
stack_diameter=2.0,
emission_rate=1.5,
building_height=25.0,
building_width=40.0,
building_length=60.0,
building_x_offset=0.0,
building_y_offset=20.0,
)
Building downwash (PRIME) is also supported on AreaSource and VolumeSource
with the same five fields. For direction-dependent building downwash, use the
BPIP module or the GUI's built-in BPIP calculator.
Background Concentrations¶
Add ambient background levels to account for existing pollution:
from pyaermod import (
SourcePathway, BackgroundConcentration, BackgroundSector,
)
sources = SourcePathway()
# ... add sources ...
# Uniform background — one value applied to every hour
sources.background = BackgroundConcentration(uniform_value=12.0) # ug/m3
# Or per-averaging-period
sources.background = BackgroundConcentration(
period_values={"ANNUAL": 12.0, "24": 35.0},
)
# Or sector-dependent — define starting directions + per-sector values
sources.background = BackgroundConcentration(
sectors=[
BackgroundSector(sector_id=1, start_direction=0.0),
BackgroundSector(sector_id=2, start_direction=90.0),
],
sector_values={(1, "ANNUAL"): 12.0, (1, "24"): 35.0,
(2, "ANNUAL"): 8.0, (2, "24"): 25.0},
)
Deposition Modeling¶
Enable dry or wet deposition for any source type:
from pyaermod import (
PointSource, DepositionMethod, GasDepositionParams,
ParticleDepositionParams,
)
stack = PointSource(
source_id="STACK1",
x_coord=500.0, y_coord=500.0,
stack_height=50.0, stack_temp=400.0,
exit_velocity=15.0, stack_diameter=2.0,
emission_rate=1.5,
deposition_method=DepositionMethod.GASDEPVD,
gas_deposition=GasDepositionParams(
diffusivity=0.126, # cm^2/s (SO2 reference value)
alpha_r=10.0, # dimensionless
reactivity=8.0, # dimensionless
henry_constant=1.2e-3, # M/atm (alternative to dry_dep_velocity)
),
)
Set OutputPathway(output_type="DEPOS") to get deposition flux instead of
concentration in the output.
NO2 Chemistry Options¶
For NO2 modeling, configure Tier 2/3 chemistry:
from pyaermod import (
ControlPathway, PollutantType, ChemistryMethod,
ChemistryOptions, OzoneData,
)
control = ControlPathway(
title_one="NO2 Tier 3 Analysis",
pollutant_id=PollutantType.NO2,
averaging_periods=["1", "ANNUAL"],
chemistry=ChemistryOptions(
method=ChemistryMethod.PVMRM,
default_no2_ratio=0.5,
ozone_data=OzoneData(uniform_value=40.0), # ppb
),
)
Source Groups¶
Define custom source groups for separate impact analysis:
from pyaermod import SourcePathway, SourceGroupDefinition
sources = SourcePathway()
# ... add sources STACK1, STACK2, STACK3 ...
sources.group_definitions = [
SourceGroupDefinition(
group_name="BOILERS",
member_source_ids=["STACK1", "STACK2"],
description="Boiler stacks",
),
SourceGroupDefinition(
group_name="PROCESS",
member_source_ids=["STACK3"],
description="Process vents",
),
]
EVENT Processing¶
Run AERMOD in event mode for specific date/receptor combinations:
from pyaermod import EventPathway, EventPeriod
events = EventPathway(events=[
EventPeriod(
event_name="MAXDAY",
source_group="ALL",
averaging_period="24",
start_date="24031501",
end_date="24031524",
),
])
project = AERMODProject(control, sources, receptors, meteorology, output)
project.write("facility.inp", event_filename="facility.evn")
Parameter Sweep¶
from pyaermod import *
for rate in [1.0, 2.0, 3.0, 4.0, 5.0]:
control = ControlPathway(
title_one=f"Emission Rate = {rate} g/s",
pollutant_id=PollutantType.SO2,
averaging_periods=["1", "24", "ANNUAL"],
)
sources = SourcePathway()
sources.add_source(PointSource(
source_id="STACK1",
x_coord=500.0,
y_coord=500.0,
stack_height=50.0,
stack_temp=400.0,
exit_velocity=15.0,
stack_diameter=2.0,
emission_rate=rate,
))
project = AERMODProject(control, sources, receptors, meteorology, output)
project.write(f"scenario_rate_{rate}.inp")
POSTFILE Parsing¶
from pyaermod.postfile import read_postfile
# Auto-detects text (PLOT) or binary (UNFORM) format
result = read_postfile("postfile.pst")
print(f"Max concentration: {result.max_concentration:.4g}")
print(f"Location: {result.max_location}")
# Step through timesteps
for date in result.data["date"].unique():
ts = result.get_timestep(date)
print(f"{date}: max={ts['concentration'].max():.4g}")
Binary POSTFILE with Deposition¶
Binary (UNFORM) POSTFILEs from deposition runs store concentration, dry deposition,
and wet deposition as contiguous blocks of N floats each (3N total per record).
from pyaermod.postfile import read_postfile
# Explicit deposition flag
result = read_postfile("depo_post.pst", has_deposition=True)
df = result.to_dataframe()
print(df[["x", "y", "concentration", "dry_depo", "wet_depo"]])
# Auto-detect: provide num_receptors, parser checks if 3N floats
result = read_postfile("post.pst", num_receptors=50)
# If 150 floats found, deposition is auto-detected
Validation¶
The input generator includes built-in validation:
- Required parameters are enforced
- Parameter types are checked (floats, ints, strings)
- Enum values are validated (pollutant types, terrain types)
- Coordinate systems are consistent
from pyaermod.validator import Validator
validator = Validator()
errors = validator.validate(project)
for error in errors:
print(error)
What's Available Now¶
| Feature | Module |
|---|---|
| 10 source types (POINT, AREA, AREACIRC, AREAPOLY, VOLUME, LINE, RLINE, RLINEXT, BUOYLINE, OPENPIT) | input_generator |
| Background concentrations (uniform, period, sector) | input_generator |
| Deposition modeling (dry, wet, combined) | input_generator |
| NO2/SO2 chemistry (OLM, PVMRM, ARM2, GRSM) | input_generator |
| Source group management and per-group output | input_generator |
| EVENT processing for date/receptor analysis | input_generator |
| Input file validation | validator |
| AERMOD execution | runner |
| Output parsing to DataFrames | output_parser |
| POSTFILE parsing (text and binary, with deposition) | postfile |
| Contour plots, Folium maps | visualization |
| 3D surfaces, wind roses, animations | advanced_viz |
| AERMET meteorological preprocessing | aermet |
| AERMAP terrain preprocessing | aermap |
| DEM download and terrain pipeline | terrain |
| Coordinate transforms, GIS export | geospatial |
| Building downwash (BPIP) for POINT, AREA, VOLUME | bpip |
| Interactive 7-page web GUI | gui |
AERMOD Keywords Supported¶
Based on AERMOD version 24142:
Control Pathway (CO)¶
STARTING, FINISHED, TITLEONE, TITLETWO, MODELOPT, AVERTIME, POLLUTID, RUNORNOT, ELEVUNIT, FLAGPOLE, HALFLIFE, DCAYCOEF, URBANOPT, LOW_WIND, EVENTFIL, O3VALUES, OZONEFIL, NO2RATIO, NOXFIL
Source Pathway (SO)¶
LOCATION (POINT, AREA, AREACIRC, AREAPOLY, VOLUME, LINE, RLINE, RLINEXT, BUOYLINE, OPENPIT), SRCPARAM, BUILDHGT, BUILDWID, BUILDLEN, XBADJ, YBADJ, SRCGROUP, URBANSRC, APTS_CAP, BLPINPUT, BLPGROUP, RBARRIER, RDEPRESS, BACKGRND, BGSECTOR, GASDEPOS, PARTDIAM, MASSFRAX, PARTDENS, METHOD_2, DEPRESS
Receptor Pathway (RE)¶
GRIDCART (with ELEV/HILL terrain support), GRIDPOLR (with ELEV/HILL terrain support), DISCCART, ELEVUNIT
Meteorology Pathway (ME)¶
SURFFILE, PROFFILE, SURFDATA, UAIRDATA, STARTEND, WDROTATE
Output Pathway (OU)¶
RECTABLE, MAXTABLE, DAYTABLE, SUMMFILE, MAXIFILE, PLOTFILE, POSTFILE
Event Pathway (EV)¶
EVENTPER, EVENTLOC
Testing & Validation¶
PyAERMOD includes comprehensive testing validated against official EPA data:
- 1600+ unit and integration tests with 97%+ code coverage
- 315 EPA test cases parsed from official AERMOD v24142 output files (LOVETT, FLATELEV, TESTPART, etc.)
- End-to-end pipeline tests chaining input generation through visualization
Next Steps¶
- Try the GUI User Guide for a no-code modeling experience
- Browse the API Reference for detailed module documentation
- Check the examples/ directory for runnable scripts and Jupyter notebooks
Getting Help¶
- Check the examples/ directory for more usage patterns
- Review the Architecture document for design details
- Browse the API Reference for module documentation
- Read AERMOD documentation from EPA SCRAM
License¶
MIT