Resources Numerical accuracy Calculation policy

People flow and evacuation time

How fast people pass doors, corridors and stairs, which way each room empties, and how long it takes everyone to reach an exit.

How the precision is kept

The file's header names its method: the flow-limited hydraulic egress model of the fire engineering literature. A passage lets through at most a specific flow times its effective width, then people walk its length at a fixed speed. The arithmetic is 64-bit, but the result is set by the model and its constants, which the file says must be checked against the code the design answers to. The times themselves come from the discrete-event engine in engine.js.

Calculations under this policy

6 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

people.js Shared shell, every app 64-bit 6 calculations Reviewed

Tests None found by the review

Flow through a passage

64-bit

Persons per second through a door or corridor of a given width.

rate = max(0, Fsm × (width − 2 × boundary))

One subtraction and one product, clamped at zero. The defaults 1.32 and 0.15 are empirical constants, not derived values.

  • people.js
  • lines 21 to 22
/** persons per second through a passage of `width` with a boundary layer on each side */
export const flowRate = (width, fsm = DEFAULTS.fsm, boundary = DEFAULTS.boundary) => Math.max(0, fsm * (width - 2 * boundary));

Speed and specific flow at a density

64-bit

Walking speed and flow per meter of width at a given density of people.

S = k × (1 − 0.266 × D); Fs = S × D; k = 1.4 m/s

Two closed-form expressions with the empirical coefficient 0.266. They are exported for use elsewhere; egress in this file does not call them and uses a fixed speed and specific flow.

  • people.js
  • lines 24 to 25
export const speedAtDensity = (D, k = 1.4) => k * (1 - 0.266 * D);
export const specificFlow = (D, k = 1.4) => D * speedAtDensity(D, k);

Rate and speed of each passage

64-bit

The flow rate and walking speed each passage gets, from its own values or the defaults for its kind.

A passage's own specific flow and speed are used when given, else the stair or level defaults. A passage whose rate is not above zero after the boundary layers is reported as an error and left out.

  • people.js
  • lines 38 to 41
const stair = p.kind === 'stair', fsm = p.fsm ?? (stair ? o.stairFsm : o.fsm), rate = flowRate(p.width, fsm, o.boundary);
if (!(rate > 0)) { P.push(problem('egress.width', 'error', `Passage "${p.id}" is ${p.width} m wide; after the ${o.boundary} m boundary layer on each side nothing is left to walk through.`, [{ id: p.id }], { found: p.width, req: 2 * o.boundary })); continue; }
if (stair && p.speed === undefined) used.add('stairSpeed'); if (stair && p.fsm === undefined) used.add('stairFsm');
edges.push({ ...p, fsm, rate, v: p.speed ?? (stair ? o.stairSpeed : o.speed) });

Door service time and walking time

64-bit

How long a door takes per person, how long the passage takes to walk, and each room's distance to an exit in time.

service = 1 ÷ rate; walk = length ÷ speed; to exit = least total walking time

Each is one 64-bit division. The rate is above zero and a length must be above zero to get here; the speed is not checked, so a speed of zero given for a passage makes its walking time infinite.

  • people.js
  • lines 44 to 53
// distance from each room to the nearest exit, measured in walking time
const walk = new Graph({ nodes: g.nodes, edges: edges.map((e) => [e.from, e.to, e.length / e.v, { oneway: !!e.oneway }]) });
const toExit = (r) => Math.min(...[...exits].map((x) => walk.distance(r, x)));
const out = new Map(), nodeFor = (e, dir) => {
  const key = e.id + (dir > 0 ? '>' : '<'); if (out.has(key)) return out.get(key);
  const from = dir > 0 ? e.from : e.to, to = dir > 0 ? e.to : e.from, door = `door:${key}`, wk = `walk:${key}`;
  nodes.push({ type: 'server', name: door, units: 1, service: 1 / e.rate, to: wk, leaves: `room:${from}` });
  nodes.push({ type: 'delay', name: wk, time: e.length / e.v, to: `room:${to}` });
  out.set(key, door); return door;
};

Which way a room empties

64-bit

