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.jsShared shell, every app64-bit6 calculationsReviewed
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.
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;
};
}
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.