Esempio n. 1
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
Esempio n. 2
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))
            ])
Esempio n. 3
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))
            ])
Esempio n. 4
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)
def _params_text(spec, ax, color, path_effects, stack_plots):
    global station_text_ypos
    if stack_plots:
        params_text_ypos = station_text_ypos - 0.04
    else:
        params_text_ypos = 0.03
        color = 'black'
    fc = spec.stats.par['fc']
    Mw = spec.stats.par['Mw']
    Mo = mag_to_moment(Mw)
    t_star = spec.stats.par['t_star']
    if 'par_err' in spec.stats.keys():
        fc_err_left, fc_err_right = spec.stats.par_err['fc']
        Mw_err_left, Mw_err_right = spec.stats.par_err['Mw']
        t_star_err_left, t_star_err_right =\
            spec.stats.par_err['t_star']
    else:
        fc_err_left = fc_err_right = 0.
        Mw_err_left = Mw_err_right = 0.
        t_star_err_left = t_star_err_right = 0.
    Mo_text = 'Mo: {:.2g}'.format(Mo)
    Mw_text = 'Mw: {:.2f}'.format(Mw)
    if round(Mw_err_left, 2) == round(Mw_err_right, 2):
        Mw_text += '±{:.2f}'.format(Mw_err_left)
    else:
        Mw_text += '[-{:.2f},+{:.2f}]'.format(
            Mw_err_left, Mw_err_right)
    fc_text = 'fc: {:.2f}'.format(fc)
    if round(fc_err_left, 2) == round(fc_err_right, 2):
        fc_text += '±{:.2f}Hz'.format(fc_err_left)
    else:
        fc_text += '[-{:.2f},+{:.2f}]Hz'.format(
            fc_err_left, fc_err_right)
    t_star_text = 't*: {:.2f}'.format(t_star)
    if round(t_star_err_left, 2) == round(t_star_err_right, 2):
        t_star_text += '±{:.2f}s'.format(t_star_err_left)
    else:
        t_star_text += '[-{:.2f},+{:.2f}]s'.format(
            t_star_err_left, t_star_err_right)
    if len(fc_text+t_star_text) > 38:
        sep = '\n'
        params_text_ypos -= 0.01
        station_text_ypos += 0.06
    else:
        sep = ' '
    params_text = '{} {}\n{}{}{}'.format(
        Mo_text, Mw_text, fc_text, sep, t_star_text)
    ax.text(
        0.05, params_text_ypos, params_text,
        horizontalalignment='left',
        verticalalignment='bottom',
        color=color,
        fontsize=9,
        transform=ax.transAxes,
        zorder=50,
        path_effects=path_effects)
Esempio n. 6
0
def station_correction(spec_st, config):
    """
    Correct spectra using station-average residuals.

    Residuals are obtained from a previous run.
    """
    res_filepath = config.residuals_filepath
    if res_filepath is None:
        return spec_st
    try:
        with open(res_filepath, 'rb') as fp:
            residual = pickle.load(fp)
    except Exception as msg:
        logger.error(msg)
        ssp_exit(1)

    for spec in [spec for spec in spec_st if (spec.stats.channel[-1] == 'H')]:
        station = spec.stats.station
        if station in set(x.stats.station for x in residual):
            # apply correction
            corr = residual.select(station=station)[0]
            freq = spec.get_freq()
            fmin = freq.min()
            fmax = freq.max()
            corr = corr.slice(fmin, fmax)
            corr.data_mag = moment_to_mag(corr.data)
            spec_corr = spec.copy()
            # uncorrected spectrum will have component name 'h'
            spec.stats.channel = spec.stats.channel[:-1] + 'h'
            spec_corr.data_mag -= corr.data_mag
            # interpolate the corrected data_mag to log frequencies
            f = interp1d(freq, spec_corr.data_mag, fill_value='extrapolate')
            spec_corr.data_log_mag = f(spec_corr.freq_log)
            # convert mag to moment
            spec_corr.data = mag_to_moment(spec_corr.data_mag)
            spec_corr.data_log = mag_to_moment(spec_corr.data_log_mag)
            spec_st.append(spec_corr)
            logger.info(
                '{} corrected, frequency range is: {:.2f} {:.2f} Hz'.format(
                    spec_corr.id, fmin, fmax))
    return spec_st
