Skip to content

Commit

Permalink
Switch to NMODL-Glia (#33)
Browse files Browse the repository at this point in the history
* switched to pyproject.toml

* define public api

* black

* bump glia

* fix workflow file

* fix workflow file pt 2

* fix main workflow deps

* drop 3.8

* run some tests separately to avoid segfault

see neuronsimulator/nrn#2641

* fix parallel spike assertions

* bump numpy dep for faster 3.11 tests
  • Loading branch information
Helveg committed Dec 15, 2023
1 parent 1cc6e6c commit d0d669f
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 104 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/black.yml
Expand Up @@ -8,8 +8,8 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- name: Black Check
uses: jpetrucciani/black-check
uses: jpetrucciani/black-check@master
with:
path: 'patch'
path: 'arborize'
12 changes: 7 additions & 5 deletions .github/workflows/main.yml
Expand Up @@ -8,19 +8,19 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
py: ["3.8", "3.9", "3.10"]
py: ["3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.py }}
uses: actions/setup-python
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.py }}
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install openmpi-bin libopenmpi-dev
- name: Cache pip
uses: actions/cache
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
Expand All @@ -30,12 +30,14 @@ jobs:
- name: Install dependencies & self
run: |
python -m pip install --upgrade pip
pip install wheel
pip install . --no-deps
pip install -r requirements.txt --prefer-binary
pip install bsb==4.0.0a57
pip install -e .
- name: Run tests & coverage
run: |
coverage run --parallel-mode -m unittest
mpiexec -n 2 coverage run --parallel-mode -m unittest
NRN_SEGFAULT=1 coverage run --parallel-mode -m unittest
NRN_SEGFAULT=1 mpiexec -n 2 coverage run --parallel-mode -m unittest
bash <(curl -s https://codecov.io/bash)
24 changes: 21 additions & 3 deletions arborize/__init__.py
@@ -1,5 +1,10 @@
from .builders import *
from .schematics import *
"""
Write descriptions for NEURON cell models in an Arbor-like manner for both the Arbor and
NEURON brain simulation engines.
"""

from .builders import neuron_build
from .schematics import file_schematic, bsb_schematic
from .definitions import (
CableType,
CableProperties,
Expand All @@ -11,4 +16,17 @@
)
from .schematic import Schematic

__version__ = "4.0.0b0"
__version__ = "4.0.0b1"
__all__ = [
"CableProperties",
"CableType",
"Ion",
"Mechanism",
"ModelDefinition",
"Schematic",
"bsb_schematic",
"define_model",
"file_schematic",
"is_mech_id",
"neuron_build",
]
2 changes: 1 addition & 1 deletion arborize/_util.py
Expand Up @@ -20,5 +20,5 @@ def get_location_name(pts: Iterable["Point"]) -> str:
def get_arclengths(pts: Iterable["Point"]) -> npt.NDArray[float]:
coords = np.array([pt.coords for pt in pts])
rel_dist = np.diff(coords, axis=0, prepend=[coords[0, :]])
arcsums = np.cumsum(np.sum(rel_dist ** 2, axis=1) ** 0.5)
arcsums = np.cumsum(np.sum(rel_dist**2, axis=1) ** 0.5)
return arcsums / arcsums[-1]
7 changes: 7 additions & 0 deletions arborize/builders/_neuron.py
Expand Up @@ -124,6 +124,13 @@ def insert_transmitter(
def get_random_location(self):
return random.choice([*self._locations.keys()])

def record(self):
soma = [s for s in self._sections if "soma" in s.labels]
if not soma:
raise RuntimeError("No soma to record from")
else:
return soma[0].record()

def __getattr__(self, item):
if item in self._cable_types:
return [s for s in self._sections if item in s.labels]
Expand Down
2 changes: 1 addition & 1 deletion arborize/schematics/__init__.py
@@ -1,2 +1,2 @@
from ._bsb import bsb_schematic
from ._file import file_schematic
from ._file import file_schematic
4 changes: 2 additions & 2 deletions docs/source/conf.py
Expand Up @@ -49,7 +49,7 @@
"scipy",
"six",
"plotly",
"morphio"
"morphio",
]

# -- General configuration ---------------------------------------------------
Expand Down Expand Up @@ -78,4 +78,4 @@
# -- Options for HTML output -------------------------------------------------

html_theme = "furo"
html_static_path = ["_static"]
html_static_path = ["_static"]
21 changes: 21 additions & 0 deletions pyproject.toml
@@ -0,0 +1,21 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "arborize"
authors = [{name = "Robin De Schepper", email = "robingilbert.deschepper@unipv.it"}]
readme = "README.md"
license = {file = "LICENSE"}
classifiers = ["License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"]
dynamic = ["version", "description"]
dependencies = ["numpy", "errr>=1.2.0", "morphio>=3.3.6"]
requires-python = ">=3.9"

[project.optional-dependencies]
neuron = ["nrn-patch>=4.0.0b3", "nmodl-glia[neuron]>=4.0.0b6"]
arbor = ["arbor>=0.8"]
dev = ["sphinx", "sphinx_rtd_theme"]

[project.urls]
Home = "https://github.com/dbbs-lab/arborize"
9 changes: 5 additions & 4 deletions requirements.txt
@@ -1,5 +1,6 @@
nrn-patch==4.0.0a4
nrn-glia==4.0.0a4
numpy==1.22.0
nrn-patch==4.0.0b3
nmodl-glia==4.0.0b6
numpy==1.26.0
errr==1.2.0
mpi4py
coverage~=7.0
mpi4py==3.1.5
33 changes: 0 additions & 33 deletions setup.py

This file was deleted.

122 changes: 70 additions & 52 deletions tests/test_neuron_models.py
@@ -1,3 +1,4 @@
import os
import unittest

from ._shared import SchematicsFixture
Expand All @@ -10,6 +11,10 @@
import numpy as np


@unittest.skipIf(
"NRN_SEGFAULT" in os.environ,
"These tests are skipped to test the other tests below separately. See https://github.com/neuronsimulator/nrn/issues/2641",
)
class TestModelBuilding(SchematicsFixture, unittest.TestCase):
def test_mech_insert(self):
cell = neuron_build(self.p75_pas)
Expand Down Expand Up @@ -49,60 +54,11 @@ def test_synapses(self):

p.run(100)

self.assertEqual(list(r), list(r2), "Recording from same loc should be identical")
self.assertFalse(min(r) == max(r), "No synaptic currents detected")
self.assertTrue(min(r_nosyn) == max(r_nosyn), "Synaptic currents detected")

def test_transmitter_receiver(self):
if not p.parallel.id():
cell2 = neuron_build(self.p75_expsyn)
ais = cell2.filter_sections(["soma"])[0].locations[-1]
cell2.insert_transmitter(1, ais)
cell2.insert_synapse("ExpSyn", (0, 0)).stimulate(
start=0, number=5, interval=10, weight=1, delay=1
)

cell = neuron_build(self.p75_expsyn)
cell.insert_receiver(1, "ExpSyn", (0, 0), weight=0.04, delay=1)
r = p.record(cell.get_segment((0, 0), 0.5))
spt, spid = p.parallel.spike_record()
p.parallel._warn_new_gids = False

p.run(100)

arr = np.array(p.parallel.py_allgather([*r])).T
self.assertEqual(2, len(spid), "Expected 2 spikes")
self.assertTrue(np.allclose(np.diff(arr, axis=1), 0), "diff across nodes")
self.assertNotEqual(min(r), max(r), "no current detected")

def test_double_transmitter_receiver(self):
# Tests that NEURON still only transmits spikes from 1 detector per Section
if not p.parallel.id():
cell2 = neuron_build(self.p75_expsyn)
ais = cell2.filter_sections(["soma"])[0].locations[-1]
cell2.insert_transmitter(2, ais)
# Arborize doesn't let you place 2 transmitters on the same section,
# so bypass the security measure with some magic.
la = cell2.get_location(ais)
tm = p.ParallelCon(cell2.get_segment(ais, 0.5), 3)
cell2.insert_synapse("ExpSyn", (0, 0)).stimulate(
start=0, number=5, interval=10, weight=1, delay=1
)

cell = neuron_build(self.p75_expsyn)
cell.insert_receiver(2, "ExpSyn", (0, 0), weight=0.04, delay=1)
cell.insert_receiver(3, "ExpSyn", (0, 0), weight=0.04, delay=50)
spt, spid = p.parallel.spike_record()
p.parallel._warn_new_gids = False

p.run(100)

# If you'd insert more than 1 threshold detectors into the same Section,
# only 1 of them would actually detect thresholds, go figure. If this assert fails
# NEURON finally fixed that! :tada:
self.assertEqual(
2, len(spid), "NEURON stopped silently not doing what it should! Yay?"
list(r), list(r2), "Recording from same loc should be identical"
)
self.assertFalse(min(r) == max(r), "No synaptic currents detected")
self.assertTrue(min(r_nosyn) == max(r_nosyn), "Synaptic currents detected")

def test_cable_building(self):
self.cell010.definition = define_model(
Expand Down Expand Up @@ -163,6 +119,68 @@ def test_cable_building(self):
self.assertEqual(130, na["nao"][0])


@unittest.skipIf(
"NRN_SEGFAULT" not in os.environ,
"Run these tests separately. See https://github.com/neuronsimulator/nrn/issues/2641",
)
class TestTransmission(SchematicsFixture, unittest.TestCase):
def test_transmitter_receiver(self):
if not p.parallel.id():
cell2 = neuron_build(self.p75_expsyn)
ais = cell2.filter_sections(["soma"])[0].locations[-1]
cell2.insert_transmitter(1, ais)
cell2.insert_synapse("ExpSyn", (0, 0)).stimulate(
start=0, number=5, interval=10, weight=1, delay=1
)

cell = neuron_build(self.p75_expsyn)
cell.insert_receiver(1, "ExpSyn", (0, 0), weight=0.04, delay=1)
r = p.record(cell.get_segment((0, 0), 0.5))
spt, spid = p.parallel.spike_record()
p.parallel._warn_new_gids = False

p.run(100)

arr = np.array(p.parallel.py_allgather([*r])).T

if not p.parallel.id():
self.assertEqual(2, len(spid), "Expected 2 spikes")
self.assertTrue(np.allclose(np.diff(arr, axis=1), 0), "diff across nodes")
self.assertNotEqual(min(r), max(r), "no current detected")

def test_double_transmitter_receiver(self):
# We insert 2 transmitters that SHOULD each cause a spike in their receiver. But
# NEURON is broken and transmits spikes only through 1 detector per Section.
# If this test starts failing, NEURON fixed the issue, and we can simplify our
# logic.
if not p.parallel.id():
cell2 = neuron_build(self.p75_expsyn)
ais = cell2.filter_sections(["soma"])[0].locations[-1]
cell2.insert_transmitter(2, ais)
# Arborize doesn't let you place 2 transmitters on the same section for this reason,
# so bypass the security measure by manually inserting another PCon.
p.ParallelCon(cell2.get_segment(ais, 0.5), 3)
cell2.insert_synapse("ExpSyn", (0, 0)).stimulate(
start=0, number=5, interval=10, weight=1, delay=1
)

cell = neuron_build(self.p75_expsyn)
cell.insert_receiver(2, "ExpSyn", (0, 0), weight=0.08, delay=1)
cell.insert_receiver(3, "ExpSyn", (0, 0), weight=0.08, delay=50)
r = cell.record()
spt, spid = p.parallel.spike_record()
p.parallel._warn_new_gids = False

p.run(100, v_init=-65)

if not p.parallel.id():
# If you'd insert more than 1 threshold detectors into the same Section,
# only 1 of them would actually detect thresholds, go figure. If this assert
# fails NEURON finally fixed that! :tada:
self.assertEqual(1, len(spid))
self.assertTrue(np.any(np.array(r) != -65))


tagsGrC = {
16: ["axon", "axon_hillock"],
17: ["axon", "axon_initial_segment"],
Expand Down

0 comments on commit d0d669f

Please sign in to comment.