
Atmospheric dispersion: sigmas, plume rise, plume, puff, grid and particles
Source:R/air_dispersion.R
pg_sigmas.RdSix standard models of how a pollutant released into the air spreads downwind, for exposure estimates around a stack, a fire or an industrial site (for example when relating air quality to health or justice outcomes by neighbourhood):
pg_sigmas(): how wide and how tall a plume has grown at a given distance, by atmospheric stability class;briggs_plume_rise(): how far a hot plume rises above the stack top;gaussian_plume(): the steady concentration at receptor points from a continuous release;gaussian_puff(): the concentration from a single instantaneous release, some time after it;advection_diffusion_2d(): a concentration field on a grid moved by wind and spread by diffusion, step by step;lagrangian_particles(): the same physics as a random walk of many particles.
They are screening models with textbook assumptions (flat terrain, steady uniform wind, no chemistry or deposition), not regulatory model runs.
Usage
pg_sigmas(x, stability = "D", setting = "rural")
briggs_plume_rise(
x,
u,
diameter,
exit_velocity,
stack_temp,
ambient_temp,
stability = "D",
dtheta_dz = NULL,
g = 9.80616
)
gaussian_plume(
q,
u,
h,
receptors,
stability = "D",
setting = "rural",
mixing_height = NULL,
n_images = 3
)
gaussian_puff(
mass,
u,
h,
receptors,
t,
stability = "D",
setting = "rural",
mixing_height = NULL,
n_images = 3
)
advection_diffusion_2d(
c0,
u,
v,
kx,
ky,
dx,
dy,
dt,
n_steps,
source = NULL,
max_work = 1e+08
)
lagrangian_particles(
n,
x0,
y0,
u,
v,
kx,
ky,
dt,
n_steps,
seed = 0,
grid = NULL,
normals = NULL,
max_work = 1e+08
)Arguments
- x
Downwind distance(s) in metres.
- stability
Pasquill stability class, one of
"A"to"F".- setting
"rural"(the default) or"urban": which set of Briggs coefficients to use.- u
Wind speed in m/s (the x velocity for the grid models).
- diameter, exit_velocity, stack_temp, ambient_temp
Stack diameter (m), exit velocity (m/s), stack gas and ambient temperatures (K). Only buoyant rise is modelled: a stack at the air's temperature (within half a kelvin) has zero rise, and momentum rise from the exit velocity is not added.
- dtheta_dz
Potential temperature gradient (K/m) for the stable classes; the defaults are 0.020 (E) and 0.035 (F).
- g
Gravitational acceleration in m/s^2, default 9.80616.
- q
Emission rate (mass per second, for example g/s).
- h
Effective stack height (m): physical height plus plume rise.
- receptors
A matrix with columns x, y, z, or a list of length-3 vectors.
- mixing_height
Mixing-lid height in metres, or
NULL(the default) for no lid. Under a lid the plume is reflected between the ground and the lid.- n_images
Whole number, default 3: the number of lid image pairs (0 to 1,000). The terms decay quickly; a few are enough.
- mass
Puff mass (for example g).
- t
Time since release (s).
- c0
Initial concentration grid: a non-negative numeric matrix, one row per x cell and one column per y cell.
- v
Wind velocity along y (m/s), may be negative.
- kx, ky
Eddy diffusivities along x and y (m^2/s), non-negative.
- dx, dy
Grid spacings (m), positive.
- dt
Time step (s), positive.
- n_steps
Whole number of time steps (0 returns the start).
- source
Source grid (concentration per unit time), or
NULL.- max_work
The most cell updates (cells x steps x sub-steps; for
lagrangian_particles(), particles x steps) a run may take, 100 million by default (a few minutes of the explicit loop);Inflifts it. The same physical run costs the same whether the caller sub-steps it or the scheme does, so the cap reads the same either way.- n
Whole number of particles, at most 100 million.
- x0, y0
Release point (m).
- seed
Number, default 0: the seed of the particle random walk.
- grid
NULL, orc(x_min, x_max, y_min, y_max, nx, ny)to count particles into a concentration grid.- normals
A function
(n, seed, stream)returningnstandard normals; stepk(from 0) draws streams2kfor x and2k + 1for y. The default draws from the package's shared uniform stream (core_uniforms) through the normal quantile. rmorie'sLagrangianParticles()uses its own Philox stream, so its paths differ from the default here; pass its generator to reproduce them.
Value
pg_sigmas(): a list with sigma_y and sigma_z.
briggs_plume_rise(): a list with flux, final_rise,
x_final and rise (at each x). gaussian_plume()
and gaussian_puff(): a numeric vector of concentrations, one per
receptor. advection_diffusion_2d(): a list with field,
mass, cfl, diffusion_number,
stability_number, stable, dt, substeps and
the axes, as a list of class rmbl_field that plot()
draws as an image; lagrangian_particles() a list of class
rmbl_particles that plot() draws as points, with
x, y (final particle positions), mean_x,
mean_y, grid (as given) and, with a grid,
concentration (an nx by ny matrix of particle
density per unit area, summing to the share of particles inside the grid
divided by the cell area).
Details
pg_sigmas(): Briggs (1973) fits of the Pasquill-Gifford dispersion
coefficients by stability class A-F, rural
sigma_y = a_y x (1 + 0.0001 x)^(-1/2) (urban: 0.0004) and
sigma_z = a_z x (1 + b_z x)^(e_z).
briggs_plume_rise(): buoyancy flux
F = g v_s d^2 (T_s - T_a) / (4 T_s); final rise
21.425 F^(3/4) / u with x_f = 49 F^(5/8) when F < 55,
else 38.71 F^(3/5) / u with x_f = 119 F^(2/5), for classes A-D;
2.6 (F / (u s))^(1/3) with s = g (dtheta/dz) / T_a and
x_f = 2.0715 u / sqrt(s) for E and F; transitional rise
1.6 F^(1/3) x^(2/3) / u before x_f (EPA ISC3).
gaussian_plume():
Q / (2 pi u sigma_y sigma_z) exp(-y^2 / (2 sigma_y^2)) V,
with V the vertical term with ground reflection and, under a mixing
lid, n_images pairs of image terms.
gaussian_puff(): an instantaneous release with sigma_x = sigma_y
evaluated at the travel distance u t.
advection_diffusion_2d(): explicit upwind advection, central
diffusion, forward Euler, zero boundaries. The explicit scheme is stable
when |u dt/dx| + |v dt/dy| + 2 (kx dt/dx^2 + ky dt/dy^2) <= 1
(the von Neumann bound for upwind advection with forward-time
centred-space diffusion); that sum is returned as
stability_number with stable. A call outside the bound is
not run as given: each step is split into enough sub-steps of a smaller
dt to satisfy it (reported as substeps and dt),
with a warning, so the field is a solution over the same physical time
rather than a numerical explosion. The separate cfl and
diffusion_number are reported for reference; each below 1 is
necessary, not sufficient.
lagrangian_particles(): the random walk
x + u dt + sqrt(2 K dt) xi (Thomson 1987) with an optional counting
grid.
The stability classes run from "A" (very unstable, sunny with
light wind) through "D" (neutral, overcast or windy) to
"F" (stable, clear night with light wind). Units are SI: metres,
seconds, kelvin; with q in g/s, gaussian_plume() returns
g/m^3, and with mass in g, gaussian_puff() returns g/m^3.
The plume is not defined at or upwind of the source: a receptor with
\(x \le 0\) gets 0. The plume's h is the effective height,
the stack height plus the rise from briggs_plume_rise().
Every physical input is checked: emission rates, masses, heights and
distances are non-negative, wind speed, travel time and grid steps
positive, n_images a whole number up to 1,000, the stack as hot
as the air or hotter (temperatures below 150 are refused as probably
Celsius), and x0, y0 single numbers; a release above
mixing_height warns, and so does a receptor below ground or above
the lid (the reflection terms give mirror values there). Unknown
stability classes or settings are errors.
Cost: gaussian_plume() and gaussian_puff() are fast for
thousands of receptors. advection_diffusion_2d() is an R loop at
roughly 0.8 million cell updates a second, and both grid models refuse a
run above max_work cell or particle updates. The particle walk
is reproducible from seed and does not touch R's random number
stream. Progress bars are shown in an interactive session.
Boundary
the outermost ring of cells is held at zero (an open boundary):
mass
that reaches it leaves the domain and mass falls accordingly. Pad c0
with
empty cells so the plume of interest stays inside the ring.
References
Briggs, G. A. (1973). Diffusion estimation for small emissions. ATDL Contribution 79. Briggs, G. A. (1975). Plume rise predictions. American Meteorological Society. Turner, D. B. (1970). Workbook of Atmospheric Dispersion Estimates. EPA AP-26. Seinfeld, J. H. and Pandis, S. N. (2016). Atmospheric Chemistry and Physics, 3rd ed. Thomson, D. J. (1987). Criteria for the selection of stochastic models of particle trajectories in turbulent flows. Journal of Fluid Mechanics 180, 529-556.
Examples
# plume widths 1 km downwind: neutral, then very stable, rural and urban
pg_sigmas(1000, "D")
#> $sigma_y
#> [1] 76.27701
#>
#> $sigma_z
#> [1] 37.94733
#>
pg_sigmas(c(500, 1000, 2000), "F", setting = "urban")
#> $sigma_y
#> [1] 50.20790 92.96697 163.97832
#>
#> $sigma_z
#> [1] 30.23716 50.59644 80.00000
#>
# rise of a hot plume, and the effective height it gives a 50 m stack
rise <- briggs_plume_rise(c(100, 1000), u = 4, diameter = 1, exit_velocity = 12,
stack_temp = 420, ambient_temp = 285)
rise$rise
#> [1] 18.22335 28.88281
h_eff <- 50 + rise$final_rise
# ground-level concentration (g/m^3) from 100 g/s along the centreline
rec <- cbind(x = c(500, 1000, 2000, 8000), y = 0, z = 0)
gaussian_plume(100, u = 4, h = h_eff, receptors = rec)
#> [1] 2.120279e-05 3.168716e-04 3.826280e-04 1.051326e-04
# under a 120 m mixing lid the far receptor sees a trapped plume
gaussian_plume(100, u = 4, h = h_eff, receptors = rec, mixing_height = 120)
#> [1] 2.120279e-05 3.172063e-04 4.073068e-04 1.738501e-04
# a 1 kg puff, 10 minutes after release, at the puff centre
gaussian_puff(1000, u = 3, h = 10, receptors = rbind(c(1800, 0, 0)), t = 600)
#> [1] 0.0001266794
# a small grid: a blob moved by wind and spread by diffusion
c0 <- matrix(0, 15, 15)
c0[4, 8] <- 1
f <- advection_diffusion_2d(c0, u = 1, v = 0, kx = 0.5, ky = 0.5,
dx = 1, dy = 1, dt = 0.2, n_steps = 20)
f$stable
#> [1] TRUE
round(f$mass, 3)
#> [1] 0.962
# 2,000 particles, counted on a grid
p <- lagrangian_particles(2000, x0 = 0, y0 = 0, u = 1, v = 0, kx = 1, ky = 1,
dt = 1, n_steps = 10, seed = 1,
grid = c(-10, 30, -20, 20, 8, 8))
c(p$mean_x, p$mean_y)
#> [1] 10.033977334 -0.003237509
# inputs that cannot be right are refused
try(pg_sigmas(100, "G"))
#> Error : `stability` must be one of the Pasquill classes "A" to "F"
try(briggs_plume_rise(100, u = 4, diameter = 1, exit_velocity = 12,
stack_temp = 150 + 5, ambient_temp = 20))
#> Error : temperatures are in kelvin: stack_temp 155 and ambient_temp 20 look like degrees Celsius (ambient air is about 290 K)