Resources Numerical accuracy Calculation policy

Irrigation zones: flow, pressure and coverage

Adds up the flow of each irrigation zone, works out the pressure it needs at the valve and at the supply, the precipitation rate and run time, and checks head coverage.

How the precision is kept

The engine works in SI with 64-bit numbers. Friction loss is the empirical Hazen-Williams formula and several conversion constants are written to a few digits. Coverage is counted on a grid. The file header calls it a calculation aid that a designer checks. The interface file converts feet with 0.3048 while the engine's project foot is 1200 ÷ 3937 m.

Open item. Convert feet in irr-ui.js with the same factor as the engine (1200 ÷ 3937, not 0.3048) and write the psi, gallon and triangle factors at full precision.

Calculations under this policy

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

landscape/irrigation.js CivilCraft 0.8.1 64-bit 16 calculations Reviewed

Tests bin/test-landscape.mjs

Pressure and flow unit constants

64-bit

The factors between kPa and meters of water, kPa and psi, and L/min and US gallons per minute.

kPa per m = 9.80665; kPa per psi = 6.894757; L/min per gpm = 3.785412

9.80665 is the exact standard gravity and assumes water at 1000 kg/m³. 6.894757 is the psi rounded to 7 digits. 3.785412 is the US gallon rounded to 7 digits; its exact value is 3.785411784 L.

  • landscape/irrigation.js
  • lines 31 to 33
  • CivilCraft 0.8.1
export const G = 9.80665;                       // kPa per metre of water (ρ g / 1000)
export const KPA_PER_PSI = 6.894757;
export const LMIN_PER_GPM = 3.785412;

Pipe inside diameters

64-bit

The inside diameter of each pipe size, from its outside diameter and dimension ratio.

ID = (OD − 2 × OD ÷ SDR) × 0.0254 m (inch sizes); ID = (OD − 2 × OD ÷ 11) ÷ 1000 m (metric)

Computed once from the listed outside diameters; 0.0254 is the exact inch. The result is a nominal diameter from the dimension ratio, not a measured one.

  • landscape/irrigation.js
  • lines 55 to 58
  • CivilCraft 0.8.1
export const PIPE_SIZES = [
  ...IPS_OD.map(([n, od]) => { const sdr = n === '1/2' ? 13.5 : 21; return { key: 'ips-' + n, label: n + '" PVC SDR ' + sdr, id: (od - (2 * od) / sdr) * 0.0254, system: 'ft' }; }),
  ...METRIC_OD.map((od) => ({ key: 'pe-' + od, label: od + ' mm PE SDR 11', id: (od - (2 * od) / 11) / 1000, system: 'm' })),
];

Hazen-Williams loss in one pipe

64-bit

The head loss, pressure loss and velocity for a flow in a pipe.

hf = 10.67 × L × q^1.852 ÷ (C^1.852 × d^4.8704), q in m³/s; kPa = hf × 9.80665; v = q ÷ (π d² ÷ 4)

The factor 60000 between L/min and m³/s is exact. The diameter exponent here is 4.8704, while the water mains solver in water.js uses 4.871 for the same formula. A zero or negative flow or diameter returns zeros.

  • landscape/irrigation.js
  • lines 68 to 74
  • CivilCraft 0.8.1
// ---- the hydraulics ----
export function hazenWilliams({ Q, d, L, C = 150 }) {
  const q = Q / 60000;   // m³/s
  if (!(q > 0) || !(d > 0)) return { hf: 0, kPa: 0, v: 0 };
  const hf = (10.67 * L * q ** 1.852) / (C ** 1.852 * d ** 4.8704);
  return { hf, kPa: hf * G, v: q / ((Math.PI * d * d) / 4) };
}

Loss in psi per 100 ft

64-bit

The US form of the Hazen-Williams formula.

psi per 100 ft = 4.52 × Q^1.852 ÷ (C^1.852 × d^4.87) × 100

The coefficient 4.52 and the exponent 4.87 are the rounded constants of the US form. No other file in the released CivilCraft build calls this function.

  • landscape/irrigation.js
  • line 75
  • CivilCraft 0.8.1
export const psiPer100ft = (Qgpm, dIn, C = 150) => (4.52 * Qgpm ** 1.852) / (C ** 1.852 * dIn ** 4.87) * 100;

