Resources Numerical accuracy Calculation policy

Protection curves and grading

Relay and fuse operating times, grading margins between devices, and the cable damage time.

How the precision is kept

Standard inverse-time formulas evaluated in 64-bit with the constants the file lists for IEC 60255-151 and IEEE C37.112. Fuse and breaker curves are the person's own points, interpolated on log–log axes. Near pickup the formulas subtract 1 from a number close to 1 and lose digits.

Open item. Compute (I ÷ Is)^α − 1 as expm1(α × ln(I ÷ Is)) so no digits are lost close to pickup.

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.

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

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

Curve constants

64-bit

The constants of the IEC and IEEE inverse-time curves and the cable factors.

IEC normal inverse k = 0.14, α = 0.02; IEEE very inverse A = 19.61, B = 0.491, p = 2; cable k: copper PVC 115

The constants are written as the file's header lists them for the two standards. They define each curve; they are not rounded physical values.

  • protection.js
  • lines 14 to 16
export const IEC_CURVES = { NI: { k: 0.14, a: 0.02, name: 'Normal inverse' }, VI: { k: 13.5, a: 1, name: 'Very inverse' }, EI: { k: 80, a: 2, name: 'Extremely inverse' }, LTI: { k: 120, a: 1, name: 'Long-time inverse' } };
export const IEEE_CURVES = { MI: { A: 0.0515, B: 0.114, p: 0.02, name: 'Moderately inverse' }, VI: { A: 19.61, B: 0.491, p: 2, name: 'Very inverse' }, EI: { A: 28.2, B: 0.1217, p: 2, name: 'Extremely inverse' } };
export const CABLE_K = { 'Cu-PVC': 115, 'Cu-XLPE': 143, 'Al-PVC': 76, 'Al-XLPE': 94 };

Relay operating time

64-bit

How long a relay takes to trip at a current.

IEC: t = TMS × k ÷ (M^α − 1); IEEE: t = TD ÷ divisor × (A ÷ (M^p − 1) + B), M = I ÷ pickup

M^α − 1 subtracts two nearly equal numbers when M is near 1 or α is small. For the normal inverse curve (α = 0.02) at twice the pickup the difference is about 0.014, so about two digits are lost; closer to pickup, more. A current exactly at pickup gives no trip (the test is I > pickup).

  • protection.js
  • lines 18 to 27
/** Operating time (s) of a relay at primary current I (A); Infinity when it does not pick up. */
export function relayTime(r, I) {
  if (r.inst && I >= r.inst.pickup) return r.inst.time ?? 0.02;
  if (!(I > r.pickup)) return Infinity;
  let t;
  if (r.definite) t = r.definite.time;
  else if (r.standard === 'ieee') { const c = IEEE_CURVES[r.curve]; if (!c) throw new Error('unknown IEEE curve ' + r.curve); const M = I / r.pickup; t = (r.td ?? 1) / (r.divisor || 1) * (c.A / (Math.pow(M, c.p) - 1) + c.B); }
  else { const c = IEC_CURVES[r.curve || 'NI']; if (!c) throw new Error('unknown IEC curve ' + r.curve); t = (r.tms ?? 1) * c.k / (Math.pow(I / r.pickup, c.a) - 1); }
  return Math.max(t, r.minTime || 0);
}

Fuse or breaker curve from points

64-bit

The operating time of a device given as a table of current and time.

ln t = ln t₀ + (ln I − ln I₀) ÷ (ln I₁ − ln I₀) × (ln t₁ − ln t₀)

Straight lines between the given points on log–log axes; the shape between points is an assumption of the method. At a given point the value is returned through exp(ln t), which can differ from the entered time in the last bit.

  • protection.js
  • lines 28 to 36
/** Log–log interpolated curve from [[I, t], …] (a fuse or breaker as the maker published it). Outside the points: Infinity below, the last time above. */
export function tableCurve(points) {
  const p = [...points].sort((a, b) => a[0] - b[0]);
  return (I) => {
    if (I < p[0][0]) return Infinity; if (I >= p[p.length - 1][0]) return p[p.length - 1][1];
    for (let i = 1; i < p.length; i++) if (I <= p[i][0]) { const [i0, t0] = p[i - 1], [i1, t1] = p[i]; return Math.exp(Math.log(t0) + (Math.log(I) - Math.log(i0)) / (Math.log(i1) - Math.log(i0)) * (Math.log(t1) - Math.log(t0))); }
    return Infinity;
  };
}

Cable damage time

64-bit

How long a cable can carry a fault current.

