Resources Numerical accuracy Calculation policy

Schedule risk (Monte Carlo)

The finish at P10, P50, P80 and P90, how often each task is critical, and which durations drive the finish.

How the precision is kept

Each trial draws task durations from triangular distributions, rounds them to whole working days and runs the critical path. The answers are sample statistics from 1000 trials by default; the file's own note says percentiles move by about 1 ÷ √n.

Calculations under this policy

3 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.

simulate.js Shared shell, every app 64-bit 3 calculations Reviewed

Tests bin/test-engines.mjs, tools/schedule-test/test.mjs

Duration drawn for a task

64-bit

One trial's duration of a task given as minimum, most likely and maximum.

u < f: min + √(u (max − min)(mode − min)); else max − √((1 − u)(max − min)(max − mode)); duration = round(v)

The inverse of the triangular distribution in 64-bit, then rounded to a whole day with Math.round (halves go up). The rounding is part of the model, as the comment above the function says, and is far larger than any rounding of the arithmetic.

  • simulate.js
  • lines 18 to 20
const { min, mode, max } = t.dur, u = correlation > 0 && rand() < correlation ? shared : rand();
const f = (mode - min) / ((max - min) || 1), v = u < f ? min + Math.sqrt(u * (max - min) * (mode - min)) : max - Math.sqrt((1 - u) * (max - min) * (max - mode));
return { ...t, dur: Math.round(v) };

Finish percentiles, mean and spread

64-bit

The finish day at each confidence level over all trials.

P = sorted[⌈p × n⌉ − 1]; mean = Σ ÷ n; sd = √(Σ (x − mean)² ÷ n)

Percentiles are picked from the sorted finishes without interpolation. The deviation divides by n, not n − 1, and is taken from the mean, so no large totals are subtracted.

  • simulate.js
  • lines 26 to 28
const sorted = finishes.slice().sort((a, b) => a - b), pct = (p) => sorted[Math.min(n - 1, Math.ceil(p * n) - 1)];
const mean = finishes.reduce((a, b) => a + b, 0) / n, sd = Math.sqrt(finishes.reduce((a, b) => a + (b - mean) ** 2, 0) / n);
const corr = (xs) => { const mx = xs.reduce((a, b) => a + b, 0) / n; let sxy = 0, sxx = 0; for (let k = 0; k < n; k++) { sxy += (xs[k] - mx) * (finishes[k] - mean); sxx += (xs[k] - mx) ** 2; } return sxx > 0 && sd > 0 ? sxy / Math.sqrt(sxx * n * sd * sd) : 0; };

Criticality and sensitivity

64-bit

The share of trials in which a task was critical, and the correlation of its duration with the finish.

criticality = times critical ÷ n; sensitivity = Σ (d − d̄)(F − F̄) ÷ √(Σ (d − d̄)² × n × sd²)

A count divided by the number of trials, and a correlation from sums of deviations. A task whose duration never varies gets 0.

  • simulate.js
  • lines 29 to 30
return { ok: true, n, seed, mean, sd, p10: pct(0.1), p50: pct(0.5), p80: pct(0.8), p90: pct(0.9), min: sorted[0], max: sorted[n - 1],
  criticality: Object.fromEntries(ids.map((i) => [i, crit[i] / n])), sensitivity: Object.fromEntries(ids.map((i) => [i, corr(durs[i])])), problems: [], method: m };

In detail

Method
Monte Carlo with triangular durations and the criticality index (the file cites Van Slyke 1963); percentiles by order statistics; correlation of each duration with the finish.
Number types
64-bit numbers; durations are whole days after rounding.
Limits
Durations are rounded with Math.round before the path is computed. The spread is the population form (divided by n). Percentiles are single order statistics with no interpolation.

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