Multiple-outlet factor

64-bit

The reduction in friction loss for a pipe that discharges through N evenly spaced outlets.

F = 1 ÷ (m + 1) + 1 ÷ (2N) + √(m − 1) ÷ (6N²)

Closed form with one square root; m defaults to 1.852. The half-spacing form multiplies by 2N ÷ (2N − 1). No other file in the released CivilCraft build calls this function.

  • landscape/irrigation.js
  • lines 76 to 83
  • CivilCraft 0.8.1
/** Christiansen (1942): F = 1/(m+1) + 1/(2N) + √(m−1)/(6N²) with the first outlet a full spacing from the inlet; else the
 *  half-spacing form F = 2N/(2N−1) · (1/(m+1) + √(m−1)/(6N²)). */
export function outletFactor(N, { m = 1.852, firstFull = true } = {}) {
  if (!(N >= 1)) return 1;
  if (N === 1) return 1;
  const a = 1 / (m + 1), c = Math.sqrt(m - 1) / (6 * N * N);
  return firstFull ? a + 1 / (2 * N) + c : ((2 * N) / (2 * N - 1)) * (a + c);
}

Precipitation rate

64-bit

Millimeters of water per hour from a flow spread over an area.

mm/h = 60 × L/min ÷ m²

The factor 60 is exact, since one liter over one square meter is one millimeter. A zero area returns 0.

  • landscape/irrigation.js
  • line 84
  • CivilCraft 0.8.1
export const precipRate = (flowLmin, areaM2) => (areaM2 > 0 ? (60 * flowLmin) / areaM2 : 0);   // mm/h

Precipitation rate, US form

64-bit

Inches of water per hour from gallons per minute over square feet.

in/h = 96.3 × gpm ÷ ft²

96.3 is the trade's rounded constant; the exact factor is 231 × 60 ÷ 144 = 96.25. No other file in the released CivilCraft build calls this function.

  • landscape/irrigation.js
  • line 85
  • CivilCraft 0.8.1
export const prInches = (gpm, areaFt2) => (areaFt2 > 0 ? (96.3 * gpm) / areaFt2 : 0);        // in/h, the US form

Flow of one head

64-bit

The flow of a sprinkler head, from its own listed flow or from its design precipitation rate.

L/min = pr × r² × (arc ÷ 360) ÷ 60

One 64-bit expression. The rate is taken over a square of side r, the head-to-head spacing the comment describes, and scaled by the arc. A head type with a listed flow uses it as given.

  • landscape/irrigation.js
  • lines 135 to 142
  • CivilCraft 0.8.1
/** A head's flow (L/min): the type's own, else from its design precipitation rate — quoted, as catalogs do, at head-to-head
 *  square spacing (spacing = radius): flow = pr × r² × arc/360 (matched precipitation: a half circle gives half the flow). */
export function headFlow(h, type) {
  if (!type) return 0;
  if (type.flow && !type.pr) return type.flow * (type.kind === 'microspray' ? h.arc / 360 : 1);
  const r = headRadius(h, type);
  return (type.pr * r * r * (h.arc || 360)) / 360 / 60;
}

Precipitation rate at a head spacing

64-bit

The precipitation rate of heads of one type set out on a square or triangular grid.

area = s² (square) or 0.866 × s² (triangular)

The triangular factor is written as 0.866, three digits of √3 ÷ 2. No other file in the released CivilCraft build calls this function.

  • landscape/irrigation.js
  • lines 145 to 150
  • CivilCraft 0.8.1
/** The precipitation rate of heads of one type set out at `spacing` (m): square or triangular. */
export function spacingPR(type, spacing, pattern = 'square') {
  const full = headFlow({ arc: 360, radius: null }, type);
  const area = pattern === 'triangle' ? spacing * spacing * 0.866 : spacing * spacing;
  return precipRate(full, area);
}

Joining pipe ends into nodes

64-bit

Decides which pipe ends, heads and drip lines meet at the same point.

same node when distance ≤ join tolerance ÷ meters per project unit

The tolerance is the joinTol setting, 0.05 m by default, an absolute distance. A point joins the first node found within it, so the result can depend on drawing order when points are closer than the tolerance to two nodes.

  • landscape/irrigation.js
  • lines 181 to 194
  • CivilCraft 0.8.1
