Resources Numerical accuracy Calculation policy

Line study file (.DON) export and import

Writes an overhead line study as a .DON XML file in meters and decanewtons for another line program, and reads such a file back into a line.

How the precision is kept

The values written are computed in 64-bit and then fixed to the format's decimals: 3 for coordinates and lengths, 2 for forces in daN, 4 for angles and 1 for temperatures. A file read back therefore carries millimeter coordinates. The file header says the layout is untested with the program that reads it.

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.

powerline/don.js CivilCraft 0.8.1 64-bit 7 calculations Reviewed

Tests bin/test-powerline-don.mjs

Numbers in the file

64-bit

Turns a value into the fixed-decimal text the file carries, and newtons into decanewtons.

text = v.toFixed(d); daN = N ÷ 10, 2 decimals

Every exported number is rounded by toFixed to the decimals its field uses. A negative zero is written without its sign. Non-finite values are left out.

  • powerline/don.js
  • lines 70 to 71
  • CivilCraft 0.8.1
const fx = (v, d) => { if (!fin(v)) return null; const s = v.toFixed(d); return /^-0\.0*$/.test(s) ? s.slice(1) : s; };
const daN = (N) => fx(fin(N) ? N / N_PER_DAN : NaN, 2);

Signed line angle at each support

64-bit

The turn of the line at each support in degrees, counter-clockwise positive.

turn = atan2 of the span ahead − atan2 of the span behind, wrapped to ±180°

A difference of two Math.atan2 bearings wrapped by 2π and multiplied by 180 ÷ Math.PI; it is then written with 4 decimals.

  • powerline/don.js
  • lines 75 to 85
  • CivilCraft 0.8.1
/** Signed turn at each support in degrees, counter-clockwise positive (0 at the ends). */
function signedTurns(line) {
  const P = line.supports;
  return P.map((p, i) => {
    if (i === 0 || i === P.length - 1) return 0;
    let d = Math.atan2(P[i + 1].y - p.y, P[i + 1].x - p.x) - Math.atan2(p.y - P[i - 1].y, p.x - P[i - 1].x);
    while (d > Math.PI) d -= 2 * Math.PI;
    while (d < -Math.PI) d += 2 * Math.PI;
    return d * 180 / Math.PI;
  });
}

Support coordinates and heights written

64-bit

Each support's position, ground level, pole length, height above ground and line angle as written to the file.

X = x × m per unit, 3 decimals; Hauteur = typed height × m per unit + embedment

Project coordinates are multiplied by the unit factor and fixed to 3 decimals, one millimeter. Large grid coordinates keep their full size in the file; no origin is subtracted.

  • powerline/don.js
  • lines 126 to 133
  • CivilCraft 0.8.1
el('X', fx(p.x * um, 3));
el('Y', fx(p.y * um, 3));
const g = r.groundZ ?? p.ground;
if (fin(g)) el('Altitude', fx(g * um, 3));
const total = p.h != null ? p.h * um + (item?.embed_m ?? 0) : item?.height_m;
el('Hauteur', fx(total, 3));
el('HSol', fx(r.aboveGround_m ?? (p.h != null ? p.h * um : null), 3));
el('AngleDeriv', fx(turns[i], 4));

Section tension and catenary parameter written

64-bit

The reference temperature, horizontal tension and catenary parameter of each section as written to the file.

Parametre = H ÷ w, 3 decimals; TensionIni = H ÷ 10 daN, 2 decimals

One division each, then fixed decimals. The tension keeps 0.01 daN, which is 0.1 N.

  • powerline/don.js
  • lines 170 to 172
  • CivilCraft 0.8.1
el('TempInitiale', fx(sv.ref.temp, 1));
el('TensionIni', daN(sv.ref.H));
el('Parametre', fx(sv.ref.H / sv.ref.w, 3));

Reading a number

64-bit

Turns a field's text into a number, accepting a decimal comma.

