Skip to content

Commit c1cad73

Browse files
committed
Merge branch 'master' into nestml
2 parents 2903323 + 301cc92 commit c1cad73

26 files changed

Lines changed: 520 additions & 72 deletions

.github/workflows/full-test.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
python-version: ["3.9", "3.10", "3.12"]
2020
os: ["ubuntu-latest", "windows-latest"]
2121
steps:
22-
- uses: actions/checkout@v4
22+
- uses: actions/checkout@v6
2323
- uses: mpi4py/setup-mpi@v1
2424
- name: Set up Python ${{ matrix.python-version }}
25-
uses: actions/setup-python@v5
25+
uses: actions/setup-python@v6
2626
with:
2727
python-version: ${{ matrix.python-version }}
2828
- name: Install Linux system dependencies
@@ -32,22 +32,22 @@ jobs:
3232
- name: Install basic Python dependencies
3333
run: |
3434
python -m pip install --upgrade pip
35-
python -m pip install pytest pytest-cov coveralls flake8 "setuptools<=75.8.2" "numpy<2"
35+
python -m pip install pytest pytest-cov coveralls flake8
3636
- name: Install Brian 2
3737
run: |
3838
python -m pip install brian2
3939
- name: Install NEURON
4040
if: startsWith(matrix.os, 'ubuntu')
4141
run: |
42-
python -m pip install neuron==8.2.4
42+
python -m pip install "neuron>=9.0.0"
4343
python -m pip install "nrnutils>0.2.0"
4444
- name: Install NEST
4545
if: startsWith(matrix.os, 'ubuntu')
4646
run: |
47-
python -m pip install cython
48-
wget https://github.com/nest/nest-simulator/archive/refs/tags/v3.8.tar.gz -O nest-simulator-3.8.tar.gz
49-
tar xzf nest-simulator-3.8.tar.gz
50-
cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local -Dwith-mpi=ON ./nest-simulator-3.8
47+
python -m pip install "cython<3.1.0"
48+
wget https://github.com/nest/nest-simulator/archive/refs/tags/v3.9.tar.gz -O nest-simulator-3.9.tar.gz
49+
tar xzf nest-simulator-3.9.tar.gz
50+
cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local -Dwith-mpi=ON ./nest-simulator-3.9
5151
make
5252
make install
5353
- name: Install Arbor
@@ -78,7 +78,7 @@ jobs:
7878
flake8 pyNN --count --exit-zero --max-complexity=20 --max-line-length=127 --statistics
7979
- name: Run unit and system tests
8080
run: |
81-
pytest -v --cov=pyNN --cov-report=term test
81+
pytest -v -n auto --cov=pyNN --cov-report=term test
8282
- name: Upload coverage data
8383
run: |
8484
coveralls --service=github

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ examples/*.svg
7878
/examples/Potjans2014/clean.sh
7979
/test/benchmarks/*.h5
8080

81+
# Test environment (Makefile targets)
82+
.local-nest*/
83+
.nest-src/
84+
.nest-build/
85+
.condaenv-*/
86+
8187
# Other
8288
*.btr
8389
*.log

