Пример #1
0
def add_curves(ax,
               pressure,
               temperature,
               mixing_ratio,
               altitude,
               linewidth=1.0,
               LH_Tdepend=False):
    """
    overlaying new curves of multiple soundings from profiles
    """
    p = pressure * units('mbar')
    T = temperature * units('degC')
    q = mixing_ratio * units('kilogram/kilogram')
    qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p)
    Td = mpcalc.dewpoint(mpcalc.vapor_pressure(p, q))  # dewpoint
    Tp = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')  # parcel profile

    # Altitude based on the hydrostatic eq.
    if len(altitude) == len(pressure):  # (1) altitudes for whole levels
        altitude = altitude * units('meter')
    elif len(altitude
             ) == 1:  # (2) known altitude where the soundings was launched
        z_surf = altitude.copy() * units('meter')
        # given altitude
        altitude = np.zeros((np.size(T))) * units('meter')
        for i in range(np.size(T)):
            altitude[i] = mpcalc.thickness_hydrostatic(
                p[:i + 1], T[:i + 1]) + z_surf  # Hypsometric Eq. for height
    else:
        print(
            '***NOTE***: the altitude at the surface is assumed 0 meter, and altitudes are derived based on the hypsometric equation'
        )
        altitude = np.zeros(
            (np.size(T))) * units('meter')  # surface is 0 meter
        for i in range(np.size(T)):
            altitude[i] = mpcalc.thickness_hydrostatic(
                p[:i + 1], T[:i + 1])  # Hypsometric Eq. for height

    # specific energies
    if LH_Tdepend == False:
        mse = mpcalc.moist_static_energy(altitude, T, q)
        mse_s = mpcalc.moist_static_energy(altitude, T, qs)
        dse = mpcalc.dry_static_energy(altitude, T)
    else:
        # A short course in cloud physics, Roger and Yau (1989)
        Lvt = (2500.8 - 2.36 * T.magnitude +
               0.0016 * T.magnitude**2 - 0.00006 * T.magnitude**3) * units(
                   'joule/gram')  # latent heat of evaporation
        #Lf = 2834.1 - 0.29*T - 0.004*T**2                  # latent heat of fusion

        mse = Cp_d * T + g * altitude + Lvt * q
        mse_s = Cp_d * T + g * altitude + Lvt * qs
        dse = mpcalc.dry_static_energy(altitude, T)

    ax.plot(dse, p, '--k', linewidth=linewidth)
    ax.plot(mse, p, '--b', linewidth=linewidth)
    ax.plot(mse_s, p, '--r', linewidth=linewidth)
Пример #2
0
def birner(pFull, TFull, lapseC=2.0*units("K/km"), height=False):
    """
    Implements the calculation of the thermal tropopause as described by Birner in
    Appendix A of:
        T. Birner. Fine-scale structure of the extratropical tropopause region.
        Journal
    """

    # Calculate the heights of the pressure levels using the hypsometric equation
    z = np.zeros_like(pFull)*units.km
    for h in range(0,pFull.size):
        z[h] = thickness_hydrostatic(pFull[0:h+1],TFull[0:h+1])

    # Calculate the lapse rate through use of a centered difference
    # This needs the heights "z" at each pressure level, which can be calculated
    # using the hypsometric equation, or sent as a function argument
    lapse = (TFull[2:]-TFull[:-2])/(z[2:]-z[:-2])

    found = False
    for i in range(3,lapse.size):
        if lapse[i] >= lapseC:
            k = i
            for j in range(k,lapse.size-2):
                meanAbove = np.mean(lapse[j:j+2])
                meanBelow = np.mean(lapse[j-3:j-1])
                if meanAbove >= lapseC and meanBelow < lapseC:
                    if meanAbove > 0:
                        minTempIdx = np.argmin(lapse[j-3:j+2])
                        iTrop = k+minTempIdx
                        zTrop = z[iTrop]
                        TTrop = TFull[iTrop]
                    else:
                        print("linearly interpolated...")
                        minTempIdx = np.argmin(lapse[j-3:j+2])
                        iTrop = k+minTempIdx
                        # Linearly interpolate and find intersection point
                        ma = (TFull[j+2]-TFull[j])/(z[j+2]-z[j])
                        mb = (TFull[j-1]-TFull[j-3])/(z[j-1]-z[j-3])
                        zTrop = (TFull[j]-TFull[j-1]+mb*z[j-1]-ma*z[j])/(mb-ma)
                        TTrop = ma*(zTrop-z[j])+T[j]
                        # Find index of "layer" that has calculated tropopause
                        n = 0
                        while z[n] < zTrop:
                            iTrop = n
                    found = np.abs(zTrop-z[j]) <= 250*units.meters
                    found = found and pFull[iTrop] < 500*units.mbar
                    i2km = iTrop+1
                    while z[i2km] - z[iTrop] < 2000*units.meters:
                        found = found and (TFull[i2km]-TTrop)/(z[i2km]-zTrop) > lapseC
                        found = found and (TFull[i2km+1]-TTrop)/(z[i2km+1]-zTrop) > lapseC
                        i2km += 1
                    if found:
                        break
            if found:
                break
    if height:
        return zTrop.to(units.km)

    pTrop = pFull[iTrop]
    return pTrop.to(units.mbar)
