Resources Numerical accuracy Calculation policy

Layer of protection and SIL arithmetic

Works out the mitigated frequency of a hazard scenario, the risk reduction still needed, and the average probability of failure on demand of a safety function with its SIL band.

How the precision is kept

Frequencies and probabilities are multiplied and added in 64-bit. The average probability of failure is a numerical average (Simpson's rule) of the channel unavailability over the life, which has a jump at every proof test, so the step count sets its accuracy. The channel unavailability is written 1 − exp(−x) for very small x.

Open item. Write the channel unavailability as −Math.expm1(−x) and evaluate the common-cause term at the end of the life the same way as the channel term.

Calculations under this policy

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

process/safety/lopa.js CadCraft 0.8.2 64-bit 7 calculations Reviewed

Tests tools/process-test/test-safety.mjs

Mitigated event frequency

64-bit

The frequency of a scenario after its protection layers, against the target.

f = Σ f_IE × Π p_modifier × Π PFD_IPL; required PFD = target ÷ f

A sum and two running products in 64-bit, unrounded. A missing probability is taken as 1 and a missing frequency as 0, each is listed in `missing`, and the result is marked incomplete.

  • process/safety/lopa.js
  • lines 55 to 67
  • CadCraft 0.8.2
/** Mitigated frequency per year: sum over initiating events of f x prod(modifier p) x prod(IPL PFD). Missing inputs are listed, never assumed. */
export function scenarioResult(sc) {
  const missing = [];
  if (!sc.initiating.length) missing.push('an initiating event');
  for (const e of sc.initiating) if (!(e.f > 0)) missing.push('frequency of ' + (e.name || e.id));
  for (const m of sc.modifiers) if (m.p === undefined) missing.push('probability of ' + (m.name || 'a modifier'));
  for (const p of sc.ipls) if (!(p.pfd > 0)) missing.push('PFD of ' + (p.name || p.id));
  const fIE = sc.initiating.reduce((s, e) => s + (e.f > 0 ? e.f : 0), 0), pm = sc.modifiers.reduce((a, m) => a * (m.p ?? 1), 1), pi = sc.ipls.reduce((a, p) => a * (p.pfd > 0 ? p.pfd : 1), 1);
  const f = fIE * pm * pi, t = sc.target;
  const out = { id: sc.id, complete: !missing.length, missing, fIE, modifiers: pm, ipls: pi, fMitigated: f, target: t ?? null };
  if (t > 0) { out.meets = f <= t; out.gap = f / t; out.rrf = f > t ? f / t : 1; out.requiredPfd = f > t ? t / f : 1; }
  return out;
}

Required PFD of a new layer

64-bit

The probability of failure a safety function must reach to close the gap.

required PFD = target ÷ (frequency without that layer)

Recomputes the scenario without the named layer and divides; the SIL is read from the band table.

  • process/safety/lopa.js
  • lines 68 to 72
  • CadCraft 0.8.2
/** The PFD a new layer (a SIF) must reach: target / (mitigated frequency without it). */
export function requiredPfd(sc, withoutSifId) {
  const copy = clone(sc); copy.ipls = copy.ipls.filter((p) => p.id !== withoutSifId); const r = scenarioResult(copy);
  return { requiredPfd: r.requiredPfd ?? null, silTarget: r.requiredPfd ? silBand(r.requiredPfd) : null, fWithout: r.fMitigated };
}

SIL band of a PFD

64-bit

The safety integrity level a probability of failure on demand falls in.

SIL n when 10^−(n+1) ≤ PFD < 10^−n

Plain comparisons against the decimal band edges with no tolerance. A value exactly on an edge falls in the band above it, the lower SIL. Values below 1e-5 return 4 and values at or above 0.1 return 0.

  • process/safety/lopa.js
  • lines 74 to 80
  • CadCraft 0.8.2
/** SIL for a PFDavg: the band it falls in; 0 when above SIL1's upper edge (no SIL), null when below SIL4's lower edge ("better than SIL4": not claimed). */
export function silBand(pfd) {
  if (!(pfd > 0)) return null;
  if (pfd >= 1e-1) return 0;
  for (const b of SIL_LOW) if (pfd >= b.lo && pfd < b.hi) return b.sil;
  return pfd < 1e-5 ? 4 : 0;
}

Unavailability of one channel

64-bit

The probability that one channel has failed dangerously at a time since new.

u(t) = 1 − exp(−λ_t·(t mod TI) − λ_n·t)

The exponent is typically very small, so 1 − exp(−x) subtracts two nearly equal numbers; Math.expm1 is not used. The absolute rounding error is about 1e-16, so the relative error is about 1e-16 ÷ x.

  • process/safety/lopa.js
  • lines 88 to 92
  • CadCraft 0.8.2
/** Unavailability of ONE channel at time t since new: dangerous undetected failures that the proof test finds (rate PTC lDU) are cleared every TI; the rest (1-PTC) stay until the end of life LT. */
export function pfdChannel(t, { lambdaDU, TI, PTC = 1, beta = 0 }) {
  const lt = (1 - beta) * lambdaDU * PTC, ln = (1 - beta) * lambdaDU * (1 - PTC), tau = t % TI;
  return 1 - Math.exp(-lt * tau - ln * t);
}

PFDavg by the simplified equations

64-bit

The average probability of failure on demand for each voting architecture.

1oo1: λ·TI ÷ 2; 1oo2: ((1 − β)λ·TI)² ÷ 3 + β·λ·TI ÷ 2; 2oo3: ((1 − β)λ·TI)² + β·λ·TI ÷ 2

Short 64-bit expressions of products and powers; nothing cancels.

  • process/safety/lopa.js
  • lines 94 to 98
  • CadCraft 0.8.2
/** PFDavg by the closed forms (needs PTC = 1). */
export function pfdAvgFormula({ arch, lambdaDU, TI, beta = 0 }) {
  const l = lambdaDU * TI, a = (1 - beta) * l, c = beta * l / 2;
  switch (arch) { case '1oo1': return l / 2; case '1oo2': return a * a / 3 + c; case '2oo2': return l; case '1oo3': return a ** 3 / 4 + c; case '2oo3': return a * a + c; default: throw new Error('Unknown architecture ' + arch); }
}

PFDavg by averaging over the life

64-bit

The average probability of failure on demand with imperfect proof tests, by numerical averaging.

PFDavg = (1 ÷ life) × ∫₀^life [P(at least N − K + 1 channels failed) + common cause] dt

Composite Simpson's rule with 400 steps per test interval. The integrand jumps at each proof test, where the error of Simpson's rule falls only in proportion to the step. At the last sample the channel term is evaluated at life − 1e-9, but the common-cause term uses t mod TI, which is 0 when the life is a whole number of test intervals, so that end value is left out: with one interval and perfect tests this lowers the common-cause part by 1 ÷ 600.

  • process/safety/lopa.js
  • lines 99 to 113
  • CadCraft 0.8.2
/** PFDavg by averaging the exact channel unavailability over the life (Simpson), with KooN voting and the beta part as a common-cause element added. Handles PTC < 1. */
export function pfdAvgNumeric({ arch, lambdaDU, TI, beta = 0, PTC = 1, LT }) {
  if (!ARCH[arch]) throw new Error('Unknown architecture ' + arch);
  const [K, N] = ARCH[arch]; if (K === N) beta = 0;   // beta has no meaning with one channel, and in a 2oo2 any single failure already defeats the function
  const life = LT && LT >= TI ? LT : TI, steps = Math.max(200, Math.ceil(life / TI) * 400) & ~1, h = life / steps;
  const f = (t) => {
    const u = pfdChannel(Math.min(t, life - 1e-9), { lambdaDU, TI, PTC, beta });
    let p = 0; for (let j = N - K + 1; j <= N; j++) p += choose(N, j) * u ** j * (1 - u) ** (N - j);   // at least N-K+1 channels failed dangerously
    const ccf = N > 1 ? beta * lambdaDU * (PTC * (t % TI) + (1 - PTC) * t) : 0;   // first order common-cause term
    return p + ccf;
  };
  let s = f(0) + f(life); for (let i = 1; i < steps; i++) s += f(i * h) * (i % 2 ? 4 : 2);
  const v = s * h / 3 / life;
  return v;
}

Safety function total

64-bit

The PFDavg of each subsystem, their total and the SIL of the total.

PFDavg = Σ subsystem PFDavg

Subsystem values are added in order, unrounded. The closed-form value is reported next to the numerical one only when the proof-test coverage is 1.

  • process/safety/lopa.js
  • lines 115 to 129
  • CadCraft 0.8.2
/** A SIF: PFDavg per subsystem and in total (sum), the SIL of the total and of the weakest subsystem; notes list what was not checked. */
export function verifySif(sif, { target = null } = {}) {
  const subs = sif.subsystems.map((u) => {
    const ok = u.lambdaDU >= 0 && u.TI > 0;
    if (!ok) return { name: u.name, arch: u.arch, pfd: null, missing: [u.lambdaDU >= 0 ? '' : 'dangerous undetected failure rate', u.TI > 0 ? '' : 'proof-test interval'].filter(Boolean) };
    const pfd = pfdAvgNumeric({ arch: u.arch, lambdaDU: u.lambdaDU, TI: u.TI, beta: u.beta || 0, PTC: u.PTC ?? 1, LT: u.LT });
    return { name: u.name, arch: u.arch, pfd, formula: (u.PTC ?? 1) === 1 ? pfdAvgFormula({ arch: u.arch, lambdaDU: u.lambdaDU, TI: u.TI, beta: u.beta || 0 }) : null, sil: silBand(pfd), HFT: ARCH[u.arch][1] - ARCH[u.arch][0] };
  });
  const complete = subs.length > 0 && subs.every((s) => s.pfd != null), total = complete ? subs.reduce((a, s) => a + s.pfd, 0) : null;
  const out = { id: sif.id, complete, subsystems: subs, pfdAvg: total, sil: total == null ? null : silBand(total), demand: sif.demand,
    notes: ['Architectural constraints (hardware fault tolerance, safe failure fraction, route 1H or 2H) and systematic capability are not checked.', 'Failure rates and test intervals are the person\'s inputs; none is assumed.', REVIEW.note] };
  if (sif.demand === 'high') out.notes.push('High-demand mode needs PFH, not PFDavg: not computed here.');
  if (target != null && total != null) { out.meetsTarget = total <= target; out.targetPfd = target; }
  return out;
}

In detail

Method
f_mitigated = f_IE × Π P × Π PFD; simplified PFDavg equations and SIL bands the file attributes to IEC 61508 and IEC 61511 (typed, not confirmed, as its REVIEW says); composite Simpson's rule with K-out-of-N voting and a first-order common-cause term.
Number types
64-bit numbers.
Tolerances
Simpson steps: 400 per proof-test interval, at least 200, forced even. Channel term evaluated at life − 1e-9 at the end point. SIL bands are half-open: lower edge included, upper edge excluded.
Limits
Architectural constraints are not checked. High-demand mode is not computed.

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