Resources Numerical accuracy Calculation policy

Power network: load current and voltage drop

Joins lines, cable routes and equipment into a network and works out the current in every span and cable and the voltage drop at every node from each source.

How the precision is kept

64-bit numbers. The drop in each edge is the usual approximate formula √3·I·L·(R·cos φ + X·sin φ), with resistance at 20 °C and a typical reactance when the library item gives none, so the formula and its data set the accuracy. Currents use the nominal voltage, not the dropped voltage. The file header calls it a design aid with lumped loads on radial circuits.

Calculations under this policy

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

powerline/network.js CivilCraft 0.8.1 64-bit 5 calculations Reviewed

Tests bin/test-powerline-net.mjs

Length of a span in the network

64-bit

The electrical length of each overhead span.

length = √(Δx² + Δy²) × m per unit

The plan distance between the two supports; the conductor's sag and slope are not added.

  • powerline/network.js
  • line 73
  • CivilCraft 0.8.1
net.edges.push({ id: `${l.id}:${i - 1}`, kind: 'span', from: a.id, to: b.id, length_m: Math.hypot(b.x - a.x, b.y - a.y) * um, item: l.conductor || null, line: l.id, span: i - 1 });

Snapping to the nearest node

64-bit

Finds the support or equipment a route end or a piece of equipment connects to.

nearest node with distance × m per unit ≤ snap + 1e−9

The snap distance is absolute, 1 m by default. A node wins when it is nearer by more than 1e-12; at an equal distance within 1e-12 equipment is preferred over a support.

  • powerline/network.js
  • lines 82 to 90
  • CivilCraft 0.8.1
const nearest = (q, kinds) => {
  let best = null, bd = Infinity;
  for (const n of net.nodes) {
    if (!kinds.includes(n.kind)) continue;
    const d = Math.hypot(n.x - q.x, n.y - q.y) * um;
    if (d <= snap + 1e-9 && (d < bd - 1e-12 || (Math.abs(d - bd) <= 1e-12 && n.kind === 'equipment'))) { bd = d; best = n; }
  }
  return best;
};

Resistance, reactance and ampacity of an edge

64-bit

The electrical data of a span or cable route, with parallel cables counted.

R = R₂₀ ÷ n; X = X ÷ n (0.08 or 0.35 Ω/km by default); ampacity × n

When the item has no reactance a typical value is used and a note is added. The resistance is the library's value at 20 °C, without a temperature correction.

  • powerline/network.js
  • lines 134 to 143
  • CivilCraft 0.8.1
const elec = (e) => {
  if (e.kind === 'tie') return { R: 0, X: 0, amp: null, U: null };
  const it = getItem(lib, e.item);
  const n = e.kind === 'route' ? Math.max(1, routes.get(e.route)?.count || 1) : 1;
  if (!it) return { R: null, X: null, amp: null, U: e.kind === 'span' ? lines.get(e.line)?.voltage_kV ?? null : null, cableU: null };
  let X = fin(it.x_ohm_km) ? it.x_ohm_km : null;
  if (X == null) { X = it.kind === 'cable' ? X_CABLE : X_OVERHEAD; out.notes.push({ code: 'xdefault', edge: e.id, key: e.item, value: X }); }
  return { R: fin(it.r20_ohm_km) ? it.r20_ohm_km / n : null, X: X / n, amp: fin(it.ampacity_A) ? it.ampacity_A * n : null,
    U: e.kind === 'span' ? lines.get(e.line)?.voltage_kV ?? null : null, cableU: fin(it.voltage_kV) ? it.voltage_kV : null };
};

Loads summed upstream

64-bit

The active and reactive power each edge carries, and the current each load draws.

Q = P·tan(acos pf); I = √(P² + Q²) ÷ (√3·U)

The power factor is limited to (0, 1], so the acos argument stays in range. P and Q are added from the far ends of the tree back to the source in reverse breadth-first order.

  • powerline/network.js
  • lines 173 to 186
  • CivilCraft 0.8.1
// loads, summed upstream (reverse BFS order)
const P = new Map(), Q = new Map();
for (const v of order) {
  const eq = equip.get(v);
  const p = eq && eq.source !== true && fin(eq.load_kW) && eq.load_kW > 0 ? eq.load_kW : 0;
  const pf = fin(eq?.pf) && eq.pf > 0 && eq.pf <= 1 ? eq.pf : 0.95;
  P.set(v, p); Q.set(v, p * Math.tan(Math.acos(pf)));
  const U = Unom.get(v);
  out.nodes[v].I_A = fin(U) && U > 0 ? Math.hypot(p, p * Math.tan(Math.acos(pf))) / (SQ3 * U) : null;
}
for (let i = order.length - 1; i > 0; i--) {
  const v = order[i], e = parent.get(v), u = e.from === v ? e.to : e.from;
  P.set(u, P.get(u) + P.get(v)); Q.set(u, Q.get(u) + Q.get(v));
}

Current, voltage drop and node voltage

64-bit

The current and voltage drop in each edge and the voltage and cumulative drop at each node.

ΔU = √3·I·(L ÷ 1000)·(R·cos φ + X·sin φ); drop % += ΔU ÷ (U × 1000) × 100; U node = U·(1 − drop ÷ 100)

The approximate drop formula, as the file header says. The current is computed from the nominal voltage of the edge, not from the voltage after the drop upstream, so the result is a first pass and not an iterated load flow. Percent drops are added along the path.

  • powerline/network.js
  • lines 187 to 206
  • CivilCraft 0.8.1
// edges and voltages (BFS order: the parent first)
out.nodes[src].U_kV = U0; out.nodes[src].drop_pct = fin(U0) ? 0 : null;
for (const v of order.slice(1)) {
  const e = parent.get(v), u = e.from === v ? e.to : e.from, k = E.get(e.id);
  const U = Unom.get(v);   // the edge's nominal voltage
  const p = P.get(v), q = Q.get(v), S = Math.hypot(p, q);
  const r = out.edges[e.id];
  const I = fin(U) && U > 0 ? S / (SQ3 * U) : null;   // kVA / (√3 kV) = A
  r.I_A = I;
  r.ampacity_A = k.amp;
  r.load_pct = I != null && k.amp > 0 ? (I / k.amp) * 100 : null;
  if (I != null && fin(k.R) && fin(k.X)) {
    const cos = S > 0 ? p / S : 1, sin = S > 0 ? q / S : 0;
    r.dU_V = SQ3 * I * (e.length_m / 1000) * (k.R * cos + k.X * sin);
  } else if (I != null && I > 0) out.notes.push({ code: 'nor', edge: e.id, key: e.item });
  const d0 = out.nodes[u].drop_pct;
  const d = d0 != null && fin(U) && U > 0 ? d0 + (r.dU_V ?? 0) / (U * 1000) * 100 : null;
  out.nodes[v].drop_pct = d;
  out.nodes[v].U_kV = d != null ? U * (1 - d / 100) : null;
}

In detail

Method
Breadth-first tree from each source; loads summed upstream; I = S ÷ (√3·U); ΔU = √3·I·L·(R cos φ + X sin φ); drop percent summed along the path, as the file header describes.
Number types
64-bit numbers. √3 from Math.sqrt(3).
Tolerances
Route ends and equipment snap to a node within 1 m by default, with a slack of 1e-9; ties at equal distance are decided within 1e-12. A drop is flagged above the limit plus 1e-9; an overload above 100 percent with no slack.
Limits
Reactance defaults to 0.08 Ω/km for a cable and 0.35 Ω/km for an overhead conductor. Resistance is the 20 °C value. Span lengths are plan lengths. No transformation inside a tree and no loops.

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