Mondaic

Salvus 0.11.16

Released: 2020-09-08

This release comes with several improvements regarding data selection and inverse modeling.

There are no breaking changes in the stable API. However, the window selection in SalvusProject has been completely rewritten, and is not compatible with previous versions.

The new DataSelectionConfiguration allows window- and station-dependent weights. Furthermore, there are several new options for visualization and quality control of misfits. See the Seismological Gradient tutorial in the Experimental section for several examples.

The mapping function has been extended to offer built-in support for a region of interest, and event-dependent masking of sources and receivers to mitigate singularities in the sensitivity kernels.

A new function for uniform mesh refinement and model interpolation has been added to SalvusMesh.

There are several more small changes documented below.

Changes

SalvusFlow

Added a new low level EventWindowAndWeightSet class. It is a per-event data structure that can be serialized to disc that is able to store interval windows (e.g. windows that have a start as well as an end time). Additionally it can store weights at the receiver, component, as well as the individual window level.

In most cases this will be utilized through SalvusProject but it can be used outside of it and it directly integrates with SalvusFlow’s Event and EventMisfit infrastructure.

SalvusMesh

Added ability to create SmoothieSEM meshes with surface as well as Moho topography, ellipticity and ocean loading. Includes several minor bugfixes to the SmoothieSEM meshes introduced in the previous release.

sm = sn.simple_mesh.SmoothieSEM()

sm.basic.model = "prem_iso_one_crust"
sm.basic.min_period_in_seconds = 200.0
sm.basic.elements_per_wavelength = 2.0
sm.basic.number_of_lateral_elements = 2

sm.advanced.tensor_order = 4

sm.source.latitude = 38.82
sm.source.longitude = 40.14

# Ellipticity.
sm.spherical.ellipticity = 0.0033528106647474805

# Surface topography.
sm.topography.topography_file = "topography_earth2014_egm2008_lmax_256.nc"
sm.topography.topography_varname = (
    "topography_earth2014_egm2008_lmax_256_lmax_16"
)

# Moho topography.
sm.topography.moho_topography_file = "moho_topography_crust_1_0_egm2008.nc"
sm.topography.moho_topography_varname = (
    "moho_topography_crust_1_0_egm2008_lmax_16"
)

# Ocean loading.
sm.ocean.bathymetry_file = "bathymetry_earth2014_lmax_256.nc"
sm.ocean.bathymetry_varname = "bathymetry_earth2014_lmax_256_lmax_16"
SalvusMesh

Added a function to uniformly refine any mesh to double its element count along each dimension and thus doubles its resolved frequency. This should work for arbitrarily complicated meshes including discontinuities. Material parameters will be interpolated onto the new elements.

from salvus.mesh.tools.transforms import uniformly_refine

new_mesh = uniformly_refine(mesh, tensor_order=2)

Changes to Experimental Features

SalvusCompute

Include the reference frame in the HDF5 gradient output files.

SalvusOpt

Added features for defining a region of interest and for cutting around sources and receivers in the adjoint mapping function.

SalvusProject

Implement p.viz.nb.domain() method for 2D box domains.

SalvusProject

Move job submission settings to the InverseProblemConfiguration and enable different site configurations for simulations and the preconditioner. The previous way of providing site_name, ranks_per_job and wall_time_per_job_in_seconds through the iterate and resume functions still work, but will be deprecated in the future.

SalvusProject

Add interactive misfit plots to the inverse problem Jupyter notebook widget.

SalvusProject

Better workflow management for misfit and gradient computations. The newly added functionality ensures consistent settings for site name and the number of ranks and automatically resubmits simulations if the checkpoints are no longer available.

SalvusProject

With very large models and / or meshes, interpolating volumetric models can take some time. Here a ‘verbose’ argument is now accepted by toolbox.interpolate_spherical, which will print a progress bar if the verbosity is 1 or greater. The verbose behavior is the default when generating spherical meshes with project.

SalvusProject

Better error handling and management of entities using UnstructuredMeshSimulationConfiguration objects.

SalvusProject

Adding an already existing inverse problem configuration again with exactly the same parameters will no longer throw.

SalvusProject API CHANGE

Projects created with an older Salvus version using the windows component cannot be imported anymore.

The windows component has been removed in favor of a more generic DataSelectionConfiguration. This is for one more in line with the rest of SalvusProject but also more powerful and flexible.

A direct consequence is that any function or object that took a window_set_name argument now takes a data_selection_configuration argument.

A DataSelectionConfiguration is either a function that is applied on the fly to select pieces of data (think generalized windows and weights) or one EventWindowAndWeightSet set per event (or a combination of both).

Projects that used an on-the-fly temporal weighting/windowing function now have to use it by creating a new DataSelectionConfiguration with an attached data_selection_function. The function itself does not have to be changed.

p.add_to_project(
    sn.DataSelectionConfiguration(
        name="mute_with_direct_phase",
        receiver_field="phi",
        data_selection_function=mute_with_direct_phase,
    )
)

The pick_windows() seismological action also requires changing window_set_name to data_selection_configuration (if that configuration does not yet exist, it will be created). In addition the window_taper_width_in_seconds argument is now exposed and required. It controls the width of the taper for the windows.

p.actions.seismology.pick_windows(
    data_selection_configuration="custom_windows",
    ...
    window_taper_width_in_seconds=10.0,
)

