From 406cce3c876b8e6fe18265035f2a0713880b63d9 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 10:54:24 -0400 Subject: [PATCH 01/15] Add regression test for preallocated Monitor short-run bug (PR #761) Covers the fix in #761 (commit a9f7e430): a preallocated Monitor (time set) run for fewer steps than the preallocated duration left placeholder lists in the recording, crashing torch.cat in Monitor.get. The new TestMonitorShortRun asserts get() returns a tensor truncated to the actual run length, and still returns the full length once the buffer is filled. Co-Authored-By: Claude Opus 4.8 --- test/network/test_monitors.py | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/network/test_monitors.py b/test/network/test_monitors.py index a2d194b5c..7f57ec756 100644 --- a/test/network/test_monitors.py +++ b/test/network/test_monitors.py @@ -45,6 +45,46 @@ class TestMonitor: assert _if_mon.get("v").size() == torch.Size([500, 1, _if.n]) +class TestMonitorShortRun: + """ + Testing a preallocated Monitor (``time`` set) that runs for fewer steps than + the preallocated duration. The leftover placeholders must be dropped so that + ``get`` returns a tensor truncated to the actual run length instead of + crashing in ``torch.cat`` (regression test for the preallocated-buffer bug). + """ + + network = Network() + + inpt = Input(75) + network.add_layer(inpt, name="X") + _if = IFNodes(25) + network.add_layer(_if, name="Y") + conn = Connection(inpt, _if, w=torch.rand(inpt.n, _if.n)) + network.add_connection(conn, source="X", target="Y") + + # Preallocate for 100 steps but only run 10. + inpt_mon = Monitor(inpt, state_vars=["s"], time=100) + network.add_monitor(inpt_mon, name="X") + _if_mon = Monitor(_if, state_vars=["s", "v"], time=100) + network.add_monitor(_if_mon, name="Y") + + network.run(inputs={"X": torch.bernoulli(torch.rand(10, inpt.n))}, time=10) + + assert inpt_mon.get("s").size() == torch.Size([10, 1, inpt.n]) + assert _if_mon.get("s").size() == torch.Size([10, 1, _if.n]) + assert _if_mon.get("v").size() == torch.Size([10, 1, _if.n]) + + # Filling the buffer afterwards still returns the full preallocated length. + inpt_mon.reset_state_variables() + _if_mon.reset_state_variables() + + network.run(inputs={"X": torch.bernoulli(torch.rand(100, inpt.n))}, time=100) + + assert inpt_mon.get("s").size() == torch.Size([100, 1, inpt.n]) + assert _if_mon.get("s").size() == torch.Size([100, 1, _if.n]) + assert _if_mon.get("v").size() == torch.Size([100, 1, _if.n]) + + class TestNetworkMonitor: """ Testing NetworkMonitor object. @@ -86,4 +126,5 @@ class TestNetworkMonitor: if __name__ == "__main__": tm = TestMonitor() + tmsr = TestMonitorShortRun() tnm = TestNetworkMonitor() From 593d8dceb8ba6fc0f3dc62d8de4f311ea20432ea Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 11:10:23 -0400 Subject: [PATCH 02/15] fix: bernoulli_loader honors max_prob kwarg (#743) bernoulli_loader read kwargs.get("dt") instead of kwargs.get("max_prob"). Since dt is a named parameter it never lands in **kwargs, so max_prob was silently ignored and the spike rate stayed at 1.0 regardless of the argument. Adds test_bernoulli_loader_max_prob, which asserts the empirical spike rate tracks max_prob (the existing test only checked output shape, so the bug went undetected). Fix mirrors PR #743. Co-Authored-By: Claude Opus 4.8 --- bindsnet/encoding/loaders.py | 2 +- test/encoding/test_encoding.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/bindsnet/encoding/loaders.py b/bindsnet/encoding/loaders.py index 3ba7f881b..eb194d1f2 100644 --- a/bindsnet/encoding/loaders.py +++ b/bindsnet/encoding/loaders.py @@ -26,7 +26,7 @@ def bernoulli_loader( :param float max_prob: Maximum probability of spike per Bernoulli trial. """ # Setting kwargs. - max_prob = kwargs.get("dt", 1.0) + max_prob = kwargs.get("max_prob", 1.0) for i in range(len(data)): # Encode datum as Bernoulli spike trains. diff --git a/test/encoding/test_encoding.py b/test/encoding/test_encoding.py index f56ee8cea..2e95998b2 100644 --- a/test/encoding/test_encoding.py +++ b/test/encoding/test_encoding.py @@ -37,6 +37,29 @@ def test_bernoulli_loader(self): for i, spikes in enumerate(spike_loader): assert spikes.size() == torch.Size((t, n)) + def test_bernoulli_loader_max_prob(self): + # Regression test (PR #743): bernoulli_loader must honor the ``max_prob`` + # keyword argument. Previously it read ``kwargs.get("dt")``, which never + # exists in kwargs (``dt`` is a named parameter), so max_prob was silently + # ignored and the spike rate stayed at 1.0 regardless of the argument. + torch.manual_seed(0) + + # All-ones input over many trials: the empirical spike rate should track + # max_prob, not the dt default of 1.0. + data = torch.ones(1, 20000) + for m in [0.0, 0.25, 0.5, 0.75]: + spikes = next(bernoulli_loader(data, time=1, max_prob=m)) + assert abs(spikes.float().mean().item() - m) < 0.02 + + # dt must not leak into max_prob: with dt set but max_prob explicit, + # the rate follows max_prob. + spikes = next(bernoulli_loader(data, time=1, dt=0.5, max_prob=0.3)) + assert abs(spikes.float().mean().item() - 0.3) < 0.02 + + # Back-compat: omitting max_prob defaults to 1.0. + spikes = next(bernoulli_loader(data, time=1)) + assert spikes.float().mean().item() == 1.0 + def test_poisson(self): for n in [1, 100]: # number of nodes in layer for t in [1000]: # number of timesteps From 53fcdcb1ff32817af9b575e6b4edf22eb19c8b97 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:11:58 -0400 Subject: [PATCH 03/15] NeuroEval WO-04/05: add CITATION.cff and fix README version/install consistency Addresses two reproducibility/transparency gaps flagged in #765: - WO-05: README "Requirements" said Python >=3.9,<3.12 while pyproject.toml and CI target >=3.11,<3.14 (tested 3.11/3.12/3.13). Align the README and add a "Reproducible install" note pointing at poetry.lock and Dockerfile. - WO-04: add machine-readable CITATION.cff (CFF 1.2.0) with preferred-citation to the Frontiers paper (DOI 10.3389/fninf.2018.00089). Software Zenodo DOI left as a TODO pending WO-03. Refs #765 Co-Authored-By: Claude Opus 4.8 --- CITATION.cff | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 12 +++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..f7799a879 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,55 @@ +cff-version: 1.2.0 +message: "If you use BindsNET, please cite the article below." +title: "BindsNET: A Machine Learning-Oriented Spiking Neural Networks Library in Python" +type: software +version: 0.3.3 +license: AGPL-3.0-only +repository-code: "https://github.com/BindsNET/bindsnet" +url: "https://bindsnet-docs.readthedocs.io/" +keywords: + - spiking + - neural + - networks + - pytorch +authors: + - family-names: Hazan + given-names: Hananel + - family-names: Saunders + given-names: Daniel J. + - family-names: Khan + given-names: Hassaan + - family-names: Patel + given-names: Devdhar + - family-names: Sanghavi + given-names: Darpan T. + - family-names: Siegelmann + given-names: Hava T. + - family-names: Kozma + given-names: Robert +# identifiers: # TODO(WO-03): add once the software DOI is minted on Zenodo +# - type: doi +# value: 10.5281/zenodo.XXXXXXX +# description: Archived software release +preferred-citation: + type: article + title: "BindsNET: A Machine Learning-Oriented Spiking Neural Networks Library in Python" + doi: 10.3389/fninf.2018.00089 + url: "https://www.frontiersin.org/article/10.3389/fninf.2018.00089" + journal: "Frontiers in Neuroinformatics" + volume: 12 + year: 2018 + authors: + - family-names: Hazan + given-names: Hananel + - family-names: Saunders + given-names: Daniel J. + - family-names: Khan + given-names: Hassaan + - family-names: Patel + given-names: Devdhar + - family-names: Sanghavi + given-names: Darpan T. + - family-names: Siegelmann + given-names: Hava T. + - family-names: Kozma + given-names: Robert diff --git a/README.md b/README.md index b04396689..62d4ea464 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,20 @@ Check out the [BindsNET examples](https://github.com/BindsNET/bindsnet/tree/mast ## Requirements -- Python >=3.9,<3.12 +- Python >=3.11,<3.14 (continuously tested on 3.11, 3.12, and 3.13) ## Setting things up +### Reproducible install +For a byte-for-byte reproducible environment, install the pinned dependency set from +the committed `poetry.lock`: + +``` +poetry install +``` + +Alternatively, the provided `Dockerfile` builds the full pinned stack (see *Using Docker* below). + ## Using Pip To install the most recent stable release from the GitHub repository From 227297eb752fa42050cdaae7f91663f41e43d251 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:14:16 -0400 Subject: [PATCH 04/15] NeuroEval WO-01: add central data & stimulus declaration (DATA.md) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declares every dataset and synthetic stimulus used by the examples, benchmarks, and dataset loaders — source, retrieval method, license pointer, and spike-encoding preprocessing. Targets the Data & Stimulus Disclosure and Dataset Resolvability axes flagged in #765. Linked from README and the docs index. Refs #765 Co-Authored-By: Claude Opus 4.8 --- DATA.md | 82 +++++++++++++++++++++++++++++++++++++++++++ README.md | 7 ++++ docs/source/index.rst | 2 ++ 3 files changed, 91 insertions(+) create mode 100644 DATA.md diff --git a/DATA.md b/DATA.md new file mode 100644 index 000000000..4408609a9 --- /dev/null +++ b/DATA.md @@ -0,0 +1,82 @@ +# Datasets & Stimuli used in BindsNET + +BindsNET ships **no** third-party datasets. Its dataset loaders fetch data from the +upstream sources declared below; all licenses are the upstream providers' and BindsNET +does not redistribute the data. This file declares every dataset and synthetic stimulus +referenced by the shipped examples and benchmarks, plus the additional dataset loaders +the library provides. + +> Licenses below are pointers to the upstream source, not assertions by BindsNET. +> Confirm the current license at the source before using a dataset in your own work. + +--- + +## 1. Datasets used by the shipped examples + +### MNIST +- **Loader:** `from bindsnet.datasets import MNIST` — a thin wrapper over + `torchvision.datasets.MNIST` (`bindsnet/datasets/torchvision_wrapper.py`). +- **Upstream source:** torchvision → http://yann.lecun.com/exdb/mnist/ +- **Version/snapshot:** whatever the installed `torchvision` resolves (mirror-hosted). +- **Obtained by:** automatic download on first run (`download=True` in the examples). +- **License:** as published by the upstream/torchvision mirror (verify upstream). +- **Used in:** `examples/mnist/*.py` + (e.g. `eth_mnist.py`, `batch_eth_mnist.py`, `supervised_mnist.py`, `conv_mnist.py`, + `reservoir.py`, `MCC_reservoir.py`, `conv1d_MNIST.py`, `conv3d_MNIST.py`, + `loc1d_mnist.py`, `loc2d_mnist.py`, `loc3d_mnist.py`, `SOM_LM-SNNs.py`). +- **Preprocessing → spikes:** `transforms.ToTensor()` then scaling by `--intensity` + (default 128 in `eth_mnist.py`), then rate coding via + `bindsnet.encoding.PoissonEncoder(time, dt)` — pixel intensities become Poisson + spike trains over `time` ms at step `dt`. + +### Atari — Breakout (and Space Invaders) +- **Loader:** `bindsnet.environment.GymEnvironment("BreakoutDeterministic-v4")` + (see `examples/breakout/*.py`). +- **Upstream source:** Arcade Learning Environment via `gymnasium[atari]` + `ale-py` + (declared in `pyproject.toml`). ROMs are provided through the ALE/AutoROM tooling. +- **Obtained by:** the Gymnasium/ALE runtime; not stored in this repo. +- **License:** ALE/ROM licensing applies (verify via ale-py / AutoROM). +- **Used in:** `examples/breakout/breakout.py`, `breakout_stdp.py`, + `play_breakout_from_ANN.py`, `random_baseline.py`, `random_network_baseline.py`. +- **Preprocessing → spikes:** Atari observations are converted to network input by the + example pipelines (see each script and `bindsnet/encoding/`). + +--- + +## 2. Synthetic stimuli (no external dataset) + +### Scaling-benchmark Poisson drive +Used by `examples/benchmark/benchmark.py` and reported in the README "Benchmarking" +section and Hazan et al. 2018: +- Population of **n** Poisson input neurons, firing rates drawn from **U(0, 100) Hz**. +- Connected all-to-all to an equally sized population of LIF neurons; connection + weights sampled from **N(0, 1)**. +- **n** varied 250 → 10,000 in steps of 250; each run simulated **1,000 ms** at + **dt = 1.0 ms**. + +This stimulus is generated programmatically; there is no dataset to download. + +--- + +## 3. Additional dataset loaders provided by the library + +These loaders are part of `bindsnet.datasets` and are available to users, though not +every one is exercised by a shipped example. Sources are taken directly from the loader +modules. + +| Dataset | Loader | Upstream source | Notes | +|---------|--------|-----------------|-------| +| Spoken MNIST (Free Spoken Digit Dataset) | `bindsnet.datasets.SpokenMNIST` (`spoken_mnist.py`) | https://github.com/Jakobovski/free-spoken-digit-dataset (downloads `master.zip`) | License per upstream repo | +| ALOV300++ | `bindsnet.datasets.ALOV300` (`alov300.py`) | frames `http://isis-data.science.uva.nl/alov/alov300++_frames.zip`, GT text `http://isis-data.science.uva.nl/alov/alov300++GT_txtFiles.zip`; info `http://alov300pp.joomlafree.it/dataset-resources.html` | Visual-tracking dataset | +| DAVIS 2017 | `bindsnet.datasets.Davis` (`davis.py`) | https://davischallenge.org/davis2017/code.html | Video object segmentation | +| Other torchvision datasets | `create_torchvision_dataset_wrapper(...)` (`torchvision_wrapper.py`) | torchvision | Wrappers exported for CIFAR10/100, FashionMNIST, EMNIST, KMNIST, SVHN, STL10, Omniglot, VOC*, COCO*, etc. — each downloads from its torchvision-declared source | + +--- + +## Data handling notes +- Datasets download to a user-specified `root` directory (the examples typically use a + local `data/` path); they are **not** committed to this repository. +- BindsNET does not modify or redistribute upstream data; it applies encodings + (`bindsnet/encoding/`) to turn inputs into spike trains at simulation time. +- If a download URL has moved, consult the loader module in `bindsnet/datasets/` and the + upstream project page listed above. diff --git a/README.md b/README.md index 62d4ea464..a8968550b 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,13 @@ python -m pytest test/ Some tests will fail if Open AI `gym` is not installed on your machine. +## Datasets + +BindsNET ships no third-party datasets; its loaders fetch them from upstream sources. +Every dataset and synthetic stimulus used by the examples, benchmarks, and dataset +loaders — with source, retrieval method, license pointer, and spike-encoding +preprocessing — is declared in [DATA.md](DATA.md). + ## Background The simulation of biologically plausible spiking neuron dynamics can be challenging. It is typically done by solving ordinary differential equations (ODEs) which describe said dynamics. PyTorch does not explicitly support the solution of differential equations (as opposed to [`brian2`](https://github.com/brian-team/brian2), for example), but we can convert the ODEs defining the dynamics into difference equations and solve them at regular, short intervals (a `dt` on the order of 1 millisecond) as an approximation. Of course, under the hood, packages like `brian2` are doing the same thing. Doing this in [`PyTorch`](http://pytorch.org/) is exciting for a few reasons: diff --git a/docs/source/index.rst b/docs/source/index.rst index 774175183..05803cc84 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,6 +22,8 @@ Neurons are connected together with directed edges (*synapses*) which are (in ge At its core, BindsNET provides software objects and methods which support the simulation of groups of different types of neurons (**bindsnet.network.nodes**), as well as different types of connections between them (**bindsnet.network.topology**). These may be arbitrarily combined together under a single **bindsnet.network.Network** object, which is responsible for the coordination of the simulation logic of all underlying components. On creation of a network, the user can specify a simulation timestep constant, :math:`dt`, which determines the granularity of the simulation. Choosing this parameter induces a trade-off between simulation speed and numerical precision: large values result in fast simulation, but poor simulation accuracy, and vice versa. Monitors (**bindsnet.network.monitors**) are available for recording state variables from arbitrary network components (e.g., the voltage :math:`v` of a group of neurons). +A full declaration of the datasets and synthetic stimuli used by the examples and benchmarks — with sources, retrieval methods, license pointers, and spike-encoding preprocessing — is maintained in `DATA.md `_. + The development of BindsNET is supported by the Defense Advanced Research Project Agency Grant DARPA/MTO HR0011-16-l-0006. .. toctree:: From 9bd20e495a7a75a5f5ae4d5d6219727c1fa59e24 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:16:03 -0400 Subject: [PATCH 05/15] NeuroEval WO-02: add reproducibility mapping (REPRODUCING.md) Maps each shipped model / published claim to model class, example script, exact command, seed, and expected output, with determinism notes. Verified against the source; accuracy/timing cells are honestly marked as not-yet-measured rather than asserted. Documents that the multi-simulator benchmark script is not a single-command repro. Targets the Model-to-Code Traceability axis flagged in #765. Linked from README and docs index. Refs #765 Co-Authored-By: Claude Opus 4.8 --- README.md | 7 +++++++ REPRODUCING.md | 46 +++++++++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 2 +- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 REPRODUCING.md diff --git a/README.md b/README.md index a8968550b..b8070a2a2 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,13 @@ Every dataset and synthetic stimulus used by the examples, benchmarks, and datas loaders — with source, retrieval method, license pointer, and spike-encoding preprocessing — is declared in [DATA.md](DATA.md). +## Reproducing results + +[REPRODUCING.md](REPRODUCING.md) maps each shipped model and published claim to its +model class, example script, exact command, seed, and expected output (e.g. the +Diehl & Cook 2015 MNIST replication via `examples/mnist/eth_mnist.py`, and the +Hazan et al. 2018 scaling benchmark). + ## Background The simulation of biologically plausible spiking neuron dynamics can be challenging. It is typically done by solving ordinary differential equations (ODEs) which describe said dynamics. PyTorch does not explicitly support the solution of differential equations (as opposed to [`brian2`](https://github.com/brian-team/brian2), for example), but we can convert the ODEs defining the dynamics into difference equations and solve them at regular, short intervals (a `dt` on the order of 1 millisecond) as an approximation. Of course, under the hood, packages like `brian2` are doing the same thing. Doing this in [`PyTorch`](http://pytorch.org/) is exciting for a few reasons: diff --git a/REPRODUCING.md b/REPRODUCING.md new file mode 100644 index 000000000..cc951fa21 --- /dev/null +++ b/REPRODUCING.md @@ -0,0 +1,46 @@ +# Reproducing results with BindsNET + +This table traces each model BindsNET describes or ships back to executable code: the +model class, the example script, an exact command, the seed, the expected output, and +the data it needs (declared in [DATA.md](DATA.md)). + +> **Honesty note.** Commands, defaults, seeds, and model classes below are verified +> against the source. The **Expected output** cells describe *what the script reports* +> and the qualitative trend; cells marked *(not measured here)* have **not** been run to +> a final metric in producing this table — run the command to obtain the number for your +> hardware. No accuracy/timing figure is asserted that was not measured. + +## Model → code → command map + +| Claim / source | Model class | Script | Command (defaults shown) | Seed | Expected output | Data | +|----------------|-------------|--------|--------------------------|------|-----------------|------| +| Diehl & Cook 2015 MNIST replication (DOI `10.3389/fncom.2015.00099`) | `DiehlAndCook2015` | `examples/mnist/eth_mnist.py` | `python examples/mnist/eth_mnist.py --n_neurons 100 --n_epochs 1 --time 250 --seed 0` | `--seed 0` (`torch.manual_seed`) | Prints test accuracy at end; accuracy rises with `--n_neurons` (Diehl & Cook report up to ~95% at 6400 neurons). *(not measured here)* | MNIST | +| Batched ETH MNIST | `DiehlAndCook2015` | `examples/mnist/batch_eth_mnist.py` | `python examples/mnist/batch_eth_mnist.py --n_neurons 100 --batch_size 32 --time 100 --seed 0` | `--seed 0` | Prints test accuracy; faster per-epoch via batching. *(not measured here)* | MNIST | +| Supervised MNIST (label-clamped) | `DiehlAndCook2015` | `examples/mnist/supervised_mnist.py` | `python examples/mnist/supervised_mnist.py --n_neurons 100 --time 250 --intensity 32 --seed 0` | `--seed 0` | Prints test accuracy. *(not measured here)* | MNIST | +| Convolutional SNN on MNIST | (in-script conv network) | `examples/mnist/conv_mnist.py` | `python examples/mnist/conv_mnist.py --time 50 --batch_size 1 --seed 0` | `--seed 0` | Prints accuracy. *(not measured here)* | MNIST | +| Reservoir / liquid-state MNIST | (in-script reservoir) | `examples/mnist/reservoir.py` | `python examples/mnist/reservoir.py --n_neurons 500 --n_epochs 100 --time 250 --seed 0` | `--seed 0` | Prints accuracy after readout training. *(not measured here)* | MNIST | +| Scaling benchmark (Hazan et al. 2018, DOI `10.3389/fninf.2018.00089`) | `Input` + `LIFNodes` via `Connection` | `examples/benchmark/benchmark.py` | **Not single-command** — see note below | n/a (timing study) | Runtime-vs-`n` curve; published figure is `docs/BindsNET benchmark.png` | synthetic Poisson drive (DATA.md) | +| Atari Breakout (ANN→SNN demo) | trained ANN + SNN pipeline | `examples/breakout/play_breakout_from_ANN.py` | `python examples/breakout/play_breakout_from_ANN.py` | set in script | Plays Breakout from the shipped `trained_shallow_ANN.pt` | Atari Breakout (DATA.md) | + +## Notes + +### Determinism +- Each MNIST example accepts `--seed` (default `0`) and calls `torch.manual_seed(seed)` + and `torch.cuda.manual_seed_all(seed)`. Pass the same `--seed` to repeat a run. +- Residual nondeterminism can come from CUDA atomic operations and first-run dataset + download ordering. For stricter determinism run on CPU and, where feasible, set + `torch.use_deterministic_algorithms(True)`. + +### Scaling benchmark is a multi-simulator study +`examples/benchmark/benchmark.py` compares BindsNET against **BRIAN2, PyNEST, ANNarchy, +BRIAN2genn, and Nengo**, and imports those packages plus an `experiments` helper module. +It is therefore **not** a single-command reproduction: it requires those external +simulators installed and the benchmark harness. The published BindsNET result is the +figure `docs/BindsNET benchmark.png` and the parameters in the README "Benchmarking" +section (Poisson inputs U(0,100) Hz, weights N(0,1), dt = 1.0 ms, 1000 ms/run, n from +250 to 10,000). A BindsNET-only timing reproduction (no external simulators) can be +built from `Input` + `LIFNodes` + `Connection`. + +### Data +All datasets and synthetic stimuli these scripts use are declared in +[DATA.md](DATA.md), including how they are downloaded and the spike encoding applied. diff --git a/docs/source/index.rst b/docs/source/index.rst index 05803cc84..4bac44ed0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,7 +22,7 @@ Neurons are connected together with directed edges (*synapses*) which are (in ge At its core, BindsNET provides software objects and methods which support the simulation of groups of different types of neurons (**bindsnet.network.nodes**), as well as different types of connections between them (**bindsnet.network.topology**). These may be arbitrarily combined together under a single **bindsnet.network.Network** object, which is responsible for the coordination of the simulation logic of all underlying components. On creation of a network, the user can specify a simulation timestep constant, :math:`dt`, which determines the granularity of the simulation. Choosing this parameter induces a trade-off between simulation speed and numerical precision: large values result in fast simulation, but poor simulation accuracy, and vice versa. Monitors (**bindsnet.network.monitors**) are available for recording state variables from arbitrary network components (e.g., the voltage :math:`v` of a group of neurons). -A full declaration of the datasets and synthetic stimuli used by the examples and benchmarks — with sources, retrieval methods, license pointers, and spike-encoding preprocessing — is maintained in `DATA.md `_. +A full declaration of the datasets and synthetic stimuli used by the examples and benchmarks — with sources, retrieval methods, license pointers, and spike-encoding preprocessing — is maintained in `DATA.md `_. Commands to reproduce shipped models and published claims are tabulated in `REPRODUCING.md `_. The development of BindsNET is supported by the Defense Advanced Research Project Agency Grant DARPA/MTO HR0011-16-l-0006. From c138ce9c60bec88307388cdf9a1a2ead801e1c85 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:23:44 -0400 Subject: [PATCH 06/15] NeuroEval WO-06: add neural model-spec docs page New docs/source/models_spec.rst documenting the difference equations and default parameters for every neuron model (IF/LIF/CurrentLIF/AdaptiveLIF/DiehlAndCook/ Izhikevich/SRM0/CSRM/McCullochPitts) and the PostPre learning rule, transcribed from source; remaining learning rules summarized with source pointers. Added to the docs toctree. Targets the Neural Model Spec Clarity axis flagged in #765. Note: docs build not run locally (sphinx/docutils unavailable here); RST uses only standard directives. Verify via the Read the Docs build. Refs #765 Co-Authored-By: Claude Opus 4.8 --- docs/source/index.rst | 1 + docs/source/models_spec.rst | 208 ++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 docs/source/models_spec.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 4bac44ed0..91c9faa55 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -33,6 +33,7 @@ The development of BindsNET is supported by the Defense Advanced Research Projec installation quickstart guide + models_spec .. toctree:: :maxdepth: 2 diff --git a/docs/source/models_spec.rst b/docs/source/models_spec.rst new file mode 100644 index 000000000..8f2183abc --- /dev/null +++ b/docs/source/models_spec.rst @@ -0,0 +1,208 @@ +Neural model specifications +=========================== + +This page gives the **mathematical specification** of the neuron, and learning-rule +models BindsNET implements: the difference equations actually solved each timestep and +the default parameters with units. Equations and defaults below were transcribed from +the source (``bindsnet/network/nodes.py`` and ``bindsnet/learning/learning.py``); when +in doubt, the source is authoritative. Parameter defaults are stated as of package +version 0.3.3. + +Discretization +-------------- + +BindsNET does not integrate ODEs symbolically. Continuous-time dynamics are converted to +**difference equations** and advanced at a fixed timestep :math:`dt` (milliseconds; the +examples use :math:`dt = 1.0`). A network-wide :math:`dt` is set on the +``Network`` object. Exponential leak terms are precomputed once per :math:`dt` as a +decay factor + +.. math:: + + \text{decay} = \exp\!\left(-\,dt / \tau\right), + +so a leaky variable :math:`y` relaxing toward a baseline :math:`y_0` updates as +:math:`y \leftarrow \text{decay}\,(y - y_0) + y_0`. + +Notation: :math:`v` membrane voltage, :math:`v_\text{rest}` rest, :math:`v_\text{reset}` +post-spike reset, :math:`v_\text{thr}` threshold, :math:`s` spike (boolean), +:math:`x` spike trace, :math:`\tau` a time constant. All voltages are in millivolts and +follow the biological convention used in the code (e.g. rest :math:`-65`\ mV). + +Neuron models +------------- + +All neuron layers live in ``bindsnet.network.nodes``. Spikes are emitted when the +(possibly adapted) threshold is crossed; most models then apply a reset and a refractory +period ``refrac`` during which inputs are ignored. Optional spike **traces** decay with +time constant ``tc_trace`` (default 20 ms) and are used by the trace-based learning +rules. + +McCulloch–Pitts (``McCullochPitts``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Stateless threshold unit; the voltage equals the input and a spike is emitted when it +reaches threshold. + +.. math:: + + v_t = x_t, \qquad s_t = \big[\,v_t \ge v_\text{thr}\,\big] + +Defaults: ``thresh`` :math:`= 1.0`. + +Integrate-and-fire (``IFNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Non-leaky accumulator with reset and refractory period. + +.. math:: + + v_t = v_{t-1} + \mathbb{1}[\text{refrac}\le 0]\,x_t, \qquad + s_t = [\,v_t \ge v_\text{thr}\,], \qquad v_t \leftarrow v_\text{reset}\ \text{if}\ s_t + +Defaults: ``thresh`` :math:`=-52`, ``reset`` :math:`=-65`, ``refrac`` :math:`=5`. + +Leaky integrate-and-fire (``LIFNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Leak toward rest, integrate input, threshold-reset-refractory. + +.. math:: + + v_t = \text{decay}\,(v_{t-1} - v_\text{rest}) + v_\text{rest} + x_t, + \qquad \text{decay} = \exp(-dt/\tau_\text{decay}) + +.. math:: + + s_t = [\,v_t \ge v_\text{thr}\,], \qquad v_t \leftarrow v_\text{reset}\ \text{if}\ s_t + +Defaults: ``thresh`` :math:`=-52`, ``rest`` :math:`=-65`, ``reset`` :math:`=-65`, +``refrac`` :math:`=5`, ``tc_decay`` :math:`=100`\ ms. Inputs are masked to zero while a +neuron is refractory. + +``BoostedLIFNodes`` is a performance-oriented LIF variant (per source: no separate +rest/reset/lower-bound handling); use it when those features are not needed. + +Current-based LIF (``CurrentLIFNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adds a decaying synaptic **current** :math:`i` between input and membrane: + +.. math:: + + i_t = i_\text{decay}\,i_{t-1} + x_t, \qquad + v_t = \text{decay}\,(v_{t-1} - v_\text{rest}) + v_\text{rest} + + \mathbb{1}[\text{refrac}\le 0]\,i_t + +with :math:`i_\text{decay} = \exp(-dt/\tau_{i})`. See source for the ``tc_i_decay`` +default and the remaining (LIF-shared) parameters. + +Adaptive-threshold LIF (``AdaptiveLIFNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LIF with a threshold adaptation variable :math:`\theta` that increases on each spike and +decays otherwise: + +.. math:: + + \theta_t = \theta_\text{decay}\,\theta_{t-1} + \theta_+ \textstyle\sum s_t, + \qquad s_t = [\,v_t \ge v_\text{thr} + \theta_t\,] + +Defaults add ``theta_plus`` :math:`=0.05`, ``tc_theta_decay`` :math:`=10^{7}`\ ms (on top +of the LIF defaults). Adaptation is applied while ``learning`` is enabled. + +Diehl & Cook 2015 (``DiehlAndCookNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adaptive-threshold LIF tuned for the Diehl & Cook (2015) MNIST replication, with the +additional ``one_spike`` option (default ``True``) that permits at most one spike per +layer per timestep. Same parameter defaults as ``AdaptiveLIFNodes``. Used by the +``DiehlAndCook2015`` model and ``examples/mnist/eth_mnist.py``. + +Izhikevich (``IzhikevichNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Two-variable model (membrane :math:`v`, recovery :math:`u`) integrated with two half +Euler steps per timestep: + +.. math:: + + v \leftarrow v + \tfrac{dt}{2}\,(0.04 v^2 + 5 v + 140 - u + x)\quad(\text{applied twice}), + \qquad u \leftarrow u + dt\,a\,(b v - u) + +On spike (:math:`v \ge v_\text{thr}`): :math:`v \leftarrow c`, :math:`u \leftarrow u + d`. +Excitatory/inhibitory populations are parameterized as in Izhikevich (2003): excitatory +:math:`a=0.02,\,b=0.2,\,c=-65+15r^2,\,d=8-6r^2`; inhibitory +:math:`a=0.02+0.08r,\,b=0.25-0.05r,\,c=-65,\,d=2`, with :math:`r\sim U(0,1)`. + +SRM0 (``SRM0Nodes``) +~~~~~~~~~~~~~~~~~~~~~ +Simplified Spike Response Model with **stochastic** ("escape noise") firing: + +.. math:: + + v_t = \text{decay}\,(v_{t-1}-v_\text{rest}) + v_\text{rest} + + \mathbb{1}[\text{refrac}\le 0]\,\varepsilon_0\,x_t + +.. math:: + + \rho = \rho_0 \exp\!\Big(\tfrac{v_t - v_\text{thr}}{\Delta_\text{thr}}\Big), + \qquad P(\text{spike}) = 1 - e^{-\rho\,dt}, + \qquad s_t = [\,U(0,1) < P(\text{spike})\,] + +Cumulative SRM (``CSRMNodes``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Cumulative Spike Response Model (Gerstner & van Hemmen 1992; Gerstner et al. 1996): +refractoriness and adaptation arise from the summed after-potentials of several previous +spikes rather than only the most recent one. See the source for the response-kernel +implementation. + +Input (``Input``) +~~~~~~~~~~~~~~~~~ +Passes externally provided spike tensors (e.g. from ``bindsnet.encoding``) into the +network; it has no internal membrane dynamics. + +Learning rules +-------------- + +Learning rules live in ``bindsnet.learning``. They modify connection weights ``w`` from +pre-synaptic spikes/traces (``source``) and post-synaptic spikes/traces (``target``). +``nu`` is the (pre, post) learning-rate pair; ``reduction`` aggregates over the batch; +``weight_decay`` optionally decays weights each step. + +Post-pre STDP (``PostPre``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Trace-based spike-timing-dependent plasticity (requires traces on both layers). For a +dense ``Connection`` the per-step update is + +.. math:: + + \Delta w = -\,\nu_\text{pre}\;(s_\text{pre} \otimes x_\text{post}) + \;+\; \nu_\text{post}\;(x_\text{pre} \otimes s_\text{post}) + +i.e. a pre-synaptic spike **depresses** the synapse in proportion to the post-synaptic +trace, and a post-synaptic spike **potentiates** it in proportion to the pre-synaptic +trace. Convolutional and locally-connected variants apply the same rule patch-wise. + +Hebbian (``Hebbian``) +~~~~~~~~~~~~~~~~~~~~~~ +Both pre- and post-synaptic events **increase** the weight (no depression term), +proportional to the opposite layer's trace. + +Weight-dependent post-pre (``WeightDependentPostPre``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``PostPre`` whose potentiation/depression magnitudes are scaled by the distance of the +weight from its bounds (``wmin``/``wmax``), yielding soft saturation at the limits. + +Reward-modulated STDP (``MSTDP``, ``MSTDPET``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Three-factor rules: a STDP-like eligibility signal is gated by a scalar **reward**. +``MSTDP`` modulates the immediate pre/post correlation by reward; ``MSTDPET`` adds an +**eligibility trace** that accumulates the correlation over time (time constant +``tc_e_trace``) before reward gating. Reward is supplied via the pipeline / an +``AbstractReward`` (e.g. ``MovingAvgRPE``). See source for the exact eligibility update. + +Rmax (``Rmax``) +~~~~~~~~~~~~~~~ +Reward-maximizing rule intended for stochastic (SRM0) neurons; see source for its +formulation. + +.. note:: + + Where this page summarizes a rule "see source", the equations were not reproduced here + to avoid mis-stating constants; consult ``bindsnet/learning/learning.py`` for the + authoritative form. If an implementation deviates from a textbook model, the code is + the specification. From b0f4417aadfcbf04be013337c826b5ebcd9038c6 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:23:45 -0400 Subject: [PATCH 07/15] NeuroEval WO-08: document provenance of trained_shallow_ANN.pt Add examples/breakout/README.md describing the shipped pretrained artifact (Linear(6400,1000)->ReLU->Linear(1000,4) Breakout Q-network), how it is consumed by play_breakout_from_ANN.py (ANN->SNN transplant), and that no training script ships. Cross-linked from DATA.md. Targets the Resolvability/Traceability axes flagged in #765. Refs #765 Co-Authored-By: Claude Opus 4.8 --- DATA.md | 3 +++ examples/breakout/README.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 examples/breakout/README.md diff --git a/DATA.md b/DATA.md index 4408609a9..2a0e5b307 100644 --- a/DATA.md +++ b/DATA.md @@ -40,6 +40,9 @@ the library provides. `play_breakout_from_ANN.py`, `random_baseline.py`, `random_network_baseline.py`. - **Preprocessing → spikes:** Atari observations are converted to network input by the example pipelines (see each script and `bindsnet/encoding/`). +- **Pretrained artifact:** `examples/breakout/trained_shallow_ANN.pt` (a Breakout + Q-network transplanted into an SNN) — provenance in + [examples/breakout/README.md](examples/breakout/README.md). --- diff --git a/examples/breakout/README.md b/examples/breakout/README.md new file mode 100644 index 000000000..17dac5a7d --- /dev/null +++ b/examples/breakout/README.md @@ -0,0 +1,29 @@ +# Breakout examples + +Scripts demonstrating reinforcement-learning-style use of BindsNET on the Atari +**Breakout** environment (`BreakoutDeterministic-v4`, via `gymnasium[atari]` + `ale-py`; +see [../../DATA.md](../../DATA.md)). + +- `breakout.py`, `breakout_stdp.py` — run an SNN on Breakout (with/without STDP). +- `random_baseline.py`, `random_network_baseline.py` — random-action / random-network baselines. +- `play_breakout_from_ANN.py` — convert a pretrained ANN into an SNN and play (see below). + +## Pretrained artifact: `trained_shallow_ANN.pt` + +| Property | Value | +|----------|-------| +| File | `trained_shallow_ANN.pt` (~25 MB, tracked in git) | +| What it is | A pretrained **shallow ANN** (Q-network) for Atari Breakout | +| Architecture | `nn.Linear(6400, 1000)` → `ReLU` → `nn.Linear(1000, 4)` (class `Net` in `play_breakout_from_ANN.py`) | +| Input | 6400 features = a flattened 80×80 preprocessed Breakout frame | +| Output | 4 units = the Breakout discrete action space | +| Consumed by | `play_breakout_from_ANN.py:55` (`torch.load("trained_shallow_ANN.pt")`) | +| How it is used | Its `fc1`/`fc2` weights are transposed, scaled (`layer1scale=57.68`, `layer2scale=77.48`), and transplanted into a spiking network `Input(6400) → LIFNodes(1000) → LIFNodes(4)`, which is then run on Breakout through an `EnvironmentPipeline` with Poisson encoding — an ANN→SNN conversion demo. | + +### Regeneration + +**The training script that produced `trained_shallow_ANN.pt` is not included in this +repository.** The file is shipped as a pretrained weight blob. To regenerate it you would +need to train a network with the `Net` architecture above (input 6400, hidden 1000, +output 4) as a Breakout Q-network and save it with `torch.save`. If you reproduce or +replace this artifact, please document the training data, hyperparameters, and seed here. From 1b4c1e4fdb7d70fb5626888cf77764a2df155c93 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:23:45 -0400 Subject: [PATCH 08/15] NeuroEval WO-09: add CHANGELOG.md Keep a Changelog format with an Unreleased section sourced from git log since the 0.3.3 tag (2024-10-18) and a pointer from CONTRIBUTING.md. Older history links to the GitHub releases page rather than being reconstructed. Targets the Reproducibility Package Quality axis flagged in #765. Refs #765 Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 3 +++ 2 files changed, 40 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..660e74f7c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +All notable changes to BindsNET are documented here. The format is based on +[Keep a Changelog](https://keepachangelog.com/). For releases prior to the entries below, +see the [GitHub releases / tags](https://github.com/BindsNET/bindsnet/releases). + +## [Unreleased] + +### Added +- Sparse-tensor support for additional learning rules (plus a batch dimension and docs + for `sparse=True`). +- Validation tests for the reward-modulated learning rules `MSTDP` and `MSTDPET`. +- Regression test for a preallocated `Monitor` short-run bug (PR #761). +- Read the Docs configuration for documentation builds. +- Reproducibility/transparency docs: `DATA.md` (dataset & stimulus declaration), + `REPRODUCING.md` (model→script→command→seed map), `CITATION.cff`, and a + `docs/source/models_spec.rst` neural-model specification page. + +### Changed +- `assign_labels` / evaluation: handle abstention for inactive samples, mark + never-firing neurons with `-1`, and accuracy/performance improvements. +- CI: dropped Python 3.10 (project requires `>=3.11`); upgraded GitHub Actions; test on + Python 3.11/3.12/3.13. README Python requirement aligned to `>=3.11,<3.14`. +- Routine dependency updates via Poetry. + +### Fixed +- `bernoulli_loader` now honors the `max_prob` kwarg (PR #743). +- Bug with preallocated buffers and `torch.cat`. +- `torch.save` compatibility for PyTorch 2.6.0. +- Python 3.13 support / tests. +- `eth_mnist` example. + +## [0.3.3] - 2024-10-18 + +Baseline for this changelog. See the +[releases page](https://github.com/BindsNET/bindsnet/releases) for the history of +0.1.x–0.3.3. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa2270289..1ca0f8aef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,9 @@ Run the tests, they all should pass poetry run pytest ``` +Notable changes are recorded in [`CHANGELOG.md`](CHANGELOG.md); please add an entry to the +`Unreleased` section in your pull request. + All development should take place on a branch separate from master. To create a branch, issue ```shell From 8734369fc24c3a776b9a9d55bd3cdf94c8720a52 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:32:13 -0400 Subject: [PATCH 09/15] docs: add Zenodo DOI badge to README Uses the concept DOI (10.5281/zenodo.20695115), which always resolves to the latest release on Zenodo. Co-Authored-By: Claude Opus 4.8 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8070a2a2..eda9b4cf8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Check out the [BindsNET examples](https://github.com/BindsNET/bindsnet/tree/mast [![CodeQL](https://github.com/BindsNET/bindsnet/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/BindsNET/bindsnet/actions/workflows/github-code-scanning/codeql) [![Documentation Status](https://readthedocs.org/projects/bindsnet-docs/badge/?version=latest)](https://bindsnet-docs.readthedocs.io/?badge=latest) [![Neuromorphic Computing](https://img.shields.io/badge/Collaboration_Network-Open_Neuromorphic-blue)](https://open-neuromorphic.org/neuromorphic-computing/) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20695115.svg)](https://doi.org/10.5281/zenodo.20695115) ## Requirements From d9c2ddba682d6889ce061553316fbae0eb81ebb5 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:37:05 -0400 Subject: [PATCH 10/15] NeuroEval WO-03: wire Zenodo software DOI into citation metadata Completes the Zenodo software-archive work (release 0.3.4): - CITATION.cff: add concept DOI (10.5281/zenodo.20695115) and version DOI (10.5281/zenodo.20695116); bump version to 0.3.4. - README: add a "Citing the software" block alongside the DOI badge. - pyproject.toml: bump version 0.3.3 -> 0.3.4 to match the released tag. - CHANGELOG: add the released [0.3.4] section with the archival DOIs. Refs #765 Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 22 ++++++++++++++++++---- CITATION.cff | 14 +++++++++----- README.md | 11 +++++++++++ pyproject.toml | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660e74f7c..056b7c4d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,21 +6,35 @@ see the [GitHub releases / tags](https://github.com/BindsNET/bindsnet/releases). ## [Unreleased] +### Added +- Reproducibility/transparency docs: `DATA.md` (dataset & stimulus declaration), + `REPRODUCING.md` (model→script→command→seed map), and a + `docs/source/models_spec.rst` neural-model specification page. +- `CITATION.cff` with the paper citation and the Zenodo software DOI. +- `CHANGELOG.md`. +- `examples/breakout/README.md` documenting the `trained_shallow_ANN.pt` provenance. + +### Changed +- README Python requirement aligned to `>=3.11,<3.14`; added a reproducible-install note. +- `pyproject.toml` version bumped to 0.3.4 to match the released tag. + +## [0.3.4] - 2026-06-15 + +Archived on Zenodo — concept DOI [10.5281/zenodo.20695115](https://doi.org/10.5281/zenodo.20695115), +version DOI [10.5281/zenodo.20695116](https://doi.org/10.5281/zenodo.20695116). + ### Added - Sparse-tensor support for additional learning rules (plus a batch dimension and docs for `sparse=True`). - Validation tests for the reward-modulated learning rules `MSTDP` and `MSTDPET`. - Regression test for a preallocated `Monitor` short-run bug (PR #761). - Read the Docs configuration for documentation builds. -- Reproducibility/transparency docs: `DATA.md` (dataset & stimulus declaration), - `REPRODUCING.md` (model→script→command→seed map), `CITATION.cff`, and a - `docs/source/models_spec.rst` neural-model specification page. ### Changed - `assign_labels` / evaluation: handle abstention for inactive samples, mark never-firing neurons with `-1`, and accuracy/performance improvements. - CI: dropped Python 3.10 (project requires `>=3.11`); upgraded GitHub Actions; test on - Python 3.11/3.12/3.13. README Python requirement aligned to `>=3.11,<3.14`. + Python 3.11/3.12/3.13. - Routine dependency updates via Poetry. ### Fixed diff --git a/CITATION.cff b/CITATION.cff index f7799a879..e54f7d9d3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,8 +2,9 @@ cff-version: 1.2.0 message: "If you use BindsNET, please cite the article below." title: "BindsNET: A Machine Learning-Oriented Spiking Neural Networks Library in Python" type: software -version: 0.3.3 +version: 0.3.4 license: AGPL-3.0-only +doi: 10.5281/zenodo.20695115 repository-code: "https://github.com/BindsNET/bindsnet" url: "https://bindsnet-docs.readthedocs.io/" keywords: @@ -26,10 +27,13 @@ authors: given-names: Hava T. - family-names: Kozma given-names: Robert -# identifiers: # TODO(WO-03): add once the software DOI is minted on Zenodo -# - type: doi -# value: 10.5281/zenodo.XXXXXXX -# description: Archived software release +identifiers: + - type: doi + value: 10.5281/zenodo.20695115 + description: Concept DOI (resolves to the latest archived release) + - type: doi + value: 10.5281/zenodo.20695116 + description: Archived software release v0.3.4 preferred-citation: type: article title: "BindsNET: A Machine Learning-Oriented Spiking Neural Networks Library in Python" diff --git a/README.md b/README.md index eda9b4cf8..da2f0cf82 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,17 @@ If you use BindsNET in your research, please cite the following [article](https: ``` +### Citing the software + +To cite a specific version of the BindsNET software, use the archived release on Zenodo. +The concept DOI below always resolves to the latest version: + +> BindsNET contributors. *BindsNET*. Zenodo. https://doi.org/10.5281/zenodo.20695115 + +(For the exact release used, cite its version DOI; e.g. v0.3.4 is +[10.5281/zenodo.20695116](https://doi.org/10.5281/zenodo.20695116).) A machine-readable +citation is provided in [`CITATION.cff`](CITATION.cff). + ## Contributors - Hava Siegelmann - Director of BINDS lab at UMass diff --git a/pyproject.toml b/pyproject.toml index 14202ee32..237d3685b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bindsnet" -version = "0.3.3" +version = "0.3.4" description = "Spiking neural networks for ML in Python" authors = [ "Hananel Hazan ", "Daniel Saunders", "Darpan Sanghavi", "Hassaan Khan" ] license = "AGPL-3.0-only" From e8f54db7c8a91e39176df7ffe5313483fd8470e5 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:37:49 -0400 Subject: [PATCH 11/15] docs: add CI build-status badge to README Adds a build-status (monitor) badge for the "BindsNET build status" workflow alongside the existing CodeQL, docs, and DOI badges. Refs #765 Co-Authored-By: Claude Opus 4.8 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index da2f0cf82..16f225369 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This package is used as part of ongoing research on applying SNNs, machine learn Check out the [BindsNET examples](https://github.com/BindsNET/bindsnet/tree/master/examples) for a collection of experiments, functions for the analysis of results, plots of experiment outcomes, and more. Documentation for the package can be found [here](https://bindsnet-docs.readthedocs.io). +[![Build Status](https://github.com/BindsNET/bindsnet/actions/workflows/python-app.yml/badge.svg?branch=master)](https://github.com/BindsNET/bindsnet/actions/workflows/python-app.yml) [![CodeQL](https://github.com/BindsNET/bindsnet/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/BindsNET/bindsnet/actions/workflows/github-code-scanning/codeql) [![Documentation Status](https://readthedocs.org/projects/bindsnet-docs/badge/?version=latest)](https://bindsnet-docs.readthedocs.io/?badge=latest) [![Neuromorphic Computing](https://img.shields.io/badge/Collaboration_Network-Open_Neuromorphic-blue)](https://open-neuromorphic.org/neuromorphic-computing/) From ea04b90f346a8a599f62481442d4703801ae38b7 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 22:48:11 -0400 Subject: [PATCH 12/15] NeuroEval WO-07 (part 1): add seeded smoke-reproduction test; bump docs version - test/repro/test_smoke_repro.py: deterministic end-to-end CPU run of a tiny network asserting an exact pre-measured output (179 spikes), verified to pass and to be stable across repeated runs. Runs as part of the existing pytest CI. - REPRODUCING.md: reference the automated smoke test. - models_spec.rst: bump stated version to 0.3.4. Refs #765 Co-Authored-By: Claude Opus 4.8 --- REPRODUCING.md | 3 ++ docs/source/models_spec.rst | 2 +- test/repro/test_smoke_repro.py | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 test/repro/test_smoke_repro.py diff --git a/REPRODUCING.md b/REPRODUCING.md index cc951fa21..0d2891358 100644 --- a/REPRODUCING.md +++ b/REPRODUCING.md @@ -30,6 +30,9 @@ the data it needs (declared in [DATA.md](DATA.md)). - Residual nondeterminism can come from CUDA atomic operations and first-run dataset download ordering. For stricter determinism run on CPU and, where feasible, set `torch.use_deterministic_algorithms(True)`. +- An automated, seeded smoke-reproduction test + (`test/repro/test_smoke_repro.py`) runs a tiny network end-to-end on CPU and asserts + an exact pre-measured output, so determinism is checked continuously in CI. ### Scaling benchmark is a multi-simulator study `examples/benchmark/benchmark.py` compares BindsNET against **BRIAN2, PyNEST, ANNarchy, diff --git a/docs/source/models_spec.rst b/docs/source/models_spec.rst index 8f2183abc..a4e64118d 100644 --- a/docs/source/models_spec.rst +++ b/docs/source/models_spec.rst @@ -6,7 +6,7 @@ models BindsNET implements: the difference equations actually solved each timest the default parameters with units. Equations and defaults below were transcribed from the source (``bindsnet/network/nodes.py`` and ``bindsnet/learning/learning.py``); when in doubt, the source is authoritative. Parameter defaults are stated as of package -version 0.3.3. +version 0.3.4. Discretization -------------- diff --git a/test/repro/test_smoke_repro.py b/test/repro/test_smoke_repro.py new file mode 100644 index 000000000..9b0c6a121 --- /dev/null +++ b/test/repro/test_smoke_repro.py @@ -0,0 +1,59 @@ +""" +Seeded smoke-reproduction test (NeuroEval WO-07). + +Builds a tiny, fully deterministic BindsNET network on CPU, drives it with a +seeded Bernoulli spike train, and asserts an exact, pre-measured output. This is a +fast end-to-end reproduction check: with a fixed seed the simulation must produce +the same result on every run and in CI. + +The expected value (179 output spikes) was measured on CPU with the pinned +``torch`` (2.11.x). If a future ``torch`` upgrade changes CPU RNG and this value +shifts, re-measure with the same builder and update ``EXPECTED_SPIKES``. +""" + +import torch + +from bindsnet.network import Network +from bindsnet.network.monitors import Monitor +from bindsnet.network.nodes import Input, LIFNodes +from bindsnet.network.topology import Connection + +SEED = 0 +TIME = 100 +EXPECTED_SPIKES = 179 + + +def _build_and_run(seed: int = SEED, time: int = TIME) -> int: + """Build the fixed network, run it on CPU, return total output spikes.""" + torch.manual_seed(seed) + network = Network(dt=1.0, learning=False) + + inpt = Input(n=100) + out = LIFNodes(n=50) + network.add_layer(inpt, name="X") + network.add_layer(out, name="Y") + + # Static weights (no learning rule) generated from the same seed. + w = 0.3 * torch.rand(100, 50) + network.add_connection(Connection(inpt, out, w=w), source="X", target="Y") + + monitor = Monitor(out, state_vars=["s"], time=time) + network.add_monitor(monitor, name="Y") + + # Seeded input spike train, shape [time, n]. + torch.manual_seed(seed) + inputs = {"X": torch.bernoulli(0.1 * torch.rand(time, inpt.n))} + + network.run(inputs=inputs, time=time) + return int(monitor.get("s").sum().item()) + + +def test_smoke_repro_matches_expected(): + """The seeded run reproduces the pre-measured output spike count.""" + assert _build_and_run() == EXPECTED_SPIKES + + +def test_smoke_repro_is_deterministic(): + """Repeated seeded runs produce identical results.""" + results = {_build_and_run() for _ in range(3)} + assert results == {EXPECTED_SPIKES} From cfb4ee1baf13b9fbaaea9870c85acc5d6af2e25d Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Sun, 14 Jun 2026 23:23:50 -0400 Subject: [PATCH 13/15] fix: move MulticompartmentConnection features to device on .to()/.cuda()/.cpu() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AbstractMulticompartmentConnection stores its features (and their learning-rule state) in a plain `self.pipeline` list of non-Module objects, so their tensors (e.g. Weight.value, learning-rule nu/traces/eligibility) were not relocated by torch.nn.Module._apply. As a result, `network.to('cuda')` left feature tensors on the CPU and `compute`/`update` raised "Expected all tensors to be on the same device" — the GPU path for models built on MulticompartmentConnection (e.g. DiehlAndCook2015 / examples/mnist/eth_mnist.py) crashed. Override `_apply` on AbstractMulticompartmentConnection to also relocate each feature's tensors and its learning rule's tensor state. The weight `value` is moved in place via `.data` so it stays aliased to the learning rule's cached reference (which is updated in place during training). Verified: eth_mnist now runs end-to-end on GPU; full test suite passes on CPU. Co-Authored-By: Claude Opus 4.8 --- bindsnet/network/topology.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/bindsnet/network/topology.py b/bindsnet/network/topology.py index 10df45824..ea3876e04 100644 --- a/bindsnet/network/topology.py +++ b/bindsnet/network/topology.py @@ -261,6 +261,36 @@ def remove_pipeline(self, feature) -> None: self.pipeline.remove(feature) del self.feature_index[feature.name] + def _apply(self, fn): + # language=rst + """ + Relocate pipeline features (and their learning rules) along with the connection + on ``.to()`` / ``.cuda()`` / ``.cpu()``. + + Feature tensors (e.g. ``Weight.value``) and the per-feature learning-rule state + live on non-``Module`` objects in ``self.pipeline``, so they are not moved by + ``torch.nn.Module._apply``. Without this, ``network.to(device)`` leaves them on + their original device and ``compute``/``update`` raise a cpu/cuda device + mismatch. The feature value is moved in place (via ``.data``) so it stays + aliased to the learning rule's cached reference. + """ + super()._apply(fn) + for feature in self.pipeline: + value = getattr(feature, "value", None) + if isinstance(value, torch.Tensor): + value.data = fn(value.data) + self.device = value.device + for attr, val in list(vars(feature).items()): + if attr != "value" and isinstance(val, torch.Tensor): + setattr(feature, attr, fn(val)) + rule = getattr(feature, "learning_rule", None) + if rule is not None: + for attr, val in list(vars(rule).items()): + # feature_value is aliased to (the already-moved) feature.value. + if attr != "feature_value" and isinstance(val, torch.Tensor): + setattr(rule, attr, fn(val)) + return self + class Connection(AbstractConnection): # language=rst From ba464ff61ecbcfaa2924e9303d7fd61035a974d4 Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Mon, 15 Jun 2026 13:15:15 -0400 Subject: [PATCH 14/15] NeuroEval WO-07 (part 2): record measured eth_mnist reference accuracy Overnight GPU reference run (RTX 2070, torch 2.6, seed 0, --n_train 20000 --n_test 10000) measured DiehlAndCook2015 / eth_mnist at 0.81 (all-activity) and 0.82 (proportion-weighting) test accuracy. Recorded in REPRODUCING.md with the exact config, replacing the "(not measured here)" placeholder. Refs #765 Co-Authored-By: Claude Opus 4.8 --- REPRODUCING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REPRODUCING.md b/REPRODUCING.md index 0d2891358..88c71ee4d 100644 --- a/REPRODUCING.md +++ b/REPRODUCING.md @@ -14,7 +14,7 @@ the data it needs (declared in [DATA.md](DATA.md)). | Claim / source | Model class | Script | Command (defaults shown) | Seed | Expected output | Data | |----------------|-------------|--------|--------------------------|------|-----------------|------| -| Diehl & Cook 2015 MNIST replication (DOI `10.3389/fncom.2015.00099`) | `DiehlAndCook2015` | `examples/mnist/eth_mnist.py` | `python examples/mnist/eth_mnist.py --n_neurons 100 --n_epochs 1 --time 250 --seed 0` | `--seed 0` (`torch.manual_seed`) | Prints test accuracy at end; accuracy rises with `--n_neurons` (Diehl & Cook report up to ~95% at 6400 neurons). *(not measured here)* | MNIST | +| Diehl & Cook 2015 MNIST replication (DOI `10.3389/fncom.2015.00099`) | `DiehlAndCook2015` | `examples/mnist/eth_mnist.py` | `python examples/mnist/eth_mnist.py --n_neurons 100 --n_epochs 1 --time 250 --seed 0` | `--seed 0` (`torch.manual_seed`) | Prints test accuracy at end. **Measured:** all-activity **0.81**, proportion-weighting **0.82** at seed 0 with `--n_train 20000 --n_test 10000` (GPU, torch 2.6, ~7.8 h on an RTX 2070). Accuracy rises with `--n_neurons` and with the full 60000-sample train set (Diehl & Cook report up to ~95% at 6400 neurons). | MNIST | | Batched ETH MNIST | `DiehlAndCook2015` | `examples/mnist/batch_eth_mnist.py` | `python examples/mnist/batch_eth_mnist.py --n_neurons 100 --batch_size 32 --time 100 --seed 0` | `--seed 0` | Prints test accuracy; faster per-epoch via batching. *(not measured here)* | MNIST | | Supervised MNIST (label-clamped) | `DiehlAndCook2015` | `examples/mnist/supervised_mnist.py` | `python examples/mnist/supervised_mnist.py --n_neurons 100 --time 250 --intensity 32 --seed 0` | `--seed 0` | Prints test accuracy. *(not measured here)* | MNIST | | Convolutional SNN on MNIST | (in-script conv network) | `examples/mnist/conv_mnist.py` | `python examples/mnist/conv_mnist.py --time 50 --batch_size 1 --seed 0` | `--seed 0` | Prints accuracy. *(not measured here)* | MNIST | From 09f4577af80820ebd3b57d3c7dac923fb88c383c Mon Sep 17 00:00:00 2001 From: Hananel Hazan Date: Mon, 15 Jun 2026 13:53:02 -0400 Subject: [PATCH 15/15] fix: remove duplicate device kwarg in batch_eth_mnist DiehlAndCook2015 call `device=device` was passed twice to DiehlAndCook2015(...), making examples/mnist/batch_eth_mnist.py raise SyntaxError (repeated keyword argument) and fail to run at all. Remove the duplicate. Co-Authored-By: Claude Opus 4.8 --- examples/mnist/batch_eth_mnist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/mnist/batch_eth_mnist.py b/examples/mnist/batch_eth_mnist.py index 5a53ea047..eb1374537 100644 --- a/examples/mnist/batch_eth_mnist.py +++ b/examples/mnist/batch_eth_mnist.py @@ -113,7 +113,6 @@ nu=(1e-4, 1e-2), theta_plus=theta_plus, inpt_shape=(1, 28, 28), - device=device, w_dtype=getattr(torch, args.w_dtype), )