def get_v_hydro_unweighted(beam, list_hydrom): vh_avg = np.zeros(beam.values['T'].shape) vh_avg.fill(np.nan) n_avg = np.zeros(beam.values['T'].shape) n_avg.fill(np.nan) for i, h in enumerate(list_hydrom.keys()): if cfg.CONFIG['microphysics']['scheme'] == 1: if h == 'S': list_hydrom['S'].set_psd(beam.values['T'], beam.values['Q' + h + '_v']) else: list_hydrom[h].set_psd(beam.values['Q' + h + '_v']) elif cfg.CONFIG['microphysics']['scheme'] == 2: list_hydrom[h].set_psd(beam.values['QN' + h + '_v'], beam.values['Q' + h + '_v']) # Get fall speed vh, n = list_hydrom[h].integrate_V() vh_avg = utilities.nansum_arr(vh_avg, vh) n_avg = utilities.nansum_arr(n_avg, n) v_hydro_unweighted = vh_avg / n_avg # Average over all hydrometeors return v_hydro_unweighted * (beam.values['RHO'] / beam.values['RHO'][0])**(0.5)
def integrate_GH_pts(list_GH_pts): num_beams = len(list_GH_pts) list_variables = list_GH_pts[0].values.keys() integrated_variables = {} for k in list_variables: integrated_variables[k] = [float('nan')] for i in list_GH_pts: integrated_variables[k] = utilities.nansum_arr( integrated_variables[k], i.values[k] * i.GH_weight) # Get index of central beam idx_0 = int(num_beams / 2) # Sum the mask of all beams to get overall mask mask = np.zeros( num_beams, ) # This mask serves to tell if the measured point is ok, or below topo or above COSMO domain for i, p in enumerate(list_GH_pts): mask = utilities.sum_arr(mask, p.mask) # Get mask of every Beam mask /= float( num_beams ) # Larger than 1 means that every Beam is below TOPO, smaller than 0 that at least one Beam is above COSMO domain mask[np.logical_and(mask >= 0, mask < 1)] = 0 heights_radar = list_GH_pts[idx_0].heights_profile distances_radar = list_GH_pts[idx_0].dist_profile lats = list_GH_pts[idx_0].lats_profile lons = list_GH_pts[idx_0].lons_profile integrated_beam = Beam(integrated_variables, mask, lats, lons, distances_radar, heights_radar) return integrated_beam
def get_doppler_velocity(list_beams, lut_sz=0): ########################################################################### # Get setup global doppler_scheme global microphysics_scheme doppler_scheme = cfg.CONFIG['doppler']['scheme'] microphysics_scheme = cfg.CONFIG['microphysics']['scheme'] # Check if necessary variables for turbulence scheme are present if doppler_scheme == 4 and 'EDR' in list_beams.keys(): add_turb = True else: add_turb = False if doppler_scheme == 4: print( 'No eddy dissipitation rate variable found in COSMO file, could not use doppler_scheme == 4, using doppler_scheme == 3 instead' ) # Get dimensions num_beams = len(list_beams) # Number of beams idx_0 = int(num_beams / 2) # Index of central beam len_beams = max([len(l.dist_profile) for l in list_beams]) # Beam length if microphysics_scheme == 1: hydrom_types = ['R', 'S', 'G'] # Rain, snow and graupel elif microphysics_scheme == 2: hydrom_types = ['R', 'S', 'G', 'H'] # Add hail # Create dic of hydrometeors list_hydrom = {} for h in hydrom_types: list_hydrom[h] = hydrometeors.create_hydrometeor( h, microphysics_scheme) ########################################################################### # Get radial wind and doppler spectrum (if scheme == 1 or 2) if doppler_scheme == 1 or doppler_scheme == 2: rvel_avg = np.zeros(len_beams, ) * float( 'nan') # average radial velocity sum_weights = np.zeros(len_beams, ) # mask of GH weights for beam in list_beams: if doppler_scheme == 1: # Weighting by PSD only v_hydro = get_v_hydro_unweighted(beam, list_hydrom) elif doppler_scheme == 2: # Weighting by RCS and PSD v_hydro = get_v_hydro_weighted(beam, list_hydrom, lut_sz) # Get radial velocity knowing hydrometeor fall speed and U,V,W from model theta = beam.elev_profile * DEG2RAD phi = beam.GH_pt[0] * DEG2RAD proj_wind = proj_vel(beam.values['U'], beam.values['V'], beam.values['W'], v_hydro, theta, phi) # Get mask of valid values sum_weights = utilities.sum_arr( sum_weights, ~np.isnan(proj_wind) * beam.GH_weight) # Average radial velocity for all sub-beams rvel_avg = utilities.nansum_arr(rvel_avg, (proj_wind) * beam.GH_weight) # We need to divide by the total weights of valid beams at every bin rvel_avg /= sum_weights elif doppler_scheme == 3: rvel_avg = np.zeros(len_beams, ) doppler_spectrum = np.zeros((len_beams, len(constants.VARRAY))) for beam in list_beams: beam_spectrum = get_doppler_spectrum( beam, list_hydrom, lut_sz) * beam.GH_weight # Multiply by GH weight if add_turb: # Spectrum spread caused by turbulence turb_std = get_turb_std(constants.RANGE_RADAR, beam.values['EDR']) beam_spectrum = turb_spectrum_spread(beam_spectrum, turb_std) doppler_spectrum += beam_spectrum try: rvel_avg = np.sum(np.tile(constants.VARRAY, (len_beams, 1)) * doppler_spectrum, axis=1) / np.sum(doppler_spectrum, axis=1) except: rvel_avg *= float('nan') ########################################################################### # Get mask # This mask serves to tell if the measured point is ok, or below topo or above COSMO domain mask = np.zeros(len_beams, ) for i, beam in enumerate(list_beams): mask = utilities.sum_arr(mask, beam.mask) # Get mask of every Beam mask /= num_beams # Larger than 1 means that every Beam is below TOPO, smaller than 0 that at least one Beam is above COSMO domain mask[np.logical_and(mask >= 0, mask < 1)] = 0 # Finally get vectors of distances, height and lat/lon at the central beam idx_0 = int(len(list_beams) / 2) heights_radar = list_beams[idx_0].heights_profile distances_radar = list_beams[idx_0].dist_profile lats = list_beams[idx_0].lats_profile lons = list_beams[idx_0].lons_profile if doppler_scheme == 3: dic_vars = {'RVEL': rvel_avg, 'DSPECTRUM': doppler_spectrum} else: # No doppler spectrum is computed dic_vars = {'RVEL': rvel_avg} beam_doppler = Beam(dic_vars, mask, lats, lons, distances_radar, heights_radar) return beam_doppler
def get_v_hydro_weighted(beam, list_hydrom, lut_sz): hydrom_scheme = cfg.CONFIG['microphysics']['scheme'] vh_avg = np.zeros(beam.values['T'].shape) vh_avg.fill(np.nan) n_avg = np.zeros(beam.values['T'].shape) n_avg.fill(np.nan) for i, h in enumerate(list_hydrom.keys()): # Get list of diameters for this hydrometeor list_D = lut_sz[h].axes[lut_sz[h].axes_names['d']] valid_data = beam.values['Q' + h + '_v'] > 0 # Get all elevations elev = beam.elev_profile # Since lookup tables are defined for angles >0, we have to check # if angles are larger than 90°, in that case we take 180-elevation # by symmetricity elev_lut = copy.deepcopy(elev) 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 = beam.values['T'] # Get SZ matrix sz = lut_sz[h].lookup_line(e=elev[valid_data], t=T[valid_data]) ''' Part 1: Query of the SZ Lookup table and RCS computation ''' # Get SZ matrix sz = lut_sz[h].lookup_line(e=elev_lut[valid_data], t=T[valid_data]) # get RCS rcs = 2 * np.pi * (sz[:, :, 0] - sz[:, :, 1] - sz[:, :, 2] + sz[:, :, 3]) rcs = rcs.T ''' Part 2 : Get the PSD of the particles ''' QM = beam.values['Q' + h + '_v'] # Get mass densities # 1 Moment case if hydrom_scheme == 1: if h != 'S': list_hydrom[h].set_psd(QM[valid_data]) else: # For snow N0 is Q and temperature dependent list_hydrom[h].set_psd(T[valid_data], QM[valid_data]) # 2 Moment case elif hydrom_scheme == 2: QN = beam.values['QN' + h + '_v'] # Get concentrations as well list_hydrom[h].set_psd(QN[valid_data], QM[valid_data]) N = list_hydrom[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 3 : Integrate ''' # Get fall speed v_f = list_hydrom[h].get_V(list_D) vh_w = np.trapz(N * v_f[:, np.newaxis] * rcs, axis=0) n_w = np.trapz(N * rcs, axis=0) vh_avg[valid_data] = utilities.nansum_arr(vh_avg[valid_data], vh_w) n_avg[valid_data] = utilities.nansum_arr(n_avg[valid_data], n_w) v_hydro_weighted = vh_avg / n_avg # Average over all hydrometeors return v_hydro_weighted * (beam.values['RHO'] / beam.values['RHO'][0])**(0.5)
def get_radar_observables(list_beams, lut_sz): ########################################################################### # Get setup att_corr = cfg.CONFIG['attenuation']['correction'] hydrom_scheme = cfg.CONFIG['microphysics']['scheme'] # Get dimensions num_beams = len(list_beams) # Number of beams idx_0 = int(num_beams / 2) # Index of central beam len_beams = len(list_beams[idx_0].dist_profile) # Beam length # Initialize radial_res = cfg.CONFIG['radar']['radial_resolution'] if hydrom_scheme == 1: hydrom_types = ['R', 'S', 'G'] # Rain, snow and graupel elif hydrom_scheme == 2: hydrom_types = ['R', 'S', 'G', 'H'] # Add hail # Initialize matrices sz_integ = np.zeros((len_beams, len(hydrom_types), 12), dtype='float32') sz_integ.fill(np.nan) ########################################################################### for j, h in enumerate(hydrom_types): # Loop on hydrometeors # Create a hydrometeor instance scheme = '2mom' if hydrom_scheme == 2 else '1mom' hydrom = create_hydrometeor(h, scheme) # Get list of diameters for this hydrometeor list_D = lut_sz[h].axes[lut_sz[h].axes_names['d']] # Diameter bin size dD = list_D[1] - list_D[0] for i, beam in enumerate(list_beams): # Loop on subbeams # For GPM some sub-beams are longer than the main beam, so we discard # the "excess" part for k in beam.values.keys(): beam.values[k] = beam.values[k][0:len_beams] valid_data = beam.values['Q' + h + '_v'] > 0 elev = beam.elev_profile # Since lookup tables are defined for angles >0, we have to check # if angles are larger than 90°, in that case we take 180-elevation # by symmetricity elev[elev > 90] = 180 - elev[elev > 90] # Also check if angles are smaller than 0, in that case, flip sign elev[elev < 0] = -elev[elev < 0] T = beam.values['T'] ''' Part 1: Query of the SZ Lookup table ''' # Get SZ matrix sz = lut_sz[h].lookup_line(e=elev[valid_data], t=T[valid_data]) ''' Part 2 : Get the PSD of the particles ''' QM = beam.values['Q' + h + '_v'] # Get mass densities # 1 Moment case if hydrom_scheme == 1: if h != 'S': hydrom.set_psd(QM[valid_data]) else: # For snow N0 is Q and temperature dependent hydrom.set_psd(T[valid_data], QM[valid_data]) # 2 Moment case elif hydrom_scheme == 2: QN = beam.values['QN' + h + '_v'] # Get concentrations as well hydrom.set_psd(QN[valid_data], QM[valid_data]) # Compute particle numbers for all diameters N = hydrom.get_N(list_D) if len(N.shape) == 1: N = np.reshape( N, [len(N), 1]) # To be consistent with the einsum dimensions ''' Part 3 : Integrate the SZ coefficients ''' sz_psd_integ = np.einsum('ijk,ji->ik', sz, N) * dD sz_integ[valid_data, j, :] = nansum_arr(sz_integ[valid_data, j, :], sz_psd_integ * beam.GH_weight) # Finally we integrate for all hydrometeors 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) # print 10*np.log10(ZH)[0] # print(sz_integ[0]) KDP_m = KDP + DELTA_HV # Account for differential phase on prop. PHIDP = nan_cumsum(2 * KDP_m) * radial_res / 1000. if att_corr: # AH and AV are in dB so we need to convert them to linear ZV *= nan_cumprod( 10**(-0.1 * AV * (radial_res / 1000.))) # divide to get dist in km ZH *= nan_cumprod(10**(-0.1 * AH * (radial_res / 1000.))) # print(nan_cumprod(10**(-0.1*AH*(radial_res/1000.)))) ZDR = ZH / ZV ########################################################################### # Create outputs rad_obs = {} rad_obs['ZH'] = ZH rad_obs['ZDR'] = ZDR rad_obs['ZV'] = ZV rad_obs['KDP'] = KDP_m rad_obs['DELTA_HV'] = DELTA_HV rad_obs['PHIDP'] = PHIDP rad_obs['RHOHV'] = RHOHV rad_obs['AH'] = AH rad_obs['AV'] = AV # This mask serves to tell if the measured point is ok, or below topo or above COSMO domain mask = np.zeros(len_beams, ) for i, beam in enumerate(list_beams): mask = sum_arr(mask, beam.mask[0:len_beams], cst=1) # Get mask of every Beam # Larger than 0 means that at least one Beam is below TOPO, smaller than 0 that at least one Beam is above COSMO domain mask /= num_beams mask[np.logical_and( mask >= 0, mask < 1 )] = 0 # If at least one beam is above topo, we still consider this gate # Finally get vectors of distances, height and lat/lon at the central beam heights_radar = list_beams[idx_0].heights_profile distances_radar = list_beams[idx_0].dist_profile lats = list_beams[idx_0].lats_profile lons = list_beams[idx_0].lons_profile beam_pol = Beam(rad_obs, mask, lats, lons, distances_radar, heights_radar) return beam_pol
def get_radar_observables(list_beams, lut): ########################################################################### # Get setup att_corr = cfg.CONFIG['attenuation']['correction'] hydrom_scheme = cfg.CONFIG['microphysics']['scheme'] # Get dimensions num_beams = len(list_beams) idx_0 = int(num_beams / 2) len_beams = len(list_beams[idx_0].dist_profile) # Initialize if att_corr: # Compute radar bins range radial_res = cfg.CONFIG['radar']['radial_resolution'] if hydrom_scheme == 1: hydrom_types = ['R', 'S', 'G'] # Rain, snow and graupel elif hydrom_scheme == 2: hydrom_types = ['R', 'S', 'G', 'H'] # Add hail rad_obs_integrated = {} rad_obs = {} for o in LIST_OBSERVABLES: rad_obs_integrated[o] = np.zeros( (len_beams, len(hydrom_types))) * float('nan') rad_obs[o] = np.zeros( (len_beams, len(hydrom_types), num_beams)) * float('nan') ########################################################################### for j, h in enumerate(hydrom_types): # Loop on hydrometeors sum_weights = np.zeros((len_beams, )) for i, beam in enumerate(list_beams[0:]): # Loop on subbeams elev = beam.elev_profile # Since lookup tables are defined for angles >0, we have to check # if angles are larger than 90°, in that case we take 180-elevation # by symmetricity elev[elev > 90] = 180 - elev[elev > 90] # Also check if angles are smaller than 0, in that case, flip sign elev[elev < 0] = -elev[elev < 0] T = beam.values['T'] QM = np.log10(beam.values['Q' + h + '_v']) # Get log mass densities if hydrom_scheme == 2: # Get log number densities as well QN = np.log10(beam.values['QN' + h + '_v']) lut_pts = np.column_stack((elev, T)).T elif hydrom_scheme == 1: lut_pts = np.column_stack((elev, T, QM)).T # Get polarimetric variables from lookup-table ZH_prof = lut[h]['ZH'].lookup_pts(lut_pts) ZDR_prof = lut[h]['ZDR'].lookup_pts(lut_pts) KDP_prof = lut[h]['KDP'].lookup_pts( lut_pts) + lut[h]['DELTAHV'].lookup_pts(lut_pts) RHOHV_prof = lut[h]['RHOHV'].lookup_pts(lut_pts) # DELTAHV_prof=lut[h]['DELTAHV'].lookup_pts(lut_pts) # PHIDP_prof=nan_cumsum(KDP_prof+DELTAHV_prof)*cfg.CONFIG['radar']['radial_resolution']/1000 ZV_prof = ZH_prof / ZDR_prof # Use ZDR and ZH to get ZV # Note that Z2=Z1-a*r in dB gives Z2_l = Z1_l * (1/a_l)**r in linear if att_corr: # AH and AV are in dB so we need to convert them to linear Av_prof = lut[h]['AV'].lookup_pts(lut_pts) ZV_prof = ZH_prof / ZDR_prof ZV_prof *= nan_cumprod( 10**(-Av_prof / 10. * (radial_res / 1000.))) # divide to get dist in km Ah_prof = lut[h]['AH'].lookup_pts(lut_pts) # convert to linear ZH_prof *= nan_cumprod(10**(-Ah_prof / 10. * (radial_res / 1000.))) ZDR_prof = ZH_prof / ZV_prof # Add contributions from this subbeam rad_obs_integrated['ZH'][:, j] = nansum_arr( rad_obs_integrated['ZH'][:, j], ZH_prof * beam.GH_weight) rad_obs_integrated['ZV'][:, j] = nansum_arr( rad_obs_integrated['ZV'][:, j], ZV_prof * beam.GH_weight) rad_obs_integrated['KDP'][:, j] = nansum_arr( rad_obs_integrated['KDP'][:, j], KDP_prof * np.sqrt(beam.GH_weight)) rad_obs_integrated['RHOHV'][:, j] = nansum_arr( rad_obs_integrated['RHOHV'][:, j], RHOHV_prof**np.sqrt(beam.GH_weight)) rad_obs['ZH'][:, j, i] = ZH_prof rad_obs['ZV'][:, j, i] = ZV_prof rad_obs['KDP'][:, j, i] = KDP_prof rad_obs['RHOHV'][:, j, i] = RHOHV_prof sum_weights = sum_arr(sum_weights, ~np.isnan(QM) * np.sqrt(beam.GH_weight)) sum_weights_sqrt = sum_arr(np.sqrt(sum_weights), ~np.isnan(QM) * np.sqrt(beam.GH_weight)) # Rhohv and Kdp are divided by the total received power rad_obs_integrated['KDP'][:, j] = divide_by_power( rad_obs_integrated['KDP'][:, j], sum_weights_sqrt) rad_obs_integrated['RHOHV'][:, j] = divide_by_power( rad_obs_integrated['RHOHV'][:, j], sum_weights_sqrt) # If weight = 0, this will get infinite ########################################################################### # This mask serves to tell if the measured point is ok, or below topo or above COSMO domain mask = np.zeros(len_beams, ) for i, beam in enumerate(list_beams): mask = sum_arr(mask, beam.mask) # Get mask of every Beam # Larger than 1 means that every Beam is below TOPO, smaller than 0 that at least one Beam is above COSMO domain mask /= num_beams mask[np.logical_and(mask >= 0, mask < 1)] = 0 rad_obs_integrated = combine(rad_obs_integrated) rad_obs = combine(rad_obs) # Add standard deviation to output for var in rad_obs.keys(): rad_obs_integrated['std_' + var] = np.nanstd(rad_obs[var], axis=1) # Finally get vectors of distances, height and lat/lon at the central beam idx_0 = int(len(list_beams) / 2) heights_radar = list_beams[idx_0].heights_profile distances_radar = list_beams[idx_0].dist_profile lats = list_beams[idx_0].lats_profile lons = list_beams[idx_0].lons_profile beam_pol = Beam(rad_obs_integrated, mask, lats, lons, distances_radar, heights_radar) return beam_pol