Example #1
0
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)
Example #2
0
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
Example #3
0
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)
Example #4
0
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)