Пример #1
0
def make_synth(config, spec_st, trace_spec=None):
    import math
    import numpy as np
    from copy import deepcopy
    from sourcespec.spectrum import Spectrum
    from sourcespec.ssp_spectral_model import spectral_model, objective_func
    from sourcespec.ssp_util import mag_to_moment, moment_to_mag
    fdelta = 0.01
    fmin = config.options.fmin
    fmax = config.options.fmax + fdelta

    residuals = list()
    n = 0
    for fc, mag, Mo, t_star, alpha in zip(config.options.fc,
                                          config.options.mag,
                                          config.options.Mo,
                                          config.options.t_star,
                                          config.options.alpha):
        spec = Spectrum()
        if trace_spec:
            spec.stats = deepcopy(trace_spec.stats)
        else:
            spec.stats.begin = fmin
            spec.stats.delta = fdelta
            spec.stats.npts = int((fmax - fmin) / fdelta)

        if math.isnan(Mo):
            Mo = mag_to_moment(mag)
        else:
            mag = moment_to_mag(Mo)

        spec.stats.station = 'S{:02d}'.format(n)
        n += 1
        spec.stats.instrtype = 'Synth'
        spec.stats.channel = spec.stats.channel[:-1] + 'S'
        spec.stats.par = {
            'Mw': mag,
            'fc': fc,
            't_star': t_star,
            'alpha': alpha
        }

        freq = spec.get_freq()
        freq_log = trace_spec.freq_log
        spec.freq_log = freq_log
        spec.data_mag = spectral_model(freq, mag, fc, t_star, alpha)
        spec.data = mag_to_moment(spec.data_mag)
        spec.data_log_mag = spectral_model(freq_log, mag, fc, t_star, alpha)
        spec.data_log = mag_to_moment(spec.data_log_mag)
        spec_st.append(spec)

        if trace_spec:
            objective_func2 = objective_func(freq, trace_spec.data_mag,
                                             np.ones_like(trace_spec.data_mag))
            print(Mo, mag, fc, t_star, alpha,
                  objective_func2((mag, fc, t_star, alpha)))
            residuals.append([
                Mo, mag, fc, t_star,
                objective_func2((mag, fc, t_star, alpha))
            ])
Пример #2
0
def _synth_spec(config, spec, par, par_err):
    """Return a stream with one or more synthetic spectra."""
    spec_st = Stream()
    params_opt = [par[key] for key in ('Mw', 'fc', 't_star')]

    freq = spec.get_freq()
    spec_synth = spec.copy()
    spec_synth.stats.channel = spec.stats.channel[:-1] + 'S'
    spec_synth.stats.par = par
    spec_synth.stats.par_err = par_err
    spec_synth.data_mag = spectral_model(freq, *params_opt)
    spec_synth.data = mag_to_moment(spec_synth.data_mag)
    spec_st.append(spec_synth)

    # Add an extra spectrum with no attenuation
    if config.plot_spectra_no_attenuation:
        spec_synth = spec.copy()
        spec_synth.stats.channel = spec.stats.channel[:-1] + 's'
        _params = list(params_opt)
        _params[-1] = 0
        spec_synth.data_mag = spectral_model(freq, *_params)
        spec_synth.data = mag_to_moment(spec_synth.data_mag)
        spec_st.append(spec_synth)

    if config.plot_spectra_no_fc:
        spec_synth = spec.copy()
        spec_synth.stats.channel = spec.stats.channel[:-1] + 't'
        _params = list(params_opt)
        _params[1] = 1e999
        spec_synth.data_mag = spectral_model(freq, *_params)
        spec_synth.data = mag_to_moment(spec_synth.data_mag)
        spec_st.append(spec_synth)
    return spec_st
