No BSD License  

Highlights from
gaussianPlume

from gaussianPlume by Harold Bien
Gaussian Plume model of airborne particulate distribution

[C,z0]=gaussianPlume(Q, u_ref, h, varargin)
function [C,z0]=gaussianPlume(Q, u_ref, h, varargin)
%gaussianPlume  Steady-state gaussian plume distribution model
%
%   gaussianPlume models the dispersion of a continuous point source, i.e.
%   plume, in various conditions and terrains. The output of gaussianPlume
%   is a 3-dimensional matrix containing the concentrations of the emitted
%   substance over a field with the first dimension (y) representing the
%   cross-wind axis, the second dimension (x) the downwind distance, and
%   the third dimension (z) the vertical axis. The origin is set at the
%   base of the stack unless Briggs plume rise model is used in which case
%   the horizontal (x axis) coordinate is offset such that x=0 marks the 
%   maximum rise of the plume downwind. All units are in <mgs> (meters,
%   grams, seconds) except where noted.
%
%   Most of the equations were taken from the ISC3 User's Manual, Volume
%   II, available online at
%   http://www.epa.gov/scram001/userg/regmod/isc3v2.pdf
%
%   The EPA now has newer software for modeling atmospheric dispersion
%   including AERMOD and CALPUFF
%   http://www.epa.gov/scram001/dispersion_prefrec.htm#aermod as these are
%   their preferred/recommended models.
%
%   C = gaussianPlume(Q) returns the steady-state Gaussian distribution
%   model of a single, continuous point source emitting at a rate of Q
%   grams per second for a 50m physical stack height with no calculations
%   for plume rise, in rural terrain with stability class "F" in the 
%   Guifford-Pasquiill scale. Wind speed is assumed to be 1m/s at the stack
%   tip (50m).
%
%   C = gaussianPlume(Q, u_ref) sets the wind at 1m to be u_ref
%
%   C = gaussianPlume(Q, u_ref, h) sets the stack height to h
%
%   [C, z0] = gaussianPlume(...) returns the effective stack height in
%       meters as z0.
%
%   C = gaussianPlume(Q, u_ref, h, ...) allows you to set certain options.
%   Options are set by 'option', <option value> pairs. The list of
%   permissible options are listed below:
%
%       {'h_ref', h_ref} Sets the reference height for the wind speed u_ref
%           in meters. Default is 1m.
%       {'stability', 'class'}    Sets Guifford-Pasquill stability class to
%           class 'class'. 'class' must be one of 'A', 'B', 'C', 'D', 'E', 
%           or 'F'. Default is 'F'.
%       {'terrain', 't_type'}   Sets the terrain to one of 'rural' or
%           'urban'. Default is 'urban'
%       {'p', p}    Sets the scaling factor for change in wind as a
%           function of altitude to p. Default is 0.4.
%       {'plum_rise_model', 'model'}  Sets the model to use for calculation
%           of plume rise. 'model' is one of 'none', 'CONCAWE',
%           'CarlsonMoses', 'Holland', or 'Briggs'. Default is 'none'.
%       {'mw', mw}  Sets the molecular weight of the exhaust for plume rise
%           calculations. Specify in atomic mass units. Default is average
%           of air, 30g/mole.
%       {'amb_temp', Ta} Sets the ambient temperature in degrees Celsius.
%           Default is 25C. Alternatively, you may specify {'Ta',Ta}.
%       {'stack_temp', Ts} Sets the stack temperature in degrees Celsius.
%           Default is 200C. Alternatively, you may specify {'Ts',Ts}.
%       {'stack_diameter', ds} Sets the stack diameter (in meters). Default
%           is 10m. Alternatively, you may specify {'ds',ds}.
%       {'specific_heat', Cp} Sets the specific heat of the exhaust gas in
%           J/degree Celsius/g. Default is 1.020 (constant pressure Cp for
%           dry air). Alternatively, you may specify {'Cp',Cp}.
%       {'amb_pres', Pa} Sets ambient pressure in millibars. Default is
%           1010mb. Alternatively, you may specify {'Pa',Pa}.
%       {'stack_pres', Ps} Sets stack tip pressure in millibars. Default is
%           1010mb. Alternatively, you may specify {'Ps',Ps}.
%       {'stack_velocity', vs} Sets the stack exit velocity in m/s. Default
%           is 1m/s. Alternativelt, you may specify {'vs',vs}.
%       {'lapse', eta} Sets the lapse rate for stable conditions (class E or
%           F). Alternatively, you may specify {'eta',eta}.
%       {'reflection', true} enables ground reflection whereas
%       {'reflection', false} disables ground reflection
%       {'deposition', true} models dry deposition whereas
%       {'deposition', false} disables dry deposition modeling
%       {'term_velocity', vt} Sets terminal/settling velocity in m/s.
%           Default is settling velocity for PM2.5, 0.5cm/s. Synonyms
%           include {'settling_velocity', vt} or simply {'vt', vt}.
%       {'X', x} Sets sampling points at the downwind distances. x is a
%           vector specifying the downwind distances to evaluate for C in
%           meters. Default is 1 to 5km in 100m resolution.
%       {'Y', y} Same as above except for the y-axis (cross-wind)
%       {'Z', z} Same as above except for the vertical axis
%
%   Example
%   ------------------------------
%   C=gaussianPlume(500, 5, 50, 'h_ref', 10, 'reflection', false, ...
%       'deposition', false, 'amb_pres', 1103, 'amb_temp', 22);
%       Computes the steady-state Gaussian-distribution of an emission at a
%       rate of 500g/s with average wind speed of 5m/s at 10m from a
%       physical stack height of 50m. No plume rise, reflection, nor
%       deposition will be modeled. The data will be sampled in 100m
%       resolution across the downwind and crosswind axes from 1 to 5km and
%       1 to 1km in the vertical axis.


