Fire Dynamics Simulator (FDS) coupled evacuation modeling with smoke-speed reduction, toxic gas dose (FED), and dynamic route rerouting.
The project includes:
- Smoke-speed model (visibility/extinction-based speed reduction)
- Full ISO 13571 FED model (toxic gas dose accumulation)
- Dynamic smoke-based route rerouting with congestion awareness
- Sign-visibility-gated route rejection (fdsvismap integration)
- Per-agent cognitive maps with
fullanddiscoveryfamiliarity tiers - JuPedSim scenario loading and simulation
This project uses uv for dependency management.
uv syncActivate the virtual environment:
uv shellRun a JSON-first scenario with the CLI runner:
uv run run.py --scenario assets/ISO-table21 --cleanupSee docs/usage.md for the full catalogue of CLI flags,
post-processing scripts, and the scripts/run_and_plot.sh driver that
runs a simulation and produces every plot in one go.
See docs/smoke-speed-model.md for the full model description, configuration, and API reference.
The smoke-speed model uses extinction coefficient K [1/m] as the primary
input. Two speed laws are available, selected via SmokeSpeedConfig.speed_law:
speed_law |
Model | Reference |
|---|---|---|
"lund" (default) |
Linear: speed_factor = 1 + β·K/α, clamped to [min_speed_factor, 1] |
Frantzich & Nilsson / FDS+Evac |
"fridolf" |
Non-linear: speed_factor = V / (V + 2) where V = C / K (Jin) |
Fridolf et al. (2019) |
The Fridolf law is empirically validated against individual walking-speed
measurements in smoke-filled tunnels and naturally asymptotes to zero without
a hard clamp. Select it with SmokeSpeedConfig(speed_law="fridolf");
visibility_factor_c controls the Jin constant (default 3 for reflective
signs, 8 for light-emitting signs).
For real FDS output, fdsreader provides the local extinction field
via SliceFieldSampler. For verification cases such as ISO 20414 Table 21,
the runner can also apply a constant extinction coefficient directly.
All FDS slice data is read through a single library:
fdsreader— reads raw FDS slice quantities with nearest-neighbor spatial and temporal lookup viaSliceFieldSampler(pyfds_evac/core/fds_sampling.py)- Used by both the smoke-speed model (extinction
K [1/m]) and the FED model (CO, CO2, O2, and optional irritant gases) - When a scenario needs both extinction and FED fields from the same FDS
case, pass a shared
fdsreader.Simulationinstance to avoid parsing the directory twice (see FDS sampling API)
Run the ISO Table 21 corridor with a constant extinction coefficient:
uv run run.py \
--scenario assets/ISO-table21 \
--constant-extinction 1.0 \
--smoke-update-interval 0.1 \
--output-smoke-history /tmp/iso-table21-smoke-history.csv \
--cleanupRun the smoke-speed model against FDS results read through fdsreader:
uv run run.py \
--scenario assets/ISO-table21 \
--fds-dir fds_data \
--smoke-update-interval 0.1 \
--output-smoke-history /tmp/iso-table21-fds-smoke-history.csv \
--cleanupInspect the FDS quantities available through fdsreader:
uv run run.py --inspect-fds --fds-dir fds_data --scenario assets/ISO-table21Plot smoke-speed history for a single agent:
uv run python scripts/plot_smoke_history.py \
--input /tmp/iso-table21-smoke-history.csv \
--output /tmp/iso-table21-smoke-history.png \
--agent-id 1Plot aggregate smoke-speed history:
uv run python scripts/plot_smoke_history.py \
--input /tmp/iso-table21-smoke-history.csv \
--output /tmp/iso-table21-smoke-history-aggregate.pngGenerate a stable ISO Table 21 sweep artifact under artifacts/:
uv run python scripts/generate_iso_table21_sweep.pyGenerate the FDS+Evac smoke-density vs speed verification plot:
uv run python scripts/generate_smoke_density_speed_plot.pyThe FED model implements the full ISO 13571 / Purser formulation as described in Section 3.4 of the FDS+Evac Technical Reference and User's Guide (Korhonen, 2021).
| Term | Guide Eq. | Formula | Input |
|---|---|---|---|
| FED_CO | (13) | CO (ppm) | |
| FED_CN | (14-15) |
|
HCN, NO2 (ppm) |
| FED_NOx | (16) |
|
NO, NO2 (ppm) |
| FLD_irr | (17) | HCl, HBr, HF, SO2, NO2, acrolein, formaldehyde (ppm) | |
| HV_CO2 | (19) | CO2 (vol %) | |
| FED_O2 | (18) | O2 (vol %) |
Irritant Ct values (ppm·min) from guide Table 2:
| 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. Required
species: CO, CO2, O2. Optional species (HCN, NO, NO2, HCl, HBr, HF,
SO2, acrolein, formaldehyde) are loaded when available; missing species
default to 0 and contribute nothing to the FED sum. With only the three
required species, the model reduces to the original FDS+Evac default
pathway: FED_CO * HV_CO2 + FED_O2.
The FED model was extended in March 2026 to include all ISO 13571 terms:
- HCN (hydrogen cyanide) and NO2 (nitrogen dioxide): CN-term for narcosis, where NO2 has a protective effect (C_CN = C_HCN - C_NO2)
- NO (nitric oxide): Added to NOx-term alongside NO2
- Multiple irritant gases: HCl, HBr, HF, SO2, NO2, acrolein, formaldehyde with species-specific Ct thresholds from guide Table 2
- O2 hypoxia guard: The O2 FED term (guide Eq. 18) is suppressed at or above 19.5 % O2 (OSHA safe-air threshold). At ambient conditions (20.9 %) the denominator of Eq. 18 is non-zero, producing a tiny but finite rate that accumulates spuriously over long simulations or when agents sample outside the FDS domain (where O2 defaults to 20.9 %). The guard sets the rate to zero when O2 ≥ 19.5 %, matching the default behaviour in Pathfinder (Thunderhead Engineering).
All new terms are fully tested with constant-exposure unit tests in
tests/test_fed.py.
- Equation-level constant-exposure checks for all ISO 13571 terms are covered in tests/test_fed.py
- An ISO Table 22 style stationary benchmark is covered with
assets/ISO-table22, comparing the runtimeFED=1crossing time against the analytical reference
Generate the ISO Table 22 stationary FED verification figure:
uv run python scripts/generate_iso_table22_stationary_plot.py- Thermal FED terms (radiant heat, convective heat)
- Height-relative FED and smoke sampling: gas concentrations and extinction
are sampled from a single horizontal FDS slice at a fixed height
(
slice_height_m, default 2.0 m), shared by all agents 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.
See docs/usage.md for the full catalogue of
run.py flags (scenario, FDS coupling, FED, rerouting, tenability,
Smokeview export) and the post-processing scripts. Note: if an agent
sample lies outside the FDS domain the implementation falls back to
ambient conditions.
On top of the Frantzich–Nilsson extinction–speed law, pyFDS-Evac applies two Purser/FDS+Evac rules when a FED model is loaded:
- FIC-driven slowdown. Purser's Fractional Irritant
Concentration (HCl, HBr, HF, SO2, NO2, acrolein, formaldehyde; see
pyfds_evac.core.fed.default_fic) multiplies the Frantzich speed bymax(fic_min_factor, 1 − fic_alpha·FIC). Defaults:fic_alpha = 0.7,fic_min_factor = 0.3. - Binary incapacitation at FED ≥ 1. Matches FDS+Evac §3.4 (Korhonen 2021): once cumulative FED crosses the threshold, the agent's target speed is driven to zero for the remainder of the run. The agent remains as a static obstacle.
Both rules are enabled by default and can be tuned via CLI flags
--fic-alpha, --fic-min-factor, --fed-threshold, or turned off
entirely with --disable-tenability. The FED history CSV
(--output-fed-history) gains three extra columns fic,
fic_speed_factor, incapacitated.
See docs/routing.md for the full routing model, cost formulas, and API reference.
The routing system implements smoke-aware path planning with dynamic rerouting:
- StageGraph: Dijkstra-based shortest-path routing on a graph of stages (distributions, checkpoints, exits)
- Route cost evaluation: Samples extinction (K) along candidate paths
to compute smoke exposure (FED terms are supported when a
fed_modelis provided; otherwise only smoke drives ranking) - Dynamic rerouting: Agents recompute routes at configurable intervals, selecting lower-exposure paths when available
- Congestion-aware routing: Optional exit-congestion term (
w_queue) balances load across exits based on current agent counts and capacities - Throughput throttling: Optional exit flux limiting via
enable_throughput_throttlingandmax_throughputin scenario config
See docs/usage.md for the full rerouting CLI
(--enable-rerouting, --reroute-interval, --output-route-history,
--output-route-cost-history, --vis-cache) and the plotting scripts
that consume the generated route-cost CSVs.
Implements Spec 008: sign visibility gates route rejection and per-agent cognitive maps control what knowledge each agent has about the building layout.
Each exit and checkpoint can carry a "sign" descriptor in the scenario
config:
{
"exits": {
"exit_A": {
"sign": {"x": 0.5, "y": 11.5, "alpha": 90, "c": 3}
}
}
}alpha is a compass bearing (degrees from north, clockwise): 90 = sign
visible from the east, 270 = from the west, 180 = from the south.
At each reevaluation tick the VisibilityModel checks whether an agent
can see the next node's sign using a cached fdsvismap
pickle. If the sign is not visible, the route is rejected with
rejection_reason="next_node_not_visible".
# Build or reuse the vismap cache and enable visibility-gated rejection
uv run run.py \
--scenario assets/demo \
--fds-dir fds_data/demo \
--enable-rerouting \
--vis-cache fds_data/demo/vismap_cache.pkl \
--output-route-cost-history route_costs.csv \
--cleanupRejected routes are recorded in the route-cost CSV with
rejected=True, rejection_reason=next_node_not_visible.
# Coverage and ASET maps (sign placement validation)
uv run python scripts/demo_vismap_phase0.py
# With fresh vismap recompute
uv run python scripts/demo_vismap_phase0.py --no-cacheAgents have a familiarity tier that controls how much of the building they know at the start of the simulation:
| Tier | familiarity |
Knowledge at spawn | Expansion |
|---|---|---|---|
| Trained staff | "full" |
Complete stage graph | — |
| Visitors | "discovery" |
Spawn node + visible neighbors | On arrival + at reevaluation |
Set per distribution group in the scenario config:
{
"distributions": {
"visitors": {
"parameters": {
"familiarity": "discovery"
}
}
}
}Default when the key is absent: "full" (backward compatible).
Discovery expansion rules:
- At spawn — agent learns its spawn node plus any adjacent node whose sign is currently visible from the spawn centroid.
- On arrival — when an agent physically reaches a node, all immediate neighbours are added to the cognitive map unconditionally.
- At reevaluation — adjacent nodes whose sign is visible from the agent's current position are added.
Routing (Dijkstra) runs over the agent's known sub-graph only. If no exit is reachable in the cognitive map, no rerouting occurs and the agent continues on its last assigned route until the cognitive map expands via an arrival or reevaluation event.
# 4-panel figure: spawn → junction → reroute → full baseline
uv run python scripts/demo_cognitive_map_vis.py
# Without cached vismap (all neighbours assumed visible at spawn)
uv run python scripts/demo_cognitive_map_vis.py --no-cacheTwo scenario configs differ only in familiarity tier:
| Config | Tier |
|---|---|
assets/demo/config_full.json |
familiarity=full |
assets/demo/config_discovery.json |
familiarity=discovery |
Run both back-to-back and produce a 3-panel comparison (exit split, rejection timeline, evacuation time):
uv run python scripts/run_familiarity_comparison.py \
--fds-dir fds_data/demo \
--vis-cache fds_data/demo/vismap_cache.pklOutputs: results/familiarity_comparison/{full,discovery}_route_costs.csv,
results/familiarity_comparison/comparison.png.
After a run, pyFDS-Evac can export agent trajectories as an FDS
Lagrangian-particle file (<CHID>_agents.prt5) and register it in the
existing <CHID>.smv, so Smokeview renders agents alongside the smoke
slices and 3-D smoke output:
uv run run.py --scenario assets/demo \
--fds-dir fds_data/demo \
--smv-export
smokeview fds_data/demo/demo.smvIn Smokeview: Load → Particles → AGENTS and scrub the timebar. Options:
--smv-particle-z(default0.0): constant agent height in meters (avatars stand on the floor).--smv-class-id(defaultAGENTS):CLASS_OF_PARTICLESlabel in the.smv.--smv-avatar-style {human,arrow,sphere}(defaulthuman): which AVATARDEF to drop into<CHID>.svo.arrowshows a body sphere with a red directional marker and makes per-particle rotation (if present) visually obvious;sphereis a single plain sphere for debugging.--smv-with-azimuth(off by default): write per-particle AZIMUTH (deg) as a PRT5 quantity column. Currently disabled because per-particle avatar rotation is not supported by Smokeview (firemodels/smv#2597) and because of a separate Smokeview 6.10.x bug that sticks playback on frame 0 whenever the PRT5 has any quantity column. Seedocs/smv-avatars.mdfor the trace.
The patch to .smv is idempotent — re-running the exporter will not
duplicate the block.
Reference materials are stored in materials/:
- FDS+Evac Technical Reference and User's Guide — Korhonen (2021). Primary reference for the FED equations (Section 3.4) and smoke-speed model (Section 3.4, Eq. 11).
- Boerger et al. (2024) — Beer-Lambert integrated extinction along line of sight (Eq. 8-9), waypoint-based visibility maps. Fire Safety Journal 150:104269.
- Haensel (2014) — Knowledge-based routing and cognitive map framework for evacuation modelling.
- Schroder et al. (2020) — Waypoint-based visibility and evacuation modeling.
- Ronchi et al. (2013) — FDS+Evac evacuation model validation and verification.
- evac.f90 — Original FDS+Evac Fortran source for cross-referencing implementation details.
Scenario definitions are stored in assets/:
- ISO-table21: ISO 20414 corridor verification case (single exit)
- ISO-table22: ISO 20414 stationary benchmark (single agent, analytical FED=1 time)
- haspel: Multi-exit scenario with three zones and dynamic rerouting
- demo: T-corridor FDS scenario with cable fire, two exits (A open, B smoke-accumulating),
200 visitors spawning in the branch; used for visibility-aware routing and cognitive
map verification (Spec 008). Includes
config_full.jsonandconfig_discovery.jsonfor familiarity-tier comparison. - basic: Minimal scenarios for smoke-speed verification
- HC: Hazard composition cases
- social_force: Social force model test cases
- jupedsim
- pedpy
- fdsreader
- plotly
- nbformat



