Resources Numerical accuracy Calculation policy

Gaussian plume concentration and contours

Works out the concentration downwind of a continuous release, how far a concentration level reaches, and the outline of that level on a site plan.

How the precision is kept

All arithmetic is 64-bit. The concentration itself is a closed form, but the dispersion coefficients are two-digit empirical constants, the reach is found on a fixed grid of distances followed by bisection, and the contour is a polygon through a fixed number of stations. Those choices, not the number type, set the accuracy.

Open item. Refine the reach inside every grid cell where the concentration crosses the endpoint, not only the last one, and state the grid in the result.

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.

process/env/plume.js CadCraft 0.8.2 64-bit 7 calculations Reviewed

Tests tools/process-test/test-env.mjs

Dispersion coefficients

64-bit

The crosswind and vertical spread of the plume at a distance downwind, for a stability class and terrain.

σy = ay·x·(1 + by·x)^ey; σz = az·x·(1 + bz·x)^ez

One 64-bit expression per coefficient using Math.pow. The constants ay, az, by, bz are empirical values with one or two significant digits.

  • process/env/plume.js
  • lines 41 to 46
  • CadCraft 0.8.2
/** Briggs sigmas (m) at downwind distance x (m). terrain 'rural' (default, open country) or 'urban'. */
export function sigma(stab, x, terrain = 'rural') {
  const t = (terrain === 'urban' ? URBAN : RURAL)[stab];
  if (!t) throw new Error('Stability class A to F.');
  return { sy: t.ay * x * Math.pow(1 + t.by * x, t.ey), sz: t.az * x * (t.bz ? Math.pow(1 + t.bz * x, t.ez) : 1) };
}

Plume concentration at a point

64-bit

The concentration at a point downwind, crosswind and above ground from a continuous release.

C = Q ÷ (2π·u·σy·σz) × exp(−y² ÷ 2σy²) × [exp(−(z − H)² ÷ 2σz²) + exp(−(z + H)² ÷ 2σz²)]

64-bit throughout. With a mixing lid the reflections are a fixed sum of seven image pairs (n = −3 to 3), and the formula switches to a uniformly mixed column when σz > 1.6 × lid height, so the result steps at that switch. It returns 0 for x ≤ 0 and for a release at or above the lid.

  • process/env/plume.js
  • lines 47 to 60
  • CadCraft 0.8.2
/** Concentration (Q units per m³) at (x downwind, y crosswind, z height) from a continuous release Q (mass/s) at effective height H, wind u (m/s) at that height.
 *  Optional `zi` (m) = mixing height: images reflect the plume between the ground and the lid. */
export function plumeConc({ Q, u, H = 0, stab = 'D', terrain = 'rural', zi = 0 }, x, y = 0, z = 0) {
  if (!(Q >= 0) || !(u > 0)) throw new Error('Plume needs Q >= 0 and wind u > 0.');
  if (x <= 0) return 0;
  const { sy, sz } = sigma(stab, x, terrain), lat = Math.exp(-y * y / (2 * sy * sy));
  if (zi > 0) {
    if (H >= zi) return 0;                                     // released above the lid: nothing reaches the ground in this model
    if (sz > 1.6 * zi) return Q / (Math.sqrt(2 * Math.PI) * u * sy * zi) * lat;
    let s = 0; for (let n = -3; n <= 3; n++) s += Math.exp(-((z - H + 2 * n * zi) ** 2) / (2 * sz * sz)) + Math.exp(-((z + H + 2 * n * zi) ** 2) / (2 * sz * sz));
    return Q / (2 * Math.PI * u * sy * sz) * lat * s;
  }
  return Q / (2 * Math.PI * u * sy * sz) * lat * (Math.exp(-((z - H) ** 2) / (2 * sz * sz)) + Math.exp(-((z + H) ** 2) / (2 * sz * sz)));
}

Reach of a concentration level

64-bit

The farthest downwind distance where the ground-level centerline concentration is still at or above an endpoint.

Scans 401 distances at a constant ratio between xMin and xMax (5 m to 50 km by default, about 2.3 % apart), keeps the farthest one at or above the endpoint, then halves the next grid cell 60 times. A stretch above the endpoint that lies wholly between two grid distances is not seen.

  • process/env/plume.js
  • lines 61 to 71
  • CadCraft 0.8.2
/** Farthest downwind distance (m) where the ground-level centreline concentration is still >= Cend; scans a log grid then refines. null when it is never reached. */
export function plumeReach(p, Cend, { xMin = 5, xMax = 50000 } = {}) {
  if (!(Cend > 0)) throw new Error('Give the endpoint concentration (kg/m³).');
  const at = (x) => plumeConc(p, x, 0, 0); let last = null, n = 400;
  for (let i = 0; i <= n; i++) { const x = xMin * Math.pow(xMax / xMin, i / n); if (at(x) >= Cend) last = x; }
  if (last == null) return null;
  const hi = last * Math.pow(xMax / xMin, 1 / n); let a = last, b = Math.min(hi, xMax);
  if (at(b) >= Cend) return b;
  for (let i = 0; i < 60; i++) { const m = (a + b) / 2; if (at(m) >= Cend) a = m; else b = m; }
  return a;
}