Makefile

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# PyNN test environment — three options for running the test suite
2+
#
3+
# Option A (native): builds NEST from source into a project-local venv+prefix
4+
# Option B (Docker): runs tests in an Ubuntu container with bind-mounted source
5+
# Option C (conda): uses micromamba + conda-forge (no compilation)
6+
#
7+
# All targets accept NEST_VERSION=<version> to switch NEST versions.
8+
#
9+
# Version string format differs between Options A/B and C for pre-releases:
10+
# stable: NEST_VERSION=3.9 (same for all options)
11+
# pre-release: GitHub tag format for A/B (e.g. 3.10.0rc1)
12+
# conda-forge format for C (e.g. 3.10_rc1)
13+
# Check https://github.com/nest/nest-simulator/releases for GitHub tag names.
14+
15+
NEST_VERSION ?= 3.9
16+
17+
# ── Option A: paths and variables ─────────────────────────────────────────────
18+
NEST_PREFIX = $(CURDIR)/.local-nest$(NEST_VERSION)
19+
NEST_PY = $(NEST_PREFIX)/bin/python3
20+
NEST_PIP = $(NEST_PREFIX)/bin/pip
21+
NEST_PYTEST = $(NEST_PREFIX)/bin/pytest
22+
NEST_VENV_STAMP = $(NEST_PREFIX)/.venv-ready
23+
NEST_STAMP = $(NEST_PREFIX)/.nest-installed
24+
NEST_SRC_DIR = $(CURDIR)/.nest-src
25+
NEST_BUILD_DIR = $(CURDIR)/.nest-build/nest-$(NEST_VERSION)
26+
NEST_TARBALL = $(NEST_SRC_DIR)/nest-$(NEST_VERSION).tar.gz
27+
NEST_SRC_UNPACKED = $(NEST_SRC_DIR)/nest-simulator-$(NEST_VERSION)
28+
NPROC = $(shell sysctl -n hw.logicalcpu 2>/dev/null || echo 4)
29+
# cmake will search /usr/local by default; override if your C deps are elsewhere
30+
EXTRA_CMAKE_ARGS ?=
31+
32+
# ── Option B: variables ────────────────────────────────────────────────────────
33+
DOCKER_IMAGE = pynn-test:nest$(NEST_VERSION)
34+
# Pass NEST_VERSION as env var so docker-compose.yml substitution picks it up
35+
COMPOSE = NEST_VERSION=$(NEST_VERSION) docker compose -f test/docker-compose.yml
36+
37+
# ── Option C: variables ────────────────────────────────────────────────────────
38+
MICROMAMBA ?= micromamba
39+
CONDAENV_PREFIX = $(CURDIR)/.condaenv-nest$(NEST_VERSION)
40+
CONDA_ENV_FILE = test/environment-nest$(NEST_VERSION).yml
41+
42+
# ══════════════════════════════════════════════════════════════════════════════
43+
# Option A: native venv + NEST built from source
44+
# ══════════════════════════════════════════════════════════════════════════════
45+
46+
.PHONY: setup
47+
setup: $(NEST_STAMP) ## [A] Build NEST $(NEST_VERSION) from source + create venv
48+
@echo ""
49+
@echo "Setup complete. Run 'make test NEST_VERSION=$(NEST_VERSION)'"
50+
51+
# 1. Create venv and install Python build prerequisites
52+
# mpi4py must be compiled against the same MPI that NEST will link to,
53+
# and must be present before cmake runs so NEST can detect it.
54+
$(NEST_VENV_STAMP):
55+
python3 -m venv $(NEST_PREFIX)
56+
$(NEST_PIP) install --upgrade pip
57+
$(NEST_PIP) install "cython<3.1.0"
58+
MPICC=$(MPI_ROOT)/bin/mpicc \
59+
$(NEST_PIP) install --no-binary=mpi4py mpi4py
60+
touch $@
61+
62+
# 2. Download NEST tarball
63+
$(NEST_TARBALL):
64+
@mkdir -p $(NEST_SRC_DIR)
65+
curl -fL -o $@ \
66+
https://github.com/nest/nest-simulator/archive/refs/tags/v$(NEST_VERSION).tar.gz
67+
68+
# 3. Unpack source and apply backport of NEST PR #3794:
69+
# "Add missing include needed on macOS 26.4" — <cstddef> was previously
70+
# available in numerics.h only via transitive includes that macOS 26 removed.
71+
$(NEST_SRC_UNPACKED): $(NEST_TARBALL)
72+
tar xzf $< -C $(NEST_SRC_DIR)
73+
sed -i '' 's|#include <cmath>|#include <cmath>\n#include <cstddef>|' \
74+
$(NEST_SRC_UNPACKED)/libnestutil/numerics.h
75+
touch $@
76+
77+
# 4. cmake build, install, then install remaining Python deps
78+
$(NEST_STAMP): $(NEST_VENV_STAMP) $(NEST_SRC_UNPACKED)
79+
mkdir -p $(NEST_BUILD_DIR)
80+
cd $(NEST_BUILD_DIR) && cmake \
81+
-DCMAKE_INSTALL_PREFIX=$(NEST_PREFIX) \
82+
-DPython_EXECUTABLE=$(NEST_PY) \
83+
-Dwith-mpi=ON \
84+
-Dwith-python=ON \
85+
-Dwith-gsl=ON \
86+
-Dwith-ltdl=ON \
87+
-Dwith-openmp=OFF \
88+
$(EXTRA_CMAKE_ARGS) \
89+
$(NEST_SRC_UNPACKED)
90+
cd $(NEST_BUILD_DIR) && make -j$(NPROC)
91+
cd $(NEST_BUILD_DIR) && make install
92+
# Build and install PyNN NEST extensions (pynn_extensions module)
93+
mkdir -p $(NEST_BUILD_DIR)/pynn_extensions
94+
cd $(NEST_BUILD_DIR)/pynn_extensions && cmake \
95+
-Dwith-nest=$(NEST_PREFIX)/bin/nest-config \
96+
$(EXTRA_CMAKE_ARGS) \
97+
$(CURDIR)/pyNN/nest/extensions
98+
cd $(NEST_BUILD_DIR)/pynn_extensions && make install
99+
$(NEST_PIP) install \
100+
"neuron>=9.0.0" nrnutils "arbor==0.9.0" \
101+
brian2 libNeuroML scipy matplotlib Cheetah3 h5py Jinja2 \
102+
pytest pytest-xdist pytest-cov flake8
103+
$(NEST_PIP) install -e .
104+
# Compile NEURON .mod mechanisms against the venv's NEURON.
105+
# The compiled arm64/ dir lives in the source tree and is version-specific,
106+
# so it must be rebuilt whenever the NEURON version changes.
107+
cd $(CURDIR)/pyNN/neuron/nmodl && $(NEST_PREFIX)/bin/nrnivmodl .
108+
touch $@
109+
110+
.PHONY: test
111+
test: $(NEST_STAMP) ## [A] Run full test suite (NEST_VERSION=$(NEST_VERSION))
112+
$(NEST_PYTEST) -v -n auto test/
113+
114+
.PHONY: test-unit
115+
test-unit: $(NEST_STAMP) ## [A] Unit tests only (no simulator needed)
116+
$(NEST_PYTEST) -n auto test/unittests/
117+
118+
.PHONY: test-nest
119+
test-nest: $(NEST_STAMP) ## [A] NEST system + scenario tests
120+
$(NEST_PYTEST) -n auto test/system/test_nest.py test/system/scenarios/
121+
122+
.PHONY: test-neuron
123+
test-neuron: $(NEST_STAMP) ## [A] NEURON system + scenario tests
124+
$(NEST_PYTEST) -n auto test/system/test_neuron.py test/system/scenarios/
125+
126+
.PHONY: test-brian2
127+
test-brian2: $(NEST_STAMP) ## [A] Brian2 system tests
128+
$(NEST_PYTEST) -n auto test/system/test_brian2.py
129+
130+
.PHONY: clean-nmodl
131+
clean-nmodl: ## [A] Remove compiled NEURON mechanisms (required before switching NEURON versions)
132+
rm -rf $(CURDIR)/pyNN/neuron/nmodl/arm64 \
133+
$(CURDIR)/pyNN/neuron/nmodl/x86_64
134+
135+
.PHONY: clean
136+
clean: ## [A] Remove .local-nest$(NEST_VERSION)/ (triggers full rebuild)
137+
rm -rf $(NEST_PREFIX)
138+
139+
.PHONY: clean-build
140+
clean-build: ## [A] Remove cmake build dir only (retry after cmake failure)
141+
rm -rf $(NEST_BUILD_DIR)
142+
143+
.PHONY: clean-all
144+
clean-all: ## [A] Remove all .local-nest*/, .nest-build/, .nest-src/
145+
rm -rf .local-nest*/ .nest-build/ .nest-src/
146+
147+
# ══════════════════════════════════════════════════════════════════════════════
148+
# Option B: Docker with bind-mounted source
149+
# ══════════════════════════════════════════════════════════════════════════════
150+
151+
.PHONY: docker-build
152+
docker-build: ## [B] Build Docker image pynn-test:nest$(NEST_VERSION)
153+
$(COMPOSE) build
154+
155+
.PHONY: docker-test
156+
docker-test: ## [B] Run full test suite in Docker
157+
$(COMPOSE) run --rm pynn pytest -v -n auto test/
158+
159+
.PHONY: docker-test-unit
160+
docker-test-unit: ## [B] Unit tests in Docker
161+
$(COMPOSE) run --rm pynn pytest -n auto test/unittests/
162+
163+
.PHONY: docker-test-nest
164+
docker-test-nest: ## [B] NEST tests in Docker
165+
$(COMPOSE) run --rm pynn pytest -n auto test/system/test_nest.py test/system/scenarios/
166+
167+
.PHONY: docker-test-neuron
168+
docker-test-neuron: ## [B] NEURON tests in Docker
169+
$(COMPOSE) run --rm pynn pytest -n auto test/system/test_neuron.py test/system/scenarios/
170+
171+
.PHONY: docker-test-brian2
172+
docker-test-brian2: ## [B] Brian2 tests in Docker
173+
$(COMPOSE) run --rm pynn pytest -n auto test/system/test_brian2.py
174+
175+
.PHONY: docker-shell
176+
docker-shell: ## [B] Interactive bash inside Docker container
177+
$(COMPOSE) run --rm pynn bash
178+
179+
# ══════════════════════════════════════════════════════════════════════════════
180+
# Option C: micromamba + conda-forge (no compilation)
181+
# Install micromamba first: "${SHELL}" <(curl -L micro.mamba.pm/install.sh)
182+
# conda-forge RC version string uses underscore: e.g. NEST_VERSION=3.10_rc1
183+
# ══════════════════════════════════════════════════════════════════════════════
184+
185+
.PHONY: conda-setup
186+
conda-setup: ## [C] Create micromamba env .condaenv-nest$(NEST_VERSION)/
187+
@test -f $(CONDA_ENV_FILE) || \
188+
(echo "Error: $(CONDA_ENV_FILE) not found."; \
189+
echo "Copy test/environment-nest3.9.yml and update the nest-simulator pin."; \
190+
exit 1)
191+
$(MICROMAMBA) env create -p $(CONDAENV_PREFIX) -f $(CONDA_ENV_FILE) -y
192+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) pip install -e .
193+
194+
.PHONY: conda-test
195+
conda-test: ## [C] Run full test suite via micromamba
196+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) pytest -v -n auto test/
197+
198+
.PHONY: conda-test-unit
199+
conda-test-unit: ## [C] Unit tests via micromamba
200+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) pytest -n auto test/unittests/
201+
202+
.PHONY: conda-test-nest
203+
conda-test-nest: ## [C] NEST tests via micromamba
204+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) \
205+
pytest -n auto test/system/test_nest.py test/system/scenarios/
206+
207+
.PHONY: conda-test-neuron
208+
conda-test-neuron: ## [C] NEURON tests via micromamba
209+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) \
210+
pytest -n auto test/system/test_neuron.py test/system/scenarios/
211+
212+
.PHONY: conda-test-brian2
213+
conda-test-brian2: ## [C] Brian2 tests via micromamba
214+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) pytest -n auto test/system/test_brian2.py
215+
216+
.PHONY: conda-shell
217+
conda-shell: ## [C] Interactive shell via micromamba
218+
$(MICROMAMBA) run -p $(CONDAENV_PREFIX) bash
219+
220+
.PHONY: conda-clean
221+
conda-clean: ## [C] Remove .condaenv-nest$(NEST_VERSION)/
222+
rm -rf $(CONDAENV_PREFIX)
223+
224+
# ══════════════════════════════════════════════════════════════════════════════
225+
# Help
226+
# ══════════════════════════════════════════════════════════════════════════════
227+
228+
.PHONY: help
229+
help: ## Show available targets
230+
@echo "Usage: make <target> [NEST_VERSION=3.9]"
231+
@echo ""
232+
@grep -E '^[a-zA-Z0-9_-]+:.*##' $(MAKEFILE_LIST) | \
233+
awk 'BEGIN {FS = ":.*##"}; {printf " %-26s %s\n", $$1, $$2}'
234+
@echo ""
235+
@echo "Current NEST_VERSION: $(NEST_VERSION)"
236+
237+
.DEFAULT_GOAL := help

