Salvus 0.11.48
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
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()More stable implementation of the rewritten receiver placement and it should now also work for highly distored meshes.
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"},
)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.
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.