Пример #3
0
def spectral_residuals(config, spec_st, sourcepar):
    """
    Compute spectral residuals with respect to an average spectral model.

    Saves a stream of residuals to disk using pickle.
    """
    # Use weighted means
    means = sourcepar.means_weight
    params_name = ('Mw', 'fc', 't_star')
    sourcepar_mean = dict(
        zip(params_name, [means['Mw'], means['fc'], means['t_star']]))
    residuals = Stream()
    for station in set(x.stats.station for x in spec_st.traces):
        spec_st_sel = spec_st.select(station=station)
        for spec in spec_st_sel.traces:
            if spec.stats.channel[-1] != 'H':
                continue

            xdata = spec.get_freq()
            synth_mean_mag = spectral_model(xdata, **sourcepar_mean)

            res = spec.copy()
            res.data_mag = spec.data_mag - synth_mean_mag
            res.data = mag_to_moment(res.data_mag)
            residuals.append(res)

    # Save residuals as pickle file
    evid = config.hypo.evid
    res_file = os.path.join(config.options.outdir, evid + '-residuals.pickle')
    logger.info('Spectral residuals saved to: %s' % res_file)
    with open(res_file, 'wb') as fp:
        pickle.dump(residuals, fp)
Пример #4
0
def make_synth(config, spec_st, trace_spec=None):
    fdelta = 0.01
    fmin = config.options.fmin
    fmax = config.options.fmax + fdelta

    residuals = list()
    for fc, mag, Mo, t_star, alpha in zip(config.options.fc,
                                          config.options.mag,
                                          config.options.Mo,
                                          config.options.t_star,
                                          config.options.alpha):
        spec = Spectrum()
        if trace_spec:
            spec.stats = deepcopy(trace_spec.stats)
        else:
            spec.stats.begin = fmin
            spec.stats.delta = fdelta
            spec.stats.npts = int((fmax - fmin) / fdelta)

        if math.isnan(Mo):
            Mo = mag_to_moment(mag)
        else:
            mag = moment_to_mag(Mo)

        spec.stats.station = 'Mw: %.1f fc: %.2fHz t*: %.2fs alpha: %.2f' %\
            (mag, fc, t_star, alpha)
        spec.stats.instrtype = 'Synth'
        spec.stats.channel = spec.stats.channel[:-1] + 'S'
        spec.stats.par = {
            'Mw': mag,
            'fc': fc,
            't_star': t_star,
            'alpha': alpha
        }

        freq = spec.get_freq()
        spec.data_mag = spectral_model(freq, mag, fc, t_star, alpha)
        spec.data = mag_to_moment(spec.data_mag)
        spec_st.append(spec)

        if trace_spec:
            objective_func2 = objective_func(freq, trace_spec.data_mag,
                                             np.ones_like(trace_spec.data_mag))
            print(Mo, mag, fc, t_star, alpha,
                  objective_func2((mag, fc, t_star, alpha)))
            residuals.append([
                Mo, mag, fc, t_star,
                objective_func2((mag, fc, t_star, alpha))
            ])