pyNN/arbor/simulator.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from collections import defaultdict
66
import logging
7+
import os
78
import os.path
89
import subprocess
910
import numpy as np
@@ -26,10 +27,13 @@ def build_mechanisms():
2627
cat_builder = find("arbor-build-catalogue")
2728
if not cat_builder:
2829
raise Exception("Unable to find arbor-build-catalogue. Please ensure Arbor is correctly installed.")
29-
proc = subprocess.run([cat_builder, "PyNN", mech_path], cwd=mech_path)
30+
env = os.environ.copy()
31+
# Newer Xcode/Clang requires an explicit include for NULL; pass it via CXXFLAGS
32+
# so cmake picks it up when arbor-build-catalogue compiles the generated C++.
33+
env["CXXFLAGS"] = env.get("CXXFLAGS", "") + " -include cstddef"
34+
proc = subprocess.run([cat_builder, "PyNN", mech_path], cwd=mech_path, env=env)
3035
if proc.returncode != 0:
31-
err_msg = "\n ".join(proc.stdout)
32-
raise Exception(f"Unable to compile Arbor mechanisms. Output was:\n {err_msg}")
36+
raise Exception("Unable to compile Arbor mechanisms (arbor-build-catalogue returned non-zero exit status).")
3337
else:
3438
logger.info("Successfully compiled Arbor mechanisms.")
3539
return mech_path