Esempio n. 7
0
def station_correction(spec_st, config):
    """
    Correct spectra using station-average residuals.

    Residuals are obtained from a previous run.
    """
    res_filepath = config.residuals_filepath
    if res_filepath is None:
        msg = "'-C' option set, but 'residuals_filepath' not specified "
        msg += "in config file: ignoring station correction"
        logger.warning(msg)
        return spec_st
    with open(res_filepath, 'rb') as fp:
        residual = pickle.load(fp)

    for spec in [spec for spec in spec_st if (spec.stats.channel[-1] == 'H')]:
        station = spec.stats.station
        if station in set(x.stats.station for x in residual):
            # apply correction
            corr = residual.select(station=station)[0]
            freq = spec.get_freq()
            fmin = freq.min()
            fmax = freq.max()
            corr = corr.slice(fmin, fmax)
            corr.data_mag = moment_to_mag(corr.data)
            spec.data_mag -= corr.data_mag
            # interpolate the corrected data_mag to log frequencies
            f = interp1d(freq, spec.data_mag, fill_value='extrapolate')
            spec.data_log_mag = f(spec.freq_log)
            # convert mag to moment
            spec.data = mag_to_moment(spec.data_mag)
            spec.data_log = mag_to_moment(spec.data_log_mag)

            logger.info('%s corrected, frequency range is: %f %f' %
                        (spec.id, fmin, fmax))
    return spec_st
Esempio n. 8
0
def _M0_avg_and_std(Mw_mean, Mw_error):
    """Compute average and standard deviation for Mo starting from Mw."""
    Mo_mean = mag_to_moment(Mw_mean)
    Mo_min = mag_to_moment(Mw_mean - Mw_error)
    Mo_max = mag_to_moment(Mw_mean + Mw_error)
    return Mo_mean, (Mo_mean - Mo_min, Mo_max - Mo_mean)
Esempio n. 9
0
    freqs_min = [spec.get_freq().min() for spec in res]
    freqs_max = [spec.get_freq().max() for spec in res]
    freq_min = min(freqs_min)
    freq_max = max(freqs_max)

    spec_mean = Spectrum()
    spec_mean.stats.begin = freq_min
    spec_mean.stats.delta = res[0].stats.delta
    spec_mean.stats.station = res[0].stats.station
    spec_mean.data_mag = None
    for spec in res:
        spec_slice = spec.slice(freq_min,
                                freq_max,
                                pad=True,
                                fill_value=mag_to_moment(0))
        spec_slice.data_mag = moment_to_mag(spec_slice.data)
        norm = (spec_slice.data_mag != 0).astype(int)
        if spec_mean.data_mag is None:
            spec_mean.data_mag = spec_slice.data_mag
            norm_mean = norm
        else:
            spec_mean.data_mag += spec_slice.data_mag
            norm_mean += norm
    spec_mean.data_mag /= norm_mean
    spec_mean.data = mag_to_moment(spec_mean.data_mag)

    residual_mean.append(spec_mean)

    # plot traces
    if options.plot:
Esempio n. 10
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
Esempio n. 11
0
def plot_spectra(config,
                 spec_st,
                 specnoise_st=None,
                 ncols=4,
                 stack_plots=False,
                 plottype='regular',
                 async_plotter=None):
    """
    Plot spectra for signal and noise.

    Display to screen and/or save to file.
    """
    # Check config, if we need to plot at all
    if not config.PLOT_SHOW and not config.PLOT_SAVE:
        return

    matplotlib.rcParams['pdf.fonttype'] = 42  # to edit text in Illustrator

    nlines, ncols, freq_minmax, moment_minmax, mag_minmax =\
        _nplots(config, spec_st, specnoise_st,
                config.plot_spectra_maxrows, ncols, plottype)
    figures = []
    fig, axes, ax0 = _make_fig(config, nlines, ncols, freq_minmax,
                               moment_minmax, mag_minmax, stack_plots,
                               plottype)
    figures.append(fig)

    # Path effect to contour text in white
    path_effects = [PathEffects.withStroke(linewidth=3, foreground='white')]

    # Plot!
    plotn = 0
    if config.plot_spectra_ignored:
        stalist = sorted(
            set((sp.stats.hypo_dist, sp.id[:-1]) for sp in spec_st))
    else:
        stalist = sorted(
            set((sp.stats.hypo_dist, sp.id[:-1]) for sp in spec_st
                if not sp.stats.ignore))
    for t in stalist:
        plotn += 1
        _, specid = t
        # 'code' is band+instrument code
        network, station, location, code = specid.split('.')
        spec_st_sel = spec_st.select(network=network,
                                     station=station,
                                     location=location)
        if plotn > nlines * ncols:
            # Add lables and legend before making a new figure
            _add_labels(axes, plotn - 1, ncols, plottype)
            _add_legend(config, ax0, spec_st, specnoise_st, stack_plots,
                        plottype)
            fig, axes, ax0 = _make_fig(config, nlines, ncols, freq_minmax,
                                       moment_minmax, mag_minmax, stack_plots,
                                       plottype)
            figures.append(fig)
            plotn = 1
        ax_text = False
        ax, ax2 = axes[plotn - 1]
        sn_text_ypos = 0.95
        for spec in spec_st_sel.traces:
            if spec.stats.channel[:-1] != code:
                continue
            if not config.plot_spectra_ignored and spec.stats.ignore:
                continue
            orientation = spec.stats.channel[-1]
            color, linestyle, linewidth =\
                _color_lines(config, orientation, plotn, stack_plots)
            # dim out ignored spectra
            if spec.stats.ignore:
                alpha = 0.3
            else:
                alpha = 1.0
            if plottype == 'regular':
                ax.loglog(spec.get_freq(),
                          spec.data,
                          color=color,
                          alpha=alpha,
                          linestyle=linestyle,
                          linewidth=linewidth,
                          zorder=20)
                # Write spectral S/N for regular Z,N,E components
                if orientation not in ['S', 's', 't', 'H']:
                    _text = 'S/N: {:.1f}'.format(spec.stats.spectral_snratio)
                    ax.text(0.95,
                            sn_text_ypos,
                            _text,
                            ha='right',
                            va='top',
                            fontsize=8,
                            color=color,
                            path_effects=path_effects,
                            transform=ax.transAxes,
                            zorder=20)
                    sn_text_ypos -= 0.05
                if orientation == 'S':
                    fc = spec.stats.par['fc']
                    if 'par_err' in spec.stats.keys():
                        fc_err = spec.stats.par_err['fc']
                        fc_min = fc - fc_err
                        if fc_min < 0:
                            fc_min = 0.01
                        ax.axvspan(fc_min,
                                   fc + fc_err,
                                   color='#bbbbbb',
                                   alpha=0.3,
                                   zorder=1)
                    ax.axvline(fc, color='#999999', linewidth=2., zorder=1)
                    Mw = spec.stats.par['Mw']
                    if 'par_err' in spec.stats.keys():
                        Mw_err = spec.stats.par_err['Mw']
                        ax2.axhspan(Mw - Mw_err,
                                    Mw + Mw_err,
                                    color='#bbbbbb',
                                    alpha=0.3,
                                    zorder=1)
            elif plottype == 'weight':
                ax.semilogx(spec.get_freq(),
                            spec.data,
                            color=color,
                            alpha=alpha,
                            zorder=20)
            else:
                raise ValueError('Unknown plot type: %s' % plottype)
            # leg = ax.legend(('N', 'E', 'H'), 'lower right')

            if specnoise_st:
                if spec.stats.channel[-1] != 'S':
                    specid = spec.get_id()
                    try:
                        sp_noise = specnoise_st.select(id=specid)[0]
                    except IndexError:
                        continue
                    orientation = sp_noise.stats.channel[-1]
                    ax.loglog(sp_noise.get_freq(),
                              sp_noise.data,
                              linestyle=':',
                              linewidth=linewidth,
                              color=color,
                              alpha=alpha,
                              zorder=20)

            if not ax_text:
                ax_text = '%s %s' % (spec.id[:-1], spec.stats.instrtype)
                if stack_plots:
                    text_y = 0.05 + (plotn - 1) * 0.05
                else:
                    text_y = 0.15
                    color = 'black'
                    ax_text += '\n%.1f km (%.1f km)' % (spec.stats.hypo_dist,
                                                        spec.stats.epi_dist)
                ax.text(0.05,
                        text_y,
                        ax_text,
                        horizontalalignment='left',
                        verticalalignment='bottom',
                        color=color,
                        transform=ax.transAxes,
                        zorder=50,
                        path_effects=path_effects)
                ax_text = True

            if orientation == 'S':
                if stack_plots:
                    text_y2 = text_y - 0.02
                else:
                    text_y2 = 0.03
                    color = 'black'
                fc = spec.stats.par['fc']
                Mw = spec.stats.par['Mw']
                Mo = mag_to_moment(Mw)
                t_star = spec.stats.par['t_star']
                if 'par_err' in spec.stats.keys():
                    fc_err = spec.stats.par_err['fc']
                    Mw_err = spec.stats.par_err['Mw']
                    t_star_err = spec.stats.par_err['t_star']
                else:
                    fc_err = Mw_err = t_star_err = 0.
                ax.text(0.05,
                        text_y2,
                        'Mo: %.2g Mw: %.2f±%.2f\n'
                        'fc: %.2f±%.2f Hz t*: %.2f±%.2fs' %
                        (Mo, Mw, Mw_err, fc, fc_err, t_star, t_star_err),
                        horizontalalignment='left',
                        verticalalignment='bottom',
                        color=color,
                        fontsize=9,
                        transform=ax.transAxes,
                        zorder=50,
                        path_effects=path_effects)

    # Add lables and legend for the last figure
    _add_labels(axes, plotn, ncols, plottype)
    _add_legend(config, ax0, spec_st, specnoise_st, stack_plots, plottype)
    # Turn off the unused axes
    for ax, ax2 in axes[plotn:]:
        ax.set_axis_off()
        if ax2:
            ax2.set_axis_off()

    if config.PLOT_SHOW:
        plt.show()
    if config.PLOT_SAVE:
        _savefig(config, plottype, figures, async_plotter)
