Fractional effective dose
Based on: Asphyxiant fractional effective dose, Irritant gases, Heat and Incapacitation thresholds.
Symbols follow the notation table. The model codes the Purser sum in the form of the FDS+Evac guide (Korhonen 2021, §3.4), which adds irritants into the dose; ISO 13571 keeps them in a separate concentration endpoint. The published equations are on the Fundamentals pages linked above.
Background: the Concepts page and the talk A Modular Workflow for Visibility-Aware Evacuation Modelling.
Coded form
FedComponents.total_rate_per_min (pyfds_evac/core/fed.py) returns the
gas FED rate [1/min]
and DefaultFedModel integrates it over time, with t in minutes. Each term
is one function in fed.py:
| Term | Function | Coded rate [1/min] | Input |
|---|---|---|---|
| CO | _co_fed_rate_per_minute | \(2.764 \times 10^{-5}\, C_{\mathrm{CO}}^{1.036}\) | CO (ppm) |
| CN | _cn_fed_rate_per_minute | \(\max\bigl(0,\ \exp(C_{\mathrm{CN}}/43)/220 - 1/220\bigr)\), \(C_{\mathrm{CN}} = \max\bigl(0,\ C_{\mathrm{HCN}} - (C_{\mathrm{NO}} + C_{\mathrm{NO_2}})\bigr)\) | HCN, NO, NO2 (ppm) |
| NOₓ | _nox_fed_rate_per_minute | \((C_{\mathrm{NO}} + C_{\mathrm{NO_2}})/1500\) | NO, NO2 (ppm) |
| Irritants | _irritant_fld_rate_per_minute | \(\sum_i C_i / F_{\mathrm{FLD},i}\) | seven irritants (ppm) |
| HV_CO2 | _hyperventilation_factor | \(\exp(0.1903\, C_{\mathrm{CO_2}} + 2.0004)/7.1\) for \(C_{\mathrm{CO_2}} > 0\) (at least 1.041), and 1 without CO2, as in FDS; a run warns once if CO is sampled with zero CO2, which points to a deck with no ambient CO2 (a factor, not a rate) | CO2 (vol %) |
| O2 | _o2_hypoxia_rate_per_minute | \(1/\exp\bigl(8.13 - 0.54\,(20.9 - C_{\mathrm{O_2}})\bigr)\); 0 at or above o2_threshold_percent, 20.0 % by default | O2 (vol %) |
The lethal Ct doses \(F_{\mathrm{FLD},i}\) [ppm·min] are constants in
_irritant_fld_rate_per_minute, taken from Table 2 of the FDS+Evac guide:
| Species | HCl | HBr | HF | SO2 | NO2 | acrolein | formaldehyde |
|---|---|---|---|---|---|---|---|
| F_FLD | 114000 | 114000 | 87000 | 12000 | 1900 | 4500 | 22500 |
Gas species are read from FDS slice outputs via fdsreader. CO, CO2 and O2
are required. The optional species (HCN, NO, NO2, HCl, HBr, HF, SO2, acrolein,
formaldehyde), the guide’s optional terms, are loaded when available; a
missing species reads 0 and contributes nothing. With only the three required
species the sum reduces to the FDS+Evac default,
\(\dot{\mathrm{FED}}_{\mathrm{CO}}\,\mathrm{HV}_{\mathrm{CO_2}} + \dot{\mathrm{FED}}_{\mathrm{O_2}}\).
Convective heat
The heat dose is opt-in (--enable-heat-fed), as FDS+Evac has none, and is
a running total of its own, never added to the gas FED. By default it is
ISO 13571:2012 Eq. (9) for fully clothed subjects (fed.py,
_heat_fed_rate_per_minute),
with T in °C. Everything else about it (the other laws, the total-flux method, the threshold and its limits) is specified on Models › Heat.
Tenability: irritant slowdown and incapacitation
TenabilityConfig (fed.py) adds two rules. The slowdown and the gas
stop need the gas FED model; the heat stop needs only the heat FED model
(run_config.py, _build_tenability_config).
- Irritant slowdown, off by default. FDS+Evac has no irritant slowdown, so
enable_fic_speeddefaults to false andrun.pyswitches the rule on only with--enable-fic-speed. Before it became opt-in, it was on whenever a gas FED model was loaded. When on,default_ficsums \(C_i / F_{\mathrm{FIC},i}\) over the same seven irritants (constants in_FIC_COEFFS_PPM, not integrated over time). At each FED update where FIC > 0, the agent’s irritant factor is set to \(g = \max(\texttt{fic\_min\_factor},\ 1 - \texttt{fic\_alpha}\cdot\mathrm{FIC})\) (scenario.py,run_scenario) and multiplies the smoke factor. The rule is a pyFDS-Evac assumption with no known source (#147). When FIC is exactly 0 the last factor stays in force (#142). - Incapacitation. Once the cumulative gas FED or heat FED reaches the
agent’s threshold for that dose, its desired speed is set to zero and it
remains as a static obstacle. An agent incapacitated during pre-movement is
released again when its pre-movement time ends
(#145). In
probabilisticmode each agent draws each threshold once, from a stream seeded by the run’s seed and the agent’s spawn order, as_sample_thresholddoes: \(D_i = \texttt{fed\_threshold} \cdot \exp(\sigma Z)\), \(Z \sim N(0, 1)\). Indeterministicmode every agent uses the threshold itself. Both doses are deterministic by default: the gas dose as in FDS+Evac, and the heat dose since no population spread for heat is published. The heat dose uses the gas thresholdfed_threshold, as ISO 13571:2012 asks for one threshold (§5.4);heat_fed_thresholdoverrides it, a departure from ISO (Models › Heat). An agent reaching its threshold in a run does not on its own show whether a design is acceptable; FED 1 describes the median occupant (see Incapacitation thresholds).
| Field | Default | CLI flag |
|---|---|---|
enable_fic_speed | False | --enable-fic-speed |
fic_alpha | 0.7 | --fic-alpha |
fic_min_factor | 0.3 | --fic-min-factor |
fed_threshold | 1.0 | --fed-threshold |
incapacitation_mode | "deterministic" | --incapacitation-mode |
susceptibility_sigma | 0.94 | --susceptibility-sigma |
heat_fed_threshold | none, fed_threshold | --heat-fed-threshold |
heat_incapacitation_mode | "deterministic" | --heat-incapacitation-mode |
heat_susceptibility_sigma | 0.94 | --heat-susceptibility-sigma |
o2_threshold_percent (DefaultFedConfig) | 20.0 | --o2-threshold-percent |
The gas and heat FED are updated every DefaultFedConfig.update_interval_s,
which run.py sets from --smoke-update-interval. --disable-tenability
turns off the slowdown and both stops; the doses are still computed. The FED
history CSV (--output-fed-history) carries the columns fic,
fic_speed_factor and incapacitated.

(a) Frantzich–Nilsson factor f(K) [-] against K [1/m], floor 0.1.
(b) Irritant factor g(FIC) [-] against FIC [-], floor 0.3. (c) The product
f(K)·g(FIC) [-], with contours at 0.25, 0.5 and 0.75; where both floors apply
it is 0.1 × 0.3 = 0.03. FED does not appear on these axes; it only sets the speed to
zero at the agent’s threshold. Script: scripts/generate_tenability_curves.py.
FDS input pitfalls
- Conflicting
&INITrecords silently zero out prescribed gas concentrations. FDS resets the entire domain’s species composition on each&INITrecord that has noXBbounding box, so a second&INIT(e.g. one that only sets soot) overwrites an earlier one (e.g. one that sets CO/CO2/O2), leaving those species at 0 with no warning. All species prescribed via&INITin a test deck must go in a single record. This is an FDS input-authoring pitfall, not a pyFDS-Evac bug; its symptom is near-zero toxic gas readings. - Units. FDS writes volume fractions;
FdsFedField.sample_inputs(fed.py) multiplies CO, CO₂ and O₂ by 100 to get vol %, and the other gases by 10⁶ to get ppm.
Verification
- ISO 20414 Test 19: passes for CO, CO₂ and O₂. The HCN, NOₓ and irritant terms are not verified end to end (#257), and the coupled check compares against the code’s own formula (#249).
- CO dose in a sealed room: FDS cases at constant CO.
- FED and FIC in every zone
(
tests/verification/test_fed_fic_all_zones.py). - Equation-level checks of every coded term
(
tests/test_fed.py).
What is not modelled
- Radiant heat from hot surfaces or a flame, except as the excess f·(U − 4σT_s⁴) from FDS
INTEGRATED INTENSITYwith--heat-radiant-source integrated-intensityand a user factor. Otherwise the heat dose is convective, or with--heat-fed-method total-fluxadds the radiation of the gas at the head, or with--heat-regime layerthat of a hot upper layer (Heat). - Effects of heat on route choice or walking speed: the heat FED only incapacitates.
- FED activity level: the CO term is fixed at light work; rest and heavy work are not supported (#135).
- Removing incapacitated agents: they stay in the simulation, so a run with
any incapacitated agent reports
evacuation_time=max_simulation_time(#141), and an agent incapacitated during its pre-movement time walks off when it ends (#145); see Limitations. - Height-relative FED and smoke sampling: extinction, temperature and
each gas are sampled from the horizontal FDS slice closest to
--smoke-slice-height(default 1.6 m, FDS+EvacHUMAN_SMOKE_HEIGHT). All agents share these slices regardless of their individual heights. Pathfinder samples at 90 % of each occupant’s height, which is more accurate for scenarios with mixed-height populations (children, wheelchair users). A per-agent sampling height would require either multiple slice outputs at different elevations or 3-D slice data, and is a known approximation of the current model.
Usage
See docs/usage.md for the full catalogue of
run.py flags (scenario, FDS coupling, FED, rerouting, tenability)
and the post-processing scripts. Note: if an agent
sample lies outside the FDS domain the implementation falls back to
ambient conditions.
Deviations from the literature
The published forms are on Asphyxiant FED
and Irritant gases. The gas sum has the Purser /
FDS+Evac guide structure (fed.py, FedComponents.total_rate_per_min), not the ISO 13571 one. Within it, the
code follows the guide, which cites the 3rd edition of the SFPE Handbook,
rather than the 5th edition, except that the HCN term is computed as FDS
computes it
(#149):
- HCN. The code computes the exponential term as FDS does,
\(\exp(C_{\mathrm{CN}}/43)/220 - 1/220\) with
\(C_{\mathrm{CN}} = C_{\mathrm{HCN}} - (C_{\mathrm{NO}} + C_{\mathrm{NO_2}})\)
(
_cn_fed_rate_per_minute), not the 5th-edition power law (Eq. 63.24; #149). The FDS code that FDS+Evac runs subtracts NO + NO2, with the offset 0.00454545, about 1/220 (FDS 6.7.6func.f90, functionFED), and has done so since firemodels/fds 694e033 (2011); earlier versions had no HCN term. The FDS+Evac guide instead writes the offset as 0.0045 (Eq. 14) and \(C_{\mathrm{CN}} = C_{\mathrm{HCN}} - C_{\mathrm{NO_2}}\) (Eq. 15), which FDS and FDS+Evac never computed; pyFDS-Evac followed that text before (#159). The FDS verification caseFED_FICseparates the two forms and is reproduced intests/verification/test_fed_verif.py(A2.8). The 5th edition also subtracts NO + NO2, because methaemoglobin formed by NO and NO2 binds cyanide (Ch. 63, p. 2370): with coefficient 1 in Eq. 63.26 (p. 2362) but 0.67 in the note to the FED equation (p. 2372), so the chapter is inconsistent. No source we read supports NO2 alone, and the 3rd-edition wording the guide cites is not verified (#152). Organic nitriles are ignored. Ch. 63 gives the critical range of its 5th-edition time-to-incapacitation relationship as about 80 to 180 ppm, from primate and human data (p. 2361). That range is not documented for the exponential term; that the term is an extrapolation below 80 ppm is our inference (#159). - CO₂. Eq. 63.34 (
fed.py,_hyperventilation_factor), not its simplification Eq. 63.35 used in Eq. 63.38. The 70 L/min limit on \(V_E\times VCO_2\) is not applied, and the CO₂ asphyxiant endpoint \(F_{I_{CO_2}}\) is not computed. - CO. Fixed at light work (
fed.py,_co_fed_rate_per_minute), Eq. 63.18 at its default \(V_E\) and D. - O₂. The rate is zero at or above
o2_threshold_percent, 20.0 % O₂ by default, the guard of FDS+Evac’s code (theFEDfunction of FDS 6.7.6func.f90adds the term only when X_O2 < 0.20); neither Purser nor the guide has it. It stops a tiny ambient rate from accumulating over long runs or outside the FDS domain.--o2-threshold-percent 19.5restores the previous default, the OSHA limit that Pathfinder uses. The O₂ rate is Handbook Eq. 63.50 per minute, with no factor 60; see the warning below on the FDS+Evac guide’s Eq. 18.

Time to incapacitation by low oxygen, \(t_{IO}\) [min], Eq. 63.50: solid
over the decompression data (3.9–9.6 % O₂), dashed above. pyFDS-Evac
evaluates this law up to o2_threshold_percent (20 % by default, dash-dot
line, about 2090 min) and adds nothing at or above it; everything between
about 10 % and the gate is extrapolation. Dotted: 15 % O₂, down to which
there is little effect in humans (Purser & McAllister 2016, p. 2364). Figure
and sources: Asphyxiant FED. Script:
scripts/figures/fundamentals_fed_o2.py.
Warning
FDS+Evac guide, Eq. 18 (low O₂). The guide (Korhonen 2021) divides the O₂ term by 60 while stating that t is in minutes. Taken literally, that gives an O₂ dose 60 times smaller than Purser’s Eq. 63.50. The code FDS+Evac runs does not do this: FDS divides by 60 only because its time step is in seconds. pyFDS-Evac uses Eq. 63.50 per minute, as the code does. If you rebuild FDS+Evac’s FED from the guide, leave the 60 out.
Our reading is that the 60 survives from the 2009 guide, which gave the FED equations with t in seconds; the evidence is on Asphyxiant FED.
The irritant slowdown \(g\), when enabled (fed.py, TenabilityConfig.fic_alpha, TenabilityConfig.fic_min_factor), is multiplied with the smoke
factor (direct_steering_runtime.py, set_agent_fic_factor). Its constants were not found
in the Handbook, the FDS+Evac guide or evac.f90
(#147). The
Handbook uses a different curve and adds the smoke and irritant losses instead
of multiplying them (Eq. 63.14). Because the Frantzich–Nilsson smoke contained
acetic acid, \(f(K)\) already includes irritant slowing, so multiplying it by
\(g\) partly counts irritancy twice
(#153).
The heat laws and their deviations are on Models › Heat.
The log-normal σ of both
thresholds (fed.py, TenabilityConfig.susceptibility_sigma, TenabilityConfig.heat_susceptibility_sigma) is, for the gas dose, a compromise between
two bin edges of NIST TN 1797: it puts 10 % of agents below FED 0.3 and 88 %
below 3 (see Incapacitation thresholds
and #148). The
heat dose is deterministic by default; in probabilistic mode it reuses the
same σ without a data basis.