ISO 20414 Test 19: incapacitation by toxic gases
| Component | Gas FED and incapacitation (Models › FED) |
| Level | FDS case: a full run on FDS output |
| Asset | assets/iso_table22_coupled (five cases, a–e) |
| Expected value from | hand calculation, and FDS’s own FED device |
| Status | passes for CO, CO₂, O₂, HCN, NOₓ and the NO₂ irritant term; NO and the other six irritants not yet tested (#257) |

Download the files for this page (34 kB zip)
What is tested
ISO 20414:2020, Test 19 (Table 22): an occupant stands still in gas, and the time at which its dose reaches FED = 1 must equal a hand calculation. The standard asks for the test to be repeated for each hazardous condition the model has; here that is done for CO, CO₂ and O₂ (cases a–d) and, in case e, for HCN, NO₂ and the CO₂ factor on those terms. Four gas mixtures switch the first three on one at a time. A wrong unit (ppm against %), a missing species read as zero, a dropped CO₂ factor or a missing O₂ gate (case c would cross 13 s early, case d 3 s) each moves the crossing in at least one case.
Equation
The gas FED of Purser (SFPE Handbook, 4th ed., Sec. 2 Ch. 6), as coded in
FDS 6.10.1’s FED function
(Source/func.f90;
Models › FED; the published law is on
Fundamentals › Asphyxiant FED). CCO is in ppm,
CCO₂ and CO₂ are in volume percent, and rates are
per minute. The CO coefficient is Purser’s light-work value,
3.317 × 10⁻⁵ × 25 L/min ÷ 30 % COHb = 2.764 × 10⁻⁵:
r_CO = 2.764e-5 x (C_CO)^1.036
HV_CO2 = exp(0.1903 x C_CO2 + 2.0004) / 7.1 if C_CO2 > 0
HV_CO2 = 1 if C_CO2 = 0
r_O2 = 1 / exp[8.13 - 0.54 x (20.9 - C_O2)] if C_O2 < 20 %, else 0
r = r_CO x HV_CO2 + r_O2HV_CO₂ = 1 at CCO₂ = 0 is FDS’s convention, not Purser’s law: Purser’s expression gives exp(2.0004)/7.1 = 1.041 there, so FDS’s factor jumps at zero. Only case c tests that branch; in air with 0.04 % CO₂, HV_CO₂ = 1.049. With the optional gases the sum is r = (r_CO + r_CN + r_NOx + r_irr) × HV_CO₂ + r_O₂; they are zero in cases a–d and switched on in case e.
The gas is constant, so FED grows linearly, FED(t) = r t / 60, and reaches 1 at
$$ t^{*} = \frac{60}{r_{\mathrm{CO}}\,\mathrm{HV}_{\mathrm{CO_2}} + r_{\mathrm{O_2}}}\ \text{s}. $$Setup

FDS: the ISO room, 10 × 10 × 3 m, no fire, no vents, one mesh with 0.5 m cells. At t = 0 a single
&INITfills it with one mixture per case. Gas slices at 1.6 m, which FDS places at 1.5 m. AFEDdevice sits at (5, 5, 1.6) m, next to the occupant.Cases from the FDS+Evac guide’s own FED test (Korhonen 2021, §4.2 Component Testing, Fig. 8 “A FED test”); ISO gives no values. The guide compares the agent’s FED with a worksheet and with an FDS
FEDdevice “at the position of the agent”, as this page does:case CO₂ CO O₂ terms switched on a 2 % 0.1 % 15 % CO, CO₂ factor, O₂ b 0 0 12 % O₂ only c 0 0.1 % 21 % CO only d 3.43 % 0.1 % 21 % CO and CO₂ factor Case e (HCN, NO₂) is described below.
Occupant: one, near the centre (spawn box 4.4–5.6 m; it lands at (4.92, 4.68) m), held in place by a pre-evacuation time drawn from [1.2 × 10⁷, 2 × 10⁷] s, the method ISO prescribes (> 10⁷ s).
Runs: one per case,
--incapacitation-mode deterministic(the default, given explicitly), so the occupant is incapacitated at FED = 1, not at a random threshold. The FED is updated every 1 s (--smoke-update-interval 1).
Expected
The concentrations are read from the FDS slices with fdsreader,
independently of pyFDS-Evac. Every cell at every slice time holds the same
value, and each species is within 2 × 10⁻⁵ of what the deck prescribes.
| case | CO [ppm] | CO₂ [%] | O₂ [%] | r_CO [/min] | HV_CO₂ | r_O₂ [/min] | t*, hand [s] | FDS FED device [s] |
|---|---|---|---|---|---|---|---|---|
| a | 1000.01 | 2.00000 | 15.0003 | 0.035444 | 1.52334 | 0.0071250 | 981.71 | 981.65 |
| b | 0 | 0 | 12.0002 | 0 | 1 | 0.036004 | 1666.49 | 1666.49 |
| c | 1000.01 | 0 | 21.0003 | 0.035444 | 1 | 0 | 1692.82 | 1692.72 |
| d | 1000.01 | 3.43000 | 21.0003 | 0.035444 | 1.99977 | 0 | 846.50 | 846.45 |

FDS’s device crosses up to 0.10 s earlier. FDS uses 2.7641667 × 10⁻⁵ for the CO coefficient, not 2.764 × 10⁻⁵. That speeds the CO term by 6.0 × 10⁻⁵, which accounts for the whole gap: after correcting for it, FDS and the hand calculation agree to 2 × 10⁻⁷ of t*.
Before #194, pyFDS-Evac applied Purser’s expression at zero CO₂ as well, HV_CO₂ = 1.041, which gave 1626 s for case c. It now follows FDS’s convention, HV_CO₂ = 1 without CO₂; FDS’s device gives 1692.7 s, as the hand calculation does.
Case e: HCN and nitrogen dioxide
ISO asks for the test to be repeated for each hazardous condition. Case e
does this for the optional gases that FDS’s FED function sums: 200 ppm HCN
and 50 ppm NO₂ with 2 % CO₂ and 21 % O₂, and no CO. These values are not
from the guide’s Fig. 8. CO is zero, not 0.1 %, so the whole dose comes from
the terms under test; O₂ is above the 20 % gate. The deck adds the two
species, their slices at 1.6 m and the same FED device. The rate in the
FDS form, with CCN = CHCN − (CNO + CNO₂)
= 150 ppm (CNO = 0):
r_CN = (exp(C_CN/43) - 1) / 220 = 0.14424 /min
r_NOx = (C_NO + C_NO2) / 1500 = 0.03333 /min
r_irr = C_NO2 / 1900 = 0.02632 /min
HV = exp(0.1903 x 2 + 2.0004)/7.1 = 1.52334
r = (r_CN + r_NOx + r_irr) x HV = 0.31059 /min t* = 60/r = 193.18 sFDS adds NO₂ twice, as NOₓ and as an irritant (its built-in lethal exposure dose for NITROGEN DIOXIDE is 1900 ppm·min), and applies HV_CO₂ to the whole narcotic and irritant sum; pyFDS-Evac does the same. The two codes agree in form. FDS writes the HCN offset as 0.00454545 where pyFDS-Evac uses 1/220, a difference of 3 × 10⁻⁸ of the term.
| t* [s] | |
|---|---|
| hand calculation, prescribed gas | 193.180 |
| hand calculation, gas as the FDS slices hold it | 193.183 |
FDS FED device | 193.1835 |
| pyFDS-Evac: first update with FED ≥ 1, and the stop | 194.0 (+0.82 s) |
The FDS device is 0.0035 s (1.8 × 10⁻⁵ of t*) later than the hand
calculation on the prescribed gas. The slices do not hold exactly the
prescribed gas: they show 199.9997 ppm HCN, 50.0011 ppm NO₂ and 1.999994 %
CO₂, where the deck’s mass fractions with its declared molar masses give
200.0002, 50.00002 and 1.999997. Rounding the mass fractions to 6 digits does
not explain this; the offset comes from FDS’s internal conversion, and the
cause is not established. The rate computed from the slice values gives
193.183 s, and FDS and the hand calculation then agree to 10⁻⁷. The simulated stop lies in
[t*, t* + 1 s), as in the four other cases. The automated test (tests/test_iso_table22_coupled_e.py) holds the
hand calculation as literal constants and imports nothing from pyFDS-Evac. It
fails if the correction is dropped (crossing at 74 s) or if HV_CO₂ does not
multiply the HCN term (255 s, beyond the end of the deck). Case e is not in the
figures below.
Result

In every case the occupant’s FED is on the hand line to within 3 × 10⁻¹⁴, and it is incapacitated at the first FED update after t*.

| case | Expected t* | FDS device | Simulated: FED ≥ 1 and stop | Simulated − expected | Pass |
|---|---|---|---|---|---|
| a | 981.71 s | 981.65 s | 982.0 s | +0.29 s | yes |
| b | 1666.49 s | 1666.49 s | 1667.0 s | +0.51 s | yes |
| c | 1692.82 s | 1692.72 s | 1693.0 s | +0.18 s | yes |
| d | 846.50 s | 846.45 s | 847.0 s | +0.50 s | yes |
| Check | Expected | Simulated |
|---|---|---|
| gas at the occupant against the deck | within 10⁻⁴ | within 2.0 × 10⁻⁵; exactly 0 where the deck has none |
| FED rate against the hand calculation | equal | relative difference ≤ 1.1 × 10⁻¹⁵ |
| FED(t) against the hand line | equal | max difference 3.1 × 10⁻¹⁴ |
| occupant position | fixed | moved 0 m; not evacuated |
Pass criteria
- Gas arrives unaltered. Each FDS slice equals the prescribed value to
10⁻⁴ (relative), and is exactly 0 where the deck has none. The deck writes
mass fractions to 6 significant digits from molar masses given to 3
decimals; each rounding is below 2 × 10⁻⁵. The gas the occupant sampled
(
fed_history.csv) equals the slice to the same 10⁻⁴. - Dose. |FED − r t/60| ≤ 10⁻⁹ at every update: both sides are the same arithmetic on the same numbers, so only round-off may differ.
- Crossing and stop. The first update with FED ≥ 1 and the
incapacitation are the same row, and \(t^{*} \le t < t^{*} + \Delta t\)
with Δt = 1 s, the FED update interval. ISO asks for the same time;
the model can only report the first update after t*, so the offset
lies in [0, Δt) and shrinks with
--smoke-update-interval. The automated test allows 2 Δt. - FDS agrees with the hand calculation. After correcting for FDS’s CO coefficient, |tFDS − t*| ≤ 10⁻⁶ t*. FDS writes the device with 8 significant digits, so interpolating the crossing is good to about 10⁻⁷.
- The occupant stays put. Position unchanged and not evacuated: otherwise the exposure, and the comparison, would be meaningless.
Run it yourself
The FDS output (slices only, 256 kB for a–d and 29 kB for e) is committed in
assets/iso_table22_coupled/fds/, and tests/test_iso_table22_coupled.py
and tests/test_iso_table22_coupled_e.py run on it in CI. For the figures you also need FDS’s _devc.csv, which is
in the project’s data folder (fds-evac-data/iso_table22_coupled/), or
rerun FDS, about 40 s per case:
uv run python assets/iso_table22_coupled/build_geometry.py # writes decks and configs
for c in a b c d e; do
mkdir -p <out>/fds/$c && cp assets/iso_table22_coupled/iso_table22_$c.fds <out>/fds/$c/
(cd <out>/fds/$c && fds iso_table22_$c.fds)
doneThen run pyFDS-Evac, about 40 s per case, and draw the figures:
for c in a b c d; do
uv run python run.py --scenario assets/iso_table22_coupled/config_$c.json \
--fds-dir <out>/fds/$c --seed 420 --incapacitation-mode deterministic \
--smoke-update-interval 1 \
--output-sqlite <out>/evac/$c/run.sqlite \
--output-fed-history <out>/evac/$c/fed_history.csv \
--vis-cache <out>/evac/$c/vis_cache.npz
done
uv run python scripts/verification/iso_test19_figures.py --data <out>The script prints each check and stops at the first pass criterion that fails.
Limits
- Of the optional gases, only HCN and NO₂ are run (case e). NO is not used, so the NO half of the HCN − NOₓ correction and of the NOₓ sum is untested; the irritants HCl, HBr, HF, SO₂, acrolein and formaldehyde are untested too, though FDS sums them (#257). The convective heat dose is checked in Heat dose in a uniform room.
- A missing O₂ gate is caught, a misplaced one is not: any gate between 15 % and 21 % gives the same four times.
- One occupant per case and the deterministic threshold. The opt-in probabilistic threshold is checked on 100 agents in CO dose in a uniform room.
- A uniform, constant field cannot show whether the gas is sampled at the agent’s current position (#24) or at the right height; the height is checked in test_fed_slice_height.py.
- The automated test takes its expected values from pyFDS-Evac’s own FED functions and allows 2 s; only the figure script uses the independent hand calculation and FDS’s device (#249).
- The FED equations are the Purser forms FDS uses, not the SFPE 5th edition ones (#149).
- A second version of this test,
assets/ISO-table22intests/test_fed.py, supplies the gas directly without FDS. It checks the dose accumulator only, for one mixture (CO 0.1 %, CO₂ 5 %, O₂ 12 %).
References
- ISO 20414:2020. Fire safety engineering — Verification and validation protocol for building fire evacuation models, Table 22 (Test 19).
- Korhonen, T. (2021). Fire Dynamics Simulator with Evacuation: FDS+Evac. Technical Reference and User’s Guide (FDS 6.7.6, Evac 2.6.0-draft), §4.2, Fig. 8. VTT Technical Research Centre of Finland. github.com/tkorhon1/FDS-Evac-Guide.
- Purser, D. A. (2008). Assessment of hazards to occupants from smoke, toxic gases, and heat. In SFPE Handbook of Fire Protection Engineering, 4th ed., Sec. 2, Ch. 6. NFPA.