t = (k S ÷ I)²

One division and a square through Math.pow.

  • protection.js
  • line 37
export const cableDamageTime = (kS, I) => Math.pow(kS / I, 2);

Grading margin

64-bit

The time between a downstream and an upstream device at each fault current.

margin = t_up − t_down; ok when margin ≥ the grading interval (0.3 s by default)

The margin is a plain difference and the test is ≥ with no allowance, so a margin that equals the interval only to rounding can fail.

  • protection.js
  • lines 45 to 58
/**
 * Grading margin of an upstream device behind a downstream one at the listed fault currents (A, seen by the downstream device).
 * → { rows: [{ I, down, up, margin, ok }], problems }  margin = up − down; cti default 0.3 s (set by the person's own standard).
 */
export function coordinate(up, down, currents, { cti = 0.3, upId = 'upstream', downId = 'downstream' } = {}) {
  const rows = [], problems = [];
  for (const I of currents) {
    const td = timeOf(down, I), tu = timeOf(up, I), margin = tu - td, ok = !Number.isFinite(tu) ? true : margin >= cti;
    rows.push({ I, down: td, up: tu, margin, ok });
    if (!Number.isFinite(td)) problems.push(problem('coord.nopickup', 'warning', `At ${I.toFixed(0)} A the ${downId} device does not operate.`, [{ kind: 'device', id: downId }]));
    else if (Number.isFinite(tu) && margin < cti) problems.push(problem('coord.margin', margin < 0 ? 'error' : 'warning', `At ${I.toFixed(0)} A ${upId} operates ${margin < 0 ? 'before' : 'only ' + margin.toFixed(2) + ' s after'} ${downId} (${tu.toFixed(2)} s vs ${td.toFixed(2)} s); the grading margin is ${cti} s.`, [{ kind: 'device', id: upId }, { kind: 'device', id: downId }], { req: { op: '>=', value: cti, unit: 's' }, found: { value: margin, unit: 's' } }));
  }
  return { rows, problems };
}

Pickup and cable checks

64-bit

Pickup against load and the smallest fault, and clearing time against the cable.

pickup ≥ 1.05 × load; pickup ≤ smallest fault ÷ 1.5; clearing time ≤ (k S ÷ I)²

Plain comparisons with the factors 1.05 and 1.5 written in the code. The values stored with a failed check are not rounded; only the message text is.

  • protection.js
  • lines 59 to 66
/** Pickup against load and the smallest fault it must see; and the device against the cable's damage curve at the listed fault currents. */
export function relayChecks(r, { Iload, IfaultMin, IfaultMax, cable, id = 'relay' }) {
  const out = [];
  if (Iload && r.pickup < 1.05 * Iload) out.push(problem('pickup.load', 'warning', `${id}: pickup ${r.pickup} A is under 1.05 times the load current ${Iload} A and may trip on load.`, [{ kind: 'device', id }], { req: { op: '>=', value: 1.05 * Iload, unit: 'A' }, found: { value: r.pickup, unit: 'A' } }));
  if (IfaultMin && r.pickup > IfaultMin / 1.5) out.push(problem('pickup.sensitivity', 'warning', `${id}: pickup ${r.pickup} A is too close to the smallest fault ${IfaultMin} A (want at most ${(IfaultMin / 1.5).toFixed(0)} A for a 1.5 margin).`, [{ kind: 'device', id }], { req: { op: '<=', value: IfaultMin / 1.5, unit: 'A' }, found: { value: r.pickup, unit: 'A' } }));
  if (cable) for (const I of [IfaultMin, IfaultMax].filter(Boolean)) { const t = relayTime(r, I), tc = cableDamageTime(cable.k * cable.S, I); if (t > tc) out.push(problem('cable.damage', 'error', `${id}: at ${I} A it clears in ${t.toFixed(2)} s, longer than the cable can stand (${tc.toFixed(2)} s).`, [{ kind: 'device', id }], { req: { op: '<=', value: tc, unit: 's' }, found: { value: t, unit: 's' } })); }
  return out;
}

In detail

Method
t = TMS × k ÷ ((I ÷ Is)^α − 1) for IEC curves; t = TD × (A ÷ (M^p − 1) + B) for IEEE curves; t = (k S ÷ I)² for cable damage; log–log interpolation of tabulated curves.
Number types
64-bit numbers.
Tolerances
None: pickup, margin and damage comparisons are plain comparisons.
Limits
An instantaneous element without a time uses 0.02 s. A table curve returns its last time above its last point and infinity below its first.

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