Salvus contains a lot of convenience functions to pick windows and weight individual receivers but it is also possible to directly operate on these data structures for full control.

# Get the data selection configuration.
dsc = p.entities.get(
    entity_type="data_selection_configuration", entity_name="custom_windows"
)
# If interval windows have been picked before an event, its
# `EventWindowAndWeightSet` can be retrieved.
ewws = dsc.get_event_window_and_weight_set(event=p.events.get("event_0000"))

This can than be used to exert detailed control on everything. Once this has been done it will be applied in all subsequent steps that utilize that particular DataSelectionConfiguration.

In  [1]: ewws.receivers
Out [1]:
{'XX.A0000.': {'receiver_weight': 1.5462720296177797,
  'components': {'Z': {'component_weight': 1.0,
    'windows': [{'window_start': 229.67554,
      'window_end': 1389.219377,
      'window_weight': 1.0}]},
...

In [2]: ewws.receivers["XX.A0000."]["receiver_weight"] = 0.7

In [3]: ewws.write(overwrite=True)
SalvusProject

Can now add weights to individual receivers and components which will then be applied to each misfit and adjoint source computation. This could have been previously done with a custom windowing function with weights but this is more convenient and easier to control. It is detailed in other changelog items of this release.

It can either happen via window picking and receiver weighting or directly in the data selection function:

def data_selection_function(st, receiver, sources):

    # Receiver and component weights.
    weights = {
        # This is optional but allows to specify additional weights
        # important for the misfit and adjoint source computations.
        "receiver_and_component_weights": {
            # Pass the receiver weight here.
            "receiver_weight": 0.75,
            # It is also possible to give weights to individual
            # receivers.
            "component_weights": {"X": 1.0, "Y": 0.8, "Z": 0.5},
        }
    }

    for tr in st:
        component = tr.stats.channel[-1]
        temporal_weights = compute_window(...)

        # Individual window weights.
        weights[component] = [
            {"values": temporal_weights, "misfit_weight": 0.8},
            ...
        ]

    return weights

# Add to the project.
p.add_to_project(
    sn.DataSelectionConfiguration(
        name="custom_data_selection",
        receiver_field="phi",
        data_selection_function=data_selection_function,
    ),
    overwrite=True,
)
SalvusProject

Added implementations for a few common seismological receiver weighting schemes with a general and easy to extend interface.

# Custom receiver weighting function.
def my_favorite_station(event, receivers, receiver_name):
    weights = {}
    for name in receivers:
        if name == receiver_name:
            weight = 2.0
        else:
            weight = 1.0
        weights[name] = weight
    return weights


p.actions.seismology.add_receiver_weights_to_windows(
    # Add the weights to existing windows in this data selection
    # configuration.
    data_selection_configuration="custom_windows",
    events=p.events.list(),
    # The weights for each item in the chain will be multiplied together.
    weighting_chain=[
        {
            "weighting_scheme": "ruan_et_al_2019",
            "function_kwargs": {"ref_distance_condition_fraction": 1.0 / 3.0},
        },
        {
            "weighting_scheme": "mute_near_source_receivers",
            "function_kwargs": {
                # All receivers closer than this to the sources
                # will have zero weight.
                "minimum_receiver_distance_in_m": 200e3,
                # All receivers further away than this will not be muted.
                "maximum_receiver_mute_distance_in_m": 1000e3,
                # How to go from weight 0 to weight 1 for receivers in the
                # transition region. Currently only "hanning" is supported.
                "taper_type": "hanning",
            },
        },
        # Custom weighting function.
        {
            "weighting_scheme": my_favorite_station,
            "function_kwargs": {"receiver_name": "XX.A0044."},
        },
    ],
    # Normalize the sum of all receiver weights to the receiver count.
    normalize=True,
)
SalvusProject

New seismological receiver weight map plot.

p.viz.seismology.receiver_weights_map(
    data_selection_configuration="custom_windows"
)
SalvusProject

Various part of SalvusProject can serialize functions to disc. This is now more powerful and easier to use as the serialized functions can now access closure values as well as other variables in the global scope of the functions which will also be serialized alongside. Thus external variables and imports will now work with these functions.

Basically it means that it should now just work in more cases without any extra work or rewrites.

SalvusProject

The p.viz.seismology.misfit_map() method is now a widget and the event can be selected interactively. Thus passing the event argument is no longer necessary.

SalvusProject

New method to visualize and compare histograms of misfits.

p.viz.misfit_histogram(
    simulation_configuration_a="initial_model_70_120_seconds",
    simulation_configuration_b=p.inversions.get_simulation_name(
            inverse_problem_configuration="inversion", iteration_id=25
    ),
    misfit_configuration="phase_misfit_70_120_seconds",
    events=p.events.list(),
    merge_all_components=False
)
SalvusProject

A few new utilities to analyze the statistics of interval windows.

One is the ability to get a pandas.DataFrame object containing statistics about all windows for a list of events. This is very useful to do some custom analysis and figure out which events to use in the final inversion.

df = p.entities.get(
    entity_type="data_selection_configuration", entity_name="selection_a"
).get_interval_window_statistics_table(events=p.events.get_all())

A styled version of this table with all events in a project can be visualized with:

p.viz.interval_window_statistics("selection_a")