pyNN/brian2/projections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _set(self, attr_name, value):
4949
1,), schema=self.projection.synapse_type.get_schema())
5050
native_ps = self.projection.synapse_type.translate(ps)
5151
native_ps.evaluate()
52-
getattr(self._syn_obj, attr_name)[self.index] = native_ps[attr_name]
52+
getattr(self._syn_obj, attr_name)[self.index] = native_ps[attr_name][0]
5353

5454
def _set_weight(self, w):
5555
self._set("weight", w)

pyNN/brian2/recording.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _clear_simulator(self):
7474
def _get_spiketimes(self, requested_ids, clear=False):
7575
id_array = self._devices["spikes"].i + self.population.first_id
7676
times_array = self._devices["spikes"].t / ms
77-
mask = np.in1d(id_array, requested_ids)
77+
mask = np.isin(id_array, requested_ids)
7878
return id_array[mask], times_array[mask]
7979

8080
def _get_all_signals(self, variable, ids, clear=False):

pyNN/brian2/simulator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ def run(self, simtime):
6161
self.network.run(simtime * ms)
6262

6363
def run_until(self, tstop):
64-
self.run(tstop - self.t)
64+
if tstop > self.t:
65+
self.run(tstop - self.t)
6566

6667
def clear(self):
6768
self.recorders = set([])

