Quick Start¶
The example below is a complete, self-contained Deepwave program. It builds a two-layer velocity model, propagates a single shot through it with the scalar wave equation, and backpropagates to obtain the gradient of a simple objective function with respect to the velocity model. It demonstrates the two things that make Deepwave useful: forward wave propagation, and the fact that the output is differentiable with respect to the inputs (here the model), so it plugs directly into PyTorch’s automatic differentiation and optimisers. If you are new to seismic/wave-equation terminology, keep the Glossary open alongside this page – it defines terms (such as PML, CFL, and FWI) the first time each is used below.
Install PyTorch for your CPU or CUDA device first (see Deepwave), then install Deepwave and the plotting dependency used below:
python -m pip install deepwave matplotlib
import torch
import deepwave
import matplotlib.pyplot as plt
v = 1500 * torch.ones(100, 100)
v[50:] = 2000
v.requires_grad_()
out = deepwave.scalar(
v, grid_spacing=4, dt=0.004,
source_amplitudes=deepwave.wavelets.ricker(25, 200, 0.004, 0.06, shape=(1, 1)),
source_locations=torch.tensor([[[0, 0]]]),
receiver_locations=torch.tensor([[[0, 99]]]),
pml_freq=25,
max_vel=2000,
)
(out.receiver_amplitudes**2).sum().backward()
_, ax = plt.subplots(1, 3, figsize=(9, 3))
ax[0].imshow(v.detach())
ax[0].set_title("Velocity model")
ax[1].plot(out.receiver_amplitudes.detach().flatten())
ax[1].set_title("Receiver data")
ax[2].imshow(v.grad.detach(), vmin=-1e-5, vmax=1e-5)
ax[2].set_title("Gradient")
plt.show()
A few things to note:
plt.show()only opens a window if a display is available. Running this on a headless machine (e.g. over SSH, or in a plain terminal with no X server), it does nothing and the script exits without complaint; replace it withplt.savefig("quickstart.png")to see the result in that case.Deepwave does not attach units to anything: it only requires that yours are consistent. These docs use SI throughout, which is what the numbers above are:
vis a wavespeed in metres per second,grid_spacingis a cell size in metres,dtis a time step in seconds, andpml_freq(below) is in hertz. The propagators that take a density take it in kilograms per cubic metre, and the elastic ones take \(\lambda\) and \(\mu\) in pascals and buoyancy (the reciprocal of density) in cubic metres per kilogram. Source and receiver amplitudes are a separate question – what physical quantity each propagator injects and records is set out in Units and source conventions.source_amplitudeshas shape[shot, source, time]andsource_locations/receiver_locationshave shape[shot, source_or_receiver, dimension]. Here there is one shot, one source, and one receiver.The locations are integer grid cell indices (
torch.long). You may also provide floating-point coordinates to place sources and receivers off-grid; see Usage.Deepwave orders spatial dimensions from slowest-varying to fastest-varying, and attaches no physical meaning to any of them: dimension 0 is depth only if you decide it is. These docs and examples put depth in dimension 0 throughout, which is why the location columns above read
[depth, x]. See Dimension order and the axis convention for the full rule, including what the_y/_z/_xcomponent suffixes mean and what changes when a 2-D script is promoted to 3-D.outis aPropagatorOutput, soout.receiver_amplitudesis the recorded receiver data, of shape[shot, receiver, time].outis still a plain tuple at its core, soout[-1]and unpacking (e.g.*wavefields, receivers = out) keep working the same way. The earlier elements are the final wavefields, plus oneauxiliary_stateentry (an opaqueAuxiliaryState, not a tensor) carrying the absorbing boundary’s memory variables – forscalarthis iswavefield_0, wavefield_m1, auxiliary_state. Pass the whole output back in asinitial_state=outto continue the propagation or to use it as an initial condition; see Usage.Because
v.requires_grad_()was called, the receiver data is differentiable with respect tov, sobackward()populatesv.grad. This is the foundation of full-waveform inversion and related methods. The objective here is receiver energy, not a data misfit: an inversion compares predictions with observations.max_vel=2000fixes the speed bound used for the time step and PML. At thisdt, CFL substepping makes the default model gradient a sampled approximation. Exact discrete model derivatives require additional conditions, including sampling every internal step and lossless storage; see What Deepwave guarantees about gradients before comparing gradients with finite differences.pml_freqis a frequency, in hertz (assumingdtis in seconds) – normally the dominant frequency of your source wavelet, 25 Hz here to match the Ricker wavelet above. Choose it poorly and the absorbing boundary works less well, reflecting energy back into the model instead of absorbing it. It is optional: leave it out and Deepwave infers it from the peak of your source amplitudes’ spectrum (25 Hz for the wavelet above), reporting the value it used asout.info.pml_freq. Give it explicitly, as here, for anything you publish – an inferred value ties the absorbing boundary to the wavelet, so changing the wavelet changes the boundary (see Choosing pml_freq).Unsure what
grid_spacinganddtto use? Ask:deepwave.suggest_discretisation(min_vel=1500, max_freq=25, ndim=2, max_vel=2000)
For this model that suggests
grid_spacing=10.0anddt=0.00212ataccuracy=4(the example above uses a finer 4 m grid, which is simply more accurate and more expensive), along with anotesaying which constraint was binding. See Accuracy and grid design.The source and receiver above (at grid cell 0 and cell 99) are placed right at the edge of the model. The default 20-cell PML is padding outside the model, so cell 0 is not inside it – it is at its inner edge, the first cell of the model proper. That is fine for a first run, but it is worth knowing: the absorbing layer assigns a disproportionate share of the gradient to the model cells next to it (see Large gradients at edges). See Boundary conditions for what the PML does and how to keep your survey away from it (or make an edge reflective instead) if that matters for your use case.
This example never moves
vor the other tensors to a GPU, so it runs on the CPU even if one is available; move them there yourself with.to(device)(see Full-Waveform Inversion (FWI) for the idiom) once you are ready to use one.
Elastic in ~10 lines¶
The elastic propagator has three model parameters (\(\lambda\), \(\mu\), and buoyancy, the reciprocal of density) instead of one, and separates its wavefield into velocity/stress components, but a minimal run looks much the same. This example converts from the more familiar \(v_p\)/\(v_s\)/\(\rho\) parameterisation using deepwave.vpvsrho_to_lambmubuoyancy(), runs one shot, and looks at the vertical particle velocity received at one point:
import torch
import deepwave
vp = 1500 * torch.ones(50, 50)
vs = 1000 * torch.ones(50, 50)
rho = 2200 * torch.ones(50, 50)
lamb, mu, buoyancy = deepwave.vpvsrho_to_lambmubuoyancy(vp, vs, rho)
out = deepwave.elastic(
lamb, mu, buoyancy, grid_spacing=4, dt=0.004,
source_amplitudes_y=deepwave.wavelets.ricker(25, 200, 0.004, 0.06, shape=(1, 1)),
source_locations_y=torch.tensor([[[0, 25]]]),
receiver_locations_y=torch.tensor([[[0, 49]]]),
pml_freq=25,
)
vy_receiver_data = out.receiver_amplitudes_y
Elastic has more outputs than scalar (one wavefield/receiver pair per velocity component, plus pressure), so out.receiver_amplitudes_y is used here to select the vertical-component receiver data by name rather than track its position in a plain tuple. For 2-D propagation the _y suffix denotes the in-plane vertical component (_x is the in-plane horizontal component; _z is only used in 3-D); see Elastic propagation and FWI for a full walkthrough and Usage for the complete set of components and the \(v_p\)/\(v_s\)/\(\rho\) \(\leftrightarrow\) \(\lambda\)/\(\mu\)/buoyancy conversion functions.
Once you have run this, FWI in one file is the next step: a complete full-waveform inversion in one file, with nothing to download. Reverse-Time Migration (RTM) does the same for reverse-time migration, and the Examples show progressively more realistic workflows (forward modelling on the Marmousi model, full-waveform inversion, migration, and more), and Usage documents the propagators in detail. If you are new to PyTorch, the PyTorch page covers the concepts that Deepwave relies on. For choosing the grid spacing and time step, see Accuracy and grid design; for the treatment of model edges, see Boundary conditions; for source/receiver amplitude conventions, see Units and source conventions; and if something goes wrong, see Troubleshooting.