def test_sigtor_scalar(): """Test significant tornado parameter function with a single value.""" sbcape = 4000 * units('J/kg') sblcl = 800 * units('meter') srh1 = 400 * units('m^2/s^2') shr6 = 35 * units('m/s') truth = 10.666667 sigtor = significant_tornado(sbcape, sblcl, srh1, shr6) assert_almost_equal(sigtor, truth, 6)
def test_sigtor(): """Test significant tornado parameter function.""" sbcape = [2000., 2000., 2000., 2000., 3000, 4000] * units('J/kg') sblcl = [3000., 1500., 500., 1500., 1500, 800] * units('meter') srh1 = [200., 200., 200., 200., 300, 400] * units('m^2/s^2') shr6 = [20., 5., 20., 35., 20., 35] * units('m/s') truth = [0., 0, 1.777778, 1.333333, 2., 10.666667] sigtor = significant_tornado(sbcape, sblcl, srh1, shr6) assert_almost_equal(sigtor, truth, 6)
def test_sigtor_scalar(): """Test significant tornado parameter function with a single value.""" sbcape = 4000 * units('J/kg') sblcl = 800 * units('meter') srh1 = 400 * units('m^2/s^2') shr6 = 35 * units('m/s') truth = 10.666667 sigtor = significant_tornado(sbcape, sblcl, srh1, shr6) assert_almost_equal(sigtor, truth, 6)
def test_sigtor(): """Test significant tornado parameter function.""" sbcape = [2000., 2000., 2000., 2000., 3000, 4000] * units('J/kg') sblcl = [3000., 1500., 500., 1500., 1500, 800] * units('meter') srh1 = [200., 200., 200., 200., 300, 400] * units('m^2/s^2') shr6 = [20., 5., 20., 35., 20., 35] * units('m/s') truth = [0., 0, 1.777778, 1.333333, 2., 10.666667] sigtor = significant_tornado(sbcape, sblcl, srh1, shr6) assert_almost_equal(sigtor, truth, 6)
def scalardata(field, valid_time, targetdir=".", debug=False): # Get color map, levels, and netCDF variable name appropriate for requested variable (from fieldinfo module). info = fieldinfo[field] if debug: print("scalardata: found", field, "fieldinfo:", info) cmap = colors.ListedColormap(info['cmap']) levels = info['levels'] fvar = info['fname'][0] # Get narr file and filename. ifile = get(valid_time, targetdir=targetdir, narrtype=info['filename']) if debug: print("About to open " + ifile) nc = xarray.open_dataset(ifile) # Tried to rename vars and dimensions so metpy.parse_cf() would not warn "Found latitude/longitude values, assuming latitude_longitude for projection grid_mapping variable" # It didn't help. Only commenting out the metpy.parse_cf() line helped. # It didn't help with MetpyDeprecationWarning: Multidimensional coordinate lat assigned for axis "y". This behavior has been deprecated and will be removed in v1.0 (only one-dimensional coordinates will be available for the "y" axis) either #nc = nc.rename_vars({"gridlat_221": "lat", "gridlon_221" : "lon"}) #nc = nc.rename_dims({"gridx_221": "x", "gridy_221" : "y"}) #nc = nc.metpy.parse_cf() # TODO: figure out why filled contour didn't have .metpy.parse_cf() if fvar not in nc.variables: print(fvar, "not in", ifile, '. Try', nc.var()) sys.exit(1) # Define data array. Speed and shear derived differently. # Define 'long_name' attribute # if field[0:5] == "speed": u = nc[info['fname'][0]] v = nc[info['fname'][1]] data = u # copy metadata/coordinates from u data.values = wind_speed(u, v) data.attrs['long_name'] = "wind speed" elif field[0:3] == 'shr' and '_' in field: du, dv = shear(field, valid_time=valid_time, targetdir=targetdir, debug=debug) ws = wind_speed(du, dv) attrs = { 'long_name': 'wind shear', 'units': str(ws.units), 'verttitle': du.attrs["verttitle"] } # Use .m magnitude because you can't transfer units of pint quantity to xarray numpy array (xarray.values) data = xarray.DataArray(data=ws.m, dims=du.dims, coords=du.coords, name=field, attrs=attrs) elif field == 'theta2': pres = nc[info['fname'][0]] temp = nc[info['fname'][1]] data = pres # retain xarray metadata/coordinates theta = potential_temperature(pres, temp) data.values = theta data.attrs['units'] = str(theta.units) data.attrs['long_name'] = 'potential temperature' elif field == 'thetae2': pres = nc[info['fname'][0]] temp = nc[info['fname'][1]] dwpt = nc[info['fname'][2]] data = pres # retain xarray metadata/coordinates thetae = equivalent_potential_temperature(pres, temp, dwpt) data.values = thetae data.attrs['units'] = str(thetae.units) data.attrs['long_name'] = 'equivalent potential temperature' elif field == 'scp' or field == 'stp' or field == 'tctp': cape = nc[info['fname'][0]] cin = nc[info['fname'][1]] ifile = get(valid_time, targetdir=targetdir, narrtype=narrFlx) ncFlx = xarray.open_dataset(ifile).metpy.parse_cf() srh = ncFlx[info['fname'][2]] shear_layer = info['fname'][3] bulk_shear = scalardata(shear_layer, valid_time, targetdir=targetdir, debug=debug) lifted_condensation_level_height = scalardata('zlcl', valid_time, targetdir=targetdir, debug=debug) if field == 'scp': # In SPC help, cin is positive in SCP formulation. cin_term = -40 / cin cin_term = cin_term.where(cin < -40, other=1) scp = supercell_composite(cape, srh, bulk_shear) * cin_term.metpy.unit_array attrs = { 'units': str(scp.units), 'long_name': 'supercell composite parameter' } data = xarray.DataArray(data=scp, dims=cape.dims, coords=cape.coords, name=field, attrs=attrs) if field == 'stp': cin_term = (200 + cin) / 150 cin_term = cin_term.where(cin <= -50, other=1) cin_term = cin_term.where(cin >= -200, other=0) # CAPE, srh, bulk_shear, cin may be one vertical level, but LCL may be multiple heights. # xarray.broadcast() makes them all multiple heights with same shape, so significant_tornado doesn't # complain about expecting lat/lon 2 dimensions and getting 3 dimensions.. (cape, lifted_condensation_level_height, srh, bulk_shear, cin_term) = xarray.broadcast(cape, lifted_condensation_level_height, srh, bulk_shear, cin_term) stp = significant_tornado(cape, lifted_condensation_level_height, srh, bulk_shear) * cin_term.metpy.unit_array attrs = { 'units': str(stp.units), 'long_name': 'significant tornado parameter', 'verttitle': lifted_condensation_level_height.attrs['verttitle'] } data = xarray.DataArray(data=stp, dims=cape.dims, coords=cape.coords, name=field, attrs=attrs) if field == 'tctp': tctp = srh / (40 * munits['m**2/s**2']) * bulk_shear / ( 12 * munits['m/s']) * (2000 - lifted_condensation_level_height ) / (1400 * munits.m) # But NARR storm relative helicity (srh) is 0-3 km AGL, while original TCTP expects 0-1 km AGL. # So the shear term is too large using the NARR srh. Normalize the srh term with a larger denominator. # In STP, srh is normalized by 150 m**2/s**2. Use that. tctp_0_3kmsrh = srh / (150 * munits['m**2/s**2']) * bulk_shear / ( 12 * munits['m/s']) * (2000 - lifted_condensation_level_height ) / (1400 * munits.m) attrs = { 'units': 'dimensionless', 'long_name': 'TC tornado parameter' } data = xarray.DataArray(data=tctp_0_3kmsrh, dims=cape.dims, coords=cape.coords, name=field, attrs=attrs) elif field == 'lcl': pres = nc[info['fname'][0]] temp = nc[info['fname'][1]] dwpt = nc[info['fname'][2]] LCL_pressure, LCL_temperature = lcl(pres.fillna(pres.mean()), temp.fillna(temp.mean()), dwpt.fillna(dwpt.mean())) # convert units to string or xarray.DataArray.metpy.unit_array dies with ttributeError: 'NoneType' object has no attribute 'evaluate' attrs = { "long_name": "lifted condensation level", "units": str(LCL_pressure.units), "from": "metpy.calc.lcl" } data = xarray.DataArray(data=LCL_pressure, coords=pres.coords, dims=pres.dims, name='LCL', attrs=attrs) elif field == 'zlcl': LCL_pressure = scalardata('lcl', valid_time, targetdir=targetdir, debug=debug) ifile = get(valid_time, targetdir=targetdir, narrtype=narr3D) nc3D = xarray.open_dataset(ifile).metpy.parse_cf() hgt3D = nc3D["HGT_221_ISBL"] data = pressure_to_height(LCL_pressure, hgt3D, targetdir=targetdir) else: data = nc[fvar] data = units(data, info, debug=debug) data = vertical(data, info, debug=debug) data = temporal(data, info, debug=debug) data.attrs['field'] = field data.attrs['ifile'] = os.path.realpath(ifile) data.attrs['levels'] = levels data.attrs['cmap'] = cmap if data.min() > levels[-1] or data.max() < levels[0]: print('levels', levels, 'out of range of data', data.min(), data.max()) sys.exit(2) return data
def significant_tornado(self): u, v = self.bulk_shear() return mpcalc.significant_tornado( self.cape_cin(0)[0], mpint.log_interpolate_1d(self.lcl(0)[0], self.p, self.z), self.storm_relative_helicity()[2], (u**2 + v**2)**0.5)
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