hv-tctn-universal
A universal delay-line coincidence detector for velocity estimation across sensor arrays. One algorithm. Four substrates. No training.
Runtime: ~2 s per substrate (10 velocities Γ 20k-step drives). Dependencies: NumPy only.
What it does
Given a bank of sensors at known positions and a propagating spatial pattern, estimate the pattern's velocity. The algorithm is the TCTN (Temporal Coincidence of Triggered Neurons-style cell):
cell (i, j, k) fires when:
sensor i was active at delay Ο_k ago
AND
sensor j is active now
preferred velocity of cell (i, j, k):
v_pref(i, j, k) = (x_j β x_i) / Ο_k
For a pulse moving at velocity v, the coincidence is strong exactly when Ο_k β (x_j β x_i) / v. Classification is by top-k cell voting over the peaks of all cells.
The substrate changes. Sensor response shape, decay constants, and noise models differ. The algorithm does not. This is the central claim of the XuanJi SKILLS archive (chapters 4β7), reproduced here on synthetic drive signals.
Headline numbers
Four substrates, ten velocities each (five positive, five negative), N = 8β16 sensors, no noise:
| substrate | mean rel err | max rel err | all < 15% |
|---|---|---|---|
| spaced_8 (photodiode-style) | 4.4% | 11.3% | β |
| spaced_16 (EEG-style) | 2.2% | 3.5% | β |
| dielectric_8 (Ο ladder) | 5.5% | 10.1% | β |
| qubit_8 (T2 ladder) | 6.1% | 12.6% | β |
Sign correct in all 80 substrate-velocity combinations. 85/85 consistency checks pass.
The four substrates
| name | positions | Ο ladder | mode | notes |
|---|---|---|---|---|
spaced_8 |
linspace(0, 1, 8) | uniform 0.1 | pulse | photodiode-like |
spaced_16 |
linspace(0, 1, 16) | uniform 0.05 | pulse | EEG-like, denser array |
dielectric_8 |
linspace(0, 1, 8) | geomspace(0.05, 0.5, 8) | step | relaxation ladder |
qubit_8 |
linspace(0, 1, 8) | geomspace(0.1, 1.0, 8) | step | T2 ladder |
The substrate spec is a dictionary: positions, response_tau, mode
(pulse or step), and differentiate (True for step-mode).
The four rules from SKILLS chapter 7
Enforced in the code and validated in the benchmark:
- Single pulse per sensor. Never periodic drive.
- Differentiate step responses. A step response is a plateau; the TCTN needs a pulse.
- Direction-aware reference sensor. For v < 0, use the rightmost sensor as reference; for v > 0, the leftmost.
- Adaptive pulse width. Cap at
spacing / |v| / 4so adjacent pulses do not overlap at high velocity.
Noise robustness
spaced_8, v = 2.0, white noise added to the drive:
| noise | v_est | rel err |
|---|---|---|
| 0.00 | 2.013 | 0.7% |
| 0.01 | 1.959 | 2.1% |
| 0.02 | 1.904 | 4.8% |
| 0.05 | 1.876 | 6.2% |
| 0.10 | 1.984 | 0.8% |
| 0.20 | 1.252 | 37.4% |
Noise floor at ~0.05β0.10. The 0.10 result is a lucky estimate; the 0.20 result is a real failure. On real hardware with calibrated sensors, the effective noise is usually well below 0.05.
The differentiate on/off demonstration
For step-mode substrates, the difference is categorical:
| substrate | differentiate=False | differentiate=True |
|---|---|---|
| dielectric_8, v=1 | 0.000 (100% err) | 0.993 (0.7%) |
| dielectric_8, v=2 | 0.000 (100% err) | 2.195 (9.7%) |
| qubit_8, v=1 | 0.000 (100% err) | 1.007 (0.7%) |
| qubit_8, v=2 | 0.000 (100% err) | 2.139 (7.0%) |
A step response is invisible to the TCTN. The derivative converts it to a pulse. This is rule 2, and it is the single most important rule in the archive: the raw readout of a physical sensor is usually a step, not a pulse.
Periodic drive failure (rule 1)
The benchmark includes one negative control: a periodic drive at period 0.5 across the array. The TCTN gives a velocity far from the intended 2.0, because the period aliases with the delay ladder. This is why the algorithm requires single pulses.
How to use
from hv_tctn_universal import (
substrate, measure_velocity, UniversalTCTN, choose_delays, make_drive)
# One-shot velocity estimate
spec = substrate('qubit_8')
v_hat, confidence = measure_velocity(spec, velocity=2.0, noise=0.01)
print(f"v_hat = {v_hat:.3f}, confidence = {confidence:.4f}")
# Custom substrate
my_spec = {
'positions': np.linspace(0, 1, 12),
'response_tau': np.geomspace(0.02, 0.4, 12),
'mode': 'step',
'differentiate': True,
'description': 'custom photodiode array',
}
v_hat, _ = measure_velocity(my_spec, velocity=4.0)
# Manual pipeline (for streaming)
positions = np.linspace(0, 1, 8)
delays = choose_delays(positions, v_min=0.5, v_max=8.0, n_delays=30)
tctn = UniversalTCTN(positions, delays, dt=0.02, differentiate=False)
for t in range(n_steps):
tctn.step(readout[t])
v_hat, confidence = tctn.classify(k_top=3)
- Downloads last month
- 17