Пример #3
0
def test_thickness_hydrostatic():
    """Test the thickness calculation for a moist layer."""
    pressure = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.hPa
    temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.degC
    mixing = np.array([0.01458, 0.00209, 0.00224, 0.00240, 0.00256, 0.00010])
    thickness = thickness_hydrostatic(pressure, temperature, mixing=mixing)
    assert_almost_equal(thickness, 9892.07 * units.m, 2)
Пример #4
0
def test_thickness_hydrostatic_isothermal_subset():
    """Test the thickness calculation for a dry isothermal layer subset at 0 degC."""
    pressure = np.arange(1000, 500 - 1e-10, -10) * units.hPa
    temperature = np.zeros_like(pressure) * units.degC
    thickness = thickness_hydrostatic(pressure, temperature, bottom=850 * units.hPa,
                                      depth=350 * units.hPa)
    assert_almost_equal(thickness, 4242.68 * units.m, 2)
Пример #5
0
def vertical_wind_shear(u, v):
    diff_u = xr.zeros_like(u[:, 1:])
    diff_u.attrs[
        'long_name'] = 'Horizontal wind U component delta to level below'
    diff_v = xr.zeros_like(v[:, 1:])
    diff_v.attrs[
        'long_name'] = 'Horizontal wind V component delta to level below'

    diff_u[:, :] = u[:, 1:].values - u[:, :-1].values
    diff_v[:, :] = v[:, 1:].values - v[:, :-1].values

    # mpcalc.bulk_shear(ls.lev[1:3].values*units.hPa, ls.u[:1, 1:3].squeeze().values*units('m/s'),
    #                   ls.v[:1, 1:3].squeeze().values*units('m/s'), depth=25.*units.hPa)

    velo_change = np.sqrt(diff_u**2 + diff_v**2)

    # change of velocity is [m/s], but I want it per meter, which is dwind_dz => [m/s/m] = [1/s]
    thickness = delta_height(ls.lev[1:], ls.T[:, 1:])
    thickness_my = thickness.copy(deep=True)
    thickness_metpy = thickness.copy(deep=True)
    thickness_metpy[:, :] = 0

    for i in range(len(thickness.lev)):
        thickness_metpy[:, i] = mpcalc.thickness_hydrostatic(
            ls.lev[i + 1:i + 3], ls.T[:1, i + 1:i + 3])[0]

    shear = velo_change  #/ thickness_metpy
    shear.attrs['long_name'] = 'Vertical wind shear'
    shear.attrs['units'] = 'm/s'

    return xr.merge([ls, xr.Dataset({'dwind_dz': shear})])
Пример #6
0
def test_thickness_hydrostatic_isothermal_subset():
    """Test the thickness calculation for a dry isothermal layer subset at 0 degC."""
    pressure = np.arange(1000, 500 - 1e-10, -10) * units.hPa
    temperature = np.zeros_like(pressure) * units.degC
    thickness = thickness_hydrostatic(pressure, temperature, bottom=850 * units.hPa,
                                      depth=350 * units.hPa)
    assert_almost_equal(thickness, 4242.68 * units.m, 2)
Пример #7
0
def test_thickness_hydrostatic():
    """Test the thickness calculation for a moist layer."""
    pressure = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.hPa
    temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.degC
    mixing = np.array([0.01458, 0.00209, 0.00224, 0.00240, 0.00256, 0.00010])
    thickness = thickness_hydrostatic(pressure, temperature, mixing=mixing)
    assert_almost_equal(thickness, 9892.07 * units.m, 2)
Пример #8
0
def test_thickness_hydrostatic_subset():
    """Test the thickness calculation with a subset of the moist layer."""
    pressure = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.hPa
    temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.degC
    mixing = np.array([0.01458, 0.00209, 0.00224, 0.00240, 0.00256, 0.00010])
    thickness = thickness_hydrostatic(pressure, temperature, mixing=mixing,
                                      bottom=850 * units.hPa, depth=150 * units.hPa)
    assert_almost_equal(thickness, 1630.81 * units.m, 2)