// ---- the network: nodes where pipe ends meet, trees from each valve (laterals) and from the supply (main) ----
function network(irr, units) {
  const k = unitM(units), tol = irr.settings.joinTol / k, nodes = [], at = (p) => {
    for (const n of nodes) if (Math.hypot(n.x - p.x, n.y - p.y) <= tol) return n;
    const n = { i: nodes.length, x: p.x, y: p.y, edges: [], heads: [], drips: [] };
    nodes.push(n);
    return n;
  };
  for (const p of irr.pipes) { const a = at(p.a), b = at(p.b); a.edges.push({ p, to: b }); b.edges.push({ p, to: a }); }
  for (const h of irr.heads) { const n = nodes.find((m) => Math.hypot(m.x - h.x, m.y - h.y) <= tol); if (n) n.heads.push(h); }
  for (const d of irr.drips) { const n = nodes.find((m) => Math.hypot(m.x - d.pts[0].x, m.y - d.pts[0].y) <= tol); if (n) n.drips.push(d); }
  const nodeAt = (p) => nodes.find((m) => Math.hypot(m.x - p.x, m.y - p.y) <= tol) || null;
  return { nodes, nodeAt, k };
}

Flow of a drip line

64-bit

The flow of a drip line from its length, emitter spacing and emitter rate.

L/min = (⌊length ÷ spacing⌋ + 1) × (L/h per emitter ÷ 60)

The emitter count comes from Math.floor with no tolerance, so a length that is a whole number of spacings can lose one emitter to rounding in the division. The length is the plan length in meters.

  • landscape/irrigation.js
  • line 196
  • CivilCraft 0.8.1
export const dripFlow = (d, k) => (Math.floor(dripLen(d, k) / d.spacing) + 1) * (d.q / 60);   // L/min

Pressure needed at the valve

64-bit

The loss from the valve to every head and the pressure the worst head needs.

loss to node = loss to parent + friction kPa + (z node − z parent) × 9.80665; need = max(head nominal pressure + loss) + valve loss

Losses are accumulated down the tree in order. Each pipe's length is the plan distance between its nodes, so a sloping pipe is slightly longer than the length used. Drip lines are given a fixed need of 140 kPa. The velocity check allows 1e-9 of slack.

  • landscape/irrigation.js
  • lines 228 to 242
  • CivilCraft 0.8.1
const lossTo = new Map([[root.i, 0]]);
for (const n of order.slice(1)) {
  const par = parent.get(n.i), L = Math.hypot(n.x - par.from.x, n.y - par.from.y) * k, sz = sizeOf(par.p.size);
  const hw = hazenWilliams({ Q: down.get(n.i), d: sz.id, L, C: S.C });
  lossTo.set(n.i, lossTo.get(par.from.i) + hw.kPa + (elev(n) - elev(par.from)) * G);
  zone.pipes.push({ id: par.p.id, size: par.p.size, L, Q: down.get(n.i), v: hw.v, kPa: hw.kPa });
  zone.length += L;
  if (hw.v > S.vMax + 1e-9) zone.problems.push({ key: 'velocity', id: par.p.id, v: hw.v, max: S.vMax });
}
for (const n of order) for (const h of n.heads) {
  const ty = cat.get(h.type), need = (ty?.pressure.nominal ?? 200) + lossTo.get(n.i);
  if (!zone.worst || need > zone.worst.need) zone.worst = { head: h.id, need, loss: lossTo.get(n.i), x: h.x, y: h.y };
}
for (const n of order) for (const d of n.drips) { const need = 140 + lossTo.get(n.i); if (!zone.worst || need > zone.worst.need) zone.worst = { drip: d.id, need, loss: lossTo.get(n.i), x: d.pts[0].x, y: d.pts[0].y }; }
zone.pressureNeed = zone.worst ? zone.worst.need + S.valveLoss : 0;

Pressure needed at the supply

64-bit

The loss along the mainline from the supply to a zone's valve and the pressure that zone needs at the supply.

at supply = zone need + Σ (friction + elevation) along the main + supply loss

The main is taken to carry one zone's flow at a time, as the comment says. Terms are added from the valve back to the supply. The comparison with the available pressure and flow allows 1e-9 of slack.

  • landscape/irrigation.js
  • lines 267 to 274
  • CivilCraft 0.8.1
