Full-Waveform Inversion
Part 4 - Iterations
%matplotlib inlineimport numpy as np
import os
import salvus.namespace as snSALVUS_FLOW_SITE_NAME = os.environ.get("SITE_NAME", "local")p = sn.Project(path="project")Step 6: Initialize the inverse problem
The inverse problem is a similar entity like all the other configuration objects. We want to be able to keep track of all the individual steps, and avoid unnecessary repetitions of the same task. Fortunately, SalvusProject takes care of data management and book keeping.
We need to specify the prior, which is just the SimulationConfiguration object of the initial model we created above. Furthermore, we need to specify all possible events that we might consider during the inversion. This could be a subset of events defined in the project, and we could add more events later on.
Together with the events, we need to pass the observed data. Because we created it synthetically, this is also just a SimulationConfiguration object. The remaining parameters specify which parameters to invert for (VP and RHO), what misfit functional to use, preconditioner and descent method, and where to run the simulations.
p.add_to_project(
sn.InverseProblemConfiguration(
name="my_inversion",
prior_model="initial_model",
events=p.events.list(),
mapping=sn.Mapping(
scaling="absolute", inversion_parameters=["VP", "RHO"]
),
preconditioner=sn.ConstantSmoothing({"VP": 0.01, "RHO": 0.01}),
method=sn.TrustRegion(initial_trust_region_linf=10.0),
misfit_configuration="L2",
wavefield_compression=sn.WavefieldCompression(
forward_wavefield_sampling_interval=10
),
job_submission=sn.SiteConfig(
site_name=SALVUS_FLOW_SITE_NAME, ranks_per_job=4
),
)
)Instead of monolithic inversions and a linear flow of iteration, SalvusOpt uses a tree-based framework of iteration. At any point during the inversion, you can branch off, modify the settings, and run one or more streams simultaneously.
Ready for our first iteration? Without specifying any additional information, all parameters will be inherited from the InverseProblemConfiguration.
p.inversions.add_iteration(inverse_problem_configuration="my_inversion")[2026-10-12 21:06:57,003] INFO: Adding new iteration #0.
True
Step 7: Update the model
Many steps within a single iteration involve expensive simulations, e.g., for computing misfits or adjoint simulations to compute gradients. In order to be able to closely monitor the progress, SalvusOpt steps through an iteration, and automatically dispatches simulations whenever necessary. The function resume will return whenever SalvusOpt is waiting for other tasks to finish first. Calling it several time, will step through the iteration in sequence.
p.inversions.resume(
inverse_problem_configuration="my_inversion",
)[2026-10-12 21:06:57,042] INFO: Resuming iteration #0. Current stage: initialize [2026-10-12 21:06:57,047] INFO: 1 new tasks have been issued. [2026-10-12 21:06:57,050] INFO: Processing task `misfit_and_gradient` [2026-10-12 21:06:57,053] INFO: -> Mapping between inversion and simulation space: 1 ms [2026-10-12 21:06:57,182] INFO: While looking for checkpoints, the following error occured: Remote input file `/builds/Mondaic/Code/integrations/TutorialsAndIntegrationTests/SALVUS_INSTALL/SalvusFlow/tmp/job_array_2610122106970286_6b6c1dc040/job_0_of_job_array_2610122106970286_6b6c1dc040/output.h5` does not exist.. The simulations will be resubmitted. [2026-10-12 21:06:57,184] INFO: While looking for checkpoints, the following error occured: Remote input file `/builds/Mondaic/Code/integrations/TutorialsAndIntegrationTests/SALVUS_INSTALL/SalvusFlow/tmp/job_array_2610122106970286_6b6c1dc040/job_1_of_job_array_2610122106970286_6b6c1dc040/output.h5` does not exist.. The simulations will be resubmitted. [2026-10-12 21:06:57,188] INFO: While looking for checkpoints, the following error occured: Remote input file `/builds/Mondaic/Code/integrations/TutorialsAndIntegrationTests/SALVUS_INSTALL/SalvusFlow/tmp/job_array_2610122106970286_6b6c1dc040/job_2_of_job_array_2610122106970286_6b6c1dc040/output.h5` does not exist.. The simulations will be resubmitted. [2026-10-12 21:06:57,192] INFO: While looking for checkpoints, the following error occured: Remote input file `/builds/Mondaic/Code/integrations/TutorialsAndIntegrationTests/SALVUS_INSTALL/SalvusFlow/tmp/job_array_2610122106970286_6b6c1dc040/job_3_of_job_array_2610122106970286_6b6c1dc040/output.h5` does not exist.. The simulations will be resubmitted. [2026-10-12 21:06:57,193] INFO: While looking for checkpoints, the following error occured: Remote input file `/builds/Mondaic/Code/integrations/TutorialsAndIntegrationTests/SALVUS_INSTALL/SalvusFlow/tmp/job_array_2610122106970286_6b6c1dc040/job_4_of_job_array_2610122106970286_6b6c1dc040/output.h5` does not exist.. The simulations will be resubmitted. [2026-10-12 21:06:57,196] 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'] [2026-10-12 21:06:57,222] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:06:57,649] INFO: Launched simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:06:57,655] INFO: Some tasks of iteration #0 are still running. Please check again later.
True
p.inversions.resume(
inverse_problem_configuration="my_inversion",
)[2026-10-12 21:06:57,674] INFO: Resuming iteration #0. Current stage: check_convergence [2026-10-12 21:06:57,675] INFO: Processing task `misfit_and_gradient` [2026-10-12 21:06:57,680] INFO: -> Mapping between inversion and simulation space: 0 ms [2026-10-12 21:06:58,293] INFO: Some tasks of iteration #0 are still running. Please check again later.
False
p.viz.nb.iteration(
inverse_problem_configuration="my_inversion", iteration_id=0
)If you feel confident and don’t want to be bothered with every single task, you can also tell SalvusOpt to run an entire iteration at once. Note the parameter timeout_in_seconds, which will force the cell to return even if the iteration has not been completed yet, and there might still be a few simulations running in the back.
Again, you can execute the cell several times or mix it with calls to the previous one until the iteration is complete.
p.inversions.iterate(
inverse_problem_configuration="my_inversion",
timeout_in_seconds=360,
ping_interval_in_seconds=10,
)
p.viz.nb.inversion(inverse_problem_configuration="my_inversion")[2026-10-12 21:06:58,654] INFO: Resuming iteration #0. [2026-10-12 21:06:58,655] INFO: Processing task `misfit_and_gradient` [2026-10-12 21:06:58,786] INFO: Some tasks of iteration #0 are still running. Please check again later. [2026-10-12 21:07:08,788] INFO: Processing task `misfit_and_gradient` [2026-10-12 21:07:09,361] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:07:09,483] INFO: Launched adjoint simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:07:09,487] INFO: Some tasks of iteration #0 are still running. Please check again later. [2026-10-12 21:07:19,490] INFO: Processing task `misfit_and_gradient` [2026-10-12 21:07:20,329] INFO: Iteration 0: Number of events: 5 chi = 0.016385616624463036 ||g|| = 0.020187598686424082 pred = --- ared = --- norm_update = --- tr_radius = --- [2026-10-12 21:07:20,333] INFO: 1 new tasks have been issued. [2026-10-12 21:07:20,334] INFO: Processing task `preconditioner` [2026-10-12 21:07:20,507] INFO: Some tasks of iteration #0 are still running. Please check again later. [2026-10-12 21:07:30,529] INFO: Processing task `preconditioner` [2026-10-12 21:07:30,682] INFO: 1 new tasks have been issued. [2026-10-12 21:07:30,685] INFO: Processing task `misfit` [2026-10-12 21:07:30,757] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:07:31,441] INFO: Launched simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:07:31,445] INFO: Some tasks of iteration #0 are still running. Please check again later. [2026-10-12 21:07:41,485] INFO: Processing task `misfit` [2026-10-12 21:07:42,564] INFO: old misfit control group: 0.016385616624463036 new misfit control group: 0.007147293367325605 predicted reduction control group: -0.00784755353561195 actual reduction control group: -0.00923832325713743 5 out of 5 event(s) improved the misfit. [2026-10-12 21:07:42,567] INFO: Model update accepted. [2026-10-12 21:07:42,568] INFO: 1 new tasks have been issued. [2026-10-12 21:07:42,569] INFO: Processing task `finalize_iteration` [2026-10-12 21:07:42,629] INFO: Successfully completed iteration #0. [2026-10-12 21:07:42,634] INFO: Adding new iteration #1.
There is still quite a strong imprint of the source locations, but we start moving into the right direction. The high velocity inclusion in the top part also shows up in the density update.
Step 8: Automate
The first update gave us confidence in the setup. For the moment, our work here is done. Let’s run a few more iterations, lean back and wait for the results.
We just loop over the iterate function and perform two more model updates. Intermediate data can pile up quickly during an inversion. Each iteration computes waveforms and gradients for a list of events and creates additional auxiliary files for preconditioning and approximating the curvature. When running low on disk-space it is thus advisable to remove those files using the argument delete_disposable_files. It will automatically clear data from waveforms, gradients and auxiliary files for completed iterations, which are no longer needed to continue with the inversion.
This operation is safe in the sense that those data can always be recreated with the provenance information about the model and the simulation. Note, however, that recomputing would involve additional simulations.
for i in range(2):
p.inversions.iterate(
inverse_problem_configuration="my_inversion",
timeout_in_seconds=360,
ping_interval_in_seconds=10,
delete_disposable_files="all",
)[2026-10-12 21:07:48,360] INFO: Resuming iteration #1. [2026-10-12 21:07:48,360] INFO: 1 new tasks have been issued. [2026-10-12 21:07:48,361] INFO: Processing task `gradient` [2026-10-12 21:07:48,526] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:07:48,637] INFO: Launched adjoint simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:07:48,644] INFO: Some tasks of iteration #1 are still running. Please check again later. [2026-10-12 21:07:58,652] INFO: Processing task `gradient` [2026-10-12 21:07:59,355] INFO: Iteration 1: Number of events: 5 chi = 0.007147293367325606 ||g|| = 0.00967324834204606 pred = -0.00784755353561195 ared = -0.00923832325713743 norm_update = 0.8696898963294257 tr_radius = 0.8696898285635942 [2026-10-12 21:07:59,377] INFO: 1 new tasks have been issued. [2026-10-12 21:07:59,378] INFO: Processing task `preconditioner` [2026-10-12 21:07:59,515] INFO: Some tasks of iteration #1 are still running. Please check again later. [2026-10-12 21:08:09,551] INFO: Processing task `preconditioner` [2026-10-12 21:08:09,722] INFO: 1 new tasks have been issued. [2026-10-12 21:08:09,723] INFO: Processing task `misfit` [2026-10-12 21:08:09,778] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:08:09,973] INFO: Launched simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:08:09,977] INFO: Some tasks of iteration #1 are still running. Please check again later. [2026-10-12 21:08:20,057] INFO: Processing task `misfit` [2026-10-12 21:08:21,121] INFO: old misfit control group: 0.007147293367325605 new misfit control group: 0.00536331951589388 predicted reduction control group: -0.000913511215003382 actual reduction control group: -0.001783973851431725 5 out of 5 event(s) improved the misfit. [2026-10-12 21:08:21,122] INFO: Model update accepted. [2026-10-12 21:08:21,128] INFO: 1 new tasks have been issued. [2026-10-12 21:08:21,129] INFO: Processing task `finalize_iteration` [2026-10-12 21:08:21,249] INFO: ... searching for obsolete files in project/INVERSIONS/my_inversion/00001 [2026-10-12 21:08:21,314] INFO: Freed up 3.3 MB of space. [2026-10-12 21:08:21,318] INFO: Successfully completed iteration #1. [2026-10-12 21:08:21,323] INFO: Adding new iteration #2. [2026-10-12 21:08:21,334] INFO: Resuming iteration #2. [2026-10-12 21:08:21,335] INFO: 1 new tasks have been issued. [2026-10-12 21:08:21,338] INFO: Processing task `gradient` [2026-10-12 21:08:21,540] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:08:21,644] INFO: Launched adjoint simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:08:21,650] INFO: Some tasks of iteration #2 are still running. Please check again later. [2026-10-12 21:08:31,652] INFO: Processing task `gradient` [2026-10-12 21:08:32,251] INFO: Iteration 2: Number of events: 5 chi = 0.005363319515893881 ||g|| = 0.006710484855649243 pred = -0.000913511215003382 ared = -0.001783973851431725 norm_update = 0.23593330459627415 tr_radius = 1.7393796571271884 [2026-10-12 21:08:32,283] INFO: 1 new tasks have been issued. [2026-10-12 21:08:32,284] INFO: Processing task `preconditioner` [2026-10-12 21:08:32,426] INFO: Some tasks of iteration #2 are still running. Please check again later. [2026-10-12 21:08:42,443] INFO: Processing task `preconditioner` [2026-10-12 21:08:42,656] INFO: 1 new tasks have been issued. [2026-10-12 21:08:42,659] INFO: Processing task `misfit` [2026-10-12 21:08:42,727] INFO: Submitting job array with 5 jobs ... [2026-10-12 21:08:43,859] INFO: Launched simulations for 5 events. Please check again to see if they are finished. [2026-10-12 21:08:43,862] INFO: Some tasks of iteration #2 are still running. Please check again later. [2026-10-12 21:08:53,919] INFO: Processing task `misfit` [2026-10-12 21:08:55,109] INFO: old misfit control group: 0.00536331951589388 new misfit control group: 0.0035375487295140396 predicted reduction control group: -0.0012253801560291322 actual reduction control group: -0.0018257707863798401 5 out of 5 event(s) improved the misfit. [2026-10-12 21:08:55,113] INFO: Model update accepted. [2026-10-12 21:08:55,117] INFO: 1 new tasks have been issued. [2026-10-12 21:08:55,118] INFO: Processing task `finalize_iteration` [2026-10-12 21:08:55,190] INFO: ... searching for obsolete files in project/INVERSIONS/my_inversion/00002 [2026-10-12 21:08:55,270] INFO: Freed up 3.3 MB of space. [2026-10-12 21:08:55,273] INFO: Successfully completed iteration #2. [2026-10-12 21:08:55,281] INFO: Adding new iteration #3.
Done!
Did we converge towards the ground truth?
p.viz.nb.inversion(inverse_problem_configuration="my_inversion")Well, at least we are getting closer and the heterogeneities start to form. You can, of course, continue with more iterations or play around with the settings.
That’s the end of this tutorial. We ran a few iterations of full-waveform inversion in a typical aperture for ultrasound screenings. Note that although the problem size is small and we did not apply any sophisticated inversion technique, we were able to create a suitable initial model and to perform a few model update with just a few lines of code. Nothing would change really if we applied this at a larger scale. By changing the site name to a remote site with more compute capabilities, we could easily scale up the problem.