Fractional effective dose

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]

$$ \dot{\mathrm{FED}} = \bigl(\dot{\mathrm{FED}}_{\mathrm{CO}} + \dot{\mathrm{FED}}_{\mathrm{CN}} + \dot{\mathrm{FED}}_{\mathrm{NO_x}} + \dot{\mathrm{FLD}}_{\mathrm{irr}}\bigr) \times \mathrm{HV}_{\mathrm{CO_2}} + \dot{\mathrm{FED}}_{\mathrm{O_2}}, $$

and DefaultFedModel integrates it over time, with t in minutes. Each term is one function in fed.py:

TermFunctionCoded 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 defaultO2 (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:

SpeciesHClHBrHFSO2NO2acroleinformaldehyde
F_FLD11400011400087000120001900450022500

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),

$$ \dot{\mathrm{FED}}_{\mathrm{heat}} = T^{3.61} / (4.1 \times 10^{8}) \quad [1/\mathrm{min}], $$

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_speed defaults to false and run.py switches 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_fic sums \(C_i / F_{\mathrm{FIC},i}\) over the same seven irritants, not integrated over time. The constants in _FIC_COEFFS_PPM are the SFPE incapacitation column of Table 63.6, equal to FDS User’s Guide Table 22.3 and FDS+Evac guide Table 2, so this FIC is neither the ISO 13571 FEC nor the escape-impairment FIC (see Irritant gases). 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); how it compares with the published rule is below. 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. The speed is never restored (pre-movement, smoke, FIC, steering zones and checkpoints do not undo it), and the agent is not steered, rerouted or removed. Its exposure record ends with the collapse row. The run ends when only incapacitated agents remain (Reading the results). In probabilistic mode each agent draws each threshold once, from a stream seeded by the run’s seed and the agent’s spawn order, as _sample_threshold does: \(D_i = \texttt{fed\_threshold} \cdot \exp(\sigma Z)\), \(Z \sim N(0, 1)\). In deterministic mode 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 threshold fed_threshold, as ISO 13571:2012 asks for one threshold (§5.4); heat_fed_threshold overrides 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).
FieldDefaultCLI flag
enable_fic_speedFalse--enable-fic-speed
fic_alpha0.7--fic-alpha
fic_min_factor0.3--fic-min-factor
fed_threshold1.0--fed-threshold
incapacitation_mode"deterministic"--incapacitation-mode
susceptibility_sigma0.94--susceptibility-sigma
heat_fed_thresholdnone, fed_threshold--heat-fed-threshold
heat_incapacitation_mode"deterministic"--heat-incapacitation-mode
heat_susceptibility_sigma0.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.

Three panels: f(K) against K, g(FIC) against FIC, and their product as a heat map over K and FIC

(a) Frantzich–Nilsson factor f(K) [-] against K [1/m], floor 0.1. (b) Irritant factor g(FIC) [-] against FIC [-], floor 0.3; g has no known source and is off by default. (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 &INIT records silently zero out prescribed gas concentrations. FDS resets the entire domain’s species composition on each &INIT record that has no XB bounding 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 &INIT in 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

What is not modelled

  • Radiant heat from hot surfaces or a flame, except as the excess f·(U − 4σT_s⁴) from FDS INTEGRATED INTENSITY with --heat-radiant-source integrated-intensity and a user factor. Otherwise the heat dose is convective, or with --heat-fed-method total-flux adds the radiation of the gas at the head, or with --heat-regime layer that 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 where they collapsed until the run ends. The run ends when only incapacitated agents remain and no flow window is open (end_reason all_mobile_left), and evacuation_time is then the end of the loop, not an egress time; 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+Evac HUMAN_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.

Outside the FDS domain. Where no gas slice covers the agent, the gases read ambient air: CO 0 %, CO₂ 0 % (the atmospheric 0.04 % is not used), O₂ 20.9 % and every optional species 0 ppm, so the FED rate is 0. This matches FDS+Evac, and no warning is logged per sample. The FED history marks such rows with in_fds_domain = False, and the run counts them at the end. A point that some loaded gas slices cover and others do not stops the run with a ValueError that names the point, the time and the missing quantities (#427). With --require-fds-coverage any point outside stops the run. Details on FDS slice sampling.

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.6 func.f90, function FED), 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 case FED_FIC separates the two forms and is reproduced in tests/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 (the FED function of FDS 6.7.6 func.f90 adds 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.5 restores 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.

Low-oxygen time to incapacitation against O2 from 3.9 to 20.9 % on a log time axis: a straight line, solid over the decompression data from 3.9 to 9.6 % and dashed above, with vertical lines at 15 % (140 min) and 20 % (about 2090 min)

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 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.

Irritant slowdown against the published rule

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, Eq. 63.13, and adds the smoke and irritant losses instead of multiplying them (Eq. 63.14; see Irritant gases). Both differences make the coded rule milder, except at very low FIC:

FICcoded \(g\)Eq. 63.13
0.10.930.71
0.20.860.31
0.50.650.08
10.300
  • The curve. With the defaults fic_alpha = 0.7 and fic_min_factor = 0.3, \(g\) is above Eq. 63.13 for FIC from about 0.017 to 1. Below about 0.017 it is lower, by at most 0.002 (our arithmetic).
  • The combination. For the same smoke fraction \(F_s\) and irritant fraction \(F_i\), the product exceeds the additive rule by \(F_s F_i - (F_s + F_i - 1) = (1 - F_s)(1 - F_i) \ge 0\) (our identity). For the same \(F_i\), multiplying is therefore never more severe than adding.
  • FIC ≥ 1 stops no agent. The sources predict incapacitation at FIC = 1 (Purser 2003, p. 99; Ch. 63, p. 2344). In the code, with the default fic_min_factor of 0.3, \(g\) floors at 0.3, so the agent walks on at 0.3 of its smoke-reduced speed. FIC never marks an agent incapacitated; irritants count toward incapacitation only through \(FLD_{irr}\) inside the gas FED. With --fic-min-factor 0, \(g\) reaches 0 at FIC = 1/fic_alpha (about 1.43), but the agent is still not marked incapacitated (#398). \(g\) is recomputed only while FIC is above 0: the agent walks on once FIC falls but stays above 0, and if FIC returns to exactly 0 the factor of 0 stays in force and the agent stands still for the rest of the run without being counted as incapacitated (#142).

Because the Frantzich–Nilsson smoke contained acetic acid, \(f(K)\) already includes irritant slowing, so multiplying it by \(g\) partly counts irritancy twice (#153).

Two panels. (a) Speed factor against FIC from 0 to 1.2: Eq. 63.13 falls from 1 to 0 at FIC 1 and goes negative beyond it, while the coded g falls linearly from 1 to its floor of 0.3 at FIC 1. (b) Combined walking-speed fraction against FIC for smoke fractions 1, 0.75 and 0.5: the published additive rule reaches zero and goes negative, while the coded product stays at or above 0.15

(a) Speed factor [-] against FIC [-]: Eq. 63.13 (thin solid, a concept curve; dotted beyond FIC = 1, outside its range) and the coded \(g\) with its defaults (dashed, no source). (b) Walking-speed fraction [-] combining smoke fraction \(F_s\) = 1, 0.75 and 0.5 with the irritant effect: Eq. 63.14 with Eq. 63.13 (solid) against the code’s \(F_s \cdot g\) (dashed), one colour per \(F_s\). The gap between a solid and a dashed line of the same colour is the effect of both the curve and the combination; the identity above covers the combination alone. Grey: where the published sum is at most 0, which neither source clips. Script: scripts/figures/models_fic_combination.py.

Last updated on