Mondaic

Salvus 2025.1.2

Released: 2025-09-01

This is a minor release bringing in a range of improvements and bug fixes.

Notable changes include the ability to accurately compute a stable time step in Python prior to the simulation and new customization options for preconditioners and data selection routines.

This release also includes various internal improvements, bug fixes, and enhancements to stability and performance. Please refer to the detailed changelog for a complete list of changes.

Changes

SalvusCore

Support for NumPy 2.0 and later versions. Numpy 1 is also still supported.

To upgrade, please reinstall your Salvus Mamba environment following the instructions on docs.mondaic.com.

SalvusFlow

Fix a bug that would prevent the temporal interpolation of data with a time axis specified in UTC time.

SalvusFlow

Ensure that the order of dependent functions in serialized code is deterministic.

SalvusFlow

Improve error reporting for using task chains.

SalvusFlow

Fix gradient validation tests for simultaneously testing multiple events. p.actions.validation.validate_model_gradients() will now return dictionaries with the finite-difference approximation and errors of each event, as well as the aggegrated gradient from all events, which is available from the key cumulative.

SalvusMesh

Allow for a more ergonomic way to add topography, bathymetry, and ocean loading to smoothiesem meshes. In particular, a new function was added named add_spherical_topography_and_bathymetry_to_model in the _recipes.smoothiesem module which assists in adding topography to a smoothiesem mesh.

SalvusMesh API CHANGE

Improve the Python-side computation of time steps for meshes by adding UnstructuredMesh.compute_time_step. This method accounts for spectral wavefield order and defaults to the Courant numbers used in SalvusCompute: 0.6 for 2D simulations and 0.47 for 3D simulations. The method UnstructuredMesh.estimate_time_step has also been refined to produce results matching those in SalvusCompute, but still allows for computationally cheaper estimation of the time step.

SalvusOpt

Enable passing extra keywords arguments to a custom preconditioner callback.

Example:

# Custom preconditioner function with two extra keyword arguments `alpha` and
# `beta`.
def custom_preconditioner(unstructured_mesh, alpha, beta):
    # Apply a depth-dependent scaling
    scaling = (
        alpha * (1.0 + np.abs(unstructured_mesh.get_element_nodes()[..., -1]))
    ) ** beta
    inversion_parameters = ["VP", "VS", "RHO"]
    for field in inversion_parameters:
        unstructured_mesh.elemental_fields[field] *= scaling
    return unstructured_mesh

# When passing the function to an inverse problem configuration, make sure to
# also pass the extra keyword arguments.
sn.InverseProblemConfiguration(
    name="inversion",
    prior_model="prior_model",
    events=["event"],
    method=sn.TrustRegion(initial_trust_region_linf=0.1),
    misfit_configuration="L2",
    mapping=sn.Mapping(
        scaling="relative_deviation_from_prior",
        inversion_parameters=["VP", "VS", "RHO"],
    ),
    preconditioner=custom_preconditioner,
    extra_kwargs_preconditioner={
        "alpha": 1.0,
        "beta": 2.0,
    }
    job_submission=sn.SiteConfig("local", 4),
)
SalvusOpt

Enable passing extra keywords arguments to a custom temporal weights function.

Example:

def select_data(data_array, event, station_list, components):
    '''
    A temporal weights function that selects a subset of the data. It
    includes the extra keyword arguments `station_list` and `components`,
    specifying the components and stations that are kept.
    '''
    selection = [
        (station, component)
        for station, component in set(data_array.trace.data)
        if (component in components and station in station_list)
    ]
    weights = (data_array.sel(trace=selection)) * 0.0 + 1.0
    weights.coords["trace_weights"] = (
        ("trace",),
        np.ones(len(selection), dtype=np.float64),
    )
    return weights

# You can add this function to a project via a DataSelectionConfiguration
# where you would additionally pass a dictionary with the extra keyword
# arguments as so:
dsc = sn.DataSelectionConfiguration(
    name="custom_data_selection",
    receiver_field="displacement",
    data_selection_function=select_data,
    extra_kwargs_data_selection={
        "station_list": ["XX.AA1.", "XX.AA2."],
        "components": ["Z"],
    }
)

Changes to Experimental Features

SalvusFlow

Fix a bug in the SRF reader (standard rupture format) that caused seismic moments to be off by a factor 10.

All inputs from the SRF file are now converted already at the time of parsing the file, and the dictionary returned by _read_srf_file will contain all quantities in SI units.