def _calculate_quadrature_points(hydrom_type): """ Computes the quadrature points and weights for the integration of the distributions of orientations and aspect ratio type for a given hydrometeor type and saves them to the file Args: hydrom_type: the hydrometeor type, can be either 'R', 'S', 'G', 'H', 'mS' or 'mG' Returns: None but saves the quadrature points to the drive """ # Scheme doesn't matter here scheme = '1mom' hydrom = create_hydrometeor(hydrom_type, scheme) if hydrom_type in ['S', 'G', 'R', 'H', 'I']: list_D = np.linspace(hydrom.d_min, hydrom.d_max, NUM_DIAMETERS).astype('float32') hydrom = create_hydrometeor(hydrom_type, scheme) # In order to save time, we precompute the points of the canting quadrature # (since they are independent of temp, elev and frequency) if hasattr(hydrom, 'get_aspect_ratio_pdf_masc'): canting_stdevs = hydrom.get_canting_angle_std_masc(list_D) else: canting_stdevs = hydrom.canting_angle_std * np.ones( (len(list_D, ))) quad_pts_canting = _compute_gautschi_canting(canting_stdevs) # In order to save time, we precompute the points of the ar quadrature # (since they are independent of temp, elev and frequency) if hasattr(hydrom, 'get_aspect_ratio_pdf_masc'): ar_alpha, ar_loc, ar_scale = hydrom.get_aspect_ratio_pdf_masc( list_D) quad_pts_ar = _compute_gautschi_ar(ar_alpha, ar_loc, ar_scale) else: # If no pdf is available we just take a quadrature of one single point # (the axis-ratio) with a weight of one, for sake of generality ar = hydrom.get_aspect_ratio(list_D) quad_pts_ar = ([[a] for a in ar], [[1]] * len(ar)) quad_pts = (quad_pts_canting, quad_pts_ar) elif hydrom_type in ['mS', 'mG']: # wet hydrometeors num_cores = multiprocessing.cpu_count() quad_pts = (Parallel(n_jobs=num_cores)( delayed(_quadrature_parallel_melting)(hydrom, w) for w in W_CONTENTS)) quad_pts = np.array(quad_pts) np.save(FOLDER_QUAD + '/quad_pts_' + hydrom_type, quad_pts)
def get_radar_observables(list_subradials, lut_sz): """ Computes Doppler and polarimetric radar variables for all subradials over ensembles of hydrometeors and integrates them over all subradials at the end Args: list_subradials: list of subradials (Radial claass instances) as returned by the interpolation.py code lut_sz: Lookup tables for all hydrometeor species as returned by the load_all_lut function in the lut submodule Returns: A radial class instance containing the integrated radar observables """ # Get setup global doppler_scheme global microphysics_scheme # Get info from user config from cosmo_pol.config.cfg import CONFIG doppler_scheme = CONFIG['doppler']['scheme'] add_turb = CONFIG['doppler']['turbulence_correction'] add_antenna_motion = CONFIG['doppler']['motion_correction'] microphysics_scheme = CONFIG['microphysics']['scheme'] with_ice_crystals = CONFIG['microphysics']['with_ice_crystals'] melting = CONFIG['microphysics']['with_melting'] att_corr = CONFIG['microphysics']['with_attenuation'] radar_type = CONFIG['radar']['type'] radial_res = CONFIG['radar']['radial_resolution'] integration_scheme = CONFIG['integration']['scheme'] KW = CONFIG['radar']['K_squared'] if radar_type == 'GPM': # For GPM no need to simulate Doppler variables simulate_doppler = False else: simulate_doppler = True # Get dimensions of subradials num_beams = len(list_subradials) # Number of subradials (quad. pts) idx_0 = int(num_beams / 2) # Index of central subradial # Nb of gates in final integrated radial ( = max length of all subradials) n_gates = max([len(l.dist_profile) for l in list_subradials]) # Here we get the list of all hydrometeor types that must be considered hydrom_types = [] hydrom_types.extend(['R', 'S', 'G']) # Rain, snow and graupel if melting: # IMPORTANT: melting hydrometeors must be placed first hydrom_types.extend(['mS', 'mG']) if microphysics_scheme == '2mom': hydrom_types.extend(['H']) # Add hail if with_ice_crystals: hydrom_types.extend('I') # Initialize # Create dictionnary with all hydrometeor Class instances dic_hydro = {} for h in hydrom_types: dic_hydro[h] = create_hydrometeor(h, microphysics_scheme) # Add info on number of bins to use for numerical integrations # Needs to be the same as in the lookup tables _nbins_D = lut_sz[h].value_table.shape[-2] _dmin = lut_sz[h].axes[2][:, 0] if h in ['mS', 'mG' ] else lut_sz[h].axes[2][0] _dmax = lut_sz[h].axes[2][:, -1] if h in ['mS', 'mG' ] else lut_sz[h].axes[2][-1] dic_hydro[h].nbins_D = _nbins_D dic_hydro[h].d_max = _dmax dic_hydro[h].d_min = _dmin # Consider special case of 'ml' quadrature scheme, where most # quadrature points are used only near the melting layer edges if integration_scheme == 'ml': all_weights = np.array([b.quad_weight for b in list_subradials]) total_weight_at_gates = np.sum(all_weights, axis=0) # Initialize integrated scattering matrix, see lut submodule for info # about the 12 columns sz_integ = np.zeros( (n_gates, len(hydrom_types), 12), dtype='float32') + np.nan # Doppler variables if simulate_doppler: # average terminal velocity rvel_avg = np.zeros(n_gates, ) + np.nan if doppler_scheme == 1 or doppler_scheme == 2: # total weight where the radial velocity is finite total_weight_rvel = np.zeros(n_gates, ) elif doppler_scheme == 3: # Full Doppler spectrum doppler_spectrum = np.zeros((n_gates, len(constants.VARRAY))) ########################################################################### for i, subrad in enumerate( list_subradials): # Loop on subradials (quad pts) if simulate_doppler: v_integ = np.zeros(n_gates, ) # Integrated fall velocity n_integ = np.zeros(n_gates, ) # Integrated number of particles if doppler_scheme == 3: # Total attenuation at every subbeam, needed to take into # account attenuation ah_per_beam = np.zeros((n_gates, ), dtype='float32') + np.nan for j, h in enumerate(hydrom_types): # Loop on hydrometeors # If melting mode is on, we skip the melting hydrometeors # for the beams where no melting is detected if melting: if not subrad.has_melting: if h in ['mS', 'mG']: continue """ Since lookup tables are defined for angles in [0,90], we have to check if elevations are larger than 90°, in that case we take 180-elevation by symmetricity. Also check if angles are smaller than 0, in that case, flip sign """ elev_lut = subrad.elev_profile elev_lut[elev_lut > 90] = 180 - elev_lut[elev_lut > 90] # Also check if angles are smaller than 0, in that case, flip sign elev_lut[elev_lut < 0] = -elev_lut[elev_lut < 0] T = subrad.values['T'] ''' Part 1 : Compute the PSD of the particles ''' QM = subrad.values['Q' + h + '_v'] # Get mass densities valid_data = QM > 0 if not np.isscalar(subrad.quad_weight): # Consider only gates where QM > 0 for subradials with non # zero weight valid_data = np.logical_and(valid_data, subrad.quad_weight > 0) if not np.any(valid_data): continue # Skip # 1 Moment case if microphysics_scheme == '1mom': if h == 'mG': # For melting graupel, need QM and wet fraction fwet = subrad.values['fwet_' + h] dic_hydro[h].set_psd(QM[valid_data], fwet[valid_data]) elif h == 'mS': # For melting snow, need T, QM, wet fraction fwet = subrad.values['fwet_' + h] dic_hydro[h].set_psd(T[valid_data], QM[valid_data], fwet[valid_data]) elif h in ['S', 'I']: # For snow and ice crystals, we need T and QM dic_hydro[h].set_psd(T[valid_data], QM[valid_data]) else: # Rain and graupel dic_hydro[h].set_psd(QM[valid_data]) # 2 Moment case elif microphysics_scheme == '2mom': QN = subrad.values['QN' + h + '_v'] # Get nb concentrations as well dic_hydro[h].set_psd(QN[valid_data], QM[valid_data]) # Get list of diameters for this hydrometeor # For melting hydrometeor, diameters depend on wet fraction... if h in ['mS', 'mG']: # Number of diameter bins in lookup table list_D = vlinspace(dic_hydro[h].d_min, dic_hydro[h].d_max, dic_hydro[h].nbins_D) dD = list_D[:, 1] - list_D[:, 0] else: list_D = lut_sz[h].axes[lut_sz[h].axes_names['d']] dD = list_D[1] - list_D[0] # Compute particle numbers for all diameters N = dic_hydro[h].get_N(list_D) if len(N.shape) == 1: N = np.reshape( N, [len(N), 1]) # To be consistent with the einsum dimensions ''' Part 2: Query of the scattering Lookup table ''' # Get SZ matrix if h in ['mS', 'mG']: sz = lut_sz[h].lookup_line(e=elev_lut[valid_data], wc=fwet[valid_data]) else: sz = lut_sz[h].lookup_line(e=elev_lut[valid_data], t=T[valid_data]) ''' Part 3 : Integrate the SZ coefficients over PSD ''' if h in ['mS', 'mG']: # dD is a vector sz_psd_integ = np.einsum('ijk,ij->ik', sz, N) * dD[:, None] else: # dD is a scalar sz_psd_integ = np.einsum('ijk,ij->ik', sz, N) * dD if len(valid_data) < n_gates: # Check for special cases valid_data = np.pad(valid_data, (0, n_gates - len(valid_data)), mode='constant', constant_values=False) if not np.isscalar(subrad.quad_weight): weights = (subrad.quad_weight[valid_data] / total_weight_at_gates[valid_data]) sz_integ[valid_data, j, :] = nansum_arr(sz_integ[valid_data, j, :], weights[:, None] * sz_psd_integ) else: sz_integ[valid_data, j, :] = nansum_arr(sz_integ[valid_data, j, :], sz_psd_integ * subrad.quad_weight) ''' Part 4 : Doppler ''' if not simulate_doppler: continue # No Doppler info for GPM if doppler_scheme == 1: # Get terminal velocity integrated over PSD vh, n = dic_hydro[h].integrate_V() v_integ[valid_data] = nansum_arr(v_integ[valid_data], vh) n_integ[valid_data] = nansum_arr(n_integ[valid_data], n) elif doppler_scheme == 2: # Get PSD integrated rcs at hor. pol. rcs = 2 * np.pi * (sz[:, :, 0] - sz[:, :, 1] - sz[:, :, 2] + sz[:, :, 3]) # Get terminal velocity v_f = dic_hydro[h].get_V(list_D) # Integrate terminal velocity over PSD with rcs weighting vh_w = np.trapz(np.multiply(v_f, N * rcs), axis=1) n_w = np.trapz(N * rcs, axis=1) v_integ[valid_data] = nansum_arr(v_integ[valid_data], vh_w) n_integ[valid_data] = nansum_arr(n_integ[valid_data], n_w) elif doppler_scheme == 3: """ Computation of Doppler reflectivities will be done at the the loop, but we need to know the attenuation at every gate """ wavelength = constants.WAVELENGTH ah = 4.343e-3 * 2 * wavelength * sz_psd_integ[:, 11] ah *= radial_res / 1000. # Multiply by bin length ah_per_beam = nansum_arr(ah_per_beam, ah) """ For every beam, we get the average fall velocity for all hydrometeors and the resulting radial velocity """ if not simulate_doppler: continue if doppler_scheme in [1, 2]: # Obtain hydrometeor average fall velocity v_hydro = v_integ / n_integ # Add density weighting v_hydro * (subrad.values['RHO'] / subrad.values['RHO'][0])**(0.5) # Get radial velocity knowing hydrometeor fall speed and U,V,W from model theta = np.deg2rad(subrad.elev_profile) # elevation phi = np.deg2rad(subrad.quad_pt[0]) # azimuth proj_wind = proj_vel(subrad.values['U'], subrad.values['V'], subrad.values['W'], v_hydro, theta, phi) # Get mask of valid values total_weight_rvel = sum_arr( total_weight_rvel, ~np.isnan(proj_wind) * subrad.quad_weight) # Average radial velocity for all sub-beams rvel_avg = nansum_arr(rvel_avg, (proj_wind) * subrad.quad_weight) elif doppler_scheme == 3: # Full Doppler ''' NOTE: the full Doppler scheme is kind of badly integrated within the overall routine and there are lots of code repetitions for exemple RCS are recomputed even though they were computed above. It could be reimplemented in a better way ''' # Get list of hydrometeors to process hydros_to_process = dic_hydro.keys() # If melting mode is on, we remove the melting hydrometeors # for the beams where no melting is detected if melting: if not subrad.has_melting: hydros_to_process.remove('mS') hydros_to_process.remove('mG') beam_spectrum = get_doppler_spectrum(subrad, dic_hydro, lut_sz, hydros_to_process, KW) # Account for spectrum spread caused by turbulence and antenna motion add_specwidth = np.zeros(len(beam_spectrum)) if add_turb: add_specwidth += spectral_width_turb(constants.RANGE_RADAR, subrad.values['EDR']) if add_antenna_motion: add_specwidth += spectral_width_motion(subrad.elev_profile) if np.sum(add_specwidth) > 0: beam_spectrum = broaden_spectrum(beam_spectrum, add_specwidth) # Correct spectrum for attenuation if att_corr: wavelength = constants.WAVELENGTH # Total number of velocity bins with positive reflectivity ah_per_beam = nan_cumsum(ah_per_beam) # cumulated attenuation sum_power = np.nansum(beam_spectrum, axis=1) idx_valid = sum_power > 0 sum_power_db = 10 * np.log10(sum_power) # Ratio between non-attenuated ZH and attenuated ZH frac = sum_power[idx_valid] / (10**( 0.1 * (sum_power_db[idx_valid] - ah_per_beam[idx_valid]))) # Add computed attenuation beam_spectrum[idx_valid, :] /= frac[:, None] if not np.isscalar(subrad.quad_weight): beam_spectrum *= subrad.quad_weight[:, None] # Multiply by quad weight else: beam_spectrum *= subrad.quad_weight # Multiply by quad weight doppler_spectrum += beam_spectrum ''' Here we derive the final quadrature integrated radar observables after integrating all scattering properties over hydrometeor types and all subradials ''' sz_integ = np.nansum(sz_integ, axis=1) sz_integ[sz_integ == 0] = np.nan # Get radar observables ZH, ZV, ZDR, RHOHV, KDP, AH, AV, DELTA_HV = get_pol_from_sz(sz_integ, KW) PHIDP = nan_cumsum(2 * KDP) * radial_res / 1000. + DELTA_HV ZV_ATT = ZV.copy() ZH_ATT = ZH.copy() if att_corr: # AH and AV are in dB so we need to convert them to linear ZV_ATT -= nan_cumsum(AV) * (radial_res / 1000. ) # divide to get dist in km ZH_ATT -= nan_cumsum(AH) * (radial_res / 1000.) ZDR = ZH_ATT / ZV_ATT if simulate_doppler: if doppler_scheme in [1, 2]: rvel_avg /= total_weight_rvel elif doppler_scheme == 3: try: rvel_avg = np.nansum(np.tile(constants.VARRAY, (n_gates, 1)) * doppler_spectrum, axis=1) rvel_avg /= np.nansum(doppler_spectrum, axis=1) except: rvel_avg *= np.nan ########################################################################### ''' Create the final Radial class instance containing all radar observables ''' # Create outputs rad_obs = {} rad_obs['ZH'] = ZH rad_obs['ZDR'] = ZDR rad_obs['ZV'] = ZV rad_obs['KDP'] = KDP rad_obs['DELTA_HV'] = DELTA_HV rad_obs['PHIDP'] = PHIDP rad_obs['RHOHV'] = RHOHV # Add attenuation at every gate rad_obs['ATT_H'] = AH rad_obs['ATT_V'] = AV if simulate_doppler: rad_obs['RVEL'] = rvel_avg if doppler_scheme == 3: rad_obs['DSPECTRUM'] = doppler_spectrum ''' Once averaged , the meaning of the mask is the following mask == -1 : all beams are below topography mask == 1 : all beams are above COSMO top mask > 0 : at least one beam is above COSMO top We will keep only gates where no beam is above COSMO top and at least one beam is above topography ''' # Sum the mask of all beams to get overall average mask mask = np.zeros(n_gates, ) for i, beam in enumerate(list_subradials): mask = sum_arr(mask, beam.mask[0:n_gates], cst=1) # Get mask of every Beam mask /= float(num_beams) mask[np.logical_and(mask > -1, mask <= 0)] = 0 # Finally get vectors of distances, height and lat/lon at the central beam heights_radar = list_subradials[idx_0].heights_profile distances_radar = list_subradials[idx_0].dist_profile lats = list_subradials[idx_0].lats_profile lons = list_subradials[idx_0].lons_profile # Create final radial radar_radial = Radial(rad_obs, mask, lats, lons, distances_radar, heights_radar) return radar_radial
def sz_lut(scheme, hydrom_type, list_frequencies, list_elevations, list_temperatures, quad_pts): """ Computes and saves a scattering lookup table for a given hydrometeor type (non melting) and various frequencies, elevations, etc. Args: scheme: microphysical scheme, '1mom' or '2mom' hydrom_type: the hydrometeor type, either 'mS' (melt. snow) or 'mG' (melt. graup) list_frequencies: list of frequencies for which to obtain the lookup tables, in GHz list_elevations: list of incident elevation angles for which to obtain the lookup tables, in degrees list_temperatures: list of temperatures for which to obtain the lookup tables, in K quad_pts: the quadrature points computed with the calculate_quadrature_points function (see below) Returns: No output but saves a lookup table """ if np.isscalar(list_frequencies): list_frequencies = [list_frequencies] hydrom = create_hydrometeor(hydrom_type, scheme) if hydrom_type != 'R': hydrom.radius_type = Scatterer.RADIUS_EQUAL_VOLUME else: hydrom.radius_type = Scatterer.RADIUS_MAXIMUM list_D = np.linspace(hydrom.d_min, hydrom.d_max, NUM_DIAMETERS).astype('float32') num_cores = multiprocessing.cpu_count() for f in list_frequencies: global SCATTERER wavelength = constants.C / (f * 1E09) * 1000 # in mm SCATTERER = _create_scatterer(wavelength, hydrom.canting_angle_std) SZ_matrices = np.zeros( (len(list_elevations), len(list_temperatures), len(list_D), 12)) for i, e in enumerate(list_elevations): print('Running elevation : ' + str(e)) results = (Parallel(n_jobs=num_cores)( delayed(_compute_sz_with_quad)(hydrom, f, e, t, quad_pts[0], quad_pts[1], list_D) for t in list_temperatures)) arr_SZ = _flatten_matrices(results) SZ_matrices[i, :, :, :] = arr_SZ # Create lookup table for a given frequency lut_SZ = Lookup_table() lut_SZ.add_axis('e', list_elevations) lut_SZ.add_axis('t', list_temperatures) lut_SZ.add_axis('d', list_D) lut_SZ.add_axis('sz', np.arange(12)) lut_SZ.set_value_table(SZ_matrices) # The name of the lookup table is lut_SZ_<hydro_name>_<freq>_<scheme>.lut filename = (FOLDER_LUT + "lut_SZ_" + hydrom_type + '_' + str(f).replace('.', '_') + '_' + scheme + ".lut") save_lut(lut_SZ, filename)
def sz_lut_melting(scheme, hydrom_type, list_frequencies, list_elevations, list_wcontent, quad_pts): """ Computes and saves a scattering lookup table for a given melting hydrometeortype and various frequencies, elevations, etc. Args: scheme: microphysical scheme, '1mom' or '2mom' hydrom_type: the hydrometeor type, either 'mS' (melt. snow) or 'mG' (melt. graup) list_frequencies: list of frequencies for which to obtain the lookup tables, in GHz list_elevations: list of incident elevation angles for which to obtain the lookup tables, in degrees list_wcontent: list of water contents for which to obtain the lookup tables, unitless, from 0 to 1 quad_pts: the quadrature points computed with the calculate_quadrature_points function (see below) Returns: No output but saves a lookup table """ if np.isscalar(list_frequencies): list_frequencies = [list_frequencies] hydrom = create_hydrometeor(hydrom_type, scheme) hydrom.radius_type = Scatterer.RADIUS_MAXIMUM array_D = [] for wc in W_CONTENTS: hydrom.f_wet = wc list_D = np.linspace(hydrom.d_min, hydrom.d_max, NUM_DIAMETERS).astype('float32') array_D.append(list_D) array_D = np.array(array_D) num_cores = multiprocessing.cpu_count() for f in list_frequencies: global SCATTERER wavelength = constants.C / (f * 1E09) * 1000 # in mm # The 40 here is the orientation std, but it doesn't matter since # we integrate "manually" over the distributions, we just have # to set something to start SCATTERER = _create_scatterer(wavelength, 40) SZ_matrices = np.zeros( (len(list_elevations), len(list_wcontent), len(list_D), 12)) for i, e in enumerate(list_elevations): print('Running elevation : ' + str(e)) results = (Parallel(n_jobs=num_cores)( delayed(_compute_sz_with_quad_melting)( hydrom, f, e, wc, quad_pts[j][0], quad_pts[j][1]) for j, wc in enumerate(list_wcontent))) arr_SZ = _flatten_matrices(results) SZ_matrices[i, :, :, :] = arr_SZ # Create lookup table for a given frequency lut_SZ = Lookup_table() lut_SZ.add_axis('e', list_elevations) lut_SZ.add_axis('wc', list_wcontent) lut_SZ.add_axis('d', array_D) lut_SZ.add_axis('sz', np.arange(12)) lut_SZ.set_value_table(SZ_matrices) # The name of the lookup table is lut_SZ_<hydro_name>_<freq>_<scheme>.lut filename = (FOLDER_LUT + "lut_SZ_" + hydrom_type + '_' + str(f).replace('.', '_') + '_' + scheme + ".lut") save_lut(lut_SZ, filename)