pyNN/connectors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ def connect(self, projection):
585585
# - use np.unique, or just do in1d(self.conn_list)?
586586
idx = np.argsort(self.conn_list[:, 1])
587587
targets = np.unique(self.conn_list[:, 1]).astype(int)
588-
local = np.in1d(targets,
588+
local = np.isin(targets,
589589
np.arange(projection.post.size)[projection.post._mask_local],
590590
assume_unique=True)
591591
local_targets = targets[local]

pyNN/nest/recording.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def get_spiketimes(self, desired_ids, clear=False):
151151
id_array, times_array = self._get_data_arrays("times", "times", 1, clear=clear)
152152
recorded_ids = np.unique(id_array)
153153
desired_and_existing_ids = np.intersect1d(recorded_ids, np.array(desired_ids))
154-
mask = np.in1d(id_array, desired_and_existing_ids)
154+
mask = np.isin(id_array, desired_and_existing_ids)
155155
return id_array[mask], times_array[mask]
156156

157157
def get_spike_counts(self, desired_ids):

pyNN/nest/simulator.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ def build_extensions(build_dir=None):
7373
warnings.warn("Cannot create build directory for nest extensions")
7474
return
7575

76+
# Remove any stale CMakeCache.txt so cmake re-detects the SDK and toolchain.
77+
stale_cache = os.path.join(nest_build_dir, "CMakeCache.txt")
78+
try:
79+
os.remove(stale_cache)
80+
except FileNotFoundError:
81+
pass
82+
7683
source_dir = os.path.join(os.path.dirname(__file__), "extensions")
7784
result, stdout = run_command(f"cmake -Dwith-nest={nest_config} {source_dir}",
7885
nest_build_dir)
@@ -255,7 +262,8 @@ def run(self, simtime):
255262
nest.Simulate(simtime)
256263

257264
def run_until(self, tstop):
258-
self.run(tstop - self.t)
265+
if tstop > self.t:
266+
self.run(tstop - self.t)
259267

260268
def reset(self):
261269
if self.t > 0:

0 commit comments

Comments
 (0)