Capability matrix¶
Deepwave’s nine propagators share a common calling convention but do not all support the same options: which dimensionalities, which backend values, which boundary faces, mpml_fraction, surface_method, batching (torch.func.vmap), second-order differentiation (create_graph), forward-mode AD (torch.func.jvp), and the callback hooks. The tables below answer “does propagator X support Y?” in one place. Use this page once you already know which propagator you want, to check whether a specific option is available on it before relying on the combination.
Every shared option is accepted on every propagator. mpml_fraction, surface_method, q_freq, q_n_mechanisms and q_band appear in all nine signatures, at the same default, so that a wrapper can forward one uniform set of keyword arguments to whichever propagator it was handed without first asking which options that propagator implements. The one exception is that viscoacoustic requires q_freq (unless q_n_mechanisms > 1, which requires q_band and rejects q_freq): a quality factor without the frequency it was specified at is not an attenuation model, so there is no default for a wrapper to forward. What the columns below report is therefore whether a non-default value is acted on: a value the propagator cannot act on raises a DeepwaveValueError naming the option, the propagator, and the propagators that do implement it. That is the distinction the “yes”/”no” cells make.
The cells are per-option answers, not an unrestricted product of rows and columns. The backend selected for one option can rule out another. For example, elastic_born(mpml_fraction>0) uses eager under backend="auto"; adding storage_mode="disk", lossy compression or a backward callback is then rejected because eager does not implement those facilities. The runtime guards decide the full call and raise rather than silently dropping an option.
How this page stays accurate. The generated table reads declared support from PropagatorSpec and the finite set of family contracts in deepwave._capabilities. Building documentation does not run a propagation or infer support from a caught exception. tests/test_capabilities.py compares runtime probes with those declarations and independently checks selected option intersections. A promised feature that raises unexpectedly therefore fails validation instead of disappearing from this table. Each individual cell describes a supported contract; the runtime guards still decide combinations of options.
A few columns need more explanation than a table cell can hold:
- Status
The propagator’s stability tier, as defined by the Stability policy policy. It is a documentation label:
experimentalpropagators are exported, documented and tested exactly like the others, and the interface promise Deepwave makes is identical on all nine – no call emits a warning about its own maturity, and nothing on any of the nine is renamed, removed or reordered after0.1.0. What the label means is that the propagator has never appeared in a release, so0.1.0is the first time real use meets its design. The five propagators that have never appeared in a release –acoustic_born,acoustic_vti,viscoacoustic,elastic_bornandelastic_vti– areexperimental; the four released inv0.0.27(scalar,scalar_born,acoustic,elastic) arestable. Thedeepwave.transducersmodule and theelastic/Elasticsurface_method="pm"option areprovisional– their shape may still be corrected, and never without a release’s notice – but are not rows in this table (transducersis not a propagator;surface_method="pm"is one option of the stableelasticpropagator, not the whole propagator) – see Stability policy for the full per-name list.- mpml_fraction
Implemented only by the propagators whose physics can actually provoke the CFS-C-PML corner instability M-PML cures: the elastic family and
acoustic_vti(the pseudo-acoustic reduction ofelastic_vti). The isotropic scalar/acoustic propagators – includingacoustic_bornandviscoacoustic– have no shear waves and no free-surface waveguide mode (see Boundary conditions), so there is nothing for it to cure there: they acceptmpml_fraction=0.0and raiseDeepwaveValueErroron anything else. Where it is implemented it is not always backend-uniform:elastic_bornaccepts the keyword but its compiled M-PML forward kernel is not implemented yet, sompml_fraction > 0there runs on the eager backend.backend="auto"selects it for you (out.info.backendthen reads"eager"); onlybackend="compiled", which asserts that the compiled kernel ran, raises. Every othermpml_fraction-supporting propagator accepts it on every backend.- Storage modes
storage_modecontrols where wavefield snapshots needed for the backward pass are kept ("device","cpu","disk", or"none"to keep no history, which rules out model gradients). The compiled backend’sStorageManagersupports all four for every propagator that has a compiled backend at all; the pure-Pythoneager/torch_compilebackends run their own native-autograd time loop with noStorageManagerand so accept"device"only, for every propagator. Usebackend="compiled"to require offload or compression;"auto"also selects it where all requested options have compiled implementations. The two Born families additionally reject every differentiable input understorage_mode="none".- Backends
backend="auto"(the default) selects native C/CUDA execution with the explicit elastic-Born M-PML fallback described below. It does not search backends for arbitrary option combinations or autodiff transforms. Requestbackend="eager"explicitly for a supported eager-only case.Two things make that choice visible rather than hidden.
out.info.backendreports the backend that actually ran (never"auto"), which is machine-checkable inside a training loop or a distributed worker, where a warning would not be seen. Andbackend="compiled"turns the assumption into an assertion.elastic_bornwithmpml_fraction > 0is the automatic fallback implemented today, so"auto"resolves to"eager"for that call and to the compiled backend for every otherelastic_borncall. The selected backend’s restrictions still apply; for example, eager M-PML cannot be combined with native disk storage.backend="compiled"raisesdeepwave.DeepwaveNotImplementedError, naming the propagator, dtype, ndim, device and the option at fault, wherever the compiled kernel is unavailable, rather than resolving around it the way"auto"does."torch_compile"(torch.compileover the same eager reference implementation) is available for every propagator, since it only needs the eager reference kernels themselves to be traceable. Batching (vmap) and forward-mode AD (jvp) behave differently again on the compiled backend – see those columns below.- Boundary faces accepted
Which
boundaryface configurations the propagator accepts and runs. Each face type is called on its own (boundary=["free"] * 4and so on, withpml_width=0on every face for the non-absorbing types), and then one mixed per-face combination is called as well, since accepting each type individually is not by itself evidence that they can be combined.freeandrigidare each probed twice, on every face and on the low faces alone (the beginning of each axis, with the high faces left as zero-widthpml), because that is the distinction Deepwave makes: a guard-ring fill that conserves a discrete energy on the low side of an axis need not conserve on the high side. Every one of those calls is made on both the default and theeagerbackend, and the cell names the backends that ran it. See Boundary conditions for what each face type means and for its measured reflection coefficient.This column is probed at the default
accuracyof 4. The low-face restriction onfree(acoustic family) and onrigid(elastic family) applies fromaccuracy=4upwards only; ataccuracy=2those propagators accept the face on either side of an axis. Which faces each propagator accepts states the whole rule, and is the page to read before relying on a face.“Accepted” is not an accuracy guarantee. The probe behind this column runs a handful of time steps on a 10-cell grid: enough to establish that a configuration is implemented and does not raise, and nothing more. It is, however, now a statement about long-run stability as well, because a face Deepwave accepts is one whose fill was measured to conserve a discrete energy: a face that does not conserve raises
deepwave.DeepwaveNotImplementedErrorduring argument validation instead of running and growing. The measurement isbenchmarks/boundary_energy.py.- Batching (vmap)
torch.func.vmapover a propagator’s property models (velocity, Lamé parameters, stiffnesses) works on the eager backend for every propagator exceptacoustic_bornandelastic_born, which currently rejectvmapover any of their model tensors outright. This is a real gap relative to their non-Born siblings and toscalar_born, which does support it (seedeepwave._common.check_inputs_not_vmapped’s call sites). The compiled backend never supportsvmap– thectypesC/CUDA call cannot operate on the batched tensors a transform produces – and raises an actionable error pointing at the eager backend instead.What that covers is one model argument at a time: every model argument of every propagator has been measured, one batched and the rest ordinary tensors, including the ones that reach only part of the time step – such as
elastic_vti’sc55, which enters the shear stresses alone and no normal stress.tests/test_vmap_models.pyruns that whole matrix and checks each batched result against the equivalent loop over the same models, so the claim above is not an assumption made from the first model of each propagator.What is not supported on any propagator, on any backend, is
vmapover an input that is not a property model: source amplitudes and initial wavefields raiseNotImplementedError, and batched locations fail inside PyTorch’s own transform machinery. Batch those over the shot dimension the propagators already take instead.Batching is also what makes
max_velnecessary.max_velhas to be given when a model that the maximum wave speed is computed from is batched bytorch.func.vmap: a batched tensor stands for one model per batch entry, so it has no single maximum, whilemax_velis one number for the whole call. Omitting it then raises an error namingmax_vel=. The table below is that set, per propagator; batching any other model does not requiremax_vel, and a call that runs today keeps running. Under every othertorch.functransform (grad,jacrev,jvp) the values stay readable, so nothing has to be supplied.Models the maximum wave speed is read from¶ Propagator
Models
scalarvscalar_bornvacousticvacoustic_bornvviscoacousticvacoustic_vtic11,c33,buoyancyelasticlamb,mu,buoyancyelastic_bornlamb,mu,buoyancyelastic_vtic11,c33,c55,buoyancy, andc66in 3-DThe anisotropic rows read fewer models than they take:
c13does not raise the bound these propagators use. The two Born propagators in the table rejectvmapover any model outright, as above, so their rows say which models the maximum comes from withoutmax_velever being the thing that stops such a call.- Second-order (create_graph)
Double-backward: differentiating a gradient again, which is what
torch.autograd.grad(..., create_graph=True)asks for. Hessian-vector products, Gauss-Newton and truncated-Newton inversion, and second-order optimisers all need it. It works on theeagerbackend for every propagator, because PyTorch’s own autograd builds the second-order graph from the eager reference implementation’s operations generically, with nothing for Deepwave to derive.The compiled backend has to be told how. Its backward pass is a hand-derived adjoint that enters C/CUDA through
ctypes, not a graph autograd can differentiate again, so double-backward works there only where a second-order rule has been derived by hand:scalar,acousticandelastic. Every other propagator raises on the compiled backend rather than returning the zeros that differentiating through an opaque call would otherwise produce – forscalar_born, for example (wrapped here; the real message is one line):RuntimeError: Second-order differentiation (double-backward / Hessian-vector products) is not implemented for the scalar_born propagator. Its hand-derived adjoint has no analytic second-order path, so attempting to differentiate through its backward pass (for example, calling torch.autograd.grad(..., create_graph=True) and then differentiating the resulting gradient) would silently return incorrect (typically zero) results rather than the true second-order derivative. Deepwave therefore raises this error instead of returning a wrong answer. The scalar, acoustic, and elastic propagators do support correct second-order differentiation (create_graph=True) on the compiled backend. For scalar_born, compute Hessian-vector products via finite differences of first-order gradients (perturb the model, recompute the gradient, and take the difference). Native second-order support may be added in a future release.
backend="eager"is the other way out, at the eager backend’s usual cost in speed. Two ofelastic’s options are exceptions within a supported propagator:mpml_fraction > 0andsurface_method="pm"each raise aNotImplementedErrorfor double-backward on the compiled backend, both namingbackend="eager"as the alternative.- jvp (forward-mode AD)
Works generically on every propagator’s eager backend (plain differentiable PyTorch operations support forward-mode AD without any special-casing). On the compiled backend (what
"auto", the default, resolves to), onlyscalar,acoustic, andelastichave a hand-derived Born-basedjvprule; the other six compiled propagators raise an actionableRuntimeErrorpointing atbackend="eager"instead of PyTorch’s generic, unhelpful “implement the jvp function” error.- Callbacks
forward_callbackis supported by every propagator (it has no dependency on the shared step-based engine thatbackward_callbackneeds).backward_callbackworks on every propagator that has a compiled backend – but, on the eager backend specifically, onlyscalarsupports it; every other propagator’s eager backend rejects abackward_callbackoutright.get_wavefield(role="forward")(the reconstructed-forward-wavefield lookup inside abackward_callback, for building custom imaging conditions) is narrower still: it is ascalar-only capability, on both ofscalar’s backends. Every other propagator’sbackward_callbackcan only see the adjoint wavefield, not the forward one; use aforward_callbackto record the forward field instead. The argument that opts into it,backward_callback_needs_forward_wavefield, is accepted on all nine so that a wrapper can forward one uniform keyword set, but only at its default ofFalsewhere the capability is missing: passingTruethere raisesdeepwave.DeepwaveNotImplementedError.deepwave.spec(propagator).has_forward_wavefieldreports which propagators implement it, without making a call. Abackward_callbackfires on its usual schedule on every backend even when the output being differentiated turns out to be unused and autograd has only an undefined (identically zero) gradient to propagate: the adjoint wavefield it is given is then a tensor of zeros, neverNone. Seedeepwave.CallbackState.
Not supported: outer torch.compile, torch.export and CUDA graphs¶
Outer torch.compile, torch.export and CUDA graphs are not supported. Deepwave’s compiled backend enters C/CUDA through ctypes, which is opaque to Dynamo’s tracer, to torch.export’s graph capture and to CUDA-graph capture. Wrapping a propagator in torch.compile will graph-break at the call (at best) and is untested; torch.export cannot serialise it (it fails while tracing, on a data-dependent guard); capturing a propagator call into a CUDA graph is not supported. The backend="torch_compile" option is different and is supported: it compiles Deepwave’s own eager reference implementation (see the Backends section above). If you need a traceable propagator, use backend="eager" and expect it to be substantially slower.
Scope and backends¶
Propagator | Status | Dims | Backends | Boundary faces accepted | Storage modes |
|---|---|---|---|---|---|
| stable | 1-D, 2-D, 3-D |
| free on every face, free on the low faces, rigid on every face, rigid on the low faces, periodic, pml, mixed per-face combination (default + eager) |
|
| stable | 1-D, 2-D, 3-D |
| free on every face, free on the low faces, rigid on every face, rigid on the low faces, periodic, pml, mixed per-face combination (default + eager) |
|
| stable | 1-D, 2-D, 3-D |
| free on every face, periodic (not accepted); free on the low faces, rigid on every face, rigid on the low faces, pml, mixed per-face combination (default + eager) |
|
| experimental | 1-D, 2-D, 3-D |
| free on every face, periodic (not accepted); free on the low faces, rigid on every face, rigid on the low faces, pml, mixed per-face combination (default + eager) |
|
| experimental | 2-D, 3-D |
| free on every face, free on the low faces, periodic, mixed per-face combination (not accepted); rigid on every face, rigid on the low faces, pml (default + eager) |
|
| experimental | 1-D, 2-D, 3-D |
| free on every face, periodic (not accepted); free on the low faces, rigid on every face, rigid on the low faces, pml, mixed per-face combination (default + eager) |
|
| stable | 1-D, 2-D, 3-D |
| free on every face, free on the low faces, rigid on every face, periodic, mixed per-face combination (not accepted); rigid on the low faces, pml (default + eager) |
|
| experimental | 1-D, 2-D, 3-D |
| free on every face, free on the low faces, rigid on every face, periodic, mixed per-face combination (not accepted); rigid on the low faces, pml (default + eager) |
|
| experimental | 2-D, 3-D |
| free on every face, free on the low faces, rigid on every face, periodic, mixed per-face combination (not accepted); rigid on the low faces, pml (default + eager) |
|
Physics options and differentiation¶
Propagator | mpml_fraction | surface_method | Q options | Batching (vmap) | Second-order (create_graph) | jvp |
|---|---|---|---|---|---|---|
| no | no | no | yes, over property models (eager backend) | default (effective stride 1) + eager | default + eager |
| no | no | no | yes, over property models (eager backend) | eager | eager |
| no | no | no | yes, over property models (eager backend) | default (effective stride 1) + eager | default + eager |
| no | no | no | not supported | eager | eager |
| yes (all backends) | no | no | yes, over property models (eager backend) | eager | eager |
| no | no | yes (all backends) | yes, over property models (eager backend) | eager | eager |
| yes (all backends) | yes (“pm”, all backends) | no | yes, over property models (eager backend) | default (effective stride 1) + eager | default + eager |
| yes (all backends) | no | no | not supported | eager | eager |
| yes (all backends) | no | no | yes, over property models (eager backend) | eager | eager |
Callbacks¶
Propagator | forward_callback | backward_callback | get_wavefield(role=”forward”) |
|---|---|---|---|
| yes | default + eager | yes |
| yes | default | no |
| yes | default | no |
| yes | default | no |
| yes | default | no |
| yes | default | no |
| yes | default | no |
| yes | default | no |
| yes | default | no |