Method

We quoted 21.6 hours for a job that was going to take 60

Before a model runs, we tell you what it will cost — runtime and peak memory, from a table measured on real hardware rather than estimated. It is one of the more useful things the product does, because the alternative is discovering at hour forty that the machine cannot finish.

Then we ran the full benchmark: 1,327,200 cells, 24 meshes, 1500 seconds of simulated time. The model said 21.6 hours. Forty-nine minutes in, it had solved 20.4 seconds.

Run 84 · measured against prediction
predicted21.6 h
tracking to~60 h2.8× the quote
memory13.1 GBagainst 14.0 predicted — 6% out
The memory model was almost exactly right. Only the time was wrong, which is the clue: whatever we had missed cost cycles, not space.

What it was not

The obvious suspect is mesh count. Twenty-four MPI ranks means twenty-four subdomains exchanging boundary data every timestep, and communication overhead does scale with the number of neighbours. It is a reasonable hypothesis and it was wrong: earlier calibration runs were already at 24 meshes and matched the model fine.

It was not hardware. Same box, same cores, same FDS build. It was not cell count either — the cost model is expressed per million cells per simulated second precisely so that scale is already divided out.

What it was

Every point in the calibration table was measured on a deck with zero pressure zones.

The benchmark script that produced the table runs a residential archetype. Leakage, &ZONE, and the glazing control tree were all added to the engine afterwards. So the table described a solver doing strictly less work than the one we ship — and had no way of knowing.

Wall-seconds per Mcell-simsec, by deck complexity
runzonesmeasured · vs table
22030.0   0.8×   calibration set
36050.4   1.3×   calibration set
40071.3   1.8×   calibration set
83449.2   1.3×
82460.5   1.6×
8415110.2   2.8×
453180.0   4.6×   excluded — see below
FDS solves a global pressure correction across zones every timestep. Zone count is not correlated with the cost — it is the mechanism.

Control and device counts correlate here too, and it would have been easy to blame them. Runs 82 and 83 separate the variables: 83 has twice the glazing controls of 82 and is cheaper. The control tree is not what costs. The pressure solve is.

The run we threw out, and why we named it

Run 45 measured 4.6× the table — the worst point in the set, and excluding it moves the fit substantially. It is the run from the sealed compartment: it reached 60,275 Pa because nothing vented.

A pressure solver asked to converge on a compartment with no path to atmosphere iterates far harder than one that vents. Its 4.6× measures a broken model, not the cost of pressure zones. Folding it into the fit would have inflated every future quote to cover a defect the register now blocks outright.

Excluding an outlier is a decision, not a data-cleaning step. It gets named in the source, with the reason, so the next person can disagree with it.

The fix

A linear multiplier on zone count, because zone count is the mechanism:

Corrected model · full benchmark, 1500 s, 15 zones
old21.6 hzone-blind
new42–82 hobserved ~59.5 h
Three usable data points does not support anything more elaborate than a slope. A curve fitted through three confounded points would be false precision — which is the failure this file already warns about for the cell-size exponent.

The mistake inside the fix

The first version of the corrected model quoted a band of ±15% around the estimate. It was fitted only to the zoned runs, and it failed three of the seven measurements it was meant to describe — two of them from its own calibration set.

A confidence band that excludes its own data is not a band. It is a wish.

It is now derived from the runs of commercially meaningful length, with the sub-minute ones excluded and said — a 45% error on a 77-second run is 35 seconds, and letting that widen the band would make every real quote useless to protect a number nobody is billed for.

The estimate is also returned as a range rather than a number. A single figure reads as a promise, and this one cannot see how hard the pressure solve will turn out to be, what the fire does, or whether the glazing fails early.

Why this matters commercially

The cost model is not an internal curiosity. It feeds three things:

  • The quote. Under-predicting by 2.8× on large jobs is a direct loss on every one of them.
  • The watchdog ceiling, derived from the prediction. Set it from zone-free data and a legitimate run gets killed near completion — the most expensive possible failure, because it wastes the whole run.
  • The engineer's afternoon. "Four hours" and "two days" are different decisions about whether to start now or refine the model first.

Every fire modelling tool has a cost model, whether or not it admits it. Most of them live in someone's head as "about a day for something like this". The difference is not whether the estimate is wrong — it is whether anyone measures it afterwards, and whether the correction gets written down.

One thing this does not fix

The correction is to a model, not to hardware. On faster cores the absolute number falls, but the zone multiplier does not — the pressure solve is real work, and it scales with what the building actually is. A larger job needs a larger machine, and the honest version of that is that compute is a real cost we pass through rather than a rounding error we absorb.

Told before it runs, not at hour forty

Every model is costed against measurements from real runs, and the estimate is a range because the things it cannot see are real. When we get it wrong, we measure it again and write down what changed.

Arrange a demonstration →