Mondaic

Salvus 0.11.48

Released: 2022-03-30

This release contains a long-sought feature for reducing the memory footprint of checkpoints for adjoint simulations by sampling the forward wavefield on a coarser grid during the adjoint run. This includes breaking changes for some of the inversion routines, which may require updating the syntax of some notebooks. Details are listed below. If you are concerned about upgrading, please get in touch with us in the user forum.

The release also contains bug fixes concerning the use of custom bm files in SalvusProject and attaching receivers on SmoothieSEM meshes, as well as the option to provide a custom scaling for the mapping function of the inversion.

Also, don’t forget to keep your python environment up-to-date! We recommend to always run

wget https://mondaic.com/environment.yml -O ~/environment.yml
conda env update -n salvus -f ~/environment.yml

before upgrading.

Changes

SalvusFlow

New TaskChain workflow primitive that can be used to run multiple Salvus jobs and/or Python scripts in a linear chain within a single local/ssh/HPC job.

This is a first implementation so expect some rough edges:

# Construct a forward simulation object.
w_forward = sn.simple_config.simulation.Waveform(
    ..., store_adjoint_checkpoints=True
)

# Construct an adjoint simulation object.
w_adjoint = sn.simple_config.simulation.Waveform(...)
...
# There is a new `PROMISE_FILE:` prefix to tell SalvusFlow that a file does
# not exist yet but it will exist when the simulation is run.
w_adjoint.adjoint.point_source_block = {
    "filename": "PROMISE_FILE:task_2_of_2/input/adjoint_source.h5",
    "groups": ["adjoint_sources"],
}

# Define a Python function that is run between forward and adjoint simulations
# to generate the adjoint sources.
def compute_adjoint_source(
    task_index: int, task_index_folder_map: dict[int, pathlib.Path]
):
    folder_forward = task_index_folder_map[task_index - 1]
    folder_adjoint = task_index_folder_map[task_index + 1]

    output_folder = folder_forward / "output"

    event = sn.EventData.from_output_folder(output_folder=output_folder)

    event_misfit = sn.EventMisfit(
        synthetic_event=event,
        misfit_function="L2_energy_no_observed_data",
        receiver_field="displacement",
    )

    input_folder_adjoint = folder_adjoint / "input"
    event_misfit.write(input_folder_adjoint / "adjoint_source.h5")


# Launch the task chain. It will serialize the Python function and launches
# everything either locally or in Slurm/other supported systems.
tc = sn.api.run_task_chain_async(
    site_name="local",
    tasks=[w_forward, compute_adjoint_source, w_adjoint],
    ranks=4,
)
# Wait until it finishes.
tc.wait()
SalvusFlow

More stable implementation of the rewritten receiver placement and it should now also work for highly distored meshes.

SalvusOpt

Add option for custom scaling in mapping function. The custom scaling parameters need to be defined on the same mesh as the simulation and provided as elemental fields for all parameters.

Example

mesh = p.simulations.get_mesh("sim")

# Modify scaling parameters for all fields
mesh.elemental_fields["VP"] = ...
mesh.elemental_fields["RHO"] = ...

m = Mapping(
  scaling=mesh,
  inversion_parameters=["M"],
  map_to_physical_parameters={"VP": "M", "RHO": "M"},
)
SalvusProject API CHANGE

Add proper serialization of bm files in SalvusProject. Previously, custom bm files were not added to the project and relied on external paths which prevented copying projects to other machines.

Adding custom background models as string will now throw a deprecation warning. It is strongly recommended to add custom bm files using the class model.background.one_dimensional.FromBm.

SalvusProject API CHANGE

Add option for wavefield compression during adjoint runs to reduce the memory overhead. In order to allocate the correct number of checkpoints, this setting needs to be available at the time of computing the forward wavefield. This setting can now be used in several locations listed below by adding

sn.WavefieldCompression(
      forward_wavefield_sampling_interval=N
  )

if checkpoints should be stored for a resampling interval of N during the adjoint run. forward_wavefield_sampling_interval=1 corresponds to no compression and is equivalent to what was done prior to this release.

This is a (small) breaking change that requires updating the syntax of inversion-related functionality!

new optional arguments:

The wavefield compression settings enter now as optional argument into some objects and functions. If not given, it will default to forward_wavefield_sampling_interval=1, which is consistent with Salvus <= 0.11.47.

Examples:

# Inversion actions:
p.action.inversion.compute_misfits(
  ...,
  derived_job_config=sn.WavefieldCompression(
      forward_wavefield_sampling_interval=5
  ),
  ...
)

p.action.inversion.compute_gradients(
  ...,
  wavefield_compression=sn.WavefieldCompression(
      forward_wavefield_sampling_interval=5
  ),
  ...
)

p.action.inversion.sum_gradients(
  ...,
  wavefield_compression=sn.WavefieldCompression(
      forward_wavefield_sampling_interval=5
  ),
  ...
)

# InverseProblemConfiguration
sn.InverseProblemConfiguration(
  ...
  wavefield_compression=sn.WavefieldCompression(
      forward_wavefield_sampling_interval=5
  ),
  ...
)

deprecated parameters:

The parameters store_adjoint_checkpoints in p.simulations.launch(...) and store_checkpoints in p.actions.inversion.compute_misfits(...) are deprecated. Instead, use

  derived_job_config=WavefieldCompression(
      forward_wavefield_sampling_interval=N
  ),

derived_job_config=None is the new default, which corresponds to the deprecated store_adjoint_checkpoints=False or store_checkpoints=False, respectively.

breaking changes:

compute_misfits and compute_gradients in p.action.inversion are now keyword-only functions. Additionally, we made the wavefield compression settings mandatory arguments of a few lower-level functions, such as:

p.misfits.get_gradient_filenames()
p.simulations.get_adjoint_input_files()
p.action.validation.validate_model_gradients()

In case you are using one of these directly, you will notice an error like

TypeError: compute_gradients() needs keyword-only argument
wavefield_compression

which can be fixed by just adding the wavefield_compression object shown above.