Version:
This tutorial is presented as Python code running inside a Jupyter Notebook, the recommended way to use Salvus. To run it yourself you can copy/type each individual cell or directly download the full notebook, including all required files.

Full-Waveform Inversion

Part 3 - Sensitivities and smoothing

Copy
%matplotlib inline
# This notebook will use this variable to determine which
# remote site to run on.
import os
import time
import salvus.namespace as sn

SALVUS_FLOW_SITE_NAME = os.environ.get("SITE_NAME", "local")
p = sn.Project(path="project")
Before triggering the iterations, most applications necessitate at least some amount of quality control, which makes it inevitable to run a few simulations manually. Let start by computing some misfits for the initial model.
Wait!
Do we actually need to run the simulations again? We have already computed shot gathers for the initial model, haven't we? Let's see what the function call will do.
p += sn.MisfitConfiguration(
    name="L2",
    observed_data="true_model_100kHz",
    misfit_function="L2",
    receiver_field="phi",
)
print(
    p.actions.inversion.compute_misfits(
        simulation_configuration="initial_model",
        misfit_configuration="L2",
        events=p.events.list(),
        store_checkpoints=False,
        site_name=SALVUS_FLOW_SITE_NAME,
        ranks_per_job=4,
    )
)
{'event_0000': 0.023497037053311104, 'event_0001': 0.01951971930758876, 'event_0002': 0.016728040452719597, 'event_0003': 0.012159438217321714, 'event_0004': 0.016546150670016135}
Hm. We did not include any output in the previous run, but since we are now expecting to run an adjoint simulation soon, we had to rerun the forward simulations to store some checkpoints.
If you closely study the output, you will notice that we did not repeat any simulations this time, but just queries the precomputed values.
What's next? Sensitivities!
while not p.actions.inversion.compute_gradients(
    simulation_configuration="initial_model",
    misfit_configuration="L2",
    wavefield_compression=sn.WavefieldCompression(
        forward_wavefield_sampling_interval=10
    ),
    events=p.events.list(),
    site_name=SALVUS_FLOW_SITE_NAME,
    ranks_per_job=4,
):
    time.sleep(10.0)
[2024-03-15 09:24:54,621] INFO: The following events have been simulated before, but checkpoints are not available for this combination of `site_name` and `ranks_per_job` and wavefield compression settings. They will be run again: ['event_0000', 'event_0001', 'event_0002', 'event_0003', 'event_0004']
[2024-03-15 09:24:54,652] INFO: Submitting job array with 5 jobs ...

[2024-03-15 09:24:55,001] INFO: Launched simulations for 5 events. Please check again to see if they are finished.
[2024-03-15 09:25:05,550] INFO: Submitting job array with 5 jobs ...
Uploading 1 files...
Uploading 1 files...
Uploading 1 files...
Uploading 1 files...
Uploading 1 files...

[2024-03-15 09:25:05,603] INFO: Launched adjoint simulations for 5 events. Please check again to see if they are finished.
[2024-03-15 09:25:15,820] INFO: 5 events have already been submitted. They will not be submitted again.
This function returns individual gradients, which can be useful for event-dependent or batch inversions. Again, take a close look at the verbose output. The forward runs were cached and not repeated to obtain the gradients.
Even better, in 2D you can immediately visualize them in a widget.
p.viz.nb.gradients(
    simulation_configuration="initial_model",
    misfit_configuration="L2",
    wavefield_compression=sn.WavefieldCompression(
        forward_wavefield_sampling_interval=10
    ),
    events=p.events.list(),
)
We can also obtain the summed gradient over all selected events in one go.
gradient = p.actions.inversion.sum_gradients(
    simulation_configuration="initial_model",
    misfit_configuration="L2",
    wavefield_compression=sn.WavefieldCompression(
        forward_wavefield_sampling_interval=10
    ),
    events=p.events.list(),
)
gradient
<salvus.mesh.unstructured_mesh.UnstructuredMesh at 0x7fb444677ad0>
There is still quite a strong imprint of the source locations in the gradient. Some smoothing will help to obtain better updates.
p.actions.inversion.smooth_model(
    model=gradient,
    smoothing_configuration=sn.ConstantSmoothing(
        smoothing_lengths_in_meters={
            "VP": 0.01,
            "RHO": 0.01,
        },
    ),
    ranks_per_job=4,
    site_name=SALVUS_FLOW_SITE_NAME,
)

<salvus.mesh.unstructured_mesh.UnstructuredMesh at 0x7fb444761510>
Smooth. Feel free to play around with the smoothing length to see the effects on the gradient. Indeed, the smoothed gradient looks much better. We will use a similar concept as a preconditioner encapsulated in the optimization method.
This toy problem fortunately does not require thorough QC and with the initial waveform fits and gradients, we feel prepared to start the inversion.
PAGE CONTENTS