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.