pyfofem
pyfofem is a Python library for modelling first-order fire effects. Its calculations are validated against the pinned FOFEM C++ reference implementation. It provides vectorized APIs for tree mortality, fuel consumption, smoke emissions, burnup, and soil heating.
Start with the quick start guide: docs/QUICK_START.md
Directory Structure
pyfofem/
|-- src/pyfofem/ # Python library
| |-- __init__.py # Public API re-exports
| |-- pyfofem.py # Core orchestrators
| `-- components/ # Specialized computation modules
|-- tests/ # Unit, golden, and parity tests (pytest package)
| |-- __init__.py # Makes tests/ a package for qualified imports
| |-- _support.py # Shared path constants (no src/ sys.path insert)
| |-- conftest.py # Fixtures, markers, installed-only session check
| |-- run_unified_tests.py # `--suite core|full`, `--installed-only` runner
| |-- prepare_cpp_reference.py # Regenerates reference fixtures
| |-- compare_cpp_python_soil_heating.py # Lay* driver; run as a tests package module
| |-- unit/ # Golden-CSV + reference-independent unit tests
| |-- integration/ # Full-pipeline (`run_fofem_emissions`) tests
| |-- regression/ # Behavior regression tests
| |-- cpp_parity_live/ # Tests requiring the compiled reference
| `-- test_data/ # Input CSVs and expected outputs
|-- examples/ # Batch/array usage driver + example data
|-- development/burnup_array/ # Experimental prototype, outside the test gate
|-- reference/fofem_cpp/ # Pinned FOFEM reference source
|-- docs/reference/ # Literature and reference docs
|-- docs/CODEBASE.md # Architecture and model mapping
`-- README.md
Features
- Tree mortality models (
crnsch,bolchar,crcabe) - Fuel consumption for litter, duff, herb, shrub, canopy, mineral soil
- Burnup post-frontal combustion engine based on Albini & Reinhardt
- Smoke emissions (
legacy,default,expandedmodes) - Campbell soil-heating model (Massman HMV is in development and unavailable)
- Integrated soil-heating outputs in
run_fofem_emissions(Lay0,Lay2,Lay4,Lay6,Lay60d,Lay275d) - Reference-validation scripts/tests for burnup, consumption, and soil-heating outputs
Python support
PyFOFEM supports Python 3.11, 3.12, 3.13, and 3.14. GitHub Actions runs
smoke tests on Ubuntu for every supported version and on macOS and Windows for
Python 3.12 on pull requests. It runs the core suite on Ubuntu for every
supported version on updates to master.
Installation
Install the published distribution with pip or uv:
python -m pip install pyfofem-fire-effects
# or
uv pip install pyfofem-fire-effects
`$([Environment]::NewLine)
The distribution name differs from the Python import package. Use
`import pyfofem` in Python code.
Install [uv](https://docs.astral.sh/uv/) and create the project environment:
```bash
git clone https://github.com/gagreene/pyfofem.git
cd pyfofem
uv sync
For development and the supported test suite, install the test extra and the
repository's dev dependency group, then use the locked environment:
uv sync --all-extras --group dev
uv run python tests/run_unified_tests.py --suite core
uv.lock records the reproducible development and CI resolution. Users who
prefer pip can install the package with python -m pip install .; contributors
using pip should add the test extra with python -m pip install -e .[test].
Usage
from pyfofem import run_fofem_emissions
results = run_fofem_emissions(
litter=2.5, duff=10.0, duff_depth=3.0, herb=0.5, shrub=0.2,
crown_foliage=0.1, crown_branch=0.2, pct_crown_burned=50,
region="InteriorWest", cvr_grp="Ponderosa pine", season="Summer",
fuel_category="Natural", duff_moist=40, l_moist=10,
dw10_moist=12, dw1000_moist=20,
dw1=0.1, dw10=0.2, dw100=0.3, dw1000s=0.4, dw1000r=0.1,
soil_family="Fine-Silt", # required when soil_heating is enabled
soil_moisture=15.0, # optional mineral-soil moisture (%)
soil_heating=True, # bool or dict of advanced overrides
)
print(results["PM10F"])
print(results["DufCon"])
print(results["Lay2"])
To match original FOFEM legacy emissions behavior, pass em_mode="legacy".
In this mode, smoldering NOx (NOXS) is expected to be 0 by design.
In expanded mode, default smolder group 7 (CWDRSC) also has NOx as NO = 0,
so NOXS mainly comes from the duff group unless you change factor groups.
Output variables
Scalar calls return scalar values. When any modelled input is an array,
corresponding outputs are NumPy arrays with one value per input case. Fuel-load
and consumption outputs use the selected unit system: T/ac for Imperial and
kg/m² for SI. Emissions use lb/acre for Imperial and g/m² for SI.
Mortality outputs
run_fofem_mortality() returns the mortality probability directly; it does
not return a dictionary. The result is dimensionless and ranges from 0 (tree
survives) to 1 (tree dies). Unsupported species/model combinations can return
NaN. Scalar inputs produce a float, while array inputs produce a NumPy
array.
| Model | Output | Description |
|---|---|---|
bolchar |
Mortality probability | Probability of post-fire mortality from the bole-char model. |
crnsch |
Mortality probability | Probability of post-fire mortality from the crown-scorch model. |
crcabe |
Mortality probability | Probability of post-fire mortality from the cambium-kill model. |
Consumption, emissions, and soil-heating outputs
run_fofem_emissions() returns a dictionary. The following table includes
every key that can appear. The seven *_Duff keys depend on em_mode, and the
six Lay* keys are present only when soil heating is enabled.
| Variable | Units (SI, Imperial) |
Description |
|---|---|---|
LitPre, LitCon, LitPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire litter load. |
DW1Pre, DW1Con, DW1Pos |
kg/m², T/ac | Pre-fire, consumed, and post-fire 1-hour down woody fuel. |
DW10Pre, DW10Con, DW10Pos |
kg/m², T/ac | Pre-fire, consumed, and post-fire 10-hour down woody fuel. |
DW100Pre, DW100Con, DW100Pos |
kg/m², T/ac | Pre-fire, consumed, and post-fire 100-hour down woody fuel. |
DW1kSndPre, DW1kSndCon, DW1kSndPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire sound 1000-hour down woody fuel, summed across diameter classes. |
DW1kRotPre, DW1kRotCon, DW1kRotPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire rotten 1000-hour down woody fuel, summed across diameter classes. |
DufPre, DufCon, DufPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire duff load. |
HerPre, HerCon, HerPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire herbaceous fuel. |
ShrPre, ShrCon, ShrPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire shrub fuel. |
FolPre, FolCon, FolPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire crown foliage. |
BraPre, BraCon, BraPos |
kg/m², T/ac | Pre-fire, consumed, and post-fire crown branch fuel. |
MSE |
% | Mineral soil exposure. |
DufDepPre, DufDepCon, DufDepPos |
cm, in | Pre-fire, consumed, and post-fire duff depth. |
FlaDur |
s | Duration through the last timestep with flaming consumption above the Burnup reporting threshold. |
SmoDur |
s | Duration through the last timestep with smoldering consumption above the Burnup reporting threshold. |
FlaCon |
kg/m², T/ac | Total fuel consumed in the flaming phase. |
SmoCon |
kg/m², T/ac | Total fuel consumed in the smoldering phase. |
Lit-Equ |
Equation ID | Litter-consumption equation selected for the case. |
DufCon-Equ |
Equation ID | Duff-consumption equation selected for the case. |
DufRed-Equ |
Equation ID | Duff-depth-reduction equation selected for the case. |
MSE-Equ |
Equation ID | Mineral-soil-exposure equation selected for the case. |
Herb-Equ |
Equation ID | Herbaceous-consumption equation selected for the case. |
Shrub-Equ |
Equation ID | Shrub-consumption equation selected for the case. |
BurnupLimitAdj |
Code | Recoverable Burnup input-adjustment code. 0 means no adjustment; concatenated digits identify multiple adjustments. See the code list below. |
BurnupError |
Code | Burnup outcome code. 0 means success; nonzero values identify a failure. See the code list below. |
PM10F, PM10S |
g/m², lb/acre | PM10 emissions from flaming and total smoldering combustion. |
PM25F, PM25S |
g/m², lb/acre | PM2.5 emissions from flaming and total smoldering combustion. |
CH4F, CH4S |
g/m², lb/acre | Methane emissions from flaming and total smoldering combustion. |
COF, COS |
g/m², lb/acre | Carbon monoxide emissions from flaming and total smoldering combustion. |
CO2F, CO2S |
g/m², lb/acre | Carbon dioxide emissions from flaming and total smoldering combustion. |
NOXF, NOXS |
g/m², lb/acre | Nitrogen oxides, reported as NO, from flaming and total smoldering combustion. |
SO2F, SO2S |
g/m², lb/acre | Sulfur dioxide emissions from flaming and total smoldering combustion. |
PM10S_Duff |
g/m², lb/acre | Duff-only smoldering PM10. Present for legacy and expanded modes. |
PM25S_Duff |
g/m², lb/acre | Duff-only smoldering PM2.5. Present for legacy and expanded modes. |
CH4S_Duff |
g/m², lb/acre | Duff-only smoldering methane. Present for legacy and expanded modes. |
COS_Duff |
g/m², lb/acre | Duff-only smoldering carbon monoxide. Present for legacy and expanded modes. |
CO2S_Duff |
g/m², lb/acre | Duff-only smoldering carbon dioxide. Present for legacy and expanded modes. |
NOXS_Duff |
g/m², lb/acre | Duff-only smoldering nitrogen oxides, reported as NO. Present for legacy and expanded modes. |
SO2S_Duff |
g/m², lb/acre | Duff-only smoldering sulfur dioxide. Present for legacy and expanded modes. |
Lay0 |
°C | Maximum modelled mineral-soil surface temperature. Present only when soil heating is enabled. |
Lay2 |
°C | Maximum modelled mineral-soil temperature at 2 cm depth. Present only when soil heating is enabled. |
Lay4 |
°C | Maximum modelled mineral-soil temperature at 4 cm depth. Present only when soil heating is enabled. |
Lay6 |
°C | Maximum modelled mineral-soil temperature at 6 cm depth. Present only when soil heating is enabled. |
Lay60d |
Layer index | Deepest requested soil layer whose modelled temperature exceeds 60 °C; -1 means no layer exceeded the threshold. With the default 1-cm depth grid, the index is also the depth in cm. |
Lay275d |
Layer index | Deepest requested soil layer whose modelled temperature exceeds 275 °C; -1 means no layer exceeded the threshold. With the default 1-cm depth grid, the index is also the depth in cm. |
Units in paired entries are listed in the table header's SI, Imperial
order. The PM10S, PM25S, CH4S, COS, CO2S, NOXS, and SO2S values include
duff smoldering. Their corresponding *_Duff values report the duff-only
portion rather than an additional quantity to add to the total.
Examples
Example scripts live in examples/:
examples/emissions_batch.py
Example input datasets are in examples/example_data/:
fofem_emissions_batch_test.csv
Soil-heating options in run_fofem_emissions
soil_heating:False(default),True, ordictsoil_family: required when soil heating is enabled; accepts GUI-style names (for exampleFine-Silt) or internal family namessoil_moisture: optional top-level mineral-soil moisture override (%)soil_heatingdict overrides:soil_moisturestart_tempefficiency_wlefficiency_hsefficiency_duffdepth_layers_cm(13 depths expected)timestep_s
Soil moisture precedence during soil-heating runs:
- Top-level
soil_moisture soil_heating["soil_moisture"]moisture_regimesoil value- Clipped
duff_moistfallback (0..25%)
Burnup status codes in run_fofem_emissions
run_fofem_emissions() returns two burnup-status fields:
BurnupError: hard burnup failure code.0means burnup ran successfully.BurnupLimitAdj: clipping/adjustment code for recoverable inputs.0means no clipping was applied.
When BurnupError != 0, the burnup model does not run for that case and
pyfofem falls back to simplified consumption-duration defaults for the
emissions pipeline.
BurnupError codes:
0: success10:fistartstarting fire intensity, below 40 kW/m². The accepted range is 40 to 100,000 kW/m², inclusive; values above the maximum are clipped under adjustment code1.11:tisurface fire residence time, below 10 s. The accepted range is 10 to 200 s, inclusive; values above the maximum are clipped under adjustment code2.12:uwindspeed at the top of the fuel bed, below 0 m/s. The accepted range is 0 to 5 m/s, inclusive; values above the maximum are clipped under adjustment code3.13:tamb_cambient temperature in degrees Celsius, below -40 °C. The accepted range is -40 to 40 °C, inclusive; values above the maximum are clipped under adjustment code5.14:dfmduff moisture content as a fraction of dry weight, above 1.972 (197.2% in the publicduff_moistinput). When duff is present, the accepted range is 0.1 to 1.972 (10% to 197.2%), inclusive; values below the minimum are clipped under adjustment code6. Duff moisture is not range-checked when the duff load is zero.15: fire cannot dry the fuel. This is a calculated physical failure rather than a separate input-range threshold.16: no fuel ignited within the residence time. This is a calculated physical failure rather than a separate input-range threshold.20:wdryoven-dry fuel loading, outside (1e-8, 1e6) kg/m².21:ashmineral ash content as a dry-mass fraction, outside (0.0001, 0.1).22:htvallow heat of combustion, outside (1e7, 3e7) J/kg.23:fmoisfuel moisture content as a fraction of dry weight, outside (0.01, 3.0) (1% to 300%).24:dendryoven-dry fuel mass density, outside (200, 1000) kg/m³.25:sigmafuel-particle surface-area-to-volume ratio, outside (4, 10,000) m⁻¹.26:cheatfuel specific heat capacity, outside (1000, 3000) J/(kg·K).27:condryoven-dry fuel thermal conductivity, outside (0.025, 0.25) W/(m·K).28:tpigpiloted-ignition temperature, outside (200, 400) °C.29:tcharend-of-pyrolysis char temperature, outside (250, 500) °C.90: no fuel particles; every fuel loading is less than or equal to zero.91:ntimesmaximum number of simulation timesteps, is less than or equal to zero; this is controlled byburnup_kwargs["max_times"].99: unexpected burnup exception
Square brackets or the word "inclusive" above indicate valid endpoints. The
parenthesized fuel-particle ranges for codes 20-29 are strict: values equal
to either endpoint are rejected.
BurnupLimitAdj codes are concatenated digits when more than one adjustment is
applied. For example, 13 means codes 1 and 3 both occurred, and 246
means codes 2, 4, and 6 occurred.
0: no clipping applied1:fistartstarting fire intensity, above 100,000 kW/m²; clipped to 100,000 kW/m².2:tisurface fire residence time, above 200 s; clipped to 200 s.3:uwindspeed at the top of the fuel bed, above 5 m/s; clipped to 5 m/s.4:dfuel bed depth, below 0.1 m or above 5 m; clipped to the nearest endpoint of the inclusive 0.1 to 5 m range.5:tamb_cambient temperature in degrees Celsius, above 40 °C; clipped to 40 °C.6:dfmduff moisture content as a fraction of dry weight, below 0.1 when duff is present; clipped to 0.1 (10% in the publicduff_moistinput).
Testing
Run the full supported suite. pyproject.toml sets testpaths = ["tests"],
so plain pytest (or python -m pytest) collects only the supported
package suite under tests/ and does not touch the experimental prototype
below:
python -m pytest
Run the unified publish-oriented suite (recommended for CI/package checks):
# Fastest, representative pull-request checks
python tests/run_unified_tests.py --suite ci-smoke
# Fast publish-safe suite
python tests/run_unified_tests.py --suite core
# Extended suite with parity/comparison tests
python tests/run_unified_tests.py --suite full
Run the standalone Lay* soil-heating reference comparison from the repository
root so its tests package import resolves correctly:
python -m tests.compare_cpp_python_soil_heating
This diagnostic reads the pinned reference/fofem_cpp/soil.tmp fixture, exits
nonzero when a comparison exceeds its embedded tolerance, and is not part of
the unified test suites.
.github/workflows/ci.yml runs ci-smoke on pull requests and core on
pushes to master. core validates Python behavior against committed golden
data and does not build or run the C++ reference. The full suite invokes the
live C++ harness and golden generators; it is intentionally excluded from
ordinary CI and release checks. Core reads the pinned C++ Git revision and every
golden manifest, so a changed reference commit fails before it can be treated as
a current golden baseline. Run full only when that failure identifies a pinned
upstream C++ change or when a deliberate Python/parity investigation needs new C++
evidence.
Experimental prototype: development/burnup_array
development/burnup_array is a non-production, array-based burnup
prototype. It is outside the default/release test gate — testpaths
does not include it, run_unified_tests.py does not run it, and it is not
part of core or full.
Its explicit, separate diagnostic invocation:
python -m pytest development/burnup_array/tests -q
As of this writing that command fails during collection, not just an
individual test: test_consumption_calcs_array.py imports
burnup_array_calcs as a top-level module, but burnup_array_calcs.py
itself uses a relative import (from .burnup_array_kernel import ...),
raising ImportError: attempted relative import with no known parent package. This is a known, tracked failure — not a skip, and not silently
part of the supported suite. Maintain it independently of the library's
supported test suites.
For package-validation workflows where you want to ensure tests are running
against the installed package (not local src/), use:
python tests/run_unified_tests.py --suite core --installed-only
Packaging pipeline usage
PyPI wheel/sdist check:
python -m pip install build twine
python -m build
python -m twine check dist/*
python -m pip install .
python tests/run_unified_tests.py --suite core --installed-only
Conda recipe test:commands example:
test:
commands:
- python tests/run_unified_tests.py --suite core --installed-only
The Conda recipe lives in conda-recipe/. See conda-recipe/README.md for
build and test commands.
Reference-validation tooling and deterministic golden-data verification live
under tests/cpp_parity_live/. See CODEBASE.md for the
current test tiers and maintenance guidance. Release provenance and attribution
for bundled FOFEM-derived data are in
THIRD_PARTY_NOTICES.md.
License
PyFOFEM source is licensed under the MIT License. Bundled FOFEM-derived runtime tables and their provenance are described in THIRD_PARTY_NOTICES.md.
Metadata
Release files for pyfofem-fire-effects 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyfofem_fire_effects-0.1.1.tar.gz | 142.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyfofem_fire_effects-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 283.5 kB
Release files / pyfofem_fire_effects-0.1.1.tar.gz
| Download URL | pyfofem_fire_effects-0.1.1.tar.gz |
|---|---|
| Size | 142.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a3770774b3be547739838e7c57ddd67d45dc2d8c36bebe66343f6d148414440b
|
|
BLAKE2b-256 checksum How to use checksums |
567d52a5b34121ef64eafe52eafe56966e631c46abed7f94bcd94c2d5b912c3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / pyfofem_fire_effects-0.1.1-py3-none-any.whl
| Download URL | pyfofem_fire_effects-0.1.1-py3-none-any.whl |
|---|---|
| Size | 140.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c86f7b6f8445fc29b5cb1d1ea9e01cfacf7ba812cb76d6854d28eeb71b4a8e93
|
|
BLAKE2b-256 checksum How to use checksums |
59d0852c68dfdaaf11a711564ab71c1fb85c2279b7511742f0b34f74d2389afd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log