Skip to content

Commit

Permalink
Allow API functions to be used without loading hoc/mod #minor (#124)
Browse files Browse the repository at this point in the history
* replace in with ==

* load hoc and mod when required to allow API functions to be used without them
  • Loading branch information
anilbey authored Feb 2, 2024
1 parent 28a7a8f commit d7a5240
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ nrnivmodl.log
.ruff_cache
.pytest_cache
*.btr
*.whl
4 changes: 3 additions & 1 deletion bluecellulab/cell/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from bluecellulab.circuit.node_id import CellId
from bluecellulab.circuit.simulation_access import get_synapse_replay_spikes
from bluecellulab.exceptions import BluecellulabError
from bluecellulab.importer import load_hoc_and_mod_files
from bluecellulab.neuron_interpreter import eval_neuron
from bluecellulab.rngsettings import RNGSettings
from bluecellulab.stimuli import SynapseReplay
Expand All @@ -54,6 +55,7 @@ class Cell(InjectableMixin, PlottableMixin):

last_id = 0

@load_hoc_and_mod_files
def __init__(self,
template_path: str | Path,
morphology_path: str | Path,
Expand Down Expand Up @@ -134,7 +136,7 @@ def __init__(self,
self.secname_to_psection: dict[str, psection.PSection] = {}

self.emodel_properties = emodel_properties
if template_format in ['v6']:
if template_format == 'v6':
if self.emodel_properties is None:
raise BluecellulabError('EmodelProperties must be provided for v6 template')
self.hypamp: float | None = self.emodel_properties.holding_current
Expand Down
19 changes: 16 additions & 3 deletions bluecellulab/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import neuron

from bluecellulab.exceptions import BluecellulabError
from bluecellulab.utils import run_once


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,6 +72,18 @@ def print_header(neuron: ModuleType, mod_lib_path: str) -> None:
logger.info(f"Mod lib: {mod_lib_path}")


mod_lib_paths = import_mod_lib(neuron)
import_neurodamus(neuron)
print_header(neuron, mod_lib_paths)
@run_once
def _load_hoc_and_mod_files() -> None:
"""Import hoc and mod files."""
logger.info("Loading the mod files.")
mod_lib_paths = import_mod_lib(neuron)
logger.info("Loading the hoc files.")
import_neurodamus(neuron)
print_header(neuron, mod_lib_paths)


def load_hoc_and_mod_files(func):
def wrapper(*args, **kwargs):
_load_hoc_and_mod_files()
return func(*args, **kwargs)
return wrapper
3 changes: 2 additions & 1 deletion bluecellulab/rngsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
from bluecellulab import Singleton
from bluecellulab.circuit.circuit_access import CircuitAccess
from bluecellulab.exceptions import UndefinedRNGException
from bluecellulab.importer import load_hoc_and_mod_files

logger = logging.getLogger(__name__)


class RNGSettings(metaclass=Singleton):
"""Class that represents RNG settings in bluecellulab."""

@load_hoc_and_mod_files
def __init__(
self,
mode: Optional[str] = None,
Expand All @@ -41,7 +43,6 @@ def __init__(
circuit: circuit access object, if present seeds are read from simulation
base_seed: base seed for entire sim, overrides config value
"""

self._mode = ""
if mode is None:
if circuit_access is not None:
Expand Down
2 changes: 2 additions & 0 deletions bluecellulab/ssim.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from bluecellulab.circuit.format import determine_circuit_format, CircuitFormat
from bluecellulab.circuit.node_id import create_cell_id, create_cell_ids
from bluecellulab.circuit.simulation_access import BluepySimulationAccess, SimulationAccess, SonataSimulationAccess, _sample_array
from bluecellulab.importer import load_hoc_and_mod_files
from bluecellulab.stimuli import Noise, OrnsteinUhlenbeck, RelativeOrnsteinUhlenbeck, RelativeShotNoise, ShotNoise
import bluecellulab.stimuli as stimuli
from bluecellulab.exceptions import BluecellulabError
Expand All @@ -54,6 +55,7 @@
class SSim:
"""Class that loads a circuit simulation to do cell simulations."""

@load_hoc_and_mod_files
def __init__(
self,
simulation_config: str | Path | SimulationConfig,
Expand Down
11 changes: 11 additions & 0 deletions bluecellulab/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Utility functions."""


def run_once(func):
"""A decorator to ensure a function is only called once."""
def wrapper(*args, **kwargs):
if not wrapper.has_run:
wrapper.has_run = True
return func(*args, **kwargs)
wrapper.has_run = False
return wrapper
15 changes: 15 additions & 0 deletions tests/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,18 @@ def test_print_header(caplog):

assert "Imported NEURON from: /path/to/neuron" in caplog.text
assert "Mod lib: /path/to/mod_lib" in caplog.text


def test_print_header_with_decorator(caplog):
"""Ensure the decorator loading hoc and mod files work as expected."""
with caplog.at_level(logging.INFO):
@importer.load_hoc_and_mod_files
def x():
pass

x() # call 3 times to ensure the decorator is called only once
x()
x()

assert caplog.text.count("Loading the mod files.") == 1
assert caplog.text.count("Loading the hoc files.") == 1
2 changes: 2 additions & 0 deletions tests/test_neuron_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

import bluecellulab
from bluecellulab.exceptions import NeuronEvalError
from bluecellulab.importer import _load_hoc_and_mod_files
from bluecellulab.neuron_interpreter import eval_neuron

script_dir = os.path.dirname(__file__)


def test_eval_neuron():
"""Unit test for the eval_neuron function."""
_load_hoc_and_mod_files()
eval_neuron("neuron.h.nil", neuron=neuron)
with raises(NeuronEvalError):
eval_neuron("1+1")
Expand Down
2 changes: 2 additions & 0 deletions tests/test_simulation/test_neuron_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import neuron

from bluecellulab.circuit.config.sections import ConditionEntry, Conditions, MechanismConditions
from bluecellulab.importer import _load_hoc_and_mod_files
from bluecellulab.simulation.neuron_globals import set_global_condition_parameters, set_init_depleted_values, set_minis_single_vesicle_values, set_tstop_value


def test_set_tstop_value():
_load_hoc_and_mod_files() # this is a run_once function
set_tstop_value(100.0)
assert neuron.h.tstop == 100.0

Expand Down
26 changes: 26 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from bluecellulab.utils import run_once


# Decorated function for testing
@run_once
def increment_counter(counter):
counter[0] += 1
return "Executed"


def test_run_once_execution():
"""Test that the decorated function runs only once."""
counter = [0] # Using a list for mutability

assert increment_counter(counter) == "Executed"
increment_counter(counter)
assert counter[0] == 1

# Called 3 times but increased once
increment_counter(counter)
increment_counter(counter)
increment_counter(counter)

assert counter[0] == 1

assert increment_counter(counter) is None

0 comments on commit d7a5240

Please sign in to comment.