pyaermod 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyaermod-1.0.0/CHANGELOG.md +121 -0
- pyaermod-1.0.0/CONTRIBUTING.md +139 -0
- pyaermod-1.0.0/LICENSE +29 -0
- pyaermod-1.0.0/MANIFEST.in +16 -0
- pyaermod-1.0.0/PKG-INFO +229 -0
- pyaermod-1.0.0/README.md +161 -0
- pyaermod-1.0.0/docs/api/advanced_viz.md +3 -0
- pyaermod-1.0.0/docs/api/aermap.md +3 -0
- pyaermod-1.0.0/docs/api/aermet.md +3 -0
- pyaermod-1.0.0/docs/api/bpip.md +3 -0
- pyaermod-1.0.0/docs/api/geospatial.md +3 -0
- pyaermod-1.0.0/docs/api/gui.md +3 -0
- pyaermod-1.0.0/docs/api/index.md +52 -0
- pyaermod-1.0.0/docs/api/input_generator.md +3 -0
- pyaermod-1.0.0/docs/api/output_parser.md +3 -0
- pyaermod-1.0.0/docs/api/postfile.md +3 -0
- pyaermod-1.0.0/docs/api/runner.md +3 -0
- pyaermod-1.0.0/docs/api/terrain.md +3 -0
- pyaermod-1.0.0/docs/api/validator.md +3 -0
- pyaermod-1.0.0/docs/api/visualization.md +3 -0
- pyaermod-1.0.0/docs/architecture.md +772 -0
- pyaermod-1.0.0/docs/gui-guide.md +387 -0
- pyaermod-1.0.0/docs/index.md +55 -0
- pyaermod-1.0.0/docs/quickstart.md +488 -0
- pyaermod-1.0.0/examples/area_sources.py +361 -0
- pyaermod-1.0.0/examples/bpip.py +174 -0
- pyaermod-1.0.0/examples/deposition_modeling.py +416 -0
- pyaermod-1.0.0/examples/end_to_end.py +523 -0
- pyaermod-1.0.0/examples/line_sources.py +540 -0
- pyaermod-1.0.0/examples/notebooks/01_Getting_Started.ipynb +427 -0
- pyaermod-1.0.0/examples/notebooks/02_Point_Source_Modeling.ipynb +589 -0
- pyaermod-1.0.0/examples/notebooks/03_Area_Source_Modeling.ipynb +738 -0
- pyaermod-1.0.0/examples/notebooks/04_Parameter_Sweeps.ipynb +619 -0
- pyaermod-1.0.0/examples/notebooks/05_Visualization.ipynb +605 -0
- pyaermod-1.0.0/examples/notebooks/06_Postfile_Analysis.ipynb +267 -0
- pyaermod-1.0.0/examples/notebooks/07_Advanced_Features.ipynb +305 -0
- pyaermod-1.0.0/examples/volume_sources.py +426 -0
- pyaermod-1.0.0/pyproject.toml +132 -0
- pyaermod-1.0.0/setup.cfg +4 -0
- pyaermod-1.0.0/setup.py +10 -0
- pyaermod-1.0.0/src/pyaermod/__init__.py +339 -0
- pyaermod-1.0.0/src/pyaermod/advanced_viz.py +475 -0
- pyaermod-1.0.0/src/pyaermod/aermap.py +382 -0
- pyaermod-1.0.0/src/pyaermod/aermet.py +391 -0
- pyaermod-1.0.0/src/pyaermod/bpip.py +263 -0
- pyaermod-1.0.0/src/pyaermod/geospatial.py +971 -0
- pyaermod-1.0.0/src/pyaermod/gui.py +3124 -0
- pyaermod-1.0.0/src/pyaermod/input_generator.py +1933 -0
- pyaermod-1.0.0/src/pyaermod/output_parser.py +731 -0
- pyaermod-1.0.0/src/pyaermod/postfile.py +760 -0
- pyaermod-1.0.0/src/pyaermod/runner.py +528 -0
- pyaermod-1.0.0/src/pyaermod/terrain.py +804 -0
- pyaermod-1.0.0/src/pyaermod/validator.py +1072 -0
- pyaermod-1.0.0/src/pyaermod/visualization.py +480 -0
- pyaermod-1.0.0/src/pyaermod.egg-info/PKG-INFO +229 -0
- pyaermod-1.0.0/src/pyaermod.egg-info/SOURCES.txt +77 -0
- pyaermod-1.0.0/src/pyaermod.egg-info/dependency_links.txt +1 -0
- pyaermod-1.0.0/src/pyaermod.egg-info/entry_points.txt +2 -0
- pyaermod-1.0.0/src/pyaermod.egg-info/requires.txt +48 -0
- pyaermod-1.0.0/src/pyaermod.egg-info/top_level.txt +1 -0
- pyaermod-1.0.0/tests/__init__.py +3 -0
- pyaermod-1.0.0/tests/conftest.py +112 -0
- pyaermod-1.0.0/tests/test_advanced_features.py +1006 -0
- pyaermod-1.0.0/tests/test_aermap.py +308 -0
- pyaermod-1.0.0/tests/test_aermet.py +446 -0
- pyaermod-1.0.0/tests/test_bpip.py +441 -0
- pyaermod-1.0.0/tests/test_epa_cases.py +920 -0
- pyaermod-1.0.0/tests/test_geospatial.py +922 -0
- pyaermod-1.0.0/tests/test_gui.py +1728 -0
- pyaermod-1.0.0/tests/test_init.py +96 -0
- pyaermod-1.0.0/tests/test_input_generator.py +1219 -0
- pyaermod-1.0.0/tests/test_integration.py +1199 -0
- pyaermod-1.0.0/tests/test_output_parser.py +621 -0
- pyaermod-1.0.0/tests/test_postfile.py +1142 -0
- pyaermod-1.0.0/tests/test_property_based.py +326 -0
- pyaermod-1.0.0/tests/test_runner.py +509 -0
- pyaermod-1.0.0/tests/test_terrain.py +771 -0
- pyaermod-1.0.0/tests/test_validator.py +1562 -0
- pyaermod-1.0.0/tests/test_visualization.py +570 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to PyAERMOD will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0] - 2026-02-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### Source Types
|
|
15
|
+
- **AreaSource** — rectangular area sources with rotation angle
|
|
16
|
+
- **AreaCircSource** — circular area sources with configurable vertex count
|
|
17
|
+
- **AreaPolySource** — irregular polygonal area sources
|
|
18
|
+
- **VolumeSource** — 3D emission volumes with initial dispersion
|
|
19
|
+
- **LineSource** — general linear sources (conveyors, pipelines)
|
|
20
|
+
- **RLineSource** — roadway-specific sources with mobile source physics
|
|
21
|
+
- **RLineExtSource** — extended roadway with per-endpoint elevations, optional barriers and road depression
|
|
22
|
+
- **BuoyLineSource** / **BuoyLineSegment** — buoyant line source groups with BLPINPUT/BLPGROUP
|
|
23
|
+
- **OpenPitSource** — open pit mine/quarry sources
|
|
24
|
+
|
|
25
|
+
#### Modules
|
|
26
|
+
- **Validator** (`pyaermod.validator`) — configuration validation for all 5 AERMOD pathways with cross-field checks
|
|
27
|
+
- **BPIP** (`pyaermod.bpip`) — building downwash / BPIP integration with 36-direction building parameters
|
|
28
|
+
- **AERMET** (`pyaermod.aermet`) — meteorological preprocessor input generation (Stages 1-3)
|
|
29
|
+
- **AERMAP** (`pyaermod.aermap`) — terrain preprocessor input generation with `from_aermod_project()` bridge
|
|
30
|
+
- **POSTFILE** (`pyaermod.postfile`) — POSTFILE output parser with timestep/receptor queries, auto-detection of text (PLOT) and binary (UNFORM) formats
|
|
31
|
+
- **Geospatial** (`pyaermod.geospatial`) — coordinate transforms (UTM/WGS84), GeoDataFrame creation, contour generation, GeoTIFF/GeoPackage/Shapefile/GeoJSON export
|
|
32
|
+
- **Terrain** (`pyaermod.terrain`) — DEM tile download from USGS TNM, AERMAP runner, output parser, elevation update pipeline
|
|
33
|
+
- **GUI** (`pyaermod.gui`) — 7-page Streamlit web application for interactive AERMOD workflow
|
|
34
|
+
|
|
35
|
+
#### Background Concentrations
|
|
36
|
+
- `BackgroundConcentration` and `BackgroundSector` dataclasses for ambient background levels
|
|
37
|
+
- Three modes: uniform value, period-specific values, or sector-dependent concentrations
|
|
38
|
+
- `SourcePathway.background` field generating `BACKGRND` and `BGSECTOR` keywords
|
|
39
|
+
|
|
40
|
+
#### Deposition Modeling
|
|
41
|
+
- `DepositionMethod` enum (`DRYDPLT`, `WETDPLT`, `GASDEPVD`, `GASDEPDF`)
|
|
42
|
+
- `GasDepositionParams` and `ParticleDepositionParams` for gas and particle deposition settings
|
|
43
|
+
- Deposition fields added to all 10 source types with shared `_deposition_to_aermod_lines()` helper
|
|
44
|
+
- `OutputPathway.output_type` for selecting concentration vs. deposition output
|
|
45
|
+
|
|
46
|
+
#### EVENT Processing
|
|
47
|
+
- `EventPeriod` and `EventPathway` dataclasses for event-based analysis
|
|
48
|
+
- `ControlPathway.eventfil` for linking event file
|
|
49
|
+
- `AERMODProject.write(event_filename=...)` generates EV pathway with `EVENTPER` records
|
|
50
|
+
|
|
51
|
+
#### NO2 / SO2 Chemistry Options
|
|
52
|
+
- `ChemistryMethod` enum: OLM, PVMRM, ARM2, GRSM
|
|
53
|
+
- `ChemistryOptions` dataclass with method, default NO2/NOx ratio, and ozone data
|
|
54
|
+
- `OzoneData` dataclass supporting ozone file, uniform value, or sector-specific values
|
|
55
|
+
- `ControlPathway.chemistry` field generating `MODELOPT`, `O3VALUES`, `OZONEFIL`, `NOXFIL` keywords
|
|
56
|
+
- Per-source `no2_ratio` field on `PointSource`
|
|
57
|
+
|
|
58
|
+
#### Source Group Management
|
|
59
|
+
- `SourceGroupDefinition` dataclass with group name, member source IDs, and description
|
|
60
|
+
- `SourcePathway.group_definitions` generating `SRCGROUP` keywords
|
|
61
|
+
- Per-group PLOTFILE output via `OutputPathway.plot_file_groups`
|
|
62
|
+
|
|
63
|
+
#### Building Downwash Expansion
|
|
64
|
+
- Building downwash (PRIME) fields extended from `PointSource` to also support `AreaSource` and `VolumeSource`
|
|
65
|
+
- `_building_downwash_lines()` and `_set_building_from_bpip()` module-level helpers shared across source types
|
|
66
|
+
- Terrain grid elevations via `CartesianGrid.terrain_elevations` and `PolarGrid.terrain_elevations`
|
|
67
|
+
|
|
68
|
+
#### Binary POSTFILE Deposition
|
|
69
|
+
- `UnformattedPostfileParser` now handles deposition records with `has_deposition` parameter (auto-detect or explicit)
|
|
70
|
+
- Parses 3N floats into concentration, dry deposition, and wet deposition columns
|
|
71
|
+
|
|
72
|
+
#### GUI Enhancements
|
|
73
|
+
- **ProjectSerializer**: JSON save/load for complete session state with round-trip fidelity
|
|
74
|
+
- **AreaCirc/AreaPoly forms** in SourceFormFactory
|
|
75
|
+
- **BPIP integration**: building forms, BPIP calculator wired to point, area, and volume sources
|
|
76
|
+
- **AERMAP elevation import**: 5th tab in receptor editor for terrain elevation upload
|
|
77
|
+
- **AERMET configuration**: dual-mode meteorology page (existing files vs. 3-stage AERMET config)
|
|
78
|
+
- **POSTFILE viewer**: 4th tab in Results Viewer with timestep slider, receptor time-series, animation GIF
|
|
79
|
+
- **Chemistry Options UI**: NO2 chemistry configuration with method, ozone data, and NOx file inputs
|
|
80
|
+
- **Source Groups UI**: create/delete source groups, per-group PLOTFILE checkboxes
|
|
81
|
+
- **Statistics helpers**: cross-period summary table, ranked receptor table, model complexity indicator
|
|
82
|
+
- **Export format detection**: dynamic format list based on installed optional dependencies
|
|
83
|
+
|
|
84
|
+
#### Testing & Quality
|
|
85
|
+
- 1166 tests across 18 test files, 95% code coverage
|
|
86
|
+
- 315 EPA v24142 integration tests parsing official test case outputs
|
|
87
|
+
- End-to-end mock pipeline tests (input generation → output parsing → visualization → postfile)
|
|
88
|
+
- `conftest.py` with shared fixtures for all test files
|
|
89
|
+
- Property-based testing with Hypothesis strategies for source types
|
|
90
|
+
- `ruff` linting (replaced flake8) with comprehensive rule set
|
|
91
|
+
- `.pre-commit-config.yaml` for automated lint on commit
|
|
92
|
+
- Performance benchmarks in `benchmarks/` directory
|
|
93
|
+
|
|
94
|
+
#### Documentation
|
|
95
|
+
- 7 Jupyter tutorial notebooks (Getting Started through Advanced Features)
|
|
96
|
+
- 7 example scripts (area sources, volume sources, line sources, BPIP, chemistry, deposition, end-to-end)
|
|
97
|
+
- MkDocs documentation site with Material theme and mkdocstrings API reference
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
- **Package layout**: moved from flat root modules to `src/pyaermod/` package structure
|
|
101
|
+
- **Imports**: `from pyaermod.input_generator import ...` (was `from pyaermod_input_generator import ...`)
|
|
102
|
+
- **Python**: minimum version raised to 3.11 (was 3.8) — required by NumPy 2.1+, SciPy 1.14+, Pandas 2.3+
|
|
103
|
+
- Updated `setup.py` extras: added `[geo]`, `[gui]`, `[terrain]`, `[all]` dependency groups
|
|
104
|
+
- CI matrix runs Python 3.11, 3.12, 3.13 with GDAL system dependencies
|
|
105
|
+
|
|
106
|
+
## [0.1.0] - 2026-02-04
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
- **PointSource** with full stack parameters and building downwash (PRIME) support
|
|
110
|
+
- **Receptor grids**: Cartesian, polar, and discrete receptors
|
|
111
|
+
- **AERMOD input generation** for all 5 pathways (CO, SO, RE, ME, OU)
|
|
112
|
+
- **Output parser**: parse `.out` files to pandas DataFrames, extract metadata, find max concentrations
|
|
113
|
+
- **Visualization**: contour plots (matplotlib), interactive maps (folium)
|
|
114
|
+
- **Runner**: `AERMODRunner` with subprocess execution, `BatchRunner` for parallel processing
|
|
115
|
+
- Project setup: `setup.py`, MIT license, `.gitignore`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
[Unreleased]: https://github.com/atmmod/pyaermod/compare/v1.0.0...HEAD
|
|
120
|
+
[1.0.0]: https://github.com/atmmod/pyaermod/compare/v0.1.0...v1.0.0
|
|
121
|
+
[0.1.0]: https://github.com/atmmod/pyaermod/releases/tag/v0.1.0
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Contributing to PyAERMOD
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to PyAERMOD! This guide covers
|
|
4
|
+
the development workflow, coding standards, and submission process.
|
|
5
|
+
|
|
6
|
+
## Development Setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# Clone the repository
|
|
10
|
+
git clone https://github.com/atmmod/pyaermod.git
|
|
11
|
+
cd pyaermod
|
|
12
|
+
|
|
13
|
+
# Install in editable mode with all dev dependencies
|
|
14
|
+
pip install -e ".[dev,all]"
|
|
15
|
+
|
|
16
|
+
# Install pre-commit hooks
|
|
17
|
+
pre-commit install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### System Dependencies
|
|
21
|
+
|
|
22
|
+
For geospatial features (GeoTIFF export, coordinate transforms), you need
|
|
23
|
+
GDAL:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Ubuntu / Debian
|
|
27
|
+
sudo apt-get install libgdal-dev gdal-bin
|
|
28
|
+
|
|
29
|
+
# macOS (Homebrew)
|
|
30
|
+
brew install gdal
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Running Tests
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Run full test suite with coverage
|
|
37
|
+
pytest
|
|
38
|
+
|
|
39
|
+
# Run a specific test file
|
|
40
|
+
pytest tests/test_input_generator.py
|
|
41
|
+
|
|
42
|
+
# Run tests matching a keyword
|
|
43
|
+
pytest -k "test_point_source"
|
|
44
|
+
|
|
45
|
+
# Skip slow property-based tests
|
|
46
|
+
pytest -m "not slow"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The project targets **89%+ code coverage**. New features should include
|
|
50
|
+
tests that maintain or improve this threshold.
|
|
51
|
+
|
|
52
|
+
## Code Style
|
|
53
|
+
|
|
54
|
+
PyAERMOD uses [ruff](https://docs.astral.sh/ruff/) for linting and import
|
|
55
|
+
sorting. Configuration is in `pyproject.toml`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Check for lint errors
|
|
59
|
+
ruff check src/ tests/
|
|
60
|
+
|
|
61
|
+
# Auto-fix where possible
|
|
62
|
+
ruff check --fix src/ tests/
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Key style rules:
|
|
66
|
+
|
|
67
|
+
- **Line length**: 120 characters
|
|
68
|
+
- **Python**: 3.11+ (type hints use `typing` module style, not `X | Y`)
|
|
69
|
+
- **Imports**: sorted by ruff/isort with `pyaermod` as first-party
|
|
70
|
+
- **Docstrings**: NumPy style
|
|
71
|
+
- **Dataclasses**: used throughout for configuration objects
|
|
72
|
+
|
|
73
|
+
## Project Layout
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
src/pyaermod/ # Package source (src layout)
|
|
77
|
+
tests/ # Test files (test_*.py)
|
|
78
|
+
docs/ # MkDocs documentation
|
|
79
|
+
examples/ # Example scripts and notebooks
|
|
80
|
+
benchmarks/ # Performance benchmarks
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Key Patterns
|
|
84
|
+
|
|
85
|
+
- **Source types** are dataclasses with a `to_aermod_input() -> str` method
|
|
86
|
+
- **Validation** uses `isinstance` dispatch in `validator.py`
|
|
87
|
+
- **Optional dependencies** use `try/except ImportError` with `HAS_*` flags
|
|
88
|
+
and `_require_*()` guard functions
|
|
89
|
+
- **GUI** uses Streamlit with `st.session_state` for state management
|
|
90
|
+
|
|
91
|
+
## Adding a New Source Type
|
|
92
|
+
|
|
93
|
+
1. Define the dataclass in `src/pyaermod/input_generator.py` following
|
|
94
|
+
existing patterns (e.g., `PointSource`)
|
|
95
|
+
2. Implement `to_aermod_input()` to generate valid AERMOD keywords
|
|
96
|
+
3. Add validation rules in `src/pyaermod/validator.py`
|
|
97
|
+
4. Add the type to `SourceFormFactory.SOURCE_TYPES` in `gui.py`
|
|
98
|
+
5. Update `__init__.py` exports and `__all__`
|
|
99
|
+
6. Add tests in `tests/test_input_generator.py` and `tests/test_gui.py`
|
|
100
|
+
7. Update documentation in `docs/quickstart.md`
|
|
101
|
+
|
|
102
|
+
## Submitting Changes
|
|
103
|
+
|
|
104
|
+
1. **Fork** the repository and create a feature branch
|
|
105
|
+
2. **Write tests** for any new functionality
|
|
106
|
+
3. **Run the full test suite**: `pytest`
|
|
107
|
+
4. **Run the linter**: `ruff check src/ tests/`
|
|
108
|
+
5. **Update documentation** if you changed user-facing behavior
|
|
109
|
+
6. **Open a pull request** against `main` with a clear description
|
|
110
|
+
|
|
111
|
+
### Commit Messages
|
|
112
|
+
|
|
113
|
+
Use clear, descriptive commit messages:
|
|
114
|
+
|
|
115
|
+
- `Add RLINEXT source type with barrier support`
|
|
116
|
+
- `Fix POSTFILE binary parser for big-endian systems`
|
|
117
|
+
- `Update quickstart guide with deposition examples`
|
|
118
|
+
|
|
119
|
+
### Pull Request Checklist
|
|
120
|
+
|
|
121
|
+
- [ ] Tests pass (`pytest`)
|
|
122
|
+
- [ ] Linter passes (`ruff check src/ tests/`)
|
|
123
|
+
- [ ] New features have tests
|
|
124
|
+
- [ ] Documentation updated (if applicable)
|
|
125
|
+
- [ ] CHANGELOG.md updated under `[Unreleased]`
|
|
126
|
+
|
|
127
|
+
## Reporting Issues
|
|
128
|
+
|
|
129
|
+
Please include:
|
|
130
|
+
|
|
131
|
+
- Python version and OS
|
|
132
|
+
- PyAERMOD version (`python -c "import pyaermod; print(pyaermod.__version__)"`)
|
|
133
|
+
- Minimal reproducible example
|
|
134
|
+
- Full error traceback
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
By contributing, you agree that your contributions will be licensed under the
|
|
139
|
+
MIT License.
|
pyaermod-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shannon Capps
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
AERMOD Disclaimer:
|
|
26
|
+
This software is a wrapper around the EPA's AERMOD atmospheric dispersion model.
|
|
27
|
+
AERMOD is public domain software developed by the U.S. Environmental Protection Agency.
|
|
28
|
+
This wrapper does not modify AERMOD's calculations and maintains regulatory compliance.
|
|
29
|
+
Users must obtain the AERMOD executable separately from EPA's SCRAM website.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include CHANGELOG.md
|
|
4
|
+
include CONTRIBUTING.md
|
|
5
|
+
include pyproject.toml
|
|
6
|
+
|
|
7
|
+
recursive-include src/pyaermod *.py
|
|
8
|
+
recursive-include tests *.py
|
|
9
|
+
recursive-include docs *.md *.yml
|
|
10
|
+
recursive-include examples *.py *.ipynb
|
|
11
|
+
|
|
12
|
+
prune aermod
|
|
13
|
+
prune aermap
|
|
14
|
+
prune .github
|
|
15
|
+
prune benchmarks
|
|
16
|
+
prune .claude
|
pyaermod-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyaermod
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python wrapper for EPA's AERMOD air dispersion model
|
|
5
|
+
Author-email: Shannon Capps <shannon.capps@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/atmmod/pyaermod
|
|
8
|
+
Project-URL: Documentation, https://atmmod.github.io/pyaermod/
|
|
9
|
+
Project-URL: Repository, https://github.com/atmmod/pyaermod
|
|
10
|
+
Project-URL: Issues, https://github.com/atmmod/pyaermod/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/atmmod/pyaermod/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: aermod,air quality,dispersion,modeling,atmospheric
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.20.0
|
|
26
|
+
Requires-Dist: pandas>=1.3.0
|
|
27
|
+
Provides-Extra: viz
|
|
28
|
+
Requires-Dist: matplotlib>=3.3.0; extra == "viz"
|
|
29
|
+
Requires-Dist: scipy>=1.7.0; extra == "viz"
|
|
30
|
+
Requires-Dist: folium>=0.12.0; extra == "viz"
|
|
31
|
+
Provides-Extra: geo
|
|
32
|
+
Requires-Dist: pyproj>=3.0.0; extra == "geo"
|
|
33
|
+
Requires-Dist: geopandas>=0.10.0; extra == "geo"
|
|
34
|
+
Requires-Dist: rasterio>=1.2.0; extra == "geo"
|
|
35
|
+
Requires-Dist: shapely>=1.8.0; extra == "geo"
|
|
36
|
+
Requires-Dist: scipy>=1.7.0; extra == "geo"
|
|
37
|
+
Provides-Extra: gui
|
|
38
|
+
Requires-Dist: streamlit>=1.28.0; extra == "gui"
|
|
39
|
+
Requires-Dist: streamlit-folium>=0.15.0; extra == "gui"
|
|
40
|
+
Requires-Dist: folium>=0.14.0; extra == "gui"
|
|
41
|
+
Requires-Dist: pyproj>=3.0.0; extra == "gui"
|
|
42
|
+
Requires-Dist: geopandas>=0.10.0; extra == "gui"
|
|
43
|
+
Requires-Dist: rasterio>=1.2.0; extra == "gui"
|
|
44
|
+
Requires-Dist: shapely>=1.8.0; extra == "gui"
|
|
45
|
+
Requires-Dist: scipy>=1.7.0; extra == "gui"
|
|
46
|
+
Requires-Dist: matplotlib>=3.3.0; extra == "gui"
|
|
47
|
+
Provides-Extra: terrain
|
|
48
|
+
Requires-Dist: requests>=2.25.0; extra == "terrain"
|
|
49
|
+
Provides-Extra: all
|
|
50
|
+
Requires-Dist: matplotlib>=3.3.0; extra == "all"
|
|
51
|
+
Requires-Dist: scipy>=1.7.0; extra == "all"
|
|
52
|
+
Requires-Dist: folium>=0.14.0; extra == "all"
|
|
53
|
+
Requires-Dist: pyproj>=3.0.0; extra == "all"
|
|
54
|
+
Requires-Dist: geopandas>=0.10.0; extra == "all"
|
|
55
|
+
Requires-Dist: rasterio>=1.2.0; extra == "all"
|
|
56
|
+
Requires-Dist: shapely>=1.8.0; extra == "all"
|
|
57
|
+
Requires-Dist: streamlit>=1.28.0; extra == "all"
|
|
58
|
+
Requires-Dist: streamlit-folium>=0.15.0; extra == "all"
|
|
59
|
+
Requires-Dist: requests>=2.25.0; extra == "all"
|
|
60
|
+
Provides-Extra: dev
|
|
61
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
62
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
63
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
64
|
+
Requires-Dist: hypothesis>=6.82.0; extra == "dev"
|
|
65
|
+
Requires-Dist: build; extra == "dev"
|
|
66
|
+
Requires-Dist: twine; extra == "dev"
|
|
67
|
+
Dynamic: license-file
|
|
68
|
+
|
|
69
|
+
# PyAERMOD
|
|
70
|
+
|
|
71
|
+
Python wrapper for EPA's AERMOD atmospheric dispersion model.
|
|
72
|
+
|
|
73
|
+
PyAERMOD automates input file generation, model execution, output parsing, and result visualization — replacing manual text-file editing with a type-safe Python API.
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install pyaermod # core (input generation + output parsing)
|
|
79
|
+
pip install pyaermod[viz] # + matplotlib/folium visualization
|
|
80
|
+
pip install pyaermod[geo] # + geospatial export (GeoTIFF, Shapefile)
|
|
81
|
+
pip install pyaermod[gui] # + Streamlit interactive GUI
|
|
82
|
+
pip install pyaermod[all] # everything
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For development:
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/atmmod/pyaermod.git
|
|
88
|
+
cd pyaermod
|
|
89
|
+
pip install -e ".[dev,all]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
### Generate AERMOD Input
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from pyaermod.input_generator import (
|
|
98
|
+
AERMODProject, ControlPathway, SourcePathway, ReceptorPathway,
|
|
99
|
+
MeteorologyPathway, OutputPathway, PointSource, CartesianGrid,
|
|
100
|
+
PollutantType, TerrainType,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
control = ControlPathway(
|
|
104
|
+
title_one="My Facility",
|
|
105
|
+
pollutant_id=PollutantType.PM25,
|
|
106
|
+
averaging_periods=["ANNUAL", "24"],
|
|
107
|
+
terrain_type=TerrainType.FLAT,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
sources = SourcePathway()
|
|
111
|
+
sources.add_source(PointSource(
|
|
112
|
+
source_id="STACK1", x_coord=500.0, y_coord=500.0,
|
|
113
|
+
base_elevation=10.0, stack_height=50.0, stack_temp=400.0,
|
|
114
|
+
exit_velocity=15.0, stack_diameter=2.0, emission_rate=1.5,
|
|
115
|
+
))
|
|
116
|
+
|
|
117
|
+
receptors = ReceptorPathway()
|
|
118
|
+
receptors.add_cartesian_grid(CartesianGrid.from_bounds(
|
|
119
|
+
x_min=0, x_max=2000, y_min=0, y_max=2000, spacing=100,
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
meteorology = MeteorologyPathway(
|
|
123
|
+
surface_file="met_data.sfc", profile_file="met_data.pfl",
|
|
124
|
+
)
|
|
125
|
+
output = OutputPathway(receptor_table=True, max_table=True)
|
|
126
|
+
|
|
127
|
+
project = AERMODProject(control, sources, receptors, meteorology, output)
|
|
128
|
+
project.write("facility.inp")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Run AERMOD & Parse Results
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from pyaermod.runner import run_aermod
|
|
135
|
+
from pyaermod.output_parser import parse_aermod_output
|
|
136
|
+
|
|
137
|
+
result = run_aermod("facility.inp")
|
|
138
|
+
results = parse_aermod_output(result.output_file)
|
|
139
|
+
|
|
140
|
+
df = results.get_concentrations("ANNUAL")
|
|
141
|
+
print(results.summary())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Parse POSTFILE Output
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from pyaermod.postfile import read_postfile
|
|
148
|
+
|
|
149
|
+
# Auto-detects text vs binary format
|
|
150
|
+
post = read_postfile("postfile.out")
|
|
151
|
+
df = post.to_dataframe()
|
|
152
|
+
|
|
153
|
+
# Binary postfile with deposition data
|
|
154
|
+
dep = read_postfile("depo_post.out", has_deposition=True)
|
|
155
|
+
print(dep.to_dataframe()[["concentration", "dry_depo", "wet_depo"]])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Features
|
|
159
|
+
|
|
160
|
+
### Source Types (10)
|
|
161
|
+
POINT, AREA, AREACIRC, AREAPOLY, VOLUME, LINE, RLINE, RLINEXT, BUOYLINE, OPENPIT
|
|
162
|
+
|
|
163
|
+
### Advanced Modeling
|
|
164
|
+
- **Background concentrations** — uniform, period-specific, or sector-dependent
|
|
165
|
+
- **Deposition** — dry, wet, or combined for gas and particle emissions
|
|
166
|
+
- **NO2/SO2 chemistry** — OLM, PVMRM, ARM2, GRSM with ozone data
|
|
167
|
+
- **Source groups** — custom groupings with per-group PLOTFILE output
|
|
168
|
+
- **EVENT processing** — date/receptor-specific analysis
|
|
169
|
+
|
|
170
|
+
### Preprocessors
|
|
171
|
+
- **AERMET** — meteorological data preprocessing (Stages 1-3)
|
|
172
|
+
- **AERMAP** — terrain elevation extraction with DEM download pipeline
|
|
173
|
+
|
|
174
|
+
### Analysis & Visualization
|
|
175
|
+
- Output parsing to pandas DataFrames
|
|
176
|
+
- POSTFILE parser for timestep-level results (text and binary formats)
|
|
177
|
+
- Contour plots, interactive Folium maps, 3D surfaces, wind roses
|
|
178
|
+
- Geospatial export: GeoTIFF, GeoPackage, Shapefile, GeoJSON
|
|
179
|
+
|
|
180
|
+
### Validation & Automation
|
|
181
|
+
- Input validation across all AERMOD pathways
|
|
182
|
+
- Building downwash / BPIP integration (point, area, and volume sources)
|
|
183
|
+
- Batch processing with parallel execution
|
|
184
|
+
- Interactive Streamlit GUI (`pyaermod-gui`)
|
|
185
|
+
|
|
186
|
+
## Project Structure
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
src/pyaermod/
|
|
190
|
+
__init__.py # Public API
|
|
191
|
+
input_generator.py # AERMOD input file generation (all source types)
|
|
192
|
+
validator.py # Configuration validation
|
|
193
|
+
runner.py # AERMOD subprocess execution
|
|
194
|
+
output_parser.py # Output file parsing
|
|
195
|
+
postfile.py # POSTFILE output parser
|
|
196
|
+
visualization.py # Matplotlib/Folium plots
|
|
197
|
+
advanced_viz.py # 3D surfaces, wind roses, animations
|
|
198
|
+
aermet.py # AERMET preprocessor wrapper
|
|
199
|
+
aermap.py # AERMAP input generation
|
|
200
|
+
terrain.py # DEM download + AERMAP pipeline
|
|
201
|
+
geospatial.py # Coordinate transforms, GIS export
|
|
202
|
+
bpip.py # Building downwash calculations
|
|
203
|
+
gui.py # Streamlit web GUI
|
|
204
|
+
tests/ # 1166 tests, 95% coverage
|
|
205
|
+
examples/ # Example scripts and Jupyter notebooks
|
|
206
|
+
docs/ # Architecture and quickstart guides
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Requirements
|
|
210
|
+
|
|
211
|
+
- Python >= 3.11
|
|
212
|
+
- numpy, pandas (core)
|
|
213
|
+
- AERMOD executable (free from [EPA SCRAM](https://www.epa.gov/scram))
|
|
214
|
+
|
|
215
|
+
## Documentation
|
|
216
|
+
|
|
217
|
+
- [Quick Start Guide](docs/quickstart.md)
|
|
218
|
+
- [GUI User Guide](docs/gui-guide.md)
|
|
219
|
+
- [Architecture](docs/architecture.md)
|
|
220
|
+
- [API Reference](https://atmmod.github.io/pyaermod/api/)
|
|
221
|
+
- [Examples](examples/)
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|
|
226
|
+
|
|
227
|
+
## Disclaimer
|
|
228
|
+
|
|
229
|
+
PyAERMOD is a wrapper around AERMOD, not a reimplementation. It uses official EPA binaries for all calculations and maintains regulatory acceptance. Always validate results against EPA test cases for your specific use case.
|
pyaermod-1.0.0/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# PyAERMOD
|
|
2
|
+
|
|
3
|
+
Python wrapper for EPA's AERMOD atmospheric dispersion model.
|
|
4
|
+
|
|
5
|
+
PyAERMOD automates input file generation, model execution, output parsing, and result visualization — replacing manual text-file editing with a type-safe Python API.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pyaermod # core (input generation + output parsing)
|
|
11
|
+
pip install pyaermod[viz] # + matplotlib/folium visualization
|
|
12
|
+
pip install pyaermod[geo] # + geospatial export (GeoTIFF, Shapefile)
|
|
13
|
+
pip install pyaermod[gui] # + Streamlit interactive GUI
|
|
14
|
+
pip install pyaermod[all] # everything
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For development:
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/atmmod/pyaermod.git
|
|
20
|
+
cd pyaermod
|
|
21
|
+
pip install -e ".[dev,all]"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Generate AERMOD Input
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from pyaermod.input_generator import (
|
|
30
|
+
AERMODProject, ControlPathway, SourcePathway, ReceptorPathway,
|
|
31
|
+
MeteorologyPathway, OutputPathway, PointSource, CartesianGrid,
|
|
32
|
+
PollutantType, TerrainType,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
control = ControlPathway(
|
|
36
|
+
title_one="My Facility",
|
|
37
|
+
pollutant_id=PollutantType.PM25,
|
|
38
|
+
averaging_periods=["ANNUAL", "24"],
|
|
39
|
+
terrain_type=TerrainType.FLAT,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
sources = SourcePathway()
|
|
43
|
+
sources.add_source(PointSource(
|
|
44
|
+
source_id="STACK1", x_coord=500.0, y_coord=500.0,
|
|
45
|
+
base_elevation=10.0, stack_height=50.0, stack_temp=400.0,
|
|
46
|
+
exit_velocity=15.0, stack_diameter=2.0, emission_rate=1.5,
|
|
47
|
+
))
|
|
48
|
+
|
|
49
|
+
receptors = ReceptorPathway()
|
|
50
|
+
receptors.add_cartesian_grid(CartesianGrid.from_bounds(
|
|
51
|
+
x_min=0, x_max=2000, y_min=0, y_max=2000, spacing=100,
|
|
52
|
+
))
|
|
53
|
+
|
|
54
|
+
meteorology = MeteorologyPathway(
|
|
55
|
+
surface_file="met_data.sfc", profile_file="met_data.pfl",
|
|
56
|
+
)
|
|
57
|
+
output = OutputPathway(receptor_table=True, max_table=True)
|
|
58
|
+
|
|
59
|
+
project = AERMODProject(control, sources, receptors, meteorology, output)
|
|
60
|
+
project.write("facility.inp")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Run AERMOD & Parse Results
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from pyaermod.runner import run_aermod
|
|
67
|
+
from pyaermod.output_parser import parse_aermod_output
|
|
68
|
+
|
|
69
|
+
result = run_aermod("facility.inp")
|
|
70
|
+
results = parse_aermod_output(result.output_file)
|
|
71
|
+
|
|
72
|
+
df = results.get_concentrations("ANNUAL")
|
|
73
|
+
print(results.summary())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Parse POSTFILE Output
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from pyaermod.postfile import read_postfile
|
|
80
|
+
|
|
81
|
+
# Auto-detects text vs binary format
|
|
82
|
+
post = read_postfile("postfile.out")
|
|
83
|
+
df = post.to_dataframe()
|
|
84
|
+
|
|
85
|
+
# Binary postfile with deposition data
|
|
86
|
+
dep = read_postfile("depo_post.out", has_deposition=True)
|
|
87
|
+
print(dep.to_dataframe()[["concentration", "dry_depo", "wet_depo"]])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Features
|
|
91
|
+
|
|
92
|
+
### Source Types (10)
|
|
93
|
+
POINT, AREA, AREACIRC, AREAPOLY, VOLUME, LINE, RLINE, RLINEXT, BUOYLINE, OPENPIT
|
|
94
|
+
|
|
95
|
+
### Advanced Modeling
|
|
96
|
+
- **Background concentrations** — uniform, period-specific, or sector-dependent
|
|
97
|
+
- **Deposition** — dry, wet, or combined for gas and particle emissions
|
|
98
|
+
- **NO2/SO2 chemistry** — OLM, PVMRM, ARM2, GRSM with ozone data
|
|
99
|
+
- **Source groups** — custom groupings with per-group PLOTFILE output
|
|
100
|
+
- **EVENT processing** — date/receptor-specific analysis
|
|
101
|
+
|
|
102
|
+
### Preprocessors
|
|
103
|
+
- **AERMET** — meteorological data preprocessing (Stages 1-3)
|
|
104
|
+
- **AERMAP** — terrain elevation extraction with DEM download pipeline
|
|
105
|
+
|
|
106
|
+
### Analysis & Visualization
|
|
107
|
+
- Output parsing to pandas DataFrames
|
|
108
|
+
- POSTFILE parser for timestep-level results (text and binary formats)
|
|
109
|
+
- Contour plots, interactive Folium maps, 3D surfaces, wind roses
|
|
110
|
+
- Geospatial export: GeoTIFF, GeoPackage, Shapefile, GeoJSON
|
|
111
|
+
|
|
112
|
+
### Validation & Automation
|
|
113
|
+
- Input validation across all AERMOD pathways
|
|
114
|
+
- Building downwash / BPIP integration (point, area, and volume sources)
|
|
115
|
+
- Batch processing with parallel execution
|
|
116
|
+
- Interactive Streamlit GUI (`pyaermod-gui`)
|
|
117
|
+
|
|
118
|
+
## Project Structure
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
src/pyaermod/
|
|
122
|
+
__init__.py # Public API
|
|
123
|
+
input_generator.py # AERMOD input file generation (all source types)
|
|
124
|
+
validator.py # Configuration validation
|
|
125
|
+
runner.py # AERMOD subprocess execution
|
|
126
|
+
output_parser.py # Output file parsing
|
|
127
|
+
postfile.py # POSTFILE output parser
|
|
128
|
+
visualization.py # Matplotlib/Folium plots
|
|
129
|
+
advanced_viz.py # 3D surfaces, wind roses, animations
|
|
130
|
+
aermet.py # AERMET preprocessor wrapper
|
|
131
|
+
aermap.py # AERMAP input generation
|
|
132
|
+
terrain.py # DEM download + AERMAP pipeline
|
|
133
|
+
geospatial.py # Coordinate transforms, GIS export
|
|
134
|
+
bpip.py # Building downwash calculations
|
|
135
|
+
gui.py # Streamlit web GUI
|
|
136
|
+
tests/ # 1166 tests, 95% coverage
|
|
137
|
+
examples/ # Example scripts and Jupyter notebooks
|
|
138
|
+
docs/ # Architecture and quickstart guides
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Requirements
|
|
142
|
+
|
|
143
|
+
- Python >= 3.11
|
|
144
|
+
- numpy, pandas (core)
|
|
145
|
+
- AERMOD executable (free from [EPA SCRAM](https://www.epa.gov/scram))
|
|
146
|
+
|
|
147
|
+
## Documentation
|
|
148
|
+
|
|
149
|
+
- [Quick Start Guide](docs/quickstart.md)
|
|
150
|
+
- [GUI User Guide](docs/gui-guide.md)
|
|
151
|
+
- [Architecture](docs/architecture.md)
|
|
152
|
+
- [API Reference](https://atmmod.github.io/pyaermod/api/)
|
|
153
|
+
- [Examples](examples/)
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
|
158
|
+
|
|
159
|
+
## Disclaimer
|
|
160
|
+
|
|
161
|
+
PyAERMOD is a wrapper around AERMOD, not a reimplementation. It uses official EPA binaries for all calculations and maintains regulatory acceptance. Always validate results against EPA test cases for your specific use case.
|