Skip to content

Commit 25ac410

Browse files
committed
Added periodic mixing layer
Added mixing layer model based on an infinite sum of cosines. This converges very slowly (in my testing) but is used in some places.
1 parent c7b532d commit 25ac410

5 files changed

Lines changed: 118 additions & 54 deletions

File tree

docs/src/plume.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,23 +222,25 @@ The gaussian mixing layer model allows for the plume to be contained entirely wi
222222
c(x, y, z) = \frac{m_i}{u} \frac{1}{\sqrt{2\pi} \sigma_y} \exp\left(-\frac{1}{2}\left(\frac{y}{\sigma_y}\right)^2\right) F_z
223223
```
224224

225-
Where $F_z$ is the vertical dispersion term. For the method of images this takes the form of the infinite sum:
225+
Where $F_z$ is the vertical dispersion term. For the method of images this takes the form of the infinite sum ([Beychok 1994](references.md) p 123)
226226

227227
```math
228228
F_z = \frac{1}{\sqrt{2\pi} \sigma_z} \left( \exp \left( -\frac{1}{2} \left( { z -h } \over \sigma_{z} \right)^2 \right)
229229
+ \exp \left( -\frac{1}{2} \left( { z + h } \over \sigma_{z} \right)^2 \right) \right) \\
230-
\sum_{i=1}^{n} \left[ \exp\left(-\frac{1}{2}\left(\frac{z - h + 2i h_m}{\sigma_z}\right)^2\right)
231-
+ \exp\left(-\frac{1}{2}\left(\frac{z - h - 2i h_m}{\sigma_z}\right)^2\right) \\
232-
+ \exp\left(-\frac{1}{2}\left(\frac{z + h + 2i h_m}{\sigma_z}\right)^2\right) \\
233-
+ \exp\left(-\frac{1}{2}\left(\frac{z + h - 2i h_m}{\sigma_z}\right)^2\right) \right]
230+
\sum_{n=1}^{\infty} \left[ \exp\left(-\frac{1}{2}\left(\frac{z - h + 2n h_m}{\sigma_z}\right)^2\right)
231+
+ \exp\left(-\frac{1}{2}\left(\frac{z - h - 2n h_m}{\sigma_z}\right)^2\right) \\
232+
+ \exp\left(-\frac{1}{2}\left(\frac{z + h + 2n h_m}{\sigma_z}\right)^2\right) \\
233+
+ \exp\left(-\frac{1}{2}\left(\frac{z + h - 2n h_m}{\sigma_z}\right)^2\right) \right]
234234
```
235235

236-
For the periodic boundary it takes the form of another infinite sum:
236+
For the periodic boundary it takes the form of another infinite sum ([Seinfeld and Pandis 2006](references.md) p 858)
237237

238238
```math
239-
F_z = ...
239+
F_z = \frac{2}{h_m} \left( \frac{1}{2} + \sum_{n=1}^{\infty} cos\left( {n\pi z} \over h_m \right) cos\left( {n\pi h} \over h_m \right) \exp\left(-\frac{1}{2}\left(\frac{n\pi \sigma_z}{h_m}\right)^2\right) \right)
240240
```
241241

242+
The periodic boundary layer approach can be very slow to converge, in which case the number of terms given by the keyword argument `n_terms` should be increased. By default the sums terminate early if the solution converges, e.g. if `n_terms=100_000_000` or some other huge number but the sum converges after 5 terms, only 5 terms will be calculated. The simple mixing layer approach converges much more quickly, and typically 10 terms are more than enough.
243+
242244
As `SimpleAtmosphere`s do not define mixing height, one is calculated based on the following:
243245
- for stable atmospheres (class E and F) the mixing height is assumed to be infinite, and the model defaults back to a [Simple Gaussian Plume](@ref)
244246
- for unstable and neutral atmospheres (classes A through D) the mixing height is calculated from the friction velocity $u^{*}$ and the coriolis parameter $f$: $h_m = 0.3 \frac{u^{*}}{f}$ where $f$ is calculated at a default latitude of 40°N (consistent with [US EPA 1995](references.md)).

docs/src/references.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
AIChE/CCPS. 1999. *Guidelines for Consequence Analysis of Chemical Releases*. New York: American Institute of Chemical Engineers
44

5+
Beychok, Milton R. 1994. *Fundamentals of Stack Gas Dispersion* 3rd Ed. Irvine, CA: Milton R. Beychok.
6+
57
Bakkum, E.A. and N.J. Duijm. 2005. "Chapter 4 - Vapour Cloud Dispersion" in *Methods for the Calculation of Physical Effects, CPR 14E* (TNO Yellow Book) Edited by C.J.H. van den Bosch and R.A.P.M. Weterings. The Netherlands.
68

79
Businger, J. A., J. C. Wyngaard, Y. Izumi, and E. F. Bradley. 1971. "Flux-Profile Relationships in the Atmospheric Surfaace Layer." *Journal of the Atmospheric Sciences*. 28, 181-189. [doi:10.1175/1520-0469(1971)028<0181:FPRITA>2.0.CO;2](https://doi.org/10.1175/1520-0469(1971)028<0181:FPRITA>2.0.CO;2)
@@ -30,7 +32,7 @@ Paulson, C. A. 1970. "The Mathematical Representation of Wind Speed and Temperat
3032

3133
Spicer, Thomas O. and Jerry A. Havens. 1988. *Development of Vapor Dispersion Models for Non-Neutrally Buoyant Gas Mixtures--Analysis of TFI/NH3 Test Data*. Tyndall Air Force Base, FL: USAF Engineering & Services Laboratory
3234

33-
Seinfeld, John H. 1986. *Atmospheric Chemistry and Physics of Air Pollution*. New York: John Wiley and Sons
35+
Seinfeld, John H. and Spyros N. Pandis. 2006. *Atmospheric Chemistry and Physics* 2nd Ed. New York: John Wiley and Sons.
3436

3537
Turner, D. Bruce. 1970. *Workbook of Atmospheric Dispersion Estimates*. United States Environmental Protection Agency.
3638

src/models/gaussian_mixing_layer.jl

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# for dispatch
22
struct GaussianMixingLayer <: PlumeModel end
33

4-
# new vertical term
4+
# mixing layer vertical term -- method of images
55
struct SimpleMixingLayer{N<:Integer,F<:Number} <: GaussianVerticalTerm
66
nterms::N
77
mixing_height::F
@@ -23,27 +23,50 @@ function vertical_term(z, h, σz, ml::SimpleMixingLayer)
2323
return Fz/((2π)*σz)
2424
end
2525

26+
# mixing layer vertical term -- periodic
27+
struct PeriodicMixingLayer{N<:Integer,F<:Number} <: GaussianVerticalTerm
28+
nterms::N
29+
mixing_height::F
30+
end
31+
32+
function vertical_term(z, h, σz, ml::PeriodicMixingLayer)
33+
Fz = 1/2
34+
for n=1:ml.nterms
35+
next_term = cos(n*π*z/ml.mixing_height)*cos(n*π*h/ml.mixing_height)*exp(-0.5*(n*π*σz/ml.mixing_height)^2)
36+
Fz += next_term
37+
if next_term 0
38+
break
39+
end
40+
end
41+
return 2*Fz/ml.mixing_height
42+
end
43+
2644
@doc doc"""
2745
plume(::Scenario, GaussianMixingLayer[, ::EquationSet]; **kwargs...)
2846
2947
Returns the solution to a Gaussian plume dispersion model with a simple reflective mixing layer.
3048
3149
```math
32-
c(x, y, z) = \frac{m_i}{u} { \exp\left(-\frac{1}{2}\left(\frac{y}{\sigma_y}\right)^2\right) \over { \sqrt{2\pi} \sigma_y} } F_z
50+
c(x, y, z) = \frac{m_i}{u} \frac{1}{ \sqrt{2\pi} \sigma_y} \exp\left(-\frac{1}{2}\left(\frac{y}{\sigma_y}\right)^2\right) F_z
3351
```
3452
35-
where $ Fz $ is the vertical dispersion term, a function of the mixing height $ h_m $, *n* is the number of image terms, and other symbols are as defined for a Gaussian
53+
where $ F\_z $ is the vertical dispersion term, a function of the mixing height $ h\_m $, and other symbols are as defined for a Gaussian
3654
plume model.
3755
56+
There are two methods for the mixing layer:
57+
- `:simplemixinglayer` uses a simple method of images, a series of reflections off of the mixing height
58+
- `:periodicmixinglayer` uses an infinite series of cosine terms to calculate the vertical dispersion, which is more accurate for large mixing heights
59+
3860
# Keyword Arguments
3961
- `h_min=1.0`: Minimum height for windspeed calculations.
4062
- `method=:simplemixinglayer`: The method used for the mixing layer.
41-
- `n_terms=10`: Number of image terms for the mixing layer reflection.
63+
- `n_terms=10`: Number terms in the series calculation of $ F_z $.
4264
- `mixing_limit=10_000.0`: Limit for the mixing height, in meters. Mixing heights above this are treated as infinite.
4365
4466
# References
4567
+ AIChE/CCPS. 1999. *Guidelines for Consequence Analysis of Chemical Releases*. New York: American Institute of Chemical Engineers
4668
+ US EPA. 1995. *User's Guide for the Industrial Source Complex (ISC3) Dispersion Models EPA-454/B-95-003b, vol 2*. Research Triangle Park, NC: Office of Air Quality Planning and Standards
69+
+ Seinfeld, John H. and Spyros N. Pandis. 2006. *Atmospheric Chemistry and Physics* 2nd Ed. New York: John Wiley and Sons.
4770
4871
"""
4972
function plume(scenario::Scenario, ::Type{GaussianMixingLayer}, eqs=DefaultSet();
@@ -63,6 +86,9 @@ function plume(scenario::Scenario, ::Type{GaussianMixingLayer}, eqs=DefaultSet()
6386
hₘ = _mixing_height(scenario, eqs)
6487
ρₐ = _gas_density(scenario.substance,Tₐ,Pₐ)
6588
Qᵒ = Q*ρⱼ/ρₐ
89+
if hᵣ>hₘ
90+
error("Release height $hᵣ exceeds mixing height $hₘ. This is not supported by the Gaussian mixing layer model.")
91+
end
6692

6793
# max concentration
6894
c_max = m/Qᵒ
@@ -71,9 +97,12 @@ function plume(scenario::Scenario, ::Type{GaussianMixingLayer}, eqs=DefaultSet()
7197
# mixing layer
7298
if hₘ > mixing_limit
7399
# infinite mixing height
100+
@warn "Mixing height $hₘ exceeds limit $mixing_limit, using infinite mixing height."
74101
ml = SimpleVerticalTerm()
75102
elseif method == :simplemixinglayer
76103
ml = SimpleMixingLayer(n_terms, hₘ)
104+
elseif method == :periodicmixinglayer
105+
ml = PeriodicMixingLayer(n_terms, hₘ)
77106
else
78107
error("Unknown mixing layer method: $method")
79108
end
@@ -100,24 +129,28 @@ end
100129
Returns the solution to a Gaussian plume dispersion model with a simple reflective mixing layer.
101130
102131
```math
103-
c(x, y, z) = \frac{m_i}{u} { \exp\left(-\frac{1}{2}\left(\frac{y}{\sigma_y}\right)^2\right) \over { \sqrt{2\pi} \sigma_y} } F_z
132+
c(x, y, z) = \frac{m_i}{u} \frac{1}{ \sqrt{2\pi} \sigma_y} \exp\left(-\frac{1}{2}\left(\frac{y}{\sigma_y}\right)^2\right) F_z
104133
```
105-
106-
where $ Fz $ is the vertical dispersion term, a function of the mixing height $ h_m $, *n* is the number of image terms, and other symbols are as defined for a Gaussian
134+
where $ F\_z $ is the vertical dispersion term, a function of the mixing height $ h\_m $, and other symbols are as defined for a Gaussian
107135
plume model.
108136
137+
There are two methods for the mixing layer:
138+
- `:simplemixinglayer` uses a simple method of images, a series of reflections off of the mixing height
139+
- `:periodicmixinglayer` uses an infinite series of cosine terms to calculate the vertical dispersion, which is more accurate for large mixing heights
140+
109141
# Keyword Arguments
110142
- `downwash::Bool=false`: Include stack-downwash effects if true.
111143
- `plumerise::Bool=true`: Include plume-rise effects using Briggs' model if true.
112144
- `h_min=1.0`: Minimum height, in meters, for windspeed calculations.
113145
- `method=:simplemixinglayer`: The method used for the mixing layer.
114-
- `n_terms=10`: Number of image terms for the mixing layer reflection.
146+
- `n_terms=10`: Number terms in the series calculation of $ F_z $.
115147
- `mixing_limit=10_000.0`: Limit for the mixing height, in meters. Mixing heights above this are treated as infinite.
116148
117149
# References
118150
- AIChE/CCPS. 1999. *Guidelines for Consequence Analysis of Chemical Releases*. New York: American Institute of Chemical Engineers
119151
- Briggs, Gary A. 1969. *Plume Rise* Oak Ridge: U.S. Atomic Energy Commission
120152
- US EPA. 1995. *User's Guide for the Industrial Source Complex (ISC3) Dispersion Models EPA-454/B-95-003b, vol 2*. Research Triangle Park, NC: Office of Air Quality Planning and Standards
153+
- Seinfeld, John H. and Spyros N. Pandis. 2006. *Atmospheric Chemistry and Physics* 2nd Ed. New York: John Wiley and Sons.
121154
122155
"""
123156
function plume(scenario::Scenario{<:AbstractSubstance,<:VerticalJet,<:Atmosphere},
@@ -140,6 +173,9 @@ function plume(scenario::Scenario{<:AbstractSubstance,<:VerticalJet,<:Atmosphere
140173
stab = _stability(scenario)
141174
Γ = _lapse_rate(scenario)
142175
hₘ = _mixing_height(scenario, eqs)
176+
if hᵣ>hₘ
177+
error("Release height $hᵣ exceeds mixing height $hₘ. This is not supported by the Gaussian mixing layer model.")
178+
end
143179

144180
# jet at ambient conditions
145181
Tₐ = _atmosphere_temperature(scenario)
@@ -171,9 +207,12 @@ function plume(scenario::Scenario{<:AbstractSubstance,<:VerticalJet,<:Atmosphere
171207
# mixing layer
172208
if hₘ > mixing_limit
173209
# infinite mixing height
210+
@warn "Mixing height $hₘ exceeds limit $mixing_limit, using infinite mixing height."
174211
ml = SimpleVerticalTerm()
175212
elseif method == :simplemixinglayer
176213
ml = SimpleMixingLayer(n_terms, hₘ)
214+
elseif method == :periodicmixinglayer
215+
ml = PeriodicMixingLayer(n_terms, hₘ)
177216
else
178217
error("Unknown mixing layer method: $method")
179218
end

src/models/palazzi_puff.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ downwind dispersion, σx, is calculated:
3737
- `:tno` follows the TNO Yellow Book eqn 4.60b, using the distance *x* while the plume is still attached and the distance to the cloud center, *ut*, afterwards
3838
3939
# Arguments
40-
- `plume_model::Type{Plume} = GaussianPlume` : the plume model $\chi$
4140
- `disp_method = :default` : the method for calculating the downwind dispersion
41+
- `plume_model::Type{Plume} = GaussianPlume` : the plume model $\chi$
42+
- additional keyword arguments are passed to the plume model
4243
4344
# References
4445
+ Palazzi, E, M De Faveri, Giuseppe Fumarola, and G Ferraiolo. “Diffusion from a Steady Source of Short Duration.” *Atmospheric Environment*. 16, no. 12 (1982): 2785–90.
4546
4647
"""
47-
function puff(scenario::Scenario, ::Type{Palazzi}, eqs=PalazziDefaultSet(); plume_model=GaussianPlume, disp_method=:default)
48+
function puff(scenario::Scenario, ::Type{Palazzi}, eqs=PalazziDefaultSet(); plume_model=GaussianPlume, disp_method=:default, kwargs...)
4849

4950
stab = _stability(scenario)
5051
Δt = _duration(scenario)
@@ -55,7 +56,7 @@ function puff(scenario::Scenario, ::Type{Palazzi}, eqs=PalazziDefaultSet(); plum
5556
scenario, #scenario::Scenario
5657
:palazzi, #model::Symbol
5758
disp_method, #dispersion model::Symbol
58-
plume(scenario, plume_model, eqs), # plume model
59+
plume(scenario, plume_model, eqs; kwargs...), # plume model
5960
Δt, # duration
6061
u, # windspeed
6162
stab, # stability class

test/models/gaussian_ml_tests.jl

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,57 +13,77 @@
1313
latent_heat=1,
1414
gas_heat_capacity=1,
1515
liquid_heat_capacity=1)
16-
rel = HorizontalJet(mass_rate=0.1,
16+
hj = HorizontalJet(mass_rate=0.1,
1717
duration=Inf,
1818
diameter=1,
1919
velocity=1,
2020
height=0,
2121
temperature=298.,
2222
pressure=101325.,
2323
fraction_liquid=0)
24-
atm1 = SimpleAtmosphere(temperature=298,
25-
pressure=101325,
26-
windspeed=2,
27-
windspeed_height=1,
28-
stability=ClassF)
2924

30-
atm2 = SimpleAtmosphere(temperature=298,
31-
pressure=101325,
32-
windspeed=2,
33-
windspeed_height=1,
34-
stability=ClassD)
35-
scn = Scenario(sub,rel,atm1)
36-
37-
# test default behaviour and type inheritance
38-
pl1 = plume(scn, GaussianMixingLayer)
39-
@test pl1.verticalterm isa GasDispersion.SimpleVerticalTerm
25+
hj_toohigh = HorizontalJet(mass_rate=0.1,
26+
duration=Inf,
27+
diameter=1,
28+
velocity=1,
29+
height=10_000, # too high for the mixing layer
30+
temperature=298.,
31+
pressure=101325.,
32+
fraction_liquid=0)
4033

41-
scn2 = Scenario(sub,rel,atm2)
42-
pl2 = plume(scn2, GaussianMixingLayer)
43-
@test pl2.verticalterm isa GasDispersion.SimpleMixingLayer
44-
45-
# test vertical jet with mixing layer
46-
rel = VerticalJet(mass_rate=0.1,
34+
vj = VerticalJet(mass_rate=0.1,
4735
duration=Inf,
4836
diameter=1,
4937
velocity=1,
5038
height=10,
5139
temperature=298.,
5240
pressure=101325.,
5341
fraction_liquid=0)
54-
scn3 = Scenario(sub,rel,atm1)
55-
pl3 = plume(scn3, GaussianMixingLayer)
56-
@test pl3.verticalterm isa GasDispersion.SimpleVerticalTerm
42+
43+
vj_toohigh = VerticalJet(mass_rate=0.1,
44+
duration=Inf,
45+
diameter=1,
46+
velocity=1,
47+
height=10_000, # too high for the mixing layer
48+
temperature=298.,
49+
pressure=101325.,
50+
fraction_liquid=0)
51+
52+
stbl = SimpleAtmosphere(temperature=298,
53+
pressure=101325,
54+
windspeed=2,
55+
windspeed_height=10,
56+
stability=ClassF)
57+
58+
neut = SimpleAtmosphere(temperature=298,
59+
pressure=101325,
60+
windspeed=2,
61+
windspeed_height=10,
62+
stability=ClassD)
63+
64+
65+
# horizontal jet test default behaviour and type inheritance
66+
@test plume(Scenario(sub,hj,stbl), GaussianMixingLayer).verticalterm isa GasDispersion.SimpleVerticalTerm
67+
@test plume(Scenario(sub,hj,neut), GaussianMixingLayer).verticalterm isa GasDispersion.SimpleMixingLayer
68+
@test plume(Scenario(sub,hj,neut), GaussianMixingLayer; method=:periodicmixinglayer).verticalterm isa GasDispersion.PeriodicMixingLayer
69+
@test_throws ErrorException plume(Scenario(sub,hj,neut), GaussianMixingLayer; method=:someothermethod)
70+
@test_throws ErrorException plume(Scenario(sub,hj_toohigh,neut), GaussianMixingLayer)
5771

58-
atm3 = SimpleAtmosphere(temperature=298,
59-
pressure=101325,
60-
windspeed=2,
61-
windspeed_height=10,
62-
stability=ClassD)
63-
scn4 = Scenario(sub,rel,atm3)
64-
pl4 = plume(scn4, GaussianMixingLayer; downwash=true, plumerise=true)
65-
@test pl4.verticalterm isa GasDispersion.SimpleMixingLayer
66-
@test pl4.verticalterm.mixing_height 640.0311954829524
67-
@test pl4(500, 0, 0) 1.7194314353343084e-5
72+
# vertical jet test default behaviour and type inheritance
73+
@test plume(Scenario(sub,vj,stbl), GaussianMixingLayer).verticalterm isa GasDispersion.SimpleVerticalTerm
74+
@test plume(Scenario(sub,vj,neut), GaussianMixingLayer).verticalterm isa GasDispersion.SimpleMixingLayer
75+
@test plume(Scenario(sub,vj,neut), GaussianMixingLayer; method=:periodicmixinglayer).verticalterm isa GasDispersion.PeriodicMixingLayer
76+
@test_throws ErrorException plume(Scenario(sub,vj,neut), GaussianMixingLayer; method=:someothermethod)
77+
@test_throws ErrorException plume(Scenario(sub,vj_toohigh,neut), GaussianMixingLayer)
78+
79+
# integration test with a scenario -- simple mixing layer
80+
pl_smpl = plume(Scenario(sub,vj,neut), GaussianMixingLayer; downwash=true, plumerise=true)
81+
@test pl_smpl.verticalterm.mixing_height 640.0311954829524
82+
@test pl_smpl(500, 0, 0) 1.7194314353343084e-5
83+
84+
# integration test with a scenario -- periodic mixing layer
85+
pl_per = plume(Scenario(sub,vj,neut), GaussianMixingLayer; downwash=true, plumerise=true, method=:periodicmixinglayer, n_terms=100_000_000)
86+
@test pl_per.verticalterm.mixing_height 640.0311954829524
87+
@test pl_per(500, 0, 0) 1.7194314353343084e-5
6888

6989
end

0 commit comments

Comments
 (0)