Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Commit e6d414e

Browse files
committed
Update mechanisms loading using NEURON API
add type_aliases.py, use NEURON types consistently remove nrn_dpsable_banner, no longer required use neuron.load_mechanisms in import_mod_lib ModuleType to type functions taking neuron as arg use BluecellulabError instead of bare Exception add test_importer.py use typing-extensions for older python versions
1 parent 7c5e04d commit e6d414e

10 files changed

Lines changed: 114 additions & 83 deletions

File tree

bluecellulab/cell/core.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import json
2121
from pathlib import Path
2222
import queue
23-
from typing import Any, Optional
23+
from typing import Optional
2424

2525
import numpy as np
2626

@@ -41,11 +41,10 @@
4141
from bluecellulab.rngsettings import RNGSettings
4242
from bluecellulab.stimuli import SynapseReplay
4343
from bluecellulab.synapse import SynapseFactory, Synapse
44+
from bluecellulab.type_aliases import HocObjectType
4445

4546
logger = logging.getLogger(__name__)
4647

47-
NeuronType = Any
48-
4948

5049
class Cell(InjectableMixin, PlottableMixin):
5150
"""Represents a BGLib Cell object."""
@@ -74,7 +73,7 @@ def __init__(self, template_path: str | Path, morphology_path: str | Path,
7473
super().__init__()
7574
# Persistent objects, like clamps, that exist as long
7675
# as the object exists
77-
self.persistent: list[NeuronType] = []
76+
self.persistent: list[HocObjectType] = []
7877

7978
self.morphology_path = morphology_path
8079

@@ -99,12 +98,12 @@ def __init__(self, template_path: str | Path, morphology_path: str | Path,
9998
else:
10099
self.rng_settings = rng_settings
101100

102-
self.recordings: dict[str, NeuronType] = {}
101+
self.recordings: dict[str, HocObjectType] = {}
103102
self.synapses: dict[int, Synapse] = {}
104103
self.connections: dict[int, bluecellulab.Connection] = {}
105104

106-
self.ips: dict[int, NeuronType] = {}
107-
self.syn_mini_netcons: dict[int, NeuronType] = {}
105+
self.ips: dict[int, HocObjectType] = {}
106+
self.syn_mini_netcons: dict[int, HocObjectType] = {}
108107
self.serialized = None
109108

110109
# Be careful when removing this,
@@ -122,7 +121,7 @@ def __init__(self, template_path: str | Path, morphology_path: str | Path,
122121

123122
self.delayed_weights = queue.PriorityQueue() # type: ignore
124123
self.secname_to_isec: dict[str, int] = {}
125-
self.secname_to_hsection: dict[str, NeuronType] = {}
124+
self.secname_to_hsection: dict[str, HocObjectType] = {}
126125
self.secname_to_psection: dict[str, psection.PSection] = {}
127126

128127
self.emodel_properties = emodel_properties

bluecellulab/cell/injector.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
RelativeOrnsteinUhlenbeck,
3737
RelativeShotNoise,
3838
)
39+
from bluecellulab.type_aliases import HocObjectType
40+
3941

4042
logger = logging.getLogger(__name__)
4143

@@ -450,9 +452,9 @@ def add_alpha_synapse(
450452
tau: float,
451453
gmax: float,
452454
e: float,
453-
section: bluecellulab.neuron.hoc.HocObject,
455+
section: HocObjectType,
454456
segx=0.5,
455-
) -> bluecellulab.neuron.hoc.HocObject:
457+
) -> HocObjectType:
456458
"""Add an AlphaSynapse NEURON point process stimulus to the cell."""
457459
syn = bluecellulab.neuron.h.AlphaSynapse(segx, sec=section)
458460
syn.onset = onset

bluecellulab/cell/random.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
"""Contains probability distributions."""
1515

1616
from math import log, sqrt
17-
from typing import Any
1817

19-
from bluecellulab import neuron
18+
import neuron
2019

21-
NEURON_RNG = Any
20+
21+
from bluecellulab.type_aliases import NeuronRNG, NeuronVector
2222

2323

2424
# Gamma-distributed sample generator (not available in NEURON)
25-
def gamma(rng: NEURON_RNG, a: float, b: float, N: int = 1) -> neuron.h.Vector:
25+
def gamma(rng: NeuronRNG, a: float, b: float, N: int = 1) -> NeuronVector:
2626
"""Sample N variates from a gamma distribution with parameters shape = a,
2727
scale = b using the NEURON random number generator rng.
2828

bluecellulab/cell/template.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from bluecellulab import neuron
2727
from bluecellulab.circuit import EmodelProperties
2828
from bluecellulab.exceptions import BluecellulabError
29+
from bluecellulab.type_aliases import HocObjectType
2930

3031
import logging
3132

@@ -54,7 +55,7 @@ def __init__(
5455

5556
def get_cell(
5657
self, template_format: str, gid: Optional[int], emodel_properties: Optional[EmodelProperties]
57-
) -> neuron.hoc.HocObject:
58+
) -> HocObjectType:
5859
"""Returns the hoc object matching the template format."""
5960
morph_dir, morph_fname = os.path.split(self.morph_filepath)
6061
if template_format == "v6":

bluecellulab/importer.py

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,62 +14,36 @@
1414
"""Main importer of bluecellulab."""
1515

1616
import os
17+
from types import ModuleType
1718
import pkg_resources
19+
import neuron
1820

21+
from bluecellulab.exceptions import BluecellulabError
1922

20-
def _nrn_disable_banner():
21-
"""Disable NEURON banner."""
2223

23-
import importlib.util
24-
import ctypes
25-
26-
neuron_spec = importlib.util.find_spec("neuron")
27-
nrnpy_path = neuron_spec.submodule_search_locations[0]
28-
import glob
29-
hoc_so_list = \
30-
glob.glob(os.path.join(nrnpy_path, 'hoc*.so'))
31-
32-
if len(hoc_so_list) != 1:
33-
raise Exception(
34-
'hoc shared library not found in %s' %
35-
nrnpy_path)
36-
37-
hoc_so = hoc_so_list[0]
38-
nrndll = ctypes.cdll[hoc_so]
39-
ctypes.c_int.in_dll(nrndll, 'nrn_nobanner_').value = 1
40-
41-
42-
def import_neuron():
43-
"""Import NEURON simulator."""
44-
_nrn_disable_banner()
45-
46-
import neuron
47-
48-
return neuron
49-
50-
51-
def import_mod_lib(neuron):
24+
def import_mod_lib(neuron: ModuleType) -> str:
5225
"""Import mod files."""
53-
54-
mod_lib_list = None
55-
if 'BGLIBPY_MOD_LIBRARY_PATH' in os.environ:
56-
26+
res = ""
27+
if 'BLUECELLULAB_MOD_LIBRARY_PATH' in os.environ:
5728
# Check if the current directory contains 'x86_64'.
58-
5929
if os.path.isdir('x86_64'):
60-
raise Exception("BGLIBPY_MOD_LIBRARY_PATH is set"
61-
" and current directory contains the x86_64 folder."
62-
" Please remove one of them.")
30+
raise BluecellulabError("BLUECELLULAB_MOD_LIBRARY_PATH is set"
31+
" and current directory contains the x86_64 folder."
32+
" Please remove one of them.")
6333

64-
mod_lib_path = os.environ["BGLIBPY_MOD_LIBRARY_PATH"]
65-
mod_lib_list = mod_lib_path.split(':')
66-
for mod_lib in mod_lib_list:
67-
neuron.h.nrn_load_dll(mod_lib)
34+
mod_lib_path = os.environ["BLUECELLULAB_MOD_LIBRARY_PATH"]
35+
neuron.load_mechanisms(mod_lib_path)
36+
res = mod_lib_path
37+
elif os.path.isdir('x86_64'):
38+
# NEURON 8.* automatically load these mechamisms
39+
res = os.path.abspath('x86_64')
40+
else:
41+
res = "No mechanisms are loaded."
6842

69-
return mod_lib_list
43+
return res
7044

7145

72-
def import_neurodamus(neuron):
46+
def import_neurodamus(neuron: ModuleType) -> None:
7347
"""Import neurodamus."""
7448
neuron.h.load_file("stdrun.hoc") # nrn
7549
hoc_files = [
@@ -83,13 +57,12 @@ def import_neurodamus(neuron):
8357
neuron.h.load_file(hoc_file_path)
8458

8559

86-
def print_header(neuron, mod_lib_path):
60+
def print_header(neuron: ModuleType, mod_lib_path: str) -> None:
8761
"""Print bluecellulab header to stdout."""
88-
print("Imported neuron from %s" % neuron.__file__)
89-
print('Mod libs: ', mod_lib_path)
62+
print("Imported NEURON from: %s" % neuron.__file__)
63+
print('Mod lib: ', mod_lib_path)
9064

9165

92-
neuron = import_neuron()
9366
mod_lib_paths = import_mod_lib(neuron)
9467
import_neurodamus(neuron)
95-
# print_header(neuron, mod_lib_paths)
68+
print_header(neuron, mod_lib_paths)

bluecellulab/synapse/synapse_types.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@
1414
"""Class that represents a synapse in bluecellulab."""
1515

1616
from __future__ import annotations
17-
from typing import Any, Optional
17+
from typing import Optional
1818
import pandas as pd
1919
import logging
2020

2121
import bluecellulab
2222
from bluecellulab.circuit import SynapseProperty
23+
from bluecellulab.type_aliases import HocObjectType
2324

2425

25-
NeuronType = Any
26-
2726
logger = logging.getLogger(__name__)
2827

2928

@@ -54,7 +53,7 @@ def __init__(
5453
extracellular_calcium: float
5554
the extracellular calcium concentration
5655
"""
57-
self.persistent: list[NeuronType] = []
56+
self.persistent: list[HocObjectType] = []
5857
self.synapseconfigure_cmds: list[str] = []
5958
self._delay_weights: list[tuple[float, float]] = []
6059
self._weight: Optional[float] = None
@@ -65,7 +64,7 @@ def __init__(
6564
self.projection, self.sid = self.syn_id
6665
self.extracellular_calcium = extracellular_calcium
6766
self.syn_description = self.update_syn_description(syn_description)
68-
self.hsynapse: Optional[NeuronType] = None
67+
self.hsynapse: Optional[HocObjectType] = None
6968

7069
self.source_popid, self.target_popid = popids
7170

bluecellulab/tools.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import numpy as np
3333

3434
import bluecellulab
35-
from bluecellulab import neuron
3635
from bluecellulab.circuit.circuit_access import EmodelProperties
3736
from bluecellulab.exceptions import UnsteadyCellError
3837

@@ -101,18 +100,6 @@ def rep_func(*args, **kwargs):
101100
return rep_func
102101

103102

104-
def load_nrnmechanisms(libnrnmech_path: str) -> None:
105-
"""Load another shared library with NEURON mechanisms. (Created by
106-
nrnivmodl)
107-
108-
Parameters
109-
----------
110-
libnrnmech_path: string
111-
Path to a NEURON mechanisms file
112-
"""
113-
neuron.h.nrn_load_dll(libnrnmech_path)
114-
115-
116103
def calculate_input_resistance(
117104
template_path: str | Path,
118105
morphology_path: str | Path,

bluecellulab/type_aliases.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Type aliases used within the package."""
2+
3+
from typing_extensions import TypeAlias
4+
from neuron import h as hoc_type
5+
6+
HocObjectType: TypeAlias = hoc_type # until NEURON is typed, most NEURON types are this
7+
NeuronRNG: TypeAlias = hoc_type
8+
NeuronVector: TypeAlias = hoc_type

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"pandas>=1.0.0,<3.0.0",
4444
"bluepysnap>=1.0.5,<2.0.0",
4545
"pydantic>=1.10.2,<2.0.0",
46+
"typing-extensions>=4.8.0"
4647
],
4748
keywords=[
4849
'computational neuroscience',

tests/test_importer.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
import pytest
3+
from unittest.mock import MagicMock, patch
4+
from bluecellulab import importer
5+
from bluecellulab.exceptions import BluecellulabError
6+
7+
8+
@patch("os.path.isdir", return_value=False) # when x86_64 isdir returns False
9+
def test_import_mod_lib_no_env_no_folder(mocked_isdir):
10+
mock_neuron = MagicMock()
11+
with patch.dict(os.environ, {}, clear=True):
12+
assert importer.import_mod_lib(mock_neuron) == "No mechanisms are loaded."
13+
14+
15+
def test_import_mod_lib_env_var_set_folder_exists():
16+
mock_neuron = MagicMock()
17+
with patch.dict(os.environ, {"BLUECELLULAB_MOD_LIBRARY_PATH": "/fake/path"}):
18+
with patch(
19+
"os.path.isdir", return_value=True
20+
): # when x86_64 isdir returns True and env var is set
21+
with pytest.raises(
22+
BluecellulabError,
23+
match="BLUECELLULAB_MOD_LIBRARY_PATH is set and current directory contains the x86_64 folder. Please remove one of them.",
24+
):
25+
importer.import_mod_lib(mock_neuron)
26+
27+
28+
def test_import_mod_lib_env_var_set():
29+
mock_neuron = MagicMock()
30+
with patch.dict(os.environ, {"BLUECELLULAB_MOD_LIBRARY_PATH": "/fake/path"}):
31+
with patch("os.path.isdir", return_value=False):
32+
assert importer.import_mod_lib(mock_neuron) == "/fake/path"
33+
34+
35+
def test_import_mod_lib_no_env_with_folder():
36+
mock_neuron = MagicMock()
37+
with patch.dict(os.environ, {}, clear=True):
38+
with patch("os.path.isdir", return_value=True):
39+
assert importer.import_mod_lib(mock_neuron).endswith("x86_64")
40+
41+
42+
@patch.object(
43+
importer.pkg_resources,
44+
"resource_filename",
45+
return_value="/fake/path/to/hoc_file.hoc",
46+
)
47+
def test_import_neurodamus(mocked_pkg_resources):
48+
mock_neuron = MagicMock()
49+
importer.import_neurodamus(mock_neuron)
50+
assert mock_neuron.h.load_file.called
51+
# Check that it was called with the expected arguments
52+
mock_neuron.h.load_file.assert_any_call("/fake/path/to/hoc_file.hoc")
53+
54+
55+
@patch("builtins.print")
56+
def test_print_header(mocked_print):
57+
mock_neuron = MagicMock(__file__="/fake/path/to/neuron")
58+
importer.print_header(mock_neuron, "/fake/path/to/mod_lib")
59+
# Check if the expected print calls were made
60+
mocked_print.assert_any_call("Imported NEURON from: /fake/path/to/neuron")
61+
mocked_print.assert_any_call("Mod lib: ", "/fake/path/to/mod_lib")

0 commit comments

Comments
 (0)