Usage

This page illustrates how to calculate misfit and generate adjoint sources using Pyadjoint. Pyadjoint is mainly dependent on ObsPy for seismic data handling.

import obspy
import pyadjoint

Pyadjoint expects data to be fully preprocessed beforehand. Observed and synthetic data are expected to have exactly the same length, sampling rate, and spectral content. It is up to the User to input correctly formatted data.

Pyadjoint is generic and does not care what traces are provided. Despite this, we will talk about traces as “observed” and “synthetic.”

Example Data

The package comes with example data used for illustrative and debugging purposes. See the Example Dataset page for an explanation of where this data came from.

obs, syn = pyadjoint.get_example_data()

# Pyadjoint requires individual components, not data streams
# Data are available in R, T and Z components
obs = obs.select(component="Z")[0]
syn = syn.select(component="Z")[0]

Config

Each misfit function requires a corresponding configuration class to control optional processing parameters. The get_config() function provides a wrapper for grabbing the appropriate Config object.

config = pyadjoint.get_config(adjsrc_type="waveform", min_period=20.,
                              max_period=100.)

See the adjoint source type list for the definitive names of available adjoint sources (adjsrc_type). The navigation bar to the left also provides explanations of each adjoint source.

Many types of adjoint sources have additional arguments that can be passed to it. See the config page for available keyword arguments and descriptions.

config = pyadjoint.get_config(adjsrc_type="waveform", min_period=20.,
                              max_period=100., taper_percentage=0.3,
                              taper_type="cos")

Calculating Adjoint Sources

Essentially all of Pyadjoint’s functionality is accessed through its central calculate_adjoint_source() function. This function takes the previously defined Config class, the observed and synthetic waveforms, and a list of time windows.

adj_src = pyadjoint.calculate_adjoint_source(
    config=config,
    # Pass observed and synthetic data traces.
    observed=obs, synthetic=syn,
    # List of window borders in seconds since the first sample.
    windows=[(800., 900.)]
    )

The function returns an AdjointSource object which has a number of useful attributes for understanding misfit.

>>> print(adj_src)
'waveform_misfit' Adjoint Source for channel MXZ at station SY.DBO
    misfit: 4.263e-11
    adjoint_source: available with 3600 samples
    windows: generated with 1 windows
# Access misfit and adjoint sources. The misfit is a floating point number.
>>> print(adj_src.misfit)
4.263067857359352e-11
# The adjoint source is a a numpy array.
>>> print(adj_src.adjoint_source)
[0. 0. 0. ... 0. 0. 0.]
# Time windows used to generate the array are stored
>>> print(adj_src.windows)
[(800.0, 900.0)]
# Misfit stats for each window are also stored
>>> print(adj_src.window_stats)
[{'type': 'waveform', 'left': 800.0, 'right': 901.0, 'misfit': 4.263067857359352e-11, 'difference': 1.519230269510467e-08}]

Time Windows

Time windows are typically used in misfit quantification to isolate portions of waveforms that include signals of interest.

Individual time windows represent the start and end time (units: s) of a window in which to consider waveform misfit, and multiple overlapping time windows can be included in the final adjoint source.

For example, to include multiple windows:

windows = [(0, 100), (200, 500), (325, 552)]
adj_src = pyadjoint.calculate_adjoint_source(
    config=config, observed=obs, synthetic=syn, windows=windows
    )

To calculate misfit on the entire trace, we need to consider all time steps in the trace:

windows = [(0, int(obs.stats.npts * obs.stats.dt)]

Double Difference Measurements

Double difference misfit functions, defined by [Yuan2016], construct misfit and adjoint sources from differential measurements between stations to reduce the influence of systematic errors from source and stations. ‘Differential’ is defined as “between pairs of stations, from a common source.”

Double difference measurements require a second set of observed and synthetic waveforms, as well as windows for this second set of waveforms. The new windows can be independent of the first set of windows, but must contain the same number of windows. Each window will be compared in order.

Note

In the following code snippet, we use the ‘R’ component of the same station in lieu of waveforms from a second station. In practice, the second set of waveforms should come from a completely different station.

obs_2, syn_2 = pyadjoint.get_example_data()
obs_2 = obs_2.select(component="R")[0]
syn_2 = syn_2.select(component="R")[0]

adj_src, adj_src_2 = pyadjoint.calculate_adjoint_source(
    config=config, observed=obs, synthetic=syn, windows=[(800., 900.)]
    observed_2=obs_2, synthetic_2,syn_2, windows_2=[(800., 900.,)]
    )

Note

Double difference misfit functions result in two adjoint sources, one for each station in the pair of waveforms. Valid double difference adjoint source types will end in _dd.


Plotting Adjoint Sources

All adjoint source types can be plotted during calculation. The type of plot produced depends on the type of misfit measurement and adjoint source.

pyadjoint.calculate_adjoint_source(config=config, observed=obs,
                                   synthetic=syn, plot=True,
                                   plot_filename="./waveform_adjsrc.png");

Saving to Disk

One of course wants to serialize the calculated adjoint sources to disc at one point in time. You need to pass the filename and the desired format as well as some format specific parameters to the write() method of the AdjointSource object. Instead of a filename you can also pass an open file or a file-like object. Please refer to its documentation for more details.

adj_src.write(filename="NET.STA.CHA.adj_src",
              format="SPECFEM", time_offset=-10)