Esempio n. 12
0
def main():
    usage = 'usage: %prog [options] residuals_dir'

    parser = OptionParser(usage=usage)
    parser.add_option('-m',
                      '--min_spectra',
                      dest='min_spectra',
                      action='store',
                      default='20',
                      help='minimum number of spectra to '
                      'compute residuals (default=20)',
                      metavar='NUMBER')
    parser.add_option('-p',
                      '--plot',
                      dest='plot',
                      action='store_true',
                      default=False,
                      help='save residuals plots to file')
    (options, args) = parser.parse_args()

    if len(args) < 1:
        parser.print_usage(file=sys.stderr)
        sys.stderr.write("\tUse '-h' for help\n\n")
        sys.exit(1)

    resdir = args[0]
    min_spectra = int(options.min_spectra)
    outdir = 'sspec_residuals'

    residual_dict = defaultdict(Stream)
    for resfile in glob(os.path.join(resdir, '*-res*.pickle')):
        print(resfile)
        with open(resfile, 'rb') as fp:
            residual_st = pickle.load(fp)
        for spec in residual_st:
            residual_dict[spec.id].append(spec)

    if not os.path.exists(outdir):
        os.makedirs(outdir)

    residual_mean = Stream()
    for stat_id in sorted(residual_dict.keys()):
        if len(residual_dict[stat_id]) < min_spectra:
            continue
        print(stat_id)

        res = residual_dict[stat_id]

        freqs_min = [spec.get_freq().min() for spec in res]
        freqs_max = [spec.get_freq().max() for spec in res]
        freq_min = min(freqs_min)
        freq_max = max(freqs_max)

        spec_mean = Spectrum()
        spec_mean.stats.begin = freq_min
        spec_mean.stats.delta = res[0].stats.delta
        spec_mean.stats.station = res[0].stats.station
        spec_mean.data_mag = None
        for spec in res:
            spec_slice = spec.slice(freq_min,
                                    freq_max,
                                    pad=True,
                                    fill_value=mag_to_moment(0))
            spec_slice.data_mag = moment_to_mag(spec_slice.data)
            norm = (spec_slice.data_mag != 0).astype(int)
            if spec_mean.data_mag is None:
                spec_mean.data_mag = spec_slice.data_mag
                norm_mean = norm
            else:
                spec_mean.data_mag += spec_slice.data_mag
                norm_mean += norm
        spec_mean.data_mag /= norm_mean
        spec_mean.data = mag_to_moment(spec_mean.data_mag)

        residual_mean.append(spec_mean)

        # plot traces
        if options.plot:
            stnm = spec_mean.stats.station
            figurefile = os.path.join(outdir, stnm + '-res.png')
            fig = plt.figure(dpi=160)
            for spec in res:
                plt.semilogx(spec.get_freq(), spec.data_mag, 'b-')
            plt.semilogx(spec_mean.get_freq(), spec_mean.data_mag, 'r-')
            plt.xlabel('frequency (Hz)')
            plt.ylabel('residual amplitude (obs - synth) in magnitude units')
            plt.title('residuals : ' + stnm + ', ' + str(len(res)) +
                      ' records')
            fig.savefig(figurefile, bbox_inches='tight')
            plt.close()

    # writes the mean residuals (the stations corrections)
    with open(os.path.join(outdir, 'residual_mean.pickle'), 'wb') as fp:
        pickle.dump(residual_mean, fp)
