bluecellulab 2.6.71__py3-none-any.whl → 2.6.73__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bluecellulab might be problematic. Click here for more details.

bluecellulab/cell/core.py CHANGED
@@ -19,7 +19,7 @@ import logging
19
19
 
20
20
  from pathlib import Path
21
21
  import queue
22
- from typing import Optional
22
+ from typing import List, Optional, Tuple
23
23
  from typing_extensions import deprecated
24
24
 
25
25
  import neuron
@@ -793,13 +793,13 @@ class Cell(InjectableMixin, PlottableMixin):
793
793
  mech, var = variable.split(".", 1)
794
794
  mobj = getattr(seg, mech, None)
795
795
  if mobj is None or not hasattr(mobj, f"_ref_{var}"):
796
- raise ValueError(
796
+ raise AttributeError(
797
797
  f"'{variable}' not recordable at {section.name()}({segx}). "
798
798
  f"Mechanisms here: {list(section.psection()['density_mechs'].keys())}"
799
799
  )
800
800
  else:
801
801
  if not hasattr(seg, f"_ref_{variable}"):
802
- raise ValueError(
802
+ raise AttributeError(
803
803
  f"'{variable}' not recordable at {section.name()}({segx}). "
804
804
  f"(Top-level vars are typically v/ina/ik/ica)"
805
805
  )
@@ -911,6 +911,150 @@ class Cell(InjectableMixin, PlottableMixin):
911
911
  def __del__(self):
912
912
  self.delete()
913
913
 
914
+ def get_section(self, section_name: str) -> NeuronSection:
915
+ """Return a single, fully specified NEURON section (e.g., 'soma[0]',
916
+ 'dend[3]').
917
+
918
+ Raises:
919
+ ValueError or TypeError if the section is not found or invalid.
920
+ """
921
+ if section_name in self.sections:
922
+ section = self.sections[section_name]
923
+ if hasattr(section, "nseg"):
924
+ return section
925
+ raise TypeError(f"'{section_name}' exists but is not a NEURON section.")
926
+
927
+ available = ", ".join(self.sections.keys())
928
+ raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
929
+
930
+ def get_sections(self, section_name: str) -> List[NeuronSection]:
931
+ """Return a list of NEURON sections.
932
+
933
+ If the section name is a fully specified one (e.g., 'dend[3]'), return it as a list of one.
934
+ If the section name is a base name (e.g., 'dend'), return all matching sections like 'dend[0]', 'dend[1]', etc.
935
+
936
+ Raises:
937
+ ValueError if no valid sections are found.
938
+ """
939
+ # Try to interpret as fully qualified section name
940
+ try:
941
+ return [self.get_section(section_name)]
942
+ except ValueError:
943
+ pass # Not a precise match; try prefix match
944
+
945
+ # Fallback to prefix-based match (e.g., 'dend' → 'dend[0]', 'dend[1]', ...)
946
+ matched = [
947
+ section for name, section in self.sections.items()
948
+ if name.startswith(f"{section_name}[")
949
+ ]
950
+ if matched:
951
+ return matched
952
+
953
+ available = ", ".join(self.sections.keys())
954
+ raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
955
+
956
+ def get_section_by_id(self, section_id: int) -> NeuronSection:
957
+ """Return NEURON section by global section index (LibSONATA
958
+ ordering)."""
959
+ if not self.psections:
960
+ self._init_psections()
961
+
962
+ try:
963
+ return self.psections[int(section_id)].hsection
964
+ except KeyError:
965
+ raise IndexError(f"Section ID {section_id} is out of range for cell {self.cell_id.id}")
966
+
967
+ def resolve_segments_from_compartment_set(self, node_id, compartment_nodes) -> List[Tuple[NeuronSection, str, float]]:
968
+ """Resolve segments for a cell using a predefined compartment node
969
+ list.
970
+
971
+ Supports both LibSONATA format ([node_id, section_id, seg]) and
972
+ name-based format ([node_id, section_name, seg]).
973
+ """
974
+ result = []
975
+ for n_id, sec_ref, seg in compartment_nodes:
976
+ if n_id != node_id:
977
+ continue
978
+
979
+ if isinstance(sec_ref, str):
980
+ # Name-based: e.g., "dend[5]"
981
+ section = self.get_section(sec_ref)
982
+ sec_name = section.name().split(".")[-1]
983
+ elif isinstance(sec_ref, int):
984
+ # ID-based: resolve by section index
985
+ try:
986
+ section = self.get_section_by_id(sec_ref)
987
+ sec_name = section.name().split(".")[-1]
988
+ except AttributeError:
989
+ raise ValueError(f"Cell object does not support section lookup by index: {sec_ref}")
990
+ else:
991
+ raise TypeError(f"Unsupported section reference type: {type(sec_ref)}")
992
+
993
+ result.append((section, sec_name, seg))
994
+ return result
995
+
996
+ def resolve_segments_from_config(self, report_cfg) -> List[Tuple[NeuronSection, str, float]]:
997
+ """Resolve segments from NEURON sections based on config."""
998
+ compartment = report_cfg.get("compartments", "center")
999
+ if compartment not in {"center", "all"}:
1000
+ raise ValueError(
1001
+ f"Unsupported 'compartments' value '{compartment}' — must be 'center' or 'all'."
1002
+ )
1003
+
1004
+ section_name = report_cfg.get("sections", "soma")
1005
+ sections = self.get_sections(section_name)
1006
+
1007
+ targets = []
1008
+ for sec in sections:
1009
+ sec_name = sec.name().split(".")[-1]
1010
+ if compartment == "center":
1011
+ targets.append((sec, sec_name, 0.5))
1012
+ elif compartment == "all":
1013
+ for seg in sec:
1014
+ targets.append((sec, sec_name, seg.x))
1015
+ return targets
1016
+
1017
+ def configure_recording(self, recording_sites, variable_name, report_name):
1018
+ """Configure recording of a variable on a single cell.
1019
+
1020
+ This function sets up the recording of the specified variable (e.g., membrane voltage)
1021
+ in the target cell, for each resolved segment.
1022
+
1023
+ Parameters
1024
+ ----------
1025
+ cell : Any
1026
+ The cell object on which to configure recordings.
1027
+
1028
+ recording_sites : list of tuples
1029
+ List of tuples (section, section_name, segment) where:
1030
+ - section is the section object in the cell.
1031
+ - section_name is the name of the section.
1032
+ - segment is the Neuron segment index (0-1).
1033
+
1034
+ variable_name : str
1035
+ The name of the variable to record (e.g., "v" for membrane voltage).
1036
+
1037
+ report_name : str
1038
+ The name of the report (used in logging).
1039
+ """
1040
+ node_id = self.cell_id.id
1041
+
1042
+ for sec, sec_name, seg in recording_sites:
1043
+ try:
1044
+ self.add_variable_recording(variable=variable_name, section=sec, segx=seg)
1045
+ logger.info(
1046
+ f"Recording '{variable_name}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}'"
1047
+ )
1048
+ except AttributeError:
1049
+ logger.warning(
1050
+ f"Recording for variable '{variable_name}' is not implemented in Cell."
1051
+ )
1052
+ return
1053
+ except Exception as e:
1054
+ logger.warning(
1055
+ f"Failed to record '{variable_name}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}': {e}"
1056
+ )
1057
+
914
1058
  def add_currents_recordings(
915
1059
  self,
916
1060
  section,
@@ -19,6 +19,7 @@ import os
19
19
  from pathlib import Path
20
20
  from typing import Optional
21
21
 
22
+ from bluecellulab.circuit.circuit_access.definition import CircuitAccess
22
23
  import neuron
23
24
  import pandas as pd
24
25
  from bluecellulab import circuit
@@ -42,7 +43,7 @@ if BLUEPY_AVAILABLE:
42
43
  logger = logging.getLogger(__name__)
43
44
 
44
45
 
45
- class BluepyCircuitAccess:
46
+ class BluepyCircuitAccess(CircuitAccess):
46
47
  """Bluepy implementation of CircuitAccess protocol."""
47
48
 
48
49
  def __init__(self, simulation_config: str | Path | SimulationConfig) -> None:
@@ -25,7 +25,7 @@ from bluepysnap import Circuit as SnapCircuit
25
25
  import neuron
26
26
  import pandas as pd
27
27
  from bluecellulab import circuit
28
- from bluecellulab.circuit.circuit_access.definition import EmodelProperties
28
+ from bluecellulab.circuit.circuit_access.definition import CircuitAccess, EmodelProperties
29
29
  from bluecellulab.circuit import CellId, SynapseProperty
30
30
  from bluecellulab.circuit.config import SimulationConfig
31
31
  from bluecellulab.circuit.synapse_properties import SynapseProperties
@@ -38,7 +38,7 @@ from bluecellulab.circuit.synapse_properties import (
38
38
  logger = logging.getLogger(__name__)
39
39
 
40
40
 
41
- class SonataCircuitAccess:
41
+ class SonataCircuitAccess(CircuitAccess):
42
42
  """Sonata implementation of CircuitAccess protocol."""
43
43
 
44
44
  def __init__(self, simulation_config: str | Path | SimulationConfig) -> None:
@@ -188,6 +188,16 @@ class BluepySimulationConfig:
188
188
  else:
189
189
  return None
190
190
 
191
+ @property
192
+ def tstart(self) -> Optional[float]:
193
+ return 0.0
194
+
195
+ @property
196
+ def tstop(self) -> Optional[float]:
197
+ if 'Duration' in self.impl.Run:
198
+ return float(self.impl.Run['Duration'])
199
+ return None
200
+
191
201
  def add_connection_override(
192
202
  self,
193
203
  connection_override: ConnectionOverrides
@@ -130,6 +130,8 @@ class SonataSimulationConfig:
130
130
  """Resolve the full path for the report output file."""
131
131
  output_dir = Path(self.output_root_path)
132
132
  file_name = report_cfg.get("file_name", f"{report_key}.h5")
133
+ if not file_name.endswith(".h5"):
134
+ file_name += ".h5"
133
135
  return output_dir / file_name
134
136
 
135
137
  @property
@@ -31,6 +31,6 @@ def create_cell_id(cell_id: int | tuple[str, int] | CellId) -> CellId:
31
31
  return CellId("", cell_id)
32
32
 
33
33
 
34
- def create_cell_ids(cell_ids: list[int] | list[tuple[str, int] | CellId]) -> list[CellId]:
34
+ def create_cell_ids(cell_ids: list[int | tuple[str, int] | CellId]) -> list[CellId]:
35
35
  """Make a list of CellId from a list of tuple or int."""
36
36
  return [create_cell_id(cell_id) for cell_id in cell_ids]
@@ -125,7 +125,7 @@ class CircuitSimulation:
125
125
 
126
126
  def instantiate_gids(
127
127
  self,
128
- cells: int | tuple[str, int] | list[int] | list[tuple[str, int]],
128
+ cells: int | tuple[str, int] | list[int | tuple[str, int]],
129
129
  add_replay: bool = False,
130
130
  add_stimuli: bool = False,
131
131
  add_synapses: bool = False,
@@ -223,7 +223,7 @@ class CircuitSimulation:
223
223
  will automatically set this option to
224
224
  True.
225
225
  """
226
- if not isinstance(cells, Iterable) or isinstance(cells, tuple):
226
+ if not isinstance(cells, list):
227
227
  cells = [cells]
228
228
 
229
229
  # convert to CellId objects
@@ -42,7 +42,7 @@ class PlotWindow:
42
42
  linenumber = 0
43
43
  for var_name in self.var_list:
44
44
  recording = self.cell.get_recording(var_name)
45
- if recording:
45
+ if recording is not None and recording.size > 0:
46
46
  time = self.cell.get_time()
47
47
  else:
48
48
  time = self.cell.get_time()[1:]
@@ -14,7 +14,11 @@
14
14
 
15
15
  from typing import Optional, Dict
16
16
  from bluecellulab.reports.writers import get_writer
17
- from bluecellulab.reports.utils import extract_spikes_from_cells # helper you already have / write
17
+ from bluecellulab.reports.utils import SUPPORTED_REPORT_TYPES, extract_spikes_from_cells # helper you already have / write
18
+
19
+ import logging
20
+
21
+ logger = logging.getLogger(__name__)
18
22
 
19
23
 
20
24
  class ReportManager:
@@ -52,21 +56,30 @@ class ReportManager:
52
56
 
53
57
  def _write_voltage_reports(self, cells_or_traces):
54
58
  for name, rcfg in self.cfg.get_report_entries().items():
55
- if rcfg.get("type") != "compartment":
59
+ if rcfg.get("type") not in SUPPORTED_REPORT_TYPES:
56
60
  continue
57
61
 
58
- section = rcfg.get("sections")
59
- if section == "compartment_set":
62
+ report_type = rcfg.get("type")
63
+ if report_type == "compartment_set":
60
64
  if rcfg.get("cells") is not None:
61
- raise ValueError("'cells' may not be set with 'compartment_set'")
62
- src_sets, src_type = self.cfg.get_compartment_sets(), "compartment_set"
63
- else:
64
- if rcfg.get("compartments") not in ("center", "all"):
65
+ raise ValueError("'cells' may not be set when using 'compartment_set' report type.")
66
+ if rcfg.get("sections") is not None:
67
+ raise ValueError("'sections' may not be set when using 'compartment_set' report type.")
68
+ if rcfg.get("compartments") is not None:
69
+ raise ValueError("'compartments' may not be set when using 'compartment_set' report type.")
70
+ src_sets = self.cfg.get_compartment_sets()
71
+ elif report_type == "compartment":
72
+ compartments = rcfg.get("compartments") or "center"
73
+ if compartments not in ("center", "all"):
65
74
  raise ValueError("invalid 'compartments' value")
66
- src_sets, src_type = self.cfg.get_node_sets(), "node_set"
75
+ if rcfg.get("cells") is None:
76
+ raise ValueError("'cells' must be specified when using compartment reports")
77
+ src_sets = self.cfg.get_node_sets()
78
+ else:
79
+ logger.error(f"Unsupported report type '{report_type}' in configuration for report '{name}'")
67
80
 
68
81
  rcfg["_source_sets"] = src_sets
69
- rcfg["_source_type"] = src_type
82
+ rcfg["name"] = name
70
83
 
71
84
  out_path = self.cfg.report_file_path(rcfg, name)
72
85
  writer = get_writer("compartment")(rcfg, out_path, self.dt)
@@ -17,50 +17,13 @@ from collections import defaultdict
17
17
  import logging
18
18
  from typing import Dict, Any, List
19
19
 
20
- from bluecellulab.tools import resolve_segments, resolve_source_nodes
20
+ from bluecellulab.tools import (
21
+ resolve_source_nodes,
22
+ )
21
23
 
22
24
  logger = logging.getLogger(__name__)
23
25
 
24
-
25
- def _configure_recording(cell, report_cfg, source, source_type, report_name):
26
- """Configure recording of a variable on a single cell.
27
-
28
- This function sets up the recording of the specified variable (e.g., membrane voltage)
29
- in the target cell, for each resolved segment.
30
-
31
- Parameters
32
- ----------
33
- cell : Any
34
- The cell object on which to configure recordings.
35
-
36
- report_cfg : dict
37
- The configuration dictionary for this report.
38
-
39
- source : dict
40
- The source definition specifying nodes or compartments.
41
-
42
- source_type : str
43
- Either "node_set" or "compartment_set".
44
-
45
- report_name : str
46
- The name of the report (used in logging).
47
- """
48
- variable = report_cfg.get("variable_name", "v")
49
-
50
- node_id = cell.cell_id
51
- compartment_nodes = source.get("compartment_set") if source_type == "compartment_set" else None
52
-
53
- targets = resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type)
54
- for sec, sec_name, seg in targets:
55
- try:
56
- cell.add_variable_recording(variable=variable, section=sec, segx=seg)
57
- except AttributeError:
58
- logger.warning(f"Recording for variable '{variable}' is not implemented in Cell.")
59
- return
60
- except Exception as e:
61
- logger.warning(
62
- f"Failed to record '{variable}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}': {e}"
63
- )
26
+ SUPPORTED_REPORT_TYPES = {"compartment", "compartment_set"}
64
27
 
65
28
 
66
29
  def configure_all_reports(cells, simulation_config):
@@ -83,39 +46,132 @@ def configure_all_reports(cells, simulation_config):
83
46
 
84
47
  for report_name, report_cfg in report_entries.items():
85
48
  report_type = report_cfg.get("type", "compartment")
86
- section = report_cfg.get("sections", "soma")
87
-
88
- if report_type != "compartment":
89
- raise NotImplementedError(f"Report type '{report_type}' is not supported.")
90
-
91
- if section == "compartment_set":
92
- source_type = "compartment_set"
49
+ if report_type == "compartment_set":
93
50
  source_sets = simulation_config.get_compartment_sets()
94
- source_name = report_cfg.get("compartments")
51
+ source_name = report_cfg.get("compartment_set")
95
52
  if not source_name:
96
- logger.warning(f"Report '{report_name}' does not specify a node set in 'compartments' for {source_type}.")
53
+ logger.warning(
54
+ f"Report '{report_name}' does not specify a node set in 'compartment_set' for {report_type}."
55
+ )
97
56
  continue
98
- else:
99
- source_type = "node_set"
57
+ elif report_type == "compartment":
100
58
  source_sets = simulation_config.get_node_sets()
101
59
  source_name = report_cfg.get("cells")
102
60
  if not source_name:
103
- logger.warning(f"Report '{report_name}' does not specify a node set in 'cells' for {source_type}.")
61
+ logger.warning(
62
+ f"Report '{report_name}' does not specify a node set in 'cells' for {report_type}."
63
+ )
104
64
  continue
65
+ else:
66
+ raise NotImplementedError(
67
+ f"Report type '{report_type}' is not supported. "
68
+ f"Supported types: {SUPPORTED_REPORT_TYPES}"
69
+ )
105
70
 
106
71
  source = source_sets.get(source_name)
107
72
  if not source:
108
- logger.warning(f"{source_type.title()} '{source_name}' not found for report '{report_name}', skipping recording.")
73
+ logger.warning(
74
+ f"{report_type} '{source_name}' not found for report '{report_name}', skipping recording."
75
+ )
109
76
  continue
110
77
 
111
78
  population = source["population"]
112
- node_ids, _ = resolve_source_nodes(source, source_type, cells, population)
113
-
114
- for node_id in node_ids:
79
+ node_ids, compartment_nodes = resolve_source_nodes(
80
+ source, report_type, cells, population
81
+ )
82
+ recording_sites_per_cell = build_recording_sites(
83
+ cells, node_ids, population, report_type, report_cfg, compartment_nodes
84
+ )
85
+ variable_name = report_cfg.get("variable_name", "v")
86
+
87
+ for node_id, recording_sites in recording_sites_per_cell.items():
115
88
  cell = cells.get((population, node_id))
116
- if not cell:
89
+ if not cell or recording_sites is None:
117
90
  continue
118
- _configure_recording(cell, report_cfg, source, source_type, report_name)
91
+
92
+ cell.configure_recording(recording_sites, variable_name, report_name)
93
+
94
+
95
+ def build_recording_sites(
96
+ cells_or_traces, node_ids, population, report_type, report_cfg, compartment_nodes
97
+ ):
98
+ """Build per-cell recording sites based on source type and report
99
+ configuration.
100
+
101
+ This function resolves the segments (section, name, seg.x) where variables
102
+ should be recorded for each cell, based on either a node set (standard
103
+ compartment reports) or a compartment set (predefined segment list).
104
+
105
+ Parameters
106
+ ----------
107
+ cells_or_traces : dict
108
+ Either a mapping from (population, node_id) to Cell objects (live sim),
109
+ or from gid_key strings to trace dicts (gathered traces on rank 0).
110
+
111
+ node_ids : list of int
112
+ List of node IDs for which recordings should be configured.
113
+
114
+ population : str
115
+ Name of the population to which the cells belong.
116
+
117
+ report_type : str
118
+ The report type, either 'compartment_set' or 'compartment'.
119
+
120
+ report_cfg : dict
121
+ Configuration dictionary specifying report parameters
122
+
123
+ compartment_nodes : list or None
124
+ Optional list of [node_id, section_name, seg_x] defining segment locations
125
+ for each cell (used if report_type == 'compartment_set').
126
+
127
+ Returns
128
+ -------
129
+ dict
130
+ Mapping from node ID to list of recording site tuples:
131
+ (section_object, section_name, seg_x).
132
+ """
133
+ targets_per_cell = {}
134
+
135
+ for node_id in node_ids:
136
+ # Handle both (pop, id) and "pop_id" keys
137
+ key = (population, node_id)
138
+ cell_or_trace = cells_or_traces.get(key) or cells_or_traces.get(f"{population}_{node_id}")
139
+ if not cell_or_trace:
140
+ continue
141
+
142
+ if isinstance(cell_or_trace, dict): # Trace dict, not Cell
143
+ if report_type == "compartment_set":
144
+ # Find all entries matching node_id
145
+ targets = [
146
+ (None, section_name, segx)
147
+ for nid, section_name, segx in compartment_nodes
148
+ if nid == node_id
149
+ ]
150
+ elif report_type == "compartment":
151
+ section_name = report_cfg.get("sections", "soma")
152
+ segx = 0.5 if report_cfg.get("compartments", "center") == "center" else 0.0
153
+ targets = [(None, f"{section_name}[0]", segx)]
154
+ else:
155
+ raise NotImplementedError(
156
+ f"Unsupported report type '{report_type}' in trace-based output."
157
+ )
158
+ else:
159
+ # Cell object
160
+ if report_type == "compartment_set":
161
+ targets = cell_or_trace.resolve_segments_from_compartment_set(
162
+ node_id, compartment_nodes
163
+ )
164
+ elif report_type == "compartment":
165
+ targets = cell_or_trace.resolve_segments_from_config(report_cfg)
166
+ else:
167
+ raise NotImplementedError(
168
+ f"Report type '{report_type}' is not supported. "
169
+ f"Supported types: {SUPPORTED_REPORT_TYPES}"
170
+ )
171
+
172
+ targets_per_cell[node_id] = targets
173
+
174
+ return targets_per_cell
119
175
 
120
176
 
121
177
  def extract_spikes_from_cells(
@@ -144,13 +200,28 @@ def extract_spikes_from_cells(
144
200
  spikes_by_pop: defaultdict[str, Dict[int, List[float]]] = defaultdict(dict)
145
201
 
146
202
  for key, cell in cells.items():
203
+ # Resolve the key to (population, gid)
147
204
  if isinstance(key, tuple):
148
205
  pop, gid = key
206
+ elif isinstance(key, str):
207
+ try:
208
+ pop, gid_str = key.rsplit("_", 1)
209
+ gid = int(gid_str)
210
+ except Exception:
211
+ raise ValueError(
212
+ f"Cell key '{key}' could not be parsed as 'population_gid'"
213
+ )
149
214
  else:
150
- raise ValueError(f"Cell key {key} is not a (population, gid) tuple.")
215
+ raise ValueError(f"Cell key '{key}' is not a recognized format.")
216
+
217
+ if not hasattr(cell, "get_recorded_spikes"):
218
+ raise TypeError(
219
+ f"Cannot extract spikes: cell entry {key} is not a Cell object (got {type(cell)}). "
220
+ "If you have precomputed traces, pass them as `spikes_by_pop`."
221
+ )
151
222
 
152
223
  times = cell.get_recorded_spikes(location=location, threshold=threshold)
153
- if times is not None and len(times) > 0:
154
- spikes_by_pop[pop][gid] = list(times)
224
+ # Always assign, even if empty
225
+ spikes_by_pop[pop][gid] = list(times) if times is not None else []
155
226
 
156
227
  return dict(spikes_by_pop)
@@ -16,10 +16,11 @@ from pathlib import Path
16
16
  import numpy as np
17
17
  import h5py
18
18
  from typing import Dict, List
19
+
19
20
  from .base_writer import BaseReportWriter
20
21
  from bluecellulab.reports.utils import (
22
+ build_recording_sites,
21
23
  resolve_source_nodes,
22
- resolve_segments,
23
24
  )
24
25
  import logging
25
26
 
@@ -31,52 +32,67 @@ class CompartmentReportWriter(BaseReportWriter):
31
32
 
32
33
  def write(self, cells: Dict, tstart=0):
33
34
  report_name = self.cfg.get("name", "unnamed")
34
- # section = self.cfg.get("sections")
35
35
  variable = self.cfg.get("variable_name", "v")
36
+ report_type = self.cfg.get("type", "compartment")
36
37
 
38
+ # Resolve source set
37
39
  source_sets = self.cfg["_source_sets"]
38
- source_type = self.cfg["_source_type"]
39
- src_name = self.cfg.get("cells") if source_type == "node_set" else self.cfg.get("compartments")
40
+ if report_type == "compartment":
41
+ src_name = self.cfg.get("cells")
42
+ elif report_type == "compartment_set":
43
+ src_name = self.cfg.get("compartment_set")
44
+ else:
45
+ raise NotImplementedError(
46
+ f"Unsupported report type '{report_type}' in configuration for report '{report_name}'"
47
+ )
48
+
40
49
  src = source_sets.get(src_name)
41
50
  if not src:
42
- logger.warning(f"{source_type.title()} '{src_name}' not found – skipping '{report_name}'.")
51
+ logger.warning(f"{report_type} '{src_name}' not found – skipping '{report_name}'.")
43
52
  return
44
53
 
45
54
  population = src["population"]
46
- node_ids, comp_nodes = resolve_source_nodes(src, source_type, cells, population)
55
+ node_ids, comp_nodes = resolve_source_nodes(src, report_type, cells, population)
56
+ recording_sites_per_cell = build_recording_sites(
57
+ cells, node_ids, population, report_type, self.cfg, comp_nodes
58
+ )
59
+
60
+ # Detect trace mode
61
+ sample_cell = next(iter(cells.values()))
62
+ is_trace_mode = isinstance(sample_cell, dict)
47
63
 
48
64
  data_matrix: List[np.ndarray] = []
49
65
  node_id_list: List[int] = []
50
66
  idx_ptr: List[int] = [0]
51
67
  elem_ids: List[int] = []
52
68
 
53
- for nid in node_ids:
69
+ for nid in sorted(recording_sites_per_cell):
70
+ recording_sites = recording_sites_per_cell[nid]
54
71
  cell = cells.get((population, nid)) or cells.get(f"{population}_{nid}")
55
72
  if cell is None:
73
+ logger.warning(f"Cell or trace for ({population}, {nid}) not found – skipping.")
56
74
  continue
57
75
 
58
- if isinstance(cell, dict):
59
- # No section/segment structure to resolve for traces
60
- trace = np.asarray(cell["voltage"], dtype=np.float32)
61
- data_matrix.append(trace)
62
- node_id_list.append(nid)
63
- elem_ids.append(len(elem_ids))
64
- idx_ptr.append(idx_ptr[-1] + 1)
65
- continue
66
-
67
- targets = resolve_segments(cell, self.cfg, nid, comp_nodes, source_type)
68
- for sec, sec_name, seg in targets:
69
- try:
70
- if hasattr(cell, "get_variable_recording"):
71
- trace = cell.get_variable_recording(variable=variable, section=sec, segx=seg)
72
- else:
73
- trace = np.asarray(cell["voltage"], dtype=np.float32)
74
- data_matrix.append(trace)
76
+ if is_trace_mode:
77
+ voltage = np.asarray(cell["voltage"], dtype=np.float32)
78
+ for sec, sec_name, seg in recording_sites:
79
+ data_matrix.append(voltage)
75
80
  node_id_list.append(nid)
76
81
  elem_ids.append(len(elem_ids))
77
82
  idx_ptr.append(idx_ptr[-1] + 1)
78
- except Exception as e:
79
- logger.warning(f"Failed recording {nid}:{sec_name}@{seg}: {e}")
83
+ else:
84
+ for sec, sec_name, seg in recording_sites:
85
+ try:
86
+ if hasattr(cell, "get_variable_recording"):
87
+ trace = cell.get_variable_recording(variable=variable, section=sec, segx=seg)
88
+ else:
89
+ trace = np.asarray(cell["voltage"], dtype=np.float32)
90
+ data_matrix.append(trace)
91
+ node_id_list.append(nid)
92
+ elem_ids.append(len(elem_ids))
93
+ idx_ptr.append(idx_ptr[-1] + 1)
94
+ except Exception as e:
95
+ logger.warning(f"Failed recording {nid}:{sec_name}@{seg}: {e}")
80
96
 
81
97
  if not data_matrix:
82
98
  logger.warning(f"No data for report '{report_name}'.")
@@ -148,7 +164,8 @@ class CompartmentReportWriter(BaseReportWriter):
148
164
  if dt_report < sim_dt:
149
165
  logger.warning(
150
166
  f"Requested report dt={dt_report} ms is finer than simulation dt={sim_dt} ms. "
151
- f"Clamping report dt to {sim_dt} ms."
167
+ f"Clamping report dt to {sim_dt} ms. "
168
+ f"To achieve finer temporal resolution, reduce the simulation dt in your config."
152
169
  )
153
170
  dt_report = sim_dt
154
171
 
@@ -161,7 +178,7 @@ class CompartmentReportWriter(BaseReportWriter):
161
178
  # Downsample the data if needed
162
179
  # Compute start and end indices in the original data
163
180
  start_index = int(round((start_time - tstart) / sim_dt))
164
- end_index = int(round((end_time - tstart) / sim_dt)) + 1 # inclusive
181
+ end_index = int(round((end_time - tstart) / sim_dt))
165
182
 
166
183
  # Now slice and downsample
167
184
  data_matrix_downsampled = [
@@ -25,37 +25,62 @@ class SpikeReportWriter(BaseReportWriter):
25
25
  """Writes SONATA spike report from pop→gid→times mapping."""
26
26
 
27
27
  def write(self, spikes_by_pop: Dict[str, Dict[int, list]]):
28
+ """Write SONATA spike report with per-population groups.
29
+
30
+ Creates empty datasets for populations without spikes.
31
+ """
32
+
33
+ # Remove any existing file
28
34
  if self.output_path.exists():
29
35
  self.output_path.unlink()
30
36
 
37
+ # Make sure parent directory exists
31
38
  self.output_path.parent.mkdir(parents=True, exist_ok=True)
32
39
 
33
- for pop, gid_map in spikes_by_pop.items():
34
- all_node_ids: List[int] = []
35
- all_timestamps: List[float] = []
36
- for node_id, times in gid_map.items():
37
- all_node_ids.extend([node_id] * len(times))
38
- all_timestamps.extend(times)
40
+ # Always create the file and /spikes group
41
+ with h5py.File(self.output_path, "w") as f:
42
+ spikes_group = f.create_group("spikes")
43
+
44
+ for pop, gid_map in spikes_by_pop.items():
45
+ all_node_ids: List[int] = []
46
+ all_timestamps: List[float] = []
47
+
48
+ for node_id, times in gid_map.items():
49
+ all_node_ids.extend([node_id] * len(times))
50
+ all_timestamps.extend(times)
39
51
 
40
- if not all_timestamps:
41
- logger.warning(f"No spikes to write for population '{pop}'.")
52
+ if not all_timestamps:
53
+ logger.warning(f"No spikes to write for population '{pop}'.")
42
54
 
43
- # Sort by time for consistency
44
- sorted_indices = np.argsort(all_timestamps)
45
- node_ids_sorted = np.array(all_node_ids, dtype=np.uint64)[sorted_indices]
46
- timestamps_sorted = np.array(all_timestamps, dtype=np.float64)[sorted_indices]
55
+ # Sort by time for consistency (will be empty arrays if no spikes)
56
+ sorted_indices = np.argsort(all_timestamps)
57
+ node_ids_sorted = np.array(all_node_ids, dtype=np.uint64)[
58
+ sorted_indices
59
+ ]
60
+ timestamps_sorted = np.array(all_timestamps, dtype=np.float64)[
61
+ sorted_indices
62
+ ]
47
63
 
48
- with h5py.File(self.output_path, 'a') as f:
49
- spikes_group = f.require_group("spikes")
50
64
  if pop in spikes_group:
51
- logger.warning(f"Overwriting existing group for population '{pop}' in {self.output_path}.")
65
+ logger.warning(
66
+ f"Overwriting existing group for population '{pop}' in {self.output_path}."
67
+ )
52
68
  del spikes_group[pop]
53
69
 
54
70
  group = spikes_group.create_group(pop)
55
- sorting_enum = h5py.enum_dtype({'none': 0, 'by_id': 1, 'by_time': 2}, basetype='u1')
56
- group.attrs.create("sorting", 2, dtype=sorting_enum) # 2 = by_time
57
-
58
- timestamps_ds = group.create_dataset("timestamps", data=timestamps_sorted)
59
- group.create_dataset("node_ids", data=node_ids_sorted)
60
71
 
72
+ # SONATA requires the 'sorting' attribute
73
+ sorting_enum = h5py.enum_dtype(
74
+ {"none": 0, "by_id": 1, "by_time": 2}, basetype="u1"
75
+ )
76
+ if timestamps_sorted.size > 0:
77
+ group.attrs.create("sorting", 2, dtype=sorting_enum) # 2 = by_time
78
+ else:
79
+ group.attrs.create("sorting", 0, dtype=sorting_enum) # 0 = none
80
+
81
+ # Always create datasets (even empty)
82
+ timestamps_ds = group.create_dataset(
83
+ "timestamps", data=timestamps_sorted
84
+ )
61
85
  timestamps_ds.attrs["units"] = "ms" # SONATA-required
86
+ group.create_dataset("node_ids", data=node_ids_sorted)
bluecellulab/tools.py CHANGED
@@ -29,7 +29,6 @@ from bluecellulab.exceptions import UnsteadyCellError
29
29
  from bluecellulab.simulation.neuron_globals import set_neuron_globals
30
30
  from bluecellulab.simulation.parallel import IsolatedProcess
31
31
  from bluecellulab.utils import CaptureOutput
32
- from bluecellulab.type_aliases import NeuronSection
33
32
 
34
33
 
35
34
  logger = logging.getLogger(__name__)
@@ -119,7 +118,7 @@ def calculate_SS_voltage_subprocess(
119
118
  emodel_properties=emodel_properties,
120
119
  )
121
120
 
122
- sec = get_section(cell, section)
121
+ sec = cell.get_section(section)
123
122
  neuron_section = sec
124
123
  cell.add_voltage_recording(cell.sections[section], segx)
125
124
  cell.add_ramp(500, 5000, step_level, step_level, section=neuron_section, segx=segx)
@@ -313,7 +312,7 @@ def detect_spike_step_subprocess(
313
312
  template_format=template_format,
314
313
  emodel_properties=emodel_properties)
315
314
 
316
- sec = get_section(cell, section)
315
+ sec = cell.get_section(section)
317
316
  cell.add_voltage_recording(cell.sections[section], segx)
318
317
 
319
318
  neuron_section = sec
@@ -517,88 +516,19 @@ def validate_section_and_segment(cell: Cell, section_name: str, segment_position
517
516
  raise ValueError(f"Segment position must be between 0.0 and 1.0, got {segment_position}.")
518
517
 
519
518
 
520
- def get_section(cell: Cell, section_name: str) -> NeuronSection:
521
- """Return a single, fully specified NEURON section (e.g., 'soma[0]',
522
- 'dend[3]').
523
-
524
- Raises:
525
- ValueError or TypeError if the section is not found or invalid.
526
- """
527
- if section_name in cell.sections:
528
- section = cell.sections[section_name]
529
- if hasattr(section, "nseg"):
530
- return section
531
- raise TypeError(f"'{section_name}' exists but is not a NEURON section.")
532
-
533
- available = ", ".join(cell.sections.keys())
534
- raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
535
-
536
-
537
- def get_sections(cell, section_name: str):
538
- """Return a list of NEURON sections.
539
-
540
- If the section name is a fully specified one (e.g., 'dend[3]'), return it as a list of one.
541
- If the section name is a base name (e.g., 'dend'), return all matching sections like 'dend[0]', 'dend[1]', etc.
542
-
543
- Raises:
544
- ValueError or TypeError if no valid sections are found.
545
- """
546
- # Try to interpret as fully qualified section name
547
- try:
548
- return [get_section(cell, section_name)]
549
- except ValueError:
550
- pass # Not a precise match; try prefix match
551
-
552
- # Fallback to prefix-based match (e.g., 'dend' → 'dend[0]', 'dend[1]', ...)
553
- matched = [
554
- section for name, section in cell.sections.items()
555
- if name.startswith(f"{section_name}[")
556
- ]
557
- if matched:
558
- return matched
559
-
560
- available = ", ".join(cell.sections.keys())
561
- raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
562
-
563
-
564
- def resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type):
565
- """Determine which segments to record from one or more NEURON sections."""
566
- section_name = report_cfg.get("sections", "soma")
567
- compartment = report_cfg.get("compartments", "center")
568
-
569
- if source_type == "compartment_set":
570
- return [
571
- (get_section(cell, sec), sec, seg)
572
- for _, sec, seg in compartment_nodes if _ == node_id
573
- ]
574
-
575
- sections = get_sections(cell, section_name)
576
- targets = []
577
-
578
- for sec in sections:
579
- sec_name = sec.name().split(".")[-1]
580
- if compartment == "center":
581
- targets.append((sec, sec_name, 0.5))
582
- elif compartment == "all":
583
- for seg in sec:
584
- targets.append((sec, sec_name, seg.x))
585
- else:
586
- raise ValueError(
587
- f"Unsupported 'compartments' value '{compartment}' — must be 'center' or 'all'."
588
- )
589
-
590
- return targets
591
-
592
-
593
- def resolve_source_nodes(source, source_type, cells, population):
594
- if source_type == "compartment_set":
519
+ def resolve_source_nodes(source, report_type, cells, population):
520
+ if report_type == "compartment_set":
595
521
  compartment_nodes = source.get("compartment_set", [])
596
522
  node_ids = [entry[0] for entry in compartment_nodes]
597
- else: # node_set
523
+ elif report_type == "compartment":
598
524
  node_ids = source.get("node_id")
599
525
  if node_ids is None:
600
526
  node_ids = [node_id for (pop, node_id) in cells.keys() if pop == population]
601
527
  compartment_nodes = None
528
+ else:
529
+ raise NotImplementedError(
530
+ f"Unsupported source type '{report_type}' in configuration for report."
531
+ )
602
532
  return node_ids, compartment_nodes
603
533
 
604
534
 
bluecellulab/utils.py CHANGED
@@ -53,7 +53,7 @@ class NumpyEncoder(json.JSONEncoder):
53
53
  ),
54
54
  ):
55
55
  return int(obj)
56
- elif isinstance(obj, (np.float_, np.float16, np.float32, np.float64)):
56
+ elif isinstance(obj, (np.float64, np.float16, np.float32, np.float64)):
57
57
  return float(obj)
58
58
  elif isinstance(obj, np.ndarray):
59
59
  return obj.tolist()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bluecellulab
3
- Version: 2.6.71
3
+ Version: 2.6.73
4
4
  Summary: Biologically detailed neural network simulations and analysis.
5
5
  Author: Blue Brain Project, EPFL
6
6
  License: Apache2.0
@@ -19,7 +19,7 @@ Description-Content-Type: text/x-rst
19
19
  License-File: LICENSE
20
20
  License-File: AUTHORS.txt
21
21
  Requires-Dist: NEURON<9.0.0,>=8.0.2
22
- Requires-Dist: numpy<2.0.0,>=1.8.0
22
+ Requires-Dist: numpy>=2.0.0
23
23
  Requires-Dist: matplotlib<4.0.0,>=3.0.0
24
24
  Requires-Dist: pandas<3.0.0,>=1.0.0
25
25
  Requires-Dist: bluepysnap<4.0.0,>=3.0.0
@@ -102,7 +102,7 @@ Main dependencies
102
102
  =================
103
103
 
104
104
  * `Python 3.9+ <https://www.python.org/downloads/release/python-390/>`_
105
- * `Neuron 8.0.2+ <https://pypi.org/project/NEURON/>`_
105
+ * `NEURON <=8.2.7 <https://pypi.org/project/NEURON/>`__
106
106
 
107
107
  Installation
108
108
  ============
@@ -1,18 +1,18 @@
1
1
  bluecellulab/__init__.py,sha256=1d_CKIJLIpon7o13h3lBnV_-33obZEPwa9KDTjlFPD8,880
2
- bluecellulab/circuit_simulation.py,sha256=CryoBSUGc9z6nIAyNzCsyppeSlc_URJX6hZoZPgQI2Q,36807
2
+ bluecellulab/circuit_simulation.py,sha256=GGAkJq0Jk_ClB1XTyrf8g6Sirntj0E9IBeI76acJ2yU,36769
3
3
  bluecellulab/connection.py,sha256=-xT0mU7ppeHI_qjCKj17TtxXVVcUDgBsaMKt9ODmcEU,4640
4
4
  bluecellulab/dendrogram.py,sha256=FjS6RZ6xcp5zJoY5d5qv_edqPM13tL2-UANgbZuDBjY,6427
5
5
  bluecellulab/exceptions.py,sha256=1lKD92VIyD8cUggAI1SLxeKzj_09Ik_TlHCzPLCvDHg,2379
6
6
  bluecellulab/graph.py,sha256=o-9NnRrli0AqfGcw0-nhjaICH2IeqCH76uAlbZbo2dQ,3419
7
7
  bluecellulab/importer.py,sha256=ePxoquSRGGK123mR_VWDLKf9CE-N28NXAAY1DCm3T7Y,2900
8
8
  bluecellulab/neuron_interpreter.py,sha256=Nerehh6SNIRcTH8NHvyZvL_axadeix67uer-lRGuw9U,2185
9
- bluecellulab/plotwindow.py,sha256=ePU-EegZ1Sqk0SoYYiQ6k24ZO4s3Hgfpx10uePiJ5xM,2721
9
+ bluecellulab/plotwindow.py,sha256=496tNvrd7xQ9SbSLRXF1VPIvWWqA2jl03-ZPMJ-wx_0,2756
10
10
  bluecellulab/psection.py,sha256=FSOwRNuOTyB469BM-jPEf9l1J59FamXmzrQgBI6cnP4,6174
11
11
  bluecellulab/psegment.py,sha256=PTgoGLqM4oFIdF_8QHFQCU59j-8TQmtq6PakiGUQhIo,3138
12
12
  bluecellulab/rngsettings.py,sha256=2Ykb4Ylk3XTs58x1UIxjg8XJqjSpnCgKRZ8avXCDpxk,4237
13
- bluecellulab/tools.py,sha256=ePal9-y6AnIgSdRzqqL8fH57M96VXUWPEGvJ7mpz4Ps,21926
13
+ bluecellulab/tools.py,sha256=OamYjgBBBiUiXlf4zCDOjI-SuA6o9Ck8UsKsAi_8gEg,19483
14
14
  bluecellulab/type_aliases.py,sha256=DvgjERv2Ztdw_sW63JrZTQGpJ0x5uMTFB5hcBHDb0WA,441
15
- bluecellulab/utils.py,sha256=0NhwlzyLnSi8kziSfDsQf7pokO4qDkMJVAO33kSX4O0,2227
15
+ bluecellulab/utils.py,sha256=tATZ814TtQO3fTsm5Q0KiANCDLFDZYtkfFQlI-Kxx_U,2228
16
16
  bluecellulab/verbosity.py,sha256=T0IgX7DrRo19faxrT4Xzb27gqxzoILQ8FzYKxvUeaPM,1342
17
17
  bluecellulab/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  bluecellulab/analysis/analysis.py,sha256=qZN2R7IKMOda8sVEDU61TkTDNUbaZjIWbwP3pM6Fcm8,24184
@@ -21,7 +21,7 @@ bluecellulab/analysis/plotting.py,sha256=PqRoaZz33ULMw8A9YnZXXrxcUd84M_dwlYMTFhG
21
21
  bluecellulab/analysis/utils.py,sha256=eMirP557D11BuedgSqjripDxOq1haIldNbnYNetV1bg,121
22
22
  bluecellulab/cell/__init__.py,sha256=Sbc0QOsJ8E7tSwf3q7fsXuE_SevBN6ZmoCVyyU5zfII,208
23
23
  bluecellulab/cell/cell_dict.py,sha256=VE7pi-NsMVRSmo-PSdbiLYmolDOu0Gc6JxFBkuQpFdk,1346
24
- bluecellulab/cell/core.py,sha256=xNyY3Ly4E7wb4LGIYcA2vT0EKsQYd1tNItcmxgKKK2o,38267
24
+ bluecellulab/cell/core.py,sha256=ndfDwcYfINe-T9hniH-XL9TKPCa6c2sNa-HuCfDVKZE,44213
25
25
  bluecellulab/cell/injector.py,sha256=GB0pmyZMOHNV-lzd_t4cLoDE87S58IMbe7-qU1zBWvE,19033
26
26
  bluecellulab/cell/plotting.py,sha256=t2qDUabFtsBb-nJMkDh5UfsgW-wvQ2wfDwAVZ8-hWPo,4032
27
27
  bluecellulab/cell/random.py,sha256=pemekc11geRBKD8Ghb82tvKOZLfFWkUz1IKLc_NWg-A,1773
@@ -38,31 +38,31 @@ bluecellulab/cell/ballstick/morphology.asc,sha256=EO0VIRilJAwpiDP2hIevwusfvYptNY
38
38
  bluecellulab/circuit/__init__.py,sha256=Khpa13nzNvDlDS2JduyoFTukEduEkWCc5ML_JwGpmZs,361
39
39
  bluecellulab/circuit/format.py,sha256=90gWOXg6HK0R9a4WFSnnRH8XezxmzOGk5dRpJHbvbbU,1674
40
40
  bluecellulab/circuit/iotools.py,sha256=Q65xYDaiensMtrulC3OLsS2_hcWr_Kje0nXFrAizMMo,1589
41
- bluecellulab/circuit/node_id.py,sha256=FdoFAGq0_sCyQySOuNI0chdbVr3L8R0w2Y1em5MyIDA,1265
41
+ bluecellulab/circuit/node_id.py,sha256=g_f_PvjaX59yXBTWRIpXibbgFny6xBWVErjVErahTTc,1259
42
42
  bluecellulab/circuit/simulation_access.py,sha256=8LX5nbKw_Hu7AR3pGLdTFGwehvhaj-r8Mh1q3dVoiVg,7745
43
43
  bluecellulab/circuit/synapse_properties.py,sha256=TvUMiXZAAeYo1zKkus3z1EUvrE9QCIQ3Ze-jSnPSJWY,6374
44
44
  bluecellulab/circuit/validate.py,sha256=wntnr7oIDyasqD1nM-kqz1NpfWDxBGhx0Ep3e5hHXIw,3593
45
45
  bluecellulab/circuit/circuit_access/__init__.py,sha256=sgp6m5kP-pq60V1IFGUiSUR1OW01zdxXNNUJmPA8anI,201
46
- bluecellulab/circuit/circuit_access/bluepy_circuit_access.py,sha256=m5PWSYrrkORb55HN1ZgJpQFLeSHxsNBtzyJ1n0zuVro,14295
46
+ bluecellulab/circuit/circuit_access/bluepy_circuit_access.py,sha256=Ivj7RqIK0OYUuraTpQU1h_oeE2somBn_Tnji6XcjFt8,14383
47
47
  bluecellulab/circuit/circuit_access/definition.py,sha256=_sUU0DkesGOFW82kS1G9vki0o0aQP76z3Ltk7Vo9KK4,4435
48
- bluecellulab/circuit/circuit_access/sonata_circuit_access.py,sha256=tADHxVZw4VgZAz2z4NKMUwc0rd_EO40EZggw5fDhnF4,10411
48
+ bluecellulab/circuit/circuit_access/sonata_circuit_access.py,sha256=OfqVS3z_t7dsnRuihF15fW-KuOCd7BXqRoFblpo2VL4,10441
49
49
  bluecellulab/circuit/config/__init__.py,sha256=aaoJXRKBJzpxxREo9NxKc-_CCPmVeuR1mcViRXcLrC4,215
50
- bluecellulab/circuit/config/bluepy_simulation_config.py,sha256=V3eqOzskX7VrMDpl-nMQVEhDg8QWgRmRduyJBii5sgI,6974
50
+ bluecellulab/circuit/config/bluepy_simulation_config.py,sha256=3l0TqoQhsltvQnF-rKK_zFEu0-L6dAhW-Ch6z9C4wZw,7216
51
51
  bluecellulab/circuit/config/definition.py,sha256=cotKRDHOjzZKNgNSrZ29voU-66W8jaGt5yuge1hyv18,2953
52
52
  bluecellulab/circuit/config/sections.py,sha256=QRnU44-OFvHxcF1LX4bAEP9dk3I6UKsuPNBbWkdfmRE,7151
53
- bluecellulab/circuit/config/sonata_simulation_config.py,sha256=8b3OkjYG7MJnrm498Tx6x1B2MbvwDZO0aAAxy6iHuts,7983
53
+ bluecellulab/circuit/config/sonata_simulation_config.py,sha256=M3dD9B69VK01lRaR84PiAj8th-x8hrAE80IpGDLqvi8,8056
54
54
  bluecellulab/hoc/Cell.hoc,sha256=z77qRQG_-afj-RLX0xN6V-K6Duq3bR7vmlDrGWPdh4E,16435
55
55
  bluecellulab/hoc/RNGSettings.hoc,sha256=okJBdqlPXET8BrpG1Q31GdaxHfpe3CE0L5P7MAhfQTE,1227
56
56
  bluecellulab/hoc/TDistFunc.hoc,sha256=WKX-anvL83xGuGPH9g1oIORB17UM4Pi3-iIXzKO-pUQ,2663
57
57
  bluecellulab/hoc/TStim.hoc,sha256=noBJbM_ZqF6T6MEgBeowNzz21I9QeYZ5brGgUvCSm4k,8473
58
58
  bluecellulab/hoc/fileUtils.hoc,sha256=LSM7BgyjYVqo2DGSOKvg4W8IIusbsL45JVYK0vgwitU,2539
59
59
  bluecellulab/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- bluecellulab/reports/manager.py,sha256=hy54B7AXa46bgjKJ_Ar65_GYIu9C8Zg2fgxIlxSweRE,3284
61
- bluecellulab/reports/utils.py,sha256=yZPtO0HXs6HPDVOZQbeXlPajnSOqE2Qmx8VufQFJFT0,5557
60
+ bluecellulab/reports/manager.py,sha256=bKtqxOnZcta4HwrlWN51ysfMmvcbISksUpOKF0pJFV0,4021
61
+ bluecellulab/reports/utils.py,sha256=wnYFo1xKbBRu1KZTIea83Wvakoo_SQ94QiqOD26cEe0,8271
62
62
  bluecellulab/reports/writers/__init__.py,sha256=Q8Y2GC83jseH5QY9IlEQsvaUQIH-BzgqhVjGRWD5j90,826
63
63
  bluecellulab/reports/writers/base_writer.py,sha256=P5ramFD2oFIroEBCH1pAscbkfcBIVYFIBg4pVpSP2IU,1042
64
- bluecellulab/reports/writers/compartment.py,sha256=EwFc2NwqhGEwGShEFOIrpYWRgH4b7qZCwsPvpUxeboU,7212
65
- bluecellulab/reports/writers/spikes.py,sha256=ycMuoT6f-UbAbC47X9gkG44OQYeAGBQMB2RdJAq3Ykg,2558
64
+ bluecellulab/reports/writers/compartment.py,sha256=vtBRBNHm6NRnX09RZCszC9MKpiZqqVovlhwsfitzaRU,7960
65
+ bluecellulab/reports/writers/spikes.py,sha256=FJm74SKeMb0h_oNucwWoNrh8xLObzinQTOHH0v1xKeU,3375
66
66
  bluecellulab/simulation/__init__.py,sha256=P2ebt0SFw-08J3ihN-LeRn95HeF79tzA-Q0ReLm32dM,214
67
67
  bluecellulab/simulation/neuron_globals.py,sha256=wvh6HfVYXoUsAcy17wqI_4SOqBCovRGEEaW9ANDgiIQ,4943
68
68
  bluecellulab/simulation/parallel.py,sha256=oQ_oV2EKr8gP4yF2Dq8q9MiblDyi89_wBgLzQkLV_U0,1514
@@ -76,9 +76,9 @@ bluecellulab/synapse/__init__.py,sha256=RW8XoAMXOvK7OG1nHl_q8jSEKLj9ZN4oWf2nY9HA
76
76
  bluecellulab/synapse/synapse_factory.py,sha256=NHwRMYMrnRVm_sHmyKTJ1bdoNmWZNU4UPOGu7FCi-PE,6987
77
77
  bluecellulab/synapse/synapse_types.py,sha256=zs_yBvGTH4QrbQF3nEViidyq1WM_ZcTSFdjUxB3khW0,16871
78
78
  bluecellulab/validation/validation.py,sha256=D35LE_131CtIzeeePAPz1OFzncGjAcvF02lSIvGvL2E,21331
79
- bluecellulab-2.6.71.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
80
- bluecellulab-2.6.71.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
81
- bluecellulab-2.6.71.dist-info/METADATA,sha256=1dFG_vPcTlOZSX9CTbOdsa1uPxdc1OmlLrCdxTaDccA,8359
82
- bluecellulab-2.6.71.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
- bluecellulab-2.6.71.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
84
- bluecellulab-2.6.71.dist-info/RECORD,,
79
+ bluecellulab-2.6.73.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
80
+ bluecellulab-2.6.73.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
81
+ bluecellulab-2.6.73.dist-info/METADATA,sha256=5caaaN9HbahBUG9GX5Q4jhWNe4NiREQHB2WqfPdj8Sc,8354
82
+ bluecellulab-2.6.73.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
+ bluecellulab-2.6.73.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
84
+ bluecellulab-2.6.73.dist-info/RECORD,,