Solver
Every solver is an instance of a Propagator subclass (recipes/solver.py). This page documents the public API: how to instantiate a solver, run propagation, and access results.
1. Instantiation
from recipes import recipes_registry
Solver = recipes_registry['iso-acoustic'] # or any other key
solver = Solver(model, options)options is a plain dict; missing keys fall back to the defaults in the table below.
Options reference
| Key | Type | Default | Description |
|---|---|---|---|
space_order |
int | 4 | Spatial FD stencil order |
nt |
int | 100 | Number of time steps |
f0 |
float | 0.010 | Dominant Ricker source frequency (Hz) |
time_slots |
int | time_order + 1 |
Wavefield rolling-buffer depth |
t_sub |
int | 4 | Snapshot subsampling factor (every n-th step is saved) |
save |
str | 'lazy' |
Storage for saved wavefields: 'lazy' (host streaming), 'disk', or None |
compression |
str|None | None | Wavefield compression ('cvxcompress', 'bitcomp') |
coefficients |
str | 'taylor' |
FD coefficient scheme ('taylor' or 'lsqr_drp') |
interpolation |
str | 'linear' |
Src/rec interpolation ('linear' or 'sinc') |
interp_order |
int | 2 | Interpolation stencil order |
transient |
bool | False | Use transient TimeFunction to reduce memory traffic |
factorization |
int | 1 | Stiffness-tensor precompute mode for TTI elastic (0/1/2) |
fd_kernel_only |
bool | False | Skip source and receivers; use a Gaussian initial condition |
abox |
bool | False | Adaptive bounding-box subdomain (restricts computation to active wavefront) |
Wdtype |
dtype | np.float32 |
Wavefield data type (np.float16, np.float32, np.float64) |
save_mode |
str | 'all' |
Elastic adjoint save: 'all' (velocity + stress) or 'v' (velocity only, stress recomputed) |
opt |
tuple | auto | Devito Operator optimisation options, e.g. ('advanced', {'openmp': True}) |
2. Forward propagation
# Returns a Devito PerformanceSummary (operator timings, GFlops, …)
summary = solver.forward()
# Save wavefields for a subsequent gradient computation
summary = solver.forward(save=True)forward accepts the same keyword arguments as op.apply, which are forwarded verbatim after _prepare_kw adds the standard fields (dt, physical parameters, etc.). Useful overrides:
| kwarg | Description |
|---|---|
nt |
Override the number of time steps for this run |
dt |
Override the time step (default: model.critical_dt) |
autotune |
Devito auto-tune mode for this call ('off', 'basic', 'aggressive') |
src_field |
Restrict source injection to a named field |
measurement |
False to skip receiver interpolation |
opt |
Per-call operator optimisation dict (keyed by operator name) |
3. Gradient computation
After a forward pass with save=True, inject residuals into rec.data and call jacobian_adjoint:
solver.rec.data[:] = observed_data - solver.rec.data # data residuals
summary = solver.jacobian_adjoint(params='vp')
dm = solver.perturbation(param='vp') # devito Function
print(dm.data.shape)Multiple parameters in one adjoint pass:
solver.jacobian_adjoint(params=('vp', 'epsilon'))
dm_vp = solver.perturbation(param='vp')
dm_eps = solver.perturbation(param='epsilon')perturbation returns the same Function object on repeated calls, so gradients from multiple shots accumulate in-place. Zero it before a new accumulation:
solver.perturbation(param='vp').data[:] = 04. Accessing wavefields and acquisition objects
Current wavefields
solver.state is a dict of rolling-buffer TimeFunction objects. Keys are the field names defined by the solver:
| Solver | Keys |
|---|---|
| acoustic (2nd order) | u |
| acoustic (1st order) | p, v |
| TTI (Zhang, Fletcher, SA) | u, v |
| elastic isotropic | v, tau |
| elastic TTI | v, tau |
u = solver.state['u'] # TimeFunction
p = solver.pressure # symbolic pressure expression
p_data = solver.pressure_data # numpy array of the last time sliceSaved wavefields
solver.saved_state is a dict of snapshot TimeFunction objects. Available only after forward(save=True). Keys are '<name>_save':
u_save = solver.saved_state['u_save'] # shape (nt//t_sub, nx, nz)When compression is set the data is stored compressed; the compression ratio is available as:
cr = float(np.mean(solver.saved_state['u_save'].compression_ratio))Source and receivers
src = solver.src # devito RickerSource — 1 point at domain centre
rec = solver.rec # devito Receiver — surface array
# After forward():
traces = solver.rec.data # (nt, nrec)
# Reposition the source before building the operator:
solver.src.coordinates.data[0] = [500., 20.] # x, z in metressrc and rec are cached_property objects. If you reposition them you must do so before the first call to forward, fwd_op, or any method that caches the operator. Clear the cache by deleting the cached attributes (del solver.src) if you need to rebuild with different coordinates.
5. Operator-level access
The Devito Operator objects are built lazily and memoised.
op_fwd = solver.fwd_op(save=True) # forward Operator
op_jac = solver.jacobian_adj_op(params='vp') # gradient OperatorPass a per-operator opt dict to apply different optimisation levels to each pass:
opt = {
'ForwardAcousticIsotropic': ('advanced', {'openmp': True}),
'JacobianAdjAcousticIsotropic': ('advanced', {}),
}
solver.forward(save=True, opt=opt)
solver.jacobian_adjoint(params='vp', opt=opt)6. Available solvers
| Registry key | Class | Physics |
|---|---|---|
iso-acoustic |
AcousticIsotropic |
2nd-order (or 1st-order) isotropic acoustic; optional SLS viscosity |
tti-zhang / vti-zhang |
ZhangTTI |
Zhang (2011) pseudo-acoustic TTI/VTI; optional SLS viscosity |
tti-fletcher / vti-fletcher |
FletcherTTI |
Fletcher–Du–Fowler (2009) TTI/VTI |
tti-selfadjoint / vti-selfadjoint |
SelfAdjointTTI |
Self-adjoint TTI/VTI (Bube et al.) |
iso-elastic |
ElasticIsotropic |
Isotropic elastic velocity–stress (1st order) |
tti-elastic / vti-elastic |
ElasticTTI |
Anisotropic (TTI/VTI) elastic |
vti-* keys are aliases for their tti-* counterparts; the VTI regime is selected automatically when theta (and phi) are zero or absent in the model.