Changelog¶
This file lists the notable changes in each jbubble release.
The format follows Keep a Changelog, and jbubble uses Semantic Versioning. While the major version is 0, a minor release can contain breaking changes.
0.2.0 - 2026-10-06¶
jbubble 0.2.0 is a beta release. It corrects several physics models against published references, keeps gradients finite through the adaptive solver, runs parameter sweeps on every CPU core, redesigns parameter fitting, and adds a documentation site with an example gallery. It contains breaking changes: Upgrading from 0.1 lists each one and what to change.
Upgrading from 0.1¶
Installation:
- jbubble needs Python 3.13 or later. Python 3.12 is no longer supported.
jbubble.utils.io, withexport_hdf5andload_hdf5, and the h5py dependency are removed in 0.2.0. Saving and loading results is planned to return in a later release. Until then, save results with NumPy, as innp.savezandnp.load, or with h5py directly.- The
devanddocsextras are gone. In a source checkout, use the dependency groups instead:uv syncoruv sync --group docs, orpip install -e ".[examples]" --group devwith pip 25.1 or later. - In a source checkout, delete the
jbubble/_version.pyfile and thejbubble.egg-info/directory that setuptools-scm left behind. Otherwisejbubble.__version__can report the old version.
Models:
- The liquid density
rho_Land speed of soundc_Lmove from the equation of motion to the medium, soRayleighPlesset,ModifiedRayleighPlesset, andKellerMiksisno longer accept them.KellerMiksis(..., medium=NewtonianMedium(mu=1e-3), rho_L=998.0, c_L=1500.0)becomesKellerMiksis(..., medium=NewtonianMedium(mu=1e-3, rho_L=998.0, c_L=1500.0)). Both default to water at 20 °C, so you can omit them for water.P_ambandR0stay on the equation of motion. - Paths to the liquid properties change:
eqx.tree_at(lambda e: e.rho_L, eom, x)becomeseqx.tree_at(lambda e: e.medium.rho_L, eom, x), andeom.c_Lbecomeseom.medium.c_L. Update the paths in your fits, sweeps, andmake_modelfunctions. IncompressibleMonopoleandQuasiAcoustictake amediuminstead ofrho_Landc_L.QuasiAcoustic(rho_L=998.0, c_L=1500.0)becomesQuasiAcoustic(medium=eom.medium), which radiates into the same liquid as the simulation.- A custom
MediumModelsubclass inherits the keyword-only fieldsrho_Landc_L. A custom equation of motion reads them asself.medium.rho_Landself.medium.c_L. LeightonTube,SphericalConfinement, andConfinedBubbleStateare removed, with no replacement in this release, and so are theSimulationResult.has_vessel,vessel_radius, andvessel_velocityaccessors.- The module-level type variable
jbubble.bubble.eom.StateTypeis removed.EquationOfMotiondeclares its state type as a type parameter instead, soEquationOfMotion[MyState]still works. To annotate generic code of your own, declare your own type variable. ThickShell,KelvinVoigtMedium,PowerLawMedium, andGompertzSurfaceTensionnow follow their published equations, so they give different results. Rerun simulations and refit parameters that use them.GompertzSurfaceTensionraisesValueErrorat construction unless 0 < σ(R0) <sigma_rupture,chi> 0, and 0 <R_buckle_ratio< 1.chiandsigma_ruptureonMarmottantSurfaceTensionandGompertzSurfaceTensionarePropertyfields. The constructors still accept floats. To replace one witheqx.tree_at, pass aPropertyor target its.val, as inlambda s: s.chi.val.- The presets have new, cited defaults.
lipid_bubbleusesSmoothMarmottantSurfaceTensioninstead ofGompertzSurfaceTension, and SonoVue parameters from Gümmer et al. (2021).thick_shell_bubbleuses the polymer-shell parameters of Hoff et al. (2000). To keep a specific value, pass it as a keyword argument. - The
Gilmoreequation of motion, which shipped in 0.1, is removed in 0.2.0 because it isn't yet validated against published trajectories. UseKellerMiksisinstead. The code is kept on thefeature/gilmorebranch, andGilmoreis planned to return once it's validated. QuasiAcousticreturns the monopole series at the solver's sample times. Plot it againstemission.observer_time(result, r), which isresult.ts + r / c_L, instead of againstresult.ts.
Pulses:
Summedhas no window of its own, so a sum equals the sum of its children. To keep the 0.1 behaviour, call.windowed(SoftRectangularEnvelope())on the sum.ScaledandOffsetraiseValueErrorfor a non-defaultinitial_timeorenvelope, which 0.1 ignored. Setinitial_timeon the wrapped pulse, or call.windowed(envelope).SampledPulseraisesValueErrorwheninitial_timeis neither 0.0 norts[0]. To delay a sampled waveform, shiftts.NeuralPulsestorespulse_durationandpressure_scaleas static Python floats, so you can't trace, vmap over, oreqx.tree_atthem. ANeuralPulsewith a non-zeroinitial_timenow delays the waveform, so the same weights give a different signal than in 0.1.
Solver and simulation:
- The default solver is
Dopri5instead ofKvaerno5, withrtol=1e-6,atol=1e-10, andmax_steps=100_000. In 0.1 they werertol=1e-3,atol=1e-6, andmax_steps=10_000. For stiff problems, such as lipid nanobubbles, useSolverConfig.stiff(). - The step-size tolerances apply to a dimensionless state: the radius in units of R0 and the velocity in units of sqrt(P_amb / rho_L). If you set
atolin metres, divide it by R0. run_simulationwarns about a failed solve only when you call it eagerly. Underjax.jitorjax.vmap, checkresult.converged.
Sweeps:
GridSweep.run()returns NumPy arrays, andGridSweep.batches()yields NumPy parameters and outputs, instead ofjax.Array.GridSweeprunsfnon worker threads.batch_sizerounds up to a multiple of the worker count, and results can depend on the chunk size. To get the same chunks on every machine, setworkersandbatch_size. Worker threads don't inheritwith jax.enable_x64(...)orwith jax.debug_nans(...); set these withjax.config.update.
Fitting:
FitResult.loss_historyhas one entry per accepted step plus one:loss_history[0]is the loss atparams0.FitResultis frozen.step_callbackalso runs at step 0.fit_parametersfits Python floats inparams0, which 0.1 held fixed.- When a solve fails or the loss isn't finite,
fit_parametershalves the step and, aftermax_backtrackshalvings, stops with a warning instead of raising. CheckFitResult.stopped_earlyandFitResult.message.
Added¶
SmoothMarmottantSurfaceTension(R_buckle_ratio, chi, sigma_rupture, smoothing=0.01), the Marmottant law with smoothed corners, for gradient-based fitting. It stays withinsmoothing * ln 2 * sigma_ruptureof the piecewise law. Near buckling or rupture at R0, such asR_buckle_ratioclose to 1, it biases results by up to a few percent; usesmoothing=0.005there.SolverConfig.stiff(), an implicitKvaerno5configuration with aChordroot finder, for lipid nanobubbles, sub-micron bubbles in viscous liquids, and other stiff problems.- The
adjoint=argument torun_simulation, for examplediffrax.ForwardMode()forjax.jacfwd. save_specis optional inrun_simulationandfit_parameters, and defaults toSaveSpec().- The
R=andR_dot=keyword arguments toEquationOfMotion.initial_state, to start away from equilibrium. - The
EquationOfMotion.is_admissible,GasModel.is_admissible, andEquationOfMotion.state_scalehooks.VanDerWaalsGasexcludes its hard core. EmissionModel.observer_time(result, r).NoEnvelope, a window that is 1 at all times.Pulse.t_start,Pulse.t_stop, andPulse.window_edges, the start, end, and window edges of a pulse in seconds, delegated throughScaled,Offset, andSummed.- The
devicesandworkersarguments toGridSweep, and itsper_worker,workers,devices, andnum_batchesattributes. jbubble.fitting.Parameter, which fits a value on a scaled coordinate with optional bounds or a fixed value, so one learning rate suits every parameter. As in SciPy, a bound of-inforinfmeans no bound.jbubble.fitting.unwrap, which replaces eachParameterin a pytree with its physical value, for your own training loops.- The
conditions=argument tofit_parameters, which fits shared parameters to several recordings, in parallel underjax.vmapwhen the recordings have the same shapes, and themax_backtracks=argument. FitResult.num_rejected,FitResult.stopped_early, andFitResult.message.jbubble.__version__.- The
jbubble.style.lightandjbubble.style.darkMatplotlib styles. Load one withplt.style.use("jbubble.style.light"). - A documentation site at https://imperial-nsb.github.io/jbubble/, with guides, including a new guide to fitting model parameters to data, and an API reference.
- An example gallery with 11 new examples, from a first simulation to learning a shell law with a neural network. Each example is a jupytext percent-format script in
examples/, and runs as a notebook in Google Colab. - Animated README figures, generated by
scripts/make_readme_assets.py. scripts/bench_gridsweep.py, which comparesGridSweepworker counts and checks that their outputs are identical.- Physics regression tests against independent references: the linearised resonance and damping of each equation of motion, and a hand-coded Keller-Miksis trajectory.
- This changelog,
CITATION.cff, and a roadmap.
Changed¶
MediumModelholds the liquid densityrho_Land speed of soundc_L, as keyword-only fields that default to water at 20 °C (998.0 kg/m³ and 1500 m/s). The equations of motion and the emission models read them from the medium, so the simulation and the radiated pressure use the same liquid.GridSweepruns grid chunks in parallel on worker threads, by default one per CPU core available to the process, up to 31 per CPU device. You no longer needJAX_NUM_CPU_DEVICESorXLA_FLAGSto use several cores. On a GPU or TPU, it uses every local device.GridSweeptracesfnonce and pads a ragged last batch with copies of the last grid point, so the last batch doesn't compile again. Chunks stream across batch boundaries, so a slow chunk holds up only its own worker. If you stop iterating overbatches()early, it cancels queued chunks.- The
GridSweepprogress bar usestqdm.auto, which renders in notebooks, and counts grid points. GompertzSurfaceTensionimplements Eq. 15 of Gümmer, Schenke & Denner (2021) exactly. In 0.1 its rate constant lacked a factor of e, so the law rose at 0.37 times the published rate. It validates concrete values at construction and skips traced values, such as insidejax.jitorjax.grad.chiandsigma_rupturearePropertyfields onMarmottantSurfaceTension,SmoothMarmottantSurfaceTension, andGompertzSurfaceTension, so they can depend on the bubble state.lipid_bubbleusesSmoothMarmottantSurfaceTensionand the SonoVue parameters of Gümmer et al. (2021):chi0.5 N/m,kappa_s7.5e-9 N s/m,R_buckle_ratio0.98058, andgamma1.095 for SF6. It has a newsmoothingkeyword. At 100 kPa and 1 MHz, the peak expansion of a 2 µm bubble falls from 42.9% to 22.7% of R0.thick_shell_bubbleuses the polymer-shell parameters of Hoff et al. (2000):G_s11.7 MPa,mu_s0.45 Pa s, and a shell thicknessd_sof 5% of R0 by default.- Every preset docstring cites the source of each default, and the presets share one liquid: water at 20 °C.
PowerLawMediumuses a smooth shear-rate floor, with a defaultepsof 1e2 s^-1.QuasiAcousticreturns the monopole series unchanged, to plot against the observer timets + r / c_L, instead of interpolating the radius, velocity, and acceleration at retarded times.- The default solver is
Dopri5, withrtol=1e-6andatol=1e-10on a dimensionless state, andmax_stepsrises from 10_000 to 100_000. solve_eommakes an adaptive step-size controller step to every pulse window edge.- Pulse arithmetic accepts JAX scalars, including traced values, so you can differentiate, jit, or vmap over a factor or an offset.
pulse + jnp_scalarbuilds anOffset. - Pulse arithmetic raises
TypeErrorfor an operand that isn't a pulse or a number, andValueErrorfor a non-scalar array.numpy_array * pulseno longer builds an object array of pulses. - Adding pulses keeps constant offsets outside the sum, so a window on the sum never gates a constant.
fit_parametersfits Python floats inparams0, and warns about Python floats inside Equinox modules, which stay fixed.fit_parametershalves a step whose solve fails or whose loss or gradient isn't finite, and stops with a warning instead of raising mid-fit.fit_parametersdefaults toSolverConfig()andSaveSpec(), likerun_simulation.- Docstrings use Markdown numpy style with LaTeX equations, and each module lists its public names in
__all__. - Packaging uses hatchling and hatch-vcs instead of setuptools and setuptools-scm, and the repository has a
uv.locklockfile. Development uses uv, with Python 3.13 by default. - Dependencies: the
jax==0.8.1andjaxlib==0.8.1pins becomejax>=0.8.1,diffrax>=0.7.2,equinox>=0.13.4, alineax>=0.1.0floor, and a declaredoptimistix>=0.1.0, and the NumPy floor rises tonumpy>=2.1. Matplotlib moves to the newexamplesextra, with amatplotlib>=3.9.2floor. These are the first releases with Python 3.13 wheels. - CI runs the fast suite on each pull request and push to
main, on Python 3.13 with the oldest supported dependencies and on Python 3.14 with the newest releases. Every week, CI runs the full suite on Python 3.13 with the oldest supported dependencies and with the lockfile, on Python 3.14 with the newest releases on Linux and macOS, and on the next Python as an early warning. CI also smoke-tests the built wheel. The docs workflow executes the examples and runs the code in the README and the guides. - A release publishes its documentation only after PyPI has the release.
Deprecated¶
- Nothing. jbubble is in beta, so this release changes and removes APIs without a deprecation period. Upgrading from 0.1 lists each breaking change.
Removed¶
- The confinement models
LeightonTubeandSphericalConfinement,ConfinedBubbleState, and theSimulationResult.has_vessel,vessel_radius, andvessel_velocityaccessors. jbubble.bubble.eom.StateType.EquationOfMotiondeclares its state type as a type parameter.- Support for Python 3.11.
jbubble.utils.io, withexport_hdf5andload_hdf5, and the h5py dependency. Saving and loading results is planned to return in a later release. Until then, save results with NumPy (np.savez) or with h5py directly.- The
Gilmoreequation of motion, which shipped in 0.1. It's kept on thefeature/gilmorebranch and is planned to return after it's validated against published trajectories. - The
chexdependency. - The
devanddocsextras, replaced by dependency groups. - The 0.1 example scripts, replaced by the example gallery.
Fixed¶
GompertzSurfaceTensionno longer deadlocks batched simulations. In 0.1, its construction check ran JAX operations inside ajax.debug.callback, which could intermittently hangjax.vmapruns andGridSweepsweeps. Every sweep over the 0.1lipid_bubblepreset was exposed. jbubble now runs no host callbacks inside traced code.run_simulationno longer adds a host callback underjax.jitandjax.vmap.ThickShellstresses were a factor of three too small. It now uses the finite-deformation Church model of Qin & Ferrara (2010), whose thin-shell limit is Hoff et al. (2000). For the 0.1 thick-shell preset values, the resonance moves from 2.33 MHz to 3.05 MHz.- The
KelvinVoigtMediumelastic term is (4G/3)(1 - (R0/R)^3), as in Yang & Church (2005). PowerLawMediumuses the rheometric shear rate 2 sqrt(3) |R_dot| / R.- Gradients are no longer NaN when an adaptive solver tries and rejects a step that leaves the physical domain. The forward solution with explicit solvers is unchanged, bit for bit.
- The adaptive solver no longer steps over a pulse that starts late, which left the bubble at rest while reporting
converged = True. - A user-supplied
state0with an unset or integer zeroR0orP_gas0is filled from the equation of motion, instead of giving a NaN trajectory. - A
Summedpulse can be traced, so you can simulate a sum of pulses. In 0.1 every sum failed under tracing. - Calling a sum of pulses with an array of times returns the sum at each time, not one value repeated.
- A delayed pulse inside a sum is no longer zeroed when it's scaled, negated, or offset, as in
p1 - p2orp1 + 0.5 * p2. windowed()on aScaledorOffsetpulse windows the wrapped pulse instead of doing nothing, and adding a pulse to a windowed sum keeps the sum's window.SampledPulsegates its signal from its first to its last sample time, so samples that start after 0 are no longer cut off early.NeuralPulsenormalises time as(t - initial_time) / pulse_duration, soinitial_timedelays the waveform instead of changing it.ExponentialSweepgives a constant tone, not NaN, whenfreq_endequalsfreq_start, and its gradients there are finite.fit_parametersraisesTypeErrorforadjoint=diffrax.ForwardMode()instead of failing inside JAX. It differentiates in reverse mode, whichForwardModedoesn't support.fit_parametersno longer compiles twice for a weakly typed initial array.
0.1.1 - 2026-04-09¶
Fixed¶
- Type annotations for
solve_eomandPulse.duration. - README badges and links, and the package classifiers.
0.1.0 - 2026-03-18¶
First public release.