Stability policy¶
Note
Looking for numerical stability – the CFL condition, a diverging simulation, NaN wavefields? That is Accuracy and grid design (the CFL condition and grid design) and Troubleshooting (what to do when a run diverges). This page is about interface stability: which names you may rely on, and when they may change.
This page says which parts of Deepwave’s public interface you may rely on, and what a later release is allowed to change. It exists because “everything that is not documented as changeable is frozen by silence” is a promise no project can keep, and because the alternative – finding out from a broken script – is worse for you than a policy you can read in ten minutes.
0.1.0 labels public interfaces stable or provisional. The core propagators, their nn.Module forms, outputs, continuation and established helpers are stable. Planning, costing, measuring and newer helper interfaces are provisional. The tables under Every public name, and what may change give individual labels. Both tiers are supported and tested. The labels describe how a later release may change an interface; they do not grade its scientific correctness.
Which half a name is in is stated by its label, in the tables under Every public name, and what may change. The rest of this page says what each promise is worth.
The stable interface¶
Patch releases preserve stable calls and results, apart from necessary correctness or security fixes, which receive explicit migration notes. Stable names may gain optional arguments or named output attributes. An existing stable name may be deprecated in a minor release only with a working replacement, a warning and a migration example. It remains available through at least two subsequent minor releases; after 1.0 removal also requires a major release. A deprecated name is removed only in a release announced in its warning and release notes. The aliases deprecated in 0.1.0 therefore work in 0.2 and 0.3 and are removed no earlier than 0.4; see The deprecation rule.
For a fixed propagator and number of dimensions, the current tuple fields, their order and the opaque auxiliary entry are part of the stable contract. Future metadata is added as attributes, not tuple entries. Correctness fixes may still require a documented change of meaning or layout. Read receiver fields by name and continue with initial_state=out.
Two consequences are worth stating separately, because they are the ones that would otherwise look like exceptions:
An exception may become a more specific subclass of what it already raises. A caller that catches the builtin type, or
deepwave.DeepwaveError, is unaffected; see Errors and warnings.The warning categories listed in Errors and warnings keep their meaning and their builtin base, and none of them is removed or moved to a different cause. A genuinely new cause may add a further subclass of
deepwave.DeepwaveWarning, which every filter on that base – or onUserWarning– already catches. Message text is not part of the interface: it is written for a human and is reworded whenever a clearer wording is found.A
NamedTupleresult’s fields are tuple elements, so where Deepwave returns one –deepwave.Discretisation,deepwave.checkpointing.SnapshotResult,deepwave.wavefields.MeasureResult,deepwave.wavefields.BoundaryRecording– its field set and their order follow the stable compatibility policy. Future metadata is a property, not a field, so that unpacking it by position keeps working.
Deepwave is pre-1.0 and follows Semantic Versioning’s guidance for initial development. These stability commitments are narrower than the changes that versioning scheme permits.
The numerical standard¶
Numerical changes are checked on matching calls, devices and pinned thread counts, including receivers, final states and gradients. The v0.0.27 parity harness uses a separate float64 reference for each version. For a float32 quantity its comparison threshold is twice the larger of the two versions’ measured float32-to-float64 differences. Comparing both versions against one common reference would make this bound vacuous by the triangle inequality. Float64 comparisons use a scoped relative threshold and measured run-to-run spread. The harness also rejects non-finite data and restricts documented intentional changes to their affected quantities and measured magnitude.
These thresholds are validation criteria for the tested configurations, not a universal error bound for ill-conditioned calls. Agreement across versions or precisions alone does not establish correct physics. Independent analytic, convergence, adjoint and reciprocity checks remain necessary. The conditions under which a gradient of a particular call is exact are described in What Deepwave Calculates.
Bitwise equality of results between two Deepwave versions is never promised, and neither is bitwise equality between two devices, two builds or two backends. Bitwise reproducibility of repeated runs of one build, on one device, at one configuration, is a separate and narrower promise, stated with its exact conditions in What Deepwave Calculates.
The maturity labels¶
Every public name carries one of four labels. stable and experimental make the same compatibility commitment; the latter also signals that the physics or design has less use outside the project.
Label | What it tells you |
|---|---|
stable | The interface follows The stable interface. |
experimental | The same interface commitment as |
provisional | Supported and tested, but its interface may evolve in a minor release with explicit notes and a deprecation path where feasible. Scientific meaning never changes silently. |
deprecated | It remains available through the stated transition period. Calling it warns, naming its replacement and the earliest removal release. |
The stable, experimental and provisional labels do not themselves emit warnings. In particular, no propagator warns merely because it is experimental. Deprecated calls do warn, naming their replacement and earliest removal release. If a provisional interface later changes, its transition follows The provisional rule; stable and experimental interfaces follow The deprecation rule.
The provisional rule¶
A provisional interface may change in a minor release. Its changelog entry must describe the old and new forms and how to migrate. Keep the old form working with a deepwave.DeepwaveFutureWarning and a named replacement where feasible. If preserving it is impractical, explain why in the release notes before removal. A provisional label never permits an unannounced change of scientific meaning.
A name’s label is stated in its own documentation: every propagator’s docstring carries a one-line Stability: entry, and the capability matrix has a Status column read from the one declaration that records it. The column is generated from that declaration, and a test checks every propagator’s and every nn.Module form’s docstring line against it, so the two cannot disagree.
The deprecation rule¶
Stable deprecations require a replacement, a warning, a migration example and at least two subsequent minor releases retaining the old form. Removal after 1.0 also requires a major release. The names deprecated in 0.1.0 remain in 0.2 and 0.3; 0.4.0 is their earliest removal release. A later release may extend the period, with the warning and release notes updated before the previously announced date. Provisional deprecations follow The provisional rule.
Every deprecation:
keeps working, with its old behaviour, for the whole deprecation period;
emits exactly one
deepwave.DeepwaveFutureWarningper call per deprecated name, naming its replacement;states its earliest removal version in its docstring and warning.
There is one category for every deprecation, so one line hides (or, in CI, escalates) all of them:
warnings.filterwarnings("error", category=deepwave.DeepwaveFutureWarning)
deepwave.DeepwaveFutureWarning subclasses FutureWarning rather than DeprecationWarning deliberately. Python’s default filters hide DeprecationWarning outside __main__, and several of Deepwave’s deprecations are emitted from code that something else calls – PyTorch’s load_state_dict machinery, or a Module.forward reached through torch.nn.Module.__call__ – so there is no stacklevel that attributes them to your own call on every path, and a DeprecationWarning attributed to torch or to Deepwave itself would never be seen. FutureWarning is shown whatever module it ends up attributed to. A filter that already targets FutureWarning covers Deepwave’s deprecations too.
The table below lists deprecations present in 0.1.0. New stable deprecations must meet the same requirements; they are not excluded by this list. The migration warnings for differences from v0.0.27 are listed in Upgrading; their removal is announced separately from alias removal.
Deprecated today¶
Every name in the deprecated tier, with its earliest removal release:
Name | Earliest removal |
|---|---|
|
|
|
|
|
|
The |
|
|
|
|
|
|
|
|
|
|
|
The five module spellings |
|
Each of these keeps working, unchanged, through 0.3 and emits a warning naming its replacement (dv for scatter, dv_requires_grad for scatter_requires_grad, backend for python_backend, nt for length, and the private module for each shim).
Removed in 0.1.0¶
Names that only ever existed on the development branch were removed in 0.1.0 rather than deprecated: no released code can be using them, so there is nothing to give a deprecation period to. Each one raises rather than being silently ignored:
backend="torch_jit",backend="jit"andbackend="compile". Deepwave no longer callstorch.jitat all.v0.0.27’spython_backend="jit"runs the eager backend instead, as doespython_backend=Trueon a build wherev0.0.27would have chosentorch.jit; both keep working, under the existingpython_backenddeprecation warning, and the difference in numbers is within the standard above.role="backward"ondeepwave.CallbackState, whose spelling isrole="adjoint".dt_internalon the returned object, which isout.info.internal_dt.the PML attributes on
deepwave.PropagatorOutput, which areout.info.pml_widthandout.info.pml_freq.staggered=indeepwave.discretisation.the absorbing-boundary and attenuation-memory field names as
initial_statekeys; passinitial_state=outinstead.scalar_born’s barereceiver_mask=andreceiver_amplitudes=, whose spellings arebg_receiver_mask=/sc_receiver_mask=and the returnedbg_receiver_amplitudes/sc_receiver_amplitudes. Only the barereceiver_locations=shipped, and that one is deprecated rather than removed.the
""channel key, which was a second spelling of"u".
Upgrading lists them with the exact text each one prints.
Every public name, and what may change¶
The label of every name exported from the deepwave namespace. Stable and experimental names follow The stable interface; provisional names follow The provisional rule. Descriptions of likely additions do not rule out the documented deprecation or correctness processes above.
The propagators, both the function and its nn.Module form:
Name | Label | What a later release may change |
|---|---|---|
| stable | Released in |
| experimental | Never released before |
Their arguments and returns, which are shared:
Name | Label | What a later release may change |
|---|---|---|
stable | May gain named attributes. Its tuple layout is fixed on every propagator (see The returned tuple), and its existing field names and their meanings are fixed. | |
stable | Continuing a run by handing this object back is supported. It may gain methods, recorded run parameters and further tensor names. Its internal tensor keys are provisional diagnostic names, listed in Internal callback names; they do not freeze an absorbing-boundary implementation. Pass the bundle back whole. A change of layout or numerical interpretation increments | |
The field set of | stable | May gain fields as more resolved configuration becomes worth reporting. Existing fields follow the stable compatibility policy. |
provisional | May gain fields. The individual | |
| provisional | The argument’s name and its division of labour with |
| provisional | Exposed only by |
| provisional | The parameter-modified free-surface method is new and its accuracy characteristics are still being mapped. |
The Python execution modes of | provisional | Which Python execution modes exist, and what each one supports, may grow, because it follows PyTorch’s own moving parts. The compiled C/CUDA backend is the production path and is stable. |
The helper modules and functions:
Name | Label | What a later release may change |
|---|---|---|
| provisional |
|
| provisional | The estimate may gain keys, and the definition of a key may be made more precise as a defect fix. That the function exists and reports a total does not change. |
| provisional | The function may gain optional keywords; |
| provisional | May gain functions and optional keywords. |
| provisional | The same: a new module whose helper set is expected to grow. The fields of |
provisional | Frequency-domain constitutive response helpers. Their functions may gain optional arguments; the documented storage/loss sign convention and quality-factor definition remain fixed. | |
provisional | May gain element types and functions, and the element constructors may gain keywords. The unit convention has settled: positions in grid cells, speeds in cells per unit time, delays and | |
| provisional |
|
| provisional | The spec may gain fields and methods as more of a propagator’s interface becomes worth publishing. Existing fields and methods, and what they return for a given propagator, follow the provisional rule. |
| provisional | Differentiable source and receiver positions are new; the keyword and its meaning are fixed, and the numbers it produces are covered by The numerical standard. The rest of |
| stable | May gain optional keywords. |
| stable | May gain functions and optional keywords. These are the helpers a |
| provisional | May gain functions and optional keywords. Each arrived with |
| stable | May gain methods, and may accept further names. The public argument and state names accepted by |
| stable | See Errors and warnings: each of these categories keeps its meaning and its builtin base, a new cause may add a further subclass of the base a filter already catches, an exception may become a more specific subclass of the same builtin base, and message text is not part of the interface. |
The closed-set type aliases ( | stable | May be widened – gaining an accepted value – which is additive. Never narrowed. See Type annotations. |
The structural type aliases ( | stable | May gain a member of the union, which is additive: an argument annotated with one keeps accepting everything it accepts today. |
| stable | Its value, obviously; its existence and its format do not change. |
Supported aliases¶
Some arguments and fields have two supported spellings. Neither spelling is deprecated in 0.1.0. Any later stable deprecation must follow The deprecation rule.
Spelling | Aliases |
|---|---|
| The individual |
The flat | The channel-keyed |
The flat keyword rule¶
Every flat keyword on every propagator is generated by one rule. With <ch> a channel name and <s> a state field name, a propagator takes:
source_amplitudes_<ch>,source_locations_<ch>,source_mask_<ch>;receiver_locations_<ch>,receiver_mask_<ch>;<s>_0, and<s>_m1where the scheme has a previous step.
The _<ch> suffix is dropped for the scalar channel u, which is why scalar and scalar_born spell theirs source_amplitudes=. On a Born propagator the receiver keywords are bg_receiver_* and sc_receiver_*, and the scattered state adds <s>_sc_0 (and <s>_sc_m1). deepwave.spec() reports a propagator’s channel and state names, so the keywords can be derived rather than looked up.
There are exactly two departures from the rule, both deprecated spellings rather than exemptions, and both listed under “Deprecated today” above: source_{amplitudes,locations}_p on the two released propagators whose scalar source channel is "volume_rate" or "pressure_rate", and scalar_born’s bare receiver_locations. The table of departures is closed. A test asserts that every flat keyword on all nine propagators is either derived by the rule or one of those two.
The returned tuple¶
Every propagator returns a deepwave.PropagatorOutput: a real tuple subclass whose entries are also reachable by name.
For a fixed propagator and dimensionality, the current tuple fields and their order follow the stable compatibility policy. Future metadata is added as a named attribute, not a new tuple element. For example:
*_, receiver_amplitudes = scalar(...)
out[-1] # current last receiver field for scalar
out.receiver_amplitudes # the supported way to read a result
Accessing outputs by name is the recommended habit. It is the form that is correct for both 2-D and 3-D, because the number of state and receiver elements depends on the number of spatial dimensions, so a fixed positive index means different things in 2-D and 3-D even though neither layout ever changes. And the elements in the middle of the tuple are not the ones a reader would guess: on scalar, out[-2] is out.auxiliary_state, not a receiver field, whereas on scalar_born it is the background data and on acoustic/elastic in 2-D it is the y-component receiver amplitudes. Index by name and the question does not arise.
out.names lists the field names of the instance you hold, and deepwave.spec() reports them for a propagator and a number of dimensions without running anything.
Pytrees¶
deepwave.PropagatorOutput and deepwave.AuxiliaryState are registered as torch.utils._pytree nodes, which is what lets torch.func transforms, torch.compile and tree_map see the tensors inside them instead of one opaque object:
an output’s children are its tuple elements, in return order, with the auxiliary state expanding into the tensors it holds, so the leaves of an output are exactly its tensors and the flattened order is the return order above. Its context is the field names together with
info: the names belong to the instance rather than to the class, so an output cannot be rebuilt without them.an auxiliary state’s children are its tensors, and its context is their names and every recorded run parameter, including
format_version, so a state that has been through a transform still continues a run.
A treespec containing either type is not serialisable to JSON: neither type registers a serialised name or a JSON format for its run metadata. Keyed pytree registration is supported by the declared minimum PyTorch version (2.4). Object serialisation through pickle and torch.save/torch.load is separate from tree-specification serialisation. PropagatorOutput: DataParallel and pytrees records why the context is shaped this way.
Continuing a run: the auxiliary state¶
Every propagator returns a deepwave.AuxiliaryState as out.auxiliary_state. What is promised:
that it exists on all nine, as one element of the returned tuple;
that it is a read-only mapping from name to tensor (
names,keys,values,items,as_dict) with the documented helpers (to,detach,compatible_with) and the recorded run parameters it checks a continuation against;that handing it back unchanged, as part of
initial_state=out, continues the simulation;that it carries an integer
format_versiondescribing its layout and numerical interpretation, and an incompatible version is rejected with an error naming both versions;that its diagnostic keys are documented in Internal callback names for the installed version. These internal keys follow the provisional policy, not the stable public wavefield-name contract.
A saved auxiliary state is a restart for a compatible implementation, not a long-term interchange format. Keep the package version and run configuration with saved states. A necessary numerical correction may require a new state format, including in a patch release.
What is not promised is the bundle as a whole: it is opaque as an output element. Do not index it positionally, and do not build on how many tensors it holds, on their order or on their shapes – those follow the absorbing boundary’s and the attenuation model’s own implementation, which is state rather than physics. Pass the object back whole.
Segmented runs¶
Running a call as several time segments – checkpoint_segments= on a propagator, or deepwave.checkpointing.run_segments() – is a memory lever, not a different computation:
forward values agree with the equivalent unsegmented run to rounding, with the same resolved setup, backend and storage policy. Even when final wavefields match bitwise, receiver resampling can round differently because a concatenated trace and the direct call’s trace have different memory layouts;
gradients agree to rounding under the same conditions and sampling phase, because segmentation changes the accumulation order. This preserves the unsegmented call’s gradient approximation, including any time sampling or lossy storage; it does not make that approximation an exact derivative.
These equivalence promises are not provisional. An unconditional bitwise guarantee is not made. Segmented propagation and whole-record resampling explains whole-record resampling and what a caller driving its own outer loop has to do instead. Segmentation accepts the supported point-source mappings and flat keywords. Continue a state with initial_state=out; individual initial-field keywords and propagator callbacks are not supported by checkpoint_segments=. Use run_segments’s segment_callback for observations at segment boundaries. Unsupported combinations raise before propagation.
A checkpointed segmented run uses re-entrant torch.utils.checkpoint by default, which retains less native wavefield history. Pass use_reentrant=False to deepwave.checkpointing.run_segments() when torch.autograd.grad is needed; that mode can retain a history buffer per segment and use substantially more memory.
If you wrap a propagator in torch.utils.checkpoint yourself, pass use_reentrant=False. Under the re-entrant mode the return degrades to a plain tuple – losing out.info and named access – and the tensors in out.auxiliary_state come back detached; that is a property of torch.utils.checkpoint rather than of Deepwave, and Usage covers it in full. Deepwave’s own segmented runs return a complete deepwave.PropagatorOutput, out.info included, either way.
torch.func and torch.compile¶
Backend agreement for gradients depends on the model-gradient sampling stride, storage and callbacks. At effective stride one with lossless storage, fixed inferred parameters and no callback mutation, compiled and eager gradients are checked against The numerical standard, subject to any narrower per-propagator approximation. At a larger effective stride, compiled model gradients use sampled correlations while eager/torch_compile differentiate every internal step, so their model gradients can differ materially. Source and initial-state gradients still use every internal step; see What Deepwave guarantees about gradients and Backends.
backend="auto" selects native C/CUDA execution except for the documented elastic-Born M-PML fallback to eager. It does not negotiate transform support or retry failed calls. Select backend="eager" explicitly when the native implementation lacks the transform or boundary combination you need; see Backends and Capability matrix. out.info.backend records which backend ran. backend="compiled" requires native execution.
max_vel is the one argument a transform can make mandatory. Deepwave infers it, detached, from the model values, which are unreadable when torch.func.vmap batches a model: a batched tensor stands for one model per batch entry and has no single maximum. So:
if no model argument the maximum wave speed is computed from is batched at any
vmaplevel,max_velis inferred as usual and nothing has to be passed;if such a model is batched, the call raises, naming
max_vel=. Pass the maximum absolute wave speed over every model the call stands for.
The capability matrix lists, per propagator, the models the maximum wave speed is read from. Batching any other model does not require max_vel, and a call that runs today keeps running.
What a propagator added later looks like¶
A propagator added in a future release is not free-form: it follows the same layouts every existing one does, so that what you learn from one propagator transfers to the next.
Its output tuple holds, in order:
the physical state fields;
auxiliary_state;the Born scattered state fields, on a Born propagator;
the receiver amplitudes – background before scattered, on a Born propagator.
Channels are declared in one order throughout, both for the state fields and for the receivers: the scalar channel (p or u) first, then z, y, x, then any others in state-field order.
Its models are one named tensor each, one per entry of the propagator’s model-name list:
the required models that are not perturbations come first, as ordinary positional-or-keyword arguments, followed by
grid_spacinganddt;Born perturbations, and models that are optional or that exist only in some numbers of dimensions, are keyword-only;
no argument takes several models stacked into one tensor.
The anisotropic propagators have enough model arguments that position is easy to get wrong, so the documentation and the examples call them by keyword; both forms work.
Its flat keywords come from the rule above, with no new exemption.
Boundary conditions¶
boundary= names each face’s condition. A single string applies to all faces; a sequence gives one code per face, in the same order as pml_width. The accepted codes today are "pml", "free", "rigid" and "periodic"; an unrecognised one raises, listing the accepted names.
In 0.1.0 the promise attached to a face code is the same on every propagator and at every accuracy order: the face either meets Deepwave’s energy and spectral checks for that condition, or the combination raises deepwave.DeepwaveNotImplementedError there, rather than returning a plausible wrong answer. A later release that makes a raising combination work is an additive change. boundary= has never appeared in a release, so none of this changes a v0.0.27 result. Boundary conditions documents each condition, its measured reflection coefficient and its accuracy caveats, which are separate from those checks.
What that leaves accepted in 0.1.0:
"pml"on every face of every propagator, at every accuracy, at any width including zero;every code on
scalarandscalar_born;"rigid"on every face ofacoustic,acoustic_born,viscoacousticandacoustic_vti;"free"on those three isotropic acoustic propagators at the beginning of an axis at every accuracy, and on either face ataccuracy=2;"rigid"onelastic,elastic_vtiandelastic_bornat the beginning of an axis at every accuracy, and on either face ataccuracy=2.
Everything else – "periodic" anywhere but the two regular-grid propagators, "free" on the elastic family or acoustic_vti, and a reflecting face at the end of an axis at accuracy 4 or more where the list above does not allow it – raises. Model an elastic or acoustic_vti free surface with a vacuum layer instead, and put a reflecting face at the beginning of its axis; Which faces each propagator accepts has the table and the alternatives.
A zero-width "pml" face is the one case that needs a word here, because v0.0.27 had it and said nothing. It behaves identically whether boundary= was omitted, given as the single string "pml" or given per face – it is v0.0.27’s pml_width=0 idiom – and it never raises, because "pml" is the spelled-out default. It is exactly a Dirichlet (zero-exterior) truncation, which on scalar acts as a free surface and on elastic as a frictionless wall. On all nine it warns deepwave.ZeroWidthFaceWarning, which is also the way to silence it; where a boundary= code means the same condition, the warning names that code, and naming the face explicitly is silent – and, at accuracy above 2, uses the exact image rather than this approximation of it.
Errors and warnings¶
Deepwave raises its own exception types (deepwave.DeepwaveError and the mixed-in subclasses documented in Usage). Each one also inherits the builtin exception type that the same failure raised before, so except ValueError keeps catching what it caught; the addition is that except deepwave.DeepwaveError now catches everything Deepwave raises deliberately, and nothing else. The builtin base a given failure raises is frozen: changing it is a break and would need a major version. Which Deepwave*Error subclass it is may become more specific within that base at any time, which is not a break for a caller that catches either the builtin type or deepwave.DeepwaveError. A location error names the propagator and the argument you passed, so that a message about coordinates says which of your tensors they came from.
Warning categories are part of the interface. There is one per cause, and each of the ones below keeps its meaning and its builtin base: none is removed, renamed or moved to a different cause. deepwave.DeepwaveWarning (a UserWarning) is the base of the categories that describe what a call will compute:
deepwave.MigrationWarning, with itsdeepwave.PmlFreqInferenceWarningsubclass
deepwave.DeepwaveFutureWarning (a FutureWarning) is separate, because a deprecated spelling is a message about the code you wrote rather than about the physics you asked for; filtering one does not silence the other. You may equally filter on UserWarning or FutureWarning, the two builtin bases. Every warning in these two families names its own category in its own text, as the way to filter it. A cause Deepwave has not met yet may add a further subclass of deepwave.DeepwaveWarning, which a filter on that base – or on UserWarning – already catches; the categories above are not reshuffled to make room for one.
Deepwave also still emits some plain UserWarnings that predate these categories and name no category of their own – the one about a max_vel below the model’s own maximum, and the grid-cells-per-wavelength advice, for example. Filter those on UserWarning. No Deepwave deprecation uses DeprecationWarning: every one of them is a deepwave.DeepwaveFutureWarning, so there is a single category to filter rather than a choice that could change between releases.
Warning message text is not part of the interface: it is written for a human to read and is reworded whenever a clearer wording is found. Do not match on it.
Type annotations¶
The closed sets of values an argument accepts are published as type aliases (deepwave.StorageMode, deepwave.WavefieldView, deepwave.CallbackRole, deepwave.CallbackDirection, deepwave.CallbackKind, deepwave.CallbackMode, deepwave.LocationInterp, deepwave.Reduction, deepwave.Accuracy), so that a type checker can reject a typo before the call runs. Each one mirrors a runtime check, and a test asserts that the two agree, so an alias cannot quietly drift from what the code accepts.
Widening one of these aliases – adding a value – is not a breaking change: an argument that accepts more than it did still accepts everything it did. Narrowing one follows the stable deprecation or correctness-fix policy above.
accuracy is the one alias likely to be noticed: it is Literal[2, 4, 6, 8], so a computed int is a type error even though it still runs. Silence it with a cast:
from typing import cast
out = deepwave.scalar(v, dx, dt, accuracy=cast(deepwave.Accuracy, order))
boundary entries remain plain str so configuration strings can be passed without a cast. The runtime check reports accepted names and rejects unsupported combinations. This trades static typo checking for convenience; widening a Literal argument would not itself break callers using its previous values. No new boundary name is accepted until it is implemented and documented.
Reproducibility¶
Two promises are made about a single build, and What Deepwave Calculates states both with their exact scope and the reasons they stop where they do. That page is the scope: nothing wider is promised here.
Repeated runs of one build of the compiled backend, on one device and at one call configuration, give bitwise-reproducible gradients.
Compiled-backend CPU results – forward outputs and gradients alike – are invariant to
torch.get_num_threads(), on the nine propagators that have a compiled CPU backend.
Neither says anything about two devices, two builds, two backends or two releases: they are properties of one build rather than relationships between releases, which is what The numerical standard covers.
Pre-announced changes¶
Changes that are not deprecations – no name is going away – but that would alter what an existing call returns in a future release. They would be announced here a release ahead, so that the change is never the first you hear of it.
There are none at present. A future correction with a user-visible effect belongs in the changelog and migration notes; this section can announce it earlier when useful.
In particular, mode="write" is the current stable default of deepwave.CallbackState.get_wavefield(), deepwave.CallbackState.get_model() and deepwave.CallbackState.get_gradient(). All three keep returning the tensor Deepwave itself holds, with the autograd edge it carries, and writing into an internal one in place is a supported feature rather than a tolerated accident. Forward callbacks must not mutate models or wavefields during a differentiable run: that would invalidate the discrete adjoint, so Deepwave rejects it. A backward callback that edits an internal gradient accumulator deliberately changes the returned gradient; that value is a user-defined backward result, not necessarily the derivative of the unchanged forward loss. deepwave.CallbackState.writable() is how to ask whether a given name may be written where you are: with kind="model" it is True for an internal model name when mutation is supported. It is False during a differentiable forward callback and for a name merely derived from the internal state, such as acoustic’s v and rho, where there is nothing behind the name to write into.
Reserved names¶
Names Deepwave has set aside for possible later use. They are listed so that a release that starts using one is not a surprise, and so that you do not build something of your own on the assumption that they will stay free:
region=, on the propagators, for a future spatial-subsetting argument. It is not accepted today. The existingsurvey_pad/originarguments are stable and are how you restrict propagation to the part of the model your survey occupies (see Usage).Grid,Model,State,Output,propagateandPropagator, in thedeepwavenamespace. Deepwave defines none of them today.
0.1.0 publishes the nine propagator functions and their nn.Module forms, and nothing above them. Should a later release add a container type or a single propagate() entry point, it would be an addition beside those eighteen, which follow the deprecation policy above, and it would arrive with its own documentation. No naming grammar for such a thing is published here, because committing to one would be committing to the design.
Supporting both v0.0.27 and 0.1.0¶
If your code must run against both the last release and this one, detect features rather than parsing behaviour out of the version string:
import deepwave
# Preferred: ask whether the thing you need exists.
if hasattr(deepwave, "PropagatorOutput"):
receiver_amplitudes = deepwave.scalar(...).receiver_amplitudes
else: # v0.0.27 returned a plain tuple
receiver_amplitudes = deepwave.scalar(...)[-1]
Where you genuinely need a version comparison (for example to decide whether to pass an argument that did not exist before), read the version defensively and compare parsed versions rather than strings. Two traps:
deepwave.__version__does not exist in v0.0.27. It is new in0.1.0, so reading it directly is itself anAttributeErroron the version you are trying to support. Usegetattrwith a fallback, or ask the installed distribution’s metadata, which works on both.String comparison is not version comparison.
"0.0.27" < "0.1.0"happens to be true, but"0.0.9" < "0.0.27"is false.
from importlib.metadata import version as _dist_version
from packaging.version import parse
import deepwave
installed = parse(getattr(deepwave, "__version__", None) or
_dist_version("deepwave"))
if installed >= parse("0.1.0"):
...
importlib.metadata.version("deepwave") reports what was installed, which is the more reliable of the two when both are available. In this build, deepwave.__version__ is 0.1.0.