Пример #5
0
def _spec_inversion(config, spec, noise_weight):
    """Invert one spectrum."""
    # azimuth computation
    coords = spec.stats.coords
    hypo = spec.stats.hypo
    stla = coords.latitude
    stlo = coords.longitude
    evla = hypo.latitude
    evlo = hypo.longitude
    geod = gps2dist_azimuth(evla, evlo, stla, stlo)
    az = geod[1]

    freq_log = spec.freq_log
    ydata = spec.data_log_mag

    noise_weight = noise_weight.data_log
    if config.weighting == 'noise':
        weight = noise_weight
        # 'curve_fit' interprets 'yerr' as standard deviation vector
        # and calculates weights as 1/yerr^2 .
        # Therefore we build yerr as:
        yerr = 1./np.sqrt(weight)
    elif config.weighting == 'frequency':
        # frequency weighting:
        #   config.weight for f<=f_weight
        #   1      for f> f_weight
        yerr = np.ones(len(ydata))
        yerr[freq_log <= config.f_weight] = 1./math.sqrt(config.weight)
        weight = 1./np.power(yerr, 2)
    elif config.weighting is None:
        weight = yerr = np.ones(len(ydata))

    # Find the frequency range to compute Mw_0 and, possibly, t_star_0:
    # we start where signal-to-noise becomes strong
    idx0 = np.where(noise_weight > 0.5)[0][0]
    # we stop at the first max of signal-to-noise (proxy for fc)
    idx_max = argrelmax(noise_weight)[0]
    # just keep the indexes for maxima > 0.5
    idx_max = [idx for idx in idx_max if noise_weight[idx] > 0.5]
    if not idx_max:
        # if idx_max is empty, then the source and/or noise spectrum
        # is most certainly "strange". In this case, we simply give up.
        logger.warning('%s: unable to find a frequency range to compute '
                       'Mw_0' % spec.id)
        logger.warning('   This is possibly due to an uncommon '
                       'spectrum for the trace (e.g., a resonance).')
        raise RuntimeError
    idx1 = idx_max[0]
    if idx1 == idx0:
        idx1 = idx_max[1]
    # first maximum is a proxy for fc, we use it for fc_0:
    fc_0 = freq_log[idx1]

    t_star_min = t_star_max = None
    if config.invert_t_star_0:
        # fit t_star_0 and Mw on the initial part of the spectrum,
        # corrected for the effect of fc
        ydata_corr = ydata - spectral_model(freq_log, Mw=0, fc=fc_0, t_star=0)
        ydata_corr = smooth(ydata_corr, window_len=18)
        slope, Mw_0 = np.polyfit(
            freq_log[idx0: idx1], ydata_corr[idx0: idx1], deg=1)
        t_star_0 = -3./2 * slope / (np.pi * np.log10(np.e))
        t_star_min = t_star_0 * (1 - config.t_star_0_variability)
        t_star_max = t_star_0 * (1 + config.t_star_0_variability)
    if not config.invert_t_star_0 or t_star_0 < 0:
        # we calculate the initial value for Mw as an average
        Mw_0 = np.nanmean(ydata[idx0: idx1])
        t_star_0 = config.t_star_0

    initial_values = InitialValues(Mw_0, fc_0, t_star_0)
    logger.info('%s %s: initial values: %s' %
                (spec.id, spec.stats.instrtype, str(initial_values)))
    bounds = Bounds(config, spec, initial_values)
    bounds.Mw_min = Mw_0 - 0.1
    bounds.Mw_max = Mw_0 + 0.1
    if t_star_min is not None:
        bounds.t_star_min = t_star_min
    if t_star_max is not None:
        bounds.t_star_max = t_star_max
    logger.info('%s %s: bounds: %s' %
                (spec.id, spec.stats.instrtype, str(bounds)))
    try:
        params_opt, params_cov = _curve_fit(
            config, spec, weight, yerr, initial_values, bounds)
    except (RuntimeError, ValueError) as m:
        logger.warning(m)
        logger.warning('%s %s: unable to fit spectral model' %
                       (spec.id, spec.stats.instrtype))
        raise

    params_name = ('Mw', 'fc', 't_star')
    par = OrderedDict(zip(params_name, params_opt))
    par['Mo'] = mag_to_moment(par['Mw'])
    par['hyp_dist'] = spec.stats.hypo_dist
    par['epi_dist'] = spec.stats.epi_dist
    par['az'] = az
    par['lon'] = spec.stats.coords.longitude
    par['lat'] = spec.stats.coords.latitude

    error = np.sqrt(params_cov.diagonal())
    par_err = OrderedDict(zip(params_name, error))
    return par, par_err
