pulse¶
The jbubble.pulse module provides composable, differentiable acoustic driving waveforms.
All pulses are Equinox modules and implement the interface:
Base classes¶
jbubble.pulse.base.Pulse
¶
Bases: Module, ABC
Abstract acoustic driving pulse.
Every Pulse is callable: pulse(t) returns the instantaneous
pressure [Pa] at time t. Implementations must be JAX-differentiable
so that equations of motion (e.g. Keller–Miksis) can compute
jax.grad(pulse)(t).
Subclasses implement :meth:_evaluate (the raw, un-enveloped signal).
The base :meth:__call__ applies the :attr:envelope automatically.
Operator overloads for composition::
combined = pulse_a + pulse_b # → Summed
scaled = 0.5 * pulse_a # → Scaled
windowed = combined.windowed(HannEnvelope()) # swap envelope
duration
abstractmethod
property
¶
Active pulse duration [s] (excluding any leading silence).
t_end
property
¶
Suggested simulation end time [s].
Default: initial_time + 2 × duration.
__call__(t)
¶
Evaluate pressure at time t [Pa], with envelope applied.
__add__(other)
¶
Add another pulse or constant offset: pulse_a + pulse_b or pulse + 1.0
Source code in jbubble/pulse/base.py
__radd__(other)
¶
Right addition: other + self. If other is a Pulse, delegate to its add.
Source code in jbubble/pulse/base.py
__mul__(factor)
¶
__rmul__(factor)
¶
__neg__()
¶
__pos__()
¶
__sub__(other)
¶
Subtract another pulse or constant offset: pulse_a - pulse_b or pulse - 1.0
__rsub__(other)
¶
Right subtraction: other - self
Source code in jbubble/pulse/base.py
__truediv__(factor)
¶
__iadd__(other)
¶
__isub__(other)
¶
__imul__(factor)
¶
__itruediv__(factor)
¶
windowed(envelope)
¶
Return a copy of this pulse with envelope replacing the current one.
jbubble.pulse.base.Scaled
¶
Bases: Pulse
Amplitude-scaled version of another pulse.
Scaled is transparent: it delegates entirely to the child pulse's
__call__ (which already applies the child's envelope) and simply
multiplies by factor. No additional envelope is applied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pulse
|
Pulse
|
The pulse to scale. |
required |
factor
|
float
|
Multiplicative factor. |
required |
jbubble.pulse.base.Summed
¶
Bases: Pulse
Additive superposition of multiple pulses.
Each child pulse is evaluated with its own envelope, then the results
are summed. The Summed pulse's own :attr:envelope (inherited
from :class:Pulse, default RectangularEnvelope) is applied on
top — use .windowed(HannEnvelope()) to window the combined signal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pulses
|
tuple[Pulse, ...]
|
Pulses to sum. Must be a tuple (not a list) for Equinox PyTree compatibility. |
required |
jbubble.pulse.base.Offset
¶
Bases: Pulse
Constant-offset version of another pulse.
Offset is transparent: it delegates entirely to the child pulse's
__call__ (which already applies the child's envelope) and simply
adds a constant offset. No additional envelope is applied.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pulse
|
Pulse
|
The pulse to offset. |
required |
offset
|
float
|
Additive constant offset. |
required |
Pulse types¶
jbubble.pulse.tone_burst.ToneBurst
¶
Bases: Pulse
Tone burst: carrier waveform × envelope × pressure amplitude.
A ToneBurst is the standard parametric pulse used in most ultrasound
simulations. It combines a periodic :class:PulseShape (e.g. Sine)
with an :class:Envelope (e.g. HannEnvelope) and a peak pressure.
The initial_time and envelope fields are inherited from
:class:Pulse and can be set as keyword arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq
|
float
|
Carrier frequency [Hz]. |
required |
pressure
|
float
|
Peak pressure amplitude [Pa]. |
required |
shape
|
PulseShape
|
Waveform shape ( |
required |
phase
|
float
|
Carrier phase offset [rad]. Default: 0. |
required |
cycle_num
|
float
|
Number of carrier cycles in the burst. Default: 4. |
required |
Examples:
>>> import jax.numpy as jnp
>>> from jbubble.pulse import ToneBurst
>>> from jbubble.pulse.shapes import Sine
>>> pulse = ToneBurst(freq=1e6, pressure=100e3, shape=Sine(), cycle_num=5)
>>> float(pulse(jnp.array(0.0))) < 0.5 # sigmoid is ~0.5 at t=0
True
jbubble.pulse.chirp.ChirpPulse
¶
Bases: Pulse
Frequency-sweep pulse with a composable carrier shape and sweep law.
The :attr:shape determines the carrier waveform (default: sine); the
:attr:sweep determines how instantaneous frequency varies with time
(default: linear). Both are :class:~equinox.Module leaves — swapping
either keeps the same computational graph and does not force a re-trace.
The shape is evaluated at the instantaneous accumulated phase Φ(τ),
so any :class:~jbubble.pulse.shapes.PulseShape works as a carrier.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freq_start
|
float
|
Instantaneous frequency at the start [Hz]. |
required |
freq_end
|
float
|
Instantaneous frequency at the end [Hz]. |
required |
pressure
|
float
|
Peak pressure amplitude [Pa]. |
required |
sweep_duration
|
float
|
Duration of the frequency sweep [s]. |
required |
shape
|
PulseShape
|
Carrier waveform shape (default: :class: |
required |
sweep
|
ChirpSweep
|
Phase law (default: :class: |
required |
Examples:
>>> from jbubble.pulse import ChirpPulse, HannEnvelope
>>> from jbubble.pulse.chirp import ExponentialSweep
>>> from jbubble.pulse.shapes import Square
>>> chirp = ChirpPulse(
... freq_start=0.5e6, freq_end=2e6,
... pressure=200e3, sweep_duration=10e-6,
... shape=Square(), sweep=ExponentialSweep(),
... envelope=HannEnvelope(),
... )
jbubble.pulse.sampled.SampledPulse
¶
Bases: Pulse
Acoustic pulse defined by an array of pressure samples.
Evaluates the pressure at arbitrary times via piecewise-linear
interpolation (jnp.interp). The inherited envelope (default
RectangularEnvelope) gates the signal to
[initial_time, initial_time + duration].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ts
|
(Array, shape(N))
|
Sample time points [s]. Must be monotonically increasing. |
required |
pressures
|
(Array, shape(N))
|
Pressure values [Pa] at each sample time. |
required |
Examples:
>>> import jax.numpy as jnp
>>> from jbubble.pulse import SampledPulse
>>> ts = jnp.linspace(0, 10e-6, 1000)
>>> pressures = 200e3 * jnp.sin(2 * jnp.pi * 1e6 * ts)
>>> pulse = SampledPulse(ts=ts, pressures=pressures)
>>> pulse.duration # 10 µs
1e-05
from_uniform(pressures, dt, initial_time=0.0)
staticmethod
¶
Create a SampledPulse from uniformly-spaced samples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pressures
|
(Array, shape(N))
|
Pressure values [Pa]. |
required |
dt
|
float
|
Time step between samples [s]. |
required |
initial_time
|
float
|
Time of the first sample [s]. Default: 0. |
0.0
|
Source code in jbubble/pulse/sampled.py
jbubble.pulse.neural.NeuralPulse
¶
Bases: Pulse
Acoustic pulse parameterised by a neural network.
The network maps normalised time t / duration to instantaneous
pressure, scaled by pressure_scale. Because the entire pulse is an
Equinox module, its parameters participate in JAX transformations
(jit, grad, vmap) — making it straightforward to optimise
the driving waveform via gradient descent.
The initial_time and envelope fields are inherited from
:class:Pulse and can be set as keyword arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
net
|
Module
|
Any callable Equinox module (e.g. |
required |
pulse_duration
|
float
|
Nominal pulse duration [s] — used for time normalisation and
the :attr: |
required |
pressure_scale
|
float
|
Multiplicative scaling applied to the network output [Pa]. Default: 1.0. |
required |
Examples:
>>> import jax
>>> import equinox as eqx
>>> from jbubble.pulse import NeuralPulse
>>> key = jax.random.PRNGKey(0)
>>> mlp = eqx.nn.MLP(in_size=1, out_size=1, width_size=32,
... depth=2, key=key)
>>> pulse = NeuralPulse(net=mlp, pulse_duration=10e-6,
... pressure_scale=200e3)
Envelopes¶
jbubble.pulse.envelope.Envelope
¶
Bases: Module, ABC
Window function mapping relative time tau to a scale in [0, 1].
Called as envelope(tau, duration) where tau = t − initial_time.
Returns 0 outside [0, duration].
jbubble.pulse.envelope.RectangularEnvelope
¶
jbubble.pulse.envelope.SoftRectangularEnvelope
¶
Bases: Envelope
Smooth approximation to a rectangular window using sigmoid transitions.
Replaces the hard on/off step of :class:RectangularEnvelope with
smooth sigmoid ramps, keeping dp_ac/dt continuous everywhere.
This is the preferred envelope when a near-rectangular window is needed
for gradient-based parameter fitting via the adjoint method.
The window value is::
w(τ) = σ(τ / k) · σ((T − τ) / k), k = T / steepness
where σ is the logistic sigmoid and T is the pulse duration.
The plateau is flat to within 2·exp(−steepness/2) of 1.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steepness
|
float
|
Controls transition sharpness. Transitions span roughly
|
required |