The text is checked against a number pattern and converted with the unary plus, which gives the nearest 64-bit value. Values of 1e12 or more in magnitude are refused.

  • powerline/don.js
  • lines 301 to 308
  • CivilCraft 0.8.1
function numOf(s) {
  if (s == null) return null;
  let t = String(s).replace(/\s+/g, '');
  if (/^[+-]?\d*,\d+$/.test(t)) t = t.replace(',', '.');
  if (!/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d{1,3})?$/.test(t)) return null;
  const v = +t;
  return Number.isFinite(v) && Math.abs(v) < 1e12 ? v : null;
}

Angles in grades

64-bit

Converts a line angle given in grades to degrees.

degrees = grades × 0.9

0.9 is the exact ratio 360 ÷ 400, applied as one multiplication; 0.9 itself is not exactly representable in binary, so the product is rounded once.

  • powerline/don.js
  • line 342
  • CivilCraft 0.8.1
angle_deg: ang != null ? (grades ? ang * 0.9 : ang) : null,

A study file into a line

64-bit

Turns the supports read from a file into a line in project units.

x = X (m) ÷ m per unit; voltage kV = V ÷ 1000

Meters are divided by the unit factor, 0.3048 for feet. The inputs carry the 3 decimals the file was written with. Coordinates of 1e9 or more in magnitude are refused.

  • powerline/don.js
  • lines 406 to 433
  • CivilCraft 0.8.1
// ---------------------------------------------------------------------------------------------------------------- into the project
export function donToState(read, units) {
  const um = unitM(units);
  const notes = [];
  const sups = Array.isArray(read?.supports) ? read.supports : [];
  const c0 = read?.cantons?.[0] || null;
  const okXY = (v) => fin(v) && Math.abs(v) < 1e9;
  const hints = [];
  const supports = [];
  sups.forEach((s, i) => {
    if (!okXY(s.x_m) || !okXY(s.y_m)) { notes.push({ why: 'nocoords', support: i + 1, name: s.name }); return; }
    const hm = fin(s.above_m) && s.above_m > 0 ? s.above_m : fin(s.height_m) && s.height_m > 0 ? s.height_m : null;
    supports.push({ id: null, x: s.x_m / um, y: s.y_m / um, name: clean(s.name || s.fullName).slice(0, 40), support: null, equip: [],
      role: s.role === 'deadend' ? 'deadend' : 'auto', ground: fin(s.ground_m) && Math.abs(s.ground_m) < 1e6 ? s.ground_m / um : null,
      h: hm != null && hm <= 1000 ? hm / um : null, existing: s.existing === true });
    hints.push({ crossarm: s.crossarm || '', chosen: s.chosen || '', nature: NATURE_BACK[(s.nature || '').toUpperCase()] || '' });
  });
  if (supports.length < 2) notes.push({ why: 'few', have: supports.length });
  const wires = c0?.wires;
  const line = {
    id: null, name: clean(read?.meta?.description || '').slice(0, 120) || '', conductor: null,   // the UI names an unnamed line after the file
    phases: fin(wires) && wires >= 1 && wires <= 12 ? wires : 3, circuits: 1,
    voltage_kV: fin(c0?.voltage_V) && c0.voltage_V >= 0 && c0.voltage_V <= 1.2e6 ? c0.voltage_V / 1000 : 0.4,
    equip: [], clearance: 'ground', supports,
  };
  if (!c0) notes.push({ why: 'nocanton' });
  return { line, conductor: c0?.conductor || '', hints, notes };
}

In detail

Method
Fixed-decimal text through toFixed. Project units are converted to meters with unitM from units.js (0.3048 per foot) on writing and divided by it on reading. Angles in grades are multiplied by 0.9 on reading.
Number types
64-bit numbers, written as fixed-decimal text.
Tolerances
Coordinates and lengths 3 decimals of a meter; forces 2 decimals of a decanewton; angles 4 decimals of a degree; temperatures 1 decimal.
Limits
Numbers read must be below 1e12 in magnitude. A decimal comma is accepted. Integer fields that hold a fraction are rounded.

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