Пример #6
0
def _spec_inversion(config, spec, noise_weight):
    """Invert one spectrum."""
    # azimuth computation
    coords = spec.stats.coords
    hypo = spec.stats.hypo
    stla = coords.latitude
    stlo = coords.longitude
    evla = hypo.latitude
    evlo = hypo.longitude
    geod = gps2dist_azimuth(evla, evlo, stla, stlo)
    az = geod[1]

    freq_log = spec.freq_log
    ydata = spec.data_log_mag
    statId = '{} {}'.format(spec.id, spec.stats.instrtype)

    noise_weight = noise_weight.data_log
    if config.weighting == 'noise':
        weight = noise_weight
        # 'curve_fit' interprets 'yerr' as standard deviation vector
        # and calculates weights as 1/yerr^2 .
        # Therefore we build yerr as:
        yerr = 1. / np.sqrt(weight)
    elif config.weighting == 'frequency':
        # frequency weighting:
        #   config.weight for f<=f_weight
        #   1      for f> f_weight
        yerr = np.ones(len(ydata))
        yerr[freq_log <= config.f_weight] = 1. / math.sqrt(config.weight)
        weight = 1. / np.power(yerr, 2)
    elif config.weighting is None:
        weight = yerr = np.ones(len(ydata))

    # Find the frequency range to compute Mw_0 and, possibly, t_star_0:
    # we start where signal-to-noise becomes strong
    idx0 = np.where(noise_weight > 0.5)[0][0]
    # we stop at the first max of signal-to-noise (proxy for fc)
    idx_max = argrelmax(noise_weight)[0]
    # just keep the indexes for maxima > 0.5
    idx_max = [idx for idx in idx_max if noise_weight[idx] > 0.5]
    if not idx_max:
        # if idx_max is empty, then the source and/or noise spectrum
        # is most certainly "strange". In this case, we simply give up.
        msg = '{}: unable to find a frequency range to compute Mw_0. '
        msg += 'This is possibly due to an uncommon spectrum '
        msg += '(e.g., a resonance).'
        msg = msg.format(statId)
        raise RuntimeError(msg)
    idx1 = idx_max[0]
    if idx1 == idx0:
        try:
            idx1 = idx_max[1]
        except IndexError:
            # if there are no other maxima, just take 5 points
            idx1 = idx0 + 5
    # first maximum is a proxy for fc, we use it for fc_0:
    fc_0 = freq_log[idx1]

    t_star_min = t_star_max = None
    if config.invert_t_star_0:
        # fit t_star_0 and Mw on the initial part of the spectrum,
        # corrected for the effect of fc
        ydata_corr = ydata - spectral_model(freq_log, Mw=0, fc=fc_0, t_star=0)
        ydata_corr = smooth(ydata_corr, window_len=18)
        slope, Mw_0 = np.polyfit(freq_log[idx0:idx1],
                                 ydata_corr[idx0:idx1],
                                 deg=1)
        t_star_0 = -3. / 2 * slope / (np.pi * np.log10(np.e))
        t_star_min = t_star_0 * (1 - config.t_star_0_variability)
        t_star_max = t_star_0 * (1 + config.t_star_0_variability)
    if not config.invert_t_star_0 or t_star_0 < 0:
        # we calculate the initial value for Mw as an average
        Mw_0 = np.nanmean(ydata[idx0:idx1])
        t_star_0 = config.t_star_0

    initial_values = InitialValues(Mw_0, fc_0, t_star_0)
    logger.info('{}: initial values: {}'.format(statId, str(initial_values)))
    bounds = Bounds(config, spec, initial_values)
    bounds.Mw_min = Mw_0 - config.Mw_0_variability
    bounds.Mw_max = Mw_0 + config.Mw_0_variability
    if t_star_min is not None:
        bounds.t_star_min = t_star_min
    if t_star_max is not None:
        bounds.t_star_max = t_star_max
    logger.info('{}: bounds: {}'.format(statId, str(bounds)))
    try:
        params_opt, params_err, misfit = _curve_fit(config, spec, weight, yerr,
                                                    initial_values, bounds)
    except (RuntimeError, ValueError) as m:
        msg = str(m) + '\n'
        msg += '{}: unable to fit spectral model'.format(statId)
        raise RuntimeError(msg)

    params_name = ('Mw', 'fc', 't_star')
    par = OrderedDict(zip(params_name, params_opt))
    par_str = '; '.join(['{}: {:.4f}'.format(key, par[key]) for key in par])
    logger.info('{}: optimal values: {}'.format(statId, par_str))
    logger.info('{}: misfit: {:.3f}'.format(statId, misfit))

    if np.isclose(par['fc'], bounds.fc_min, rtol=0.1):
        msg = '{}: optimal fc within 10% of fc_min: {:.3f} ~= {:.3f}: '
        msg += 'ignoring inversion results'
        msg = msg.format(statId, par['fc'], bounds.fc_min)
        raise ValueError(msg)

    if np.isclose(par['fc'], bounds.fc_max, rtol=1e-4):
        msg = '{}: optimal fc within 10% of fc_max: {:.3f} ~= {:.3f}: '
        msg += 'ignoring inversion results'
        msg = msg.format(statId, par['fc'], bounds.fc_max)
        raise ValueError(msg)

    misfit_max = config.pi_misfit_max or np.inf
    if misfit > misfit_max:
        msg = '{}: misfit larger than pi_misfit_max: {:.3f} > {:.3f}: '
        msg += 'ignoring inversion results'
        msg = msg.format(statId, misfit, misfit_max)
        raise ValueError(msg)

    # Check post-inversion bounds for t_star and fc
    t_star = par['t_star']
    pi_t_star_min, pi_t_star_max =\
        config.pi_t_star_min_max or (-np.inf, np.inf)
    if not (pi_t_star_min <= t_star <= pi_t_star_max):
        msg = '{}: t_star: {:.3f} not in allowed range [{:.3f}, {:.3f}]: '
        msg += 'ignoring inversion results'
        msg = msg.format(statId, t_star, pi_t_star_min, pi_t_star_max)
        raise ValueError(msg)
    fc = par['fc']
    pi_fc_min, pi_fc_max = config.pi_fc_min_max or (-np.inf, np.inf)
    if not (pi_fc_min <= fc <= pi_fc_max):
        msg = '{}: fc: {:.3f} not in allowed range [{:.3f}, {:.3f}]: '
        msg += 'ignoring inversion results'
        msg = msg.format(statId, fc, pi_fc_min, pi_fc_max)
        raise ValueError(msg)

    par['hyp_dist'] = spec.stats.hypo_dist
    par['epi_dist'] = spec.stats.epi_dist
    par['az'] = az
    par['lon'] = spec.stats.coords.longitude
    par['lat'] = spec.stats.coords.latitude

    # additional parameters, computed from fc, Mw and t_star
    vs = config.hypo.vs
    # See if there is a travel-time vs defined
    vs_tt = config.vs_tt or vs
    # seismic moment
    par['Mo'] = mag_to_moment(par['Mw'])
    # source radius in meters
    par['ra'] = source_radius(par['fc'], vs * 1e3)
    # Brune stress drop in MPa
    par['bsd'] = bsd(par['Mo'], par['ra'])
    # quality factor
    par['Qo'] = quality_factor(par['hyp_dist'], vs_tt, par['t_star'])

    # Check post-inversion bounds for bsd
    pi_bsd_min, pi_bsd_max = config.pi_bsd_min_max or (-np.inf, np.inf)
    if not (pi_bsd_min <= par['bsd'] <= pi_bsd_max):
        msg = '{}: bsd: {:.3e} not in allowed range [{:.3e}, {:.3e}]: '
        msg += 'ignoring inversion results'
        msg = msg.format(statId, par['bsd'], pi_bsd_min, pi_bsd_max)
        raise ValueError(msg)

    par_err = OrderedDict(zip(params_name, params_err))
    # additional parameter errors, computed from fc, Mw and t_star
    vs = config.hypo.vs
    # See if there is a travel-time vs defined
    vs_tt = config.vs_tt or vs
    # seismic moment
    Mw_min = par['Mw'] - par_err['Mw'][0]
    Mw_max = par['Mw'] + par_err['Mw'][1]
    Mo_min = mag_to_moment(Mw_min)
    Mo_max = mag_to_moment(Mw_max)
    par_err['Mo'] = (par['Mo'] - Mo_min, Mo_max - par['Mo'])
    # source radius in meters
    fc_min = par['fc'] - par_err['fc'][0]
    if fc_min <= 0:
        fc_min = freq_log[0]
    fc_max = par['fc'] + par_err['fc'][1]
    ra_min = source_radius(fc_max, vs * 1e3)
    ra_max = source_radius(fc_min, vs * 1e3)
    par_err['ra'] = (par['ra'] - ra_min, ra_max - par['ra'])
    # Brune stress drop in MPa
    bsd_min = bsd(Mo_min, ra_max)
    bsd_max = bsd(Mo_max, ra_min)
    par_err['bsd'] = (par['bsd'] - bsd_min, bsd_max - par['bsd'])
    # quality factor
    t_star_min = par['t_star'] - par_err['t_star'][0]
    if t_star_min <= 0:
        t_star_min = 0.001
    t_star_max = par['t_star'] + par_err['t_star'][1]
    Qo_min = quality_factor(par['hyp_dist'], vs_tt, t_star_max)
    Qo_max = quality_factor(par['hyp_dist'], vs_tt, t_star_min)
    par_err['Qo'] = (par['Qo'] - Qo_min, Qo_max - par['Qo'])

    return par, par_err