From 626e54f67f6f306d3d07f922d3b18eb66f972eb2 Mon Sep 17 00:00:00 2001 From: ilkankilic Date: Thu, 8 Jan 2026 16:12:52 +0100 Subject: [PATCH 01/15] wip --- bluecellulab/cell/point_process.py | 246 ++++++++++++++++++++++++ bluecellulab/circuit_simulation.py | 57 +++++- bluecellulab/point/__init__.py | 0 bluecellulab/point/connection_params.py | 15 ++ bluecellulab/point/point_connection.py | 84 ++++++++ 5 files changed, 395 insertions(+), 7 deletions(-) create mode 100644 bluecellulab/cell/point_process.py create mode 100644 bluecellulab/point/__init__.py create mode 100644 bluecellulab/point/connection_params.py create mode 100644 bluecellulab/point/point_connection.py diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py new file mode 100644 index 00000000..aa4c7ed8 --- /dev/null +++ b/bluecellulab/cell/point_process.py @@ -0,0 +1,246 @@ +from __future__ import annotations + +from dataclasses import dataclass +import logging +from pathlib import Path +from typing import Any, Mapping, Optional + +from bluecellulab.circuit.simulation_access import get_synapse_replay_spikes +from bluecellulab.exceptions import BluecellulabError +from neuron import h +import numpy as np + +from bluecellulab.circuit.node_id import CellId + +logger = logging.getLogger(__name__) + +class BasePointProcessCell: + """Base class for NEURON artificial point processes (IntFire1/2/...).""" + + def __init__(self, cell_id: Optional[CellId]) -> None: + self.cell_id = cell_id + + self._spike_times = h.Vector() + self._spike_detector: Optional[h.NetCon] = None + self.pointcell = None # type: ignore[assignment] + self.synapses: dict = {} + self.connections: dict = {} + + @property + def hoc_cell(self): + return self.pointcell + + + def init_callbacks(self): + pass + + def connect_to_circuit(self, proxy) -> None: + self._circuit_proxy = proxy + + def delete(self) -> None: + # Stop recording + if self._spike_detector is not None: + # NetCon will be GC'd when no Python refs remain + self._spike_detector = None + if self._spike_times is not None: + self._spike_times = None + + # Drop pointer to underlying NEURON object + self.pointcell = None + + + def get_spike_times(self) -> list[float]: + return list(self._spike_times) + + def create_netcon_spikedetector( + self, + sec, # ignored for artificial cells + location=None, # ignored for artificial cells + threshold: float = 0.0, + ) -> h.NetCon: + nc = h.NetCon(self.pointcell, None) + nc.threshold = threshold # harmless for artificial cells + return nc + + def is_recording_spikes(self, location=None, threshold: float | None = None) -> bool: + return self._spike_detector is not None + + def start_recording_spikes(self, sec, location=None, threshold: float = 0.0) -> None: + if self._spike_detector is not None: + return + self._spike_times = h.Vector() + self._spike_detector = h.NetCon(self.pointcell, None) + self._spike_detector.threshold = threshold + self._spike_detector.record(self._spike_times) + + + def connect2target(self, target_pp=None) -> h.NetCon: + """Neurodamus-like helper: NetCon from this cell to a target point process.""" + return h.NetCon(self.pointcell, target_pp) + + +class HocPointProcessCell(BasePointProcessCell): + """Point process that wraps an arbitrary HOC/mod artificial mechanism. + """ + + def __init__( + self, + cell_id: Optional[CellId], + mechanism_name: str, + param_overrides: Optional[Mapping[str, Any]] = None, + spike_threshold: float = 1.0, + ) -> None: + super().__init__(cell_id) + + try: + mech_cls = getattr(h, mechanism_name) + except AttributeError as exc: + raise BluecellulabError( + f"Point mechanism '{mechanism_name}' not found in NEURON. " + "Make sure the mod/hoc files are compiled and loaded." + ) from exc + + point = mech_cls() + if param_overrides: + for name, value in param_overrides.items(): + if hasattr(point, name): + setattr(point, name, value) + + self.pointcell = point + self.start_recording_spikes(None, None, threshold=spike_threshold) + + def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: str) -> None: + """SONATA-style spike replay for point processes. + + This is a simplified analogue of Cell.add_synapse_replay, but instead of + mapping spikes to individual synapses, we directly connect each presynaptic + node_id's spike train to this artificial cell via VecStim → NetCon. + """ + file_path = Path(stimulus.spike_file).expanduser() + + if not file_path.is_absolute(): + config_dir = stimulus.config_dir + if config_dir is not None: + file_path = Path(config_dir) / file_path + + file_path = file_path.resolve() + + if not file_path.exists(): + raise FileNotFoundError(f"Spike file not found: {str(file_path)}") + + synapse_spikes = get_synapse_replay_spikes(str(file_path)) + + if not hasattr(self, "_replay_vecs"): + self._replay_vecs: list[h.Vector] = [] + if not hasattr(self, "_replay_vecstims"): + self._replay_vecstims: list[h.VecStim] = [] + if not hasattr(self, "_replay_netcons"): + self._replay_netcons: list[h.NetCon] = [] + + for pre_node_id, spikes in synapse_spikes.items(): + delay = getattr(stimulus, "delay", 0.0) or 0.0 + duration = getattr(stimulus, "duration", np.inf) + + spikes_of_interest = spikes[ + (spikes >= delay) & (spikes <= duration) + ] + if spikes_of_interest.size == 0: + continue + + vec = h.Vector(spikes_of_interest) + vs = h.VecStim() + vs.play(vec) + + nc = h.NetCon(vs, self.pointcell) + # Use stimulus weight if available, otherwise default to 1.0 + weight = getattr(stimulus, "weight", 1.0) + nc.weight[0] = weight + nc.delay = 0.0 # delay already baked into spike times + + self._replay_vecs.append(vec) + self._replay_vecstims.append(vs) + self._replay_netcons.append(nc) + + logger.debug( + f"Added replay connection from pre_node_id={pre_node_id} " + f"to point neuron {self.cell_id}" + ) + +def mechanism_name_from_model_template(model_template: str) -> str: + """Translate SONATA model_template into a NEURON mechanism name. + + Examples: + 'hoc:AllenPointCell' -> 'AllenPointCell' + 'nrn:IntFire1' -> 'IntFire1' + 'AllenPointCell' -> 'AllenPointCell' + """ + mt = str(model_template).strip() + if ":" in mt: + prefix, name = mt.split(":", 1) + prefix = prefix.lower() + if prefix in ("hoc", "nrn"): + return name + return mt + +@dataclass +class IntFire1Params: + tau: float = 10.0 + refrac: float = 2.0 + + +class IntFire1Cell(BasePointProcessCell): + def __init__( + self, + cell_id: Optional[CellId] = None, + tau: float = 10.0, + refrac: float = 2.0, + ) -> None: + super().__init__(cell_id) + point = h.IntFire1() + point.tau = tau + point.refrac = refrac + self.pointcell = point + + self.start_recording_spikes(None, None, threshold=1.0) + + +@dataclass +class IntFire2Params: + taum: float = 10.0 + taus: float = 20.0 + ib: float = 0.0 + + +class IntFire2Cell(BasePointProcessCell): + def __init__( + self, + cell_id: Optional[CellId] = None, + taum: float = 10.0, + taus: float = 20.0, + ib: float = 0.0, + ) -> None: + super().__init__(cell_id) + point = h.IntFire2() + point.taum = taum + point.taus = taus + point.ib = ib + self.pointcell = point + + self.start_recording_spikes(None, None, threshold=1.0) + + +def create_intfire1_cell( + tau: float = 10.0, + refrac: float = 2.0, + cell_id: Optional[CellId] = None, +) -> IntFire1Cell: + return IntFire1Cell(cell_id=cell_id, tau=tau, refrac=refrac) + + +def create_intfire2_cell( + taum: float = 10.0, + taus: float = 20.0, + ib: float = 0.0, + cell_id: Optional[CellId] = None, +) -> IntFire2Cell: + return IntFire2Cell(cell_id=cell_id, taum=taum, taus=taus, ib=ib) diff --git a/bluecellulab/circuit_simulation.py b/bluecellulab/circuit_simulation.py index a37bc84d..5e51a800 100644 --- a/bluecellulab/circuit_simulation.py +++ b/bluecellulab/circuit_simulation.py @@ -55,6 +55,8 @@ ) from bluecellulab.synapse.synapse_types import SynapseID +from bluecellulab.cell.point_process import BasePointProcessCell, HocPointProcessCell, mechanism_name_from_model_template + logger = logging.getLogger(__name__) @@ -339,7 +341,32 @@ def _add_stimuli(self, add_noise_stimuli=False, except ValueError: pass + all_point_processes = all( + isinstance(cell, BasePointProcessCell) for cell in self.cells.values() + ) + for stimulus in stimuli_entries: + + # 1) SynapseReplay: works for both morpho cells and point processes + if isinstance(stimulus, circuit_stimulus_definitions.SynapseReplay): + for cell_id, cell in self.cells.items(): + if self.circuit_access.target_contains_cell(stimulus.target, cell_id): + if hasattr(cell, "add_synapse_replay"): + print("Adding SynapseReplay to cell", cell_id) + cell.add_synapse_replay( + stimulus, self.spike_threshold, self.spike_location + ) + logger.debug( + f"Added SynapseReplay {stimulus} to point/morpho cell {cell_id}" + ) + # No section/compartment logic needed for SynapseReplay + continue + + # 2) Other stimuli: require morphology + # If all cells are point processes, skip these stimuli entirely. + if all_point_processes: + continue + # Build a unified list of (cell_id, section, segx, section_name) targets targets: list[tuple] = [] @@ -348,6 +375,9 @@ def _add_stimuli(self, add_noise_stimuli=False, elif stimulus.node_set is not None: gids_of_target = self.circuit_access.get_target_cell_ids(stimulus.node_set) for cell_id in self.cells: + # Skip point processes: they have no soma + if isinstance(self.cells[cell_id], BasePointProcessCell): + continue if cell_id not in gids_of_target: continue sec = self.cells[cell_id].soma @@ -390,13 +420,6 @@ def _add_stimuli(self, add_noise_stimuli=False, elif isinstance(stimulus, circuit_stimulus_definitions.Sinusoidal): if add_sinusoidal_stimuli: self.cells[cell_id].add_sinusoidal(stimulus) - elif isinstance(stimulus, circuit_stimulus_definitions.SynapseReplay): # sonata only - if self.circuit_access.target_contains_cell( - stimulus.target, cell_id - ): - self.cells[cell_id].add_synapse_replay( - stimulus, self.spike_threshold, self.spike_location - ) else: raise ValueError("Found stimulus with pattern %s, not supported" % stimulus) logger.debug(f"Added {stimulus} to cell_id {cell_id} at {sec_name}({segx})") @@ -887,6 +910,26 @@ def fetch_cell_kwargs(self, cell_id: CellId) -> dict: def create_cell_from_circuit(self, cell_id: CellId) -> bluecellulab.Cell: """Create a Cell object from the circuit.""" + if self.circuit_format == CircuitFormat.SONATA: + try: + info = self.circuit_access.fetch_cell_info(cell_id) # type: ignore[attr-defined] + except AttributeError: + info = pd.Series() + + model_type = str(info.get("model_type", "")).lower() + model_template = str(info.get("model_template", "")) + + if model_type == "point_process": + mech_name = mechanism_name_from_model_template(model_template) + + # TODO (later): parse dynamics_params and feed param_overrides + return HocPointProcessCell( + cell_id=cell_id, + mechanism_name=mech_name, + param_overrides=None, + spike_threshold=self.spike_threshold, + ) + cell_kwargs = self.fetch_cell_kwargs(cell_id) return bluecellulab.Cell(template_path=cell_kwargs['template_path'], morphology_path=cell_kwargs['morphology_path'], diff --git a/bluecellulab/point/__init__.py b/bluecellulab/point/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bluecellulab/point/connection_params.py b/bluecellulab/point/connection_params.py new file mode 100644 index 00000000..3cf62d22 --- /dev/null +++ b/bluecellulab/point/connection_params.py @@ -0,0 +1,15 @@ +from __future__ import annotations +from dataclasses import dataclass + + +@dataclass +class PointProcessConnParameters: + """Point-neuron connection parameters (Allen-style / Neurodamus mirror).""" + + sgid: int # source gid + delay: float # ms + weight: float # NetCon weight + + # isec: int = -1 + # ipt: int = -1 + # offset: float = 0.5 \ No newline at end of file diff --git a/bluecellulab/point/point_connection.py b/bluecellulab/point/point_connection.py new file mode 100644 index 00000000..6501107a --- /dev/null +++ b/bluecellulab/point/point_connection.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from typing import Iterable, List, Optional + +from neuron import h + +from bluecellulab.point.connection_params import PointProcessConnParameters +from bluecellulab.cell.point_process import BasePointProcessCell + +pc = h.ParallelContext() + + +DEFAULT_SPIKE_THRESHOLD = 0.0 + + +class PointProcessConnection: + """Allen-style point connection: sgid -> PointNeuronCell.pointcell. + + Mirrors Neurodamus PointConnection: + - at most one synapse per connection + - uses pc.gid_connect(sgid, cell.pointcell) + - can later be extended with replay (VecStim) if needed. + """ + + def __init__( + self, + synapse_params: Iterable[PointProcessConnParameters], + weight_factor: float = 1.0, + syndelay_override: Optional[float] = None, + attach_src_cell: bool = True, + replay=None, # placeholder for future replay object + ) -> None: + self.synapse_params = list(synapse_params) + assert len(self.synapse_params) <= 1, ( + "PointProcessConnection supports max. one synapse per connection" + ) + + self.weight_factor = weight_factor + self.syndelay_override = syndelay_override + self.attach_src_cell = attach_src_cell + self._replay = replay + + self._netcons: List[h.NetCon] = [] + + @property + def netcons(self) -> list[h.NetCon]: + return self._netcons + + def finalize(self, cell: BasePointProcessCell) -> int: + """Create NetCon(s) onto the given point neuron cell. + + Returns + ------- + int + Number of synapses (0 or 1). + """ + n_syns = 0 + + for params in self.synapse_params: + n_syns += 1 + + if self.attach_src_cell: + # --- main path: presyn cell with sgid --- + nc = pc.gid_connect(params.sgid, cell.pointcell) + nc.delay = self.syndelay_override or float(params.delay) + nc.weight[0] = float(params.weight) * self.weight_factor + nc.threshold = DEFAULT_SPIKE_THRESHOLD + self._netcons.append(nc) + + # --- replay path (optional, stubbed) --- + if self._replay is not None and getattr(self._replay, "has_data", lambda: False)(): + vecstim = h.VecStim() + vecstim.play(self._replay.time_vec) + nc = h.NetCon( + vecstim, + cell.pointcell, + 10.0, + self.syndelay_override or float(params.delay), + float(params.weight), + ) + nc.weight[0] = float(params.weight) * self.weight_factor + self._replay._store(vecstim, nc) + + return n_syns \ No newline at end of file From 3a323c07ed6518e928de1202b017b1d35f9a62c4 Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Tue, 24 Mar 2026 10:15:09 +0100 Subject: [PATCH 02/15] Introduce support to detect edges for allen circuits The logic for loading from sonata files is to swap out the list of parameters if the BBP TYPE is not present Likewise, when instantiating a synapse object, if no TYPE field is present, assume it is for allen and create a Exp2Syn synapse --- .../circuit_access/sonata_circuit_access.py | 22 +++++++++- bluecellulab/circuit/synapse_properties.py | 10 ++++- bluecellulab/synapse/__init__.py | 2 +- bluecellulab/synapse/synapse_factory.py | 16 +++++-- bluecellulab/synapse/synapse_types.py | 42 +++++++++++++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) diff --git a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py index d7abbd5f..caf76fef 100644 --- a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +++ b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py @@ -180,7 +180,8 @@ def _select_edge_pop_names(self, projections) -> list[str]: def extract_synapses( self, cell_id: CellId, projections: Optional[list[str] | str] ) -> pd.DataFrame: - """Extract the synapses. + """Extract the synapses. Checks available fields to determine which are present in + the edge file to determine the properties to extract. If projections is None, all the synapses are extracted. """ @@ -201,7 +202,10 @@ def extract_synapses( # remove optional properties if they are not present for optional_property in [SynapseProperty.U_HILL_COEFFICIENT, - SynapseProperty.CONDUCTANCE_RATIO]: + SynapseProperty.CONDUCTANCE_RATIO, + SynapseProperty.AFFERENT_SECTION_POS, + SynapseProperty.POST_SEGMENT_ID, + SynapseProperty.POST_SEGMENT_OFFSET]: if optional_property.to_snap() not in edge_population.property_names: edge_properties.remove(optional_property) @@ -212,6 +216,20 @@ def extract_synapses( ): edge_properties += list(SynapseProperties.plasticity) + # check for allen instance - replace the entire edge_properties list as appropriate + # properties for allen point/chemical neuron connection type edges + if not SynapseProperty.TYPE in edge_population.property_names: + if all( + x in edge_population.property_names + for x in SynapseProperties.allen_point + ) and len(edge_population.property_names) == len(SynapseProperties.allen_point): + edge_properties = list(SynapseProperties.allen_point) + if all( + x in edge_population.property_names + for x in SynapseProperties.allen_chemical + ): + edge_properties = list(SynapseProperties.allen_chemical) + snap_properties = properties_to_snap(edge_properties) synapses: pd.DataFrame = edge_population.get(afferent_edges, snap_properties) column_names = list(synapses.columns) diff --git a/bluecellulab/circuit/synapse_properties.py b/bluecellulab/circuit/synapse_properties.py index 50d8779b..49158588 100644 --- a/bluecellulab/circuit/synapse_properties.py +++ b/bluecellulab/circuit/synapse_properties.py @@ -29,6 +29,7 @@ class SynapseProperty(Enum): PRE_GID = "pre_gid" AXONAL_DELAY = "axonal_delay" POST_SECTION_ID = "post_section_id" + POST_SECTION_POS = "post_section_pos" POST_SEGMENT_ID = "post_segment_id" POST_SEGMENT_OFFSET = "post_segment_offset" G_SYNX = "g_synx" @@ -83,7 +84,14 @@ class SynapseProperties: "volume_CR", "rho0_GB", "Use_d_TM", "Use_p_TM", "gmax_d_AMPA", "gmax_p_AMPA", "theta_d", "theta_p" ) - + allen_chemical = ( + "afferent_section_id", "afferent_section_pos", "conductance", "delay", "tau1", "tau2", "erev", + "@source_node" + ) + allen_point = ( + "afferent_section_id", "afferent_section_pos", "conductance", "delay", + "@source_node" + ) snap_to_synproperty = MappingProxyType({ "@source_node": SynapseProperty.PRE_GID, diff --git a/bluecellulab/synapse/__init__.py b/bluecellulab/synapse/__init__.py index 17aa8cde..12ba01d2 100644 --- a/bluecellulab/synapse/__init__.py +++ b/bluecellulab/synapse/__init__.py @@ -1,4 +1,4 @@ """Synapse and synapse parameters representation and operations.""" -from .synapse_types import Synapse, AmpanmdaSynapse, GabaabSynapse, GluSynapse +from .synapse_types import Synapse, AmpanmdaSynapse, GabaabSynapse, GluSynapse, Exp2Syn from .synapse_factory import SynapseFactory diff --git a/bluecellulab/synapse/synapse_factory.py b/bluecellulab/synapse/synapse_factory.py index a074ad15..54dd7644 100644 --- a/bluecellulab/synapse/synapse_factory.py +++ b/bluecellulab/synapse/synapse_factory.py @@ -24,14 +24,14 @@ import bluecellulab from bluecellulab.exceptions import BluecellulabError -from bluecellulab.synapse import Synapse, GabaabSynapse, AmpanmdaSynapse, GluSynapse +from bluecellulab.synapse import Synapse, GabaabSynapse, AmpanmdaSynapse, GluSynapse, Exp2Syn from bluecellulab.circuit.config.sections import Conditions from bluecellulab.circuit.synapse_properties import SynapseProperties, SynapseProperty from bluecellulab.synapse.synapse_types import SynapseHocArgs from bluecellulab.type_aliases import NeuronSection -SynapseType = Enum("SynapseType", "GABAAB AMPANMDA GLUSYNAPSE") +SynapseType = Enum("SynapseType", "GABAAB AMPANMDA GLUSYNAPSE ALLEN_CHEMICAL ALLEN_POINT") logger = logging.getLogger(__name__) @@ -65,10 +65,15 @@ def create_synapse( elif syn_type == SynapseType.AMPANMDA: synapse = AmpanmdaSynapse(cell.cell_id, syn_hoc_args, syn_id, syn_description, popids, extracellular_calcium) - else: + elif syn_type == SynapseType.GLUSYNAPSE: synapse = GluSynapse(cell.cell_id, syn_hoc_args, syn_id, syn_description, popids, extracellular_calcium) - + elif syn_type == SynapseType.ALLEN_CHEMICAL: + synapse = Exp2Syn(cell.cell_id, syn_hoc_args, syn_id, syn_description, + popids, extracellular_calcium) + elif syn_type == SynapseType.ALLEN_POINT: + synapse = Exp2Syn(cell.cell_id, syn_hoc_args, syn_id, syn_description, + popids, extracellular_calcium) synapse = cls.apply_connection_modifiers(connection_modifiers, synapse) return synapse @@ -88,6 +93,9 @@ def determine_synapse_type( syn_description: pd.Series, ) -> SynapseType: """Returns the type of synapse to be created.""" + if not SynapseProperty.TYPE in syn_description: + return SynapseType.ALLEN_POINT + is_inhibitory: bool = int(syn_description[SynapseProperty.TYPE]) < 100 all_plasticity_props_available: bool = all( x in syn_description and syn_description[x] is not None and not math.isnan(syn_description[x]) diff --git a/bluecellulab/synapse/synapse_types.py b/bluecellulab/synapse/synapse_types.py index a69fc47a..38ba6104 100644 --- a/bluecellulab/synapse/synapse_types.py +++ b/bluecellulab/synapse/synapse_types.py @@ -420,3 +420,45 @@ def info_dict(self): parent_dict = super().info_dict parent_dict['synapse_parameters']['tau_d_AMPA'] = self.hsynapse.tau_d_AMPA return parent_dict + +class Exp2Syn(Synapse): + + def __init__(self, gid, hoc_args, syn_id, syn_description, popids, extracellular_calcium): + self.persistent: list[HocObjectType] = [] + self.synapseconfigure_cmds: list[str] = [] + self._delay_weights: list[tuple[float, float]] = [] + self._weight: Optional[float] = None + + self.post_cell_id = gid + self.syn_id = SynapseID(*syn_id) + self.syn_description = syn_description + self.hsynapse: Optional[HocObjectType] = None + + self.source_popid, self.target_popid = popids + + self.pre_gid = int(self.syn_description[SynapseProperty.PRE_GID]) + + self.hoc_args = hoc_args + self.mech_name: str = "not-yet-defined" + self.use_exp2syn_helper() + + def use_exp2syn_helper(self) -> None: + """Helper for creating an Exp2Syn synapse.""" + self.mech_name = 'Exp2Syn' + + self.hsynapse = neuron.h.Exp2Syn( + self.hoc_args.location, + sec=self.hoc_args.section + ) + + self.hsynapse.tau1 = self.syn_description["tau1"] + self.hsynapse.tau2 = self.syn_description["tau2"] + self.hsynapse.e = self.syn_description["erev"] + + @property + def info_dict(self): + parent_dict = super().info_dict + parent_dict['synapse_parameters']['tau1'] = self.hsynapse.tau1 + parent_dict['synapse_parameters']['tau2'] = self.hsynapse.tau2 + parent_dict['synapse_parameters']['erev'] = self.hsynapse.e + return parent_dict From 988664b38f75135ed8ae9c7fe74acdd5a0b9c0a6 Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Thu, 26 Mar 2026 11:09:36 +0100 Subject: [PATCH 03/15] Minor fixes to pass lint checks --- bluecellulab/cell/point_process.py | 6 +++--- .../circuit/circuit_access/sonata_circuit_access.py | 2 +- bluecellulab/circuit/synapse_properties.py | 1 + bluecellulab/point/connection_params.py | 2 +- bluecellulab/point/point_connection.py | 2 +- bluecellulab/synapse/synapse_factory.py | 6 +++--- bluecellulab/synapse/synapse_types.py | 3 ++- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index aa4c7ed8..ffbb1bae 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -14,6 +14,7 @@ logger = logging.getLogger(__name__) + class BasePointProcessCell: """Base class for NEURON artificial point processes (IntFire1/2/...).""" @@ -30,7 +31,6 @@ def __init__(self, cell_id: Optional[CellId]) -> None: def hoc_cell(self): return self.pointcell - def init_callbacks(self): pass @@ -48,7 +48,6 @@ def delete(self) -> None: # Drop pointer to underlying NEURON object self.pointcell = None - def get_spike_times(self) -> list[float]: return list(self._spike_times) @@ -73,7 +72,6 @@ def start_recording_spikes(self, sec, location=None, threshold: float = 0.0) -> self._spike_detector.threshold = threshold self._spike_detector.record(self._spike_times) - def connect2target(self, target_pp=None) -> h.NetCon: """Neurodamus-like helper: NetCon from this cell to a target point process.""" return h.NetCon(self.pointcell, target_pp) @@ -166,6 +164,7 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s f"to point neuron {self.cell_id}" ) + def mechanism_name_from_model_template(model_template: str) -> str: """Translate SONATA model_template into a NEURON mechanism name. @@ -182,6 +181,7 @@ def mechanism_name_from_model_template(model_template: str) -> str: return name return mt + @dataclass class IntFire1Params: tau: float = 10.0 diff --git a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py index caf76fef..c0d8d508 100644 --- a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +++ b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py @@ -218,7 +218,7 @@ def extract_synapses( # check for allen instance - replace the entire edge_properties list as appropriate # properties for allen point/chemical neuron connection type edges - if not SynapseProperty.TYPE in edge_population.property_names: + if SynapseProperty.TYPE not in edge_population.property_names: if all( x in edge_population.property_names for x in SynapseProperties.allen_point diff --git a/bluecellulab/circuit/synapse_properties.py b/bluecellulab/circuit/synapse_properties.py index 49158588..cf7af979 100644 --- a/bluecellulab/circuit/synapse_properties.py +++ b/bluecellulab/circuit/synapse_properties.py @@ -93,6 +93,7 @@ class SynapseProperties: "@source_node" ) + snap_to_synproperty = MappingProxyType({ "@source_node": SynapseProperty.PRE_GID, "delay": SynapseProperty.AXONAL_DELAY, diff --git a/bluecellulab/point/connection_params.py b/bluecellulab/point/connection_params.py index 3cf62d22..71e58198 100644 --- a/bluecellulab/point/connection_params.py +++ b/bluecellulab/point/connection_params.py @@ -12,4 +12,4 @@ class PointProcessConnParameters: # isec: int = -1 # ipt: int = -1 - # offset: float = 0.5 \ No newline at end of file + # offset: float = 0.5 diff --git a/bluecellulab/point/point_connection.py b/bluecellulab/point/point_connection.py index 6501107a..44df0ae0 100644 --- a/bluecellulab/point/point_connection.py +++ b/bluecellulab/point/point_connection.py @@ -81,4 +81,4 @@ def finalize(self, cell: BasePointProcessCell) -> int: nc.weight[0] = float(params.weight) * self.weight_factor self._replay._store(vecstim, nc) - return n_syns \ No newline at end of file + return n_syns diff --git a/bluecellulab/synapse/synapse_factory.py b/bluecellulab/synapse/synapse_factory.py index 54dd7644..f1166f3b 100644 --- a/bluecellulab/synapse/synapse_factory.py +++ b/bluecellulab/synapse/synapse_factory.py @@ -70,10 +70,10 @@ def create_synapse( popids, extracellular_calcium) elif syn_type == SynapseType.ALLEN_CHEMICAL: synapse = Exp2Syn(cell.cell_id, syn_hoc_args, syn_id, syn_description, - popids, extracellular_calcium) + popids, extracellular_calcium) elif syn_type == SynapseType.ALLEN_POINT: synapse = Exp2Syn(cell.cell_id, syn_hoc_args, syn_id, syn_description, - popids, extracellular_calcium) + popids, extracellular_calcium) synapse = cls.apply_connection_modifiers(connection_modifiers, synapse) return synapse @@ -93,7 +93,7 @@ def determine_synapse_type( syn_description: pd.Series, ) -> SynapseType: """Returns the type of synapse to be created.""" - if not SynapseProperty.TYPE in syn_description: + if SynapseProperty.TYPE not in syn_description: return SynapseType.ALLEN_POINT is_inhibitory: bool = int(syn_description[SynapseProperty.TYPE]) < 100 diff --git a/bluecellulab/synapse/synapse_types.py b/bluecellulab/synapse/synapse_types.py index 38ba6104..5488b781 100644 --- a/bluecellulab/synapse/synapse_types.py +++ b/bluecellulab/synapse/synapse_types.py @@ -420,7 +420,8 @@ def info_dict(self): parent_dict = super().info_dict parent_dict['synapse_parameters']['tau_d_AMPA'] = self.hsynapse.tau_d_AMPA return parent_dict - + + class Exp2Syn(Synapse): def __init__(self, gid, hoc_args, syn_id, syn_description, popids, extracellular_calcium): From e3b12e9c173907daef6027e84147c8ee8ecfdcdf Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Wed, 22 Apr 2026 09:40:43 +0200 Subject: [PATCH 04/15] Intermediate steps to allow point process cells (wrapped in hoc) to be instantiated. The reporting adds try/except since these don't have a soma --- bluecellulab/cell/point_process.py | 13 +++++++------ bluecellulab/circuit_simulation.py | 2 +- bluecellulab/reports/utils.py | 9 ++++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index ffbb1bae..065dda7e 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -57,7 +57,7 @@ def create_netcon_spikedetector( location=None, # ignored for artificial cells threshold: float = 0.0, ) -> h.NetCon: - nc = h.NetCon(self.pointcell, None) + nc = h.NetCon(self.pointcell.pointcell, None) nc.threshold = threshold # harmless for artificial cells return nc @@ -68,13 +68,13 @@ def start_recording_spikes(self, sec, location=None, threshold: float = 0.0) -> if self._spike_detector is not None: return self._spike_times = h.Vector() - self._spike_detector = h.NetCon(self.pointcell, None) + self._spike_detector = h.NetCon(self.pointcell.pointcell, None) self._spike_detector.threshold = threshold self._spike_detector.record(self._spike_times) def connect2target(self, target_pp=None) -> h.NetCon: """Neurodamus-like helper: NetCon from this cell to a target point process.""" - return h.NetCon(self.pointcell, target_pp) + return h.NetCon(self.pointcell.pointcell, target_pp) class HocPointProcessCell(BasePointProcessCell): @@ -98,7 +98,7 @@ def __init__( "Make sure the mod/hoc files are compiled and loaded." ) from exc - point = mech_cls() + point = mech_cls(cell_id.id) if param_overrides: for name, value in param_overrides.items(): if hasattr(point, name): @@ -149,7 +149,7 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s vs = h.VecStim() vs.play(vec) - nc = h.NetCon(vs, self.pointcell) + nc = h.NetCon(vs, self.pointcell.pointcell) # Use stimulus weight if available, otherwise default to 1.0 weight = getattr(stimulus, "weight", 1.0) nc.weight[0] = weight @@ -165,7 +165,7 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s ) -def mechanism_name_from_model_template(model_template: str) -> str: +def mechanism_name_from_model_template(template_path: str, model_template: str) -> str: """Translate SONATA model_template into a NEURON mechanism name. Examples: @@ -178,6 +178,7 @@ def mechanism_name_from_model_template(model_template: str) -> str: prefix, name = mt.split(":", 1) prefix = prefix.lower() if prefix in ("hoc", "nrn"): + h.load_file( template_path ) return name return mt diff --git a/bluecellulab/circuit_simulation.py b/bluecellulab/circuit_simulation.py index 9d490dbf..33b0c513 100644 --- a/bluecellulab/circuit_simulation.py +++ b/bluecellulab/circuit_simulation.py @@ -1204,7 +1204,7 @@ def create_cell_from_circuit(self, cell_id: CellId) -> bluecellulab.Cell: model_template = str(info.get("model_template", "")) if model_type == "point_process": - mech_name = mechanism_name_from_model_template(model_template) + mech_name = mechanism_name_from_model_template(template_path=self.circuit_access.emodel_path(cell_id), model_template=model_template) # TODO (later): parse dynamics_params and feed param_overrides return HocPointProcessCell( diff --git a/bluecellulab/reports/utils.py b/bluecellulab/reports/utils.py index c69d7fc9..096a733c 100644 --- a/bluecellulab/reports/utils.py +++ b/bluecellulab/reports/utils.py @@ -142,7 +142,10 @@ def prepare_recordings_for_reports( cell.report_sites[report_name].append(entry) for cell_id, cell in cells.items(): - sec = cell.soma + try: + sec = cell.soma + except Exception: + continue sec_name = sec.name().split(".")[-1] segx = 0.5 rec_name = section_to_variable_recording_str(sec, segx, "v") @@ -388,6 +391,10 @@ def collect_local_payload( cell = cells.get(cell_id) if cell is None: continue + try: + cell.get_time() + except Exception: + continue recs: dict[str, list[float]] = {} for rec_name in recording_index.get(cell_id, []): From 7adda77db10a6fc2e22171567a3db1767b5d251c Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Fri, 24 Apr 2026 11:32:16 +0200 Subject: [PATCH 05/15] Corrections to load synapse data correctly from sonata edge files for target point neurons and deliver replay spikes to the proper netcon --- bluecellulab/cell/point_process.py | 17 +++++++++++++++++ .../circuit_access/sonata_circuit_access.py | 2 +- bluecellulab/circuit_simulation.py | 3 --- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index 065dda7e..a7cf6a86 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -7,6 +7,9 @@ from bluecellulab.circuit.simulation_access import get_synapse_replay_spikes from bluecellulab.exceptions import BluecellulabError +from bluecellulab.synapse import SynapseFactory, Synapse +from bluecellulab.circuit import SynapseProperty +#from bluecellulab.point.point_connection import PointProcessConnection from neuron import h import numpy as np @@ -164,6 +167,20 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s f"to point neuron {self.cell_id}" ) + def add_replay_synapse(self, syn_id, syn_description, syn_connection_parameters, condition_parameters, + popids, extracellular_calcium): + """ For Point Neurons, the replay simply queues events directly to the point obj + """ + from bluecellulab.point.point_connection import PointProcessConnection + from bluecellulab.point.connection_params import PointProcessConnParameters + + # syn_connection_parameters should only have 1 element, PointProcessConnection will confirm + point_params = PointProcessConnParameters( syn_description[SynapseProperty.PRE_GID], syn_description[SynapseProperty.PRE_GID], + syn_description[SynapseProperty.AXONAL_DELAY]) + + self.pointConn = PointProcessConnection([point_params]) + self.pointConn.finalize( self.pointcell ) + def mechanism_name_from_model_template(template_path: str, model_template: str) -> str: """Translate SONATA model_template into a NEURON mechanism name. diff --git a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py index 0c521b20..a96f1a74 100644 --- a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +++ b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py @@ -224,7 +224,7 @@ def extract_synapses( if all( x in edge_population.property_names for x in SynapseProperties.allen_point - ) and len(edge_population.property_names) == len(SynapseProperties.allen_point): + ): edge_properties = list(SynapseProperties.allen_point) if all( x in edge_population.property_names diff --git a/bluecellulab/circuit_simulation.py b/bluecellulab/circuit_simulation.py index 33b0c513..41a3a35f 100644 --- a/bluecellulab/circuit_simulation.py +++ b/bluecellulab/circuit_simulation.py @@ -614,21 +614,18 @@ def _add_cell_synapses( logger.warning( f"No presynaptic cells found for gid {cell_id}, no synapses added" ) - else: for idx, syn_description in syn_descriptions.iterrows(): popids = ( syn_description["source_popid"], syn_description["target_popid"], ) - self._instantiate_synapse( cell_id=cell_id, syn_id=idx, # type: ignore syn_description=syn_description, add_minis=add_minis, popids=popids, - ) logger.info(f"Added {syn_descriptions} synapses for gid {cell_id}") if add_minis: From c203180cfc271eeb5fc44e22c2cf895fe82d02c7 Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Fri, 24 Apr 2026 16:15:03 +0200 Subject: [PATCH 06/15] Introduce test for allen point neurons --- bluecellulab/cell/point_process.py | 12 +- .../ringtest_allen_v1/circuit_config.json | 64 ++++ .../ringtest_allen_v1/create_allen_data.py | 114 ++++++ tests/examples/ringtest_allen_v1/edges_A.h5 | Bin 0 -> 19496 bytes tests/examples/ringtest_allen_v1/edges_AB.h5 | Bin 0 -> 18680 bytes tests/examples/ringtest_allen_v1/edges_B.h5 | Bin 0 -> 18680 bytes tests/examples/ringtest_allen_v1/edges_BA.h5 | Bin 0 -> 19496 bytes .../ringtest_allen_v1/hoc/IntFire1_exc_1.hoc | 49 +++ .../ringtest_allen_v1/hoc/IntFire1_inh_1.hoc | 49 +++ tests/examples/ringtest_allen_v1/nodes_A.h5 | Bin 0 -> 13016 bytes tests/examples/ringtest_allen_v1/nodes_B.h5 | Bin 0 -> 12744 bytes .../examples/ringtest_allen_v1/nodesets.json | 6 + .../output/populations_offset.dat | 2 + .../reference/allen_v1_cellstate_0.json | 324 ++++++++++++++++++ .../ringtest_allen_v1/replay_RingA.h5 | Bin 0 -> 11768 bytes .../ringtest_allen_v1/replay_RingB.h5 | Bin 0 -> 11768 bytes .../ringtest_allen_v1/simulation_config.json | 60 ++++ .../.test_ringcells_allen_v1.py.swp | Bin 0 -> 12288 bytes .../test_allen_v1/test_ringcells_allen_v1.py | 47 +++ 19 files changed, 720 insertions(+), 7 deletions(-) create mode 100644 tests/examples/ringtest_allen_v1/circuit_config.json create mode 100755 tests/examples/ringtest_allen_v1/create_allen_data.py create mode 100644 tests/examples/ringtest_allen_v1/edges_A.h5 create mode 100644 tests/examples/ringtest_allen_v1/edges_AB.h5 create mode 100644 tests/examples/ringtest_allen_v1/edges_B.h5 create mode 100644 tests/examples/ringtest_allen_v1/edges_BA.h5 create mode 100644 tests/examples/ringtest_allen_v1/hoc/IntFire1_exc_1.hoc create mode 100644 tests/examples/ringtest_allen_v1/hoc/IntFire1_inh_1.hoc create mode 100644 tests/examples/ringtest_allen_v1/nodes_A.h5 create mode 100644 tests/examples/ringtest_allen_v1/nodes_B.h5 create mode 100644 tests/examples/ringtest_allen_v1/nodesets.json create mode 100644 tests/examples/ringtest_allen_v1/output/populations_offset.dat create mode 100644 tests/examples/ringtest_allen_v1/reference/allen_v1_cellstate_0.json create mode 100644 tests/examples/ringtest_allen_v1/replay_RingA.h5 create mode 100644 tests/examples/ringtest_allen_v1/replay_RingB.h5 create mode 100644 tests/examples/ringtest_allen_v1/simulation_config.json create mode 100644 tests/test_allen_v1/.test_ringcells_allen_v1.py.swp create mode 100644 tests/test_allen_v1/test_ringcells_allen_v1.py diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index a7cf6a86..09dfb68f 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -7,9 +7,7 @@ from bluecellulab.circuit.simulation_access import get_synapse_replay_spikes from bluecellulab.exceptions import BluecellulabError -from bluecellulab.synapse import SynapseFactory, Synapse from bluecellulab.circuit import SynapseProperty -#from bluecellulab.point.point_connection import PointProcessConnection from neuron import h import numpy as np @@ -168,18 +166,18 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s ) def add_replay_synapse(self, syn_id, syn_description, syn_connection_parameters, condition_parameters, - popids, extracellular_calcium): + popids, extracellular_calcium): """ For Point Neurons, the replay simply queues events directly to the point obj """ from bluecellulab.point.point_connection import PointProcessConnection from bluecellulab.point.connection_params import PointProcessConnParameters # syn_connection_parameters should only have 1 element, PointProcessConnection will confirm - point_params = PointProcessConnParameters( syn_description[SynapseProperty.PRE_GID], syn_description[SynapseProperty.PRE_GID], - syn_description[SynapseProperty.AXONAL_DELAY]) + point_params = PointProcessConnParameters(syn_description[SynapseProperty.PRE_GID], syn_description[SynapseProperty.PRE_GID], + syn_description[SynapseProperty.AXONAL_DELAY]) self.pointConn = PointProcessConnection([point_params]) - self.pointConn.finalize( self.pointcell ) + self.pointConn.finalize(self.pointcell) def mechanism_name_from_model_template(template_path: str, model_template: str) -> str: @@ -195,7 +193,7 @@ def mechanism_name_from_model_template(template_path: str, model_template: str) prefix, name = mt.split(":", 1) prefix = prefix.lower() if prefix in ("hoc", "nrn"): - h.load_file( template_path ) + h.load_file(template_path) return name return mt diff --git a/tests/examples/ringtest_allen_v1/circuit_config.json b/tests/examples/ringtest_allen_v1/circuit_config.json new file mode 100644 index 00000000..9bdc99cb --- /dev/null +++ b/tests/examples/ringtest_allen_v1/circuit_config.json @@ -0,0 +1,64 @@ +{ + "manifest": { + "$BASE_DIR": "." + }, + "networks": { + "nodes": [ + { + "nodes_file": "nodes_A.h5", + "populations": { + "RingA": { + "type": "biophysical", + "biophysical_neuron_models_dir": "$BASE_DIR/../ringtest/hoc", + "alternate_morphologies": { + "neurolucida-asc": "$BASE_DIR/../ringtest/morphologies/asc" + } + } + } + }, + { + "nodes_file": "nodes_B.h5", + "populations": { + "RingB": { + "type": "point_process", + "biophysical_neuron_models_dir": "$BASE_DIR/hoc" + } + } + } + ], + "edges": [ + { + "edges_file": "edges_A.h5", + "populations": { + "RingA__RingA__Exp2Syn_synapse": { + "type": "Exp2Syn_synapse" + } + } + }, + { + "edges_file": "edges_B.h5", + "populations": { + "RingB__RingB__point_process": { + "type": "point_process" + } + } + }, + { + "edges_file": "edges_AB.h5", + "populations": { + "RingA__RingB__point_process": { + "type": "point_process" + } + } + }, + { + "edges_file": "edges_BA.h5", + "populations": { + "RingB__RingA__Exp2Syn_synapse": { + "type": "Exp2Syn_synapse" + } + } + } + ] + } +} diff --git a/tests/examples/ringtest_allen_v1/create_allen_data.py b/tests/examples/ringtest_allen_v1/create_allen_data.py new file mode 100755 index 00000000..24f52ebd --- /dev/null +++ b/tests/examples/ringtest_allen_v1/create_allen_data.py @@ -0,0 +1,114 @@ +#!/bin/env python +# /// script +# dependencies = ['h5py', 'libsonata', 'numpy'] +# /// +# the above allows one to run `uv run create_data.py` without a virtualenv +import itertools as it +import sys +from pathlib import Path + +import h5py +import numpy as np + +# Add path for local imports +if __name__ == "__main__": + sys.path.append(str(Path(__file__).resolve().parent.parent)) +from utils import Edges, make_edges, make_nodes + + +def make_v1_nodes(): + wanted = { + "node_type_id": -1, + "model_template": "hoc:TestCell", + "model_type": "biophysical", + "mtype": ["MTYPE0", "MTYPE1", "MTYPE2"], + "etype": ["ETYPE0", "ETYPE1", "ETYPE2"], + "x": it.count(0), + "y": it.count(1), + "z": it.count(2), + "morphology": "cell_small", + } + make_nodes(filename="nodes_A.h5", name="RingA", count=3, wanted_attributes=wanted) + + wanted = { + "node_type_id": -1, + "model_template": ["hoc:IntFire1_exc_1", "hoc:IntFire1_inh_1", "hoc:IntFire1_exc_1"], + "model_type": "point_process", + "morphology": "None", + "mtype": ["MTYPE0", "MTYPE1", "MTYPE2"], + # "etype": ["ETYPE0", "ETYPE1", "ETYPE2"], + "x": it.count(0), + "y": it.count(1), + "z": it.count(2), + } + make_nodes(filename="nodes_B.h5", name="RingB", count=3, wanted_attributes=wanted) + + +def make_v1_edges(): + edges = Edges("RingA", "RingA", "Exp2Syn_synapse", [(0, 1), (1, 2), (2, 0)]) + wanted_attributes = { + "edge_type_id": -1, + "conductance": it.count(11.0), + "delay": it.count(13.0), + "tau1": it.count(14.0), + "tau2": it.count(15.0), + "erev": it.count(16.0), + "afferent_section_id": 1, + "afferent_section_pos": 0.75, + } + make_edges(filename="edges_A.h5", edges=edges, wanted_attributes=wanted_attributes) + + edges = Edges("RingB", "RingB", "point_process", [(0, 1), (1, 2), (2, 0)]) + wanted_attributes = { + "edge_type_id": -1, + "conductance": it.count(21.0), + "delay": it.count(23.0), + "afferent_section_id": 1, + "afferent_section_pos": 0.75, + } + make_edges(filename="edges_B.h5", edges=edges, wanted_attributes=wanted_attributes) + + edges = Edges("RingA", "RingB", "point_process", [(0, 0), (1, 1), (2, 2)]) + wanted_attributes = { + "edge_type_id": -1, + "conductance": 100.0, + "delay": 3.0, + "afferent_section_id": 1, + "afferent_section_pos": 0.75, + } + make_edges(filename="edges_AB.h5", edges=edges, wanted_attributes=wanted_attributes) + + edges = Edges("RingB", "RingA", "Exp2Syn_synapse", [(0, 0), (1, 1), (2, 2)]) + wanted_attributes = { + "edge_type_id": -1, + "conductance": 100.0, + "delay": it.count(13.0), + "tau1": it.count(14.0), + "tau2": it.count(15.0), + "erev": it.count(16.0), + "afferent_section_id": 1, + "afferent_section_pos": 0.75, + } + make_edges(filename="edges_BA.h5", edges=edges, wanted_attributes=wanted_attributes) + + +def make_replay_input_file(population): + node_ids = [0, 1, 2, 0, 1, 2, 0, 1, 2] + timestamps = [0.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0] + output_file = f"replay_{population}.h5" + with h5py.File(output_file, "w") as f: + spikes_group = f.create_group("spikes") + population_group = spikes_group.create_group(population) + sorting_dtype = h5py.enum_dtype({"none": 0, "by_id": 1, "by_time": 2}, basetype="uint8") + population_group.attrs["sorting"] = np.array(2, dtype=sorting_dtype) # by_time + population_group.create_dataset("node_ids", data=node_ids, dtype="uint64") + timestamps_dataset = population_group.create_dataset( + "timestamps", data=timestamps, dtype="float64" + ) + timestamps_dataset.attrs["units"] = "ms" + + +make_v1_nodes() +make_v1_edges() +make_replay_input_file("RingA") +make_replay_input_file("RingB") diff --git a/tests/examples/ringtest_allen_v1/edges_A.h5 b/tests/examples/ringtest_allen_v1/edges_A.h5 new file mode 100644 index 0000000000000000000000000000000000000000..19f1d2db4438ab95e5c7bc2d74f2d078ac7b49ee GIT binary patch literal 19496 zcmeHOJ8Tm{5ZyC`n14W^nEyfLGJu4_PdFk0BM?|Xk?yeUOJWI~t=K`NA{8=SO3IWe zC{j|oprCX~K|x8Gf`SfqZ{`uQ(fRVj_%j#X&Cc%Z@A=){&hF-2cKqJq1E&utb0w2% zuNn%2c>Llk{fJ_qBmD&j9F}#K%LxiN%KRyn;`n1+zHRsX>BNN6mM9;Rv#Do1=C}m$714j<;t`9z^l#&`AXH-M&R`bGQ`MdkDrVi4uf|t^A_dU zJdP=@ZcP@KRfN$*7QllC}}X5PZi<}unKayA`xaxayfgP`eCXOGiK71T123uB=KtC&-}iA zlA6cxs1??m!xWG2(8$5o-N;D-Zsa_Be1Ox#=KoICj z1fu*)d;S#XUrwGPc+x$Il5{E#0r;0IY&gSm^+H_EouzNxFG_{}&oTFc-g7XQtA>+m z_zgEB+CN%i|A-e-*j~?l|AaarKz`An8s)}(JKwrhIZj@3Tid~Z%G&Osl?};Dr>z7_ zgXC+vw%lgV0E5TAR?ljI?3ENcL$Ey!r!K?P+@lo*5oLyU&`6PI(!=u@wcx-jO z?knSf5f=m9A_5&L3+13VSE%KKg4tD4s_4JW&udxB&s{dUmf=d7KkL6TdVXq32ZNed z^$WF9IpAgKjq#PT*?tYb?M7zJV+U9J#=9R~!smU2EicgR{`#{zLO<7fJ|E&=($VKP zPUulF+<>T*D|0XNh7RhKUjH7_8O=>iqnQdfQ?HsU)wo0@Q(T6W9X{{heex(Q&s40h zzw0x4xRI(Q)-|!+ig;sVuEtT4@)QIF0YN|z5CjAPK|l}?1Ox#=KoAfF1cA;&z)}Bt zJtXzd*F$J+BC{*tM(rcp=jP(}k#pCnIqt5sN($*V1h9`h$qgTcn1_`7_Q3XZDBs#eq``Gx#Pxj=!|;pWw6J`qVb_7Wr%*yQWZc-6H|JH0#-&ZlF-HUH9Dh zU8j=;PJ$4^`FgDDZ`64oPxqX5A~GQc(dSB5ZxKi%B=~upbDLVe!*HpY`kl+y{_yl}l2MIgK?b>fOKiVTw5D)|e0YN|z5Ck@lK>O#F z?rmyaXU;35{B7I+i1W9b52%;!=6?#Y>>&iA{OuXbf8A|=yZ!HTuA|>)+fQs*_}i3o TIU@DYPU&yG{k&S}XkPsW79p0? literal 0 HcmV?d00001 diff --git a/tests/examples/ringtest_allen_v1/edges_AB.h5 b/tests/examples/ringtest_allen_v1/edges_AB.h5 new file mode 100644 index 0000000000000000000000000000000000000000..92e38cea790a64aafdc38accf446abddd3249325 GIT binary patch literal 18680 zcmeHOy;BoG6yFP=5n%*JhYvrvl1@fPMH>&~BZVUwq@X)8$pM*Ra+wfjq|(ZXUH*iW z6c&`0DO2(XSXfe8SkUS2zV|M&csZjX2JRh`d%JJne*Nxud%OFVkEw;F;las4p{`_7 z^otQ4q}va?PEw$uIHhl3z(QF;d60mBCDfk~DI=qg(CYCmkX9R|q+wyj0g!qWGrR26{7=KfJP}DZ^GK#;)*TA0n+x8>4 z6d6qZPGhes$RFzDYQC^F=ekPFyKd3X7s_t2C`1m|Na2dTu_a zbi|ikf3KAF+=8EjlyauD<&~T9d?A;YgGKQDqwl0S4oo9&X&f+lOzmKK8UafT0JaF6 zg)>{r0tF7IqA5qT{fHcZVGMS3fdew8awHN7oiT~!^jVc*sF7yO5EG(Gfu54UtASvC zU%3s#e)tU~&zKPvtc*e*6#&Y_xlS9|Iu*vx(}15QG1$ruB{~ zg=ow-e~u#r{0I+I%Jdh_A9RXrjHz-kMwt5l3@hY##=N7nzC1)6(kPGaoi8x$uZza@{BzWvl0FA@*=j&xcUre<7L)6DTien4DHZX8Q*v#xb$Pv>+C5%= z>WjX1IMCj~1{K_4X^AVa4}leRen0KYZ}~hVQo1LW^he*4dvSV0wdfc3b}|YUF(LQ! zMx=%MLDOL5B|MbdraGmIz;p*9|9D@1x$;Wap(>Wwul1?8_76!(dp6`g(hehKfLHwn zg;+8Ii~u9R2rvSS03*N%FanGKBftnS0*pYfA#nctA>Yq^KLqwBoQ^;n<`*;10Y&-6 z>?hD1yCbdQLM|Et6rXxzL4;I6jVx^DbFZeMN z!T%3zAa5z3$zxj;DzBq?kYsm)@$p5_$L)UhyFRTUu;BP_a9)`9_38X@Gmd^dnF~A- zj?N$MC$B%#2Eoa3h~328>bZPaJID)BgoG~NO?c$nB?-@zr4N2k? z;X`&L$&O^V?WtWX839Is5nu!u0Y)Gif$q;MzxDLIGHktVu0NvI+qEUY-j03*Y;g$@ y2wQK@8td0iueZDZKHJaX@y(oiioLo{tQz>AYP5{ls5o9WQ{o-(KCjX}&8wf^`GPqB literal 0 HcmV?d00001 diff --git a/tests/examples/ringtest_allen_v1/edges_B.h5 b/tests/examples/ringtest_allen_v1/edges_B.h5 new file mode 100644 index 0000000000000000000000000000000000000000..4dad4b42a903d6f9bb3efcfcc6850fd905c56b00 GIT binary patch literal 18680 zcmeHOy;BoG6yFP=5n&9D4j+DSC7q0piZ%x1BZVUjQqYA=azJL7yG#;hq|(ZXUH*iW zDJ)V_Qd05qQ5{~#UK2|Xk!J*kli6|#QXxJfZ7I8D>BrwhA5^uCybR*+@h$LY{3Hd{vT+O;$bIBx%oR@Wr$z0w`I|aq+2Ct{7K!E)4etBMTn0RL+Z|l#8 z!Q+7HLYwV34y@b+NOJgm2aN;qA@D!%{R=JNJ=$?|9H9488TmuqR`0Q7aWCg2vl(S0 zz7)LOeA-F6UIucCsr;5xZ0K1xla+%-{ryMpNpl>SLf+CiVDgyS!SXZ$mKXqR5jYFK zY%L26IGl=xjA{Rv9DrdAj_V2sq^3$lA`x9MiuLqarD3=teKCV3M41v@C4pB1_3!)A zeP|xSBWkE0je?!tL({jLcO#D36(l~@cZmA&Q*f}+@AG3!_y{Z}%1?Y8uGmcRjwywx z&I(bJzkU22NO?GGVS7y}MEp(T2TtTepUS3w#a-viGXjhNBftnS0-;5~FTOOtpMv7c z^>KjE{CW&6up#R>#g{eg_X^6RN0$pk>v7STj(y#pow&rhj}?!T1O# zQqW(BQa#lT35bjOWv}3xCgHX)cGBkI1fk z)NybJB%c`pMt~7u1Q-EEfDvE>7y(9r5nu!u0Y;$v5V-vLkYATR9|Cg|@wR~Lw=QPx z0}5IfvtK}S?6$OuE4gk6(7M<@mxP^_l#BZ;r5!xTPV;j%84T&i z-x6N8`Wy-b@%JM(*zY>VWm)Yp1pN4Wi2R}M;>O^lBx*k8!vDgkJgq3J-dGHKij_Tx zzp#!OssI1L2J)85nLM^trSdqM14(`-sE;jxJ#6Q*@AWASfd$)t>-&XiUZ3_4H*{Rb zlciuKBCgB(uah6V(hl{Va?ERZ7Ge2mi)^fN^9bqZD*UCOIH8%q2sDBb8K6>?kcM zDJ&={Q&>=@w6LJGq_CjV-FxpPWSPquenNiUF_*XR?c2ZazT4YBzD6gfd%K6ag}Q=4 zaY^*KLHhZNS9k_2q*MF?1`L#Wl-mIa7()FC5kh-E=CAwBy`P#A(v$KhiOIx%Cxn7z zVA)m(M5o4Ms=@QvYGUag@{GW+lS_71D#XVqMVwH2@i*E587?C){rLO)3ivaBPxv63 zJekhlFt#U;{GsN(PdQ5ymZikFWlep`jDA>mtn9iI&tz@sM7$oM1U~Y^yV*&_Vd9;M zyrq0}9{Xh1w$WbwK&%TO$>96#*AGN`!2f@rU)uuSq7B#k0s1~|B7dma>N{rT)-$%1 zN-7)iC7WJbP1u%`PC`sBzPe=R%6iI4rV{cCNbfg&C-r_HjJzd3oySxUmX!z?q7$%1 z;0zqu8U`4!dlhBb=lb`_4jB62h?`-j)KrQU&f0s(yIvG9Hq%<#m}Kj7J3? z#TLb**7dqyhy(Sw)X{BHpdn= z8VvzMocDT2=-k&sU~M9DCeV8ABkSkp{PvM!PoO&Hnbe9iX*L9CA9)@tK1LGHT~d4S zJaR5=!AWGYqo>_K zfoz-Z_xN``?6$!O077)Wp4Rn`>$s06Q^8I|#7*yguH@jk>rnbn3(#(0YE|l&x$TrZ zdkqnF{?&Q)?P>K2m>7L9S>3N)W9dC3NjxG+d?I|vk0kk#thYbaizOq#2rvSS03*N% z_#;sNai#eJYBf?(xkedRdd;`>`G?=xtH-prUVmO~G&HaN0p^gHZ~y=R literal 0 HcmV?d00001 diff --git a/tests/examples/ringtest_allen_v1/hoc/IntFire1_exc_1.hoc b/tests/examples/ringtest_allen_v1/hoc/IntFire1_exc_1.hoc new file mode 100644 index 00000000..7c6a26c5 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/hoc/IntFire1_exc_1.hoc @@ -0,0 +1,49 @@ + +{load_file("stdrun.hoc")} +{load_file("import3d.hoc")} + +begintemplate IntFire1_exc_1 + public CellRef, pointcell + objref this, CellRef, pointcell + +proc init() { + CellRef = this + gid = $1 + biophys() + channel_seed_set = 0 + re_init_rng() +} + +proc biophys() { + // https://nrn.readthedocs.io/en/latest/progref/modelspec/programmatic/mechanisms/mech.html#IntFire1 + pointcell = new IntFire1() + pointcell.tau = 24.0 + pointcell.refrac = 3.0 +} + +proc re_init_rng() {localobj sf + strdef full_str, name + sf = new StringFunctions() + if(numarg() == 1) { + // We received a third seed + channel_seed = $1 + channel_seed_set = 1 + } else { + channel_seed_set = 0 + } +} + +obfunc getCell(){ + return this +} + +proc setCCell(){ + CellRef = $o1 +} + +proc clear() { localobj nil + CellRef = nil + pointcell = nil +} + +endtemplate IntFire1_exc_1 diff --git a/tests/examples/ringtest_allen_v1/hoc/IntFire1_inh_1.hoc b/tests/examples/ringtest_allen_v1/hoc/IntFire1_inh_1.hoc new file mode 100644 index 00000000..0c864980 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/hoc/IntFire1_inh_1.hoc @@ -0,0 +1,49 @@ + +{load_file("stdrun.hoc")} +{load_file("import3d.hoc")} + +begintemplate IntFire1_inh_1 + public CellRef, pointcell + objref this, CellRef, pointcell + +proc init() { + CellRef = this + gid = $1 + biophys() + channel_seed_set = 0 + re_init_rng() +} + +proc biophys() { + // https://nrn.readthedocs.io/en/latest/progref/modelspec/programmatic/mechanisms/mech.html#IntFire1 + pointcell = new IntFire1() + pointcell.tau = 7.0 + pointcell.refrac = 3.0 +} + +proc re_init_rng() {localobj sf + strdef full_str, name + sf = new StringFunctions() + if(numarg() == 1) { + // We received a third seed + channel_seed = $1 + channel_seed_set = 1 + } else { + channel_seed_set = 0 + } +} + +obfunc getCell(){ + return this +} + +proc setCCell(){ + CellRef = $o1 +} + +proc clear() { localobj nil + CellRef = nil + pointcell = nil +} + +endtemplate IntFire1_inh_1 diff --git a/tests/examples/ringtest_allen_v1/nodes_A.h5 b/tests/examples/ringtest_allen_v1/nodes_A.h5 new file mode 100644 index 0000000000000000000000000000000000000000..d19d89d82b65d36778ebaac8dfcbdaf6cd61aed5 GIT binary patch literal 13016 zcmeHNzf)5|5WW{cBZ`K?;fFe=q*0@lV`CVIzCtB~6j%j>49QGFGA0GFk;3BHQCd<` zQd(M4Qd(O2U+C`J-Q;CwoQZ4< zE`PcFdPXgRMZd-sk7dB}AVC$+;h%HU+&;%LdJG&v)BM2{^*OMM;Yh8{s;|NV!u4efB)Pdd-LBOAH|X#%C49cw|H|-aCqM>?o$75*pp^OSoh^G7ELYD0j1HCv&(1^(elr zFmDcMwPFky1IB^VjIptlY8x+BijC$%u~I>tzzdW(z2u+b^ceeNku=Q5 za;;u!HOd?7;qZ{X8(fH7bU7z0rT`j^+q3`Jh2S3V}w58*fLSki7P>88Fg$(?+%{=Ll_ k^C6i7?%)P1&(%LZD#i!6KT~L2OKUb}21W zrlhpAOqoBy(*Hu=-tLBD9R|z@0oj*(Vq9C za}-7YI|K3@NYH{$$bWeK@H>`o9M#->X&>5>q~QB@oelex@*G$pzvcdq;$H4|VPEA|J0{fV~;qjhN1)R-T1Xn~Nk>J88^)u!8^GGT`F(WeOgexN@ z(XS$x&-ea4oF36*T7)m+*v{u(^43T#~@s5;t?acg#I6d&GFos9pFb2-SdD#Aj z!0@OM)=yguAGU`hD2!pR!WfPW=#%Z?r!0(NOT!q0O513TpWClgTc&IsFvw%5Yf%Q2 z0cAiLPzENFfo}1oe17T`U*?vOz93mPkxWkZqB*{75e=L#{r+IEy!?G!v>3!cOEzwb z*{Q(?V+M>6+VK%Kx8tKzijR_FLB7D(3o$O5z0A91WH=|L$LiB%p%8F|aO=KM2)Itz ze=HONrU-w1A`}8<2%oJAg@9SY-{kfPyME#D{OV#X{Azf7m^-*fawA9UK4U+s*nC+z z-*79n^2dg23}KrMyK8y3S;*7-*a)hFJis4zrD~DwI3LxkKZy6C(9K|KUa8v1*Xmxu zJ%smakVAmdO%L%ulx})TI|g|Lu-o#g!45?M*uvv;JEuiuJLeX7ymz}FvgxPBa9XLO z-Eu`~_aHw3;{^60*Y?TVL8nC-PzIC%Wk4BF29yD1Kp9X5lmTTx85k4;!^`U=$0DzD yRX!FHk0^(bF!88~f2Iv<=ac3C+w2h^oFU*H9<4mr@c1YzAJzrh?;Dz9!N+gk=l!Pu literal 0 HcmV?d00001 diff --git a/tests/examples/ringtest_allen_v1/nodesets.json b/tests/examples/ringtest_allen_v1/nodesets.json new file mode 100644 index 00000000..47496548 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/nodesets.json @@ -0,0 +1,6 @@ +{ + "RingA": {"population": "RingA", "node_id": [0,1,2]}, + "RingB": {"population": "RingB", "node_id": [0,1,2]}, + "Mosaic": {"population": ["RingA", "RingB"]} +} + diff --git a/tests/examples/ringtest_allen_v1/output/populations_offset.dat b/tests/examples/ringtest_allen_v1/output/populations_offset.dat new file mode 100644 index 00000000..53c6bdb0 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/output/populations_offset.dat @@ -0,0 +1,2 @@ +RingA::0::RingA +RingB::1000::RingB diff --git a/tests/examples/ringtest_allen_v1/reference/allen_v1_cellstate_0.json b/tests/examples/ringtest_allen_v1/reference/allen_v1_cellstate_0.json new file mode 100644 index 00000000..79f1d4d8 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/reference/allen_v1_cellstate_0.json @@ -0,0 +1,324 @@ +{ + "TestCell": { + "gid": 0.0, + "nSecAll": 3.0, + "nSecApical": 0.0, + "nSecAxonal": 0.0, + "nSecAxonalOrig": 0.0, + "nSecBasal": 2.0, + "nSecSoma": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0, + "sections": [ + { + "name": "soma[0]", + "L": 25.68196246353438, + "Ra": 100.0, + "nseg": 1, + "rallbranch": 1.0, + "segments": [ + { + "x": 0.5, + "cm": 1.0, + "diam": 20.87400377077349, + "hh": { + "el": -54.3, + "gk": 0.00036664445561, + "gkbar": 0.036, + "gl": 0.0003, + "gna": 1.060919284e-05, + "gnabar": 0.12, + "h": 0.59612075350846, + "hinf": 0.59612075350846, + "htau": 0.36382974385675, + "il": -0.00321, + "m": 0.05293248525725, + "minf": 0.05293248525725, + "mtau": 0.01011539736258, + "n": 0.3176769140607, + "ninf": 0.3176769140607, + "ntau": 0.23320725203629 + }, + "k_ion": { + "dik_dv_": 0.00036664445561, + "ek": -77.0, + "ik": 0.00439973346728, + "ki": 54.4, + "ko": 2.5 + }, + "na_ion": { + "dina_dv_": 1.060919284e-05, + "ena": 50.0, + "ina": -0.00122005717647, + "nai": 10.0, + "nao": 140.0 + }, + "pas": { + "e": -70.0, + "g": 0.0, + "i": 0.0 + }, + "v": -65.0 + } + ] + }, + { + "name": "dend[0]", + "L": 200.0, + "Ra": 100.0, + "nseg": 2, + "rallbranch": 1.0, + "segments": [ + { + "x": 0.25, + "cm": 1.0, + "diam": 1.0, + "hh": { + "el": -54.3, + "gk": 0.00036664445561, + "gkbar": 0.036, + "gl": 0.0003, + "gna": 1.060919284e-05, + "gnabar": 0.12, + "h": 0.59612075350846, + "hinf": 0.59612075350846, + "htau": 0.36382974385675, + "il": -0.00321, + "m": 0.05293248525725, + "minf": 0.05293248525725, + "mtau": 0.01011539736258, + "n": 0.3176769140607, + "ninf": 0.3176769140607, + "ntau": 0.23320725203629 + }, + "k_ion": { + "dik_dv_": 0.00036664445561, + "ek": -77.0, + "ik": 0.00439973346728, + "ki": 54.4, + "ko": 2.5 + }, + "na_ion": { + "dina_dv_": 1.060919284e-05, + "ena": 50.0, + "ina": -0.00122005717647, + "nai": 10.0, + "nao": 140.0 + }, + "pas": { + "e": -65.0, + "g": 0.001, + "i": 0.0 + }, + "v": -65.0 + }, + { + "x": 0.75, + "cm": 1.0, + "diam": 1.0, + "hh": { + "el": -54.3, + "gk": 0.00036664445561, + "gkbar": 0.036, + "gl": 0.0003, + "gna": 1.060919284e-05, + "gnabar": 0.12, + "h": 0.59612075350846, + "hinf": 0.59612075350846, + "htau": 0.36382974385675, + "il": -0.00321, + "m": 0.05293248525725, + "minf": 0.05293248525725, + "mtau": 0.01011539736258, + "n": 0.3176769140607, + "ninf": 0.3176769140607, + "ntau": 0.23320725203629 + }, + "k_ion": { + "dik_dv_": 0.00036664445561, + "ek": -77.0, + "ik": 0.00439973346728, + "ki": 54.4, + "ko": 2.5 + }, + "na_ion": { + "dina_dv_": 1.060919284e-05, + "ena": 50.0, + "ina": -0.00122005717647, + "nai": 10.0, + "nao": 140.0 + }, + "pas": { + "e": -65.0, + "g": 0.001, + "i": 0.0 + }, + "v": -65.0 + } + ] + }, + { + "name": "dend[1]", + "L": 10.0, + "Ra": 100.0, + "nseg": 2, + "rallbranch": 1.0, + "segments": [ + { + "x": 0.25, + "cm": 1.0, + "diam": 1.0, + "hh": { + "el": -54.3, + "gk": 0.00036664445561, + "gkbar": 0.036, + "gl": 0.0003, + "gna": 1.060919284e-05, + "gnabar": 0.12, + "h": 0.59612075350846, + "hinf": 0.59612075350846, + "htau": 0.36382974385675, + "il": -0.00321, + "m": 0.05293248525725, + "minf": 0.05293248525725, + "mtau": 0.01011539736258, + "n": 0.3176769140607, + "ninf": 0.3176769140607, + "ntau": 0.23320725203629 + }, + "k_ion": { + "dik_dv_": 0.00036664445561, + "ek": -77.0, + "ik": 0.00439973346728, + "ki": 54.4, + "ko": 2.5 + }, + "na_ion": { + "dina_dv_": 1.060919284e-05, + "ena": 50.0, + "ina": -0.00122005717647, + "nai": 10.0, + "nao": 140.0 + }, + "pas": { + "e": -65.0, + "g": 0.001, + "i": 0.0 + }, + "v": -65.0 + }, + { + "x": 0.75, + "cm": 1.0, + "diam": 1.0, + "hh": { + "el": -54.3, + "gk": 0.00036664445561, + "gkbar": 0.036, + "gl": 0.0003, + "gna": 1.060919284e-05, + "gnabar": 0.12, + "h": 0.59612075350846, + "hinf": 0.59612075350846, + "htau": 0.36382974385675, + "il": -0.00321, + "m": 0.05293248525725, + "minf": 0.05293248525725, + "mtau": 0.01011539736258, + "n": 0.3176769140607, + "ninf": 0.3176769140607, + "ntau": 0.23320725203629 + }, + "k_ion": { + "dik_dv_": 0.00036664445561, + "ek": -77.0, + "ik": 0.00439973346728, + "ki": 54.4, + "ko": 2.5 + }, + "na_ion": { + "dina_dv_": 1.060919284e-05, + "ena": 50.0, + "ina": -0.00122005717647, + "nai": 10.0, + "nao": 140.0 + }, + "pas": { + "e": -65.0, + "g": 0.001, + "i": 0.0 + }, + "v": -65.0 + } + ] + } + ], + "n_synapses": 2, + "synapses": [ + { + "name": "Exp2Syn[0]", + "A": 0.0, + "B": 0.0, + "e": 18.0, + "g": 0.0, + "i": -0.0, + "tau1": 16.0, + "tau2": 17.0, + "location": 0.75, + "segment": "dend[0](0.75)" + }, + { + "name": "Exp2Syn[3]", + "A": 0.0, + "B": 0.0, + "e": 16.0, + "g": 0.0, + "i": -0.0, + "tau1": 14.0, + "tau2": 15.0, + "location": 0.75, + "segment": "dend[0](0.75)" + } + ], + "n_netcons": 3, + "netcons": [ + { + "name": "NetCon[3]", + "target_syn": "Exp2Syn[0]", + "target_syn_segment": "dend[0](0.75)", + "afferent_section_position": 0.75, + "srcgid": 2, + "active": true, + "weight": 13.0, + "delay": 15.0, + "threshold": -30.0, + "x": -65.0 + }, + { + "name": "NetCon[12]", + "target_syn": "Exp2Syn[3]", + "target_syn_segment": "dend[0](0.75)", + "afferent_section_position": 0.75, + "srcgid": 1000, + "active": true, + "weight": 100.0, + "delay": 13.0, + "threshold": -30.0, + "x": 0.0 + }, + { + "name": "NetCon[13]", + "target_syn": "Exp2Syn[3]", + "target_syn_segment": "dend[0](0.75)", + "afferent_section_position": 0.75, + "srcgid": -1, + "active": true, + "weight": 100.0, + "delay": 13.0, + "threshold": 10.0, + "x": 0.0 + } + ] + } +} \ No newline at end of file diff --git a/tests/examples/ringtest_allen_v1/replay_RingA.h5 b/tests/examples/ringtest_allen_v1/replay_RingA.h5 new file mode 100644 index 0000000000000000000000000000000000000000..01f0e7570f532c99f7e62e38ce6f3782490bb0ca GIT binary patch literal 11768 zcmeI0y>1gh5XWcF1dhakL<*8YLP|lA21wzC2F}8UD-ekS1sxxX1t;XQ6`LYm%FyKz zC@Fai9)XgQM{pN*c4kiYNvBgHkO=;-rJLQ^xw+flj%UaIoOYkAF5g-vZgEVPXf1T@ za;j~5S2Q-Z`8V2eDvwoujilptPL&n4-_rhr^10%@UYiJfC>dX8+-<2 zrWl%^`KMs?zY{3ufh}F|iJlL({(QLKf1U07lT6wq8}!GMw}X*t?EAZI1~syr2ez)r z4d!Q8$D`7F)VN)^X{%q$yqXlslaOie`)oTN9!z{Hr?Ln2 z<-@$sAf2yeY(K25akL~lop4%7J0}X;E|U%aB%a~tNzPAvkGz*S=%XkKH^%Cx;ijv> zfKm~~^`z+iOC5>hs5sBQX#TBwN6WL*8&wvIt=F(3i#MZjR=wP}k%+vb@}A1J$_FYR ziahG*xT})TKmY_l00ck)1pXocN0-l7SGI!v`sSmeu57PR`&9owsID)QYqE^_`m#w$ zQ1g3ic#og*Y{EZnZu+{GYuDL&nGI*_k?C#Lm@Wx@TtEN>KmY_l00im@Jbk?LES#x) u(E2uezVdib{PV~oq94~q;tB#F00JNY0w4eaAOHd&00JNY0w8d42>b?N(1lF^ literal 0 HcmV?d00001 diff --git a/tests/examples/ringtest_allen_v1/replay_RingB.h5 b/tests/examples/ringtest_allen_v1/replay_RingB.h5 new file mode 100644 index 0000000000000000000000000000000000000000..851c263aa86513c821c8461f5bc284c6a4028f97 GIT binary patch literal 11768 zcmeI0y>1gh5XWcF1dhZZi4-J*gp`6J4UobOO}Ky!S0EAv3OYU%3r@&q3!8$DGIV(a zN=hDsM@X5HM{pN*c4kiYNvBgHkO=;-rJLQ^xw+flj%UaInsy#9FWy`vZgEVPXf<^0 za;j}|M>ICJ`PbTUDi2kDkEG*>KSV9nuc`mC-HZKhm%OFtPL$%6D^Q$WrYfwa4` z#|wU`tF|cpsc~j7M#JnwZ^V%#Ewzbr%BuKH8{+W2#${dn{<!!c}ex1n)c_%Fq=y$9qh0Y{CnCw7jl>N&QxBz8kJuge2J znqp{vW}kx5|4yKs2R3!VM|wWk`t!j-?^Sl-Pcmuatlt}r-}Hy7vG4De8Pv#f9@xAh zH<+J29S=+IQR8;*rmcQ0^J-EkPeP`>=dMe)c=-$6oTb zQa;Fg4AS{p#`eR?8b=GF(+Q`Qv~!}c?K0W$PvRMVp5*+*_sDyRgFcF)aAT}~8g8;2 z3@8;*T#t(0ztoX9j*9c_o95rDceFe^xn5;4-+B!zvUoEZXVuGn>xszQD(|XntGusr zTjZA=9d}d`8VG;@2!H?xfWTiQ;OO!h>&jNJU)y+4)RpZeYM<)=2i5gua!r;oTVFOQ z32J_ib?@<0o{jmZ%?)4Ia_u@@FSFrvJu(@M0C6&5?2ra0T2KI5C8!X009sH0T2KI5CDOTL*O@xG=)w8 literal 0 HcmV?d00001 diff --git a/tests/examples/ringtest_allen_v1/simulation_config.json b/tests/examples/ringtest_allen_v1/simulation_config.json new file mode 100644 index 00000000..3bae6b9b --- /dev/null +++ b/tests/examples/ringtest_allen_v1/simulation_config.json @@ -0,0 +1,60 @@ +{ + "node_sets_file": "nodesets.json", + "network": "circuit_config.json", + "node_set": "RingB", + "target_simulator": "NEURON", + "run": { + "tstop": 50.0, + "dt": 0.1, + "random_seed": 1122 + }, + "conditions": { + "celsius": 35, + "v_init": -65 + }, + "connection_overrides": [ + { + "name": "biophysical_biophysical", + "source": "RingA", + "target": "RingA", + "weight": 1, + "modoverride": "Exp2Syn" + }, + { + "name": "point_biophysical", + "source": "RingB", + "target": "RingA", + "weight": 1, + "modoverride": "Exp2Syn" + } + ], + "inputs": { + "RingA_spikes": { + "input_type": "spikes", + "module": "synapse_replay", + "delay": 0, + "duration": 100, + "spike_file": "replay_RingA.h5", + "node_set": "RingB" + }, + "RingB_spikes": { + "input_type": "spikes", + "module": "synapse_replay", + "delay": 0, + "duration": 100, + "spike_file": "replay_RingB.h5", + "node_set": "RingA" + } + }, + "reports": { + "compartment_report": { + "type": "compartment", + "sections": "all", + "cells": "RingA", + "variable_name": "v", + "dt": 0.1, + "start_time": 0.0, + "end_time": 100.0 + } + } +} diff --git a/tests/test_allen_v1/.test_ringcells_allen_v1.py.swp b/tests/test_allen_v1/.test_ringcells_allen_v1.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..c1c0c49e5c6af232aa51b57a88f8532301807cbe GIT binary patch literal 12288 zcmeI2KWrRD6voHt2m}!H=&_EJJ>=~9oJ4@7D@X#-2~4cmrm?Km-0iGylD*y4%$&_( z8Kfi<64dl`bcmi7YAB!x1tmzNf)a$pH?zBUImcjH@T~ORdS~9eKfgD((#g|bey6ua zuSOk)>siJ=d+(wA@P$v#+~5oBj?8!M@JZJiADm?3=HIst+v}>hEe1xnZ(i-SwH%Ez zZe)?Sd6o%$ma229eKi}4Ya+`q*KZr4VQoKSXA|#rswV|^16b+lcvMdQRVLoZ6F6lA z#wNY^$~ksvrL$}+UkqNP=bzg;We|M1o`5Id33vjYfG6Mycmkfl=_X*x1@-|DoT)Li z>-)lyd;REdo`5Id33vjYfG6Mycmke)C*TQq0-k^;@E;_=6UNRy!Pt+G{r`XS`~T-> z82btO0eS%4hwedlp#-`DU55UAnz65;ub@w%yU-@I0R_-+=NS7HdH~&r?m-#!64Zje ze~Pj1pf8}0p^u;;)P){C$=EN@=a7aJbP-yD{y59n@6flrkq8|PuTUy8 z!Cdr)1a;|Pd6iZ!N6SmpS*4fleWe;WtW0hGGS`OZMk1GZS0=hCQ^zPeHbbdn#q&F@ zYDLZ9$Rt8-hhTqeEEhW>IRKaKpE!PdO;;YzOt) zV8>NAi*n+^nJLz~#e_?7sRg|$Ca)`1s4xxQMs5!Dg@d`!hZm5TK^$HbxUgFxGAaG6*th$SyHao&S~$up5-^q!NNd|w{?+6S;3PqtwOr}Qj{VO z8&h-x-ER@LMY>vfnxBFj95s#yMV`vtxX-nS6RD_eN9&$7ldc67b6czP+~8}R#;#K5 z^O)7qt}x-$L$E}_Ygty~S|rP$*|-)^mF~^0&GoI#_ Date: Fri, 24 Apr 2026 16:38:45 +0200 Subject: [PATCH 07/15] Fix lint --- bluecellulab/cell/point_process.py | 29 +++++++++++++----- .../circuit_access/sonata_circuit_access.py | 4 +-- .../.test_ringcells_allen_v1.py.swp | Bin 12288 -> 0 bytes 3 files changed, 23 insertions(+), 10 deletions(-) delete mode 100644 tests/test_allen_v1/.test_ringcells_allen_v1.py.swp diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index 09dfb68f..32b61a02 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import Any, Mapping, Optional +from bluecellulab.cell import Cell from bluecellulab.circuit.simulation_access import get_synapse_replay_spikes from bluecellulab.exceptions import BluecellulabError from bluecellulab.circuit import SynapseProperty @@ -16,10 +17,12 @@ logger = logging.getLogger(__name__) -class BasePointProcessCell: +class BasePointProcessCell(Cell): """Base class for NEURON artificial point processes (IntFire1/2/...).""" def __init__(self, cell_id: Optional[CellId]) -> None: + if cell_id is None: + raise ValueError("PointProcessCell requires valid cell_id") self.cell_id = cell_id self._spike_times = h.Vector() @@ -58,6 +61,8 @@ def create_netcon_spikedetector( location=None, # ignored for artificial cells threshold: float = 0.0, ) -> h.NetCon: + if self.pointcell is None: + raise ValueError("attempting to create netcon without valid pointprocess") nc = h.NetCon(self.pointcell.pointcell, None) nc.threshold = threshold # harmless for artificial cells return nc @@ -68,6 +73,8 @@ def is_recording_spikes(self, location=None, threshold: float | None = None) -> def start_recording_spikes(self, sec, location=None, threshold: float = 0.0) -> None: if self._spike_detector is not None: return + if self.pointcell is None: + raise ValueError("attempting to record spikes without valid pointprocess") self._spike_times = h.Vector() self._spike_detector = h.NetCon(self.pointcell.pointcell, None) self._spike_detector.threshold = threshold @@ -75,12 +82,13 @@ def start_recording_spikes(self, sec, location=None, threshold: float = 0.0) -> def connect2target(self, target_pp=None) -> h.NetCon: """Neurodamus-like helper: NetCon from this cell to a target point process.""" + if self.pointcell is None: + raise ValueError("call to connect2target without valid pointprocess") return h.NetCon(self.pointcell.pointcell, target_pp) class HocPointProcessCell(BasePointProcessCell): - """Point process that wraps an arbitrary HOC/mod artificial mechanism. - """ + """Point process that wraps an arbitrary HOC/mod artificial mechanism.""" def __init__( self, @@ -99,6 +107,8 @@ def __init__( "Make sure the mod/hoc files are compiled and loaded." ) from exc + if cell_id is None: + raise ValueError("call to create pointprocess mechanism without valid cell_id") point = mech_cls(cell_id.id) if param_overrides: for name, value in param_overrides.items(): @@ -111,9 +121,10 @@ def __init__( def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: str) -> None: """SONATA-style spike replay for point processes. - This is a simplified analogue of Cell.add_synapse_replay, but instead of - mapping spikes to individual synapses, we directly connect each presynaptic - node_id's spike train to this artificial cell via VecStim → NetCon. + This is a simplified analogue of Cell.add_synapse_replay, but + instead of mapping spikes to individual synapses, we directly + connect each presynaptic node_id's spike train to this + artificial cell via VecStim → NetCon. """ file_path = Path(stimulus.spike_file).expanduser() @@ -150,6 +161,8 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s vs = h.VecStim() vs.play(vec) + if self.pointcell is None: + raise ValueError("attempting to add replay spikes with valid pointprocess") nc = h.NetCon(vs, self.pointcell.pointcell) # Use stimulus weight if available, otherwise default to 1.0 weight = getattr(stimulus, "weight", 1.0) @@ -167,8 +180,8 @@ def add_synapse_replay(self, stimulus, spike_threshold: float, spike_location: s def add_replay_synapse(self, syn_id, syn_description, syn_connection_parameters, condition_parameters, popids, extracellular_calcium): - """ For Point Neurons, the replay simply queues events directly to the point obj - """ + """For Point Neurons, the replay simply queues events directly to the + point obj.""" from bluecellulab.point.point_connection import PointProcessConnection from bluecellulab.point.connection_params import PointProcessConnParameters diff --git a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py index a96f1a74..8794e6d0 100644 --- a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +++ b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py @@ -182,8 +182,8 @@ def _select_edge_pop_names(self, projections) -> list[str]: def extract_synapses( self, cell_id: CellId, projections: Optional[list[str] | str] ) -> pd.DataFrame: - """Extract the synapses. Checks available fields to determine which are present in - the edge file to determine the properties to extract. + """Extract the synapses. Checks available fields to determine which are + present in the edge file to determine the properties to extract. If projections is None, all the synapses are extracted. """ diff --git a/tests/test_allen_v1/.test_ringcells_allen_v1.py.swp b/tests/test_allen_v1/.test_ringcells_allen_v1.py.swp deleted file mode 100644 index c1c0c49e5c6af232aa51b57a88f8532301807cbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2KWrRD6voHt2m}!H=&_EJJ>=~9oJ4@7D@X#-2~4cmrm?Km-0iGylD*y4%$&_( z8Kfi<64dl`bcmi7YAB!x1tmzNf)a$pH?zBUImcjH@T~ORdS~9eKfgD((#g|bey6ua zuSOk)>siJ=d+(wA@P$v#+~5oBj?8!M@JZJiADm?3=HIst+v}>hEe1xnZ(i-SwH%Ez zZe)?Sd6o%$ma229eKi}4Ya+`q*KZr4VQoKSXA|#rswV|^16b+lcvMdQRVLoZ6F6lA z#wNY^$~ksvrL$}+UkqNP=bzg;We|M1o`5Id33vjYfG6Mycmkfl=_X*x1@-|DoT)Li z>-)lyd;REdo`5Id33vjYfG6Mycmke)C*TQq0-k^;@E;_=6UNRy!Pt+G{r`XS`~T-> z82btO0eS%4hwedlp#-`DU55UAnz65;ub@w%yU-@I0R_-+=NS7HdH~&r?m-#!64Zje ze~Pj1pf8}0p^u;;)P){C$=EN@=a7aJbP-yD{y59n@6flrkq8|PuTUy8 z!Cdr)1a;|Pd6iZ!N6SmpS*4fleWe;WtW0hGGS`OZMk1GZS0=hCQ^zPeHbbdn#q&F@ zYDLZ9$Rt8-hhTqeEEhW>IRKaKpE!PdO;;YzOt) zV8>NAi*n+^nJLz~#e_?7sRg|$Ca)`1s4xxQMs5!Dg@d`!hZm5TK^$HbxUgFxGAaG6*th$SyHao&S~$up5-^q!NNd|w{?+6S;3PqtwOr}Qj{VO z8&h-x-ER@LMY>vfnxBFj95s#yMV`vtxX-nS6RD_eN9&$7ldc67b6czP+~8}R#;#K5 z^O)7qt}x-$L$E}_Ygty~S|rP$*|-)^mF~^0&GoI#_ Date: Fri, 1 May 2026 13:24:04 +0200 Subject: [PATCH 08/15] To test for allen type edges, instead look for limited count of properties --- bluecellulab/circuit/circuit_access/sonata_circuit_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py index 8794e6d0..4dad72d4 100644 --- a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +++ b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py @@ -220,7 +220,7 @@ def extract_synapses( # check for allen instance - replace the entire edge_properties list as appropriate # properties for allen point/chemical neuron connection type edges - if SynapseProperty.TYPE not in edge_population.property_names: + if len(edge_population.property_names) < 10: if all( x in edge_population.property_names for x in SynapseProperties.allen_point From dc95223325eeae069a392041dbb5180467fc4f3d Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Fri, 1 May 2026 13:51:29 +0200 Subject: [PATCH 09/15] minor: limit use of docformatter to before 1.7.8 until it has bug fixed --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 27621d68..34f19438 100644 --- a/tox.ini +++ b/tox.ini @@ -41,7 +41,7 @@ deps = pandas-stubs>=2.0.0 types-setuptools>=67.8.0.0 ruff>=0.0.270 - docformatter>=1.7.2 + docformatter>=1.7.2,<1.7.8 commands = ruff check . --select F541,F401 --per-file-ignores="__init__.py:F401" pycodestyle {[base]name} --ignore=E501,W504,W503 From 8249daa315cfbdb4622215e5b908fd5f535d6ffb Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Sun, 3 May 2026 18:02:28 +0200 Subject: [PATCH 10/15] Instantiate allen biophysical cells in another test, adding support data --- .../ringtest_allen_v1/hoc/TestCell.hoc | 179 ++++++++++++++++++ .../morphologies/asc/cell_big.asc | 42 ++++ .../morphologies/asc/cell_small.asc | 17 ++ .../simulation_config_biophysical.json | 60 ++++++ .../simulation_config_point.json | 60 ++++++ 5 files changed, 358 insertions(+) create mode 100644 tests/examples/ringtest_allen_v1/hoc/TestCell.hoc create mode 100644 tests/examples/ringtest_allen_v1/morphologies/asc/cell_big.asc create mode 100644 tests/examples/ringtest_allen_v1/morphologies/asc/cell_small.asc create mode 100644 tests/examples/ringtest_allen_v1/simulation_config_biophysical.json create mode 100644 tests/examples/ringtest_allen_v1/simulation_config_point.json diff --git a/tests/examples/ringtest_allen_v1/hoc/TestCell.hoc b/tests/examples/ringtest_allen_v1/hoc/TestCell.hoc new file mode 100644 index 00000000..7b2042f7 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/hoc/TestCell.hoc @@ -0,0 +1,179 @@ +{load_file("import3d.hoc")} + +begintemplate TestCell +public is_art +public init, AddMorph, getCCell, setCCell, geom_nsec, biophys, biophys_inhomo, connect2target +public soma, dend, apic, axon, nSecAxonal, nSecAxonalOrig, synHelperList, CellRef +public synlist, all, apical, basal, somatic, axonal, gid, nSecAll, nSecSoma, nSecApical, nSecBasal, clear, x, y, z, position +objref synlist, all, apical, basal, somatic, axonal, this, CCellRef, synHelperList, CellRef + + +/*! + * Constructor to create a TestCell object usable in the bbp toolchain + * + * @param $1 gid + * @param $s2 morphology dir + * @param $s3 morphology_name + */ +proc init() { + gid = 0 + all = new SectionList() + somatic = new SectionList() + basal = new SectionList() + apical = new SectionList() + axonal = new SectionList() + forall delete_section() + if(numarg()>0) gid = $1 + if(numarg()>2){ + strdef morph_path + sprint(morph_path, "%s/%s", $s2, $s3) + AddMorph(morph_path) + } + + biophys() + + synlist = new List() + synHelperList = new List() + x = y = z = 0 // only change via position + CellRef = this + +} +create soma[1], dend[1], apic[1], axon[1] + +proc AddMorph(){ localobj morph, import, strobj, morphPath, extension + forall delete_section() + + strobj = new StringFunctions() + morphPath = new String() + extension = new String() + sscanf($s1, "%s", morphPath.s) + sscanf($s1, "%s", extension.s) + strobj.right(extension.s, strobj.len(extension.s) - 4) + strobj.tail(extension.s, "\\.", extension.s) // remove until dot (inclusive) + + // Classic formats + if( strcmp(extension.s, "asc") == 0 ) { + morph = new Import3d_Neurolucida3() + } else if( strcmp(extension.s, "swc") == 0 ) { + morph = new Import3d_SWC_read() + } else { + terminate("Unsupported file format: Morphology file has to end with .asc or .swc" ) + } + + morph.quiet = 1 + morph.input($s1) + import = new Import3d_GUI(morph, 0) + import.instantiate(this) + indexSections() + geom_nseg_fixed(40) + geom_nsec() +} + +proc biophys(){ + forsec all { + Ra = 100 + cm = 1 + } + soma { + insert hh + gnabar_hh = 0.12 + gkbar_hh = 0.036 + gl_hh = 0.0003 + el_hh = -54.3 + insert pas + g_pas = 0 + } + forsec basal { + insert pas + g_pas = 0.001 + e_pas = -65 + insert hh + } + forsec apical { + insert pas + g_pas = 0.001 + e_pas = -65 + insert hh + } +} + +proc biophys_inhomo(){} + +proc geom_nseg_fixed(/* chunkSize */) { local chunkSize + chunkSize = $1 + soma area(.5) // make sure diam reflects 3d points + forsec all { + nseg = 1 + 2*int(L/chunkSize) + } +} + +proc geom_nsec() { local nSec + nSec = 0 + forsec all { + nSec = nSec + 1 + } + nSecAll = nSec + nSec = 0 + forsec somatic { nSec = nSec + 1} + nSecSoma = nSec + nSec = 0 + forsec apical { + nseg = 2 + nSec = nSec + 1 + } + nSecApical= nSec + nSec = 0 + forsec basal { + nseg = 2 + nSec = nSec + 1 + } + nSecBasal = nSec + nSec = 0 + forsec axonal { + nseg = 2 + nSec = nSec + 1 + } + nSecAxonalOrig = nSecAxonal = nSec + +} + +/*! + * Assign section indices to the section voltage value. This will be useful later for serializing + * the sections into an array. Note, that once the simulation begins, the voltage values will revert to actual data again. + * + * @param $o1 Import3d_GUI object (optional) + */ +proc indexSections() { local index + index = 0 + forsec all { + v(0.0001) = index + index += 1 + } +} + +proc clear() { } + +/*! + * @param $o1 NetCon source (can be nil) + * @param $o2 Variable where generated NetCon will be placed + */ +proc connect2target() { //$o1 target point process, $o2 returned NetCon + soma $o2 = new NetCon(&v(1), $o1) + $o2.threshold = 10 +} +objref syn_ +proc synapses() { + /* E0 */ dend[0] syn_ = new ExpSyn(0.8) synlist.append(syn_) + syn_.tau = 2 + /* I1 */ dend[0] syn_ = new ExpSyn(0.1) synlist.append(syn_) + syn_.tau = 5 + syn_.e = -80 +} + +func is_art() { return 0 } + + +proc re_init_rng() { +} + +endtemplate TestCell diff --git a/tests/examples/ringtest_allen_v1/morphologies/asc/cell_big.asc b/tests/examples/ringtest_allen_v1/morphologies/asc/cell_big.asc new file mode 100644 index 00000000..d9bb0f17 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/morphologies/asc/cell_big.asc @@ -0,0 +1,42 @@ +("CellBody" + (Color Red) + (CellBody) + (32.5 0.0 0.0) + (0.0 15.5 0.0) + (-32.5 0.0 0.0) + (0.0 -15.5 0.0) +) + +( + (Color Red) + (Dendrite) + (15.0 0.0 0.0 1.0000) + (215.0 0.0 0.0 1.0000) + ( + (225.0 0.0 0.0 1.0000) + ( + (235.0 0.0 0.0 1.0000) + ( + (245.0 0.0 0.0 1.0000) + ( + (255.0 0.0 0.0 1.0000) + ( + (265.0 0.0 0.0 1.0000) + ( + (275.0 0.0 0.0 1.0000) + ( + (285.0 0.0 0.0 1.0000) + ( + (295.0 0.0 0.0 1.0000) + ( + (305.0 0.0 0.0 1.0000) + ) + ) + ) + ) + ) + ) + ) + ) + ) +) \ No newline at end of file diff --git a/tests/examples/ringtest_allen_v1/morphologies/asc/cell_small.asc b/tests/examples/ringtest_allen_v1/morphologies/asc/cell_small.asc new file mode 100644 index 00000000..3f8d63e7 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/morphologies/asc/cell_small.asc @@ -0,0 +1,17 @@ +("CellBody" + (Color Red) + (CellBody) + (16.5 0.0 0.0) + (0.0 15.5 0.0) + (-16.5 0.0 0.0) + (0.0 -15.5 0.0) +) + +( (Color Red) + (Dendrite) + (15.0 0.0 0.0 1.0000) + (215.0 0.0 0.0 1.0000) + ( + (225.0 0.0 0.0 1.0000) + ) +) \ No newline at end of file diff --git a/tests/examples/ringtest_allen_v1/simulation_config_biophysical.json b/tests/examples/ringtest_allen_v1/simulation_config_biophysical.json new file mode 100644 index 00000000..c2b73e79 --- /dev/null +++ b/tests/examples/ringtest_allen_v1/simulation_config_biophysical.json @@ -0,0 +1,60 @@ +{ + "node_sets_file": "nodesets.json", + "network": "circuit_config.json", + "node_set": "RingA", + "target_simulator": "NEURON", + "run": { + "tstop": 50.0, + "dt": 0.1, + "random_seed": 1122 + }, + "conditions": { + "celsius": 35, + "v_init": -65 + }, + "connection_overrides": [ + { + "name": "biophysical_biophysical", + "source": "RingA", + "target": "RingA", + "weight": 1, + "modoverride": "Exp2Syn" + }, + { + "name": "point_biophysical", + "source": "RingB", + "target": "RingA", + "weight": 1, + "modoverride": "Exp2Syn" + } + ], + "inputs": { + "RingA_spikes": { + "input_type": "spikes", + "module": "synapse_replay", + "delay": 0, + "duration": 100, + "spike_file": "replay_RingA.h5", + "node_set": "RingB" + }, + "RingB_spikes": { + "input_type": "spikes", + "module": "synapse_replay", + "delay": 0, + "duration": 100, + "spike_file": "replay_RingB.h5", + "node_set": "RingA" + } + }, + "reports": { + "compartment_report": { + "type": "compartment", + "sections": "soma", + "cells": "RingA", + "variable_name": "v", + "dt": 0.1, + "start_time": 0.0, + "end_time": 100.0 + } + } +} diff --git a/tests/examples/ringtest_allen_v1/simulation_config_point.json b/tests/examples/ringtest_allen_v1/simulation_config_point.json new file mode 100644 index 00000000..3bae6b9b --- /dev/null +++ b/tests/examples/ringtest_allen_v1/simulation_config_point.json @@ -0,0 +1,60 @@ +{ + "node_sets_file": "nodesets.json", + "network": "circuit_config.json", + "node_set": "RingB", + "target_simulator": "NEURON", + "run": { + "tstop": 50.0, + "dt": 0.1, + "random_seed": 1122 + }, + "conditions": { + "celsius": 35, + "v_init": -65 + }, + "connection_overrides": [ + { + "name": "biophysical_biophysical", + "source": "RingA", + "target": "RingA", + "weight": 1, + "modoverride": "Exp2Syn" + }, + { + "name": "point_biophysical", + "source": "RingB", + "target": "RingA", + "weight": 1, + "modoverride": "Exp2Syn" + } + ], + "inputs": { + "RingA_spikes": { + "input_type": "spikes", + "module": "synapse_replay", + "delay": 0, + "duration": 100, + "spike_file": "replay_RingA.h5", + "node_set": "RingB" + }, + "RingB_spikes": { + "input_type": "spikes", + "module": "synapse_replay", + "delay": 0, + "duration": 100, + "spike_file": "replay_RingB.h5", + "node_set": "RingA" + } + }, + "reports": { + "compartment_report": { + "type": "compartment", + "sections": "all", + "cells": "RingA", + "variable_name": "v", + "dt": 0.1, + "start_time": 0.0, + "end_time": 100.0 + } + } +} From cba681e2b9934a60c7ad96d7f162f9bba4f6aeac Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Sun, 3 May 2026 18:03:32 +0200 Subject: [PATCH 11/15] Forgot updated files in previous commit --- .../ringtest_allen_v1/circuit_config.json | 4 +- tests/examples/ringtest_allen_v1/nodes_A.h5 | Bin 13016 -> 15112 bytes .../ringtest_allen_v1/simulation_config.json | 2 +- .../test_allen_v1/test_ringcells_allen_v1.py | 47 +++++++++++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/tests/examples/ringtest_allen_v1/circuit_config.json b/tests/examples/ringtest_allen_v1/circuit_config.json index 9bdc99cb..71668bb3 100644 --- a/tests/examples/ringtest_allen_v1/circuit_config.json +++ b/tests/examples/ringtest_allen_v1/circuit_config.json @@ -9,9 +9,9 @@ "populations": { "RingA": { "type": "biophysical", - "biophysical_neuron_models_dir": "$BASE_DIR/../ringtest/hoc", + "biophysical_neuron_models_dir": "$BASE_DIR/hoc", "alternate_morphologies": { - "neurolucida-asc": "$BASE_DIR/../ringtest/morphologies/asc" + "neurolucida-asc": "$BASE_DIR/morphologies/asc" } } } diff --git a/tests/examples/ringtest_allen_v1/nodes_A.h5 b/tests/examples/ringtest_allen_v1/nodes_A.h5 index d19d89d82b65d36778ebaac8dfcbdaf6cd61aed5..ed94faf490ac80994118fcd278863ac325f2cba4 100644 GIT binary patch delta 458 zcmcbS+EF$^gNeg>qgIb7Bh$ow$;lT)IRpb3AYg_mg!b6D@j2t<3*rKd1)Cp=*D`9d zG9*Bi+%SSL7@>RyV+cb5qIGh-)}z&afmOt=|L{xd_2 zVFikUbTTn9feqkbkN`_CGv2U*sF0YvTVgQ}6N3VT%V1$Saibg;#4HAe2WA^L@-dQW k#RnuS=1x2?K|*WAieFRD9kz$jvCP} Date: Sun, 3 May 2026 21:37:20 +0200 Subject: [PATCH 12/15] Improved allen test instantiation Removed call to finalize for point process type cells since this is not needed --- bluecellulab/cell/point_process.py | 1 - .../ringtest_allen_v1/output/populations_offset.dat | 2 -- tests/test_allen_v1/test_ringcells_allen_v1.py | 7 +++---- 3 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 tests/examples/ringtest_allen_v1/output/populations_offset.dat diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index 32b61a02..25d04c6c 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -190,7 +190,6 @@ def add_replay_synapse(self, syn_id, syn_description, syn_connection_parameters, syn_description[SynapseProperty.AXONAL_DELAY]) self.pointConn = PointProcessConnection([point_params]) - self.pointConn.finalize(self.pointcell) def mechanism_name_from_model_template(template_path: str, model_template: str) -> str: diff --git a/tests/examples/ringtest_allen_v1/output/populations_offset.dat b/tests/examples/ringtest_allen_v1/output/populations_offset.dat deleted file mode 100644 index 53c6bdb0..00000000 --- a/tests/examples/ringtest_allen_v1/output/populations_offset.dat +++ /dev/null @@ -1,2 +0,0 @@ -RingA::0::RingA -RingB::1000::RingB diff --git a/tests/test_allen_v1/test_ringcells_allen_v1.py b/tests/test_allen_v1/test_ringcells_allen_v1.py index 6cef06bd..a37f8c39 100644 --- a/tests/test_allen_v1/test_ringcells_allen_v1.py +++ b/tests/test_allen_v1/test_ringcells_allen_v1.py @@ -38,7 +38,7 @@ def test_cell_point_create(capsys): cell_ids_for_this_rank = [(population, i) for i in all_node_ids] - bcl.instantiate_gids(cell_ids_for_this_rank) + bcl.instantiate_gids(cell_ids_for_this_rank, **{"add_synapses": True, "add_replay": False, "add_stimuli": True}) tau_vals = {0: 24.0, 1: 7.0, 2: 24.0} @@ -80,11 +80,10 @@ def test_cell_biophysical_create(capsys): cell_ids_for_this_rank = [(population, i) for i in all_node_ids] - bcl.instantiate_gids(cell_ids_for_this_rank) + bcl.instantiate_gids(cell_ids_for_this_rank, **{"add_synapses": True, "add_replay": False, "add_stimuli": True}) - threshold_vals = {0:0.154742, 1:0.154742, 2:0.0876128} + threshold_vals = {0: 0.154742, 1: 0.154742, 2: 0.0876128} # verify that a point neuron has been created with IntFire and parameters set according to what was in the nodes.h5 file for (cell_id, cell) in bcl.cells.items(): assert (threshold_vals[cell_id.id] == cell.threshold) - From 45b18aa2b2a8ae79f87e05b1ece1b33529d80523 Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Sun, 3 May 2026 22:27:10 +0200 Subject: [PATCH 13/15] Removed prototype code ultimately not used for allen use case Updated allen tests to collection cell_info and get ExpSyn data as consequence --- bluecellulab/cell/point_process.py | 65 ------------------- bluecellulab/point/point_connection.py | 38 ----------- bluecellulab/synapse/synapse_types.py | 20 ++++-- .../test_allen_v1/test_ringcells_allen_v1.py | 4 ++ 4 files changed, 19 insertions(+), 108 deletions(-) diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index 25d04c6c..70f2c6f7 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -1,6 +1,5 @@ from __future__ import annotations -from dataclasses import dataclass import logging from pathlib import Path from typing import Any, Mapping, Optional @@ -208,67 +207,3 @@ def mechanism_name_from_model_template(template_path: str, model_template: str) h.load_file(template_path) return name return mt - - -@dataclass -class IntFire1Params: - tau: float = 10.0 - refrac: float = 2.0 - - -class IntFire1Cell(BasePointProcessCell): - def __init__( - self, - cell_id: Optional[CellId] = None, - tau: float = 10.0, - refrac: float = 2.0, - ) -> None: - super().__init__(cell_id) - point = h.IntFire1() - point.tau = tau - point.refrac = refrac - self.pointcell = point - - self.start_recording_spikes(None, None, threshold=1.0) - - -@dataclass -class IntFire2Params: - taum: float = 10.0 - taus: float = 20.0 - ib: float = 0.0 - - -class IntFire2Cell(BasePointProcessCell): - def __init__( - self, - cell_id: Optional[CellId] = None, - taum: float = 10.0, - taus: float = 20.0, - ib: float = 0.0, - ) -> None: - super().__init__(cell_id) - point = h.IntFire2() - point.taum = taum - point.taus = taus - point.ib = ib - self.pointcell = point - - self.start_recording_spikes(None, None, threshold=1.0) - - -def create_intfire1_cell( - tau: float = 10.0, - refrac: float = 2.0, - cell_id: Optional[CellId] = None, -) -> IntFire1Cell: - return IntFire1Cell(cell_id=cell_id, tau=tau, refrac=refrac) - - -def create_intfire2_cell( - taum: float = 10.0, - taus: float = 20.0, - ib: float = 0.0, - cell_id: Optional[CellId] = None, -) -> IntFire2Cell: - return IntFire2Cell(cell_id=cell_id, taum=taum, taus=taus, ib=ib) diff --git a/bluecellulab/point/point_connection.py b/bluecellulab/point/point_connection.py index 44df0ae0..799c7330 100644 --- a/bluecellulab/point/point_connection.py +++ b/bluecellulab/point/point_connection.py @@ -5,7 +5,6 @@ from neuron import h from bluecellulab.point.connection_params import PointProcessConnParameters -from bluecellulab.cell.point_process import BasePointProcessCell pc = h.ParallelContext() @@ -45,40 +44,3 @@ def __init__( @property def netcons(self) -> list[h.NetCon]: return self._netcons - - def finalize(self, cell: BasePointProcessCell) -> int: - """Create NetCon(s) onto the given point neuron cell. - - Returns - ------- - int - Number of synapses (0 or 1). - """ - n_syns = 0 - - for params in self.synapse_params: - n_syns += 1 - - if self.attach_src_cell: - # --- main path: presyn cell with sgid --- - nc = pc.gid_connect(params.sgid, cell.pointcell) - nc.delay = self.syndelay_override or float(params.delay) - nc.weight[0] = float(params.weight) * self.weight_factor - nc.threshold = DEFAULT_SPIKE_THRESHOLD - self._netcons.append(nc) - - # --- replay path (optional, stubbed) --- - if self._replay is not None and getattr(self._replay, "has_data", lambda: False)(): - vecstim = h.VecStim() - vecstim.play(self._replay.time_vec) - nc = h.NetCon( - vecstim, - cell.pointcell, - 10.0, - self.syndelay_override or float(params.delay), - float(params.weight), - ) - nc.weight[0] = float(params.weight) * self.weight_factor - self._replay._store(vecstim, nc) - - return n_syns diff --git a/bluecellulab/synapse/synapse_types.py b/bluecellulab/synapse/synapse_types.py index 746bd3b2..057efa1f 100644 --- a/bluecellulab/synapse/synapse_types.py +++ b/bluecellulab/synapse/synapse_types.py @@ -464,8 +464,18 @@ def use_exp2syn_helper(self) -> None: @property def info_dict(self): - parent_dict = super().info_dict - parent_dict['synapse_parameters']['tau1'] = self.hsynapse.tau1 - parent_dict['synapse_parameters']['tau2'] = self.hsynapse.tau2 - parent_dict['synapse_parameters']['erev'] = self.hsynapse.e - return parent_dict + synapse_dict: dict[str, Any] = {} + + synapse_dict['synapse_id'] = self.syn_id + synapse_dict['pre_cell_id'] = self.pre_gid + synapse_dict['post_cell_id'] = self.post_cell_id.id + synapse_dict['syn_description'] = self.syn_description.to_dict() + # if keys are enum make them str + synapse_dict['syn_description'] = { + str(k): v for k, v in synapse_dict['syn_description'].items()} + synapse_dict['mech_name'] = self.mech_name + synapse_dict['synapse_parameters'] = {} + synapse_dict['synapse_parameters']['tau1'] = self.hsynapse.tau1 + synapse_dict['synapse_parameters']['tau2'] = self.hsynapse.tau2 + synapse_dict['synapse_parameters']['erev'] = self.hsynapse.e + return synapse_dict diff --git a/tests/test_allen_v1/test_ringcells_allen_v1.py b/tests/test_allen_v1/test_ringcells_allen_v1.py index a37f8c39..688f6078 100644 --- a/tests/test_allen_v1/test_ringcells_allen_v1.py +++ b/tests/test_allen_v1/test_ringcells_allen_v1.py @@ -44,6 +44,8 @@ def test_cell_point_create(capsys): # verify that a point neuron has been created with IntFire and parameters set according to what was in the nodes.h5 file for (cell_id, cell) in bcl.cells.items(): + cell_info_dict = cell.info_dict + assert cell_info_dict != {} assert (tau_vals[cell_id.id] == cell.pointcell.pointcell.tau) @@ -86,4 +88,6 @@ def test_cell_biophysical_create(capsys): # verify that a point neuron has been created with IntFire and parameters set according to what was in the nodes.h5 file for (cell_id, cell) in bcl.cells.items(): + cell_info_dict = cell.info_dict + assert cell_info_dict != {} assert (threshold_vals[cell_id.id] == cell.threshold) From e74a00b731ed693db485bbb672bf938951473c1f Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Sun, 3 May 2026 23:52:16 +0200 Subject: [PATCH 14/15] Involve NEURON ParallelContext when testing point cells to trigger more netcon code Remove more unused code sections; can be added when use case example is available --- bluecellulab/cell/point_process.py | 13 +------------ bluecellulab/circuit_simulation.py | 1 - tests/test_allen_v1/test_ringcells_allen_v1.py | 17 +++++++++++++---- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/bluecellulab/cell/point_process.py b/bluecellulab/cell/point_process.py index 70f2c6f7..c4c98210 100644 --- a/bluecellulab/cell/point_process.py +++ b/bluecellulab/cell/point_process.py @@ -2,7 +2,7 @@ import logging from pathlib import Path -from typing import Any, Mapping, Optional +from typing import Optional from bluecellulab.cell import Cell from bluecellulab.circuit.simulation_access import get_synapse_replay_spikes @@ -79,12 +79,6 @@ def start_recording_spikes(self, sec, location=None, threshold: float = 0.0) -> self._spike_detector.threshold = threshold self._spike_detector.record(self._spike_times) - def connect2target(self, target_pp=None) -> h.NetCon: - """Neurodamus-like helper: NetCon from this cell to a target point process.""" - if self.pointcell is None: - raise ValueError("call to connect2target without valid pointprocess") - return h.NetCon(self.pointcell.pointcell, target_pp) - class HocPointProcessCell(BasePointProcessCell): """Point process that wraps an arbitrary HOC/mod artificial mechanism.""" @@ -93,7 +87,6 @@ def __init__( self, cell_id: Optional[CellId], mechanism_name: str, - param_overrides: Optional[Mapping[str, Any]] = None, spike_threshold: float = 1.0, ) -> None: super().__init__(cell_id) @@ -109,10 +102,6 @@ def __init__( if cell_id is None: raise ValueError("call to create pointprocess mechanism without valid cell_id") point = mech_cls(cell_id.id) - if param_overrides: - for name, value in param_overrides.items(): - if hasattr(point, name): - setattr(point, name, value) self.pointcell = point self.start_recording_spikes(None, None, threshold=spike_threshold) diff --git a/bluecellulab/circuit_simulation.py b/bluecellulab/circuit_simulation.py index 41a3a35f..ec4ddb51 100644 --- a/bluecellulab/circuit_simulation.py +++ b/bluecellulab/circuit_simulation.py @@ -1207,7 +1207,6 @@ def create_cell_from_circuit(self, cell_id: CellId) -> bluecellulab.Cell: return HocPointProcessCell( cell_id=cell_id, mechanism_name=mech_name, - param_overrides=None, spike_threshold=self.spike_threshold, ) diff --git a/tests/test_allen_v1/test_ringcells_allen_v1.py b/tests/test_allen_v1/test_ringcells_allen_v1.py index 688f6078..a2c75ecb 100644 --- a/tests/test_allen_v1/test_ringcells_allen_v1.py +++ b/tests/test_allen_v1/test_ringcells_allen_v1.py @@ -9,7 +9,7 @@ def test_cell_point_create(capsys): from bluecellulab import CircuitSimulation sim_conf = str(SIM_DIR / "simulation_config_point.json") - bcl = CircuitSimulation(simulation_config=sim_conf) + bcl = CircuitSimulation(simulation_config=sim_conf, print_cellstate=True) # Load configuration using json with open(sim_conf) as f: @@ -38,7 +38,7 @@ def test_cell_point_create(capsys): cell_ids_for_this_rank = [(population, i) for i in all_node_ids] - bcl.instantiate_gids(cell_ids_for_this_rank, **{"add_synapses": True, "add_replay": False, "add_stimuli": True}) + bcl.instantiate_gids(cell_ids_for_this_rank, **{"add_synapses": True, "add_replay": False, "add_stimuli": True, "interconnect_cells": True}) tau_vals = {0: 24.0, 1: 7.0, 2: 24.0} @@ -53,7 +53,7 @@ def test_cell_biophysical_create(capsys): from bluecellulab import CircuitSimulation sim_conf = str(SIM_DIR / "simulation_config_biophysical.json") - bcl = CircuitSimulation(simulation_config=sim_conf) + bcl = CircuitSimulation(simulation_config=sim_conf, print_cellstate=True) # Load configuration using json with open(sim_conf) as f: @@ -82,7 +82,7 @@ def test_cell_biophysical_create(capsys): cell_ids_for_this_rank = [(population, i) for i in all_node_ids] - bcl.instantiate_gids(cell_ids_for_this_rank, **{"add_synapses": True, "add_replay": False, "add_stimuli": True}) + bcl.instantiate_gids(cell_ids_for_this_rank, **{"add_synapses": True, "add_replay": False, "add_stimuli": True, "interconnect_cells": True}) threshold_vals = {0: 0.154742, 1: 0.154742, 2: 0.0876128} @@ -91,3 +91,12 @@ def test_cell_biophysical_create(capsys): cell_info_dict = cell.info_dict assert cell_info_dict != {} assert (threshold_vals[cell_id.id] == cell.threshold) + + +def test_point_process_cell_rejects_none(capsys): + from bluecellulab.cell.point_process import HocPointProcessCell + + try: + HocPointProcessCell(None, "IntFire1") + except ValueError as error: + assert str(error) == "PointProcessCell requires valid cell_id" From f0264f2dfbfa743ab63d38bb5827739919c4ec66 Mon Sep 17 00:00:00 2001 From: James Gonzalo King Date: Mon, 4 May 2026 00:33:24 +0200 Subject: [PATCH 15/15] Corrected SynapseType to indicate ALLEN_CHEMICAL and removed ALLEN_POINT Added additional test for invalid Mechanism given to HocPointProcessCell --- bluecellulab/synapse/synapse_factory.py | 5 +---- tests/test_allen_v1/test_ringcells_allen_v1.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bluecellulab/synapse/synapse_factory.py b/bluecellulab/synapse/synapse_factory.py index 1ce24389..e8ecc136 100644 --- a/bluecellulab/synapse/synapse_factory.py +++ b/bluecellulab/synapse/synapse_factory.py @@ -71,9 +71,6 @@ def create_synapse( elif syn_type == SynapseType.ALLEN_CHEMICAL: synapse = Exp2Syn(cell.cell_id, syn_hoc_args, syn_id, syn_description, popids, cell.post_gid, extracellular_calcium) - elif syn_type == SynapseType.ALLEN_POINT: - synapse = Exp2Syn(cell.cell_id, syn_hoc_args, syn_id, syn_description, - popids, cell.post_gid, extracellular_calcium) else: synapse = GluSynapse(cell.cell_id, syn_hoc_args, syn_id, syn_description, popids, cell.post_gid, extracellular_calcium) @@ -98,7 +95,7 @@ def determine_synapse_type( ) -> SynapseType: """Returns the type of synapse to be created.""" if SynapseProperty.TYPE not in syn_description: - return SynapseType.ALLEN_POINT + return SynapseType.ALLEN_CHEMICAL is_inhibitory: bool = int(syn_description[SynapseProperty.TYPE]) < 100 all_plasticity_props_available: bool = all( diff --git a/tests/test_allen_v1/test_ringcells_allen_v1.py b/tests/test_allen_v1/test_ringcells_allen_v1.py index a2c75ecb..3b68e706 100644 --- a/tests/test_allen_v1/test_ringcells_allen_v1.py +++ b/tests/test_allen_v1/test_ringcells_allen_v1.py @@ -46,6 +46,8 @@ def test_cell_point_create(capsys): for (cell_id, cell) in bcl.cells.items(): cell_info_dict = cell.info_dict assert cell_info_dict != {} + assert cell.hoc_cell is not None + assert cell.get_spike_times() is not None assert (tau_vals[cell_id.id] == cell.pointcell.pointcell.tau) @@ -100,3 +102,14 @@ def test_point_process_cell_rejects_none(capsys): HocPointProcessCell(None, "IntFire1") except ValueError as error: assert str(error) == "PointProcessCell requires valid cell_id" + + +def test_point_process_cell_rejects_bad_mechanism_name(capsys): + from bluecellulab.circuit import CellId + from bluecellulab.cell.point_process import HocPointProcessCell + from bluecellulab.exceptions import BluecellulabError + + try: + HocPointProcessCell(CellId("point", 0), "Noexist_IntFire99") + except BluecellulabError as error: + assert str(error) == "Point mechanism 'Noexist_IntFire99' not found in NEURON. Make sure the mod/hoc files are compiled and loaded."