Home Reference Source Test Repository

src/Dispersion/DynamicGaussianPuff.js

/**
 * Created by austin on 6/16/16.
 * @file DynamicGaussianPuff.js
 * 
 */

import Vector from './Vector';
import GaussianPuff from './GaussianPuff';

/**
 * Allows for atmospheric changes between puff movements
 */
class DynamicGaussianPuff extends GaussianPuff {
    /**
     *
     * @param {Atmosphere} atmosphere
     * @param {Source} source
     * @param {number} massReleased
     * @param {array} [center] - Manually set the center, defaults to (0,0,0)
     */
    constructor(atmosphere, source, massReleased, center) {
        super(atmosphere, source, massReleased);

        /**
         * 
         * @type {number}
         * @private
         */
        this._currentTime = 0;

        /**
         * Really doesn't need to be a vector. Easy enough to use a vector as a cartesian coord though.
         * @type {Vector}
         * @private
         */
        this._currentCenter = center ? Vector.fromArray(center) : new Vector(0, 0, this.getEffectiveSourceHeight());

        /**
         * @type {Vector}
         * @private
         */
        this._startCenter = this._currentCenter.clone();

        /**
         *
         * @type {Array}
         * @private
         */
        this._path = [];

        /**
         * dY
         * @type {number}
         * @private
         */
        this._virtHoriz = 0;

        /**
         * dZ
         * @type {number}
         * @private
         */
        this._vertDist = 0;

        /**
         * 
         * @type {number}
         * @private
         */
        this._stdY = 0;

        /**
         * 
         * @type {number}
         * @private
         */
        this._stdZ = 0;
    }

    /**
     * 
     * @returns {number}
     */
    getTime() {
        return this._currentTime;
    }

    /**
     *
     * @param center
     * @returns {DynamicGaussianPuff}
     * @private
     */
    _setCenter(center) {
        this._currentCenter = center;
        return this;
    }

    /**
     *
     * @returns {Vector}
     */
    getCenter() {
       return this._currentCenter;
    }

    /**
     *
     * @returns {Vector}
     */
    getStart() {
        return this._startCenter;
    }

    /**
     * 
     * @returns {number}
     */
    getDistanceFromStart() {
        return this.getCenter().subtract(this.getStart()).abs();
    }

    /**
     * 
     * @returns {number}
     */
    getDistanceTraveled() {
        let dist = 0;
        let start = this.getStart();
        for (var point of this._path) {
            dist += point.subtract(start).abs();
            start = point;
        }
        return dist;
    }

    /**
     * A helper function for the StdZ calculation
     * @override
     * @protected
     * @returns {STD_Y_COEFF}
     */
    _getStdYCoeffs() {
        let x = this.getDistanceTraveled();
        return super._getStdYCoeffs(x);
    }
    
    /**
     * Brookhaven sigma
     * The crosswind distance standard deviation for a distance x downwind.
     * To be used in a Gaussian distribution
     * @override
     * @returns {number} crosswind standard deviation at x meters downwind (m)
     */
    getStdY() {
        return this._stdY;
    }

    /**
     * Brookhaven sigma
     * The vertical distance standard deviation for a distance x downwind.
     * To be used in a Gaussian distribution
     * @override
     * @returns {number}
     */
    getStdZ() {
        return this._stdZ;
    }

    /**
     * 
     * @returns {number|*}
     */
    getVirtHoriz() {
        return this._virtHoriz;
    }

    /**
     * 
     * @returns {number|*}
     */
    getVertDist() {
        return this._vertDist;
    }
    
    /**
     * Moves the puff along by t seconds
     * @see http://www.sciencedirect.com/science/article/pii/S0093641303000247 Section 3.2, equation 14
     * @param {number} deltaT - seconds to increment by
     * @returns {DynamicGaussianPuff}
     */
    step(deltaT) {
        // update vertHoriz and vertDist
        let x = this.getDistanceTraveled();
        let stdYCoeffs = super._getStdYCoeffs(x);
        let stdZCoeffs = super._getStdZCoeffs(x);

        // Update the Virtual horizontal and the vertical distance @see equation 15
        this._virtHoriz = Math.pow((this.getStdY() / stdYCoeffs.c), (1 / stdYCoeffs.d));
        this._vertDist = Math.pow((this.getStdZ() / stdZCoeffs.a), (1 / stdZCoeffs.b));

        // Find the change in x and y directions
        // Todo: use Navier-Stokes equation solver to account for momentum @see equation 16
        let deltaDVec = this.getAtmosphere().getWindSpeedVec().multiply(deltaT);    // The change in distance from wind
        let deltaD = deltaDVec.abs();

        // Update the standard deviations @see equation 17
        this._stdY = stdYCoeffs.c * Math.pow(this.getVirtHoriz() + deltaD, stdYCoeffs.d);
        this._stdZ = stdZCoeffs.a * Math.pow(this.getVertDist() + deltaD, stdZCoeffs.b);

        // Update position/time/path
        this._currentTime += deltaT;
        this._setCenter(this.getCenter().add(deltaDVec));
        this._path.push(this.getCenter().clone());
        
        return this;
    }

    /**
     * @see http://www.sciencedirect.com/science/article/pii/S0093641303000247 Section 3.2, equation 14
     * @override
     * @param {number} x - downwind (m)
     * @param {number} y - crosswind (m)
     * @param {number} z - height (m)
     * @returns {number}
     */
    getConcentration(x, y, z) {
        let stdY = this.getStdY();
        let stdZ = this.getStdZ();
        let H = this.getEffectiveSourceHeight();

        let a = this.getMassReleased() / (Math.pow(2 * Math.PI, 1.5) * Math.pow(stdY, 2) * stdZ);
        let b = Math.exp(-0.5 * Math.pow(x / stdY, 2));
        let c = Math.exp(-0.5 * Math.pow(y / stdY, 2));
        let d = Math.exp(-0.5 * Math.pow((z - H) / stdZ, 2));

        return a * b * c * d;
    }

}

export default DynamicGaussianPuff;