% Check for missing arguments. Set to empty so we don't get undefined
% errors.
if(nargin<2)
    u_ref=[];
end

if(nargin<3)
    h=[];
end

if(isempty(u_ref))
    % Default wind speed is 1m/s
    u_ref=1;
end

if(isempty(h))
    % Default stack height is 50m
    h=50;
end

% Set default values
stability='F';              % Guifford-Pasquill stability class
plume_rise_model='none';    % Plume rise models: 'none', 'CONCAWE', 'Briggs'
reflection=false;           % Model ground reflection?
deposition=false;           % Model dry deposition?
terrain='urban';            % Terrain type, 'urban' or 'rural'
p=0.4;                      % Wind speed variation as a function of altitude, from 0.07 to 0.6
mw=30;                      % Average molecular weight of the efflux, typically air (g/mol)
Ta=25;                      % Ambient temperature (degrees C)
Ts=200;                     % Stack tip temperature (degrees C)
ds=10;                      % Stack tip diameter (meters)
Cp=1.020;                   % Specific heat of efflux (J/g degrees Kelvin). 1.020 is for air (http://www.efunda.com/materials/common_matl/show_gas.cfm?MatlName=Air0C)
Pa=1010;                    % Ambient pressure (millibars = 100 N/m^2 = 100 [kg*m/s^2]/m^2 = 1e5 [g/m/s^2])
Ps=1010;                    % Stack tip pressure (millibar)
x_grid=[1 100:100:5e3];     % Downwind distance of each sampled point (m)
y_grid=[1 100:100:5e3];     % Crosswind distance of each sampled point (m)
z_grid=[1 100:100:1e3];     % Altitude above ground level of each sampled point (m)
vt=0.005;                   % Settling (terminal) velocity in m/s
vs=1.5;                     % Stack exit velocity (m/s)
h_ref=1;                    % Altitude above ground level of measured windspeed (u_ref) (in m)
eta=0.035;                  % Lapse rate (degrees C/m)

% The rest of the arguments are set by optional 'string' 'value' pairs
for argnum=1:2:length(varargin)
    switch(varargin{argnum})
        case 'stability'
            stability=varargin{argnum+1};            
        case 'plume_rise_model'
            plume_rise_model=varargin{argnum+1};            
        case 'reflection'
            reflection=varargin{argnum+1};        
        case 'deposition'
            deposition=varargin{argnum+1};
        case 'terrain'
            terrain=varargin{argnum+1};        
        case 'p'
            p=varargin{argnum+1};            
        case 'mw'
            mw=varargin{argnum+1};            
        case 'amb_temp'
            Ta=varargin{argnum+1};            
        case 'Ta'
            Ta=varargin{argnum+1};            
        case 'stack_temp'
            Ts=varargin{argnum+1};            
        case 'Ts'
            Ts=varargin{argnum+1};            
        case 'stack_diameter'
            ds=varargin{argnum+1};            
        case 'ds'
            ds=varargin{argnum+1};            
        case 'specific_heat'
            Cp=varargin{argnum+1};            
        case 'Cp'
            Cp=varargin{argnum+1};            
        case 'amb_pres'
            Pa=varargin{argnum+1};
        case 'Pa'
            Pa=varargin{argnum+1};
        case 'stack_pres'
            Ps=varargin{argnum+1};            
        case 'Ps'
            Ps=varargin{argnum+1};            
        case 'X'
            x_grid=varargin{argnum+1};            
        case 'Y'
            y_grid=varargin{argnum+1};            
        case 'Z'
            z_grid=varargin{argnum+1};            
        case 'term_velocity'
            vt=varargin{argnum+1};            
        case 'vt'
            vt=varargin{argnum+1};            
        case 'settling_velocity'
            vt=varargin{argnum+1};            
        case 'stack_velocity'
            vs=varargin{argnum+1};            
        case 'vs'
            vs=varargin{argnum+1};            
        case 'h_ref'
            h_ref=varargin{argnum+1};            
        case 'lapse'
            eta=varargin{argnum+1};            
        case 'eta'
            eta=varargin{argnum+1};            
        otherwise
            warn('gaussianPlume:args', ['Unrecognized option: ', varargin{argnum}]);
    end    
end

% Setup some physical constants
Rc=8.3144e3;  % Universal gas constant in mJ/mol/K = [g*m^2/s^2/mol/K]
gc=9.80616;     % Graviational acceleration, m/s^2

% For internal calculations, convert stack and ambient temperatures 
% into degrees Kelvin
Ta=Ta+273;
Ts=Ts+273;

% Force x_grid, y_grid, and z_grid to be row vectors
if(size(x_grid, 1)==1)
    x_grid=x_grid';
end
if(size(y_grid, 1)==1)
    y_grid=y_grid';
end
if(size(z_grid, 1)==1)
    z_grid=z_grid';
end

% Determine wind velocity (us) at tip of stack using power law for wind
% speed measured at height h_ref (m) to be u_ref (m/s)
us=((h/h_ref)^p)*u_ref; % in m/s

% ISC3 manual warns of issues with us<1m/s
if(us<1)
    warning('gaussianPlume:badus','Stack height wind speed, u_s, is %.3fm/s which is less than 1m/s.', us);
end

% Compute mass flow using ideal gas law assumption
% PV=nRT (ideal gas law) <==> n=PV/RT
% m_dot=[m^2]*[m/s]*[g/m/s^2]*[g/mol]/[(mJ/mol/K)*K]
%      =[m^3][1/s]*[g/m/s^2]*[g/mol]/[g*m^2/s^2/mol]
%      =[g/s]
m_dot=pi*(ds/2)^2*vs*(Ps*1e5)*mw/(Rc*Ts);   % g/s
% Now compute heat flux
% Qh=[g/s]*[J/(g*degK)]*degK
%   =[J/s]
Qh=m_dot*Cp*(Ts-Ta);    % J/s

% Compute plume rise
switch(plume_rise_model)
    case 'none'
        % The rise is then set to zero
        plume_rise=0;
        x_f=0;
    case 'CONCAWE'
        % TODO: Verify
        plume_rise=4.71*(Qh^0.44/us^0.694);
        x_f=0;
    case 'Holland'
        % TODO: Verify
        plume_rise=vs*ds/us*(1.5+0.01*Qh/(vs*ds));
        x_f=0;
    case 'CarlsonMoses'
        % TODO: Verify
        plume_rise=0.029*vs*ds/us+2.62*(Qh^0.5/us);
        x_f=0;
    case 'Briggs'
        % Compute buoyancy and momentum factors
        % Fb=[m/s^2]*[m/s]*[m^2]*[degK]/[degK]
        %   =[m^4]/[s^3]
        Fb=gc*vs*ds^2*(Ts-Ta)/(4*Ts);
        % Fm=[m^2/s^2]*[m^2]*[degK]/[degK]
        %   =[m^4]/[s^2]
        Fm=vs^2*ds^2*Ta/(4*Ts);
        % Compute stability parameter if stable
        if(stability=='E' || stability=='F')
            % s = [m/s^2]*[degK/m]/[degK]
            %   = [1/s^2]
            s=gc*eta/Ta; % 1/s^2
            % Can't perform unit analysis, factor 0.019... is unknown
            dTc=0.019582*Ts*vs*sqrt(s);
            % Check if buoyancy dominated (thermal) or momentum (kinetic)
            if( (Ts-Ta)>=dTc)
                % Buoyancy dominates
                x_f=2.0715*us/sqrt(s);
                plume_rise=2.6*(Fb/(us*s))^(1/3);
            else
                x_f=0;
                % Note that we also evaluate unstable/neutral and select
                % the lower value
                plume_rise=min(1.5*(Fm/(us*sqrt(s)))^(1/3), 3*ds*vs/us);
            end
        else
            % Unstable or neutral              
            if(Fb<55)
                % Check for buoyancy dominated or momentum
                dTc=0.0297*Ts*vs^(1/3)/ds^(2/3);
                if( (Ts-Ta)>=dTc )
                    % Buoyancy dominated
                    x_f=49*Fb^(5/8);
                    plume_rise=21.425*Fb^(3/4)/us;
                else
                    % Momentum dominated
                    x_f=0;
                    plume_rise=3*ds*vs/us;
                    % Check
                    if(vs/us<=4)
                        warning('gaussianPlume:Briggs', 'Momentum rise model selected but may not be accurate due to low stack exit velocity');
                    end
                end
            else
                % Brigg's equations seem to diverage at Fb==55
                dTc=0.00575*Ts*vs^(2/3)/ds^(1/3);
                if( (Ts-Ta)>=dTc )
                    % Offset horizontal downwind distance for distance to
                    % max height (rise)
                    x_f=119*Fb^(2/5);
                    plume_rise=38.71*Fb^(3/5)/us;
                else
                    x_f=0;
                    plume_rise=3*ds*vs/us;
                    if(vs/us<=4)
                        warning('gaussianPlume:Briggs', 'Momentum rise model selected but may not be accurate due to low stack exit velocity');
                    end
                end
            end
        end            
    otherwise
        warning('gaussianPlume:riseModel', ['Unrecognized plume rise model specified: ', plume_rise_model]);
        plume_rise=0;
        x_f=0;
end

% Set effective stack height
z0=h+plume_rise;

% Offset the downwind direction to take into account distance to max rise
% TODO: Substitute appropriate equations for distance less than final rise
% per ISC3v2 manual for buoyancy dominated conditions
x_grid=x_grid-x_f;

% Compute the dispersion coefficients
switch(terrain)
    case 'rural'
        % Pasquill-Gifford curves
        switch(stability)            
            case 'A'
                % [c, d] coefficients
                coeffs_y=[24.1670, 2.5334];
                % [x a b] matrix
                coeffs_z=[0.10 122.800 0.94470;
                          0.16 158.080 1.05420;
                          0.21 170.220 1.09320;
                          0.26 179.520 1.12620;
                          0.31 217.410 1.26440;
                          0.41 258.890 1.40940;
                          0.51 346.750 1.72830;
                          3.11 453.850 2.11660;
                          inf  nan     nan];
            case 'B'
                coeffs_y=[18.3330, 1.8096];
                coeffs_z=[0.20 90.673 0.93198;
                          0.40 98.483 0.98332;
                          inf  109.300 1.09710];
            case 'C'
                coeffs_y=[12.5000, 1.0857];
                coeffs_z=[inf 61.141 0.91465];
            case 'D'
                coeffs_y=[8.3330, 0.72382];
                coeffs_z=[0.31 34.459 0.86974;
                          1.01 32.093 0.81066;
                          3.01 32.093 0.64403;
                          10.01 33.504 0.60486;
                          30.00 36.650 0.56589;
                          inf   44.053 0.51179];
            case 'E'
                coeffs_y=[6.2500, 0.54287];
                coeffs_z=[0.10 24.260 0.83660;
                          0.31 23.331 0.81956;
                          1.01 21.628 0.75660;
                          2.01 21.628 0.63077;
                          4.01 22.534 0.57154;
                          10.01 24.703 0.50527;
                          20.01 26.970 0.46713;
                          40.00 35.420 0.37615;
                          inf 47.618 0.29592];                      
            case 'F'
                coeffs_y=[4.1667, 0.36191];
                coeffs_z=[0.21 15.209 0.81558;
                          0.71 14.457 0.78407;
                          1.01 13.953 0.68465;
                          2.01 13.953 0.63227;
                          3.01 14.823 0.54503;
                          7.01 16.187 0.46490;
                          15.01 17.836 0.41507;
                          30.01 22.651 0.32681;
                          60.00 27.074 0.27436;
                          inf   34.219 0.21716];
            otherwise
                error('gaussianPlume:stability', ['Unknown stability class ', stability]);                
        end
        % Construct sigma_y vector along the x-axis
        % Note that x should be in kilometers (x_grid/1.e3)
        sigma_y=465.11628.*(x_grid./1e3).*tan(0.017453293.*(coeffs_y(1)-coeffs_y(2).*log(x_grid./1e3))); % m
        % Construct sigma_z vector along the x-axis
        prev_boundary=0; % in km    
        % Pre-allocate (should be same size as x_grid since all tables end
        % with 'inf')
        sigma_z=nan(size(x_grid));
        for section=1:size(coeffs_z, 1)
            idx=find(prev_boundary<=(x_grid./1e3) & (x_grid./1e3)<coeffs_z(section, 1));
            sigma_z(idx)=coeffs_z(section, 2).*(x_grid(idx)./1e3).^coeffs_z(section, 3);
            prev_boundary=coeffs_z(section, 1);
        end
        % Move the vector into the proper dimension (cols=x)
        sigma_y=shiftdim(sigma_y, -1);
        % Clip all values>5e3 for stability classes A-C
        switch(stability)
            case {'a', 'b', 'c'}
                sigma_y(sigma_y>5e3)=5e3;
        end        
        %sigma_z=shiftdim(sigma_z, -1);        
    case 'urban'
        % Pasquill-Gifford with urban fit (McElroy-Pooler)
        switch(stability)
            case 'A'
                coeffs_y=0.32;
                coeffs_z=[0.24 1 0.001 0.5];
            case 'B'
                coeffs_y=0.32;
                coeffs_z=[0.24 1 0.001 0.5];
            case 'C'
                coeffs_y=0.22;
                coeffs_z=[0.20 1 0 0];
            case 'D'
                coeffs_y=0.16;
                coeffs_z=[0.14 1 0.0003 -0.5];
            case 'E'
                coeffs_y=0.11;
                coeffs_z=[0.08 1 0.0015 -0.5];
            case 'F'
                coeffs_y=0.11;
                coeffs_z=[0.08 1 0.0015 -0.5];
            otherwise
                error('gaussianPlume:stability', ['Unrecognized stability class ', stability]);
        end
        % Construct sigma_y along x-axis
        sigma_y=coeffs_y(1).*x_grid.*(1+0.0004.*x_grid).^(-0.5);
        sigma_y=shiftdim(sigma_y, -1);
        % Construct sigma_z along x-axis
        sigma_z=coeffs_z(1).*x_grid.*(coeffs_z(2)+coeffs_z(3).*x_grid).^coeffs_z(4);        
        sigma_z=shiftdim(sigma_z, -1);
    otherwise
        error('gaussianPlume:terrain', ['Unrecognized terrain option ', terrain]);        
end

% Now we have a choice when we compute Gaussian distribution. We
% can either save memory and use a for-loop, or speed the code up
% at the expense of more memory requirement. Since memory is cheap
% these days, we'll go ahead and optimize for speed in favor of
% size.
        
% Now replicate sigma vectors into 3D matrices
sigma_y=repmat(sigma_y, [length(y_grid) 1 length(z_grid)]);
sigma_z=repmat(sigma_z, [length(y_grid) 1 length(z_grid)]);

% Create wind matrix (scaled according to altitude)
u_matrix=((z_grid./h_ref).^p).*u_ref; % [m/s]
% Move over into z-dimension
u_matrix=shiftdim(u_matrix, -2);
u_matrix=repmat(u_matrix, [length(y_grid) length(x_grid) 1]);

% At this point, we will scale x_grid/y_grid/z_grid into full 3-D matrices
% Because we now manipulate them, first save their original lengths
sx=size(x_grid, 1);
sy=size(y_grid, 1);
sz=size(z_grid, 1);
x_grid=repmat(shiftdim(x_grid, -1), [sy 1 sz]);
y_grid=repmat(y_grid, [1 sx sz]);
z_grid=repmat(shiftdim(z_grid, -2), [sy sx 1]);

% Setup deposition
if(deposition)
    % Note here we had to massage x_grid into full 3-D matrix
    dz_dep=vt.*x_grid./u_matrix;
else
    dz_dep=zeros(size(x_grid));
end

if(reflection)
    % Reflection scales everything by 2
    r=2;
else
    r=1;
end

% Now compute steady-state Gaussian dispersion
% (Note everything inside the exp() becomes unitless)
% C=[g/s]/[m/s*m*m]=[g/m^3]
C=r.*Q./ ...
    (2.*pi.*u_matrix.*sigma_y.*sigma_z).*exp( ...
        (-y_grid.^2)./(2.*sigma_y.^2) - ((z_grid-z0-dz_dep).^2)./(2.*sigma_z.^2));

Contact us at files@mathworks.com