pyfofem-fire-effects 0.1.1__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.
- pyfofem_fire_effects-0.1.1/CITATION.cff +22 -0
- pyfofem_fire_effects-0.1.1/LICENSE +22 -0
- pyfofem_fire_effects-0.1.1/MANIFEST.in +9 -0
- pyfofem_fire_effects-0.1.1/PKG-INFO +437 -0
- pyfofem_fire_effects-0.1.1/README.md +405 -0
- pyfofem_fire_effects-0.1.1/THIRD_PARTY_NOTICES.md +41 -0
- pyfofem_fire_effects-0.1.1/pyproject.toml +76 -0
- pyfofem_fire_effects-0.1.1/setup.cfg +4 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/__init__.py +83 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/__init__.py +124 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/_component_helpers.py +106 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/burnup.py +1545 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/burnup_calcs.py +588 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/consumption_calcs.py +1426 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/emission_calcs.py +347 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/emission_pipeline.py +670 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/mortality_calcs.py +836 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/soil_heating.py +2019 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/components/tree_flame_calcs.py +386 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/pyfofem.py +1057 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/supporting_data/FOFEM6.7/FOF_SPP.CSV +629 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/supporting_data/emissions_factors.csv +20 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/supporting_data/fofem_bark_thickness.csv +526 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/supporting_data/fofem_crnsch_eq1_bark.csv +444 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem/supporting_data/species_codes_lut.csv +122 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem_fire_effects.egg-info/PKG-INFO +437 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem_fire_effects.egg-info/SOURCES.txt +28 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem_fire_effects.egg-info/dependency_links.txt +1 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem_fire_effects.egg-info/requires.txt +8 -0
- pyfofem_fire_effects-0.1.1/src/pyfofem_fire_effects.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use PyFOFEM, please cite this software and the underlying FOFEM model."
|
|
3
|
+
title: "PyFOFEM"
|
|
4
|
+
type: software
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
authors:
|
|
7
|
+
- family-names: Greene
|
|
8
|
+
given-names: Gregory A.
|
|
9
|
+
repository-code: "https://github.com/gagreene/pyfofem"
|
|
10
|
+
url: "https://github.com/gagreene/pyfofem"
|
|
11
|
+
keywords:
|
|
12
|
+
- fire effects
|
|
13
|
+
- FOFEM
|
|
14
|
+
- fuel consumption
|
|
15
|
+
- emissions
|
|
16
|
+
- soil heating
|
|
17
|
+
references:
|
|
18
|
+
- type: software
|
|
19
|
+
title: "First Order Fire Effects Model (FOFEM)"
|
|
20
|
+
publisher:
|
|
21
|
+
name: "USDA Forest Service"
|
|
22
|
+
url: "https://research.fs.usda.gov/firelab/products/dataandtools/fofem/spatialfofem-fire-effects-model"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gregory A. Greene
|
|
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
|
+
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfofem-fire-effects
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A Python implementation of the FOFEM fire-effects model
|
|
5
|
+
Author-email: "Gregory A. Greene" <map.n.trowel@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gagreene/pyfofem
|
|
8
|
+
Project-URL: Repository, https://github.com/gagreene/pyfofem
|
|
9
|
+
Keywords: fofem,fire,emissions,burnup,soil-heating,mortality
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
License-File: THIRD_PARTY_NOTICES.md
|
|
24
|
+
Requires-Dist: numpy>=1.23
|
|
25
|
+
Requires-Dist: pandas>=1.5
|
|
26
|
+
Requires-Dist: scipy>=1.10
|
|
27
|
+
Requires-Dist: tqdm>=4.64
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
30
|
+
Requires-Dist: psutil>=5.9; extra == "test"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# pyfofem
|
|
34
|
+
|
|
35
|
+
`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.
|
|
36
|
+
|
|
37
|
+
Start with the quick start guide: [docs/QUICK_START.md](docs/QUICK_START.md)
|
|
38
|
+
|
|
39
|
+
## Directory Structure
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
pyfofem/
|
|
43
|
+
|-- src/pyfofem/ # Python library
|
|
44
|
+
| |-- __init__.py # Public API re-exports
|
|
45
|
+
| |-- pyfofem.py # Core orchestrators
|
|
46
|
+
| `-- components/ # Specialized computation modules
|
|
47
|
+
|-- tests/ # Unit, golden, and parity tests (pytest package)
|
|
48
|
+
| |-- __init__.py # Makes tests/ a package for qualified imports
|
|
49
|
+
| |-- _support.py # Shared path constants (no src/ sys.path insert)
|
|
50
|
+
| |-- conftest.py # Fixtures, markers, installed-only session check
|
|
51
|
+
| |-- run_unified_tests.py # `--suite core|full`, `--installed-only` runner
|
|
52
|
+
| |-- prepare_cpp_reference.py # Regenerates reference fixtures
|
|
53
|
+
| |-- compare_cpp_python_soil_heating.py # Lay* driver; run as a tests package module
|
|
54
|
+
| |-- unit/ # Golden-CSV + reference-independent unit tests
|
|
55
|
+
| |-- integration/ # Full-pipeline (`run_fofem_emissions`) tests
|
|
56
|
+
| |-- regression/ # Behavior regression tests
|
|
57
|
+
| |-- cpp_parity_live/ # Tests requiring the compiled reference
|
|
58
|
+
| `-- test_data/ # Input CSVs and expected outputs
|
|
59
|
+
|-- examples/ # Batch/array usage driver + example data
|
|
60
|
+
|-- development/burnup_array/ # Experimental prototype, outside the test gate
|
|
61
|
+
|-- reference/fofem_cpp/ # Pinned FOFEM reference source
|
|
62
|
+
|-- docs/reference/ # Literature and reference docs
|
|
63
|
+
|-- docs/CODEBASE.md # Architecture and model mapping
|
|
64
|
+
`-- README.md
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- Tree mortality models (`crnsch`, `bolchar`, `crcabe`)
|
|
70
|
+
- Fuel consumption for litter, duff, herb, shrub, canopy, mineral soil
|
|
71
|
+
- Burnup post-frontal combustion engine based on Albini & Reinhardt
|
|
72
|
+
- Smoke emissions (`legacy`, `default`, `expanded` modes)
|
|
73
|
+
- Campbell soil-heating model (Massman HMV is in development and unavailable)
|
|
74
|
+
- Integrated soil-heating outputs in `run_fofem_emissions` (`Lay0`, `Lay2`, `Lay4`, `Lay6`, `Lay60d`, `Lay275d`)
|
|
75
|
+
- Reference-validation scripts/tests for burnup, consumption, and soil-heating outputs
|
|
76
|
+
|
|
77
|
+
## Python support
|
|
78
|
+
|
|
79
|
+
PyFOFEM supports Python 3.11, 3.12, 3.13, and 3.14. GitHub Actions runs
|
|
80
|
+
smoke tests on Ubuntu for every supported version and on macOS and Windows for
|
|
81
|
+
Python 3.12 on pull requests. It runs the core suite on Ubuntu for every
|
|
82
|
+
supported version on updates to `master`.
|
|
83
|
+
## Installation
|
|
84
|
+
|
|
85
|
+
Install the published distribution with pip or uv:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python -m pip install pyfofem-fire-effects
|
|
89
|
+
# or
|
|
90
|
+
uv pip install pyfofem-fire-effects
|
|
91
|
+
`$([Environment]::NewLine)
|
|
92
|
+
The distribution name differs from the Python import package. Use
|
|
93
|
+
`import pyfofem` in Python code.
|
|
94
|
+
|
|
95
|
+
Install [uv](https://docs.astral.sh/uv/) and create the project environment:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git clone https://github.com/gagreene/pyfofem.git
|
|
99
|
+
cd pyfofem
|
|
100
|
+
uv sync
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For development and the supported test suite, install the test extra and the
|
|
104
|
+
repository's `dev` dependency group, then use the locked environment:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv sync --all-extras --group dev
|
|
108
|
+
uv run python tests/run_unified_tests.py --suite core
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`uv.lock` records the reproducible development and CI resolution. Users who
|
|
112
|
+
prefer pip can install the package with `python -m pip install .`; contributors
|
|
113
|
+
using pip should add the test extra with `python -m pip install -e .[test]`.
|
|
114
|
+
## Usage
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from pyfofem import run_fofem_emissions
|
|
118
|
+
|
|
119
|
+
results = run_fofem_emissions(
|
|
120
|
+
litter=2.5, duff=10.0, duff_depth=3.0, herb=0.5, shrub=0.2,
|
|
121
|
+
crown_foliage=0.1, crown_branch=0.2, pct_crown_burned=50,
|
|
122
|
+
region="InteriorWest", cvr_grp="Ponderosa pine", season="Summer",
|
|
123
|
+
fuel_category="Natural", duff_moist=40, l_moist=10,
|
|
124
|
+
dw10_moist=12, dw1000_moist=20,
|
|
125
|
+
dw1=0.1, dw10=0.2, dw100=0.3, dw1000s=0.4, dw1000r=0.1,
|
|
126
|
+
soil_family="Fine-Silt", # required when soil_heating is enabled
|
|
127
|
+
soil_moisture=15.0, # optional mineral-soil moisture (%)
|
|
128
|
+
soil_heating=True, # bool or dict of advanced overrides
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
print(results["PM10F"])
|
|
132
|
+
print(results["DufCon"])
|
|
133
|
+
print(results["Lay2"])
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
To match original FOFEM legacy emissions behavior, pass `em_mode="legacy"`.
|
|
137
|
+
In this mode, smoldering NOx (`NOXS`) is expected to be `0` by design.
|
|
138
|
+
In `expanded` mode, default smolder group 7 (`CWDRSC`) also has `NOx as NO = 0`,
|
|
139
|
+
so `NOXS` mainly comes from the duff group unless you change factor groups.
|
|
140
|
+
|
|
141
|
+
## Output variables
|
|
142
|
+
|
|
143
|
+
Scalar calls return scalar values. When any modelled input is an array,
|
|
144
|
+
corresponding outputs are NumPy arrays with one value per input case. Fuel-load
|
|
145
|
+
and consumption outputs use the selected unit system: T/ac for `Imperial` and
|
|
146
|
+
kg/m² for `SI`. Emissions use lb/acre for `Imperial` and g/m² for `SI`.
|
|
147
|
+
|
|
148
|
+
### Mortality outputs
|
|
149
|
+
|
|
150
|
+
`run_fofem_mortality()` returns the mortality probability directly; it does
|
|
151
|
+
not return a dictionary. The result is dimensionless and ranges from 0 (tree
|
|
152
|
+
survives) to 1 (tree dies). Unsupported species/model combinations can return
|
|
153
|
+
`NaN`. Scalar inputs produce a `float`, while array inputs produce a NumPy
|
|
154
|
+
array.
|
|
155
|
+
|
|
156
|
+
| Model | Output | Description |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `bolchar` | Mortality probability | Probability of post-fire mortality from the bole-char model. |
|
|
159
|
+
| `crnsch` | Mortality probability | Probability of post-fire mortality from the crown-scorch model. |
|
|
160
|
+
| `crcabe` | Mortality probability | Probability of post-fire mortality from the cambium-kill model. |
|
|
161
|
+
|
|
162
|
+
### Consumption, emissions, and soil-heating outputs
|
|
163
|
+
|
|
164
|
+
`run_fofem_emissions()` returns a dictionary. The following table includes
|
|
165
|
+
every key that can appear. The seven `*_Duff` keys depend on `em_mode`, and the
|
|
166
|
+
six `Lay*` keys are present only when soil heating is enabled.
|
|
167
|
+
|
|
168
|
+
| Variable | Units<br>(SI, Imperial) | Description |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `LitPre`, `LitCon`, `LitPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire litter load. |
|
|
171
|
+
| `DW1Pre`, `DW1Con`, `DW1Pos` | kg/m², T/ac | Pre-fire, consumed, and post-fire 1-hour down woody fuel. |
|
|
172
|
+
| `DW10Pre`, `DW10Con`, `DW10Pos` | kg/m², T/ac | Pre-fire, consumed, and post-fire 10-hour down woody fuel. |
|
|
173
|
+
| `DW100Pre`, `DW100Con`, `DW100Pos` | kg/m², T/ac | Pre-fire, consumed, and post-fire 100-hour down woody fuel. |
|
|
174
|
+
| `DW1kSndPre`, `DW1kSndCon`, `DW1kSndPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire sound 1000-hour down woody fuel, summed across diameter classes. |
|
|
175
|
+
| `DW1kRotPre`, `DW1kRotCon`, `DW1kRotPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire rotten 1000-hour down woody fuel, summed across diameter classes. |
|
|
176
|
+
| `DufPre`, `DufCon`, `DufPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire duff load. |
|
|
177
|
+
| `HerPre`, `HerCon`, `HerPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire herbaceous fuel. |
|
|
178
|
+
| `ShrPre`, `ShrCon`, `ShrPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire shrub fuel. |
|
|
179
|
+
| `FolPre`, `FolCon`, `FolPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire crown foliage. |
|
|
180
|
+
| `BraPre`, `BraCon`, `BraPos` | kg/m², T/ac | Pre-fire, consumed, and post-fire crown branch fuel. |
|
|
181
|
+
| `MSE` | % | Mineral soil exposure. |
|
|
182
|
+
| `DufDepPre`, `DufDepCon`, `DufDepPos` | cm, in | Pre-fire, consumed, and post-fire duff depth. |
|
|
183
|
+
| `FlaDur` | s | Duration through the last timestep with flaming consumption above the Burnup reporting threshold. |
|
|
184
|
+
| `SmoDur` | s | Duration through the last timestep with smoldering consumption above the Burnup reporting threshold. |
|
|
185
|
+
| `FlaCon` | kg/m², T/ac | Total fuel consumed in the flaming phase. |
|
|
186
|
+
| `SmoCon` | kg/m², T/ac | Total fuel consumed in the smoldering phase. |
|
|
187
|
+
| `Lit-Equ` | Equation ID | Litter-consumption equation selected for the case. |
|
|
188
|
+
| `DufCon-Equ` | Equation ID | Duff-consumption equation selected for the case. |
|
|
189
|
+
| `DufRed-Equ` | Equation ID | Duff-depth-reduction equation selected for the case. |
|
|
190
|
+
| `MSE-Equ` | Equation ID | Mineral-soil-exposure equation selected for the case. |
|
|
191
|
+
| `Herb-Equ` | Equation ID | Herbaceous-consumption equation selected for the case. |
|
|
192
|
+
| `Shrub-Equ` | Equation ID | Shrub-consumption equation selected for the case. |
|
|
193
|
+
| `BurnupLimitAdj` | Code | Recoverable Burnup input-adjustment code. `0` means no adjustment; concatenated digits identify multiple adjustments. See the code list below. |
|
|
194
|
+
| `BurnupError` | Code | Burnup outcome code. `0` means success; nonzero values identify a failure. See the code list below. |
|
|
195
|
+
| `PM10F`, `PM10S` | g/m², lb/acre | PM10 emissions from flaming and total smoldering combustion. |
|
|
196
|
+
| `PM25F`, `PM25S` | g/m², lb/acre | PM2.5 emissions from flaming and total smoldering combustion. |
|
|
197
|
+
| `CH4F`, `CH4S` | g/m², lb/acre | Methane emissions from flaming and total smoldering combustion. |
|
|
198
|
+
| `COF`, `COS` | g/m², lb/acre | Carbon monoxide emissions from flaming and total smoldering combustion. |
|
|
199
|
+
| `CO2F`, `CO2S` | g/m², lb/acre | Carbon dioxide emissions from flaming and total smoldering combustion. |
|
|
200
|
+
| `NOXF`, `NOXS` | g/m², lb/acre | Nitrogen oxides, reported as NO, from flaming and total smoldering combustion. |
|
|
201
|
+
| `SO2F`, `SO2S` | g/m², lb/acre | Sulfur dioxide emissions from flaming and total smoldering combustion. |
|
|
202
|
+
| `PM10S_Duff` | g/m², lb/acre | Duff-only smoldering PM10. Present for `legacy` and `expanded` modes. |
|
|
203
|
+
| `PM25S_Duff` | g/m², lb/acre | Duff-only smoldering PM2.5. Present for `legacy` and `expanded` modes. |
|
|
204
|
+
| `CH4S_Duff` | g/m², lb/acre | Duff-only smoldering methane. Present for `legacy` and `expanded` modes. |
|
|
205
|
+
| `COS_Duff` | g/m², lb/acre | Duff-only smoldering carbon monoxide. Present for `legacy` and `expanded` modes. |
|
|
206
|
+
| `CO2S_Duff` | g/m², lb/acre | Duff-only smoldering carbon dioxide. Present for `legacy` and `expanded` modes. |
|
|
207
|
+
| `NOXS_Duff` | g/m², lb/acre | Duff-only smoldering nitrogen oxides, reported as NO. Present for `legacy` and `expanded` modes. |
|
|
208
|
+
| `SO2S_Duff` | g/m², lb/acre | Duff-only smoldering sulfur dioxide. Present for `legacy` and `expanded` modes. |
|
|
209
|
+
| `Lay0` | °C | Maximum modelled mineral-soil surface temperature. Present only when soil heating is enabled. |
|
|
210
|
+
| `Lay2` | °C | Maximum modelled mineral-soil temperature at 2 cm depth. Present only when soil heating is enabled. |
|
|
211
|
+
| `Lay4` | °C | Maximum modelled mineral-soil temperature at 4 cm depth. Present only when soil heating is enabled. |
|
|
212
|
+
| `Lay6` | °C | Maximum modelled mineral-soil temperature at 6 cm depth. Present only when soil heating is enabled. |
|
|
213
|
+
| `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. |
|
|
214
|
+
| `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. |
|
|
215
|
+
|
|
216
|
+
Units in paired entries are listed in the table header's **SI, Imperial**
|
|
217
|
+
order. The `PM10S`, `PM25S`, `CH4S`, `COS`, `CO2S`, `NOXS`, and `SO2S` values include
|
|
218
|
+
duff smoldering. Their corresponding `*_Duff` values report the duff-only
|
|
219
|
+
portion rather than an additional quantity to add to the total.
|
|
220
|
+
|
|
221
|
+
## Examples
|
|
222
|
+
|
|
223
|
+
Example scripts live in `examples/`:
|
|
224
|
+
|
|
225
|
+
- `examples/emissions_batch.py`
|
|
226
|
+
|
|
227
|
+
Example input datasets are in `examples/example_data/`:
|
|
228
|
+
|
|
229
|
+
- `fofem_emissions_batch_test.csv`
|
|
230
|
+
|
|
231
|
+
### Soil-heating options in `run_fofem_emissions`
|
|
232
|
+
|
|
233
|
+
- `soil_heating`: `False` (default), `True`, or `dict`
|
|
234
|
+
- `soil_family`: required when soil heating is enabled; accepts GUI-style names (for example `Fine-Silt`) or internal family names
|
|
235
|
+
- `soil_moisture`: optional top-level mineral-soil moisture override (%)
|
|
236
|
+
- `soil_heating` dict overrides:
|
|
237
|
+
- `soil_moisture`
|
|
238
|
+
- `start_temp`
|
|
239
|
+
- `efficiency_wl`
|
|
240
|
+
- `efficiency_hs`
|
|
241
|
+
- `efficiency_duff`
|
|
242
|
+
- `depth_layers_cm` (13 depths expected)
|
|
243
|
+
- `timestep_s`
|
|
244
|
+
|
|
245
|
+
Soil moisture precedence during soil-heating runs:
|
|
246
|
+
1. Top-level `soil_moisture`
|
|
247
|
+
2. `soil_heating["soil_moisture"]`
|
|
248
|
+
3. `moisture_regime` soil value
|
|
249
|
+
4. Clipped `duff_moist` fallback (`0..25%`)
|
|
250
|
+
|
|
251
|
+
### Burnup status codes in `run_fofem_emissions`
|
|
252
|
+
|
|
253
|
+
`run_fofem_emissions()` returns two burnup-status fields:
|
|
254
|
+
|
|
255
|
+
- `BurnupError`: hard burnup failure code. `0` means burnup ran successfully.
|
|
256
|
+
- `BurnupLimitAdj`: clipping/adjustment code for recoverable inputs. `0` means no clipping was applied.
|
|
257
|
+
|
|
258
|
+
When `BurnupError != 0`, the burnup model does not run for that case and
|
|
259
|
+
`pyfofem` falls back to simplified consumption-duration defaults for the
|
|
260
|
+
emissions pipeline.
|
|
261
|
+
|
|
262
|
+
`BurnupError` codes:
|
|
263
|
+
|
|
264
|
+
- `0`: success
|
|
265
|
+
- `10`: `fistart` starting fire intensity, below **40 kW/m²**. The accepted range is
|
|
266
|
+
**40 to 100,000 kW/m²**, inclusive; values above the maximum are clipped
|
|
267
|
+
under adjustment code `1`.
|
|
268
|
+
- `11`: `ti` surface fire residence time, below **10 s**. The accepted range
|
|
269
|
+
is **10 to 200 s**, inclusive; values above the maximum are clipped under
|
|
270
|
+
adjustment code `2`.
|
|
271
|
+
- `12`: `u` windspeed at the top of the fuel bed, below **0 m/s**. The
|
|
272
|
+
accepted range is **0 to 5 m/s**, inclusive; values above the maximum are
|
|
273
|
+
clipped under adjustment code `3`.
|
|
274
|
+
- `13`: `tamb_c` ambient temperature in degrees Celsius, below **-40 °C**.
|
|
275
|
+
The accepted range is **-40 to 40 °C**, inclusive; values above the maximum
|
|
276
|
+
are clipped under adjustment code `5`.
|
|
277
|
+
- `14`: `dfm` duff moisture content as a fraction of dry weight, above
|
|
278
|
+
**1.972** (**197.2%** in the public `duff_moist` input). When duff is
|
|
279
|
+
present, the accepted range is **0.1 to 1.972** (**10% to 197.2%**),
|
|
280
|
+
inclusive; values below the minimum are clipped under adjustment code `6`.
|
|
281
|
+
Duff moisture is not range-checked when the duff load is zero.
|
|
282
|
+
- `15`: fire cannot dry the fuel. This is a calculated physical failure rather
|
|
283
|
+
than a separate input-range threshold.
|
|
284
|
+
- `16`: no fuel ignited within the residence time. This is a calculated
|
|
285
|
+
physical failure rather than a separate input-range threshold.
|
|
286
|
+
- `20`: `wdry` oven-dry fuel loading, outside **(1e-8, 1e6) kg/m²**.
|
|
287
|
+
- `21`: `ash` mineral ash content as a dry-mass fraction, outside
|
|
288
|
+
**(0.0001, 0.1)**.
|
|
289
|
+
- `22`: `htval` low heat of combustion, outside **(1e7, 3e7) J/kg**.
|
|
290
|
+
- `23`: `fmois` fuel moisture content as a fraction of dry weight, outside
|
|
291
|
+
**(0.01, 3.0)** (**1% to 300%**).
|
|
292
|
+
- `24`: `dendry` oven-dry fuel mass density, outside **(200, 1000) kg/m³**.
|
|
293
|
+
- `25`: `sigma` fuel-particle surface-area-to-volume ratio, outside
|
|
294
|
+
**(4, 10,000) m⁻¹**.
|
|
295
|
+
- `26`: `cheat` fuel specific heat capacity, outside
|
|
296
|
+
**(1000, 3000) J/(kg·K)**.
|
|
297
|
+
- `27`: `condry` oven-dry fuel thermal conductivity, outside
|
|
298
|
+
**(0.025, 0.25) W/(m·K)**.
|
|
299
|
+
- `28`: `tpig` piloted-ignition temperature, outside **(200, 400) °C**.
|
|
300
|
+
- `29`: `tchar` end-of-pyrolysis char temperature, outside
|
|
301
|
+
**(250, 500) °C**.
|
|
302
|
+
- `90`: no fuel particles; every fuel loading is less than or equal to zero.
|
|
303
|
+
- `91`: `ntimes` maximum number of simulation timesteps, is less than or
|
|
304
|
+
equal to zero; this is controlled by `burnup_kwargs["max_times"]`.
|
|
305
|
+
- `99`: unexpected burnup exception
|
|
306
|
+
|
|
307
|
+
Square brackets or the word "inclusive" above indicate valid endpoints. The
|
|
308
|
+
parenthesized fuel-particle ranges for codes `20`-`29` are strict: values equal
|
|
309
|
+
to either endpoint are rejected.
|
|
310
|
+
|
|
311
|
+
`BurnupLimitAdj` codes are concatenated digits when more than one adjustment is
|
|
312
|
+
applied. For example, `13` means codes `1` and `3` both occurred, and `246`
|
|
313
|
+
means codes `2`, `4`, and `6` occurred.
|
|
314
|
+
|
|
315
|
+
- `0`: no clipping applied
|
|
316
|
+
- `1`: `fistart` starting fire intensity, above **100,000 kW/m²**; clipped
|
|
317
|
+
to **100,000 kW/m²**.
|
|
318
|
+
- `2`: `ti` surface fire residence time, above **200 s**; clipped to
|
|
319
|
+
**200 s**.
|
|
320
|
+
- `3`: `u` windspeed at the top of the fuel bed, above **5 m/s**; clipped to
|
|
321
|
+
**5 m/s**.
|
|
322
|
+
- `4`: `d` fuel bed depth, below **0.1 m** or above **5 m**; clipped to the
|
|
323
|
+
nearest endpoint of the inclusive **0.1 to 5 m** range.
|
|
324
|
+
- `5`: `tamb_c` ambient temperature in degrees Celsius, above **40 °C**;
|
|
325
|
+
clipped to **40 °C**.
|
|
326
|
+
- `6`: `dfm` duff moisture content as a fraction of dry weight, below
|
|
327
|
+
**0.1** when duff is present; clipped to **0.1** (**10%** in the public
|
|
328
|
+
`duff_moist` input).
|
|
329
|
+
|
|
330
|
+
## Testing
|
|
331
|
+
|
|
332
|
+
Run the full supported suite. `pyproject.toml` sets `testpaths = ["tests"]`,
|
|
333
|
+
so plain `pytest` (or `python -m pytest`) collects only the supported
|
|
334
|
+
package suite under `tests/` and does not touch the experimental prototype
|
|
335
|
+
below:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
python -m pytest
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Run the unified publish-oriented suite (recommended for CI/package checks):
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
# Fastest, representative pull-request checks
|
|
345
|
+
python tests/run_unified_tests.py --suite ci-smoke
|
|
346
|
+
|
|
347
|
+
# Fast publish-safe suite
|
|
348
|
+
python tests/run_unified_tests.py --suite core
|
|
349
|
+
|
|
350
|
+
# Extended suite with parity/comparison tests
|
|
351
|
+
python tests/run_unified_tests.py --suite full
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Run the standalone Lay* soil-heating reference comparison from the repository
|
|
355
|
+
root so its ``tests`` package import resolves correctly:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
python -m tests.compare_cpp_python_soil_heating
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
This diagnostic reads the pinned `reference/fofem_cpp/soil.tmp` fixture, exits
|
|
362
|
+
nonzero when a comparison exceeds its embedded tolerance, and is not part of
|
|
363
|
+
the unified test suites.
|
|
364
|
+
|
|
365
|
+
`.github/workflows/ci.yml` runs `ci-smoke` on pull requests and `core` on
|
|
366
|
+
pushes to `master`. `core` validates Python behavior against committed golden
|
|
367
|
+
data and does not build or run the C++ reference. The `full` suite invokes the
|
|
368
|
+
live C++ harness and golden generators; it is intentionally excluded from
|
|
369
|
+
ordinary CI and release checks. Core reads the pinned C++ Git revision and every
|
|
370
|
+
golden manifest, so a changed reference commit fails before it can be treated as
|
|
371
|
+
a current golden baseline. Run `full` only when that failure identifies a pinned
|
|
372
|
+
upstream C++ change or when a deliberate Python/parity investigation needs new C++
|
|
373
|
+
evidence.
|
|
374
|
+
|
|
375
|
+
### Experimental prototype: `development/burnup_array`
|
|
376
|
+
|
|
377
|
+
`development/burnup_array` is a non-production, array-based burnup
|
|
378
|
+
prototype. It is **outside the default/release test gate** — `testpaths`
|
|
379
|
+
does not include it, `run_unified_tests.py` does not run it, and it is not
|
|
380
|
+
part of `core` or `full`.
|
|
381
|
+
|
|
382
|
+
Its explicit, separate diagnostic invocation:
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
python -m pytest development/burnup_array/tests -q
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
As of this writing that command **fails during collection**, not just an
|
|
389
|
+
individual test: `test_consumption_calcs_array.py` imports
|
|
390
|
+
`burnup_array_calcs` as a top-level module, but `burnup_array_calcs.py`
|
|
391
|
+
itself uses a relative import (`from .burnup_array_kernel import ...`),
|
|
392
|
+
raising `ImportError: attempted relative import with no known parent
|
|
393
|
+
package`. This is a known, tracked failure — not a skip, and not silently
|
|
394
|
+
part of the supported suite. Maintain it independently of the library's
|
|
395
|
+
supported test suites.
|
|
396
|
+
|
|
397
|
+
For package-validation workflows where you want to ensure tests are running
|
|
398
|
+
against the installed package (not local `src/`), use:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
python tests/run_unified_tests.py --suite core --installed-only
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Packaging pipeline usage
|
|
405
|
+
|
|
406
|
+
PyPI wheel/sdist check:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
python -m pip install build twine
|
|
410
|
+
python -m build
|
|
411
|
+
python -m twine check dist/*
|
|
412
|
+
python -m pip install .
|
|
413
|
+
python tests/run_unified_tests.py --suite core --installed-only
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Conda recipe `test:commands` example:
|
|
417
|
+
|
|
418
|
+
```yaml
|
|
419
|
+
test:
|
|
420
|
+
commands:
|
|
421
|
+
- python tests/run_unified_tests.py --suite core --installed-only
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The Conda recipe lives in `conda-recipe/`. See `conda-recipe/README.md` for
|
|
425
|
+
build and test commands.
|
|
426
|
+
|
|
427
|
+
Reference-validation tooling and deterministic golden-data verification live
|
|
428
|
+
under `tests/cpp_parity_live/`. See [CODEBASE.md](docs/CODEBASE.md) for the
|
|
429
|
+
current test tiers and maintenance guidance. Release provenance and attribution
|
|
430
|
+
for bundled FOFEM-derived data are in
|
|
431
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
PyFOFEM source is licensed under the [MIT License](LICENSE).
|
|
436
|
+
Bundled FOFEM-derived runtime tables and their provenance are described in
|
|
437
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|