Esempio n. 13
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
Esempio n. 14
0
def write_output(config, sourcepar, sourcepar_err):
    """Write results to a plain text file and/or to a SQLite database file."""
    if len(sourcepar) == 0:
        logger.info('No source parameter calculated')
        ssp_exit()

    means = dict()
    errors = dict()
    means_weight = dict()
    errors_weight = dict()

    # Compute average source parameters
    # Mw
    Mw_values = np.array([x['Mw'] for x in sourcepar.values()])
    Mw_err = np.array([x['Mw'] for x in sourcepar_err.values()])
    means['Mw'], errors['Mw'] = _avg_and_std(Mw_values)
    means_weight['Mw'], errors_weight['Mw'] = \
        _avg_and_std(Mw_values, errors=Mw_err)

    # Mo (N.m)
    means['Mo'], errors['Mo'] = _M0_avg_and_std(means['Mw'], errors['Mw'])
    means_weight['Mo'], errors_weight['Mo'] = \
        _M0_avg_and_std(means_weight['Mw'], errors_weight['Mw'])

    # fc , hertz
    fc_values = np.array([x['fc'] for x in sourcepar.values()])
    fc_err = np.array([x['fc'] for x in sourcepar_err.values()])
    means['fc'], errors['fc'] = _avg_and_std(fc_values, logarithmic=True)
    means_weight['fc'], errors_weight['fc'] = \
        _avg_and_std(fc_values, errors=fc_err, logarithmic=True)

    # t_star
    t_star_values = np.array([x['t_star'] for x in sourcepar.values()])
    t_star_err = np.array([x['t_star'] for x in sourcepar_err.values()])
    means['t_star'], errors['t_star'] = _avg_and_std(t_star_values)
    means_weight['t_star'], errors_weight['t_star'] = \
        _avg_and_std(t_star_values, errors=t_star_err)

    # ra, radius (meters)
    vs_m = config.hypo.vs * 1000
    ra_values = 0.37 * vs_m / fc_values
    means['ra'], errors['ra'] = _avg_and_std(ra_values, logarithmic=True)

    # bsd, Brune stress drop (MPa)
    Mo_values = mag_to_moment(Mw_values)
    bsd_values = 0.4375 * Mo_values / np.power(ra_values, 3) * 1e-6
    means['bsd'], errors['bsd'] = _avg_and_std(bsd_values, logarithmic=True)

    # Ml
    # build Ml_values: use np.nan for missing values
    Ml_values = np.array([x.get('Ml', np.nan) for x in sourcepar.values()])
    Ml_values = Ml_values[~np.isnan(Ml_values)]
    if Ml_values.size:
        means['Ml'], errors['Ml'] = _avg_and_std(Ml_values)
    else:
        means['Ml'] = None
        errors['Ml'] = None

    # Er
    Er_values = np.array([x['Er'] for x in sourcepar.values()])
    means['Er'], errors['Er'] = _avg_and_std(Er_values, logarithmic=True)

    sourcepar['means'] = means
    sourcepar['errors'] = errors
    sourcepar['means_weight'] = means_weight
    sourcepar['errors_weight'] = errors_weight

    # Write to parfile
    _write_parfile(config, sourcepar, sourcepar_err)

    # Write to database, if requested
    _write_db(config, sourcepar)

    # Write to hypo file, if requested
    _write_hypo(config, sourcepar)

    params_name = ('Mw', 'fc', 't_star')
    sourcepar_mean = dict(
        zip(params_name, [means['Mw'], means['fc'], means['t_star']]))
    logger.info('params_mean: {}'.format(sourcepar_mean))
    sourcepar_mean_weight = dict(
        zip(params_name,
            [means_weight['Mw'], means_weight['fc'], means_weight['t_star']]))
    logger.info('params_mean_weighted: {}'.format(sourcepar_mean_weight))

    return sourcepar_mean