From 268614196ee17c8e2c8f20cb7425ef4f9181b14e Mon Sep 17 00:00:00 2001 From: anilbey Date: Fri, 23 Feb 2024 14:55:42 +0100 Subject: [PATCH] Refactor: separate bluepy and sonata access objects into different modules (#135) * refactor: separate bluepy circuit access def from sonata * omit bluepycircuitaccess in coverage * separate BluepySimulationConfig and SonataSimulationConfig into different modules * omit bluepysimulationconfig from coverage * Hide 'BlueConfig' type from allowed arguments * remove unused import * add from __future__ import annotations * Fix type error in bluepysimulationconfig * docstr fix * use Protocol type in BluepyCircuitAccess * add from __future__ import annotations * cover Path type when assigning self._config * revert changes to BluepySimulationAccess' init * update copyrights --- bluecellulab/cell/cell_dict.py | 2 +- bluecellulab/cell/core.py | 2 +- bluecellulab/cell/injector.py | 2 +- bluecellulab/cell/plotting.py | 2 +- bluecellulab/cell/random.py | 2 +- bluecellulab/cell/section_distance.py | 2 +- bluecellulab/cell/serialized_sections.py | 2 +- bluecellulab/cell/sonata_proxy.py | 2 +- bluecellulab/cell/stimuli_generator.py | 2 +- bluecellulab/cell/template.py | 2 +- .../circuit/circuit_access/__init__.py | 3 + .../bluepy_circuit_access.py} | 353 ++---------------- .../circuit/circuit_access/definition.py | 124 ++++++ .../circuit_access/sonata_circuit_access.py | 241 ++++++++++++ bluecellulab/circuit/config/__init__.py | 4 +- ..._config.py => bluepy_simulation_config.py} | 225 +---------- bluecellulab/circuit/config/definition.py | 103 +++++ bluecellulab/circuit/config/sections.py | 2 +- .../config/sonata_simulation_config.py | 148 ++++++++ bluecellulab/circuit/format.py | 2 +- bluecellulab/circuit/iotools.py | 2 +- bluecellulab/circuit/node_id.py | 2 +- bluecellulab/circuit/simulation_access.py | 4 +- bluecellulab/circuit/synapse_properties.py | 2 +- bluecellulab/circuit/validate.py | 2 +- bluecellulab/connection.py | 2 +- bluecellulab/dendrogram.py | 2 +- bluecellulab/exceptions.py | 2 +- bluecellulab/importer.py | 2 +- bluecellulab/neuron_interpreter.py | 2 +- bluecellulab/plotwindow.py | 2 +- bluecellulab/psection.py | 2 +- bluecellulab/psegment.py | 2 +- bluecellulab/rngsettings.py | 2 +- bluecellulab/simulation/neuron_globals.py | 2 +- bluecellulab/simulation/simulation.py | 2 +- bluecellulab/ssim.py | 2 +- bluecellulab/stimuli.py | 2 +- bluecellulab/synapse/synapse_factory.py | 2 +- bluecellulab/synapse/synapse_types.py | 2 +- bluecellulab/tools.py | 2 +- pyproject.toml | 4 + setup.py | 2 +- tests/test_circuit/test_format.py | 2 +- tox.ini | 2 +- 45 files changed, 692 insertions(+), 587 deletions(-) create mode 100644 bluecellulab/circuit/circuit_access/__init__.py rename bluecellulab/circuit/{circuit_access.py => circuit_access/bluepy_circuit_access.py} (51%) create mode 100644 bluecellulab/circuit/circuit_access/definition.py create mode 100644 bluecellulab/circuit/circuit_access/sonata_circuit_access.py rename bluecellulab/circuit/config/{simulation_config.py => bluepy_simulation_config.py} (52%) create mode 100644 bluecellulab/circuit/config/definition.py create mode 100644 bluecellulab/circuit/config/sonata_simulation_config.py diff --git a/bluecellulab/cell/cell_dict.py b/bluecellulab/cell/cell_dict.py index faa9f15d..554b68e5 100644 --- a/bluecellulab/cell/cell_dict.py +++ b/bluecellulab/cell/cell_dict.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/core.py b/bluecellulab/cell/core.py index fd6778db..4ab2da69 100644 --- a/bluecellulab/cell/core.py +++ b/bluecellulab/cell/core.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/injector.py b/bluecellulab/cell/injector.py index deb10d2e..e0061823 100644 --- a/bluecellulab/cell/injector.py +++ b/bluecellulab/cell/injector.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/plotting.py b/bluecellulab/cell/plotting.py index bfddea9a..fec722c4 100644 --- a/bluecellulab/cell/plotting.py +++ b/bluecellulab/cell/plotting.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/random.py b/bluecellulab/cell/random.py index 3b68a221..2dac34fc 100644 --- a/bluecellulab/cell/random.py +++ b/bluecellulab/cell/random.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/section_distance.py b/bluecellulab/cell/section_distance.py index 3885bfb1..a63328ad 100644 --- a/bluecellulab/cell/section_distance.py +++ b/bluecellulab/cell/section_distance.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/serialized_sections.py b/bluecellulab/cell/serialized_sections.py index c0e54686..0e5873a7 100644 --- a/bluecellulab/cell/serialized_sections.py +++ b/bluecellulab/cell/serialized_sections.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/sonata_proxy.py b/bluecellulab/cell/sonata_proxy.py index 4467bcaa..991a8405 100644 --- a/bluecellulab/cell/sonata_proxy.py +++ b/bluecellulab/cell/sonata_proxy.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/stimuli_generator.py b/bluecellulab/cell/stimuli_generator.py index de2ac6ac..ba556060 100644 --- a/bluecellulab/cell/stimuli_generator.py +++ b/bluecellulab/cell/stimuli_generator.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/cell/template.py b/bluecellulab/cell/template.py index 8706bc6f..1e808835 100644 --- a/bluecellulab/cell/template.py +++ b/bluecellulab/cell/template.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/circuit/circuit_access/__init__.py b/bluecellulab/circuit/circuit_access/__init__.py new file mode 100644 index 00000000..82a04fa2 --- /dev/null +++ b/bluecellulab/circuit/circuit_access/__init__.py @@ -0,0 +1,3 @@ +from .definition import EmodelProperties, CircuitAccess, get_synapse_connection_parameters +from .bluepy_circuit_access import BluepyCircuitAccess +from .sonata_circuit_access import SonataCircuitAccess diff --git a/bluecellulab/circuit/circuit_access.py b/bluecellulab/circuit/circuit_access/bluepy_circuit_access.py similarity index 51% rename from bluecellulab/circuit/circuit_access.py rename to bluecellulab/circuit/circuit_access/bluepy_circuit_access.py index bced3fe7..4e42a213 100644 --- a/bluecellulab/circuit/circuit_access.py +++ b/bluecellulab/circuit/circuit_access/bluepy_circuit_access.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,151 +11,44 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Layer to abstract the circuit access functionality from the rest of -bluecellulab.""" - from __future__ import annotations -from collections import defaultdict + from functools import lru_cache -import hashlib +import logging import os from pathlib import Path -from typing import Any, Optional, Protocol -import logging - -from bluecellulab import BLUEPY_AVAILABLE +from typing import Optional -if BLUEPY_AVAILABLE: - from bluepy_configfile.configfile import BlueConfig - import bluepy - from bluepy.enums import Cell as BluepyCell - from bluepy.impl.connectome_sonata import SonataConnectome - -from bluepysnap.bbp import Cell as SnapCell -from bluepysnap.circuit_ids import CircuitNodeId, CircuitEdgeIds -from bluepysnap.exceptions import BluepySnapError -from bluepysnap import Circuit as SnapCircuit import pandas as pd -from pydantic.dataclasses import dataclass - from bluecellulab import circuit, neuron -from bluecellulab.circuit import CellId, SynapseProperty -from bluecellulab.circuit.synapse_properties import SynapseProperties -from bluecellulab.circuit.config import BluepySimulationConfig, SimulationConfig, SonataSimulationConfig -from bluecellulab.circuit.synapse_properties import ( - properties_from_bluepy, - properties_from_snap, - properties_to_bluepy, - properties_to_snap, -) -from bluecellulab.exceptions import BluecellulabError, ExtraDependencyMissingError - -logger = logging.getLogger(__name__) - - -@dataclass(config=dict(extra="forbid")) -class EmodelProperties: - threshold_current: float - holding_current: float - AIS_scaler: Optional[float] = 1.0 - soma_scaler: Optional[float] = 1.0 - - -def get_synapse_connection_parameters( - circuit_access: CircuitAccess, pre_cell: CellId, post_cell: CellId) -> dict: - """Apply connection blocks in order for pre_gid, post_gid to determine a - final connection override for this pair (pre_gid, post_gid).""" - parameters: defaultdict[str, Any] = defaultdict(list) - parameters['add_synapse'] = True - - for entry in circuit_access.config.connection_entries(): - src_matches = circuit_access.target_contains_cell(entry.source, pre_cell) - dest_matches = circuit_access.target_contains_cell(entry.target, post_cell) - - if src_matches and dest_matches: - # whatever specified in this block, is applied to gid - apply_parameters = True - - if entry.delay is not None: - parameters['DelayWeights'].append((entry.delay, entry.weight)) - apply_parameters = False - - if apply_parameters: - if entry.weight is not None: - parameters['Weight'] = entry.weight - if entry.spont_minis is not None: - parameters['SpontMinis'] = entry.spont_minis - if entry.synapse_configure is not None: - # collect list of applicable configure blocks to be - # applied with a "hoc exec" statement - parameters['SynapseConfigure'].append(entry.synapse_configure) - if entry.mod_override is not None: - parameters['ModOverride'] = entry.mod_override - return parameters - - -class CircuitAccess(Protocol): - """Protocol that defines the circuit access layer.""" - - config: SimulationConfig - - @property - def available_cell_properties(self) -> set: - raise NotImplementedError - - def get_emodel_properties(self, cell_id: CellId) -> Optional[EmodelProperties]: - raise NotImplementedError - - def get_template_format(self) -> Optional[str]: - raise NotImplementedError - - def get_cell_properties( - self, cell_id: CellId, properties: list[str] | str - ) -> pd.Series: - raise NotImplementedError +from bluecellulab.circuit.circuit_access import EmodelProperties +from bluecellulab.circuit.config import BluepySimulationConfig +from bluecellulab.circuit.config.definition import SimulationConfig +from bluecellulab.circuit.node_id import CellId +from bluecellulab.circuit.synapse_properties import SynapseProperties, SynapseProperty, properties_from_bluepy, properties_to_bluepy - def extract_synapses( - self, cell_id: CellId, projections: Optional[list[str] | str] - ) -> pd.DataFrame: - raise NotImplementedError - - def target_contains_cell(self, target: str, cell_id: CellId) -> bool: - raise NotImplementedError - - def is_valid_group(self, group: str) -> bool: - raise NotImplementedError - - def get_target_cell_ids(self, target: str) -> set[CellId]: - raise NotImplementedError +from bluecellulab.exceptions import BluecellulabError, ExtraDependencyMissingError - def fetch_cell_info(self, cell_id: CellId) -> pd.Series: - raise NotImplementedError - def fetch_mini_frequencies(self, cell_id: CellId) -> tuple[float | None, float | None]: - raise NotImplementedError - - @property - def node_properties_available(self) -> bool: - raise NotImplementedError - - def get_gids_of_mtypes(self, mtypes: list[str]) -> set[CellId]: - raise NotImplementedError +from bluecellulab import BLUEPY_AVAILABLE - def get_cell_ids_of_targets(self, targets: list[str]) -> set[CellId]: - raise NotImplementedError - def morph_filepath(self, cell_id: CellId) -> str: - raise NotImplementedError +if BLUEPY_AVAILABLE: + import bluepy + from bluepy.enums import Cell as BluepyCell + from bluepy.impl.connectome_sonata import SonataConnectome - def emodel_path(self, cell_id: CellId) -> str: - raise NotImplementedError +logger = logging.getLogger(__name__) class BluepyCircuitAccess: """Bluepy implementation of CircuitAccess protocol.""" - def __init__(self, simulation_config: str | Path | BlueConfig | BluepySimulationConfig) -> None: - """Initialize bluepy circuit object.""" + def __init__(self, simulation_config: str | Path | SimulationConfig) -> None: + """Initialize bluepy circuit object. + + BlueConfig also is a valid type. + """ if not BLUEPY_AVAILABLE: raise ExtraDependencyMissingError("bluepy") if isinstance(simulation_config, Path): @@ -477,207 +370,3 @@ def emodel_path(self, cell_id: CellId) -> str: @property def _emodels_dir(self) -> str: return self.config.impl.Run['METypePath'] - - -class SonataCircuitAccess: - """Sonata implementation of CircuitAccess protocol.""" - - def __init__(self, simulation_config: str | Path | SimulationConfig) -> None: - """Initialize SonataCircuitAccess object.""" - if isinstance(simulation_config, (str, Path)) and not Path(simulation_config).exists(): - raise FileNotFoundError(f"Circuit config file {simulation_config} not found.") - - if isinstance(simulation_config, SonataSimulationConfig): - self.config: SimulationConfig = simulation_config - else: - self.config = SonataSimulationConfig(simulation_config) - circuit_config = self.config.impl.config["network"] - self._circuit = SnapCircuit(circuit_config) - - @property - def available_cell_properties(self) -> set: - return self._circuit.nodes.property_names - - def get_emodel_properties(self, cell_id: CellId) -> Optional[EmodelProperties]: - cell_properties = self._circuit.nodes[cell_id.population_name].get(cell_id.id) - if "@dynamics:AIS_scaler" in cell_properties: - AIS_scaler = cell_properties["@dynamics:AIS_scaler"] - else: - AIS_scaler = 1.0 - if "@dynamics:soma_scaler" in cell_properties: - soma_scaler = cell_properties["@dynamics:soma_scaler"] - else: - soma_scaler = 1.0 - - return EmodelProperties( - cell_properties["@dynamics:threshold_current"], - cell_properties["@dynamics:holding_current"], - AIS_scaler, - soma_scaler, - ) - - def get_template_format(self) -> Optional[str]: - return 'v6' - - def get_cell_properties( - self, cell_id: CellId, properties: list[str] | str - ) -> pd.Series: - if isinstance(properties, str): - properties = [properties] - return self._circuit.nodes[cell_id.population_name].get( - cell_id.id, properties=properties - ) - - @staticmethod - def _compute_pop_ids(source: str, target: str) -> tuple[int, int]: - """Compute the population ids from the population names.""" - def make_id(node_pop: str) -> int: - pop_hash = hashlib.md5(node_pop.encode()).digest() - return ((pop_hash[1] & 0x0f) << 8) + pop_hash[0] # id: 12bit hash - - source_popid = make_id(source) - target_popid = make_id(target) - return source_popid, target_popid - - def get_population_ids( - self, source_population_name: str, target_population_name: str - ) -> tuple[int, int]: - source_popid, target_popid = self._compute_pop_ids( - source_population_name, target_population_name) - return source_popid, target_popid - - def extract_synapses( - self, cell_id: CellId, projections: Optional[list[str] | str] - ) -> pd.DataFrame: - """Extract the synapses. - - If projections is None, all the synapses are extracted. - """ - snap_node_id = CircuitNodeId(cell_id.population_name, cell_id.id) - edges = self._circuit.edges - # select edges that are in the projections, if there are projections - if projections is None or len(projections) == 0: - edge_population_names = list(edges) - elif isinstance(projections, str): - edge_population_names = [x for x in edges if edges[x].source.name == projections] - else: - edge_population_names = [x for x in edges if edges[x].source.name in projections] - - all_synapses_dfs: list[pd.DataFrame] = [] - for edge_population_name in edge_population_names: - edge_population = edges[edge_population_name] - afferent_edges: CircuitEdgeIds = edge_population.afferent_edges(snap_node_id) - if len(afferent_edges) != 0: - # first copy the common properties to modify them - edge_properties: list[SynapseProperty | str] = list( - SynapseProperties.common - ) - - # remove optional properties if they are not present - for optional_property in [SynapseProperty.U_HILL_COEFFICIENT, - SynapseProperty.CONDUCTANCE_RATIO]: - if optional_property.to_snap() not in edge_population.property_names: - edge_properties.remove(optional_property) - - # if all plasticity props are present, add them - if all( - x in edge_population.property_names - for x in SynapseProperties.plasticity - ): - edge_properties += list(SynapseProperties.plasticity) - - snap_properties = properties_to_snap(edge_properties) - synapses: pd.DataFrame = edge_population.get(afferent_edges, snap_properties) - column_names = list(synapses.columns) - synapses.columns = pd.Index(properties_from_snap(column_names)) - - # make multiindex - synapses = synapses.reset_index(drop=True) - synapses.index = pd.MultiIndex.from_tuples( - zip([edge_population_name] * len(synapses), synapses.index), - names=["edge_name", "synapse_id"], - ) - - # add source_population_name as a column - synapses["source_population_name"] = edges[edge_population_name].source.name - - # py-neurodamus - dt = neuron.h.dt - synapses[SynapseProperty.AXONAL_DELAY] = ( - synapses[SynapseProperty.AXONAL_DELAY] / dt + 1e-5 - ).astype('i4') * dt - - if SynapseProperty.NRRP in synapses: - circuit.validate.check_nrrp_value(synapses) - - source_popid, target_popid = self.get_population_ids( - edge_population.source.name, edge_population.target.name) - synapses = synapses.assign( - source_popid=source_popid, target_popid=target_popid - ) - - all_synapses_dfs.append(synapses) - - if len(all_synapses_dfs) == 0: - return pd.DataFrame() - else: - return pd.concat(all_synapses_dfs) # outer join that creates NaNs - - def target_contains_cell(self, target: str, cell_id: CellId) -> bool: - return cell_id in self.get_target_cell_ids(target) - - @lru_cache(maxsize=1000) - def is_valid_group(self, group: str) -> bool: - return group in self._circuit.node_sets - - @lru_cache(maxsize=16) - def get_target_cell_ids(self, target: str) -> set[CellId]: - ids = self._circuit.nodes.ids(target) - return {CellId(x.population, x.id) for x in ids} - - @lru_cache(maxsize=100) - def fetch_cell_info(self, cell_id: CellId) -> pd.Series: - return self._circuit.nodes[cell_id.population_name].get(cell_id.id) - - def fetch_mini_frequencies(self, cell_id: CellId) -> tuple[float | None, float | None]: - cell_info = self.fetch_cell_info(cell_id) - exc_mini_frequency = cell_info['exc-mini_frequency'] \ - if 'exc-mini_frequency' in cell_info else None - inh_mini_frequency = cell_info['inh-mini_frequency'] \ - if 'inh-mini_frequency' in cell_info else None - return exc_mini_frequency, inh_mini_frequency - - @property - def node_properties_available(self) -> bool: - return True - - def get_gids_of_mtypes(self, mtypes: list[str]) -> set[CellId]: - all_cell_ids = set() - all_population_names: list[str] = list(self._circuit.nodes) - for population_name in all_population_names: - try: - cell_ids = self._circuit.nodes[population_name].ids( - {SnapCell.MTYPE: mtypes}) - except BluepySnapError: - continue - all_cell_ids |= {CellId(population_name, id) for id in cell_ids} - return all_cell_ids - - def get_cell_ids_of_targets(self, targets: list[str]) -> set[CellId]: - cell_ids = set() - for target in targets: - cell_ids |= self.get_target_cell_ids(target) - return cell_ids - - def morph_filepath(self, cell_id: CellId) -> str: - """Returns the .asc morphology path from 'alternate_morphologies'.""" - node_population = self._circuit.nodes[cell_id.population_name] - try: # if asc defined in alternate morphology - return str(node_population.morph.get_filepath(cell_id.id, extension="asc")) - except BluepySnapError as e: - logger.debug(f"No asc morphology found for {cell_id}, trying swc.") - return str(node_population.morph.get_filepath(cell_id.id)) - - def emodel_path(self, cell_id: CellId) -> str: - node_population = self._circuit.nodes[cell_id.population_name] - return str(node_population.models.get_filepath(cell_id.id)) diff --git a/bluecellulab/circuit/circuit_access/definition.py b/bluecellulab/circuit/circuit_access/definition.py new file mode 100644 index 00000000..8f9855a9 --- /dev/null +++ b/bluecellulab/circuit/circuit_access/definition.py @@ -0,0 +1,124 @@ +# Copyright 2012-2024 Blue Brain Project / EPFL + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Layer to abstract the circuit access functionality from the rest of +bluecellulab.""" + +from __future__ import annotations +from collections import defaultdict +from typing import Any, Optional, Protocol + + +import pandas as pd +from pydantic.dataclasses import dataclass + +from bluecellulab.circuit import CellId +from bluecellulab.circuit.config import SimulationConfig + + +@dataclass(config=dict(extra="forbid")) +class EmodelProperties: + threshold_current: float + holding_current: float + AIS_scaler: Optional[float] = 1.0 + soma_scaler: Optional[float] = 1.0 + + +def get_synapse_connection_parameters( + circuit_access: CircuitAccess, pre_cell: CellId, post_cell: CellId) -> dict: + """Apply connection blocks in order for pre_gid, post_gid to determine a + final connection override for this pair (pre_gid, post_gid).""" + parameters: defaultdict[str, Any] = defaultdict(list) + parameters['add_synapse'] = True + + for entry in circuit_access.config.connection_entries(): + src_matches = circuit_access.target_contains_cell(entry.source, pre_cell) + dest_matches = circuit_access.target_contains_cell(entry.target, post_cell) + + if src_matches and dest_matches: + # whatever specified in this block, is applied to gid + apply_parameters = True + + if entry.delay is not None: + parameters['DelayWeights'].append((entry.delay, entry.weight)) + apply_parameters = False + + if apply_parameters: + if entry.weight is not None: + parameters['Weight'] = entry.weight + if entry.spont_minis is not None: + parameters['SpontMinis'] = entry.spont_minis + if entry.synapse_configure is not None: + # collect list of applicable configure blocks to be + # applied with a "hoc exec" statement + parameters['SynapseConfigure'].append(entry.synapse_configure) + if entry.mod_override is not None: + parameters['ModOverride'] = entry.mod_override + return parameters + + +class CircuitAccess(Protocol): + """Protocol that defines the circuit access layer.""" + + config: SimulationConfig + + @property + def available_cell_properties(self) -> set: + raise NotImplementedError + + def get_emodel_properties(self, cell_id: CellId) -> Optional[EmodelProperties]: + raise NotImplementedError + + def get_template_format(self) -> Optional[str]: + raise NotImplementedError + + def get_cell_properties( + self, cell_id: CellId, properties: list[str] | str + ) -> pd.Series: + raise NotImplementedError + + def extract_synapses( + self, cell_id: CellId, projections: Optional[list[str] | str] + ) -> pd.DataFrame: + raise NotImplementedError + + def target_contains_cell(self, target: str, cell_id: CellId) -> bool: + raise NotImplementedError + + def is_valid_group(self, group: str) -> bool: + raise NotImplementedError + + def get_target_cell_ids(self, target: str) -> set[CellId]: + raise NotImplementedError + + def fetch_cell_info(self, cell_id: CellId) -> pd.Series: + raise NotImplementedError + + def fetch_mini_frequencies(self, cell_id: CellId) -> tuple[float | None, float | None]: + raise NotImplementedError + + @property + def node_properties_available(self) -> bool: + raise NotImplementedError + + def get_gids_of_mtypes(self, mtypes: list[str]) -> set[CellId]: + raise NotImplementedError + + def get_cell_ids_of_targets(self, targets: list[str]) -> set[CellId]: + raise NotImplementedError + + def morph_filepath(self, cell_id: CellId) -> str: + raise NotImplementedError + + def emodel_path(self, cell_id: CellId) -> str: + raise NotImplementedError diff --git a/bluecellulab/circuit/circuit_access/sonata_circuit_access.py b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py new file mode 100644 index 00000000..1b0c60da --- /dev/null +++ b/bluecellulab/circuit/circuit_access/sonata_circuit_access.py @@ -0,0 +1,241 @@ +# Copyright 2012-2024 Blue Brain Project / EPFL + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations +import hashlib +from functools import lru_cache +import logging +from pathlib import Path +from typing import Optional + +from bluepysnap.bbp import Cell as SnapCell +from bluepysnap.circuit_ids import CircuitNodeId, CircuitEdgeIds +from bluepysnap.exceptions import BluepySnapError +from bluepysnap import Circuit as SnapCircuit +import pandas as pd +from bluecellulab import circuit, neuron +from bluecellulab.circuit.circuit_access.definition import EmodelProperties +from bluecellulab.circuit import CellId, SynapseProperty +from bluecellulab.circuit.config import SimulationConfig +from bluecellulab.circuit.synapse_properties import SynapseProperties +from bluecellulab.circuit.config import SimulationConfig, SonataSimulationConfig +from bluecellulab.circuit.synapse_properties import ( + properties_from_snap, + properties_to_snap, +) + +logger = logging.getLogger(__name__) + + +class SonataCircuitAccess: + """Sonata implementation of CircuitAccess protocol.""" + + def __init__(self, simulation_config: str | Path | SimulationConfig) -> None: + """Initialize SonataCircuitAccess object.""" + if isinstance(simulation_config, (str, Path)) and not Path(simulation_config).exists(): + raise FileNotFoundError(f"Circuit config file {simulation_config} not found.") + + if isinstance(simulation_config, SonataSimulationConfig): + self.config: SimulationConfig = simulation_config + else: + self.config = SonataSimulationConfig(simulation_config) + circuit_config = self.config.impl.config["network"] + self._circuit = SnapCircuit(circuit_config) + + @property + def available_cell_properties(self) -> set: + return self._circuit.nodes.property_names + + def get_emodel_properties(self, cell_id: CellId) -> Optional[EmodelProperties]: + cell_properties = self._circuit.nodes[cell_id.population_name].get(cell_id.id) + if "@dynamics:AIS_scaler" in cell_properties: + AIS_scaler = cell_properties["@dynamics:AIS_scaler"] + else: + AIS_scaler = 1.0 + if "@dynamics:soma_scaler" in cell_properties: + soma_scaler = cell_properties["@dynamics:soma_scaler"] + else: + soma_scaler = 1.0 + + return EmodelProperties( + cell_properties["@dynamics:threshold_current"], + cell_properties["@dynamics:holding_current"], + AIS_scaler, + soma_scaler, + ) + + def get_template_format(self) -> Optional[str]: + return 'v6' + + def get_cell_properties( + self, cell_id: CellId, properties: list[str] | str + ) -> pd.Series: + if isinstance(properties, str): + properties = [properties] + return self._circuit.nodes[cell_id.population_name].get( + cell_id.id, properties=properties + ) + + @staticmethod + def _compute_pop_ids(source: str, target: str) -> tuple[int, int]: + """Compute the population ids from the population names.""" + def make_id(node_pop: str) -> int: + pop_hash = hashlib.md5(node_pop.encode()).digest() + return ((pop_hash[1] & 0x0f) << 8) + pop_hash[0] # id: 12bit hash + + source_popid = make_id(source) + target_popid = make_id(target) + return source_popid, target_popid + + def get_population_ids( + self, source_population_name: str, target_population_name: str + ) -> tuple[int, int]: + source_popid, target_popid = self._compute_pop_ids( + source_population_name, target_population_name) + return source_popid, target_popid + + def extract_synapses( + self, cell_id: CellId, projections: Optional[list[str] | str] + ) -> pd.DataFrame: + """Extract the synapses. + + If projections is None, all the synapses are extracted. + """ + snap_node_id = CircuitNodeId(cell_id.population_name, cell_id.id) + edges = self._circuit.edges + # select edges that are in the projections, if there are projections + if projections is None or len(projections) == 0: + edge_population_names = [x for x in edges] + elif isinstance(projections, str): + edge_population_names = [x for x in edges if edges[x].source.name == projections] + else: + edge_population_names = [x for x in edges if edges[x].source.name in projections] + + all_synapses_dfs: list[pd.DataFrame] = [] + for edge_population_name in edge_population_names: + edge_population = edges[edge_population_name] + afferent_edges: CircuitEdgeIds = edge_population.afferent_edges(snap_node_id) + if len(afferent_edges) != 0: + # first copy the common properties to modify them + edge_properties: list[SynapseProperty | str] = list( + SynapseProperties.common + ) + + # remove optional properties if they are not present + for optional_property in [SynapseProperty.U_HILL_COEFFICIENT, + SynapseProperty.CONDUCTANCE_RATIO]: + if optional_property.to_snap() not in edge_population.property_names: + edge_properties.remove(optional_property) + + # if all plasticity props are present, add them + if all( + x in edge_population.property_names + for x in SynapseProperties.plasticity + ): + edge_properties += list(SynapseProperties.plasticity) + + snap_properties = properties_to_snap(edge_properties) + synapses: pd.DataFrame = edge_population.get(afferent_edges, snap_properties) + column_names = list(synapses.columns) + synapses.columns = pd.Index(properties_from_snap(column_names)) + + # make multiindex + synapses = synapses.reset_index(drop=True) + synapses.index = pd.MultiIndex.from_tuples( + zip([edge_population_name] * len(synapses), synapses.index), + names=["edge_name", "synapse_id"], + ) + + # add source_population_name as a column + synapses["source_population_name"] = edges[edge_population_name].source.name + + # py-neurodamus + dt = neuron.h.dt + synapses[SynapseProperty.AXONAL_DELAY] = ( + synapses[SynapseProperty.AXONAL_DELAY] / dt + 1e-5 + ).astype('i4') * dt + + if SynapseProperty.NRRP in synapses: + circuit.validate.check_nrrp_value(synapses) + + source_popid, target_popid = self.get_population_ids( + edge_population.source.name, edge_population.target.name) + synapses = synapses.assign( + source_popid=source_popid, target_popid=target_popid + ) + + all_synapses_dfs.append(synapses) + + if len(all_synapses_dfs) == 0: + return pd.DataFrame() + else: + return pd.concat(all_synapses_dfs) # outer join that creates NaNs + + def target_contains_cell(self, target: str, cell_id: CellId) -> bool: + return cell_id in self.get_target_cell_ids(target) + + @lru_cache(maxsize=1000) + def is_valid_group(self, group: str) -> bool: + return group in self._circuit.node_sets + + @lru_cache(maxsize=16) + def get_target_cell_ids(self, target: str) -> set[CellId]: + ids = self._circuit.nodes.ids(target) + return {CellId(x.population, x.id) for x in ids} + + @lru_cache(maxsize=100) + def fetch_cell_info(self, cell_id: CellId) -> pd.Series: + return self._circuit.nodes[cell_id.population_name].get(cell_id.id) + + def fetch_mini_frequencies(self, cell_id: CellId) -> tuple[float | None, float | None]: + cell_info = self.fetch_cell_info(cell_id) + exc_mini_frequency = cell_info['exc-mini_frequency'] \ + if 'exc-mini_frequency' in cell_info else None + inh_mini_frequency = cell_info['inh-mini_frequency'] \ + if 'inh-mini_frequency' in cell_info else None + return exc_mini_frequency, inh_mini_frequency + + @property + def node_properties_available(self) -> bool: + return True + + def get_gids_of_mtypes(self, mtypes: list[str]) -> set[CellId]: + all_cell_ids = set() + all_population_names: list[str] = list(self._circuit.nodes) + for population_name in all_population_names: + try: + cell_ids = self._circuit.nodes[population_name].ids( + {SnapCell.MTYPE: mtypes}) + except BluepySnapError: + continue + all_cell_ids |= {CellId(population_name, id) for id in cell_ids} + return all_cell_ids + + def get_cell_ids_of_targets(self, targets: list[str]) -> set[CellId]: + cell_ids = set() + for target in targets: + cell_ids |= self.get_target_cell_ids(target) + return cell_ids + + def morph_filepath(self, cell_id: CellId) -> str: + """Returns the .asc morphology path from 'alternate_morphologies'.""" + node_population = self._circuit.nodes[cell_id.population_name] + try: # if asc defined in alternate morphology + return str(node_population.morph.get_filepath(cell_id.id, extension="asc")) + except BluepySnapError as e: + logger.debug(f"No asc morphology found for {cell_id}, trying swc.") + return str(node_population.morph.get_filepath(cell_id.id)) + + def emodel_path(self, cell_id: CellId) -> str: + node_population = self._circuit.nodes[cell_id.population_name] + return str(node_population.models.get_filepath(cell_id.id)) diff --git a/bluecellulab/circuit/config/__init__.py b/bluecellulab/circuit/config/__init__.py index 25c54fb7..a30c4e9e 100644 --- a/bluecellulab/circuit/config/__init__.py +++ b/bluecellulab/circuit/config/__init__.py @@ -1,3 +1,5 @@ """Simulation configuration file parser module.""" -from .simulation_config import BluepySimulationConfig, SimulationConfig, SonataSimulationConfig +from .definition import SimulationConfig +from .bluepy_simulation_config import BluepySimulationConfig +from .sonata_simulation_config import SonataSimulationConfig diff --git a/bluecellulab/circuit/config/simulation_config.py b/bluecellulab/circuit/config/bluepy_simulation_config.py similarity index 52% rename from bluecellulab/circuit/config/simulation_config.py rename to bluecellulab/circuit/config/bluepy_simulation_config.py index af1c1085..2ff153fc 100644 --- a/bluecellulab/circuit/config/simulation_config.py +++ b/bluecellulab/circuit/config/bluepy_simulation_config.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,112 +11,29 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Module that interacts with the simulation config.""" - from __future__ import annotations -from functools import lru_cache -from pathlib import Path -from typing import Optional, Protocol +from typing import Optional +from bluecellulab.exceptions import ExtraDependencyMissingError from bluecellulab import BLUEPY_AVAILABLE -from bluecellulab.exceptions import ExtraDependencyMissingError + +from functools import lru_cache +from pathlib import Path if BLUEPY_AVAILABLE: from bluepy_configfile.configfile import BlueConfig from bluepy.utils import open_utf8 -from bluepysnap import Simulation as SnapSimulation from bluecellulab.circuit.config.sections import Conditions, ConnectionOverrides from bluecellulab.stimuli import Stimulus -class SimulationConfig(Protocol): - """Protocol that defines the simulation config layer.""" - - def get_all_stimuli_entries(self) -> list[Stimulus]: - raise NotImplementedError - - def get_all_projection_names(self) -> list[str]: - raise NotImplementedError - - def condition_parameters(self) -> Conditions: - raise NotImplementedError - - def connection_entries(self) -> list[ConnectionOverrides]: - raise NotImplementedError - - @property - def base_seed(self) -> int: - raise NotImplementedError - - @property - def synapse_seed(self) -> int: - raise NotImplementedError - - @property - def ionchannel_seed(self) -> int: - raise NotImplementedError - - @property - def stimulus_seed(self) -> int: - raise NotImplementedError - - @property - def minis_seed(self) -> int: - raise NotImplementedError - - @property - def rng_mode(self) -> str: - raise NotImplementedError - - @property - def spike_threshold(self) -> float: - raise NotImplementedError - - @property - def spike_location(self) -> str: - raise NotImplementedError - - @property - def duration(self) -> Optional[float]: - raise NotImplementedError - - @property - def dt(self) -> float: - raise NotImplementedError - - @property - def forward_skip(self) -> Optional[float]: - raise NotImplementedError - - @property - def celsius(self) -> float: - raise NotImplementedError - - @property - def v_init(self) -> float: - raise NotImplementedError - - @property - def output_root_path(self) -> str: - raise NotImplementedError - - @property - def extracellular_calcium(self) -> Optional[float]: - raise NotImplementedError - - def add_connection_override( - self, - connection_override: ConnectionOverrides - ) -> None: - raise NotImplementedError - - class BluepySimulationConfig: """Bluepy implementation of SimulationConfig protocol.""" _connection_overrides: list[ConnectionOverrides] = [] - def __init__(self, config: str | BlueConfig) -> None: + def __init__(self, config: str) -> None: + """A str or a BlueConfig object are valid.""" if not BLUEPY_AVAILABLE: raise ExtraDependencyMissingError("bluepy") if isinstance(config, str): @@ -276,129 +193,3 @@ def add_connection_override( connection_override: ConnectionOverrides ) -> None: self._connection_overrides.append(connection_override) - - -class SonataSimulationConfig: - """Sonata implementation of SimulationConfig protocol.""" - _connection_overrides: list[ConnectionOverrides] = [] - - def __init__(self, config: str | Path | SnapSimulation) -> None: - if isinstance(config, (str, Path)): - if not Path(config).exists(): - raise FileNotFoundError(f"Config file {config} not found.") - else: - self.impl = SnapSimulation(config) - elif isinstance(config, SnapSimulation): - self.impl = config - else: - raise TypeError("Invalid config type.") - - def get_all_projection_names(self) -> list[str]: - unique_names = { - n - for n in self.impl.circuit.nodes - if self.impl.circuit.nodes[n].type == "virtual" - } - return list(unique_names) - - def get_all_stimuli_entries(self) -> list[Stimulus]: - result: list[Stimulus] = [] - inputs = self.impl.config.get("inputs") - if inputs is None: - return result - for value in inputs.values(): - stimulus = Stimulus.from_sonata(value) - if stimulus: - result.append(stimulus) - return result - - @lru_cache(maxsize=1) - def condition_parameters(self) -> Conditions: - """Returns parameters of global condition block of sonataconfig.""" - condition_object = self.impl.conditions - return Conditions.from_sonata(condition_object) - - @lru_cache(maxsize=1) - def _connection_entries(self) -> list[ConnectionOverrides]: - result: list[ConnectionOverrides] = [] - if "connection_overrides" not in self.impl.config: - return result - conn_overrides: list = self.impl.config["connection_overrides"] - if conn_overrides is None: - return result - for conn_entry in conn_overrides: - result.append(ConnectionOverrides.from_sonata(conn_entry)) - return result - - def connection_entries(self) -> list[ConnectionOverrides]: - return self._connection_entries() + self._connection_overrides - - @property - def base_seed(self) -> int: - return self.impl.run.random_seed - - @property - def synapse_seed(self) -> int: - return self.impl.run.synapse_seed - - @property - def ionchannel_seed(self) -> int: - return self.impl.run.ionchannel_seed - - @property - def stimulus_seed(self) -> int: - return self.impl.run.stimulus_seed - - @property - def minis_seed(self) -> int: - return self.impl.run.minis_seed - - @property - def rng_mode(self) -> str: - """Only Random123 is supported in SONATA.""" - return "Random123" - - @property - def spike_threshold(self) -> float: - return self.impl.run.spike_threshold - - @property - def spike_location(self) -> str: - return self.impl.conditions.spike_location.name - - @property - def duration(self) -> Optional[float]: - return self.impl.run.tstop - - @property - def dt(self) -> float: - return self.impl.run.dt - - @property - def forward_skip(self) -> Optional[float]: - """forward_skip is removed from SONATA.""" - return None - - @property - def celsius(self) -> float: - value = self.condition_parameters().celsius - return value if value is not None else 34.0 - - @property - def v_init(self) -> float: - value = self.condition_parameters().v_init - return value if value is not None else -65.0 - - @property - def output_root_path(self) -> str: - return self.impl.config["output"]["output_dir"] - - @property - def extracellular_calcium(self) -> Optional[float]: - return self.condition_parameters().extracellular_calcium - - def add_connection_override( - self, - connection_override: ConnectionOverrides - ) -> None: - self._connection_overrides.append(connection_override) diff --git a/bluecellulab/circuit/config/definition.py b/bluecellulab/circuit/config/definition.py new file mode 100644 index 00000000..d5daf397 --- /dev/null +++ b/bluecellulab/circuit/config/definition.py @@ -0,0 +1,103 @@ +# Copyright 2012-2024 Blue Brain Project / EPFL + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module that interacts with the simulation config.""" + +from __future__ import annotations +from typing import Optional, Protocol + + +from bluecellulab.circuit.config.sections import Conditions, ConnectionOverrides +from bluecellulab.stimuli import Stimulus + + +class SimulationConfig(Protocol): + """Protocol that defines the simulation config layer.""" + + def get_all_stimuli_entries(self) -> list[Stimulus]: + raise NotImplementedError + + def get_all_projection_names(self) -> list[str]: + raise NotImplementedError + + def condition_parameters(self) -> Conditions: + raise NotImplementedError + + def connection_entries(self) -> list[ConnectionOverrides]: + raise NotImplementedError + + @property + def base_seed(self) -> int: + raise NotImplementedError + + @property + def synapse_seed(self) -> int: + raise NotImplementedError + + @property + def ionchannel_seed(self) -> int: + raise NotImplementedError + + @property + def stimulus_seed(self) -> int: + raise NotImplementedError + + @property + def minis_seed(self) -> int: + raise NotImplementedError + + @property + def rng_mode(self) -> str: + raise NotImplementedError + + @property + def spike_threshold(self) -> float: + raise NotImplementedError + + @property + def spike_location(self) -> str: + raise NotImplementedError + + @property + def duration(self) -> Optional[float]: + raise NotImplementedError + + @property + def dt(self) -> float: + raise NotImplementedError + + @property + def forward_skip(self) -> Optional[float]: + raise NotImplementedError + + @property + def celsius(self) -> float: + raise NotImplementedError + + @property + def v_init(self) -> float: + raise NotImplementedError + + @property + def output_root_path(self) -> str: + raise NotImplementedError + + @property + def extracellular_calcium(self) -> Optional[float]: + raise NotImplementedError + + def add_connection_override( + self, + connection_override: ConnectionOverrides + ) -> None: + raise NotImplementedError diff --git a/bluecellulab/circuit/config/sections.py b/bluecellulab/circuit/config/sections.py index d589f401..fefa3fb9 100644 --- a/bluecellulab/circuit/config/sections.py +++ b/bluecellulab/circuit/config/sections.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/circuit/config/sonata_simulation_config.py b/bluecellulab/circuit/config/sonata_simulation_config.py new file mode 100644 index 00000000..89317757 --- /dev/null +++ b/bluecellulab/circuit/config/sonata_simulation_config.py @@ -0,0 +1,148 @@ +# Copyright 2012-2024 Blue Brain Project / EPFL + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations +from functools import lru_cache +from pathlib import Path +from typing import Optional + +from bluecellulab.circuit.config.sections import Conditions, ConnectionOverrides +from bluecellulab.stimuli import Stimulus + +from bluepysnap import Simulation as SnapSimulation + + +class SonataSimulationConfig: + """Sonata implementation of SimulationConfig protocol.""" + _connection_overrides: list[ConnectionOverrides] = [] + + def __init__(self, config: str | Path | SnapSimulation) -> None: + if isinstance(config, (str, Path)): + if not Path(config).exists(): + raise FileNotFoundError(f"Config file {config} not found.") + else: + self.impl = SnapSimulation(config) + elif isinstance(config, SnapSimulation): + self.impl = config + else: + raise TypeError("Invalid config type.") + + def get_all_projection_names(self) -> list[str]: + unique_names = { + n + for n in self.impl.circuit.nodes + if self.impl.circuit.nodes[n].type == "virtual" + } + return list(unique_names) + + def get_all_stimuli_entries(self) -> list[Stimulus]: + result: list[Stimulus] = [] + inputs = self.impl.config.get("inputs") + if inputs is None: + return result + for value in inputs.values(): + stimulus = Stimulus.from_sonata(value) + if stimulus: + result.append(stimulus) + return result + + @lru_cache(maxsize=1) + def condition_parameters(self) -> Conditions: + """Returns parameters of global condition block of sonataconfig.""" + condition_object = self.impl.conditions + return Conditions.from_sonata(condition_object) + + @lru_cache(maxsize=1) + def _connection_entries(self) -> list[ConnectionOverrides]: + result: list[ConnectionOverrides] = [] + if "connection_overrides" not in self.impl.config: + return result + conn_overrides: list = self.impl.config["connection_overrides"] + if conn_overrides is None: + return result + for conn_entry in conn_overrides: + result.append(ConnectionOverrides.from_sonata(conn_entry)) + return result + + def connection_entries(self) -> list[ConnectionOverrides]: + return self._connection_entries() + self._connection_overrides + + @property + def base_seed(self) -> int: + return self.impl.run.random_seed + + @property + def synapse_seed(self) -> int: + return self.impl.run.synapse_seed + + @property + def ionchannel_seed(self) -> int: + return self.impl.run.ionchannel_seed + + @property + def stimulus_seed(self) -> int: + return self.impl.run.stimulus_seed + + @property + def minis_seed(self) -> int: + return self.impl.run.minis_seed + + @property + def rng_mode(self) -> str: + """Only Random123 is supported in SONATA.""" + return "Random123" + + @property + def spike_threshold(self) -> float: + return self.impl.run.spike_threshold + + @property + def spike_location(self) -> str: + return self.impl.conditions.spike_location.name + + @property + def duration(self) -> Optional[float]: + return self.impl.run.tstop + + @property + def dt(self) -> float: + return self.impl.run.dt + + @property + def forward_skip(self) -> Optional[float]: + """forward_skip is removed from SONATA.""" + return None + + @property + def celsius(self) -> float: + value = self.condition_parameters().celsius + return value if value is not None else 34.0 + + @property + def v_init(self) -> float: + value = self.condition_parameters().v_init + return value if value is not None else -65.0 + + @property + def output_root_path(self) -> str: + return self.impl.config["output"]["output_dir"] + + @property + def extracellular_calcium(self) -> Optional[float]: + return self.condition_parameters().extracellular_calcium + + def add_connection_override( + self, + connection_override: ConnectionOverrides + ) -> None: + self._connection_overrides.append(connection_override) diff --git a/bluecellulab/circuit/format.py b/bluecellulab/circuit/format.py index 4c43efee..1295aafe 100644 --- a/bluecellulab/circuit/format.py +++ b/bluecellulab/circuit/format.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/circuit/iotools.py b/bluecellulab/circuit/iotools.py index 1d820070..bc822574 100644 --- a/bluecellulab/circuit/iotools.py +++ b/bluecellulab/circuit/iotools.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/circuit/node_id.py b/bluecellulab/circuit/node_id.py index 51cd0219..ab36021c 100644 --- a/bluecellulab/circuit/node_id.py +++ b/bluecellulab/circuit/node_id.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/circuit/simulation_access.py b/bluecellulab/circuit/simulation_access.py index 98f96159..a34df2a1 100644 --- a/bluecellulab/circuit/simulation_access.py +++ b/bluecellulab/circuit/simulation_access.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -91,7 +91,7 @@ def __init__(self, sim_config: str | Path | SimulationConfig) -> None: f"Circuit config file {sim_config} not found.") self.impl = bluepy.Simulation(sim_config) - self._config = BluepySimulationConfig(sim_config) + self._config = BluepySimulationConfig(sim_config) # type: ignore def get_soma_voltage( self, cell_id: CellId, t_start: float, t_end: float, t_step: Optional[float] = None diff --git a/bluecellulab/circuit/synapse_properties.py b/bluecellulab/circuit/synapse_properties.py index 5d4b2cf0..aed798a1 100644 --- a/bluecellulab/circuit/synapse_properties.py +++ b/bluecellulab/circuit/synapse_properties.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/circuit/validate.py b/bluecellulab/circuit/validate.py index 8f3b4142..56cb37e0 100644 --- a/bluecellulab/circuit/validate.py +++ b/bluecellulab/circuit/validate.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/connection.py b/bluecellulab/connection.py index 644b2ec3..323b3025 100644 --- a/bluecellulab/connection.py +++ b/bluecellulab/connection.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/dendrogram.py b/bluecellulab/dendrogram.py index 97e4f4a3..a0795df1 100644 --- a/bluecellulab/dendrogram.py +++ b/bluecellulab/dendrogram.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/exceptions.py b/bluecellulab/exceptions.py index 2288ae4f..c89de860 100644 --- a/bluecellulab/exceptions.py +++ b/bluecellulab/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/importer.py b/bluecellulab/importer.py index 0d9bb4db..d982ca22 100644 --- a/bluecellulab/importer.py +++ b/bluecellulab/importer.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/neuron_interpreter.py b/bluecellulab/neuron_interpreter.py index 24360810..0b5a167f 100644 --- a/bluecellulab/neuron_interpreter.py +++ b/bluecellulab/neuron_interpreter.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/plotwindow.py b/bluecellulab/plotwindow.py index 86dc8a6a..fe6cd806 100644 --- a/bluecellulab/plotwindow.py +++ b/bluecellulab/plotwindow.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/psection.py b/bluecellulab/psection.py index a9ab8c6c..16262f7e 100644 --- a/bluecellulab/psection.py +++ b/bluecellulab/psection.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/psegment.py b/bluecellulab/psegment.py index 0a1f5a34..9fba721e 100644 --- a/bluecellulab/psegment.py +++ b/bluecellulab/psegment.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/rngsettings.py b/bluecellulab/rngsettings.py index fe4925b8..efa52933 100644 --- a/bluecellulab/rngsettings.py +++ b/bluecellulab/rngsettings.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/simulation/neuron_globals.py b/bluecellulab/simulation/neuron_globals.py index e557f7eb..cd6243aa 100644 --- a/bluecellulab/simulation/neuron_globals.py +++ b/bluecellulab/simulation/neuron_globals.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/simulation/simulation.py b/bluecellulab/simulation/simulation.py index 122ded34..42c80078 100644 --- a/bluecellulab/simulation/simulation.py +++ b/bluecellulab/simulation/simulation.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/ssim.py b/bluecellulab/ssim.py index 8a51342b..3c68b0b5 100644 --- a/bluecellulab/ssim.py +++ b/bluecellulab/ssim.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/stimuli.py b/bluecellulab/stimuli.py index 9b8c7a01..e08dac35 100644 --- a/bluecellulab/stimuli.py +++ b/bluecellulab/stimuli.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/synapse/synapse_factory.py b/bluecellulab/synapse/synapse_factory.py index 66fc61de..9a7939c7 100644 --- a/bluecellulab/synapse/synapse_factory.py +++ b/bluecellulab/synapse/synapse_factory.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/synapse/synapse_types.py b/bluecellulab/synapse/synapse_types.py index 75c700b1..dc5848d3 100644 --- a/bluecellulab/synapse/synapse_types.py +++ b/bluecellulab/synapse/synapse_types.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/bluecellulab/tools.py b/bluecellulab/tools.py index ce49c53e..67255616 100644 --- a/bluecellulab/tools.py +++ b/bluecellulab/tools.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pyproject.toml b/pyproject.toml index 792d31f0..11d11811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,10 @@ plugins = ["pydantic.mypy"] [tool.coverage.run] concurrency = ["multiprocessing"] parallel = true +omit = [ + "bluecellulab/circuit/circuit_access/bluepy_circuit_access.py", + "bluecellulab/circuit/config/bluepy_simulation_config.py" +] [tool.coverage.report] exclude_lines = [ diff --git a/setup.py b/setup.py index b9cb4993..789db18c 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2012-2023 Blue Brain Project / EPFL +# Copyright 2012-2024 Blue Brain Project / EPFL # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_circuit/test_format.py b/tests/test_circuit/test_format.py index 4ffe319d..2aa5643f 100644 --- a/tests/test_circuit/test_format.py +++ b/tests/test_circuit/test_format.py @@ -1,7 +1,7 @@ """Unit tests for circuit::format.py.""" from pathlib import Path from bluecellulab.circuit.format import CircuitFormat, determine_circuit_format, is_valid_json_file -from bluecellulab.circuit.config.simulation_config import SonataSimulationConfig +from bluecellulab.circuit.config import SonataSimulationConfig script_dir = Path(__file__).resolve().parent.parent diff --git a/tox.ini b/tox.ini index 79dbfca0..77bcb68b 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ allowlist_externals = make ./.compile_mod.sh coverage -coverage_options = --cov-report=xml --cov-config=.coveragerc --cov={[base]name} --cov=tests +coverage_options = --cov-report=xml --cov={[base]name} --cov=tests setenv = NEURON_MODULE_OPTIONS='-nogui' OMP_NUM_THREADS=1