Works out the load on a conductor in each load case, its tension by change of state on the ruling span, and the sag, length and support tensions of every span.
How the precision is kept
SI units and 64-bit numbers throughout. Each span is an exact catenary for the tension found, but the tension itself comes from the change-of-state equation on the ruling span, solved by Newton's method to a relative step of 1e-9, with tension limits met by bisection and repeated scaling. The starter library's conductor data are computed from rounded material values and then rounded again. The file header calls it a design aid.
Calculations under this policy
10 calculations in 2 source files. 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.
Millimeters are divided by 1000 first. The ice area is the difference of two squares, formed by subtraction. The resultant comes from Math.hypot. Ice density defaults to 900 kg/m³ and g is 9.80665.
The horizontal tension in a new load case and temperature, from a known tension in another.
H³ + K·H² − M = 0; K = EA·(w₁²L² ÷ (24H₁²) + α(t₂ − t₁)) − H₁; M = EA·w₂²L² ÷ 24
Newton's method from max(H₁, 1), stopping when the step is under 1e-9 × max(1, H), so the root is good to about 9 digits, not 15. If 100 steps do not settle it, 200 halvings between 1e-6 and a doubled upper bound are used. K is a difference of terms scaled by EA, which can be much larger than H₁.
powerline/sag.js
lines 50 to 71
CivilCraft 0.8.1
export function changeOfState(c, L, from, to) {
const ea = EA(c), al = ALPHA(c);
if (!(ea > 0) || !(from.H > 0) || !(L > 0)) return NaN;
const K = ea * (sq(from.w * L) / (24 * sq(from.H)) + al * (to.temp - from.temp)) - from.H;
const M = ea * sq(to.w * L) / 24;
// f(H) = H³ + K·H² − M has exactly one positive root when M > 0
const f = (H) => H * H * H + K * H * H - M;
let H = Math.max(from.H, 1);
for (let i = 0; i < 100; i++) {
const fx = f(H), d = 3 * H * H + 2 * K * H;
if (!(d > 0)) { H *= 2; continue; }
const n = H - fx / d;
if (!(n > 0)) { H /= 2; continue; }
if (Math.abs(n - H) < 1e-9 * Math.max(1, H)) return n;
H = n;
}
// bisection fallback
let lo = 1e-6, hi = Math.max(1, from.H);
while (f(hi) < 0) hi *= 2;
for (let i = 0; i < 200; i++) { const m = (lo + hi) / 2; if (f(m) > 0) hi = m; else lo = m; }
return (lo + hi) / 2;
}
Catenary of one span
64-bit
The lowest point, sag, conductor length and the tension at each support of a span, level or inclined.
a = H ÷ w; x₀ = L ÷ 2 − a·asinh(dz ÷ (2a·sinh(L ÷ 2a))); y(x) = a·(cosh((x − x₀) ÷ a) − cosh(x₀ ÷ a)); length = a·(sinh((L − x₀) ÷ a) + sinh(x₀ ÷ a)); T = H·cosh(x ÷ a)
Heights are built from cosh(u) − 1, a subtraction of two nearly equal numbers when the span is short compared with a, which is the usual case for a tight conductor; the sag is then a further difference between the chord and the curve. The sag is taken where the curve is parallel to the chord, clamped to the span.
powerline/sag.js
lines 73 to 86
CivilCraft 0.8.1
export function catenary(span, H, w) {
const L = Math.abs(span.L), dz = span.dz || 0;
const a = H / w;
// the lowest point's x (from the left end): dz = a·(cosh((L − x0)/a) − cosh(x0/a))
const x0 = L / 2 - a * Math.asinh(dz / (2 * a * Math.sinh(L / (2 * a))));
const y0 = -a * (Math.cosh(x0 / a) - 1); // the lowest point's height below the left end
const yAt = (x) => y0 + a * (Math.cosh((x - x0) / a) - 1);
const xs = x0 + a * Math.asinh(dz / L); // where the curve runs parallel to the chord
const xm = Math.min(L, Math.max(0, xs));
const sag = (dz * xm / L) - yAt(xm);
const length = a * (Math.sinh((L - x0) / a) + Math.sinh(x0 / a));
const Tleft = H * Math.cosh(x0 / a), Tright = H * Math.cosh((L - x0) / a);
return { L, dz, a, x0, y0, xLow: x0, sag, length, Tleft, Tright, yAt };
}
Horizontal tension for a support-tension limit
64-bit
The horizontal tension at which the support tension of the level ruling span equals a limit.
find H with H·cosh(L_r·w ÷ 2H) = T
200 halvings of the interval from 1e-6 to T. H·cosh(L_r·w ÷ 2H) is not monotonic over that whole interval, since it grows without bound as H goes to zero, and the code does not check which root the halving ends on.
powerline/sag.js
lines 88 to 94
CivilCraft 0.8.1
/** Horizontal tension that gives a largest support tension T in the ruling span (level), by bisection on H. */
function hForT(T, w, Lr) {
if (!(T > 0)) return NaN;
let lo = 1e-6, hi = T;
for (let i = 0; i < 200; i++) { const m = (lo + hi) / 2; if (m * Math.cosh(Lr / (2 * (m / w))) > T) hi = m; else lo = m; }
return lo;
}
Meeting the tension limits on the real spans
64-bit
Lowers the reference tension until every support-tension limit holds on the actual spans, then reports which limits hold.
H ← H ÷ max(T_max ÷ limit); holds when value ≤ limit × (1 + 1e−6)
At most 20 passes, each one a full rebuild of every case; it stops when the worst ratio is within 1e-9 of 1. Dividing the tension by the ratio does not scale the support tension exactly, which is why more than one pass can be needed. The final check allows a relative slack of 1e-6.
powerline/sag.js
lines 135 to 154
CivilCraft 0.8.1
// a T limit is about the worst support of the real spans: scale down until every T limit holds (a few passes)
if (!fixed) {
for (let pass = 0; pass < 20; pass++) {
let worst = 1;
for (const x of cands) {
if (x.lim.kind !== 'T') continue;
const N = x.lim.N > 0 ? x.lim.N : x.lim.pct / 100 * (c.rts_N || 0);
const r = res[x.lc.id].Tmax / N;
if (r > worst + 1e-9) { worst = r; gov = x; }
}
if (worst <= 1 + 1e-9) break;
res = build(res[first.id].H / worst);
}
}
// which limits hold
const checks = cands.map((x) => {
const r = res[x.lc.id];
const N = x.lim.fixed > 0 ? x.lim.fixed : x.lim.N > 0 ? x.lim.N : x.lim.pct / 100 * (c.rts_N || 0);
const v = x.lim.kind === 'T' ? r.Tmax : r.H;
return { caseId: x.lc.id, kind: x.lim.kind || 'H', limit: N, value: v, ok: x.lim.fixed > 0 || v <= N * (1 + 1e-6) };
Stringing table
64-bit
Tension, sag and conductor length of every span at each stringing temperature, in still air without ice.
per temperature: change of state from the reference, then a catenary per span
Each row is one change of state and one catenary per span, so it carries the Newton tolerance of 1e-9. The total length is the sum of the span lengths in order.
The longitudinal pull on a support when one adjacent span is iced and the other is bare.
dT = max(|H iced, left − H bare, right|, |H iced, right − H bare, left|)
Each span is solved alone, with its own length in place of the ruling span, which the code comment calls conservative. The result is a difference of two tensions from separate Newton solves.
powerline/sag.js
lines 170 to 184
CivilCraft 0.8.1
/** Each span on its own from the section's reference: tension with ice vs bare, at the ice case's temperature. */
export function unbalancedIce(c, spans, ref, iceCase, bareCase) {
const wi = conductorLoad(c, iceCase).w, wb = conductorLoad(c, bareCase || { temp: iceCase.temp }).w;
const per = spans.map((s) => {
const Hi = changeOfState(c, Math.abs(s.L), ref, { w: wi, temp: iceCase.temp });
const Hb = changeOfState(c, Math.abs(s.L), ref, { w: wb, temp: (bareCase || iceCase).temp });
return { Hi, Hb };
});
const out = [];
for (let i = 1; i < spans.length; i++) {
const a = per[i - 1], b = per[i];
out.push({ support: i, dT: Math.max(Math.abs(a.Hi - b.Hb), Math.abs(b.Hi - a.Hb)) });
}
return out;
}
The area, diameter, mass, strength, stiffness, expansion and resistance of the generic stranded conductors that come with the app.
mass = 1.02 × Σ area × density; strength = 0.9 × Σ area × wire strength; R₂₀ = 1.02 × resistivity ÷ area × 1000
Computed from textbook material values that the code comment calls rounded, with a lay factor of 1.02 and a stranding loss of 10 percent. The results are then rounded before use: area to 1 decimal, diameter to 2, mass to 4, strength to a whole newton, modulus and expansion to 1 decimal, resistance to 4 decimals.
Catenary per span; change of state on the ruling span, H₂²·(H₂ − H₁ + EA·w₁²L² ÷ (24H₁²) + EA·α·(t₂ − t₁)) = EA·w₂²L² ÷ 24, as the file header writes it. Ruling span √(ΣL³ ÷ ΣL). Ice as a ring of the given radial thickness and density.
Number types
64-bit numbers. g = 9.80665 from units.js.
Tolerances
Newton stops when the step is below 1e-9 × max(1, H), at most 100 steps, then 200 halvings as a fallback. Tension-limit bisection: 200 halvings. Limit scaling: up to 20 passes, to a ratio of 1 + 1e-9. A limit counts as held within a relative 1e-6.
Limits
The change-of-state equation uses the w²L² ÷ 24H² slack term on the ruling span, the usual approximation; spans of very different length or slope in one section are covered only through the ruling span. Unbalanced ice treats each span on its own.
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.