let loss = 0;
for (let n = vn; parent.get(n.i); n = parent.get(n.i).from) {
  const par = parent.get(n.i), L = Math.hypot(n.x - par.from.x, n.y - par.from.y) * k, sz = sizeOf(par.p.size);
  const hw = hazenWilliams({ Q: z.flow, d: sz.id, L, C: S.C });   // one zone runs at a time: the main carries that zone's flow
  loss += hw.kPa + (elev(n) - elev(par.from)) * G;
  if (hw.v > S.vMax + 1e-9 && !z.problems.some((x) => x.key === 'mainvel')) z.problems.push({ key: 'mainvel', id: par.p.id, v: hw.v, max: S.vMax });
}
z.atSupply = z.pressureNeed + loss + (irr.supply.loss || 0);

Zone precipitation rate and weekly run time

64-bit

The precipitation rate of a zone and the minutes a week it must run for the weekly water depth.

pr = 60 × Σ flow ÷ Σ (r² × arc ÷ 360); minutes = weekly mm ÷ pr × 60

The area is the sum of each head's own square cell, not the area actually wetted on the plan, so overlap and gaps between heads do not enter. A zone with no cell area gets a rate of 0 and 0 minutes.

  • landscape/irrigation.js
  • lines 251 to 253
  • CivilCraft 0.8.1
const cells = zone.heads.reduce((a, h) => a + headCell(h, cat.get(h.type)), 0);   // each head's head-to-head cell
zone.pr = cells > 0 ? precipRate(zone.heads.reduce((a, h) => a + headFlow(h, cat.get(h.type)), 0), cells) : 0;
zone.minutes = zone.pr > 0 ? (S.weekly / zone.pr) * 60 : 0;

Bill of materials

64-bit

Counts of heads, total pipe length by size, drip length and emitter count.

pipe length = Σ √(Δx² + Δy²) × meters per project unit

Lengths are plan lengths in meters, summed per size in drawing order. No allowance for slope, fittings or waste is added.

  • landscape/irrigation.js
  • lines 283 to 290
  • CivilCraft 0.8.1
export function billOfMaterials(irr, units) {
  const k = unitM(units), cat = catalogOf(irr), heads = new Map(), pipes = new Map();
  for (const h of irr.heads) { const key = h.type + '|' + h.arc; const r = heads.get(key) || { type: h.type, name: cat.get(h.type)?.name || h.type, arc: h.arc, n: 0 }; r.n++; heads.set(key, r); }
  for (const p of irr.pipes) { const L = Math.hypot(p.b.x - p.a.x, p.b.y - p.a.y) * k, r = pipes.get(p.size) || { size: p.size, label: sizeOf(p.size)?.label || p.size, m: 0 }; r.m += L; pipes.set(p.size, r); }
  let drip = 0, emitters = 0;
  for (const d of irr.drips) { const L = dripLen(d, k); drip += L; emitters += Math.floor(L / d.spacing) + 1; }
  return { heads: [...heads.values()].sort((a, b) => a.name.localeCompare(b.name) || a.arc - b.arc), pipes: [...pipes.values()], drip, emitters, valves: irr.valves.length, stations: irr.valves.length };
}

Coverage grid and head-to-head check

64-bit

The share of the area reached by no head, one head or two and more, and the heads whose nearest neighbor is beyond their throw.

cell = larger extent ÷ 200, grown by 1.5 until at most 200000 cells; far when nearest head > 1.0001 × throw

Counts are taken at cell centers stepped by repeated addition, so the percentages are a sampling of the area. The arc test compares an angle from Math.atan2 in degrees with the arc plus 1e-9.

  • landscape/irrigation.js
  • lines 292 to 325
  • CivilCraft 0.8.1
/** Coverage on a grid inside `area` (or the heads' bounds): cells wet by none (dry), one, two or more heads; and the
 *  head-to-head check (every head reaches its nearest neighbour). */
