def add_entropy(ax, pressure, temperature, mixing_ratio, ds=100, linewidth=1.0): "add entropy curves and rescale values to fit in by 0.5*entropy + ds" 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 # 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, q * 0, p) sm1 = entropy(T, q, p) sm2 = entropy(T, qs, p) ax.plot(sd.magnitude * 0.5 + ds, p, '--k') ax.plot(sm1.magnitude * 0.5 + ds, p, '--b') ax.plot(sm2.magnitude * 0.5 + ds, p, '--r')
def calc_q_from_rh(ds): """ Input : ds : Dataset Output : q : Specific humidity values Function to estimate specific humidity from the relative humidity, temperature and pressure in the given dataset. This function uses MetPy's functions to get q: (i) mpcalc.dewpoint_from_relative_humidity() (ii) mpcalc.specific_humidity_from_dewpoint() """ # dp = mpcalc.dewpoint_from_relative_humidity( # dataset.T.values * units.degC, dataset.rh.values / 100, # ).magnitude # q = mpcalc.specific_humidity_from_dewpoint( # dp * units.degC, dataset.p.values * units.hPa # ).magnitude e_s = pp.calc_saturation_pressure(ds.ta.values) w_s = mpcalc.mixing_ratio(e_s * units.Pa, ds.p.values * units.Pa).magnitude w = ds.rh.values * w_s q = w / (1 + w) return q
def add_curves_Wyoming(ax, datetime, station, linewidth=1.0, LH_Tdepend=False): """ overlaying new curves of multiple soundings from Wyoming datasets date: using datetime module. ex. datetime(2018,06,06) station: station name. ex. 'MFL' Miami, Florida """ from siphon.simplewebservice.wyoming import WyomingUpperAir date = datetime station = station df = WyomingUpperAir.request_data(date, station) pressure = df['pressure'].values Temp = df['temperature'].values Temp_dew = df['dewpoint'].values altitude = df['height'].values q = mpcalc.mixing_ratio( mpcalc.saturation_vapor_pressure(Temp_dew * units('degC')), pressure * units('mbar')) q = mpcalc.specific_humidity_from_mixing_ratio(q) qs = mpcalc.mixing_ratio( mpcalc.saturation_vapor_pressure(Temp * units('degC')), pressure * units('mbar')) # specific energies if LH_Tdepend == False: mse = mpcalc.moist_static_energy(altitude * units('meter'), Temp * units('degC'), q) mse_s = mpcalc.moist_static_energy(altitude * units('meter'), Temp * units('degC'), qs) dse = mpcalc.dry_static_energy(altitude * units('meter'), Temp * units('degC')) 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) # adding curves on the main axes ax.plot(dse.magnitude, pressure, 'k', linewidth=linewidth) ax.plot(mse.magnitude, pressure, 'b', linewidth=linewidth) ax.plot(mse_s.magnitude, pressure, 'r', linewidth=linewidth)
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)
def thermo_plots(pressure, temperature, mixing_ratio): """" plots for vertical profiles of temperature, dewpoint, mixing ratio and relative humidity. Parameters ---------- pressure : array-like Atmospheric pressure profile (surface to TOA) temperature: array-like Atmospheric temperature profile (surface to TOA) dewpoint: array-like Atmospheric dewpoint profile (surface to TOA) Returns ------- """ p = pressure * units('mbar') q = mixing_ratio * units('kilogram/kilogram') T = temperature * units('degC') Td = mpcalc.dewpoint_from_specific_humidity(q, T, p) # dewpoint Tp = mpcalc.parcel_profile(p, T[0], Td[0]) # parcel plt.figure(figsize=(12, 5)) lev = find_nearest(p.magnitude, 100) plt.subplot(1, 3, 1) plt.plot(T[:lev], p[:lev], '-ob') plt.plot(Td[:lev], p[:lev], '-og') plt.plot(Tp[:lev], p[:lev], '-or') plt.xlabel('Temperature [C]', fontsize=12) plt.ylabel('Pressure [hpa]', fontsize=12) plt.gca().invert_yaxis() plt.legend(['Temp', 'Temp_Dew', 'Temp_Parcel'], loc=1) plt.grid() qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T), p) # Relative humidity RH = q / qs * 100 # Relative humidity plt.subplot(1, 3, 2) plt.plot(q[:lev], p[:lev], '-og') plt.xlabel('Mixing ratio [kg/kg]', fontsize=12) plt.gca().invert_yaxis() plt.grid() plt.subplot(1, 3, 3) plt.plot(RH[:lev], p[:lev], '-og') plt.xlabel('Relative humiduty [%]', fontsize=12) plt.gca().invert_yaxis() plt.grid() plt.tight_layout() return (plt)
def calc_rh_from_q(dataset, T=None): """ Input : dataset : Dataset T : Temperature values estimated from interpolated potential_temperature; if not specified, function will calculate this from given dataset using calc_T_from_theta() Output : rh : Relative humidity values Function to estimate relative humidity from specific humidity, temperature and pressure in the given dataset. This function uses MetPy's functions to get rh: (i) mpcalc.relative_humidity_from_specific_humidity() """ if T is None: T = calc_T_from_theta(dataset) # rh = mpcalc.relative_humidity_from_specific_humidity( # dataset.q.values, T * units.degC, dataset.p.values * units.hPa, # ).magnitude w = dataset.q / (1 - dataset.q) e_s = pp.calc_saturation_pressure(dataset.ta.values) w_s = mpcalc.mixing_ratio(e_s * units.Pa, dataset.p.values * units.Pa).magnitude rh = w / w_s return rh
def main(args={}): """ Main function """ try: args = get_args() except: sys.exit() setup_logging(args['verbose']) logging.debug('Gathering version information') try: __version__ = eurec4a_snd.__version__ package_version_set = True except (ModuleNotFoundError, AttributeError): logging.debug('No eurec4a_snd package version found') __version__ = 'see git_version' package_version_set = False try: git_module_version = sp.check_output( ["git", "describe", "--always", "--dirty"], stderr=sp.STDOUT).strip().decode() git_version_set = True except (sp.CalledProcessError, FileNotFoundError): logging.debug('No git-version could be found.') git_module_version = "--" git_version_set = False if (not git_version_set and not package_version_set): logging.warning('No version of the converter could be found!' ' Please consider the installation via conda' ' or if this is not working clone the git re' 'pository') logging.info('Version of script: {} (conda package), {} (git version)'.format(__version__, git_module_version)) logging.debug('Create filelist') if args['inputfile'] is None: filelist = glob.glob(args['inputpath']+'*.nc') else: filelist = glob.glob(args['inputfile']) filelist = sorted(filelist) # Create outputfolder if necessary if not os.path.exists(args['outputfolder']): os.makedirs(args['outputfolder']) for f, file in enumerate(tqdm.tqdm(filelist)): logging.info(f'Process file {file}') ds = xr.open_dataset(file, use_cftime=False) ds = ds.isel({'sounding': 0}) ds_input = ds.copy() # Check monotonic ascent/descent if np.all(np.diff(ds.isel(level=slice(20,-1)).altitude.values) > 0) or np.all(np.diff(ds.isel(level=slice(20,-1)).altitude.values) < 0): logging.debug('Sounding is monotonic ascending/descending') else: logging.warning('Sounding is not monotonic ascending/descending. The ascent rate will be artificial') # Remove standard pressure levels (extendedVerticalSoundingSignificance) # extendedVerticalSoundingSignificance == 65536 non_std_level_mask = ~np.isin(ds.pressure, std_pressures) ds = ds.isel({'level': non_std_level_mask}) # extendedVerticalSoundingSignificance == 18432 non_nan_altitude = ~np.isnan(ds.altitude.values) ds = ds.isel({'level': non_nan_altitude}) # Geopotential height issue # the geopotential height is not a measured coordinate and # the same height can occur at different pressure levels # here the first occurrence is used _, uniq_altitude_idx = np.unique(ds.altitude.values, return_index=True) ds = ds.isel({'level': uniq_altitude_idx}) # Check if platform is known # if ds.platform not in platform_rename_dict.keys(): # logging.error('The platform {} is not known. Please choose one of {}'.format(ds.platform, platform_rename_dict.keys())) # sys.exit() # Consistent platform test if f == 0: platform = ds.platform else: assert ds.platform == platform, 'The platform seems to change from {} to {}'.format(platform, ds.platform) # Unique levels test if len(ds.altitude) != len(np.unique(ds.altitude)): logging.warning('Altitude levels are not unique of {}'.format(file)) break # Prepare some data that cannot be linearly interpolated logging.debug('Calculate wind components') u, v = get_wind_components(ds.windDirection.values, ds.windSpeed.values) ds['wind_u'] = xr.DataArray(u, dims=['level']) ds['wind_v'] = xr.DataArray(v, dims=['level']) if 'altitude_WGS84' in ds.keys(): logging.debug('Calculate WGS84 altitude') logging.warning('Caution: This feature is under development. Especially, the correct averaging of the ' 'position at the 0 meridian and close to the poles is not yet implemented.') # Convert lat, lon, alt to cartesian coordinates ecef = pyproj.Proj(proj='geocent', ellps='WGS84', datum='WGS84') lla = pyproj.Proj(proj='latlong', ellps='WGS84', datum='WGS84') x, y, z = pyproj.transform(lla, ecef, ds.longitude.values, ds.latitude.values, ds.altitude_WGS84.values, radians=False) variables_dict.update({'x':'x', 'y':'y', 'z':'z'}) for var, val in {'x':x, 'y':y, 'z':z}.items(): ds[var] = xr.DataArray(val, dims=['level']) else: logging.warning('No WGS84 altitude could be found.') logging.debug('Humidity calculations to get q') theta = calc_theta_from_T(ds['temperature'].values, ds['pressure'].values) e_s = calc_saturation_pressure(ds['temperature'].values + 273.15) w_s = mpcalc.mixing_ratio(e_s * units.Pa, ds['pressure'].values*units.hPa).magnitude w = ds['humidity'].values/100.*w_s q = w/(1+w) da_w = xr.DataArray([w*1000], dims=['sounding', 'altitude'], coords={'altitude': ds.altitude.values}) da_theta = xr.DataArray([theta], dims=['sounding', 'altitude'], coords={'altitude': ds.altitude.values}) da_q = xr.DataArray([q*1000], dims=['sounding', 'altitude'], coords={'altitude': ds.altitude.values}) ds_new = xr.Dataset() for variable_name_in, variable_name_out in variables_dict.items(): try: ds_new[variable_name_out] = xr.DataArray([ds[variable_name_in].values], dims=['sounding', 'altitude'], coords={'altitude': ds.altitude.values} ) except (KeyError, ValueError): ds_new[variable_name_out] = xr.DataArray([ds[variable_name_in].values], dims=['sounding'] ) ds_new[variable_name_out].attrs = ds[variable_name_in].attrs # Copy attributes from input ds_new['mixing_ratio'] = da_w ds_new['theta'] = da_theta ds_new['specific_humidity'] = da_q flight_time_unix = ds_new.flight_time.astype(float)/1e9 ds_new['flight_time'].values = flight_time_unix # Interpolation logging.debug("Starting interpolation") if args['method'] == 'linear': ds_new = ds_new.dropna(dim='altitude', subset=output_variables, how='any') ds_interp = ds_new.interp(altitude=interpolation_grid) elif args['method'] == 'bin': ds_interp = ds_new.groupby_bins('altitude', interpolation_bins, labels=interpolation_grid, restore_coord_dims=True).mean() ds_interp = ds_interp.transpose() ds_interp = ds_interp.rename({'altitude_bins':'altitude'}) # Create bounds variable ds_interp['alt_bnds'] = xr.DataArray(np.array([interpolation_bins[:-1],interpolation_bins[1:]]).T, dims=['altitude','nv'], coords={'altitude': ds_interp.altitude.values} ) ds_interp['launch_time'] = ds_new['launch_time'] ## Interpolation NaN logging.debug('Interpolate NaN') ds_interp = ds_interp.interpolate_na('altitude', max_gap=max_gap_fill, use_coordinate=True) dims_2d = ['sounding', 'altitude'] coords_1d = {'altitude': ds_interp.altitude.values} wind_u = ds_interp.isel({'sounding': 0})['wind_u'] wind_v = ds_interp.isel({'sounding': 0})['wind_v'] dir, wsp = get_directional_wind(wind_u, wind_v) ds_interp['wind_direction'] = xr.DataArray([np.array(dir)], dims=dims_2d, coords=coords_1d) ds_interp['wind_speed'] = xr.DataArray([np.array(wsp)], dims=dims_2d, coords=coords_1d) if 'altitude_WGS84' in ds.keys(): lon, lat, alt = pyproj.transform(ecef, lla, ds_interp['x'].values[0], ds_interp['y'].values[0], ds_interp['z'].values[0], radians=False) for var, val in {'latitude':lat, 'longitude':lon, 'altitude_WGS84': alt}.items(): ds_interp[var] = xr.DataArray([val], dims=dims_2d, coords=coords_1d) del ds_interp['x'] del ds_interp['y'] del ds_interp['z'] del ds_interp['altitude_WGS84'] ds_input = ds_input.sortby('altitude') ds_input.altitude.load() ds_input.pressure.load() logging.debug("Pressure interpolation") interp_pres = pressure_interpolation(ds.pressure.values, ds.altitude.values, ds_interp.altitude.values) ds_interp['pressure'] = xr.DataArray([np.array(interp_pres)], dims=dims_2d, coords=coords_1d) ds_interp['launch_time'] = xr.DataArray([ds_interp.isel({'sounding': 0}).launch_time.item()/1e9], dims=['sounding']) ds_interp['platform'] = xr.DataArray([platform_number_dict[platform]], dims=['sounding']) # Calculations after interpolation logging.debug("Starting calculations after interpolation") # Recalculate temperature and relative humidity from theta and q temperature = calc_T_from_theta(ds_interp.isel(sounding=0)['theta'].values, ds_interp.isel(sounding=0)['pressure'].values) ds_interp['temperature'] = xr.DataArray([np.array(temperature)], dims=dims_2d, coords=coords_1d) w = (ds_interp.isel(sounding=0)['specific_humidity'].values/1000)/(1-ds_interp.isel(sounding=0)['specific_humidity'].values/1000.) e_s = calc_saturation_pressure(ds_interp.isel(sounding=0)['temperature'].values+273.15) w_s = mpcalc.mixing_ratio(e_s*units.Pa, ds_interp.isel(sounding=0)['pressure'].values*units.hPa).magnitude relative_humidity = w/w_s*100 ds_interp['relative_humidity'] = xr.DataArray([np.array(relative_humidity)], dims=dims_2d, coords=coords_1d) # Count number of measurements within each bin ds_interp['N_ptu'] = xr.DataArray( ds_new.pressure.groupby_bins('altitude', interpolation_bins, labels=interpolation_grid, restore_coord_dims=True).count().values, dims=dims_2d, coords=coords_1d) ds_interp['N_gps'] = xr.DataArray( ds_new.latitude.groupby_bins('altitude', interpolation_bins, labels=interpolation_grid, restore_coord_dims=True).count().values, dims=dims_2d, coords=coords_1d) # Cell method used data_exists = np.where(np.isnan(ds_interp.pressure), False, True) data_mean = np.where(np.isnan(ds_interp.N_ptu), False, True) # no data or interp: nan; mean > 0 data_method = np.zeros_like(data_exists, dtype='uint') data_method[np.logical_and(data_exists, data_mean)] = 2 data_method[np.logical_and(data_exists, ~data_mean)] = 1 ds_interp['m_ptu'] = xr.DataArray(data_method, dims=dims_2d, coords=coords_1d) ds_interp['N_ptu'].values[np.logical_and(~data_mean, data_method > 0)] = 0 data_exists = np.where(np.isnan(ds_interp.latitude), False, True) data_mean = np.where(np.isnan(ds_interp.N_gps), False, True) # no data or interp: nan; mean > 0 data_method = np.zeros_like(data_exists, dtype='uint') data_method[np.logical_and(data_exists, data_mean)] = 2 data_method[np.logical_and(data_exists, ~data_mean)] = 1 ds_interp['m_gps'] = xr.DataArray(data_method, dims=dims_2d, coords=coords_1d) ds_interp['N_gps'].values[np.logical_and(~data_mean, data_method > 0)] = 0 direction = get_direction(ds_interp, ds) if direction == 'ascending': ds_interp['ascent_flag'] = xr.DataArray([1], dims=['sounding']) else: ds_interp['ascent_flag'] = xr.DataArray([0], dims=['sounding']) # Copy trajectory id from level1 dataset ds_interp['sounding'] = xr.DataArray([ds['sounding'].values], dims=['sounding']) ds_interp.sounding.attrs = ds['sounding'].attrs script_basename = os.path.basename(__file__) script_modification_time = time.ctime(os.path.getmtime(os.path.realpath(__file__))) glob_attrs_dict = {'title': 'EUREC4A interpolated sounding data', 'platform': platform, 'surface_altitude': ds.attrs['surface_altitude'], 'instrument': ds.instrument, 'doi': 'pending', 'created_with': '{file} with its last modifications on {time}'. format(time=script_modification_time, file=script_basename), 'git_version': git_module_version, 'python_version': "{} (with numpy:{}, netCDF4:{}, eurec4a_snd:{})". format(sys.version, np.__version__, netCDF4.__version__, __version__), 'created_on': str(time.ctime(time.time())), 'featureType': 'trajectory', 'Conventions': 'CF-1.7' } # Overwrite standard attrs with those defined in config file # Get global meta data from mwx_config.json level='L2' glob_attrs_dict2 = get_global_attrs(json_config_fn, f'{ds.campaign_id}_{ds.platform}_{ds.instrument_id}_{level}') for attrs, value in glob_attrs_dict2.items(): glob_attrs_dict[attrs] = value import pandas as pd def convert_num2_date_with_nan(num, format): if not np.isnan(num): return num2date(num, format, only_use_python_datetimes=True, only_use_cftime_datetimes=False) else: return pd.NaT convert_nums2date = np.vectorize(convert_num2_date_with_nan) ds_interp['flight_time'].data = convert_nums2date(ds_interp.flight_time.data, "seconds since 1970-01-01") ds_interp['launch_time'].data = convert_nums2date(ds_interp.launch_time.data, "seconds since 1970-01-01") for variable in ['temperature', 'dew_point', 'wind_speed', 'pressure', 'wind_u', 'wind_v', 'latitude', 'longitude', 'mixing_ratio', 'wind_direction', 'specific_humidity', 'relative_humidity', 'ascent_rate' ]: # ds_interp[variable].values = np.round(ds_interp[variable].values, 2) ds_interp[variable].encoding['dtype'] = 'f4' ds_interp['ascent_flag'].encoding['dtype'] = 'int16' ds_interp['platform'].encoding['dtype'] = 'int16' ds_interp['m_gps'].encoding['dtype'] = 'int16' ds_interp['m_ptu'].encoding['dtype'] = 'int16' ds_interp['N_gps'].encoding['dtype'] = 'f4' ds_interp['N_ptu'].encoding['dtype'] = 'f4' ds_interp['alt_bnds'].encoding['dtype'] = 'int16' ds_interp['altitude'].encoding['dtype'] = 'int16' ds_interp.sounding.encoding = {'dtype': 'str'} ds_interp = set_global_attributes(ds_interp, glob_attrs_dict) ds_interp = set_additional_var_attributes(ds_interp, meta_data_dict) # Transpose dataset if necessary for variable in ds_interp.data_vars: if variable == 'alt_bnds': continue dims = ds_interp[variable].dims if (len(dims) == 2) and (dims[0] != 'sounding'): ds_interp[variable] = ds_interp[variable].T time_dt = pd.Timestamp(np.datetime64(ds_interp.isel({'sounding': 0}).launch_time.data.astype("<M8[ns]"))) time_fmt = time_dt.strftime('%Y%m%dT%H%M') platform_filename = platform # platform_rename_dict[platform] outfile = args['outputfolder']+'{campaign}_{platform}_{instrument_id}_{level}_{date}_{version}.nc'.format(campaign=ds.campaign_id, platform=platform_filename, instrument_id=ds.instrument_id, level=level, date=time_fmt, version=git_module_version ) logging.info('Write output to {}'.format(outfile)) write_dataset(ds_interp, outfile)
time = sorted(time) print(time) temp = data.variables['air_temperature'][:] * units('degC') dewp = data.variables['dew_point_temperature'][:] * units('degC') slp = data.variables['inches_ALTIM'][:] * units('inHg') wspd = data.variables['wind_speed'][:] * units('m/s') wdir = data.variables['wind_from_direction'][:] * units('degree') ######################################## # Use MetPy Calculations to calculate RH # -------------------------------------- # Get ambient partial pressure, use to calculate mixing ratio es = mpcalc.saturation_vapor_pressure(dewp) mixr = mpcalc.mixing_ratio(es, slp) # Calculate vapor pressure vp = mpcalc.vapor_pressure(slp, mixr) # Calculate saturation vapor pressure svp = mpcalc.saturation_vapor_pressure(temp) # Calculate relative humidity as a percentage rh = (vp / svp) * 100 ######################################## # Make Meteogram Plot # -------------------
'potential_T'] = mpcalc.potential_temperature(p, T) df_selected_time['potential_T_C'] = df_selected_time[ 'potential_T'].values - 273.15 df_selected_time[ 'saturation_vaper_pressure'] = mpcalc.saturation_vapor_pressure( T) df_selected_time[ 'vaper_pressure'] = mpcalc.saturation_vapor_pressure( Td) VPS = df_selected_time[ 'saturation_vaper_pressure'].values * units.hPa VP = df_selected_time['vaper_pressure'].values * units.hPa df_selected_time[ 'saturation_mixing_ratio'] = mpcalc.mixing_ratio( VPS, p) df_selected_time['mixing_ratio'] = mpcalc.mixing_ratio( VP, p) MRS = df_selected_time[ 'saturation_mixing_ratio'].values * units('g/kg') MR = df_selected_time['mixing_ratio'].values * units( 'g/kg') # Calculate RH #RH = mpcalc.relative_humidity_from_dewpoint(T[0], Td[0]) df_selected_time[ 'RH'] = mpcalc.relative_humidity_from_dewpoint(T, Td) df_selected_time[ 'RH_MR'] = mpcalc.relative_humidity_from_mixing_ratio( MR, T, p)
def test_mixing_ratio(): """Test mixing ratio calculation.""" p = 998. * units.mbar e = 73.75 * units.mbar assert_almost_equal(mixing_ratio(e, p), 0.04963, 2)
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 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 EnergyMassPlot(pressure, temperature, dewpoint, height, uwind, vwind, sphum=None, rh=None, label='', size=(12,10), return_fig=False): p=pressure Z=height T=temperature Td=dewpoint if isinstance(sphum,np.ndarray) and isinstance(rh,np.ndarray): q=sphum qs=q/rh else: q = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(Td),p) qs= mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T),p) s = g*Z + Cp_d*T sv= g*Z + Cp_d*mpcalc.virtual_temperature(T,q) h = s + Lv*q hs= s + Lv*qs parcel_Tprofile = mpcalc.parcel_profile(p, T[0], Td[0]).to('degC') CAPE,CIN = mpcalc.cape_cin(p,T,Td,parcel_Tprofile) ELp,ELT = mpcalc.el(p,T,Td) ELindex = np.argmin(np.abs(p - ELp)) lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) p_PWtop = max(200*units.mbar, min(p) +1*units.mbar) PW = mpcalc.precipitable_water(Td,p, top=p_PWtop) PWs = mpcalc.precipitable_water(T,p, top=p_PWtop) CRH = (PW/PWs).magnitude *100. fig,ax=setup_fig(size=size,label=label) ax.plot(s /1000, p, color='r', linewidth=1.5) ### /1000 for kJ/kg ax.plot(sv /1000, p, color='r', linestyle='-.') ax.plot(h /1000, p, color='b', linewidth=1.5) ax.plot(hs /1000, p, color='r', linewidth=1.5) ### RH rulings between s and h lines: annotate near 800 hPa level annot_level = 800 #hPa idx = np.argmin(np.abs(p - annot_level *units.hPa)) right_annot_loc = 380 for iRH in np.arange(10,100,10): ax.plot( (s+ Lv*qs*iRH/100.)/1000, p, linewidth=0.5, linestyle=':', color='k') ax.annotate(str(iRH), xy=( (s[idx]+Lv*qs[idx]*iRH/100.)/1000, annot_level), horizontalalignment='center',fontsize=6) ax.annotate('RH (%)', xy=(right_annot_loc, annot_level), fontsize=10) if not np.isnan(CAPE.magnitude) and CAPE.magnitude >10: parcelh = h [0] # for a layer mean: np.mean(h[idx1:idx2]) parcelsv = sv[0] parcelp0 = p[0] # Undilute parcel ax.plot( (1,1)*parcelh/1000., (1,0)*parcelp0, linewidth=0.5, color='g') maxbindex = np.argmax(parcel_Tprofile - T) ax.annotate('CAPE='+str(int(CAPE.magnitude)), xy=(parcelh/1000., p[maxbindex]), color='g') # Plot LCL at saturation point, above the lifted sv of the surface parcel lcl_pressure, lcl_temperature = mpcalc.lcl(p[0], T[0], Td[0]) ax.annotate('LCL', xy=(sv[0]/1000., lcl_pressure), fontsize=10, color='g', horizontalalignment='right') # Purple fill for negative buoyancy below LCL: ax.fill_betweenx(p, sv/1000., parcelsv/1000., where=p>lcl_pressure, facecolor='purple', alpha=0.4) # Positive moist convective buoyancy in green # Above LCL: ax.fill_betweenx(p, hs/1000., parcelh/1000., where= parcelh>hs, facecolor='g', alpha=0.4) # Depict Entraining parcels # Parcel mass solves dM/dz = eps*M, solution is M = exp(eps*Z) # M=1 at ground without loss of generality entrainment_distance = 10000., 5000., 2000. ax.annotate('entrain: 10,5,2 km', xy=(parcelh/1000, 140), color='g', fontsize=8, horizontalalignment='right') ax.annotate('parcel h', xy=(parcelh/1000, 120), color='g', fontsize=10, horizontalalignment='right') for ED in entrainment_distance: eps = 1.0 / (ED*units.meter) M = np.exp(eps * (Z-Z[0]).to('m')).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*h) / np.cumsum(dM) ax.plot( hent[0:ELindex+3]/1000., p[0:ELindex+3], linewidth=0.5, color='g') ### Internal waves (100m adiabatic displacements, assumed adiabatic: conserves s, sv, h). dZ = 100 *units.meter # 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)) # dT: Dry lapse rate dT/dz_dry is -g/Cp dT = (-g/Cp_d *dZ).to('kelvin') Tdisp = T[idx].to('kelvin') + dT # dp: hydrostatic rho = (p[idx]/Rd/T[idx]) dp = -rho*g*dZ # dhsat #qs = mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(T) ,p) 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( (hs[idx]+dhs*(-1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.plot( (s [idx] *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.plot( (h [idx] *( 1,1))/1000, p[idx]+dp*(-1,1), linewidth=3, color='b') # annotation to explain it if ilev == 600*ilev.units: ax.plot(right_annot_loc*hs.units +dhs*(-1,1)/1000, p[idx]+dp*(-1,1), linewidth=3, color='r') ax.annotate('+/- 100m', xy=(right_annot_loc,600), fontsize=8) ax.annotate(' internal', xy=(right_annot_loc,630), fontsize=8) ax.annotate(' waves', xy=(right_annot_loc,660), fontsize=8) ### Blue fill proportional to precipitable water, and blue annotation ax.fill_betweenx(p, s/1000., h/1000., where=h > s, facecolor='b', alpha=0.4) # Have to specify the top of the PW integral. # I want whole atmosphere of course, but 200 hPa captures it all really. #import metpy.calc as mpcalc p_PWtop = max(200*units.mbar, min(p) +1*units.mbar) PW = mpcalc.precipitable_water(Td,p, top=p_PWtop) PWs = mpcalc.precipitable_water(T,p, top=p_PWtop) CRH = (PW/PWs).magnitude *100. # PW annotation arrow tip at 700 mb idx = np.argmin(np.abs(p - 700*p.units)) centerblue = (s[idx]+h[idx])/2.0 /1000. ax.annotate('CWV='+str(round(PW.to('mm').magnitude, 1))+'mm', xy=(centerblue, 700), xytext=(285, 200), color='blue', fontsize=15, arrowprops=dict(width = 1, edgecolor='blue', shrink=0.02), ) ax.annotate('(' + str(round(CRH,1)) +'% of sat)', xy=(285, 230), color='blue', fontsize=12) ### Surface water values at 1C intervals, for eyeballing surface fluxes sT = np.trunc(T[0].to('degC')) sTint = int(sT.magnitude) for idT in [-2,0,2,4]: ssTint = sTint + idT # UNITLESS degC integers, for labels # Kelvin values for computations ssTC = ssTint * units.degC ssTK = ssTC.to('kelvin') ss = g*Z[0] + Cp_d*ssTK hs = ss + Lv*mpcalc.mixing_ratio(mpcalc.saturation_vapor_pressure(ssTK) ,p[0]) ax.annotate(str(ssTint), xy=(ss/1000., p[0]+0*p.units), verticalalignment='top', horizontalalignment='center', color='red', fontsize=7) ax.annotate(str(ssTint), xy=(hs/1000., p[0]+0*p.units), verticalalignment='top', horizontalalignment='center', color='red', fontsize=9) ax.annotate('\u00b0C water', xy=(right_annot_loc, p[0]), verticalalignment='top', fontsize=10, color='r') if return_fig: return ax,fig
def test_mixing_ratio_dimensions(): """Verify mixing ratio returns a dimensionless number.""" p = 998. * units.mbar e = 73.75 * units.hPa assert str(mixing_ratio(e, p).units) == 'dimensionless'
def plot_skewt(snd: Sounding, save_to: Optional[str] = None, p_top: int = 100): """ Plots a skew-T from the given Sounding. :param snd: The Sounding. :param save_to: Where to save the figure. Default None, which does not save the figure, and instead shows it. :param p_top: Pressure at the top of the skew-T. If you change this, Metpy might change the rotation of the isotherms. No known fix yet. :return: None. """ #################################################################################################################### # Data extraction and masking # Extract data from sounding. p = snd.p T = snd.T Td = snd.Td Tw = snd.Tw Te = snd.thetaE z = snd.z cf = snd["CFRL"] omega = snd.omega u, v = snd.wind_components() e = mpcalc.saturation_vapor_pressure(T) rv = mpcalc.mixing_ratio(e, p) w = mpcalc.vertical_velocity(omega, p, T, rv).to("cm/s") # Create masks to filter what data is plotted. mask_dewpoint = Td > -9000. * units.degC # Plot only non-missing dewpoints. mask_wetbulb = Tw > -9000. * units.degC # Plot only non-missing dewpoints. mask_thetae = Te > 0. * units.K # Plot only non-missing theta-es. mask_barbs = p > p_top * units.hPa # Plot only winds below the top of the sounding. #################################################################################################################### # Define intervals of height for coloring barbs and hodograph. z_interval_levels = [1500, 3000, 6000, 9000, 12000, 99999] z_interval_colors = ["red", "orange", "green", "blue", "purple", "grey"] z_colors = [] for item in z: for color, interval in zip(z_interval_colors, z_interval_levels): if item <= interval * units.meter: z_colors.append(color) break #################################################################################################################### # Plotting skew-T fig = plt.figure(figsize=(11, 11)) ax_hodo = fig.add_axes([0.70, 0.675, 0.25, 0.25]) ax_thte = fig.add_axes([0.70, 0.375, 0.25, 0.25]) skew = SkewT(fig, rotation=45, rect=[0.05, 0.05, 0.60, 0.9]) # Plot temperature, dewpoint, and wet-bulb. skew.plot(p, T, 'r') skew.plot(p[mask_dewpoint], Td[mask_dewpoint], 'g') skew.plot(p[mask_wetbulb], Tw[mask_wetbulb], color='#009999', linewidth=1) # Calculate and plot surface parcel trace. sfc_trace = snd.parcel_trace(0).to('degC') sfc_trace_plot = skew.plot(p, sfc_trace, c='orange', linewidth=2, zorder=-10) # Calculate and plot MU parcel trace. mu_level_index = np.argmax(Te[p > 750. * units.hPa]) mu_trace = snd.parcel_trace(mu_level_index).to('degC') mu_trace_plot = skew.plot(p[mu_level_index:], mu_trace, c='gray', linewidth=2, zorder=-9) # Plot each barb individually for control over color. Unfortunately, the c arg of plot_barbs doesn't work for this # purpose. for p_, u_, v_, c_ in zip(p[mask_barbs][::BARB_DENSITY], u[mask_barbs][::BARB_DENSITY], v[mask_barbs][::BARB_DENSITY], np.array(z_colors)[mask_barbs][::BARB_DENSITY]): skew.plot_barbs(p_, u_, v_, y_clip_radius=0.03, barbcolor=c_) #################################################################################################################### # Cloud fraction and omega zero_line = 1 / 15 cf_plot = (cf * zero_line) / 100 w_plot = (w.magnitude / 20) + zero_line skew.ax.plot(np.zeros(cf_plot.shape) + 1 / 15, snd.p, transform=skew.ax.get_yaxis_transform(), color="grey") skew.ax.plot(cf_plot, snd.p, transform=skew.ax.get_yaxis_transform(), color="black") skew.ax.plot(w_plot, snd.p, transform=skew.ax.get_yaxis_transform(), color="purple") skew.ax.text(np.max(w_plot), snd.p[np.argmax(w_plot)], " {:.1f}".format(np.max(w.magnitude)), color="purple", ha="left", va="center", transform=skew.ax.get_yaxis_transform()) skew.ax.text(max(np.min(w_plot), 0), snd.p[np.argmin(w_plot)], " {:.1f}".format(np.min(w.magnitude)), color="purple", ha="left", va="center", transform=skew.ax.get_yaxis_transform()) # skew.ax.fill_betweenx(snd.p, cloud_fractions, np.zeros(cloud_fractions.shape)) #################################################################################################################### # Tweaking skew.ax.set_xlim(-30, 40) skew.ax.set_ylim(1020, p_top) skew.ax.set_xlabel("") skew.ax.set_ylabel("") # Add legend for the parcel traces. skew.ax.legend(handles=[ mlines.Line2D([], [], color='orange', label='Surface parcel'), mlines.Line2D([], [], color='gray', label=r"Max $\theta_e$ below 750mb"), mlines.Line2D([], [], color='black', label=r"Cloud fraction"), mlines.Line2D([], [], color='purple', label=r"Vertical velocity (cm/s)"), ], loc="upper center") # Add adiabats and isohumes. skew.plot_dry_adiabats(t0=np.arange(233, 533, 10) * units.K, alpha=0.25, color='orangered') skew.plot_moist_adiabats(t0=np.arange(233, 400, 5) * units.K, alpha=0.25, color='tab:green') # Reshape required as a quirk of metpy. skew.plot_mixing_lines(w=np.array( [1, 2, 3, 4, 6, 8, 10, 12, 16, 20, 24, 28, 36]).reshape(-1, 1) / 1000., p=np.arange(1000, 99, -100) * units.hPa, linestyle='dotted', color='tab:blue') plt.title('RAP sounding at {}'.format(snd.params["STID"]), loc='left') plt.title('{:.0f}-hour forecast valid at {}'.format( snd.params["STIM"], snd.params["TIME"]), loc='right') #################################################################################################################### # Theta-E plot # Set up axis for theta-e plot. ax_thte.plot(Te[mask_thetae], p[mask_thetae]) ax_thte.set_xlim(300, 360) ax_thte.set_ylim(1020, p_top) ax_thte.set_yscale("log") ax_thte.set_yticks(np.arange(p_top, 1001, 100)) ax_thte.set_yticklabels(np.arange(p_top, 1001, 100)) ax_thte.set_xlabel("") ax_thte.grid(axis="both") plt.text(0.5, 0.9, "Theta-E (Kelvins)", ha="center", va="center", transform=ax_thte.transAxes) #################################################################################################################### # Hodograph # Set up axis for hodograph. h = Hodograph(ax_hodo, component_range=100) h.add_grid(20) # Plot each segment individually for control over color, reversed so that the full hodograph is plotted first, # followed by all but the last segment, etc. Unfortunately, the plot_colormapped() function doesn't work for this # purpose. for color, interval in zip(reversed(z_interval_colors), reversed(z_interval_levels)): mask = z < interval * units.meter h.plot(u.magnitude[mask], v.magnitude[mask], c=color) for vector in snd.bunkers_storm_motion(): h.plot(vector[0], vector[1], c="black", markersize=3, marker="o") ax_hodo.set_xticks([]) ax_hodo.set_yticks([]) ax_hodo.set_xlim(-60, 100) ax_hodo.set_ylim(-60, 100) plt.text(0.1, 0.9, "Velocity (knots)", ha="left", va="center", transform=ax_hodo.transAxes) for a in range(20, 61, 20): ax_hodo.text(-a * 0.71, -a * 0.71, a, ha="center", va="center") ######################################################################################################################## parameter_names = ["SB STP", "0-1 SRH", "SB CAPE", "SB CIN"] parameters = [ snd.significant_tornado()[0], snd.storm_relative_helicity()[2], snd.cape_cin(0)[0], snd.cape_cin(0)[1] ] for name, value, i in zip(parameter_names, parameters, range(len(parameters))): s = "{:15} {:10.3f}".format(name, value.magnitude) fig.text(0.70, 0.32 - (0.02 * i), s, ha="left", va="top", family="monospace", transform=fig.transFigure) ######################################################################################################################## if save_to is None: plt.show() else: plt.savefig(save_to) plt.close()
def f(fullname, selected_time): fullname_el = fullname.split('/') #fullname_el = fullname.split('\\') filename = fullname_el[-1] filename_el = filename.split('_') save_base_dir_name = '../1data/' save_dir_name = '{0}{1}/'.format(save_base_dir_name + dir_name, obs_year) print('site : {0}'.format(dir_name)) if os.path.isfile('{0}{1}_{2}_student.csv'\ .format(save_dir_name, filename_el[-5], selected_time[:13]))\ and os.path.isfile('{0}{1}_{2}_solution.csv'\ .format(save_dir_name, filename_el[-5], selected_time[:13])): write_log(log_file, '{3} ::: {0}{1}_{2} files are already exist'\ .format(save_dir_name, filename_el[-5], selected_time[:13], datetime.now())) else: try: f = lambda s: selected_time in s ids = df['time'].apply(f) df_selected_time = df[ids] df_selected_time = df_selected_time.sort_values('pressure', ascending=False) print('filename : {0}'.format(fullname)) print('df_selected_time.\n{0}'.format(df_selected_time)) df_selected_time.to_csv(r'{0}{1}_{2}_student.csv'\ .format(save_dir_name, filename_el[-5], selected_time[:13])) ################################################################################# ### We will pull the data out of the example dataset into individual variables and ### assign units. ################################################################################# p = df_selected_time['pressure'].values * units.hPa T = df_selected_time['temperature'].values * units.degC Td = df_selected_time['dewpoint'].values * units.degC wind_speed = df_selected_time['speed'].values * units.knots wind_dir = df_selected_time['direction'].values * units.degrees u, v = mpcalc.wind_components(wind_speed, wind_dir) # Calculate web bulb temperature df_selected_time['wet_bulb_T'] = mpcalc.wet_bulb_temperature( p, T, Td) # Calculate potential temperature df_selected_time['potential_T'] = mpcalc.potential_temperature( p, T) df_selected_time['potential_T_C'] = df_selected_time[ 'potential_T'].values - 273.15 # Calculate saturation vaper pressure df_selected_time[ 'saturation_vaper_pressure'] = mpcalc.saturation_vapor_pressure( T) df_selected_time[ 'vaper_pressure'] = mpcalc.saturation_vapor_pressure(Td) SVP = df_selected_time[ 'saturation_vaper_pressure'].values * units.hPa VP = df_selected_time['vaper_pressure'].values * units.hPa # Calculate mixing ratio df_selected_time['saturation_mixing_ratio'] = mpcalc.mixing_ratio( SVP, p) df_selected_time['mixing_ratio'] = mpcalc.mixing_ratio(VP, p) SMR = df_selected_time['saturation_mixing_ratio'].values * units( 'g/kg') MR = df_selected_time['mixing_ratio'].values * units('g/kg') # Calculate relative humidity df_selected_time['relative_humidity_from_dewpoint'] \ = mpcalc.relative_humidity_from_dewpoint(T, Td) df_selected_time['relative_humidity_from_mixing_ratio'] \ = mpcalc.relative_humidity_from_mixing_ratio(MR, T, p) # Calculate virtual temperature df_selected_time['virtual_temperature'] \ = mpcalc.virtual_temperature(T, MR) # Calculate virtual potential temperature df_selected_time['virtual_potential_temperature'] \ = mpcalc.virtual_potential_temperature(p, T, MR) print('df_selected_time after drop nan.\n{0}'.format( df_selected_time)) df_selected_time.to_csv(r'{0}{1}_{2}_solution.csv'\ .format(save_dir_name, filename_el[-5], selected_time[:13])) except Exception as err: write_log(err_log_file, '{4} ::: {0} with {1}{2} on {3}'\ .format(err, dir_name, filename, selected_time[:13], datetime.now())) print('Thread {0} is finished'.format(selected_time)) return 0 # Return a dummy value
def convert_one(f_in, f_out=False, dir_out='./', save_csv=True, debug=False): skiprows = 3 # make sure this is correct with open(f_in) as f: for i, line in enumerate(f): pass N_lines = i with open(f_in) as f: N_lines = N_lines - skiprows data = np.full((N_lines, 8), np.nan) j = 0 for i, line in enumerate(f): if i == skiprows: header = line if i > skiprows: values = line.split() values = [float(v) for v in values] #print(values) data[j, :] = np.array(values) j += 1 if debug: print('input header:', header) p = data[:, 0] z = data[:, 1] T = data[:, 2] ff = data[:, 6] dd = data[:, 7] if True: rh = data[:, 4] / 100 svp = mpcalc.saturation_vapor_pressure(T * units.K) vp = rh * svp r = mpcalc.mixing_ratio(vp, p * units.millibar) else: r = data[:, 5] / 1000. #### convert to WRF variables # constants (amsglossary) Rd = 287.05 Rv = 461.51 cpd = 1005.7 # specific heat dry air cpv = 1847. # specific heat vapor kappa_moist = Rd / cpd * (1 + r * Rv / Rd) / (1 + r * cpv / cpd) print('kappa_moist mean, std:', kappa_moist.mean(), kappa_moist.std()) #################################### # Wind modifications ff = np.concatenate( [np.linspace(2, 30, 36), np.linspace(30, 0, len(p) - 36)]) dd = np.concatenate([ np.linspace(90, 210, 10), np.linspace(213, 270, 20), np.linspace(273, 360, len(p) - 30) ]) #################################### # Temperature to potential temperature pot_tmp = T * (1000 / p)**kappa_moist u = -ff * np.cos(dd / 180 * np.pi - np.pi / 2) v = -ff * np.sin(dd / 180 * np.pi + np.pi / 2) #################################### # modifications #vp = mpcalc.vapor_pressure(p*units.millibar, r) if False: svp = mpcalc.saturation_vapor_pressure(T * units.K) #Td = mpcalc.dewpoint_from_relative_humidity(T*units.K, vp/svp) rh = 0.9 vp = rh * svp r1 = mpcalc.mixing_ratio(vp, p * units.millibar) fig, ax = plt.subplots() ax.plot(r, p, 'r-') r[(r < r1) & (T > 263)] = r1[(r < r1) & (T > 263)] # allow r to be greater than 90% ax.plot(r, p, 'g-') ax.invert_yaxis() fig.savefig('test.png') # vp = mpcalc.vapor_pressure(p*units.millibar, r) # Td = mpcalc.dewpoint_from_relative_humidity(T*units.K, vp/svp) ############### def plot(T, p, r, f_in, dir_out): T = T * units.K p = p * units.millibar vp = mpcalc.vapor_pressure(p, r) svp = mpcalc.saturation_vapor_pressure(T) Td = mpcalc.dewpoint_from_relative_humidity(T, vp / svp) Td[np.isnan( Td)] = -99. * units.degree_Celsius # fill nan with very low temp f_in_basename = os.path.basename(f_in) prof.core(p, T, Td, u, v, saveto=dir_out + 'prof_' + f_in_basename + '.png', title=f_in_basename) plot(T, p, r, f_in, dir_out) #################################### # surface measurements sp = p[0] # surface pressure t_2m = pot_tmp[0] # surface potential Temperature r_2m = r[0].magnitude * 1000. # surface vapor mixing ratio n_levels = z.shape[0] line1 = '{:9.2f} {:9.2f} {:10.2f}'.format(sp, t_2m, r_2m) if save_csv and f_out: os.makedirs(dir_out + '/csv/', exist_ok=True) csvname = dir_out + '/' + '.'.join(f_out.split('.')[:-1]) + '.csv' df = pd.DataFrame(data={'p': p, 'T': T, 'Qv': r}) df.to_csv(csvname) print(csvname, 'saved.') wrfformat = '{:9.2f} {:9.2f} {:10.2f} {:10.2f} {:10.2f}' r *= 1000. if f_out: r = r.magnitude f_out = dir_out + f_out with open(f_out, 'w') as f: f.write(line1 + ' \n') for i in range(n_levels): d = wrfformat.format(z[i], pot_tmp[i], r[i], u[i], v[i]) if debug: print(d) f.write(d + '\n') print(f_out, 'saved.')