Пример #9
0
def test_thickness_hydrostatic_subset():
    """Test the thickness calculation with a subset of the moist layer."""
    pressure = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.hPa
    temperature = np.array([22.2, 14.6, 12., 9.4, 7., -38.]) * units.degC
    mixing = np.array([0.01458, 0.00209, 0.00224, 0.00240, 0.00256, 0.00010])
    thickness = thickness_hydrostatic(pressure, temperature, mixing=mixing,
                                      bottom=850 * units.hPa, depth=150 * units.hPa)
    assert_almost_equal(thickness, 1630.81 * units.m, 2)
Пример #10
0
def coldestPoint(pFull, TFull, lapseC=2.0 * units("K/km"), height=False):
    """
    Finds the tropopause as the coldest point in a sounding
    """
    iTrop = np.argmin(TFull)
    if height:
        z = thickness_hydrostatic(pFull[0:iTrop], TFull[0:iTrop])
        return z.to(units.km)
    pTrop = pFull[iTrop]
    return pTrop.to(units.mbar)
Пример #11
0
def moist_adiabat(z, SST):
    p = 1000 * np.exp(-9.81 * z / (287. * 270.)) * units.hPa
    Tp = mpcalc.moist_lapse(p, (SST - 1) * units.K)
    qp = 0.8 * mpcalc.saturation_mixing_ratio(p, Tp)

    ztrop1 = 17e3
    ztrop2 = 19e3
    idx1 = np.argmin((z - ztrop1)**2)
    idx2 = np.argmin((z - ztrop2)**2)
    Tp[idx1:idx2] = Tp[idx1]
    Tp[idx2:] = Tp[idx1] + 2e-3 * (z[idx2:] - ztrop2) * units.K
    thetap = mpcalc.potential_temperature(p, Tp)
    thicknesses = [
        mpcalc.thickness_hydrostatic(p, Tp, bottom=p[i], depth=p[i] - p[i + 1])
        / units.m for i in range(len(p) - 1)
    ]
    zp = np.concatenate([[0.], np.cumsum(thicknesses)])

    thetaz = np.interp(z, zp, (thetap / units.K))
    qz = np.interp(z, zp, qp)
    idxs = z < 35000
    return z[idxs], np.array([float(x) for x in thetaz
                              ])[idxs], np.array([float(x) for x in qz])[idxs]
Пример #12
0
def msed_plots(pressure,
               temperature,
               mixing_ratio,
               h0_std=2000,
               ensemble_size=20,
               ent_rate=np.arange(0, 2, 0.05),
               entrain=False):
    """
    plotting the summarized static energy diagram with annotations and thermodynamic parameters
    """
    p = pressure * units('mbar')
    T = temperature * units('degC')
    q = mixing_ratio * units('kilogram/kilogram')
    qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p)
    Td = mpcalc.dewpoint(mpcalc.vapor_pressure(p, q))  # dewpoint
    Tp = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')  # parcel profile

    # Altitude based on the hydrostatic eq.
    altitude = np.zeros((np.size(T))) * units('meter')  # surface is 0 meter
    for i in range(np.size(T)):
        altitude[i] = mpcalc.thickness_hydrostatic(
            p[:i + 1], T[:i + 1])  # Hypsometric Eq. for height

    # Static energy calculations
    mse = mpcalc.moist_static_energy(altitude, T, q)
    mse_s = mpcalc.moist_static_energy(altitude, T, qs)
    dse = mpcalc.dry_static_energy(altitude, T)

    # Water vapor calculations
    p_PWtop = max(200 * units.mbar,
                  min(p) + 1 * units.mbar)  # integrating until 200mb
    cwv = mpcalc.precipitable_water(Td, p,
                                    top=p_PWtop)  # column water vapor [mm]
    cwvs = mpcalc.precipitable_water(
        T, p, top=p_PWtop)  # saturated column water vapor [mm]
    crh = (cwv / cwvs) * 100.  # column relative humidity [%]

    #================================================
    # plotting MSE vertical profiles
    fig = plt.figure(figsize=[12, 8])
    ax = fig.add_axes([0.1, 0.1, 0.6, 0.8])
    ax.plot(dse, p, '-k', linewidth=2)
    ax.plot(mse, p, '-b', linewidth=2)
    ax.plot(mse_s, p, '-r', linewidth=2)

    # mse based on different percentages of relative humidity
    qr = np.zeros((9, np.size(qs))) * units('kilogram/kilogram')
    mse_r = qr * units('joule/kilogram')  # container
    for i in range(9):
        qr[i, :] = qs * 0.1 * (i + 1)
        mse_r[i, :] = mpcalc.moist_static_energy(altitude, T, qr[i, :])

    for i in range(9):
        ax.plot(mse_r[i, :], p[:], '-', color='grey', linewidth=0.7)
        ax.text(mse_r[i, 3].magnitude / 1000 - 1, p[3].magnitude,
                str((i + 1) * 10))

    # drawing LCL and LFC levels
    [lcl_pressure, lcl_temperature] = mpcalc.lcl(p[0], T[0], Td[0])
    lcl_idx = np.argmin(np.abs(p.magnitude - lcl_pressure.magnitude))

    [lfc_pressure, lfc_temperature] = mpcalc.lfc(p, T, Td)
    lfc_idx = np.argmin(np.abs(p.magnitude - lfc_pressure.magnitude))

    # conserved mse of air parcel arising from 1000 hpa
    mse_p = np.squeeze(np.ones((1, np.size(T))) * mse[0].magnitude)

    # illustration of CAPE
    el_pressure, el_temperature = mpcalc.el(p, T, Td)  # equilibrium level
    el_idx = np.argmin(np.abs(p.magnitude - el_pressure.magnitude))
    ELps = [el_pressure.magnitude
            ]  # Initialize an array of EL pressures for detrainment profile

    [CAPE, CIN] = mpcalc.cape_cin(p[:el_idx], T[:el_idx], Td[:el_idx],
                                  Tp[:el_idx])

    plt.plot(mse_p, p, color='green', linewidth=2)
    ax.fill_betweenx(p[lcl_idx:el_idx + 1],
                     mse_p[lcl_idx:el_idx + 1],
                     mse_s[lcl_idx:el_idx + 1],
                     interpolate=True,
                     color='green',
                     alpha='0.3')

    ax.fill_betweenx(p, dse, mse, color='deepskyblue', alpha='0.5')
    ax.set_xlabel('Specific static energies: s, h, hs [kJ kg$^{-1}$]',
                  fontsize=14)
    ax.set_ylabel('Pressure [hpa]', fontsize=14)
    ax.set_xticks([280, 300, 320, 340, 360, 380])
    ax.set_xlim([280, 390])
    ax.set_ylim(1030, 120)

    if entrain is True:
        # Depict Entraining parcels
        # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z)
        # M=1 at ground without loss of generality

        # Distribution of surface parcel h offsets
        H0STDEV = h0_std  # J/kg
        h0offsets = np.sort(np.random.normal(
            0, H0STDEV, ensemble_size)) * units('joule/kilogram')
        # Distribution of entrainment rates
        entrainment_rates = ent_rate / (units('km'))

        for h0offset in h0offsets:

            h4ent = mse.copy()
            h4ent[0] += h0offset

            for eps in entrainment_rates:

                M = np.exp(eps * (altitude - altitude[0])).to('dimensionless')
                # dM is the mass contribution at each level, with 1 at the origin level.
                M[0] = 0
                dM = np.gradient(M)

                # parcel mass is a  sum of all the dM's at each level
                # conserved linearly-mixed variables like h are weighted averages
                hent = np.cumsum(dM * h4ent) / np.cumsum(dM)

                # Boolean for positive buoyancy, and its topmost altitude (index) where curve is clippes
                posboy = (hent > mse_s)
                posboy[0] = True  # so there is always a detrainment level

                ELindex_ent = np.max(np.where(posboy))
                # Plot the curve
                plt.plot(hent[0:ELindex_ent + 2],
                         p[0:ELindex_ent + 2],
                         linewidth=0.25,
                         color='g')
                # Keep a list for a histogram plot (detrainment profile)
                if p[ELindex_ent].magnitude < lfc_pressure.magnitude:  # buoyant parcels only
                    ELps.append(p[ELindex_ent].magnitude)

        # Plot a crude histogram of parcel detrainment levels
        NBINS = 20
        pbins = np.linspace(1000, 150,
                            num=NBINS)  # pbins for detrainment levels
        hist = np.zeros((len(pbins) - 1))
        for x in ELps:
            for i in range(len(pbins) - 1):
                if (x < pbins[i]) & (x >= pbins[i + 1]):
                    hist[i] += 1
                    break

        det_per = hist / sum(hist) * 100
        # percentages of detrainment ensumbles at levels

        ax2 = fig.add_axes([0.705, 0.1, 0.1, 0.8], facecolor=None)
        ax2.barh(pbins[1:],
                 det_per,
                 color='lightgrey',
                 edgecolor='k',
                 height=15 * (20 / NBINS))
        ax2.set_xlim([0, max(det_per)])
        ax2.set_ylim([1030, 120])
        ax2.set_xlabel('Detrainment [%]')
        ax2.grid()
        ax2.set_zorder(2)

        ax.plot([400, 400], [1100, 0])
        ax.annotate('Detrainment', xy=(362, 320), color='dimgrey')
        ax.annotate('ensemble: ' + str(ensemble_size * len(entrainment_rates)),
                    xy=(364, 340),
                    color='dimgrey')
        ax.annotate('Detrainment', xy=(362, 380), color='dimgrey')
        ax.annotate(' scale: 0 - 2 km', xy=(365, 400), color='dimgrey')

        # Overplots on the mess: undilute parcel and CAPE, etc.
        ax.plot((1, 1) * mse[0], (1, 0) * (p[0]), color='g', linewidth=2)

        # Replot the sounding on top of all that mess
        ax.plot(mse_s, p, color='r', linewidth=1.5)
        ax.plot(mse, p, color='b', linewidth=1.5)

        # label LCL and LCF
        ax.plot((mse_s[lcl_idx] + (-2000, 2000) * units('joule/kilogram')),
                lcl_pressure + (0, 0) * units('mbar'),
                color='orange',
                linewidth=3)
        ax.plot((mse_s[lfc_idx] + (-2000, 2000) * units('joule/kilogram')),
                lfc_pressure + (0, 0) * units('mbar'),
                color='magenta',
                linewidth=3)

    ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h).
    #dZ = 100 *mpunits.units.meter
    dp = 1000 * units.pascal

    # depict displacements at sounding levels nearest these target levels
    targetlevels = [900, 800, 700, 600, 500, 400, 300, 200] * units.hPa
    for ilev in targetlevels:
        idx = np.argmin(np.abs(p - ilev))

        # dp: hydrostatic
        rho = (p[idx]) / Rd / (T[idx])
        dZ = -dp / rho / g

        # dT: Dry lapse rate dT/dz_dry is -g/Cp
        dT = (-g / Cp_d * dZ).to('kelvin')
        Tdisp = T[idx].to('kelvin') + dT

        # dhsat
        dqs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Tdisp),
                                  p[idx] + dp) - qs[idx]
        dhs = g * dZ + Cp_d * dT + Lv * dqs

        # Whiskers on the data plots
        ax.plot((mse_s[idx] + dhs * (-1, 1)),
                p[idx] + dp * (-1, 1),
                linewidth=3,
                color='r')
        ax.plot((dse[idx] * (1, 1)),
                p[idx] + dp * (-1, 1),
                linewidth=3,
                color='k')
        ax.plot((mse[idx] * (1, 1)),
                p[idx] + dp * (-1, 1),
                linewidth=3,
                color='b')

        # annotation to explain it
        if ilev == 400 * ilev.units:
            ax.plot(360 * mse_s.units + dhs * (-1, 1) / 1000,
                    440 * units('mbar') + dp * (-1, 1),
                    linewidth=3,
                    color='r')
            ax.annotate('+/- 10mb', xy=(362, 440), fontsize=8)
            ax.annotate(' adiabatic displacement', xy=(362, 460), fontsize=8)

    # Plot a crude histogram of parcel detrainment levels
    # Text parts
    ax.text(290, pressure[3], 'RH (%)', fontsize=11, color='k')
    ax.text(285,
            200,
            'CAPE = ' + str(np.around(CAPE.magnitude, decimals=2)) + ' [J/kg]',
            fontsize=12,
            color='green')
    ax.text(285,
            250,
            'CIN = ' + str(np.around(CIN.magnitude, decimals=2)) + ' [J/kg]',
            fontsize=12,
            color='green')
    ax.text(285,
            300,
            'LCL = ' + str(np.around(lcl_pressure.magnitude, decimals=2)) +
            ' [hpa]',
            fontsize=12,
            color='darkorange')
    ax.text(285,
            350,
            'LFC = ' + str(np.around(lfc_pressure.magnitude, decimals=2)) +
            ' [hpa]',
            fontsize=12,
            color='magenta')
    ax.text(285,
            400,
            'CWV = ' + str(np.around(cwv.magnitude, decimals=2)) + ' [mm]',
            fontsize=12,
            color='deepskyblue')
    ax.text(285,
            450,
            'CRH = ' + str(np.around(crh.magnitude, decimals=2)) + ' [%]',
            fontsize=12,
            color='blue')
    ax.legend(['DSE', 'MSE', 'SMSE'], fontsize=12, loc=1)

    ax.set_zorder(3)

    return (ax)
Пример #13
0
def test_thickness_hydrostatic_isothermal():
    """Test the thickness calculation for a dry isothermal layer at 0 degC."""
    pressure = np.arange(1000, 500 - 1e-10, -10) * units.hPa
    temperature = np.zeros_like(pressure) * units.degC
    thickness = thickness_hydrostatic(pressure, temperature)
    assert_almost_equal(thickness, 5542.12 * units.m, 2)
Пример #14
0
def wmo(pFull, TFull, lapseC=2.0 * units("K/km"), height=False):
    """
    Implements NCAR's Fortran code in python:
        https://github.com/NCAR/ncl/blob/develop/ni/src/lib/nfpfort/stattrop_dp.f
    """

    nLev = pFull.size
    nLevm = nLev - 1

    pMin = 85.0 * units.mbar
    pMax = 450.0 * units.mbar

    dZ = 2000.0 * units.meters

    g = earth_gravity
    R = dry_air_gas_constant

    const = g / R

    found = False

    lapse = np.zeros(nLevm) * units.kelvin / units.km
    pHalf = np.zeros(nLevm) * units.mbar
    pTrop = 0 * units.mbar
    for iLev in range(0, nLevm):
        lapse[iLev] = const * np.log(TFull[iLev] / TFull[iLev + 1]) / np.log(
            pFull[iLev] / pFull[iLev + 1])
        pHalf[iLev] = (pFull[iLev] + pFull[iLev + 1]) * 0.5

    for iLev in range(0, nLevm - 1):
        if lapse[iLev] < lapseC and pFull[iLev] < pMax and not found:
            P1 = np.log(pHalf[iLev].magnitude)
            P2 = np.log(pHalf[iLev + 1].magnitude)
            if (lapse[iLev] != lapse[iLev + 1]):
                weight = (lapseC - lapse[iLev]) / (lapse[iLev + 1] -
                                                   lapse[iLev])
                #tropopause pressure
                pTrop = np.exp(P1 + weight * (P2 - P1)) * units.mbar
            else:
                pTrop = pHalf[iLev]

            p2km = pTrop * np.exp(-dZ * const / TFull[iLev])
            lapseAvg = 0
            lapseSum = 0
            kount = 0
            for L in range(iLev, nLevm):
                if pHalf[L] > p2km:
                    lapseAvg = lapseSum + lapse[L]
                    kount = kount + 1
                    lapseAvg = lapseSum / kount
            found = lapseAvg < lapseC
            if not found:
                print("Tropopause not found")
            else:
                iTrop = iLev
                pTrop = pMin if pTrop < pMin else pTrop

    if height:
        z = thickness_hydrostatic(pFull[0:iTrop], TFull[0:iTrop])
        return z.to(units.km)

    return pTrop.to(units.mbar)
Пример #15
0
def test_thickness_hydrostatic_isothermal():
    """Test the thickness calculation for a dry isothermal layer at 0 degC."""
    pressure = np.arange(1000, 500 - 1e-10, -10) * units.hPa
    temperature = np.zeros_like(pressure) * units.degC
    thickness = thickness_hydrostatic(pressure, temperature)
    assert_almost_equal(thickness, 5542.12 * units.m, 2)
Пример #16
0
def entropy_plots(pressure,
                  temperature,
                  mixing_ratio,
                  altitude,
                  h0_std=2000,
                  ensemble_size=20,
                  ent_rate=np.arange(0, 2, 0.05),
                  entrain=False):
    """
    plotting the summarized entropy diagram with annotations and thermodynamic parameters
    """
    p = pressure * units('mbar')
    T = temperature * units('degC')
    q = mixing_ratio * units('kilogram/kilogram')
    qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p)
    Td = mpcalc.dewpoint(mpcalc.vapor_pressure(p, q))  # dewpoint
    Tp = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC')  # parcel profile

    # Altitude based on the hydrostatic eq.
    if len(altitude) == len(pressure):  # (1) altitudes for whole levels
        altitude = altitude * units('meter')
    elif len(altitude
             ) == 1:  # (2) known altitude where the soundings was launched
        z_surf = altitude.copy() * units('meter')
        # given altitude
        altitude = np.zeros((np.size(T))) * units('meter')
        for i in range(np.size(T)):
            altitude[i] = mpcalc.thickness_hydrostatic(
                p[:i + 1], T[:i + 1]) + z_surf  # Hypsometric Eq. for height
    else:
        print(
            '***NOTE***: the altitude at the surface is assumed 0 meter, and altitudes are derived based on the hypsometric equation'
        )
        altitude = np.zeros(
            (np.size(T))) * units('meter')  # surface is 0 meter
        for i in range(np.size(T)):
            altitude[i] = mpcalc.thickness_hydrostatic(
                p[:i + 1], T[:i + 1])  # Hypsometric Eq. for height

    # specific entropy [joule/(kg*K)]
    # sd : specific entropy of dry air
    # sm1 : specific entropy of airborne mositure in state 1 (water vapor)
    # sm2 : specific entropy of airborne mositure in state 2 (saturated water vapor)

    sd = entropy(T.magnitude, q.magnitude * 1e-6, p.magnitude)
    sm1 = entropy(T.magnitude, q.magnitude, p.magnitude)
    sm2 = entropy(T.magnitude, qs.magnitude, p.magnitude)
    ###############################

    # Water vapor calculations
    p_PWtop = min(p)
    #p_PWtop = max(200*units.mbar, min(p) + 1*units.mbar) # integrating until 200mb
    cwv = mpcalc.precipitable_water(Td, p,
                                    top=p_PWtop)  # column water vapor [mm]
    cwvs = mpcalc.precipitable_water(
        T, p, top=p_PWtop)  # saturated column water vapor [mm]
    crh = (cwv / cwvs) * 100.  # column relative humidity [%]

    #================================================
    # plotting MSE vertical profiles
    fig = plt.figure(figsize=[12, 8])
    ax = fig.add_axes([0.1, 0.1, 0.6, 0.8])
    ax.plot(sd, p, '-k', linewidth=2)
    ax.plot(sm1, p, '-b', linewidth=2)
    ax.plot(sm2, p, '-r', linewidth=2)

    # mse based on different percentages of relative humidity
    qr = np.zeros((9, np.size(qs))) * units('kilogram/kilogram')
    sm1_r = qr  # container
    for i in range(9):
        qr[i, :] = qs * 0.1 * (i + 1)
        sm1_r[i, :] = entropy(T.magnitude, qr[i, :].magnitude, p.magnitude)

    for i in range(9):
        ax.plot(sm1_r[i, :], p[:], '-', color='grey', linewidth=0.7)
        ax.text(sm1_r[i, 3].magnitude - 2, p[3].magnitude, str((i + 1) * 10))

    # drawing LCL and LFC levels
    [lcl_pressure, lcl_temperature] = mpcalc.lcl(p[0], T[0], Td[0])
    lcl_idx = np.argmin(np.abs(p.magnitude - lcl_pressure.magnitude))

    [lfc_pressure, lfc_temperature] = mpcalc.lfc(p, T, Td)
    lfc_idx = np.argmin(np.abs(p.magnitude - lfc_pressure.magnitude))

    # conserved mse of air parcel arising from 1000 hpa
    sm1_p = np.squeeze(np.ones((1, np.size(T))) * sm1[0])

    # illustration of CAPE
    el_pressure, el_temperature = mpcalc.el(p, T, Td)  # equilibrium level
    el_idx = np.argmin(np.abs(p.magnitude - el_pressure.magnitude))
    ELps = [el_pressure.magnitude
            ]  # Initialize an array of EL pressures for detrainment profile

    [CAPE, CIN] = mpcalc.cape_cin(p[:el_idx], T[:el_idx], Td[:el_idx],
                                  Tp[:el_idx])

    plt.plot(sm1_p, p, color='green', linewidth=2)
    #ax.fill_betweenx(p[lcl_idx:el_idx+1],sm1_p[lcl_idx:el_idx+1],sm2[lcl_idx:el_idx+1],interpolate=True
    #                ,color='green',alpha='0.3')

    ax.fill_betweenx(p, sd, sm1, color='deepskyblue', alpha='0.5')
    ax.set_xlabel('Specific entropies: sd, sm, sm_sat [J K$^{-1}$ kg$^{-1}$]',
                  fontsize=14)
    ax.set_ylabel('Pressure [hPa]', fontsize=14)
    ax.set_xticks([0, 50, 100, 150, 200, 250, 300, 350])
    ax.set_xlim([0, 440])
    ax.set_ylim(1030, 120)

    if entrain is True:
        # Depict Entraining parcels
        # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z)
        # M=1 at ground without loss of generality

        # Distribution of surface parcel h offsets
        h0offsets = np.sort(np.random.normal(
            0, h0_std, ensemble_size)) * units('joule/kilogram')
        # Distribution of entrainment rates
        entrainment_rates = ent_rate / (units('km'))

        for h0offset in h0offsets:

            h4ent = sm1.copy()
            h4ent[0] += h0offset

            for eps in entrainment_rates:

                M = np.exp(eps * (altitude - altitude[0])).to('dimensionless')
                # dM is the mass contribution at each level, with 1 at the origin level.
                M[0] = 0
                dM = np.gradient(M)
                # parcel mass is a sum of all the dM's at each level
                # conserved linearly-mixed variables like h are weighted averages
                if eps.magnitude == 0.0:
                    hent = np.ones(len(h4ent)) * h4ent[0]  # no mixing
                else:
                    hent = np.cumsum(dM * h4ent) / np.cumsum(dM)
                # Boolean for positive buoyancy, and its topmost altitude (index) where curve is clippes
                posboy = (hent > sm2)
                posboy[0] = True  # so there is always a detrainment level

                # defining the first EL by posboy as the detrainment layer, swiching from positive buoyancy to
                # negative buoyancy (0 to 1) and skipping the surface
                ELindex_ent = 0
                for idx in range(len(posboy) - 1):
                    if posboy[idx + 1] == 0 and posboy[idx] == 1 and idx > 0:
                        ELindex_ent = idx
                        break

                # Plot the curve
                plt.plot(hent[0:ELindex_ent + 2],
                         p[0:ELindex_ent + 2],
                         linewidth=0.6,
                         color='g')
                #plt.plot( hent[0:], p[0:], linewidth=0.6, color='g')
                # Keep a list for a histogram plot (detrainment profile)
                if p[ELindex_ent].magnitude < lfc_pressure.magnitude:  # buoyant parcels only
                    ELps.append(p[ELindex_ent].magnitude)

        # Plot a crude histogram of parcel detrainment levels
        NBINS = 20
        pbins = np.linspace(1000, 150,
                            num=NBINS)  # pbins for detrainment levels
        hist = np.zeros((len(pbins) - 1))
        for x in ELps:
            for i in range(len(pbins) - 1):
                if (x < pbins[i]) & (x >= pbins[i + 1]):
                    hist[i] += 1
                    break

        det_per = hist / sum(hist) * 100
        # percentages of detrainment ensumbles at levels

        ax2 = fig.add_axes([0.705, 0.1, 0.1, 0.8], facecolor=None)
        ax2.barh(pbins[1:],
                 det_per,
                 color='lightgrey',
                 edgecolor='k',
                 height=15 * (20 / NBINS))
        ax2.set_xlim([0, 100])
        ax2.set_xticks([0, 20, 40, 60, 80, 100])
        ax2.set_ylim([1030, 120])
        ax2.set_xlabel('Detrainment [%]')
        ax2.grid()
        ax2.set_zorder(2)

        ax.plot([400, 400], [1100, 0])
        ax.annotate('Detrainment', xy=(362, 320), color='dimgrey')
        ax.annotate('ensemble: ' + str(ensemble_size * len(entrainment_rates)),
                    xy=(364, 340),
                    color='dimgrey')
        ax.annotate('Detrainment', xy=(362, 380), color='dimgrey')
        ax.annotate(' scale: 0 - 2 km', xy=(365, 400), color='dimgrey')

        # Overplots on the mess: undilute parcel and CAPE, etc.
        ax.plot((1, 1) * sm1[0], (1, 0) * (p[0]), color='g', linewidth=2)

        # Replot the sounding on top of all that mess
        ax.plot(sm2, p, color='r', linewidth=1.5)
        ax.plot(sm1, p, color='b', linewidth=1.5)

        # label LCL and LCF
        ax.plot((sm2[lcl_idx] + (-2000, 2000) * units('joule/kilogram')),
                lcl_pressure + (0, 0) * units('mbar'),
                color='orange',
                linewidth=3)
        ax.plot((sm2[lfc_idx] + (-2000, 2000) * units('joule/kilogram')),
                lfc_pressure + (0, 0) * units('mbar'),
                color='magenta',
                linewidth=3)

    # Plot a crude histogram of parcel detrainment levels
    # Text parts
    ax.text(30, pressure[3], 'RH (%)', fontsize=11, color='k')
    ax.text(20,
            200,
            'CAPE = ' + str(np.around(CAPE.magnitude, decimals=2)) + ' [J/kg]',
            fontsize=12,
            color='green')
    ax.text(20,
            250,
            'CIN = ' + str(np.around(CIN.magnitude, decimals=2)) + ' [J/kg]',
            fontsize=12,
            color='green')
    ax.text(20,
            300,
            'LCL = ' + str(np.around(lcl_pressure.magnitude, decimals=2)) +
            ' [hpa]',
            fontsize=12,
            color='darkorange')
    ax.text(20,
            350,
            'LFC = ' + str(np.around(lfc_pressure.magnitude, decimals=2)) +
            ' [hpa]',
            fontsize=12,
            color='magenta')
    ax.text(20,
            400,
            'CWV = ' + str(np.around(cwv.magnitude, decimals=2)) + ' [mm]',
            fontsize=12,
            color='deepskyblue')
    ax.text(20,
            450,
            'CRH = ' + str(np.around(crh.magnitude, decimals=2)) + ' [%]',
            fontsize=12,
            color='blue')
    ax.legend(['DEnt', 'MEnt', 'SMEnt'], fontsize=12, loc=1)

    ax.set_zorder(3)

    return (ax)