export function coverage(irr, units, area = null, { cell = null, max = 200000 } = {}) {
  const k = unitM(units), cat = catalogOf(irr), heads = irr.heads.map((h) => ({ ...h, r: headRadius(h, cat.get(h.type)) / k }));
  if (!heads.length) return { cells: 0, dry: 0, single: 0, double: 0, far: [] };
  let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
  const pts = area || heads.flatMap((h) => [{ x: h.x - h.r, y: h.y - h.r }, { x: h.x + h.r, y: h.y + h.r }]);
  for (const p of pts) { x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y); x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y); }
  let c = cell || Math.max((x1 - x0) / 200, (y1 - y0) / 200);
  while (((x1 - x0) / c) * ((y1 - y0) / c) > max) c *= 1.5;
  const inArc = (h, x, y) => {
    const dx = x - h.x, dy = y - h.y, d = Math.hypot(dx, dy);
    if (d > h.r) return false;
    if (h.arc >= 360 || d < 1e-9) return true;
    const a = (((Math.atan2(dy, dx) * 180) / Math.PI - h.rot) % 360 + 360) % 360;
    return a <= h.arc + 1e-9;
  };
  const inside = (x, y) => { if (!area) return true; let w = false; for (let i = 0, j = area.length - 1; i < area.length; j = i++) { const a = area[i], b = area[j]; if ((a.y > y) !== (b.y > y) && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) w = !w; } return w; };
  let cells = 0, dry = 0, single = 0, dbl = 0;
  for (let x = x0 + c / 2; x < x1; x += c) for (let y = y0 + c / 2; y < y1; y += c) {
    if (!inside(x, y)) continue;
    cells++;
    let n = 0;
    for (const h of heads) if (inArc(h, x, y) && ++n >= 2) break;
    if (n === 0) dry++; else if (n === 1) single++; else dbl++;
  }
  const far = [];
  for (const h of heads) {
    let nd = Infinity;
    for (const o of heads) if (o !== h) nd = Math.min(nd, Math.hypot(o.x - h.x, o.y - h.y));
    if (heads.length > 1 && nd > h.r * 1.0001) far.push({ id: h.id, gap: nd * k, r: h.r * k });   // head-to-head: the nearest head is within this head's throw
  }
  return { cells, dry, single, double: dbl, cell: c, far };
}
landscape/irr-ui.js CivilCraft 0.8.1 64-bit 4 calculations Reviewed

Tests None found by the review

Unit text for labels, the check table and its CSV

64-bit

Turns kPa, L/min, meters and mm/h into the text shown, in US units for a feet project.

psi = kPa ÷ 6.894757; gpm = L/min ÷ 3.785412; ft = m ÷ 0.3048; in/h = mm/h ÷ 25.4

Values are formatted to 0, 1 or 2 decimals. The CSV export of the check table writes this same rounded text with its unit, not the full values. Feet are formed with 0.3048 although the engine's lengths in meters were made from project feet of 1200 ÷ 3937 m.

  • landscape/irr-ui.js
  • lines 26 to 29
  • CivilCraft 0.8.1
const pTxt = (kpa) => (ft() ? fixed(kpa / KPA_PER_PSI, 1) + ' psi' : fixed(kpa, 0) + ' kPa');   // i18n-ignore: units
const qTxt = (lmin) => (ft() ? fixed(lmin / LMIN_PER_GPM, 2) + ' gpm' : fixed(lmin, 1) + ' L/min');   // i18n-ignore: units
const lTxt = (m) => (ft() ? fixed(m / 0.3048, 1) + ' ft' : fixed(m, 1) + ' m');   // i18n-ignore: units
const prTxt = (mmh) => (ft() ? fixed(mmh / 25.4, 2) + ' in/h' : fixed(mmh, 1) + ' mm/h');   // i18n-ignore: units

Head radius typed in feet

64-bit

Stores a head's throw radius, typed in project units, as meters.

radius (m) = typed feet × 0.3048

The typed value is converted with the international foot, 0.3048, and the field shows the stored value to 2 decimals. The plan then draws the radius by dividing by 1200 ÷ 3937, so a typed radius is drawn about 2 parts in a million smaller in project feet.

  • landscape/irr-ui.js
  • line 163
  • CivilCraft 0.8.1
num(t('ls.i.f.radius', { u: ft() ? 'ft' : 'm' }), 'cv-ir-r', it.radius == null ? '' : fixed(it.radius / (ft() ? 0.3048 : 1), 2), (n) => { if (!(n > 0)) return false; set(t('ls.i.undo.head'), (x) => { x.radius = n * (ft() ? 0.3048 : 1); }); });

