hv-chemical-bubble

Keller-Miksis bubble reservoir computing with a parity certificate — a 16-function capability test that classifies any reservoir substrate as linear, single-threshold-nonlinear, or multi-layer-nonlinear. NumPy only.

Size: ~26 KB source, no weights. Runtime: ~30 s for the full benchmark (multi-bubble batteries). Dependencies: NumPy only.

The finding

The SKILLS archive makes a specific quantitative prediction: a capability battery of 16 boolean functions of 2 inputs, scored by a linear readout, classifies any reservoir substrate into three regimes:

fitness capability class
16/16 multi-layer nonlinear
12–14/16 single threshold layer
≤8/16 linear

The bubble reservoir reproduces all three regimes exactly.

config fitness class
1 bubble, 0.15 atm drive 8/16 linear
1 bubble, 1.0 atm drive 14/16 single threshold layer
3+ bubbles, 1.0 atm drive 16/16 multi-layer nonlinear

The parity certificate is the contribution. The bubble is the demonstration.

The primitive

A gas bubble in water is a nonlinear oscillator. Its radial dynamics follow the Keller-Miksis equation:

(1 − Ṙ/c) R R̈ + (3/2)(1 − Ṙ/(3c)) Ṙ²
    = (1 + Ṙ/c)(p_B − p_∞)/ρ + (R/ρc) dp_B/dt

where R is radius, c the speed of sound, p_B the internal pressure, p_∞ the acoustic drive, ρ the liquid density.

The state (R(t), Ṙ(t)) is a rich response to the drive. That makes the bubble a reservoir — its trajectory is the feature space, and a linear readout on top of it computes nonlinear functions of the input.

The parity certificate

The test:

  1. Encode boolean inputs as acoustic drive patterns. Bit 1 → high amplitude, bit 0 → low amplitude, both at the Minnaert frequency.
  2. Run the bubble reservoir for each of the 2^(2^n) boolean functions.
  3. Fit a linear readout on the reservoir's final state.
  4. Fitness = fraction of functions solved with accuracy ≥ 0.8.

The classification thresholds are structural:

  • 8/16 — the number of linearly separable boolean functions of 2 inputs. A linear reservoir cannot exceed this.
  • 14/16 — the signature of a single threshold layer.
  • 16/16 — full nonlinearity (multi-layer capability).

The three diagnostics

Parity certificate. Classifies computational capability. Runs in 1.6 s per bubble for the 16-function battery.

Return-map Lyapunov exponent. Samples R once per drive period, fits nearest-neighbor divergence on the return map. Robust during collapse events where tangent propagation would blow up.

Results at three drive conditions:

frequency amp LLE regime
0.5× Minnaert 0.5 −0.14 periodic
0.5× Minnaert 1.5 −0.01 near-critical
1.0× Minnaert 0.5 +0.13 chaotic
2.0× Minnaert 1.0 +0.21 chaotic

At 0.5× Minnaert the bubble is periodic. Above Minnaert it enters chaos. The transition happens near the Minnaert frequency itself.

Shared normalization check. Reproduces the fake "+10 dB" result from the SKILLS archive. Independent normalization of input and target inflates R². The test verifies that shared normalization is at least as good as independent.

Headline numbers

diagnostic result
Free oscillation R/R0 ∈ [0.98, 1.02]
Driven at 1.0 atm R/R0 ∈ [0.69, 1.28]
Minnaert frequency (R0 = 1 μm) 3.28 MHz
Minnaert freq scaling exactly ∝ 1/R0
LLE chaotic threshold near Minnaert frequency
Multi-bubble channel count 3 channels for 3 bubbles
13/13 consistency checks pass

How to use

from hv_chemical_bubble import (
    BubbleParams, KellerMiksis, BubbleReservoir,
    capability_battery, run_single_task,
    return_map_lle, shared_normalization_check,
    interpret_fitness, make_drive_signal,
)
import math

# Single bubble simulation
params = BubbleParams()
bubble = KellerMiksis(params)
freq = params.minnaert_freq()
drive = lambda t: params.p_inf * (1.0 + 1.0 * math.sin(2 * math.pi * freq * t))
result = bubble.simulate(drive, n_steps=5000, dt=1e-9)
print(result['R_over_R0'].min(), result['R_over_R0'].max())

# Parity certificate
battery = capability_battery(n_inputs=2, n_steps_per_bit=500,
                              drive_amp=1.0, n_bubbles=5)
print(battery['fitness'])         # 1.0
print(interpret_fitness(battery['fitness']))
# 'multi-layer nonlinear (16/16)'

# Return-map Lyapunov exponent
lle = return_map_lle(drive_freq=freq, drive_amp=1.0,
                     n_periods=300, steps_per_period=150)
print(lle['lle'])                 # ~+0.06

# Shared normalization check
sn = shared_normalization_check()
print(sn['r2_independent'], sn['r2_shared'])
Downloads last month
21
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support