Heat dose

Heat dose in a uniform room

ComponentHeat FED and heat incapacitation (Models › Heat)
LevelFDS case: a full run on FDS output
Assetassets/fed_incap_heat_150c (also _100c and _200c)
Expected value fromclosed form of SFPE Eq. 63.44 (ISO 13571:2012 Eq. (10), --heat-clothing unclothed) at the deck temperature; per agent, hand sum of Eq. 63.48 on FDS’s own TEMPERATURE slice
Statuspasses with --heat-clothing unclothed; checks the pipeline, not the law (the law against the Handbook’s tables: test_heat_fed_verif.py, A3.8–A3.10, A3.12); the default law, ISO Eq. (9), has expected times only (#307)

100 agents walk a loop in a room at 150 °C; their colour shows the heat dose, and all of them stop at 120 s

What is tested

Whether the heat dose an agent accumulates in pyFDS-Evac is the dose the equation gives for the gas temperature FDS computed at that agent, and whether the agent stops when the dose reaches its threshold. FDS holds the room at the deck temperature everywhere, so every agent follows one curve, the closed form. Any difference from the hand calculation comes from the code: reading the slice, sampling it at the agent, summing the dose, or applying the threshold. It checks that the code applies SFPE Eqs. 63.44 and 63.48 correctly, not that they predict human tolerance.

The runs use --heat-clothing unclothed. The default law is ISO 13571:2012 Eq. (9), for fully clothed subjects (Models › Heat, #290). The two laws share the pipeline this page checks and differ only in their constants. Only the 100 °C deck is too short for Eq. (9): it would reach FED = 1 at 1482 s, after the end of the 1000 s FDS record. The 150 and 200 °C decks could check it (343 s and 121 s) but have not been rerun (#307), so the Eq. (9) times are expected values only. The default law is checked on synthetic fields by the S6 test (test_s6_heat_fed.py).

Equation

SFPE Handbook 5th ed., Ch. 63 (Models › Heat). The time to incapacitation by convected heat, with T the gas temperature in °C (Eq. 63.44, p. 2382):

$$ t_{I,\mathrm{conv}} = 5\times10^{7}\,T^{-3.4}\ \text{min}. $$

The Handbook states it for exposures of up to 2 h in air with less than 10 % water vapour by volume. ISO 13571:2012 gives the same expression as its Eq. (10) (§8.3.2), for unclothed or lightly clothed subjects, cited to Purser’s chapter in the 4th edition of the Handbook, with an estimated uncertainty of ±25 % (Fundamentals › Heat). The dose is summed over the exposure (Eq. 63.48, p. 2383, without its radiant term), with Δt in minutes:

FED_HEAT = sum_{t1}^{t2} [ T^3.4 / 5e7 ] * dt

Eq. 63.48 holds “providing that the temperature in the fire is stable or increasing”.

At a constant temperature the sum has a closed form, and FED = 1 is reached at \(t^{*}\):

FED_HEAT(t) = (T^3.4 / 5e7) x t_min
$$ t^{*} = \frac{60 \times 5\times10^{7}}{T^{3.4}}\ \text{s}. $$

A 1 % error in T (in °C) gives a 3.4 % error in the dose.

In the default deterministic mode every agent stops at FED = 1. In probabilistic mode (opt-in) agent i stops at its own threshold \(D_i = \exp(\sigma Z_i)\), \(Z_i \sim N(0,1)\), σ = 0.94, so the fraction of agents stopped by time t is

$$ F(t) = \Phi\!\left(\frac{\ln \mathrm{FED}(t)}{\sigma}\right). $$

Setup

Plan of the 30 m room: spawn area, the loop the agents walk, and the TEMPERATURE slice at breathing height

  • FDS: a sealed 30 × 30 × 3 m room, no fire, no species, adiabatic on all six faces, four meshes. At t = 0 the whole room is set to 100, 150 or 200 °C, and the ambient temperature TMPA is set to the same value, so the walls and the radiation field start at the gas temperature. TEMPERATURE slices at 1.5 m, the one nearest the 1.6 m sampling height on this 0.5 m grid.
  • Agents: 100 agents walk a loop between four corner checkpoints and never leave. Each agent’s temperature is sampled every second. Seed 42 (the default baseSeed); the probabilistic run is repeated with seeds 42 to 51.
  • Runs: --enable-heat-fed (heat is off by default), deterministic (the default, given explicitly) at all three temperatures; probabilistic at 150 °C, once per seed.
  • No soot: the decks have no soot slice, so run.py warns that smoke speed reduction is off and that visibility falls back to clear air (#248). Neither enters the heat-dose rate; clear-air visibility changes only where agents walk, and the expected dose is read at those positions.

Expected

FDS holds the room at the deck value. Over the whole 1000 s, every node of the 1.5 m slice stays within 0.65 mK of it, and every TEMPERATURE device within 0.52 mK (assets/fed_incap_heat_<T>c/fds/*_devc.csv). A 1 mK error moves the dose by at most 3.4 × 10⁻⁵, far below the margins in the Result table. So the expected stop is the closed form at the deck temperature, rounded up to the next 1 s dose update. Per agent, the expected dose is also the sum of Eq. 63.48 over the temperature FDS wrote at the agent’s position, read from the slice with fdsreader, independent of pyFDS-Evac.

Room temperature at 1.5 m against time for the three decks, as the difference from the deck value in mK

Deck\(t^{*}\) at the deck TFirst update with FED ≥ 1\(t^{*}\) under ISO Eq. (9), the default (not run)
100 °C475.5 s476 s1482.3 s, after the 1000 s record
150 °C119.8 s120 s343.0 s
200 °C45.0 s46 s121.4 s

The last column is \(60 \times 4.1\times10^{8}/T^{3.61}\) s.

The hand sum on the slice, taken at each agent’s own position and update times, first reaches 1 at the same update for all 100 agents. The gap to the closed form (+0.5, +0.2 and +1.0 s) is the 1 s update interval: the stop falls on the next update.

For the probabilistic run at 150 °C, F(999 s) = 98.8 % of agents are expected to have stopped by the end.

Result

Heat FED against time for the 100 agents at 150 °C, the hand sum and the closed form; below, each agent’s difference from its own hand sum

At 150 °C every agent’s dose equals its hand sum on the slice to 8.7 × 10⁻⁹. The difference comes from updates where an agent stands at a node two meshes share; there the meshes differ by up to 9.2 × 10⁻⁵ K (#239). Summed from the temperature the agent recorded, the dose agrees to 2.2 × 10⁻¹³.

Time to heat FED = 1 against temperature on log axes: the closed form, the hand sum and the simulated stop for the three decks

At all three temperatures the simulated stop (×) falls on the hand sum (○) and on the closed form at the deck value (◆), up to the 1 s update.

Fraction of agents incapacitated by heat against time at 150 °C, pooled over the probabilistic runs of 10 seeds (grey: each seed alone), with the expected log-normal curve and the 95 % band for 1000 agents

CheckExpectedSimulated
agent T against the slice, off shared nodes, 100 / 150 / 200 °C≤ 10⁻¹⁰ Kmax 1.4 × 10⁻¹⁴ / 0 / 2.8 × 10⁻¹⁴ K
agent T against the slice, at shared nodes, 100 / 150 / 200 °C≤ 1.3 × 10⁻⁴ / 9.2 × 10⁻⁵ / 7.6 × 10⁻⁵ Kmax 5.3 × 10⁻⁵ / 4.6 × 10⁻⁵ / 3.1 × 10⁻⁵ K
agent FED against the hand sum of its recorded T, 100 / 150 / 200 °C≤ 9.3 × 10⁻¹³ / 3.7 × 10⁻¹² / 9.9 × 10⁻¹²max 4.4 × 10⁻¹⁴ / 2.2 × 10⁻¹³ / 5.9 × 10⁻¹³
agent FED against the hand sum on the slice, 100 / 150 / 200 °C≤ seam bound, row by rowmax 3.8 × 10⁻⁹ / 8.7 × 10⁻⁹ / 1.2 × 10⁻⁸, all within
margin of the hand FED from 1 at the updates around the stop, 100 / 150 / 200 °C> seam bound up to the stop (≤ 1.8 / 1.0 / 0.86 × 10⁻⁷)≥ 9.8 × 10⁻⁴ / 1.8 × 10⁻³ / 9.4 × 10⁻⁴
deterministic stop, 100 °C476 sall 100 agents at 476 s
deterministic stop, 150 °C120 sall 100 agents at 120 s
deterministic stop, 200 °C46 sall 100 agents at 46 s
cause of every stopheatheat
probabilistic, 150 °C, 10 seeds pooled: stopped by 999 s98.8 %985 of 1000
probabilistic, 150 °C, 10 seeds pooled: largest gap between the curves≤ 0.0430.034 (p = 0.21)

Pass criteria

  1. Slice to agent. The temperature each agent recorded equals the slice value at its nearest node and nearest slice time, to float noise (10⁻¹⁰ K). The only larger difference allowed is at a node two meshes share, where the slice holds two values; there the tolerance is the largest such gap in the run (1.3 × 10⁻⁴, 9.2 × 10⁻⁵ and 7.6 × 10⁻⁵ K).
  2. Dose. Every agent’s FED equals the hand sum of Eq. 63.48 over the temperatures it recorded to round-off. Both sums add n = 1000 positive terms, each rounding by at most n ε FED, so the tolerance is 2 n ε FED_max with ε = 2.2 × 10⁻¹⁶ (9.3 × 10⁻¹³, 3.7 × 10⁻¹² and 9.9 × 10⁻¹²). End to end, against the hand sum on the slice, each update at a shared node may add 3.4 · ΔT_seam / T · rate · Δt, since a relative error in T grows 3.4 times in the rate. The seam bound is this term summed over the agent’s updates at shared nodes.
  3. Deterministic stop. Every agent stops at the first update where its hand sum reaches 1, with cause heat. Updates are 1 s apart (--smoke-update-interval). The stop is exact, not approximate, as long as the hand FED at the updates on either side of the stop is further from 1 than the seam bound; otherwise the adjacent update would also pass. The tightest case is 200 °C: at least 9.4 × 10⁻⁴ from 1, against a seam bound of at most 8.6 × 10⁻⁸. The stop also equals the first update at or after the closed-form \(t^{*}\) at the deck temperature.
  4. Probabilistic stop. The fraction of stopped agents, pooled over the probabilistic runs of seeds 42 to 51, stays within the 95 % Kolmogorov–Smirnov band of F(t), \(1.36/\sqrt{n} = 0.043\) for the pooled n = 1000 agents. Pooled, D = 0.034 (p = 0.21), so it passes. Each seed alone, against its own band of 0.136 for n = 100, gives D = 0.062 to 0.104 (median 0.075); none of the ten leaves its band.

Run it yourself

The FDS slices are not in the repository (65 MB per deck); only FDS’s device output *_devc.csv is, under assets/fed_incap_heat_<T>c/fds/. Either get the slices from the project’s data folder (fds-evac-data/fed_incap_heat_<T>c/fds/), or rerun FDS outside the repository, about one minute per deck on four cores:

mkdir -p <data>/fed_incap_heat_150c/fds && cd $_
cp <repo>/assets/fed_incap_heat_150c/fed_incap_heat_150c.fds .
mpiexec -n 4 fds fed_incap_heat_150c.fds

Then run pyFDS-Evac and draw the figures. The data folder must hold fed_incap_heat_<T>c/fds/ and fed_incap_heat_<T>c/evac/deterministic/, and for 150 °C evac/probabilistic_seeds/<seed>/:

for T in 100 150 200; do
  uv run python run.py --scenario assets/fed_incap_heat_${T}c \
    --fds-dir <data>/fed_incap_heat_${T}c/fds \
    --enable-heat-fed --heat-clothing unclothed \
    --heat-incapacitation-mode deterministic \
    --output-sqlite <data>/fed_incap_heat_${T}c/evac/deterministic/run.sqlite \
    --output-fed-history <data>/fed_incap_heat_${T}c/evac/deterministic/fed_history.csv
done
for seed in $(seq 42 51); do
  uv run python run.py --scenario assets/fed_incap_heat_150c \
    --fds-dir <data>/fed_incap_heat_150c/fds \
    --enable-heat-fed --heat-clothing unclothed \
    --heat-incapacitation-mode probabilistic --seed $seed \
    --output-sqlite <data>/fed_incap_heat_150c/evac/probabilistic_seeds/$seed/run.sqlite \
    --output-fed-history <data>/fed_incap_heat_150c/evac/probabilistic_seeds/$seed/fed_history.csv
done
uv run python scripts/verification/heat_room_figures.py --data <data>

Each run takes about three minutes; the deterministic runs use the default seed 42. A temperature without output is skipped. The published runs were made when Eq. 63.44 was the default law, before --heat-clothing existed; --heat-clothing unclothed selects the same law, and tests/verification/test_heat_endpoint_coupled.py checks, in a synthetic corridor, that it reproduces the FED history of that code.

The figures and numbers on this page come from the commands above at 92b8c5c, stored in <data>/fed_incap_heat_<T>c/evac_353/; the ten probabilistic runs at 8ef8dc7, in <data>/fed_incap_heat_150c/evac_353/probabilistic_seeds/. Earlier figures came from runs that also passed --constant-extinction 0 --no-visibility, which ran without a visibility model. On the same FDS output, a paired deterministic 150 °C run with and without the two flags stops all 100 agents at the same update in both; agent positions differ slightly, and the maximum heat FED differs by less than 10⁻⁴ (relative). The paired outputs are in <data>/fed_incap_heat_150c/evac/no_soot_fallback_248/.

Checks against the Handbook

Beyond the FDS rooms above, the heat laws are checked at the equation level and in coupled runs; Models › Heat summarises them.

The default, ISO Eq. (9), gives 1.8 to 3.0 times the convective times of Table 63.20 (p. 2383), and 10.7 min at 126 °C against the 7 min reported in Table 63.17 (p. 2375). Table 63.20 does not state clothing; Table 63.17’s 205 °C row (“bare headed, protected”) gives 4 min, against 1.85 min from Eq. (9), and clothing is not stated for its 126 °C row. These are recorded, not pass bands (test_heat_fed_verif.py, A3.12).

Eq. 63.44 (--heat-clothing unclothed) is checked against the convective rows of Table 63.20 (p. 2383) and the dry-air rows of Table 63.17 (p. 2375). Against Table 63.20 it gives 0.61 to 1.07 times the tabulated times, never longer than the table’s whole-minute rounding allows (read as ±0.5 min, an assumption); it never exceeds the times reported as tolerated in Table 63.17’s dry-air rows. The factor-2 band the test allows is an assumption, not a sourced tolerance. The --heat-endpoint laws are checked against Table 63.21 (tolerance) and hand formulas (#219).

The total-flux method is checked against hand formulas of Eqs. 63.49 and 63.43, the radiant rows of Table 63.20 and the convection table of spec 016, in unit tests and coupled corridor runs. The radiant threshold is checked in tests/test_heat_radiant_threshold.py for each radiant source (2.4 drops, 2.5 counts, a negative term counts as zero, convection unchanged, heat_flux_kw_m2 physical) and in the coupled cases of tests/verification/test_heat_total_flux_coupled.py: dense smoke at 300 °C (tolerance, ε 0.9, h 8) crosses at 5.8 s, clear air at 100 °C (tolerance, ε 0.05, h 8) at 190 s on convection alone, and smoke at 200 °C (fatal, ε 0.5, h 8) has a radiant term of 1.17 kW/m², below the threshold.

The layer regime is checked against hand formulas and the 200 °C / 2.5 kW/m² anchor (p. 2382), with synthetic temperature fields; no FDS case runs the layer regime yet (#308).

The INTEGRATED INTENSITY source is checked against hand formulas, a committed FDS 6.10.1 case (assets/heat_integrated_intensity, slice U and T against FDS’s own devices), and the #224 radiometer data, where f = 0.25 with convection matches the FDS skin gauge in an isotropic room in all four orientations within 3 %. The 300 °C layer, soot fraction and geometry of the committed case, and the gauge’s h = 8 and \(T_s\) = 35 °C in the #224 decks, are assumptions of those test decks, not sourced values.

Test and reference-deck assumptions

Values of the test decks and test bands that no consulted source fixes. They decide whether a test passes; they do not affect a run. The model’s own assumptions are on Models › Heat › Assumptions.

ParameterValueCLI flag / config keyWhere usedWhy this valueWhat would source it
#224 decks: gas and soot300 °C, soot mass fraction 0.005; layer base 2.0 mdeck files assets/heat_radiometer/*.fdsFDS reference data only; the heat model does not read them; the INTEGRATED INTENSITY radiometer testsA sooty layer that radiates at head heightA measured compartment fire with radiometers
#224 decks: burner and gridpropane, soot yield 0.01, 0.6 × 0.6 m at 1100 kW/m²; 4 × 4 × 3 m room (6 × 4 × 4 m open for the burner), 0.1 m cellsdeck files assets/heat_radiometer/*.fdsAs aboveA flame reaching head height on a grid that resolves 1.6 mAs above
#224 skin gaugeemissivity 1, h = 8 W/(m²·K), 35 °C&PROP in the #224 decks and assets/heat_integrated_intensityGauge values the INTEGRATED INTENSITY tests compare againstEmissivity 1 is the FDS default, taken as the skin’s; h = 8 is the top of the Handbook’s 5–8Skin emissivity and h of a clothed person
#221 CI deck300 °C sooty layer above 1.2 m, soot 0.005, 2 × 2 × 2.4 m, 0.2 m cellsassets/heat_integrated_intensity/heat_integrated_intensity.fdstests/verification/test_heat_integrated_intensity_coupled.pyTwo heights with different U in a small committed caseNone needed: it checks the reader against FDS’s own devices
Table reading±0.5 minnone (test)Eq. 63.44 against Table 63.20 (tests/verification/test_heat_fed_verif.py, #219)Whole-minute entries read as roundedThe unrounded values behind Table 63.20
Test band, convectivefactor 2none (test)Eq. 63.44 against Table 63.20 convective rows (#219)Wide enough for the 0.61–1.07 spread foundISO 13571:2012 states ±25 % for the identical Eq. (10) (§8.3.2), but the 0.61 row lies outside it; the band stays until that is settled (#289)
Test band, hot-layer anchor±10 %none (test)Black layer at 200 °C against “approximately 2.5 kW/m²” (p. 2382, #219)“approximately” in the textA precise flux for that anchor
Test band, radiant rows±30 % (±25 % in the #221 face-on check)none (test)Eq. 63.43 against Table 63.20 radiant rows (#219, #221)Chosen in #219A stated uncertainty of Table 63.20
Flame passblack sphere, radius 0.1 m, 1000 °C; one pass FED < 0.02none (test)tests/verification/test_heat_flame_pass_reference.py (#219)A small flame; the pass stays far below the fatal doseA measured flame and path

Limits

  • The law is not verified here. The expected values use Eqs. 63.44 and 63.48, the same formulas as the code. The law itself is compared with the Handbook’s tables in tests/verification/test_heat_fed_verif.py (#219): Eq. 63.44 gives 0.61 to 1.07 times the times of Table 63.20’s convective rows (p. 2383), shorter at 100–140 °C (7.9 min against 12 min at 100 °C), as the Handbook says it is “somewhat overconservative at the low-temperature end” (p. 2382); and it never exceeds the dry-air tolerance times of Table 63.17 (p. 2375).
  • σ = 0.94 has no source for heat. It is borrowed from the gas dose (#225). The probabilistic runs check the code path, not the spread.
  • Convective heat only. This room checks the convective laws; it does not run --heat-fed-method total-flux. That method is checked against hand formulas of Eqs. 63.49 and 63.43 on synthetic fields (tests/test_heat_total_flux.py, tests/verification/test_heat_total_flux_coupled.py). Its --heat-regime layer is checked the same way, against hand formulas and the 200 °C / 2.5 kW/m² anchor of p. 2382 (tests/test_heat_layer_flux.py, tests/verification/test_heat_layer_flux_coupled.py) on synthetic fields only; no FDS case runs the layer regime yet (#308). The #224 radiometer decks are FDS-only reference data (Heat radiometer). The INTEGRATED INTENSITY source is checked in tests/test_heat_integrated_intensity.py and tests/verification/test_heat_integrated_intensity_coupled.py (#221). The 2.5 kW/m² radiant threshold of total flux is checked in tests/test_heat_radiant_threshold.py. The layer anchor (2.33 kW/m² net for a black layer at 200 °C) is a check of the flux, not of the dose: it lies below that threshold and adds no radiant dose. Below the heat threshold (--fed-threshold, 1 by default) heat changes neither speed nor route (#81).
  • A near-uniform field cannot show whether the field is sampled at the agent’s current position; that needs a gradient (#24).
  • The agent checks are not an automated test: they run from the figure script on the stored output. In CI, tests/verification/test_heat_room_decks.py checks the decks (adiabatic faces, TMPA equal to the &INIT temperature), that FDS’s committed device output holds the deck value to 0.01 K, and the stop times in the table above. The equation-level tests (tests/verification/test_s6_heat_fed.py, test_heat_fed_verif.py) run on synthetic fields in CI. test_heat_flame_pass_reference.py and the radiant checks in test_heat_fed_verif.py (A3.11) call no pyFDS-Evac code: they are Handbook reference values kept apart from the total-flux method, which has its own tests (tests/test_heat_total_flux.py, #223).
  • The reference tests rest on choices that are not Handbook tolerances. Flame pass: the flame is a black-body sphere of radius 0.1 m, the skin faces it at 35 °C, there is no convective term, and one pass must stay below FED 0.02. Hot-layer anchor: a black-body layer with a full view, surface at 20 or 35 °C, within ±10 % of the Handbook’s “approximately 2.5 kW/m²”. Table 63.20 radiant rows: within ±30 %, the band chosen in #219. D = 16.7 as printed on pp. 2382 and 2384 of Ch. 63.
Last updated on