Supply pressure, flow and loss typed in US units

64-bit

Stores the supply's pressure, available flow and loss in kPa and L/min from values typed in psi and gpm.

kPa = psi × 6.894757; L/min = gpm × 3.785412

The stored value is the typed number times a 7-digit constant. The field is refilled from the stored value formatted to 1 decimal, which is display only; the stored number is not rounded.

  • landscape/irr-ui.js
  • lines 188 to 190
  • CivilCraft 0.8.1
num(t(ft() ? 'ls.i.f.psi' : 'ls.i.f.kpa'), 'cv-ir-press', fixed(ft() ? it.pressure / KPA_PER_PSI : it.pressure, 1), (n) => { if (!(n > 0)) return false; set(t('ls.i.undo.poc'), (x) => { x.pressure = ft() ? n * KPA_PER_PSI : n; }); });
num(t(ft() ? 'ls.i.f.gpm' : 'ls.i.f.lmin'), 'cv-ir-flow', fixed(ft() ? it.flow / LMIN_PER_GPM : it.flow, 1), (n) => { if (!(n >= 0)) return false; set(t('ls.i.undo.poc'), (x) => { x.flow = ft() ? n * LMIN_PER_GPM : n; }); });
num(t(ft() ? 'ls.i.f.losspsi' : 'ls.i.f.losskpa'), 'cv-ir-ploss', fixed(ft() ? it.loss / KPA_PER_PSI : it.loss, 1), (n) => { if (!(n >= 0)) return false; set(t('ls.i.undo.poc'), (x) => { x.loss = ft() ? n * KPA_PER_PSI : n; }); });

Irrigation quantities for takeoff

64-bit

Head counts and pipe and drip lengths passed to TakeoffCraft.

LF = round(m ÷ 0.3048 × 100) ÷ 100

Lengths are rounded to 2 decimals before they are handed over. In a feet project the meters, which were made with 1200 ÷ 3937 m per foot, are divided by 0.3048, so the length in feet is about 2 parts in a million larger than the drawn length.

  • landscape/irr-ui.js
  • lines 319 to 326
  • CivilCraft 0.8.1
quantities() {
  const r = result().bom, m = !ft(), out = [];
  for (const h of r.heads) out.push({ key: 'irr:' + h.type + ':' + h.arc, name: typeName(h.type) + ' ' + h.arc + '°', unit: 'EA', qty: h.n });   // i18n-ignore: a unit symbol
  for (const p of r.pipes) out.push({ key: 'irr:' + p.size, name: p.label, unit: m ? 'm' : 'LF', qty: Math.round((m ? p.m : p.m / 0.3048) * 100) / 100 });   // i18n-ignore: unit symbols
  if (r.valves) out.push({ key: 'irr:valves', name: t('ls.i.c.valves'), unit: 'EA', qty: r.valves });   // i18n-ignore: a unit symbol
  if (r.drip) out.push({ key: 'irr:drip', name: t('ls.i.c.drip'), unit: m ? 'm' : 'LF', qty: Math.round((m ? r.drip : r.drip / 0.3048) * 100) / 100 });   // i18n-ignore: unit symbols
  return out;
},

In detail

Method
Zones are trees of laterals from each valve; flows are summed upstream and losses accumulated downstream with Hazen-Williams, hf = 10.67 L Q^1.852 ÷ (C^1.852 d^4.8704), plus elevation. The worst head sets the pressure needed. Christiansen's multiple-outlet factor is cited to 1942 in the code. Coverage is a count of grid cells reached by no head, one head or more.
Number types
64-bit numbers.
Tolerances
Pipe ends, heads and valves join within the joinTol setting, 0.05 m by default. Velocity, pressure and flow checks use an absolute slack of 1e-9. The head-to-head check allows a factor of 1.0001 on the throw. The coverage grid has about 200 cells across the larger extent and at most 200000 cells.
Limits
Hazen-Williams is empirical. KPA_PER_PSI = 6.894757 and LMIN_PER_GPM = 3.785412 are rounded; the triangular spacing factor is 0.866 and the US precipitation constant is 96.3. Pipe lengths are plan lengths. Drip lines are given a fixed need of 140 kPa.

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