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`) 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 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 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": [ 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 +}