Half-width of a contour

64-bit

Half the crosswind width of a concentration level at a given distance.

w = σy × √(2 × ln(C₀ ÷ C_end))

Closed form from the centerline concentration. The function returns 0 first when the centerline is below the endpoint, so the logarithm is never negative.

  • process/env/plume.js
  • line 72
  • CadCraft 0.8.2
const halfWidth = (p, Cend, x) => { const c = plumeConc(p, x, 0, 0); if (c < Cend) return 0; return sigma(p.stab || 'D', x, p.terrain).sy * Math.sqrt(2 * Math.log(c / Cend)); };

Height of a contour

64-bit

The height above ground up to which the concentration stays at or above the endpoint at a given distance.

Bisection on height: 50 halvings of the bracket from 0 to 5σz + H + 10 m. It returns 0 whenever the ground-level value is below the endpoint, also for an elevated release, and returns the bracket top unchanged when the value there is still at or above the endpoint.

  • process/env/plume.js
  • line 73
  • CadCraft 0.8.2
const topHeight = (p, Cend, x) => { let lo = 0, hi = 5 * sigma(p.stab || 'D', x, p.terrain).sz + (p.H || 0) + 10; if (plumeConc(p, x, 0, 0) < Cend) return 0; if (plumeConc(p, x, 0, hi) >= Cend) return hi; for (let i = 0; i < 50; i++) { const m = (lo + hi) / 2; if (plumeConc(p, x, 0, m) >= Cend) lo = m; else hi = m; } return lo; };

Contour polygon in site coordinates

64-bit

The outline of a concentration level as a polygon on the site plan, with its reach and greatest height.

(x, y) → (cx + x·cos t − y·sin t, cy + x·sin t + y·cos t)

Stations are evenly spaced from 1 m to the reach (41 by default) and the outline joins their half-widths with straight segments, so the outline is as fine as the station count. The rotation uses one Math.cos and one Math.sin of the wind angle; points are not rounded.

  • process/env/plume.js
  • lines 74 to 81
  • CadCraft 0.8.2
/** The contour as a footprint polygon in site coordinates: origin at (cx, cy), wind blowing TOWARD angle `toDeg` (mathematical: 0 = +x, 90 = +y). → { reach, polygon: [[x, y]…], height } */
export function plumeFootprint(p, Cend, { cx = 0, cy = 0, toDeg = 0, steps = 40 } = {}) {
  const reach = plumeReach(p, Cend); if (reach == null) return { reach: null, polygon: [], height: 0 };
  const xs = Array.from({ length: steps + 1 }, (_, i) => 1 + (reach - 1) * i / steps), w = xs.map((x) => halfWidth(p, Cend, x)), t = toDeg * Math.PI / 180, c = Math.cos(t), s = Math.sin(t);
  const rot = (x, y) => [cx + x * c - y * s, cy + x * s + y * c];
  const polygon = [...xs.map((x, i) => rot(x, w[i])), ...xs.map((x, i) => rot(x, -w[i])).reverse()];
  return { reach, polygon, height: Math.max(...xs.map((x) => topHeight(p, Cend, x))) };
}

Gas density relative to air

64-bit

Whether the released gas is heavier than air, which makes a Gaussian result untrustworthy.

ratio = (P·M ÷ R·T) ÷ (P·M_air ÷ R·T_air); dense when ratio > 1.1

Ideal-gas densities at the same pressure, so the pressure and R cancel in the ratio. The molar mass of air is the constant 0.028964 kg/mol and the air temperature defaults to 293.15 K.

  • process/env/plume.js
  • lines 82 to 83
  • CadCraft 0.8.2
/** Is the released gas denser than air at release (so a Gaussian screening result is not trustworthy)? */
export function isDense({ M, T, Pa = P_ATM, Tair = 293.15, ratio = 1.1 }) { const rho = Pa * M / (R * T), rhoAir = Pa * M_AIR / (R * Tair); return { dense: rho / rhoAir > ratio, ratio: rho / rhoAir }; }

In detail

Method
Gaussian plume with ground reflection; Briggs (1973) open-country and urban dispersion coefficients as listed in the file's SOURCES; method of images for a mixing lid.
Number types
64-bit numbers throughout.
Tolerances
Reach: 401 distances at a constant ratio, then 60 bisections. Plume top: 50 bisections. Lid: well-mixed column when σz > 1.6 × lid height.
Limits
The file's REVIEW constant states that the coefficients were typed without the source open and are not confirmed. The contour has 41 stations per side by default. A dense gas is flagged, not modeled.

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