The next passage for people in a room: the one with the least remaining walking time to an exit.

keep a hop when toExit(next) < toExit(room) − 1e-9; choose the least walk + toExit(next)

The 1e-9 is in seconds and is absolute, not scaled to the times compared. With dynamic routing and more than one hop the choice is left to the engine with each hop's remaining time as an extra cost.

  • people.js
  • lines 59 to 64
// next hops that get strictly closer to an exit
const hops = fromRoom(r.id).map(([e, dir, next]) => ({ e, dir, next, rest: e.length / e.v + toExit(next) })).filter((h) => h.rest < Infinity && toExit(h.next) < d0 - 1e-9);
hops.sort((a, b) => a.rest - b.rest);
let to;
if (o.routing === 'dynamic' && hops.length > 1) to = { least: hops.map((h) => ({ to: nodeFor(h.e, h.dir), extra: h.rest })) };
else to = nodeFor(hops[0].e, hops[0].dir);

Evacuation results

64-bit

People evacuated, the time the last person reaches an exit, peak occupancy and density per room, and queues per door.

time = latest arrival at any exit; density = peak people in a room ÷ its area

The counts and times are read from the simulation result and the time is the largest of the exits' last arrivals, unrounded. Density is one division, done only when the room has an area. The times in the problem messages are formatted with toFixed for reading; the values stored beside them are not rounded.

  • people.js
  • lines 71 to 84
const run = ({ seed = 1, until = 36000 } = {}) => {
  const s = simulate(model, { until, seed });
  if (!s.ok) return { ok: false, problems: s.problems };
  const r = s.result, problems = [...P.filter((p) => p.severity !== "error"), ...s.problems], byExit = {}; let total = 0, last = 0, placed = 0;
  for (const x of exits) { const n = r.nodes[`room:${x}`]; if (!n) continue; byExit[x] = { count: n.count, first: n.first, last: n.last, cycle: n.cycle }; total += n.count; if (n.last > last) last = n.last; }
  for (const q of rooms) placed += q.people || 0;
  const roomsOut = {}, pass = {};
  for (const q of rooms) { const n = r.nodes[`room:${q.id}`]; if (n && n.type === 'junction') roomsOut[q.id] = { peak: n.inside.max, density: q.area ? n.inside.max / q.area : null }; }
  for (const [name, n] of Object.entries(r.nodes)) if (name.startsWith('door:')) pass[name.slice(5)] = { passed: n.completed, peakQueue: n.queue.max, busy: n.utilization };
  if (total < placed) problems.push(problem('egress.incomplete', 'error', `${placed - total} of ${placed} people had not reached an exit when the run ended at ${until} s.`, [], { found: total, req: placed }));
  if (o.limit && last > o.limit) problems.push(problem('egress.time', 'warning', `The last person reaches an exit at ${last.toFixed(1)} s; the limit is ${o.limit} s.`, [], { found: last, req: o.limit }));
  if (o.maxDensity) for (const [id, v] of Object.entries(roomsOut)) if (v.density !== null && v.density > o.maxDensity) problems.push(problem('egress.density', 'warning', `Room "${id}" peaks at ${v.density.toFixed(2)} persons/m², above the ${o.maxDensity} you set.`, [{ id }], { found: v.density, req: o.maxDensity }));
  return { ok: true, evacuated: total, placed, time: last, byExit, rooms: roomsOut, passages: pass, result: r, problems };
};

In detail

Method
Effective width = width − 2 × boundary layer. Rate = specific flow × effective width (persons per second). Walking time = length ÷ speed. Routes go to the next room that is strictly closer to an exit in walking time. Cited in the file: Predtechenskii and Milinskii, Pauls, and Nelson and MacLennan in the SFPE Handbook.
Number types
64-bit numbers.
Tolerances
A next room counts as closer to an exit only when its walking time is smaller by more than 1e-9 seconds (absolute).
Limits
Defaults: 1.32 persons/s/m, 0.15 m boundary layer, 1.4 m/s; stairs 0.8 m/s and 1.0 persons/s/m, which the code reports as placeholder values. No speed reduction with density and no counter-flow, as the code's own note says.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach