def test_no_lfc(): """Test LFC calculation when there is no LFC in the data.""" levels = np.array([959., 867.9, 779.2, 647.5, 472.5, 321.9, 251.]) * units.mbar temperatures = np.array([22.2, 17.4, 14.6, 1.4, -17.6, -39.4, -52.5]) * units.celsius dewpoints = np.array([9., 4.3, -21.2, -26.7, -31., -53.3, -66.7]) * units.celsius lfc_pressure, lfc_temperature = lfc(levels, temperatures, dewpoints) assert assert_nan(lfc_pressure, levels.units) assert assert_nan(lfc_temperature, temperatures.units)
def test_lfc_basic(): """Test LFC calculation.""" levels = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperatures = np.array([22.2, 14.6, 12., 9.4, 7., -49.]) * units.celsius dewpoints = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints) assert_almost_equal(lfc_pressure, 727.468 * units.mbar, 2) assert_almost_equal(lfc_temp, 9.705 * units.celsius, 2)
def test_lfc_basic(): """Test LFC calculation.""" levels = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperatures = np.array([22.2, 14.6, 12., 9.4, 7., -49.]) * units.celsius dewpoints = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius l = lfc(levels, temperatures, dewpoints) assert_almost_equal(l[0], 727.468 * units.mbar, 2) assert_almost_equal(l[1], 9.705 * units.celsius, 2)
def plot_sounding(date, station): p, T, Td, u, v, windspeed = get_sounding_data(date, station) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) lfc_pressure, lfc_temperature = mpcalc.lfc(p, T, Td) parcel_path = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') # Create a new figure. The dimensions here give a good aspect ratio fig = plt.figure(figsize=(8, 8)) skew = SkewT(fig) # Plot the data temperature_line, = skew.plot(p, T, color='tab:red') dewpoint_line, = skew.plot(p, Td, color='blue') cursor = mplcursors.cursor([temperature_line, dewpoint_line]) # Plot thermodynamic parameters and parcel path skew.plot(p, parcel_path, color='black') if lcl_pressure: skew.ax.axhline(lcl_pressure, color='black') if lfc_pressure: skew.ax.axhline(lfc_pressure, color='0.7') # Add the relevant special lines skew.ax.axvline(0, color='c', linestyle='--', linewidth=2) skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() # Shade areas representing CAPE and CIN skew.shade_cin(p, T, parcel_path) skew.shade_cape(p, T, parcel_path) # Add wind barbs skew.plot_barbs(p, u, v) # Add an axes to the plot ax_hod = inset_axes(skew.ax, '30%', '30%', loc=1, borderpad=3) # Plot the hodograph h = Hodograph(ax_hod, component_range=100.) # Grid the hodograph h.add_grid(increment=20) # Plot the data on the hodograph mask = (p >= 100 * units.mbar) h.plot_colormapped(u[mask], v[mask], windspeed[mask]) # Plot a line colored by wind speed # Set some sensible axis limits skew.ax.set_ylim(1000, 100) skew.ax.set_xlim(-40, 60) return fig, skew
def test_lfc_ml(): """Test Mixed-Layer LFC calculation.""" levels = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar temperatures = np.array([22.2, 14.6, 12., 9.4, 7., -49.]) * units.celsius dewpoints = np.array([19., -11.2, -10.8, -10.4, -10., -53.2]) * units.celsius __, t_mixed, td_mixed = mixed_parcel(levels, temperatures, dewpoints) mixed_parcel_prof = parcel_profile(levels, t_mixed, td_mixed) lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints, mixed_parcel_prof) assert_almost_equal(lfc_pressure, 631.794 * units.mbar, 2) assert_almost_equal(lfc_temp, -1.862 * units.degC, 2)
def test_lfc_sfc_precision(): """Test LFC when there are precision issues with the parcel path.""" levels = np.array([839., 819.4, 816., 807., 790.7, 763., 736.2, 722., 710.1, 700.]) * units.mbar temperatures = np.array([20.6, 22.3, 22.6, 22.2, 20.9, 18.7, 16.4, 15.2, 13.9, 12.8]) * units.celsius dewpoints = np.array([10.6, 8., 7.6, 6.2, 5.7, 4.7, 3.7, 3.2, 3., 2.8]) * units.celsius lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints) assert assert_nan(lfc_pressure, levels.units) assert assert_nan(lfc_temp, temperatures.units)
def test_lfc_inversion(): """Test LFC when there is an inversion to be sure we don't pick that.""" levels = np.array([963., 789., 782.3, 754.8, 728.1, 727., 700., 571., 450., 300., 248.]) * units.mbar temperatures = np.array([25.4, 18.4, 17.8, 15.4, 12.9, 12.8, 10., -3.9, -16.3, -41.1, -51.5]) * units.celsius dewpoints = np.array([20.4, 0.4, -0.5, -4.3, -8., -8.2, -9., -23.9, -33.3, -54.1, -63.5]) * units.celsius lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints) assert_almost_equal(lfc_pressure, 706.0103 * units.mbar, 2) assert_almost_equal(lfc_temp, 10.6232 * units.celsius, 2)
def test_lfc_equals_lcl(): """Test LFC when there is no cap and the lfc is equal to the lcl.""" levels = np.array([912., 905.3, 874.4, 850., 815.1, 786.6, 759.1, 748., 732.2, 700., 654.8]) * units.mbar temperatures = np.array([29.4, 28.7, 25.2, 22.4, 19.4, 16.8, 14.3, 13.2, 12.6, 11.4, 7.1]) * units.celsius dewpoints = np.array([18.4, 18.1, 16.6, 15.4, 13.2, 11.4, 9.6, 8.8, 0., -18.6, -22.9]) * units.celsius lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints) assert_almost_equal(lfc_pressure, 777.0333 * units.mbar, 2) assert_almost_equal(lfc_temp, 15.8714 * units.celsius, 2)
def test_lfc_not_below_lcl(): """Test sounding where LFC appears to be (but isn't) below LCL.""" levels = np.array([1002.5, 1001.7, 1001., 1000.3, 999.7, 999., 998.2, 977.9, 966.2, 952.3, 940.6, 930.5, 919.8, 909.1, 898.9, 888.4, 878.3, 868.1, 858., 848., 837.2, 827., 816.7, 805.4]) * units.hPa temperatures = np.array([17.9, 17.9, 17.8, 17.7, 17.7, 17.6, 17.5, 16., 15.2, 14.5, 13.8, 13., 12.5, 11.9, 11.4, 11., 10.3, 9.7, 9.2, 8.7, 8., 7.4, 6.8, 6.1]) * units.degC dewpoints = np.array([13.6, 13.6, 13.5, 13.5, 13.5, 13.5, 13.4, 12.5, 12.1, 11.8, 11.4, 11.3, 11., 9.3, 10., 8.7, 8.9, 8.6, 8.1, 7.6, 7., 6.5, 6., 5.4]) * units.degC lfc_pressure, lfc_temp = lfc(levels, temperatures, dewpoints) # Before patch, LFC pressure would show 1000.5912165339967 hPa assert_almost_equal(lfc_pressure, 811.8456357 * units.mbar, 6) assert_almost_equal(lfc_temp, 6.4992871 * units.celsius, 6)
def test_sensitive_sounding(): """Test quantities for a sensitive sounding (#902).""" # This sounding has a very small positive area in the low level. It's only captured # properly if the parcel profile includes the LCL, otherwise it breaks LFC and CAPE p = units.Quantity([ 1004., 1000., 943., 928., 925., 850., 839., 749., 700., 699., 603., 500., 404., 400., 363., 306., 300., 250., 213., 200., 176., 150. ], 'hectopascal') t = units.Quantity([ 24.2, 24., 20.2, 21.6, 21.4, 20.4, 20.2, 14.4, 13.2, 13., 6.8, -3.3, -13.1, -13.7, -17.9, -25.5, -26.9, -37.9, -46.7, -48.7, -52.1, -58.9 ], 'degC') td = units.Quantity([ 21.9, 22.1, 19.2, 20.5, 20.4, 18.4, 17.4, 8.4, -2.8, -3.0, -15.2, -20.3, -29.1, -27.7, -24.9, -39.5, -41.9, -51.9, -60.7, -62.7, -65.1, -71.9 ], 'degC') lfc_pressure, lfc_temp = lfc(p, t, td) assert_almost_equal(lfc_pressure, 947.476 * units.mbar, 2) assert_almost_equal(lfc_temp, 20.498 * units.degC, 2) pos, neg = surface_based_cape_cin(p, t, td) assert_almost_equal(pos, 0.112 * units('J/kg'), 3) assert_almost_equal(neg, -6.075 * units('J/kg'), 3)
def calculate_stability_indicies(ds, temp_name="temperature", td_name="dewpoint_temperature", p_name="pressure", moving_ave_window=0): """ Function for calculating stability indices from sounding data. Parameters ---------- ds : ACT dataset The dataset to compute the stability indicies of. Must have temperature, dewpoint, and pressure in vertical coordinates. temp_name : str The name of the temperature field. td_name : str The name of the dewpoint field. p_name : str The name of the pressure field. moving_ave_window : int Number of points to do a moving average on sounding data to reduce noise. This is useful if noise in the sounding is preventing parcel ascent. Returns ------- ds : ACT dataset An ACT dataset with additional stability indicies added. """ if not METPY_AVAILABLE: raise ImportError("MetPy need to be installed on your system to " + "calculate stability indices") t = ds[temp_name] td = ds[td_name] p = ds[p_name] if not hasattr(t, "units"): raise AttributeError("Temperature field must have units" + " for ACT to discern!") if not hasattr(td, "units"): raise AttributeError("Dewpoint field must have units" + " for ACT to discern!") if not hasattr(p, "units"): raise AttributeError("Pressure field must have units" + " for ACT to discern!") if t.units == "C": t_units = units.degC else: t_units = getattr(units, t.units) if td.units == "C": td_units = units.degC else: td_units = getattr(units, td.units) p_units = getattr(units, p.units) # Sort all values by decreasing pressure t_sorted = np.array(t.values) td_sorted = np.array(td.values) p_sorted = np.array(p.values) ind_sort = np.argsort(p_sorted) t_sorted = t_sorted[ind_sort[-1:0:-1]] td_sorted = td_sorted[ind_sort[-1:0:-1]] p_sorted = p_sorted[ind_sort[-1:0:-1]] if moving_ave_window > 0: t_sorted = np.convolve( t_sorted, np.ones((moving_ave_window,)) / moving_ave_window) td_sorted = np.convolve( td_sorted, np.ones((moving_ave_window,)) / moving_ave_window) p_sorted = np.convolve( p_sorted, np.ones((moving_ave_window,)) / moving_ave_window) t_sorted = t_sorted * t_units td_sorted = td_sorted * td_units p_sorted = p_sorted * p_units t_profile = mpcalc.parcel_profile( p_sorted, t_sorted[0], td_sorted[0]) # Calculate parcel trajectory ds["parcel_temperature"] = t_profile.magnitude ds["parcel_temperature"].attrs['units'] = t_profile.units # Calculate CAPE, CIN, LCL sbcape, sbcin = mpcalc.surface_based_cape_cin( p_sorted, t_sorted, td_sorted) lcl = mpcalc.lcl( p_sorted[0], t_sorted[0], td_sorted[0]) try: lfc = mpcalc.lfc( p_sorted[0], t_sorted[0], td_sorted[0]) except IndexError: lfc = np.nan * p_sorted.units mucape, mucin = mpcalc.most_unstable_cape_cin( p_sorted, t_sorted, td_sorted) where_500 = np.argmin(np.abs(p_sorted - 500 * units.hPa)) li = t_sorted[where_500] - t_profile[where_500] ds["surface_based_cape"] = sbcape.magnitude ds["surface_based_cape"].attrs['units'] = "J/kg" ds["surface_based_cape"].attrs['long_name'] = "Surface-based CAPE" ds["surface_based_cin"] = sbcin.magnitude ds["surface_based_cin"].attrs['units'] = "J/kg" ds["surface_based_cin"].attrs['long_name'] = "Surface-based CIN" ds["most_unstable_cape"] = mucape.magnitude ds["most_unstable_cape"].attrs['units'] = "J/kg" ds["most_unstable_cape"].attrs['long_name'] = "Most unstable CAPE" ds["most_unstable_cin"] = mucin.magnitude ds["most_unstable_cin"].attrs['units'] = "J/kg" ds["most_unstable_cin"].attrs['long_name'] = "Most unstable CIN" ds["lifted_index"] = li.magnitude ds["lifted_index"].attrs['units'] = t_profile.units ds["lifted_index"].attrs['long_name'] = "Lifted index" ds["level_of_free_convection"] = lfc.magnitude ds["level_of_free_convection"].attrs['units'] = lfc.units ds["level_of_free_convection"].attrs['long_name'] = "Level of free convection" ds["lifted_condensation_level_temperature"] = lcl[1].magnitude ds["lifted_condensation_level_temperature"].attrs['units'] = lcl[1].units ds["lifted_condensation_level_temperature"].attrs['long_name'] = "Lifted condensation level temperature" ds["lifted_condensation_level_pressure"] = lcl[0].magnitude ds["lifted_condensation_level_pressure"].attrs['units'] = lcl[0].units ds["lifted_condensation_level_pressure"].attrs['long_name'] = "Lifted condensation level pressure" return ds
'pressure': p, 'temperature': T, 'dewpoint': Td, 'speed': ws, 'direction': wd }) p = df['pressure'].values * units.hPa T = df['temperature'].values * units.degC Td = df['dewpoint'].values * units.degC wind_speed = df['speed'].values * units.meter / (units.second) wind_dir = df['direction'].values * units.degrees u, v = mpcalc.wind_components(wind_speed, wind_dir) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) lfc_pressure, lfc_temperature = mpcalc.lfc(p, T, Td) parcel_prof = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') cape, cin = mpcalc.cape_cin(p, T, Td, parcel_prof) fig = plt.figure(figsize=(12., 9.)) fig.subplots_adjust(top=0.9, bottom=0.1, left=0.05, right=0.96, wspace=0.08, hspace=0.25) gs = gridspec.GridSpec(21, 5) skew = SkewT(fig, subplot=gs[:, :4], rotation=45) # Plot the data using normal plotting functions, in this case using # log scaling in Y, as dictated by the typical meteorological plot
def process_skewt(self): # Calculation index_p100 = get_pressure_level_index(self.p_i, 100) lcl_p, lcl_t = mpcalc.lcl(self.p_i[0], self.t_i[0], self.td_i[0]) lfc_p, lfc_t = mpcalc.lfc(self.p_i, self.t_i, self.td_i) el_p, el_t = mpcalc.el(self.p_i, self.t_i, self.td_i) prof = mpcalc.parcel_profile(self.p_i, self.t_i[0], self.td_i[0]).to('degC') cape, cin = mpcalc.cape_cin(self.p_i, self.t_i, self.td_i, prof) mucape, mucin = mpcalc.most_unstable_cape_cin(self.p_i, self.t_i, self.td_i) pwat = mpcalc.precipitable_water(self.td_i, self.p_i) i8 = get_pressure_level_index(self.p_i, 850) i7 = get_pressure_level_index(self.p_i, 700) i5 = get_pressure_level_index(self.p_i, 500) theta850 = mpcalc.equivalent_potential_temperature(850 * units('hPa'), self.t_i[i8], self.td_i[i5]) theta500 = mpcalc.equivalent_potential_temperature(500 * units('hPa'), self.t_i[i5], self.td_i[i5]) thetadiff = theta850 - theta500 k = self.t_i[i8] - self.t_i[i5] + self.td_i[i8] - (self.t_i[i7] - self.td_i[i7]) a = ((self.t_i[i8] - self.t_i[i5]) - (self.t_i[i8] - self.td_i[i5]) - (self.t_i[i7] - self.td_i[i7]) - (self.t_i[i5] - self.td_i[i5])) sw = c_sweat(np.array(self.t_i[i8].magnitude), np.array(self.td_i[i8].magnitude), np.array(self.t_i[i5].magnitude), np.array(self.u_i[i8].magnitude), np.array(self.v_i[i8].magnitude), np.array(self.u_i[i5].magnitude), np.array(self.v_i[i5].magnitude)) si = showalter_index(self.t_i[i8], self.td_i[i8], self.t_i[i5]) li = lifted_index(self.t_i[0], self.td_i[0], self.p_i[0], self.t_i[i5]) srh_pos, srh_neg, srh_tot = mpcalc.storm_relative_helicity(self.u_i, self.v_i, self.alt, 1000 * units('m')) sbcape, sbcin = mpcalc.surface_based_cape_cin(self.p_i, self.t_i, self.td_i) shr6km = mpcalc.bulk_shear(self.p_i, self.u_i, self.v_i, heights=self.alt, depth=6000 * units('m')) wshr6km = mpcalc.wind_speed(*shr6km) sigtor = mpcalc.significant_tornado(sbcape, delta_height(self.p_i[0], lcl_p), srh_tot, wshr6km)[0] # Plotting self.ax.set_ylim(1050, 100) self.ax.set_xlim(-40, 50) self.plot(self.p_i, self.t_i, 'r', linewidth=1) self.plot(self.p_i[:self.dp_idx], self.td_i[:self.dp_idx], 'g', linewidth=1) self.plot_barbs(self.p_i[:index_p100], self.u_i[:index_p100] * 1.94, self.v_i[:index_p100] * 1.94) self.plot(lcl_p, lcl_t, 'ko', markerfacecolor='black') self.plot(self.p_i, prof, 'k', linewidth=2) if cin.magnitude < 0: chi = -1 * cin.magnitude self.shade_cin(self.p_i, self.t_i, prof) elif cin.magnitude > 0: chi = cin.magnitude self.shade_cin(self.p_i, self.t_i, prof) else: chi = 0. self.shade_cape(self.p_i, self.t_i, prof) self.plot_dry_adiabats(linewidth=0.5) self.plot_moist_adiabats(linewidth=0.5) self.plot_mixing_lines(linewidth=0.5) plt.title('Skew-T Plot \nStation: {} Time: {}'.format(self.st, self.time.strftime('%Y.%m.%d %H:%M')), fontsize=14, loc='left') # Add hodograph ax = self._fig.add_axes([0.95, 0.71, 0.17, 0.17]) h = Hodograph(ax, component_range=50) h.add_grid(increment=20) h.plot_colormapped(self.u_i[:index_p100], self.v_i[:index_p100], self.alt[:index_p100], linewidth=1.2) # Annotate parameters # Annotate names namelist = ['CAPE', 'CIN', 'MUCAPE', 'PWAT', 'K', 'A', 'SWEAT', 'LCL', 'LFC', 'EL', 'SI', 'LI', 'T850-500', 'θse850-500', 'SRH', 'STP'] xcor = -50 ycor = -90 spacing = -9 for nm in namelist: ax.text(xcor, ycor, '{}: '.format(nm), fontsize=10) ycor += spacing # Annotate values varlist = [cape, chi, mucape, pwat, k, a, sw, lcl_p, lfc_p, el_p, si, li, self.t_i[i8] - self.t_i[i5], thetadiff, srh_tot, sigtor] xcor = 10 ycor = -90 for v in varlist: if hasattr(v, 'magnitude'): v = v.magnitude ax.text(xcor, ycor, str(np.round_(v, 2)), fontsize=10) ycor += spacing # Annotate units unitlist = ['J/kg', 'J/kg', 'J/kg', 'mm', '°C', '°C', '', 'hPa', 'hPa', 'hPa', '°C', '°C', '°C', '°C'] xcor = 45 ycor = -90 for u in unitlist: ax.text(xcor, ycor, ' {}'.format(u), fontsize=10) ycor += spacing
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)
def do_profile(cursor, fid, gdf, nt): """Process this profile.""" # The inbound profile may contain mandatory level data that is below # the surface. It seems the best we can do here is to ensure both # temperature and dewpoint are valid and call that the bottom. td_profile = gdf[pd.notnull(gdf["tmpc"]) & pd.notnull(gdf["dwpc"])] wind_profile = gdf[pd.notnull(gdf["u"])] # Presently we are all or nothing here. The length is arb if len(td_profile.index) < 5 or len(wind_profile.index) < 5: msg = ("quorum fail td: %s wind: %s, skipping") % ( len(td_profile.index), len(wind_profile.index), ) raise ValueError(msg) if gdf["pressure"].min() > 500: raise ValueError("Profile only up to %s mb" % (gdf["pressure"].min(), )) # Does a crude check that our metadata station elevation is within 50m # of the profile bottom, otherwise we ABORT station_elevation_m = get_station_elevation(td_profile, nt) # get surface wind u_sfc, v_sfc = get_surface_winds(wind_profile) u_1km, v_1km = get_aloft_winds(wind_profile, station_elevation_m + 1000.0) u_3km, v_3km = get_aloft_winds(wind_profile, station_elevation_m + 3000.0) u_6km, v_6km = get_aloft_winds(wind_profile, station_elevation_m + 6000.0) shear_sfc_1km_smps = np.sqrt((u_1km - u_sfc)**2 + (v_1km - v_sfc)**2) shear_sfc_3km_smps = np.sqrt((u_3km - u_sfc)**2 + (v_3km - v_sfc)**2) shear_sfc_6km_smps = np.sqrt((u_6km - u_sfc)**2 + (v_6km - v_sfc)**2) total_totals = compute_total_totals(td_profile) sweat_index = compute_sweat_index(td_profile, total_totals) try: bunkers_rm, bunkers_lm, mean0_6_wind = bunkers_storm_motion( wind_profile["pressure"].values * units.hPa, wind_profile["u"].values * units("m/s"), wind_profile["v"].values * units("m/s"), wind_profile["height"].values * units("m"), ) except ValueError: # Profile may not go up high enough bunkers_rm = [np.nan * units("m/s"), np.nan * units("m/s")] bunkers_lm = [np.nan * units("m/s"), np.nan * units("m/s")] mean0_6_wind = [np.nan * units("m/s"), np.nan * units("m/s")] bunkers_rm_smps = wind_speed(bunkers_rm[0], bunkers_rm[1]) bunkers_rm_drct = wind_direction(bunkers_rm[0], bunkers_rm[1]) bunkers_lm_smps = wind_speed(bunkers_lm[0], bunkers_lm[1]) bunkers_lm_drct = wind_direction(bunkers_lm[0], bunkers_lm[1]) mean0_6_wind_smps = wind_speed(mean0_6_wind[0], mean0_6_wind[1]) mean0_6_wind_drct = wind_direction(mean0_6_wind[0], mean0_6_wind[1]) try: ( srh_sfc_1km_pos, srh_sfc_1km_neg, srh_sfc_1km_total, ) = storm_relative_helicity( wind_profile["height"].values * units("m"), wind_profile["u"].values * units("m/s"), wind_profile["v"].values * units("m/s"), 1000.0 * units("m"), ) except ValueError: srh_sfc_1km_pos = np.nan * units("m") # blah srh_sfc_1km_neg = np.nan * units("m") # blah srh_sfc_1km_total = np.nan * units("m") # blah try: ( srh_sfc_3km_pos, srh_sfc_3km_neg, srh_sfc_3km_total, ) = storm_relative_helicity( wind_profile["height"].values * units("m"), wind_profile["u"].values * units("m/s"), wind_profile["v"].values * units("m/s"), 3000.0 * units("m"), ) except ValueError: srh_sfc_3km_pos = np.nan * units("m") # blah srh_sfc_3km_neg = np.nan * units("m") # blah srh_sfc_3km_total = np.nan * units("m") # blah pwater = precipitable_water( td_profile["pressure"].values * units.hPa, td_profile["dwpc"].values * units.degC, ) (sbcape, sbcin) = surface_based_cape_cin( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) (mucape, mucin) = most_unstable_cape_cin( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) (mlcape, mlcin) = mixed_layer_cape_cin( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) el_p, el_t = el( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) lfc_p, lfc_t = lfc( td_profile["pressure"].values * units.hPa, td_profile["tmpc"].values * units.degC, td_profile["dwpc"].values * units.degC, ) (lcl_p, lcl_t) = lcl( td_profile["pressure"].values[0] * units.hPa, td_profile["tmpc"].values[0] * units.degC, td_profile["dwpc"].values[0] * units.degC, ) vals = [ el_p.to(units("hPa")).m, lfc_p.to(units("hPa")).m, lcl_p.to(units("hPa")).m, ] [el_hght, lfc_hght, lcl_hght] = log_interp( np.array(vals, dtype="f"), td_profile["pressure"].values[::-1], td_profile["height"].values[::-1], ) el_agl = gt1(el_hght - station_elevation_m) lcl_agl = gt1(lcl_hght - station_elevation_m) lfc_agl = gt1(lfc_hght - station_elevation_m) args = ( nonull(sbcape.to(units("joules / kilogram")).m), nonull(sbcin.to(units("joules / kilogram")).m), nonull(mucape.to(units("joules / kilogram")).m), nonull(mucin.to(units("joules / kilogram")).m), nonull(mlcape.to(units("joules / kilogram")).m), nonull(mlcin.to(units("joules / kilogram")).m), nonull(pwater.to(units("mm")).m), nonull(el_agl), nonull(el_p.to(units("hPa")).m), nonull(el_t.to(units.degC).m), nonull(lfc_agl), nonull(lfc_p.to(units("hPa")).m), nonull(lfc_t.to(units.degC).m), nonull(lcl_agl), nonull(lcl_p.to(units("hPa")).m), nonull(lcl_t.to(units.degC).m), nonull(total_totals), nonull(sweat_index), nonull(bunkers_rm_smps.m), nonull(bunkers_rm_drct.m), nonull(bunkers_lm_smps.m), nonull(bunkers_lm_drct.m), nonull(mean0_6_wind_smps.m), nonull(mean0_6_wind_drct.m), nonull(srh_sfc_1km_pos.m), nonull(srh_sfc_1km_neg.m), nonull(srh_sfc_1km_total.m), nonull(srh_sfc_3km_pos.m), nonull(srh_sfc_3km_neg.m), nonull(srh_sfc_3km_total.m), nonull(shear_sfc_1km_smps), nonull(shear_sfc_3km_smps), nonull(shear_sfc_6km_smps), fid, ) cursor.execute( """ UPDATE raob_flights SET sbcape_jkg = %s, sbcin_jkg = %s, mucape_jkg = %s, mucin_jkg = %s, mlcape_jkg = %s, mlcin_jkg = %s, pwater_mm = %s, el_agl_m = %s, el_pressure_hpa = %s, el_tmpc = %s, lfc_agl_m = %s, lfc_pressure_hpa = %s, lfc_tmpc = %s, lcl_agl_m = %s, lcl_pressure_hpa = %s, lcl_tmpc = %s, total_totals = %s, sweat_index = %s, bunkers_rm_smps = %s, bunkers_rm_drct = %s, bunkers_lm_smps = %s, bunkers_lm_drct = %s, mean_sfc_6km_smps = %s, mean_sfc_6km_drct = %s, srh_sfc_1km_pos = %s, srh_sfc_1km_neg = %s, srh_sfc_1km_total = %s, srh_sfc_3km_pos = %s, srh_sfc_3km_neg = %s, srh_sfc_3km_total = %s, shear_sfc_1km_smps = %s, shear_sfc_3km_smps = %s, shear_sfc_6km_smps = %s, computed = 't' WHERE fid = %s """, args, )
def SkewT_plot(p, T, Td, heights, u=0, v=0, wind_barb=0, p_lims=[1000, 100], T_lims=[-50, 35], metpy_logo=0, plt_lfc=0, plt_lcl=0, plt_el=0, title=None): #plotting fig = plt.figure(figsize=(10, 10)) skew = plots.SkewT(fig) skew.plot(p, T, 'red') #Virtual Temperature skew.plot(p, Td, 'green') #Dewpoint skew.ax.set_ylim(p_lims) skew.ax.set_xlim(T_lims) if wind_barb == 1: # resampling wind barbs interval = np.logspace(2, 3) * units.hPa idx = mpcalc.resample_nn_1d(p, interval) skew.plot_barbs(p[idx], u[idx], v[idx]) #Showing Adiabasts and Mixing Ratio Lines skew.plot_dry_adiabats() skew.plot_moist_adiabats() skew.plot_mixing_lines() parcel_path = mpcalc.parcel_profile(p, T[0], Td[0]) skew.plot(p, parcel_path, color='k') # CAPE and CIN skew.shade_cape(p, T, parcel_path) skew.shade_cin(p, T, parcel_path) # Isotherms and Isobars # skew.ax.axhline(500 * units.hPa, color='k') # skew.ax.axvline(0 * units.degC, color='c') # LCL, LFC, EL lcl_p, lcl_T = mpcalc.lcl(p[0], T[0], Td[0]) lfc_p, lfc_T = mpcalc.lfc(p, T, Td) el_p, el_T = mpcalc.el(p, T, Td) if plt_lfc == 1: skew.ax.axhline(lfc_p, color='k') if plt_lcl == 1: skew.ax.axhline(lcl_p, color='k') # skew.ax.text(lcl_p, ) if plt_el == 1: skew.ax.axhline(el_p, color='k') if metpy_logo == 1: plots.add_metpy_logo(fig, x=55, y=50) decimate = 3 for p, T, heights in zip(df['pressure'][::decimate], df['temperature'][::decimate], df['height'][::decimate]): if p >= 700: skew.ax.text(T + 1, p, round(heights, 0)) plt.title(title) plt.show() return
''' skew.shade_cin(p, T, prof_0) skew.shade_cape(p, T, prof_0) # Calculate LCL height and plot as black dot lcl_pressure, lcl_temperature = mpcalc.lcl( p[0], T[0], Td[0]) skew.plot(lcl_pressure, lcl_temperature, 'ko', markersize=8, fillstyle='none', label='LCL') # Calculate LCF height and plot as purple dot LCF_pressure, LCF_temperature = mpcalc.lfc( p, T, Td, prof_0) skew.plot(LCF_pressure, LCF_temperature, 'rx', markersize=8, fillstyle='none', label='LCF') # Calculate EL height and plot as blue dot EL_pressure, EL_temperature = mpcalc.el(p, T, Td, prof_0) skew.plot(EL_pressure, EL_temperature, 'bo', markersize=8, fillstyle='none', label='EL')
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)
def calculate_stability_indicies( ds, temp_name='temperature', td_name='dewpoint_temperature', p_name='pressure', rh_name='relative_humidity', moving_ave_window=0, ): """ Function for calculating stability indices from sounding data. Parameters ---------- ds : ACT dataset The dataset to compute the stability indicies of. Must have temperature, dewpoint, and pressure in vertical coordinates. temp_name : str The name of the temperature field. td_name : str The name of the dewpoint field. p_name : str The name of the pressure field. rh_name : str The name of the relative humidity field. moving_ave_window : int Number of points to do a moving average on sounding data to reduce noise. This is useful if noise in the sounding is preventing parcel ascent. Returns ------- ds : ACT dataset An ACT dataset with additional stability indicies added. """ if not METPY_AVAILABLE: raise ImportError( 'MetPy need to be installed on your system to ' + 'calculate stability indices' ) t = ds[temp_name] td = ds[td_name] p = ds[p_name] rh = ds[rh_name] if not hasattr(t, 'units'): raise AttributeError('Temperature field must have units' + ' for ACT to discern!') if not hasattr(td, 'units'): raise AttributeError('Dewpoint field must have units' + ' for ACT to discern!') if not hasattr(p, 'units'): raise AttributeError('Pressure field must have units' + ' for ACT to discern!') if t.units == 'C': t_units = units.degC else: t_units = getattr(units, t.units) if td.units == 'C': td_units = units.degC else: td_units = getattr(units, td.units) p_units = getattr(units, p.units) rh_units = getattr(units, rh.units) # Sort all values by decreasing pressure t_sorted = np.array(t.values) td_sorted = np.array(td.values) p_sorted = np.array(p.values) rh_sorted = np.array(rh.values) ind_sort = np.argsort(p_sorted) t_sorted = t_sorted[ind_sort[-1:0:-1]] td_sorted = td_sorted[ind_sort[-1:0:-1]] p_sorted = p_sorted[ind_sort[-1:0:-1]] rh_sorted = rh_sorted[ind_sort[-1:0:-1]] if moving_ave_window > 0: t_sorted = np.convolve(t_sorted, np.ones((moving_ave_window,)) / moving_ave_window) td_sorted = np.convolve(td_sorted, np.ones((moving_ave_window,)) / moving_ave_window) p_sorted = np.convolve(p_sorted, np.ones((moving_ave_window,)) / moving_ave_window) rh_sorted = np.convolve(rh_sorted, np.ones((moving_ave_window,)) / moving_ave_window) t_sorted = t_sorted * t_units td_sorted = td_sorted * td_units p_sorted = p_sorted * p_units rh_sorted = rh_sorted * rh_units # Calculate mixing ratio mr = mpcalc.mixing_ratio_from_relative_humidity(p_sorted, t_sorted, rh_sorted) # Discussion of issue #361 use virtual temperature. vt = mpcalc.virtual_temperature(t_sorted, mr) t_profile = mpcalc.parcel_profile(p_sorted, t_sorted[0], td_sorted[0]) # Calculate parcel trajectory ds['parcel_temperature'] = t_profile.magnitude ds['parcel_temperature'].attrs['units'] = t_profile.units # Calculate CAPE, CIN, LCL sbcape, sbcin = mpcalc.surface_based_cape_cin(p_sorted, vt, td_sorted) lcl = mpcalc.lcl(p_sorted[0], t_sorted[0], td_sorted[0]) try: lfc = mpcalc.lfc(p_sorted[0], t_sorted[0], td_sorted[0]) except IndexError: lfc = np.nan * p_sorted.units mucape, mucin = mpcalc.most_unstable_cape_cin(p_sorted, vt, td_sorted) where_500 = np.argmin(np.abs(p_sorted - 500 * units.hPa)) li = t_sorted[where_500] - t_profile[where_500] ds['surface_based_cape'] = sbcape.magnitude ds['surface_based_cape'].attrs['units'] = 'J/kg' ds['surface_based_cape'].attrs['long_name'] = 'Surface-based CAPE' ds['surface_based_cin'] = sbcin.magnitude ds['surface_based_cin'].attrs['units'] = 'J/kg' ds['surface_based_cin'].attrs['long_name'] = 'Surface-based CIN' ds['most_unstable_cape'] = mucape.magnitude ds['most_unstable_cape'].attrs['units'] = 'J/kg' ds['most_unstable_cape'].attrs['long_name'] = 'Most unstable CAPE' ds['most_unstable_cin'] = mucin.magnitude ds['most_unstable_cin'].attrs['units'] = 'J/kg' ds['most_unstable_cin'].attrs['long_name'] = 'Most unstable CIN' ds['lifted_index'] = li.magnitude ds['lifted_index'].attrs['units'] = t_profile.units ds['lifted_index'].attrs['long_name'] = 'Lifted index' ds['level_of_free_convection'] = lfc.magnitude ds['level_of_free_convection'].attrs['units'] = lfc.units ds['level_of_free_convection'].attrs['long_name'] = 'Level of free convection' ds['lifted_condensation_level_temperature'] = lcl[1].magnitude ds['lifted_condensation_level_temperature'].attrs['units'] = lcl[1].units ds['lifted_condensation_level_temperature'].attrs[ 'long_name' ] = 'Lifted condensation level temperature' ds['lifted_condensation_level_pressure'] = lcl[0].magnitude ds['lifted_condensation_level_pressure'].attrs['units'] = lcl[0].units ds['lifted_condensation_level_pressure'].attrs[ 'long_name' ] = 'Lifted condensation level pressure' return ds
def cape_cin(pressure, temperature, dewpt, parcel_profile, dz, temp): r"""Calculate CAPE and CIN. This script is originally from Metpy module but it was not avaialble in Python 3.6.3 Anaconda version. JLGF. Calculate the convective available potential energy (CAPE) and convective inhibition (CIN) of a given upper air profile and parcel path. CIN is integrated between the surface and LFC, CAPE is integrated between the LFC and EL (or top of sounding). Intersection points of the measured temperature profile and parcel profile are linearly interpolated. Especifically this script has been adapted from :cite:`montearl` and :cite:`molinari2010` which use a very particular function for CAPE. CAPE is not trivially computed from dropsonde measurements and several cautions are extended: 1. Vertical profiles usually do not reach the equilibrium level (EL) but instead are cut-off at 8-9 km. 2. Typical CAPE formula estimates the area of the difference between parcel and environmental profiles, however, this method uses a more robust approach :cite:`bogner2000`. 3. Several corrections would need to be in place for this script to be comparable to other studies (see above). It is then a simple approximation and by no means a complete and thorough algorithm. Parameters ---------- pressure : `pint.Quantity` The atmospheric pressure level(s) of interest. The first entry should be the starting point pressure. temperature : `pint.Quantity` The atmospheric temperature corresponding to pressure. dewpt : `pint.Quantity` The atmospheric dew point corresponding to pressure. parcel_profile : `pint.Quantity` The temperature profile of the parcel Returns ------- `pint.Quantity` Convective available potential energy (CAPE). `pint.Quantity` Convective inhibition (CIN). Notes ----- Formula adopted from :cite:`montearl` .. math:: \text{CAPE} = \int_{LFC}^{EL} g\frac{(T_{v} - T_{ve})}{\overline{T_{ve}}} dz .. math:: \text{CIN} = \int_{SFC}^{LFC} g\frac{(T_{v} - T_{ve})}{\overline{T_{ve}}} dz * :math:`CAPE` Convective available potential energy * :math:`CIN` Convective inhibition * :math:`LFC` Pressure of the level of free convection * :math:`EL` Pressure of the equilibrium level * :math:`SFC` Level of the surface or beginning of parcel path * :math:`g` Gravitational acceleration * :math:`T_{v}` Parcel potential temperature. * :math:`T_{ve}` Environmental potential temperature. * :math:`\overline{T_{ve}}` Mean environmental potential temperature. * :math:`dz` Height array differential. See Also :meth:`toolbox._find_append_zero_crossings`, :meth:`toolbox.parcel_profile` """ # Calculate LFC limit of integration lfc_pressure = mpcalc.lfc(pressure, temperature, dewpt)[0] g = 9.806 * units.m / units.s**2 # If there is no LFC, no need to proceed. if np.isnan(lfc_pressure): return 0 * units('J/kg'), 0 * units('J/kg') else: lfc_pressure = lfc_pressure.magnitude # Calculate the EL limit of integration el_pressure = mpcalc.el(pressure, temperature, dewpt)[0] # No EL and we use the top reading of the sounding. if np.isnan(el_pressure): el_pressure = pressure[-1].magnitude else: el_pressure = el_pressure.magnitude # Difference between the parcel path and measured temperature profiles y = (parcel_profile - temperature).to(units.degK) dzx, yz = _find_append_zero_crossings(np.copy(dz), y) # Estimate zero crossings x, y = _find_append_zero_crossings(np.copy(pressure), y) x = np.flip(x, 0) lfc_height = np.nanmean(dzx[np.isclose(x, lfc_pressure, atol=1.5)]) el_height = np.nanmean(dzx[np.isclose(x, el_pressure, atol=1.5)]) Tv_env = np.nanmean(temperature[np.where((dz > lfc_height) & (dz < el_height))]) * units.degK # CAPE # Only use data between the LFC and EL for calculation p_mask = _less_or_close(x, lfc_pressure) & _greater_or_close( x, el_pressure) z_mask = _less_or_close(dzx, lfc_height) & _greater_or_close( dzx, el_height) x_clipped = dzx[p_mask] y_clipped = yz[p_mask] cape = ((g / Tv_env) * (np.trapz(y_clipped, x_clipped, np.diff(dz)) * units.degK * units.m)).to(units('J/kg')) # CIN # Only use data between the surface and LFC for calculation p_mask = _greater_or_close(x, lfc_pressure) x_clipped = x[p_mask] y_clipped = y[p_mask] cin = ((g / Tv_env) * (np.trapz(y_clipped, x_clipped, np.diff(dz)) * units.degK * units.m)).to(units('J/kg')) return cape, cin