Skip to content

acoustics

The jbubble.acoustics module provides models for computing the acoustic pressure radiated by a bubble, given a solved trajectory.

Emission models are not Property subclasses (they depend on the full trajectory, not just instantaneous state), and not part of the EoM (they are pure post-processing). They take a SimulationResult and a field-point distance r and return the pressure time series. They read the liquid density and speed of sound from a MediumModel, so pass the medium of the simulated equation of motion:

from jbubble.acoustics import IncompressibleMonopole

emission = IncompressibleMonopole(medium=eom.medium)
p_rad = emission(result, r=1e-2)  # pressure [Pa] at 1 cm, shape (num_samples,)

For multiple field-point distances, use jax.vmap:

import jax
import jax.numpy as jnp

distances = jnp.array([1e-3, 5e-3, 1e-2, 5e-2])
p_all = jax.vmap(lambda r: emission(result, r))(distances)
# shape: (4, num_samples)

EmissionModel

EmissionModel()

Bases: Module, ABC

Acoustic emission model: bubble trajectory → radiated pressure.

Subclasses implement __call__, which takes a solved SimulationResult and a field-point distance r, and returns the radiated pressure time series. Sample i of that series reaches the field point at observer_time(result, r)[i].

To evaluate several field-point distances, use jax.vmap:

distances = jnp.array([0.001, 0.005, 0.01])
p_all = jax.vmap(lambda r: model(result, r))(distances)
# shape (3, N)
Notes

The radiated pressure of an inertial collapse is a very short spike. e.g., For a 2 µm air bubble driven at 300 kPa and 1 MHz, the collapse peak has a full width at half maximum of about 0.4 ns, and the default SaveSpec, 1024 samples over the 10 µs of a 5-cycle pulse, captures only about a tenth of its height. To resolve such peaks, sample the trajectory every 0.1 ns or more finely, for example with SaveSpec(num_samples=100_001) over 10 µs.

observer_time

observer_time(result: SimulationResult, r: ArrayLike) -> Array

Return the time at which each emitted sample reaches distance r.

The base implementation assumes instantaneous propagation, so it returns result.ts.

Parameters:

Name Type Description Default
result SimulationResult

Solved bubble trajectory.

required
r float or Array

Distance from the bubble centre to the field point [m].

required

Returns:

Type Description
(Array, shape(N))

Observer times [s].

Source code in jbubble/acoustics/emission.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def observer_time(self, result: SimulationResult, r: ArrayLike) -> jax.Array:
    """Return the time at which each emitted sample reaches distance `r`.

    The base implementation assumes instantaneous propagation, so it
    returns `result.ts`.

    Parameters
    ----------
    result : SimulationResult
        Solved bubble trajectory.
    r : float or jax.Array
        Distance from the bubble centre to the field point [m].

    Returns
    -------
    jax.Array, shape (N,)
        Observer times [s].
    """
    del r
    return result.ts

IncompressibleMonopole

IncompressibleMonopole(medium: MediumModel)

Bases: EmissionModel

Incompressible monopole radiation.

Assumes an incompressible surrounding liquid, so the volume acceleration \(\ddot{V}\) of the bubble, \(V = 4\pi R^3/3\), gives the radiated pressure at distance \(r\):

\[ p_\text{rad}(r, t) = \frac{\rho_L \ddot{V}}{4\pi r} = \frac{\rho_L}{r}\frac{\mathrm{d}}{\mathrm{d}t}\left(R^2\dot{R}\right) = \frac{\rho_L}{r}\left(2R\dot{R}^2 + R^2\ddot{R}\right) \]

It's the far-field term of the incompressible pressure field \(p - p_\infty = \rho_L\left[(R^2\ddot{R} + 2R\dot{R}^2)/r - R^4\dot{R}^2/(2r^4)\right]\), so it's accurate when:

  • the field point is far from the wall, \(r \gg R\), where the dropped \(R^4\dot{R}^2/(2r^4)\) term is negligible;
  • the field point is within a small fraction of a wavelength, \(r \ll c_L/f\), so that the propagation delay doesn't matter;
  • the bubble-wall Mach number is small, \(M = \dot{R}/c_L \ll 1\).

Parameters:

Name Type Description Default
medium MediumModel

Surrounding medium. The model reads the liquid density \(\rho_L\) from medium.rho_L. Pass the medium of the simulated equation of motion, eom.medium.

required

QuasiAcoustic

QuasiAcoustic(medium: MediumModel)

Bases: EmissionModel

Monopole radiation delayed by the acoustic travel time.

Accounts for the finite speed of sound: the field point at distance \(r\) receives the monopole pressure that the bubble emitted at the retarded time \(t - r/c_L\):

\[ p_\text{rad}(r, t) = \frac{\rho_L}{r} \left(2R\dot{R}^2 + R^2\ddot{R}\right)\Big|_{t - r/c_L} \]

The model returns the monopole series of IncompressibleMonopole on the shifted time axis observer_time = result.ts + r / c_L. The pressure values are therefore identical to the incompressible monopole's; only the arrival times differ. Because the model doesn't resample the trajectory, it keeps every collapse peak that the saved samples resolve. If you need the pressure on another time grid, interpolate the returned series, for example jnp.interp(result.ts, t_obs, p_rad, left=p_rad[0]), rather than the separate \(R\), \(\dot{R}\), and \(\ddot{R}\) series: interpolating those and recombining them overshoots the true peak.

The validity conditions of the monopole, \(r \gg R\) and \(M \ll 1\), still apply; the delay removes the \(r \ll c_L/f\) condition.

Parameters:

Name Type Description Default
medium MediumModel

Surrounding medium. The model reads the liquid density \(\rho_L\) from medium.rho_L and the speed of sound \(c_L\) from medium.c_L. Pass the medium of the simulated equation of motion, eom.medium.

required

observer_time

observer_time(result: SimulationResult, r: ArrayLike) -> Array

Return result.ts + r / medium.c_L, the arrival time of each sample [s].

Parameters:

Name Type Description Default
result SimulationResult

Solved bubble trajectory.

required
r float or Array

Distance from the bubble centre to the field point [m].

required

Returns:

Type Description
(Array, shape(N))

Observer times [s].

Source code in jbubble/acoustics/emission.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def observer_time(self, result: SimulationResult, r: ArrayLike) -> jax.Array:
    """Return `result.ts + r / medium.c_L`, the arrival time of each sample [s].

    Parameters
    ----------
    result : SimulationResult
        Solved bubble trajectory.
    r : float or jax.Array
        Distance from the bubble centre to the field point [m].

    Returns
    -------
    jax.Array, shape (N,)
        Observer times [s].
    """
    return result.ts + jnp.asarray(r) / jnp.asarray(self.medium.c_L)

Choosing an emission model

Model Arrival times (observer_time) Validity
IncompressibleMonopole result.ts \(r \gg R\), \(r \ll c_L/f\), \(\text{Ma} \ll 1\)
QuasiAcoustic result.ts + r / c_L \(r \gg R\), \(\text{Ma} \ll 1\)

Both models return the same pressure values, computed from the saved samples without resampling, so both cost the same. QuasiAcoustic adds the propagation delay: plot each model's pressure against its own observer_time(result, r).

The fully compressible acoustic emission model (analogous to APECSS's wave equation integration) is planned but not yet implemented. For most research purposes, QuasiAcoustic at a field point distance \(r \gg R_0\) is adequate.