From da68ffbdb9f9202a0e3465324512400dfed2bbd8 Mon Sep 17 00:00:00 2001 From: Timo Gierlich Date: Thu, 16 Apr 2026 16:53:26 +0200 Subject: [PATCH 1/5] Ignore microcircuits sweep param files --- scripts/microcircuits/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 scripts/microcircuits/.gitignore diff --git a/scripts/microcircuits/.gitignore b/scripts/microcircuits/.gitignore new file mode 100644 index 0000000..f8b913f --- /dev/null +++ b/scripts/microcircuits/.gitignore @@ -0,0 +1,2 @@ +# ignore the param sweep files +*/exp.[0-9]*.yaml From 1469199797d83028fc6b305aa69bf5899f44a1ab Mon Sep 17 00:00:00 2001 From: Timo Gierlich Date: Thu, 16 Apr 2026 17:01:31 +0200 Subject: [PATCH 2/5] Add notebook stdd_ana_vs_sim.ipnb --- scripts/sal_principle/stdd_ana_vs_sim.ipynb | 249 ++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 scripts/sal_principle/stdd_ana_vs_sim.ipynb diff --git a/scripts/sal_principle/stdd_ana_vs_sim.ipynb b/scripts/sal_principle/stdd_ana_vs_sim.ipynb new file mode 100644 index 0000000..3ac43ec --- /dev/null +++ b/scripts/sal_principle/stdd_ana_vs_sim.ipynb @@ -0,0 +1,249 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Compare analytical method with simulation (SI, fig. 14):\n", + "\n", + "Here we show that our analytical method to compute the STDDs (see section 4.2.1 and the `stddc`-package) is consistent with the results of numerical simulations (computed by the `neuralsampling`-package based on eq. 8ff)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"NUMBA_DISABLE_JIT\"] = \"1\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from functools import partial\n", + "\n", + "import numpy as np\n", + "from numba import njit, vectorize\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from stddc import STDDMaker, alpha_PSP, rect_PSP, exp_window\n", + "from neuralsampling import network\n", + "from neuralsampling import stdp_functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# define the style etc.\n", + "mpl.style.use(\"../../mystyle.mpl\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "FIG_DIR = Path(\"../../figs/sal_principle\")\n", + "FIG_DIR.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "T_REF = 30\n", + "T_SYN_ALPHA = 10\n", + "T_MAX_ANA = 60\n", + "T_MAX_SIM = 1_000_000\n", + "\n", + "\n", + "_alpha_kernel = vectorize(alpha_PSP)\n", + "\n", + "\n", + "@njit\n", + "def alpha_kernel(t, tau_syn):\n", + " return _alpha_kernel(t, T_REF, tau_syn)\n", + "\n", + "\n", + "def worker(psps, b, w, tau_syn):\n", + " sm = STDDMaker(\n", + " psps[0],\n", + " t_max=T_MAX_ANA,\n", + " t_ref=T_REF,\n", + " t_syn=tau_syn,\n", + " w_12=w[0, 1],\n", + " w_21=w[1, 0],\n", + " b_1=b[0],\n", + " b_2=b[1],\n", + " )\n", + " stdd = sm.calc_stdd()\n", + " spks = network.sim_poisson_neurons(\n", + " t_max=T_MAX_SIM,\n", + " psp_kernel=psps[1],\n", + " bias=b,\n", + " weights=w,\n", + " t_ref=T_REF,\n", + " tau_syn=tau_syn,\n", + " )\n", + " stds, _ = stdp_functions.get_first_order_stds_2nrn(spks, 2)\n", + " stdd_sim, bins = np.histogram(stds, bins=121, range=(-60.5, 60.5), density=True)\n", + " return stdd, sm.times, stdd_sim, bins" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "b = np.array([-0.4, 0.3])\n", + "w = np.array([[0.0, 1.2], [0.8, 0.0]])\n", + "# stdd, dts, stdd_sim, dts_sim = worker((alpha_PSP, alpha_kernel), b, w, 10)\n", + "\n", + "spks = network.sim_poisson_neurons(\n", + " t_max=T_MAX_SIM, psp_kernel=alpha_kernel, bias=b, weights=w, t_ref=T_REF, tau_syn=10\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(2, 2, sharex=\"row\", sharey=True, figsize=(8 / 2.54, 8 / 2.54))\n", + "\n", + "rng = np.random.default_rng()\n", + "\n", + "b = rng.uniform(low=-1.0, high=1.0, size=2)\n", + "w = rng.uniform(low=-1.0, high=1.0, size=(2, 2))\n", + "np.fill_diagonal(w, 0)\n", + "stdd, dts, stdd_sim, dts_sim = worker((rect_PSP, network.rect_kernel), b, w, T_REF)\n", + "ax[0, 0].stairs(stdd_sim, dts_sim, fill=True, label=\"simulation\")\n", + "ax[0, 0].plot(dts, stdd, label=\"analytical\")\n", + "\n", + "b = rng.uniform(low=-1.0, high=1.0, size=2)\n", + "w = rng.uniform(low=-1.0, high=1.0, size=(2, 2))\n", + "np.fill_diagonal(w, 0)\n", + "stdd, dts, stdd_sim, dts_sim = worker((rect_PSP, network.rect_kernel), b, w, T_REF)\n", + "ax[1, 0].stairs(stdd_sim, dts_sim, fill=True)\n", + "ax[1, 0].plot(dts, stdd)\n", + "\n", + "b = rng.uniform(low=-1.0, high=1.0, size=2)\n", + "w = rng.uniform(low=-1.0, high=1.0, size=(2, 2))\n", + "np.fill_diagonal(w, 0)\n", + "stdd, dts, stdd_sim, dts_sim = worker((alpha_PSP, alpha_kernel), b, w, T_SYN_ALPHA)\n", + "ax[0, 1].stairs(stdd_sim, dts_sim, fill=True)\n", + "ax[0, 1].plot(dts, stdd)\n", + "\n", + "b = rng.uniform(low=-1.0, high=1.0, size=2)\n", + "w = rng.uniform(low=-1.0, high=1.0, size=(2, 2))\n", + "np.fill_diagonal(w, 0)\n", + "stdd, dts, stdd_sim, dts_sim = worker((alpha_PSP, alpha_kernel), b, w, T_SYN_ALPHA)\n", + "ax[1, 1].stairs(stdd_sim, dts_sim, fill=True)\n", + "ax[1, 1].plot(dts, stdd)\n", + "\n", + "# Iterate over all subplots to customize the appearance\n", + "for i in range(2):\n", + " for j in range(2):\n", + " a = ax[i, j]\n", + "\n", + " # Remove top, right, and left spines\n", + " a.spines[\"top\"].set_visible(False)\n", + " a.spines[\"right\"].set_visible(False)\n", + " a.spines[\"left\"].set_visible(False)\n", + "\n", + " # Remove y-axis ticks and labels\n", + " a.set_yticks([])\n", + "\n", + "for i in range(2):\n", + " ax[1, i].set_xlabel(r\"$\\Delta t$\")\n", + " ax[1, i].set_xticks(\n", + " [-T_REF, 0, T_REF],\n", + " labels=[r\"$-\\tau_\\mathrm{ref}$\", \"0\", r\"$\\tau_\\mathrm{ref}$\"],\n", + " )\n", + " ax[i, 0].set_ylabel(r\"$p(\\Delta t)$\")\n", + "\n", + "ax[0, 0].set_title(\"rect PSP\")\n", + "ax[0, 1].set_title(\"alpha PSP\")\n", + "\n", + "# Get handles and labels from the first subplot\n", + "handles, labels = ax[0, 0].get_legend_handles_labels()\n", + "\n", + "# Create a figure-level legend at the top center\n", + "fig.legend(handles, labels, loc=\"upper center\", ncol=2, bbox_to_anchor=(0.5, 1.0))\n", + "\n", + "# Adjust layout to make room for the legend\n", + "plt.tight_layout()\n", + "plt.subplots_adjust(top=0.80)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "fig.savefig(FIG_DIR / \"stdd_ana_vs_sim.pdf\")\n", + "fig.savefig(FIG_DIR / \"stdd_ana_vs_sim.png\")\n", + "fig.savefig(FIG_DIR / \"stdd_ana_vs_sim.svg\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "sal", + "language": "python", + "name": "sal" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 8698aaee72563dd554d397b9eb7ae8f0b6d456e1 Mon Sep 17 00:00:00 2001 From: Timo Gierlich Date: Thu, 16 Apr 2026 17:04:24 +0200 Subject: [PATCH 3/5] Move proof-plot notebook to correct folder --- .../{ => sal_principle}/plots_for_proof.ipynb | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) rename scripts/{ => sal_principle}/plots_for_proof.ipynb (96%) diff --git a/scripts/plots_for_proof.ipynb b/scripts/sal_principle/plots_for_proof.ipynb similarity index 96% rename from scripts/plots_for_proof.ipynb rename to scripts/sal_principle/plots_for_proof.ipynb index 294b639..d79c82b 100644 --- a/scripts/plots_for_proof.ipynb +++ b/scripts/sal_principle/plots_for_proof.ipynb @@ -1,9 +1,17 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Create the plots used in the stability proof (section 4.2.2, fig. 12)" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "0", + "id": "1", "metadata": {}, "outputs": [], "source": [ @@ -20,29 +28,29 @@ { "cell_type": "code", "execution_count": null, - "id": "1", + "id": "2", "metadata": {}, "outputs": [], "source": [ "# define the style etc.\n", - "mpl.style.use(\"../mystyle.mpl\")" + "mpl.style.use(\"../../mystyle.mpl\")" ] }, { "cell_type": "code", "execution_count": null, - "id": "2", + "id": "3", "metadata": {}, "outputs": [], "source": [ - "FIG_DIR = Path(\"../figs\")\n", + "FIG_DIR = Path(\"../../figs/sal_principle\")\n", "FIG_DIR.mkdir(parents=True, exist_ok=True)" ] }, { "cell_type": "code", "execution_count": null, - "id": "3", + "id": "4", "metadata": {}, "outputs": [], "source": [ @@ -60,7 +68,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4", + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -107,7 +115,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5", + "id": "6", "metadata": {}, "outputs": [], "source": [ @@ -138,7 +146,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6", + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -169,7 +177,7 @@ { "cell_type": "code", "execution_count": null, - "id": "7", + "id": "8", "metadata": {}, "outputs": [], "source": [ @@ -216,7 +224,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8", + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -247,7 +255,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9", + "id": "10", "metadata": {}, "outputs": [], "source": [ From d195ea998b53b4435c9d8d513b1e0b1b18fa838e Mon Sep 17 00:00:00 2001 From: Timo Gierlich Date: Fri, 17 Apr 2026 13:31:42 +0200 Subject: [PATCH 4/5] Update changelog --- CHANGELOG.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9631b..0ab896a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ Versions correspond to arXiv submissions of the paper. --- +## [v0.3.0-arxiv_v3] - TODO: DATE! +### Added +- Test suite to test main simulation scripts in `tests/` +- `scripts/symm_net/scatter_stdwi_rdd_sal.ipynb` to reproduce paper figure no. 7 +- Added code for convenient reproduction + of figure 8 and figure 9 end-to-end (i.e. `scripts/symm_net/sweep*.py*`, `scripts/symm_net/plots.ipynb` and `scripts/symm_net/plot_puresymm.ipynb`) + +### Updated +- Update all docstrings in a consistent form if needed. +- Add consistent typing where needed. +- README.md to match current repo structure. +- All plotting scripts if figures have changed. + +### Remove +- obsolete code chunks in `spiking_sampling_network/src/neuralsampling` +- obsolete code chunks in `spiking_microcircuits/src/microcircuits` + + ## [v0.2.0-arxiv_v2] - 2026-03-24 ### Added - Symmnet experiments for figures 7 and 8 (`scripts/symmnet/`) plus `symmnet` package @@ -18,4 +36,4 @@ Versions correspond to arXiv submissions of the paper. - SSN experiments (figures 4 & 5, `spiking_sampling_network` and `scripts/ssn/`) - Microcircuit experiments (figure 6, `spiking_microcircuits` and `scripts/microcircuits/`) - Code for analytically calculating the STDDs (`stdd_calculator`) -- Comparison of diffeent psp shapes (figure 7, `scripts/psp_shapes.ipynb`) +- Comparison of different psp shapes (figure 7, `scripts/psp_shapes.ipynb`) From 22086efcc6ed728c2d2aac922674219abb71f844 Mon Sep 17 00:00:00 2001 From: Timo Gierlich Date: Fri, 17 Apr 2026 14:20:08 +0200 Subject: [PATCH 5/5] Add instructions for pytest to README --- README.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7ef9b5a..8d23d1a 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ For conveniently reproducing any of the data shown in figure 8, we provide two w See `python sweep.py --help` for all available settings. - For large scale parameter sweeps (for instance to reproduce the 105 runs for all datasets, algorithms and seeds) on an HPC cluster (with SLURM), we provided the following workflow that needs only minimal adaptation to the available system. 1. Run `sweep_creator.py`: It creates `jobs.sh` which contains all `main_salnet.py`-calls with the relevant settings. -See `python sweep_creator.py --help` for all available settings. +See `python sweep_creator.py --help` for all available settings (the default settings will rerun the original simulations). 2. Modify `slurm.sh` to specify the relevant settings for your HPC cluster. 3. Run `bash slurm_submit.sh`. It will call `slurm.sh` internally and start a slurm array job. 4. The data can be plotted with `scripts/symm_net/plots.ipynb` @@ -125,16 +125,33 @@ The "Dale's law" experiment can be reproduced by `scripts/dales_law/EI-system.ip #### Figure 11 -The data for figure 7 can be reproached by `scripts/psp_shapes.ipynb`. Note that this notebook typically requires a lot of memory. +The data for figure 11 can be reproached by `scripts/psp_shapes.ipynb`. Note that this notebook typically requires a lot of memory (> 8GB). #### Figure 12 -Figure 8 can be reproduced by `scripts/plots_for_proof.ipynb`. +Figure 12 can be reproduced by `scripts/plots_for_proof.ipynb`. ## Tests -TODO description +The test suite lives in `tests/` and is run with [pytest](https://pytest.org). +It contains two layers of tests: + +**Fast smoke tests** (run automatically in CI, no GPU required): +```bash +pytest tests/ +``` +These verify that all four packages import correctly and that `main_salnet.py`, `traim_mc.py` and `train_bm.py` complete a minimal +run without errors. + +**PyTorch training tests** (opt-in, GPU recommended): +```bash +pytest tests/ --pytorch # bp section only (default) +pytest tests/ --pytorch --sections bp,fa,sal # specific subset +pytest tests/ --pytorch --sections all # test all seven algorithms +``` +These run `main_salnet.py` end-to-end for one epoch per selected algorithm +section. They are skipped by default because they require PyTorch CNN training. ## Version history