Skip to content

beamax.solvers.kwave_solver

Two- and three-dimensional k-wave-python integration. Install with pip install "beamax[kwave]".

beamax.solvers.kwave_solver

KWaveSolver(simulation_options=None, execution_options=None, **kwargs)

:mod:k-wave-python wrapper (2D/3D) for forward, time-reversal, and adjoint solves.

Used as the reference solver in examples and as the low-frequency leg of :class:HybridSolver.

Parameters:

Name Type Description Default
simulation_options SimulationOptions

Legacy simulation-options object. It cannot be mixed with unified keyword options.

None
execution_options SimulationExecutionOptions

Legacy execution-options object. It cannot be mixed with unified keyword options.

None
**kwargs

Unified k-Wave arguments forwarded to :func:kwave.kspaceFirstOrder. If neither legacy options nor kwargs are supplied, sensible CPU defaults are used.

{}
Notes
  • Requires the [kwave] extra (pip install 'beamax[kwave]').
  • Boundary handling uses a PML; :attr:Domain.periodic flags control PML-inside vs. PML-outside placement via the k-Wave options.
  • Forward solves run on the C++ backend by default. Time-reversal falls back to the pure-Python backend until the upstream CppSimulation path ships source preprocessing for time-varying pressure sources. The adjoint also uses the Python backend and applies the Appendix-B source and terminal-field scalings needed for the discrete transpose.
  • binary_path can be supplied directly or via BEAMAX_KWAVE_BINARY_PATH. Direct kwargs take precedence.

Examples:

>>> import jax.numpy as jnp
>>> from beamax import Domain, Sensor
>>> from beamax.solvers import KWaveSolver
>>> domain = Domain(N=(64, 64), dx=(1e-3, 1e-3), c=1500.0, periodic=(False, False))
>>> solver = KWaveSolver(pml_size=10, device="cpu")

Initialize k-Wave solver options.

Parameters:

Name Type Description Default
simulation_options SimulationOptions

Legacy simulation options object.

None
execution_options SimulationExecutionOptions

Legacy execution options object.

None
**kwargs

Unified keyword options forwarded to :func:kwave.kspaceFirstOrder.

{}
forward(p0: Union[np.ndarray, jnp.ndarray], domain: Domain, sensors: Union[np.ndarray, jnp.ndarray], ts: Union[np.ndarray, jnp.ndarray], *, record: str = 'p') -> np.ndarray

Forward k-Wave simulation for linear wave equation.

Parameters:

Name Type Description Default
p0 array

Initial pressure.

required
domain Domain
required
sensors array

Sensor mask or positions (solver expects mask).

required
ts (array, shape(Nt))
required
record ('p', ...)

Quantity to record.

"p"

Returns:

Type Description
ndarray

Sensor time series (Nt, Ns). Sensor channels follow NumPy C mask order for both the Python and standalone C++/CUDA backends.

time_reversal(data: Union[np.ndarray, jnp.ndarray], domain, sensors, sources, ts: Union[np.ndarray, jnp.ndarray], *, record: str = 'p_final', data_layout: str = 'auto') -> np.ndarray

Run classic k-Wave time reversal.

Parameters:

Name Type Description Default
data (ndarray or ndarray, shape(Ns, Nt) or (Nt, Ns))

Sensor measurements.

required
domain Domain

Reconstruction domain.

required
sensors array - like

Sensor mask.

required
sources array - like

Source mask where the reversed data are injected.

required
ts (ndarray or ndarray, shape(Nt))

Time grid.

required
record str

k-Wave field to return.

"p_final"
data_layout ('auto', 'ns_nt', 'nt_ns')

Sensor-data layout interpretation.

"auto"

Returns:

Type Description
ndarray

Requested k-Wave record.

Notes

Enforces \(p(\mathbf{x}_s,t)=d(t,\mathbf{x}_s)\) as a Dirichlet source, where \(d\) denotes sensor_data.

adjoint(data: Union[np.ndarray, jnp.ndarray], domain, sensors, sources, ts: Union[np.ndarray, jnp.ndarray], *, record: str = 'p_final', data_layout: str = 'auto') -> np.ndarray

Discrete k-Wave adjoint following Arridge et al., Appendix B.

Parameters:

Name Type Description Default
data (array, shape(Ns, Nt) or (Nt, Ns))

Sensor measurements.

required
domain Domain
required
sensors array

Sensor mask.

required
sources array

Source mask.

required
ts (array, shape(Nt))
required
record 'p_final'

Terminal pressure field. Other records cannot be converted to the initial-pressure transpose and are therefore rejected.

"p_final"

Returns:

Type Description
ndarray

Euclidean discrete adjoint image \(A^{\mathsf T}d\) (A.T @ data) with shape domain.N.

Notes

The returned field is the algebraic transpose under unweighted discrete sums. To represent a one-cell planar detector under the continuous surface and volume rectangle rules of Arridge et al. (2016), multiply this result by \(\Delta t/\Delta x_{\mathrm{normal}}\).

The source is Eq. (B.2) of Arridge et al. (2016). k-Wave's additive pressure-source preprocessing multiplies a user source by \(2\Delta t/(d\,c\,\Delta x)\) before adding it to each split density field. Consequently the user source must be

\[ \frac{\rho_{\mathrm{source}}c_{\mathrm{source}}\Delta x} {4\Delta t}\,\beta, \]

and the terminal pressure must be divided pointwise by \(c^2\rho\). additive-no-correction is intentional: the optional cosine k-space filter on the injected source is not part of Eq. (B.2). This mode still applies k-Wave's additive-source amplitude scaling and does not disable the sinc k-space correction used by the propagation operators.

The current k-wave-python Python backend uses a different pressure scaling for each spatial axis on anisotropic grids. A single scalar pressure signal cannot then create the equal split-density increments required by Eq. (B.2), so this implementation requires isotropic grid spacing.