Releases: pearsonlab/spiking-ven
Release list
v0.3.0 - Brian2 parity fixes and dead-knob removal
A code review of the package found seven confirmed bugs, three blocks of surface that read as live but was not, and several instances of the drift the package already had machinery to prevent. Minor rather than patch because the fixes remove public surface and change two Brian2 defaults.
The numpy model and the published metrics are unchanged: K1 7.78 / K2 22.19 / K3 2.85x / K4 2.13x, and a retrained model is bit-identical across all 41 shared npz keys.
Fixed: brian2_runtime.py
The module exists so that values tuned against VocalErrorNetV2 "transfer here without translation". Nothing checked that — the tests built the groups and counted synapse objects without ever running the network, and five defects lived in that gap.
- The STDP presynaptic trace counted synapses, not spikes.
x_i_pre += 1sat in the JIE synapse'son_pre, butx_iis a neuron-level variable, so it advanced once per outgoing synapse. Measured peakx_iafter a single I spike: 61 / 306 / 1225 forN_e= 10 / 50 / 200, against 1.0 in the numpy model — the effective learning rate scaled with the E population, ~60x at the tuned operating point. Now incremented in the I group's ownreset. - The analytic threshold fallbacks used a superseded formula.
theta_ecame out at 0.184 against numpy's 0.114 (+61%), becausetarget_rate * tau_sstood in for the tonic drive;theta_idrifted whenevertau_s != 10ms. Both now matchVocalErrorNetV2exactly. - An all-zero weight matrix crashed inside Brian2's index handling with a bare
ValueError: zero-size array to reduction operation maximum. Only aud→E was guarded; all four matrices now wire through one helper. noise_iwas accepted and silently dropped — the I equation had no noise term at all. The term is now built in only when the amplitude is non-zero, so a noiseless population stays exactly noiseless instead of consuming a random draw per timestep and shifting every other group's stream.tau_swas accepted bybuild_ven_synapsesand unreadable there. It can only take effect viabuild_ven_groups, so a mismatch now raises instead of being ignored.- Conduction delays defaulted to 18/18 ms while the docstring cited 23 ms and the numpy model trains at 23/5. Defaults now come from
constants.
tests/test_brian2_parity.py pins every one of these, including a numpy-vs-Brian2 E-rate check.
Fixed: figures
- The white-noise column and the DAF column's added noise were the same realisation — both seeded
seed + 1. Independent now, which moves the DAF column's E rate from 16.7 to 18.7 Hz and leaves the other three columns pixel-identical. The attached figure is re-rendered. make_figureacceptedaud_delay_ms/hvc_delay_msand never read them, andsven-figurewired two flags straight into them. The delays belong to the trained model; the entry point now prints the ones in effect.- The output-tag regex used
re.sub, which returns its input unchanged on no match: any model not named*ven_model*.npzproduced a tag containing the whole path, and a figure path with a directory inside the basename. - Both figure modules ran
os.makedirs("outputs")at import, in the process working directory. - A DAF window past the end of the motif silently produced a DAF column identical to the training column; it now raises.
Removed
xe_th was computed, passed into the simulation loop, and never read. So r_e_th did nothing despite having a CLI flag, and alpha_bcm maintained a theta_bcm that never influenced plasticity — while the docstring said it replaced xe_th. Those are gone, along with --r-e-th and the out-of-scope Huber weight regulariser.
The knobs that are implemented but unexercised by the shipped pipeline (heterosynaptic competition, E→I iSTDP, JIE decay, song-target homeostasis, use_mask) are kept, and the class docstring now says they are untested.
Breaking
VocalErrorNetV2(...)no longer acceptsr_e_th,alpha_bcm,lambda_huber,huber_delta;.theta_bcmis gone. Models saved by earlier versions still load — the extra keys are ignored.sven-train-ven --r-e-thandsven-figure --aud-delay-ms/--hvc-delay-msare gone (all three were no-ops).build_ven_groups()threshold fallbacks andbuild_ven_synapses()delay defaults changed values;build_ven_synapsesnow raises on atau_smismatch.compute_encoding_columns()andmake_rate_figure()no longer taken_kernels(unused).generate_hvc_spikes()raises whenTis too short to hold its bursts, instead of silently dropping them.OlshausenFieldEncoder.fit()raises on a patch matrix passed positionally, which used to run the audio path on patch rows and appear to work.
Also
- K2/K3 are matched-input-rate comparisons. The encoder path RMS-normalises and then rate-calibrates every stimulus, so
DAF_WN_AMPLITUDEdivides straight back out: the metrics measure a spectrotemporal mismatch at equal input drive, not a louder stimulus. That is the stronger result, but not what the "DAF" name implies, so it is now stated inevaluate.py,constants.pyand the README. The figure's DAF column is a genuine amplitude manipulation and is unaffected. constants.SR/AUD_DELAY_MS/HVC_DELAY_MSwere read only bysven-figure;train_venhardcoded 23/5 and the sample rate. One source now.data/motifs.pyre-derived the template rendition instead of callingcorpus.highest_rms_index, which exists for that call site;train_encoderopenedmotifs.npzby hand and re-derived an unusedT_song.spike_to_rateruns throughlfilterinstead of a Python loop over timesteps — bit-identical for bool, float32 and float64 input.- Corrected paper reference: the first author is Gong, not Duarte Ortiz, and the paper is now an eLife reviewed preprint (doi:10.7554/eLife.111771.1, 2026).
39 fast tests (was 27), 7 reproduction assertions, lint clean.
v0.2.0 - remove the Smith-Lewicki encoder
Removed
smith_lewicki.py (259 lines) and its three exports — SmithLewickiDictionary, sl_gram, sl_gram_to_spikes.
The Lewicki encoder was ruled out as a VEN front end: its forward/reversed activation correlation is ~0.99 because it is amplitude-invariant, so K4 caps at 1.22× against the Olshausen-Field encoder's 2.11×. It survived here only because finchsim's circuit path still used it, and that path is moving to OF. The README note explaining why it was kept goes with it.
The OF path is unchanged — the figure renders byte-identical to v0.1.1 and all 7 reproduction assertions pass.
Added: of_to_spikes(calibrate_on=...)
The rate scale is mean_rate_hz / act.mean() over the whole array, so handing it a sparse timeline — one song tiled into motif windows with silence between — divides by a mean diluted by the silence and inflates the rate during song. Measured on a 25% duty cycle: 61 Hz against a 15 Hz target. Passing the song-window activations as calibrate_on gives 14.2 Hz. Inert when unused (output identical with calibrate_on=None).
This is needed by the finchsim circuit migration, and is the kind of error that would otherwise have been adopted silently as 'the new AIV drive' rather than noticed.
Also
n_channels's docstring no longer points at a sibling class that no longer exists, and its test is parametrised over 5/7/64 widths instead of comparing two encoder classes.
27 fast tests (was 25), 7 reproduction assertions, lint clean.
v0.1.1 - review cleanups
Quality pass over the whole package. No behaviour change — verified by the figure rendering byte-identical to v0.1.0 (sha256 9ec64bb3…) and sven-evaluate returning bit-identical metrics (K1 7.83237886428833, K4 2.0749239803578408).
API changes
Three public symbols removed, all dead in this package and in finchsim:
of_encode (superseded by coch_encode), build_sl_filterbank and build_sl_spike_group (no callers, and the only pieces here that could not compile to Brian2 standalone).
sven-figure --encoder lewicki is gone. It read outputs/lewicki_motif_filters.npz, which nothing in this package produces — that training code stayed in finchsim — so the option was unusable from a clean clone. The Smith-Lewicki classes remain exported and supported, since finchsim's circuit path uses them; passing one to the figure now raises a clear TypeError.
New
constants.py—SR,KERNEL_WIDTH_MS,PEAK_RATE_HZ,DAF_WN_AMPLITUDE,DAF_WINDOW_S, and the two conduction delays.PEAK_RATEwas derived in one file and hardcoded as300.0in three figure signatures, so changing the kernel width would silently have left the figures behind.corpus.py—Corpusandhighest_rms_index(). The template rule ('the loudest rendition is the template') was re-derived in four places; if they had diverged, the figure and the metrics would have described a different motif than training used.n_channelson both encoder classes, replacing ahasattr(enc, 'n_bases') else 64fallback that would have mis-sized arrays for any dictionary not 64 wide.
Also removed a vestigial np.random.seed() before inference — a leftover from before the network owned a seeded generator. The byte-identical render is the proof it no longer did anything.
Tests
25 fast (was 18) + 7 reproduction assertions, lint clean, make verify clean.
v0.1.0 - spiking auditory encoder and vocal error network
First release of the spiking sparse auditory encoder and vocal error network (VEN) for the zebra finch song system — the spiking counterpart to the rate-based Wilson–Cowan model in pearsonlab/vocal-error-network (Duarte Ortiz et al. 2025, bioRxiv 2025.07.18.665446).
Extracted from the finchsim circuit model so it can be cited and reused on its own. finchsim now consumes it as a dependency.
What it does
Song audio → gammatone cochleagram → sparse-coded spike train → an excitatory/inhibitory network that learns to cancel the predictable auditory response to the bird's own song, leaving an error signal on novel or perturbed sound.
The result, from outputs/encoding_comparison_k4max.pdf: excitatory-population rates of ~7 Hz on the trained song versus ~18, ~21 and ~17 Hz on the time-reversed motif, white noise, and distorted auditory feedback.
Metrics (Mandelblat-Cerf et al. 2014, Fig. 7/8)
| Metric | This release | Biological target |
|---|---|---|
| encoder forward/reversed correlation | 0.0036 | near zero |
| K1 — correct song + HVC | 7.78 Hz | mean 7.7 Hz, SD 8.7 |
| K2 — white noise (DAF) + HVC | 22.19 Hz | pop. ~16 Hz; responders ~28 Hz |
| K3 — K2/K1 | 2.85× | pop. ~2.1×; responders ~3.6× |
| K4 — reversed / correct | 2.13× | > 1× |
The model is a responder-only population, so the responder-only targets are the relevant ones. make test-repro asserts all of these against the attached model.
Reproducing
uv sync
make figure # corpus -> encoder -> network -> figure, ~10 min on CPU
make verify # check artifacts against MANIFEST.sha256Fully seeded end to end: re-running reproduces the numbers rather than landing nearby. The encoder's dictionary learning and the network's simulation noise are both driven by explicit generators.
Dependencies
Core install is numpy and scipy only — a fresh uv sync brings in three packages. Brian2, soundfile and matplotlib sit behind the brian2, data and plots extras. import spiking_ven never pulls in Brian2. No PyTorch, numba, seaborn or scikit-learn.
The ERB/gammatone filterbank is vendored (from PyPI Gammatone 1.0.3, BSD-3-Clause, upstream archived 2024) rather than depended on, copied byte-for-byte and verified bit-identical to the released package. See THIRD_PARTY_NOTICES.md.
Attached artifacts
| File | What |
|---|---|
motifs.npz |
38 DTW-aligned R469 motifs @ 16 kHz |
of_encoder.npz |
trained Olshausen-Field sparse encoder (64 bases) |
of_ven_model_k4max.npz |
trained vocal error network |
encoding_comparison_k4max.pdf |
the cancellation figure |
The corpus is derived from "Labeled Zebra Finch Songs" (Koch, Therese), DOI 10.18738/T8/SAWMUN, released under CC0 1.0. Please cite it.
Known limitations
- The corpus download fails from datacenter IPs (CloudFront returns 403), so CI runs a synthetic-corpus smoke test instead. Use
--skip-downloadwith a hand-placed corpus if you are blocked. - Checksums in
MANIFEST.sha256record what this build produced; a different platform or BLAS can give different last-bit results and so a different digest without anything being wrong.make test-reprois the authoritative reproduction check.