bluecellulab 2.6.50__py3-none-any.whl → 2.6.52__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.

@@ -4,14 +4,17 @@ try:
4
4
  except ImportError:
5
5
  efel = None
6
6
  from itertools import islice
7
+ from itertools import repeat
7
8
  import logging
8
9
  from matplotlib.collections import LineCollection
9
10
  import matplotlib.pyplot as plt
11
+ from multiprocessing import Pool
10
12
  import neuron
11
13
  import numpy as np
12
14
  import pathlib
13
15
  import seaborn as sns
14
16
 
17
+
15
18
  from bluecellulab import Cell
16
19
  from bluecellulab.analysis.inject_sequence import run_stimulus
17
20
  from bluecellulab.analysis.plotting import plot_iv_curve, plot_fi_curve
@@ -87,30 +90,35 @@ def compute_plot_iv_curve(cell,
87
90
 
88
91
  list_amp = np.linspace(rheobase - 2, rheobase - 0.1, nb_bins) # [nA]
89
92
 
90
- steps = []
91
- times = []
92
- voltages = []
93
93
  # inject step current and record voltage response
94
94
  stim_factory = StimulusFactory(dt=0.1)
95
- for amp in list_amp:
96
- step_stimulus = stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
97
- recording = run_stimulus(cell.template_params,
98
- step_stimulus,
99
- section=injecting_section,
100
- segment=injecting_segment,
101
- recording_section=recording_section,
102
- recording_segment=recording_segment)
103
- steps.append(step_stimulus)
104
- times.append(recording.time)
105
- voltages.append(recording.voltage)
95
+ steps = [
96
+ stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
97
+ for amp in list_amp
98
+ ]
99
+
100
+ with Pool(len(steps)) as p:
101
+ recordings = p.starmap(
102
+ run_stimulus,
103
+ zip(
104
+ repeat(cell.template_params),
105
+ steps,
106
+ repeat(injecting_section),
107
+ repeat(injecting_segment),
108
+ repeat(True), # cvode
109
+ repeat(True), # add_hypamp
110
+ repeat(recording_section),
111
+ repeat(recording_segment),
112
+ )
113
+ )
106
114
 
107
115
  steady_states = []
108
116
  # compute steady state response
109
117
  efel.set_setting('Threshold', threshold_voltage)
110
- for voltage, t in zip(voltages, times):
118
+ for recording in recordings:
111
119
  trace = {
112
- 'T': t,
113
- 'V': voltage,
120
+ 'T': recording.time,
121
+ 'V': recording.voltage,
114
122
  'stim_start': [stim_start],
115
123
  'stim_end': [stim_start + duration]
116
124
  }
@@ -194,24 +202,30 @@ def compute_plot_fi_curve(cell,
194
202
  rheobase = calculate_rheobase(cell=cell, section=injecting_section, segx=injecting_segment)
195
203
 
196
204
  list_amp = np.linspace(rheobase, max_current, nb_bins) # [nA]
197
- steps = []
198
- spikes = []
199
- # inject step current and record spike response
200
205
  stim_factory = StimulusFactory(dt=0.1)
201
- for amp in list_amp:
202
- step_stimulus = stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
203
- recording = run_stimulus(cell.template_params,
204
- step_stimulus,
205
- section=injecting_section,
206
- segment=injecting_segment,
207
- recording_section=recording_section,
208
- recording_segment=recording_segment,
209
- enable_spike_detection=True,
210
- threshold_spike_detection=threshold_voltage)
211
- steps.append(step_stimulus)
212
- spikes.append(recording.spike)
213
-
214
- spike_count = [len(spike) for spike in spikes]
206
+ steps = [
207
+ stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
208
+ for amp in list_amp
209
+ ]
210
+
211
+ with Pool(len(steps)) as p:
212
+ recordings = p.starmap(
213
+ run_stimulus,
214
+ zip(
215
+ repeat(cell.template_params),
216
+ steps,
217
+ repeat(injecting_section),
218
+ repeat(injecting_segment),
219
+ repeat(True), # cvode
220
+ repeat(True), # add_hypamp
221
+ repeat(recording_section),
222
+ repeat(recording_segment),
223
+ repeat(True), # enable_spike_detection
224
+ repeat(threshold_voltage), # threshold_spike_detection
225
+ )
226
+ )
227
+
228
+ spike_count = [len(recording.spike) for recording in recordings]
215
229
 
216
230
  plot_fi_curve(list_amp,
217
231
  spike_count,
bluecellulab/cell/core.py CHANGED
@@ -525,6 +525,10 @@ class Cell(InjectableMixin, PlottableMixin):
525
525
  nc.record(spike_vec)
526
526
  self.recordings[f"spike_detector_{location}_{threshold}"] = spike_vec
527
527
 
528
+ def is_recording_spikes(self, location: str, threshold: float) -> bool:
529
+ key = f"spike_detector_{location}_{threshold}"
530
+ return key in self.recordings
531
+
528
532
  def get_recorded_spikes(self, location: str, threshold: float = -30) -> list[float]:
529
533
  """Get recorded spikes in the current cell.
530
534
 
@@ -756,6 +760,18 @@ class Cell(InjectableMixin, PlottableMixin):
756
760
  """Get a vector of AIS voltage."""
757
761
  return self.get_recording('self.axonal[1](0.5)._ref_v')
758
762
 
763
+ def add_variable_recording(self, variable: str, section, segx):
764
+ if variable == "v":
765
+ self.add_voltage_recording(section, segx)
766
+ else:
767
+ raise ValueError(f"Unsupported variable for recording: {variable}")
768
+
769
+ def get_variable_recording(self, variable: str, section, segx) -> np.ndarray:
770
+ if variable == "v":
771
+ return self.get_voltage_recording(section=section, segx=segx)
772
+ else:
773
+ raise ValueError(f"Unsupported variable '{variable}'")
774
+
759
775
  @property
760
776
  def n_segments(self) -> int:
761
777
  """Get the number of segments in the cell."""
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
  from __future__ import annotations
15
15
  from functools import lru_cache
16
+ import json
17
+ import logging
16
18
  from pathlib import Path
17
19
  from typing import Optional
18
20
 
@@ -21,6 +23,8 @@ from bluecellulab.stimulus.circuit_stimulus_definitions import Stimulus
21
23
 
22
24
  from bluepysnap import Simulation as SnapSimulation
23
25
 
26
+ logger = logging.getLogger(__name__)
27
+
24
28
 
25
29
  class SonataSimulationConfig:
26
30
  """Sonata implementation of SimulationConfig protocol."""
@@ -74,9 +78,42 @@ class SonataSimulationConfig:
74
78
  result.append(ConnectionOverrides.from_sonata(conn_entry))
75
79
  return result
76
80
 
81
+ @lru_cache(maxsize=1)
82
+ def get_compartment_sets(self) -> dict[str, dict]:
83
+ filepath = self.impl.config.get("compartment_sets_file")
84
+ if not filepath:
85
+ raise ValueError("No 'compartment_sets_file' entry found in SONATA config.")
86
+ with open(filepath, 'r') as f:
87
+ return json.load(f)
88
+
89
+ @lru_cache(maxsize=1)
90
+ def get_node_sets(self) -> dict[str, dict]:
91
+ filepath = self.impl.circuit.config.get("node_sets_file")
92
+ if not filepath:
93
+ raise ValueError("No 'node_sets_file' entry found in SONATA config.")
94
+ with open(filepath, 'r') as f:
95
+ return json.load(f)
96
+
97
+ @lru_cache(maxsize=1)
98
+ def get_report_entries(self) -> dict[str, dict]:
99
+ """Returns the 'reports' dictionary from the SONATA simulation config.
100
+
101
+ Each key is a report name, and the value is its configuration.
102
+ """
103
+ reports = self.impl.config.get("reports", {})
104
+ if not isinstance(reports, dict):
105
+ raise ValueError("Invalid format for 'reports' in SONATA config.")
106
+ return reports
107
+
77
108
  def connection_entries(self) -> list[ConnectionOverrides]:
78
109
  return self._connection_entries() + self._connection_overrides
79
110
 
111
+ def report_file_path(self, report_cfg: dict, report_key: str) -> Path:
112
+ """Resolve the full path for the report output file."""
113
+ output_dir = Path(self.output_root_path)
114
+ file_name = report_cfg.get("file_name", f"{report_key}.h5")
115
+ return output_dir / file_name
116
+
80
117
  @property
81
118
  def base_seed(self) -> int:
82
119
  return self.impl.run.random_seed
@@ -135,7 +172,13 @@ class SonataSimulationConfig:
135
172
 
136
173
  @property
137
174
  def output_root_path(self) -> str:
138
- return self.impl.config["output"]["output_dir"]
175
+ return self.impl.config.get("output", {}).get("output_dir", "output")
176
+
177
+ @property
178
+ def spikes_file_path(self) -> Path:
179
+ output_dir = Path(self.output_root_path)
180
+ spikes_file = self.impl.config.get("output", {}).get("spikes_file", "spikes.h5")
181
+ return output_dir / spikes_file
139
182
 
140
183
  @property
141
184
  def extracellular_calcium(self) -> Optional[float]:
@@ -17,7 +17,6 @@ from __future__ import annotations
17
17
  from pathlib import Path
18
18
  import logging
19
19
 
20
- import bluepy
21
20
  import numpy as np
22
21
 
23
22
  from bluecellulab.circuit.node_id import CellId
@@ -28,6 +27,7 @@ logger = logging.getLogger(__name__)
28
27
  def parse_outdat(path: str | Path) -> dict[CellId, np.ndarray]:
29
28
  """Parse the replay spiketrains in a out.dat formatted file pointed to by
30
29
  path."""
30
+ import bluepy
31
31
  spikes = bluepy.impl.spike_report.SpikeReport.load(path).get()
32
32
  # convert Series to DataFrame with 2 columns for `groupby` operation
33
33
  spike_df = spikes.to_frame().reset_index()
@@ -17,10 +17,12 @@ simulations."""
17
17
 
18
18
  from __future__ import annotations
19
19
  from collections.abc import Iterable
20
+ import os
20
21
  from pathlib import Path
21
22
  from typing import Optional
22
23
  import logging
23
24
 
25
+ from collections import defaultdict
24
26
  import neuron
25
27
  import numpy as np
26
28
  import pandas as pd
@@ -45,6 +47,7 @@ from bluecellulab.circuit.simulation_access import BluepySimulationAccess, Simul
45
47
  from bluecellulab.importer import load_mod_files
46
48
  from bluecellulab.rngsettings import RNGSettings
47
49
  from bluecellulab.simulation.neuron_globals import NeuronGlobals
50
+ from bluecellulab.simulation.report import configure_all_reports, write_compartment_report, write_sonata_spikes
48
51
  from bluecellulab.stimulus.circuit_stimulus_definitions import Noise, OrnsteinUhlenbeck, RelativeOrnsteinUhlenbeck, RelativeShotNoise, ShotNoise
49
52
  import bluecellulab.stimulus.circuit_stimulus_definitions as circuit_stimulus_definitions
50
53
  from bluecellulab.exceptions import BluecellulabError
@@ -301,6 +304,16 @@ class CircuitSimulation:
301
304
  add_linear_stimuli=add_linear_stimuli
302
305
  )
303
306
 
307
+ configure_all_reports(
308
+ cells=self.cells,
309
+ simulation_config=self.circuit_access.config
310
+ )
311
+
312
+ # add spike recordings
313
+ for cell in self.cells.values():
314
+ if not cell.is_recording_spikes("soma", threshold=self.spike_threshold):
315
+ cell.start_recording_spikes(None, location="soma", threshold=self.spike_threshold)
316
+
304
317
  def _add_stimuli(self, add_noise_stimuli=False,
305
318
  add_hyperpolarizing_stimuli=False,
306
319
  add_relativelinear_stimuli=False,
@@ -458,13 +471,26 @@ class CircuitSimulation:
458
471
  @staticmethod
459
472
  def merge_pre_spike_trains(*train_dicts) -> dict[CellId, np.ndarray]:
460
473
  """Merge presynaptic spike train dicts."""
461
- filtered_dicts = [d for d in train_dicts if d not in [None, {}, [], ()]]
474
+ filtered_dicts = [d for d in train_dicts if isinstance(d, dict) and d]
475
+
476
+ if not filtered_dicts:
477
+ logger.warning("merge_pre_spike_trains: No presynaptic spike trains found.")
478
+ return {}
462
479
 
463
480
  all_keys = set().union(*[d.keys() for d in filtered_dicts])
464
- return {
465
- k: np.sort(np.concatenate([d[k] for d in filtered_dicts if k in d]))
466
- for k in all_keys
467
- }
481
+ result = {}
482
+
483
+ for k in all_keys:
484
+ valid_arrays = []
485
+ for d in filtered_dicts:
486
+ if k in d:
487
+ val = d[k]
488
+ if isinstance(val, (np.ndarray, list)) and len(val) > 0:
489
+ valid_arrays.append(np.asarray(val))
490
+ if valid_arrays:
491
+ result[k] = np.sort(np.concatenate(valid_arrays))
492
+
493
+ return result
468
494
 
469
495
  def _add_connections(
470
496
  self,
@@ -646,6 +672,8 @@ class CircuitSimulation:
646
672
  forward_skip_value=forward_skip_value,
647
673
  show_progress=show_progress)
648
674
 
675
+ self.write_reports()
676
+
649
677
  def get_mainsim_voltage_trace(
650
678
  self, cell_id: int | tuple[str, int], t_start=None, t_stop=None, t_step=None
651
679
  ) -> np.ndarray:
@@ -779,3 +807,76 @@ class CircuitSimulation:
779
807
  record_dt=cell_kwargs['record_dt'],
780
808
  template_format=cell_kwargs['template_format'],
781
809
  emodel_properties=cell_kwargs['emodel_properties'])
810
+
811
+ def write_reports(self):
812
+ """Write all reports defined in the simulation config."""
813
+ report_entries = self.circuit_access.config.get_report_entries()
814
+
815
+ for report_name, report_cfg in report_entries.items():
816
+ report_type = report_cfg.get("type", "compartment")
817
+ section = report_cfg.get("sections")
818
+
819
+ if report_type != "compartment":
820
+ raise NotImplementedError(f"Report type '{report_type}' is not supported.")
821
+
822
+ output_path = self.circuit_access.config.report_file_path(report_cfg, report_name)
823
+ if section == "compartment_set":
824
+ if report_cfg.get("cells") is not None:
825
+ raise ValueError(
826
+ "Report config error: 'cells' must not be set when using 'compartment_set' sections."
827
+ )
828
+ compartment_sets = self.circuit_access.config.get_compartment_sets()
829
+ write_compartment_report(
830
+ report_name=report_name,
831
+ output_path=output_path,
832
+ cells=self.cells,
833
+ report_cfg=report_cfg,
834
+ source_sets=compartment_sets,
835
+ source_type="compartment_set"
836
+ )
837
+
838
+ else:
839
+ node_sets = self.circuit_access.config.get_node_sets()
840
+ if report_cfg.get("compartments") not in ("center", "all"):
841
+ raise ValueError(
842
+ f"Unsupported 'compartments' value '{report_cfg.get('compartments')}' "
843
+ "for node-based section recording (must be 'center' or 'all')."
844
+ )
845
+ write_compartment_report(
846
+ report_name=report_name,
847
+ output_path=output_path,
848
+ cells=self.cells,
849
+ report_cfg=report_cfg,
850
+ source_sets=node_sets,
851
+ source_type="node_set"
852
+ )
853
+
854
+ self.write_spike_report()
855
+
856
+ def write_spike_report(self):
857
+ """Collect and write in-memory recorded spike times to a SONATA HDF5
858
+ file, grouped by population as required by the SONATA specification."""
859
+ output_path = self.circuit_access.config.spikes_file_path
860
+
861
+ if os.path.exists(output_path):
862
+ os.remove(output_path)
863
+
864
+ # Group spikes per population
865
+ spikes_by_population = defaultdict(dict)
866
+ for gid, cell in self.cells.items():
867
+ pop = getattr(gid, 'population_name', None)
868
+ if pop is None:
869
+ continue
870
+ try:
871
+ cell_spikes = cell.get_recorded_spikes(location="soma", threshold=self.spike_threshold)
872
+ if cell_spikes is not None:
873
+ spikes_by_population[pop][gid.id] = list(cell_spikes)
874
+ except AttributeError:
875
+ continue
876
+
877
+ # Ensure we at least create empty groups for all known populations
878
+ all_populations = set(getattr(gid, 'population_name', None) for gid in self.cells.keys())
879
+
880
+ for pop in all_populations:
881
+ spikes = spikes_by_population.get(pop, {}) # May be empty
882
+ write_sonata_spikes(output_path, spikes, pop)
@@ -0,0 +1,227 @@
1
+ # Copyright 2025 Open Brain Institute
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Report class of bluecellulab."""
15
+
16
+ import logging
17
+ from pathlib import Path
18
+ import h5py
19
+ from typing import List
20
+ import numpy as np
21
+ import os
22
+
23
+ from bluecellulab.tools import resolve_segments, resolve_source_nodes
24
+ from bluecellulab.cell.cell_dict import CellDict
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ def _configure_recording(cell, report_cfg, source, source_type, report_name):
30
+ variable = report_cfg.get("variable_name", "v")
31
+
32
+ node_id = cell.cell_id
33
+ compartment_nodes = source.get("compartment_set") if source_type == "compartment_set" else None
34
+
35
+ targets = resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type)
36
+ for sec, sec_name, seg in targets:
37
+ try:
38
+ cell.add_variable_recording(variable=variable, section=sec, segx=seg)
39
+ except AttributeError:
40
+ logger.warning(f"Recording for variable '{variable}' is not implemented in Cell.")
41
+ return
42
+ except Exception as e:
43
+ logger.warning(
44
+ f"Failed to record '{variable}' at {sec_name}({seg}) on GID {node_id} for report '{report_name}': {e}"
45
+ )
46
+
47
+
48
+ def configure_all_reports(cells, simulation_config):
49
+ report_entries = simulation_config.get_report_entries()
50
+
51
+ for report_name, report_cfg in report_entries.items():
52
+ report_type = report_cfg.get("type", "compartment")
53
+ section = report_cfg.get("sections", "soma")
54
+
55
+ if report_type != "compartment":
56
+ raise NotImplementedError(f"Report type '{report_type}' is not supported.")
57
+
58
+ if section == "compartment_set":
59
+ source_type = "compartment_set"
60
+ source_sets = simulation_config.get_compartment_sets()
61
+ source_name = report_cfg.get("compartments")
62
+ if not source_name:
63
+ logger.warning(f"Report '{report_name}' does not specify a node set in 'compartments' for {source_type}.")
64
+ continue
65
+ else:
66
+ source_type = "node_set"
67
+ source_sets = simulation_config.get_node_sets()
68
+ source_name = report_cfg.get("cells")
69
+ if not source_name:
70
+ logger.warning(f"Report '{report_name}' does not specify a node set in 'cells' for {source_type}.")
71
+ continue
72
+
73
+ source = source_sets.get(source_name)
74
+ if not source:
75
+ logger.warning(f"{source_type.title()} '{source_name}' not found for report '{report_name}', skipping recording.")
76
+ continue
77
+
78
+ population = source["population"]
79
+ node_ids, _ = resolve_source_nodes(source, source_type, cells, population)
80
+
81
+ for node_id in node_ids:
82
+ cell = cells.get((population, node_id))
83
+ if not cell:
84
+ continue
85
+ _configure_recording(cell, report_cfg, source, source_type, report_name)
86
+
87
+
88
+ def write_compartment_report(
89
+ report_name: str,
90
+ output_path: str,
91
+ cells: CellDict,
92
+ report_cfg: dict,
93
+ source_sets: dict,
94
+ source_type: str,
95
+ ):
96
+ """Write a SONATA-compatible compartment report to an HDF5 file.
97
+
98
+ This function collects time series data (e.g., membrane voltage, ion currents)
99
+ from a group of cells defined by either a node set or a compartment set, and
100
+ writes the data to a SONATA-style report file.
101
+
102
+ Args:
103
+ output_path (str): Path to the output HDF5 file.
104
+ cells (CellDict): Mapping of (population, node_id) to cell objects that
105
+ provide access to pre-recorded variable traces.
106
+ report_cfg (dict): Configuration for the report. Must include:
107
+ - "variable_name": Name of the variable to report (e.g., "v", "ica", "ina").
108
+ - "start_time", "end_time", "dt": Timing parameters.
109
+ - "cells" or "compartments": Name of the node or compartment set.
110
+ source_sets (dict): Dictionary of either node sets or compartment sets.
111
+ source_type (str): Either "node_set" or "compartment_set".
112
+
113
+ Raises:
114
+ ValueError: If the specified source set is not found.
115
+
116
+ Notes:
117
+ - Currently supports only variables explicitly handled in Cell.get_variable_recording().
118
+ - Cells without recordings for the requested variable will be skipped.
119
+ """
120
+ source_name = report_cfg.get("cells") if source_type == "node_set" else report_cfg.get("compartments")
121
+ source = source_sets.get(source_name)
122
+ if not source:
123
+ logger.warning(f"{source_type.title()} '{source_name}' not found for report '{report_name}', skipping write.")
124
+ return
125
+
126
+ population = source["population"]
127
+
128
+ node_ids, compartment_nodes = resolve_source_nodes(source, source_type, cells, population)
129
+
130
+ data_matrix: List[np.ndarray] = []
131
+ recorded_node_ids: List[int] = []
132
+ index_pointers: List[int] = [0]
133
+ element_ids: List[int] = []
134
+
135
+ for node_id in node_ids:
136
+ try:
137
+ cell = cells[(population, node_id)]
138
+ except KeyError:
139
+ continue
140
+ if not cell:
141
+ continue
142
+
143
+ targets = resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type)
144
+ for sec, sec_name, seg in targets:
145
+ try:
146
+ variable = report_cfg.get("variable_name", "v")
147
+ trace = cell.get_variable_recording(variable=variable, section=sec, segx=seg)
148
+ data_matrix.append(trace)
149
+ recorded_node_ids.append(node_id)
150
+ element_ids.append(len(element_ids))
151
+ index_pointers.append(index_pointers[-1] + 1)
152
+ except Exception as e:
153
+ logger.warning(f"Failed recording: GID {node_id} sec {sec_name} seg {seg}: {e}")
154
+
155
+ if not data_matrix:
156
+ logger.warning(f"No data recorded for report '{source_name}'. Skipping write.")
157
+ return
158
+
159
+ write_sonata_report_file(
160
+ output_path, population, data_matrix, recorded_node_ids, index_pointers, element_ids, report_cfg
161
+ )
162
+
163
+
164
+ def write_sonata_report_file(
165
+ output_path, population, data_matrix, recorded_node_ids, index_pointers, element_ids, report_cfg
166
+ ):
167
+ data_array = np.stack(data_matrix, axis=1)
168
+ node_ids_arr = np.array(recorded_node_ids, dtype=np.uint64)
169
+ index_ptr_arr = np.array(index_pointers, dtype=np.uint64)
170
+ element_ids_arr = np.array(element_ids, dtype=np.uint32)
171
+ time_array = np.array([
172
+ report_cfg.get("start_time", 0.0),
173
+ report_cfg.get("end_time", 0.0),
174
+ report_cfg.get("dt", 0.1)
175
+ ], dtype=np.float64)
176
+
177
+ output_path = Path(output_path)
178
+ output_path.parent.mkdir(parents=True, exist_ok=True)
179
+ with h5py.File(output_path, "w") as f:
180
+ grp = f.require_group(f"/report/{population}")
181
+ data_ds = grp.create_dataset("data", data=data_array.astype(np.float32))
182
+
183
+ variable = report_cfg.get("variable_name", "v")
184
+ if variable == "v":
185
+ data_ds.attrs["units"] = "mV"
186
+
187
+ mapping = grp.require_group("mapping")
188
+ mapping.create_dataset("node_ids", data=node_ids_arr)
189
+ mapping.create_dataset("index_pointers", data=index_ptr_arr)
190
+ mapping.create_dataset("element_ids", data=element_ids_arr)
191
+ time_ds = mapping.create_dataset("time", data=time_array)
192
+ time_ds.attrs["units"] = "ms"
193
+
194
+
195
+ def write_sonata_spikes(f_name: str, spikes_dict: dict[int, np.ndarray], population: str):
196
+ """Write a SONATA spike group to a spike file from {node_id: [t1, t2,
197
+ ...]}."""
198
+ all_node_ids: List[int] = []
199
+ all_timestamps: List[float] = []
200
+
201
+ for node_id, times in spikes_dict.items():
202
+ all_node_ids.extend([node_id] * len(times))
203
+ all_timestamps.extend(times)
204
+
205
+ if not all_timestamps:
206
+ logger.warning(f"No spikes to write for population '{population}'.")
207
+
208
+ # Sort by time for consistency
209
+ sorted_indices = np.argsort(all_timestamps)
210
+ node_ids_sorted = np.array(all_node_ids, dtype=np.uint64)[sorted_indices]
211
+ timestamps_sorted = np.array(all_timestamps, dtype=np.float64)[sorted_indices]
212
+
213
+ os.makedirs(os.path.dirname(f_name), exist_ok=True)
214
+ with h5py.File(f_name, 'a') as f: # 'a' to allow multiple writes
215
+ spikes_group = f.require_group("spikes")
216
+ if population in spikes_group:
217
+ logger.warning(f"Overwriting existing group for population '{population}' in {f_name}.")
218
+ del spikes_group[population]
219
+
220
+ group = spikes_group.create_group(population)
221
+ sorting_enum = h5py.enum_dtype({'none': 0, 'by_id': 1, 'by_time': 2}, basetype='u1')
222
+ group.attrs.create("sorting", 2, dtype=sorting_enum) # 2 = by_time
223
+
224
+ timestamps_ds = group.create_dataset("timestamps", data=timestamps_sorted)
225
+ group.create_dataset("node_ids", data=node_ids_sorted)
226
+
227
+ timestamps_ds.attrs["units"] = "ms" # SONATA-required
bluecellulab/tools.py CHANGED
@@ -394,14 +394,14 @@ def check_empty_topology() -> bool:
394
394
 
395
395
 
396
396
  def calculate_max_thresh_current(cell: Cell,
397
- threshold_voltage: float = -30.0,
397
+ threshold_voltage: float = -20.0,
398
398
  section: str = "soma[0]",
399
399
  segx: float = 0.5) -> float:
400
400
  """Calculate the upper bound threshold current.
401
401
 
402
402
  Args:
403
403
  cell (bluecellulab.cell.Cell): The initialized cell model.
404
- threshold_voltage (float, optional): Voltage threshold for spike detection. Default is -30.0 mV.
404
+ threshold_voltage (float, optional): Voltage threshold for spike detection. Default is -20.0 mV.
405
405
  section (str, optional): The section where current is injected.
406
406
  segx (float, optional): Fractional location within the section for current injection.
407
407
 
@@ -508,19 +508,85 @@ def validate_section_and_segment(cell: Cell, section_name: str, segment_position
508
508
 
509
509
 
510
510
  def get_section(cell: Cell, section_name: str) -> NeuronSection:
511
- """Retrieve a NEURON section from the cell by its name.
511
+ """Return a single, fully specified NEURON section (e.g., 'soma[0]',
512
+ 'dend[3]').
512
513
 
513
- Args:
514
- cell (Cell): The cell object containing the sections.
515
- section_name (str): The name of the section to retrieve.
514
+ Raises:
515
+ ValueError or TypeError if the section is not found or invalid.
516
+ """
517
+ if section_name in cell.sections:
518
+ section = cell.sections[section_name]
519
+ if hasattr(section, "nseg"):
520
+ return section
521
+ raise TypeError(f"'{section_name}' exists but is not a NEURON section.")
522
+
523
+ available = ", ".join(cell.sections.keys())
524
+ raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
516
525
 
517
- Returns:
518
- NeuronSection: The NEURON section corresponding to the provided name.
526
+
527
+ def get_sections(cell, section_name: str):
528
+ """Return a list of NEURON sections.
529
+
530
+ If the section name is a fully specified one (e.g., 'dend[3]'), return it as a list of one.
531
+ If the section name is a base name (e.g., 'dend'), return all matching sections like 'dend[0]', 'dend[1]', etc.
519
532
 
520
533
  Raises:
521
- ValueError: If the section with the specified name does not exist.
534
+ ValueError or TypeError if no valid sections are found.
522
535
  """
536
+ # Try to interpret as fully qualified section name
523
537
  try:
524
- return cell.sections[section_name]
525
- except KeyError:
526
- raise ValueError(f"Section '{section_name}' not found in the cell.")
538
+ return [get_section(cell, section_name)]
539
+ except ValueError:
540
+ pass # Not a precise match; try prefix match
541
+
542
+ # Fallback to prefix-based match (e.g., 'dend' → 'dend[0]', 'dend[1]', ...)
543
+ matched = [
544
+ section for name, section in cell.sections.items()
545
+ if name.startswith(f"{section_name}[")
546
+ ]
547
+ if matched:
548
+ return matched
549
+
550
+ available = ", ".join(cell.sections.keys())
551
+ raise ValueError(f"Section '{section_name}' not found. Available: [{available}]")
552
+
553
+
554
+ def resolve_segments(cell, report_cfg, node_id, compartment_nodes, source_type):
555
+ """Determine which segments to record from one or more NEURON sections."""
556
+ section_name = report_cfg.get("sections", "soma")
557
+ compartment = report_cfg.get("compartments", "center")
558
+
559
+ if source_type == "compartment_set":
560
+ return [
561
+ (get_section(cell, sec), sec, seg)
562
+ for _, sec, seg in compartment_nodes if _ == node_id
563
+ ]
564
+
565
+ sections = get_sections(cell, section_name)
566
+ targets = []
567
+
568
+ for sec in sections:
569
+ sec_name = sec.name().split(".")[-1]
570
+ if compartment == "center":
571
+ targets.append((sec, sec_name, 0.5))
572
+ elif compartment == "all":
573
+ for seg in sec:
574
+ targets.append((sec, sec_name, seg.x))
575
+ else:
576
+ raise ValueError(
577
+ f"Unsupported 'compartments' value '{compartment}' — must be 'center' or 'all'."
578
+ )
579
+
580
+ return targets
581
+
582
+
583
+ def resolve_source_nodes(source, source_type, cells, population):
584
+ if source_type == "compartment_set":
585
+ compartment_nodes = source.get("compartment_set", [])
586
+ node_ids = [entry[0] for entry in compartment_nodes]
587
+ else: # node_set
588
+ node_ids = source.get("node_id")
589
+ if node_ids is None:
590
+ node_ids = [node_id for (pop, node_id) in cells.keys() if pop == population]
591
+ compartment_nodes = None
592
+ return node_ids, compartment_nodes
bluecellulab/utils.py CHANGED
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
  import contextlib
5
5
  import io
6
6
  import json
7
+ import multiprocessing
8
+ from multiprocessing import pool
7
9
 
8
10
  import numpy as np
9
11
 
@@ -56,3 +58,27 @@ class NumpyEncoder(json.JSONEncoder):
56
58
  elif isinstance(obj, np.ndarray):
57
59
  return obj.tolist()
58
60
  return json.JSONEncoder.default(self, obj)
61
+
62
+
63
+ class NoDaemonProcess(multiprocessing.Process):
64
+ """Class that represents a non-daemon process."""
65
+
66
+ # pylint: disable=dangerous-default-value
67
+
68
+ def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
69
+ """Ensures group=None, for macosx."""
70
+ super().__init__(group=None, target=target, name=name, args=args, kwargs=kwargs)
71
+
72
+ @property
73
+ def daemon(self):
74
+ return False
75
+
76
+ @daemon.setter
77
+ def daemon(self, val):
78
+ pass
79
+
80
+
81
+ class NestedPool(pool.Pool): # pylint: disable=abstract-method
82
+ """Class that represents a MultiProcessing nested pool."""
83
+
84
+ Process = NoDaemonProcess
@@ -24,6 +24,7 @@ from bluecellulab.analysis.analysis import compute_plot_fi_curve
24
24
  from bluecellulab.analysis.analysis import compute_plot_iv_curve
25
25
  from bluecellulab.analysis.inject_sequence import run_multirecordings_stimulus
26
26
  from bluecellulab.analysis.inject_sequence import run_stimulus
27
+ from bluecellulab.cell.core import Cell
27
28
  from bluecellulab.stimulus.factory import IDRestTimings
28
29
  from bluecellulab.stimulus.factory import StimulusFactory
29
30
  from bluecellulab.tools import calculate_input_resistance
@@ -79,12 +80,12 @@ def plot_traces(recordings, out_dir, fname, title, labels=None, xlim=None):
79
80
  return outpath
80
81
 
81
82
 
82
- def spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
83
+ def spiking_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
83
84
  """Spiking test: cell should spike."""
84
85
  stim_factory = StimulusFactory(dt=1.0)
85
86
  step_stimulus = stim_factory.idrest(threshold_current=rheobase, threshold_percentage=200)
86
87
  recording = run_stimulus(
87
- cell.template_params,
88
+ template_params,
88
89
  step_stimulus,
89
90
  "soma[0]",
90
91
  0.5,
@@ -111,13 +112,13 @@ def spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
111
112
  }
112
113
 
113
114
 
114
- def depolarization_block_test(cell, rheobase, out_dir):
115
+ def depolarization_block_test(template_params, rheobase, out_dir):
115
116
  """Depolarization block test: no depolarization block should be detected."""
116
117
  # Run the stimulus
117
118
  stim_factory = StimulusFactory(dt=1.0)
118
119
  step_stimulus = stim_factory.idrest(threshold_current=rheobase, threshold_percentage=200)
119
120
  recording = run_stimulus(
120
- cell.template_params,
121
+ template_params,
121
122
  step_stimulus,
122
123
  "soma[0]",
123
124
  0.5,
@@ -151,16 +152,16 @@ def depolarization_block_test(cell, rheobase, out_dir):
151
152
  }
152
153
 
153
154
 
154
- def bpap_test(cell, rheobase, out_dir="./"):
155
+ def bpap_test(template_params, rheobase, out_dir="./"):
155
156
  """Back-propagating action potential test: exponential fit should decay.
156
157
 
157
158
  Args:
158
- cell (Cell): The cell to test.
159
+ template_params (dict): The template parameters for creating the cell.
159
160
  rheobase (float): The rheobase current to use for the test.
160
161
  out_dir (str): Directory to save the figure.
161
162
  """
162
163
  amplitude = 10. * rheobase # Use 1000% of the rheobase current
163
- bpap = BPAP(cell)
164
+ bpap = BPAP(Cell.from_template_parameters(template_params))
164
165
  bpap.run(duration=1500, amplitude=amplitude)
165
166
  soma_amp, dend_amps, dend_dist, apic_amps, apic_dist = bpap.get_amplitudes_and_distances()
166
167
  validated, notes = bpap.validate(soma_amp, dend_amps, dend_dist, apic_amps, apic_dist)
@@ -190,11 +191,11 @@ def bpap_test(cell, rheobase, out_dir="./"):
190
191
  }
191
192
 
192
193
 
193
- def ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
194
+ def ais_spiking_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
194
195
  """AIS spiking test: axon should spike before soma."""
195
196
  name = "Simulatable Neuron AIS Spiking Validation"
196
197
  # Check that the cell has an axon
197
- if len(cell.axonal) == 0:
198
+ if len(Cell.from_template_parameters(template_params).axonal) == 0:
198
199
  return {
199
200
  "name": name,
200
201
  "passed": True,
@@ -206,7 +207,7 @@ def ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
206
207
  stim_factory = StimulusFactory(dt=1.0)
207
208
  step_stimulus = stim_factory.idrest(threshold_current=rheobase, threshold_percentage=200)
208
209
  recordings = run_multirecordings_stimulus(
209
- cell.template_params,
210
+ template_params,
210
211
  step_stimulus,
211
212
  "soma[0]",
212
213
  0.5,
@@ -259,14 +260,14 @@ def ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
259
260
  }
260
261
 
261
262
 
262
- def hyperpolarization_test(cell, rheobase, out_dir):
263
+ def hyperpolarization_test(template_params, rheobase, out_dir):
263
264
  """Hyperpolarization test: hyperpolarized voltage should be lower than RMP."""
264
265
  name = "Simulatable Neuron Hyperpolarization Validation"
265
266
  # Run the stimulus
266
267
  stim_factory = StimulusFactory(dt=1.0)
267
268
  step_stimulus = stim_factory.iv(threshold_current=rheobase, threshold_percentage=-40)
268
269
  recording = run_stimulus(
269
- cell.template_params,
270
+ template_params,
270
271
  step_stimulus,
271
272
  "soma[0]",
272
273
  0.5,
@@ -331,11 +332,11 @@ def rin_test(rin):
331
332
  }
332
333
 
333
334
 
334
- def iv_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
335
+ def iv_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
335
336
  """IV curve should have a positive slope."""
336
337
  name = "Simulatable Neuron IV Curve Validation"
337
338
  amps, steady_states = compute_plot_iv_curve(
338
- cell,
339
+ Cell.from_template_parameters(template_params),
339
340
  rheobase=rheobase,
340
341
  threshold_voltage=spike_threshold_voltage,
341
342
  nb_bins=5,
@@ -369,11 +370,11 @@ def iv_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
369
370
  }
370
371
 
371
372
 
372
- def fi_test(cell, rheobase, out_dir, spike_threshold_voltage=-30.):
373
+ def fi_test(template_params, rheobase, out_dir, spike_threshold_voltage=-30.):
373
374
  """FI curve should have a positive slope."""
374
375
  name = "Simulatable Neuron FI Curve Validation"
375
376
  amps, spike_counts = compute_plot_fi_curve(
376
- cell,
377
+ Cell.from_template_parameters(template_params),
377
378
  rheobase=rheobase,
378
379
  threshold_voltage=spike_threshold_voltage,
379
380
  nb_bins=5,
@@ -421,7 +422,6 @@ def run_validations(
421
422
  out_dir = pathlib.Path(output_dir) / cell_name
422
423
  out_dir.mkdir(parents=True, exist_ok=True)
423
424
 
424
- # cell = Cell.from_template_parameters(template_params)
425
425
  # get me-model properties
426
426
  holding_current = cell.hypamp if cell.hypamp else 0.0
427
427
  if cell.threshold:
@@ -437,41 +437,69 @@ def run_validations(
437
437
  emodel_properties=cell.template_params.emodel_properties,
438
438
  )
439
439
 
440
- # Validation 1: Spiking Test
441
- logger.debug("Running spiking test")
442
- spiking_test_result = spiking_test(cell, rheobase, out_dir, spike_threshold_voltage)
440
+ logger.debug("Running validations...")
441
+ from bluecellulab.utils import NestedPool
442
+ with NestedPool(processes=8) as pool:
443
+ # Validation 1: Spiking Test
444
+ spiking_test_result_future = pool.apply_async(
445
+ spiking_test,
446
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
447
+ )
443
448
 
444
- # Validation 2: Depolarization Block Test
445
- logger.debug("Running depolarization block test")
446
- depolarization_block_result = depolarization_block_test(cell, rheobase, out_dir)
449
+ # Validation 2: Depolarization Block Test
450
+ depolarization_block_result_future = pool.apply_async(
451
+ depolarization_block_test,
452
+ (cell.template_params, rheobase, out_dir)
453
+ )
447
454
 
448
- # Validation 3: Backpropagating AP Test
449
- logger.debug("Running backpropagating AP test")
450
- bpap_result = bpap_test(cell, rheobase, out_dir)
455
+ # Validation 3: Backpropagating AP Test
456
+ bpap_result_future = pool.apply_async(
457
+ bpap_test,
458
+ (cell.template_params, rheobase, out_dir)
459
+ )
451
460
 
452
- # Validation 4: Postsynaptic Potential Test
453
- # logger.debug("Running postsynaptic potential test")
454
- # We have to wait for ProbAMPANMDA_EMS to be present in entitycore to implement this test
461
+ # Validation 4: Postsynaptic Potential Test
462
+ # We have to wait for ProbAMPANMDA_EMS to be present in entitycore to implement this test
455
463
 
456
- # Validation 5: AIS Spiking Test
457
- logger.debug("Running AIS spiking test")
458
- ais_spiking_test_result = ais_spiking_test(cell, rheobase, out_dir, spike_threshold_voltage)
464
+ # Validation 5: AIS Spiking Test
465
+ ais_spiking_test_result_future = pool.apply_async(
466
+ ais_spiking_test,
467
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
468
+ )
459
469
 
460
- # Validation 6: Hyperpolarization Test
461
- logger.debug("Running hyperpolarization test")
462
- hyperpolarization_result = hyperpolarization_test(cell, rheobase, out_dir)
470
+ # Validation 6: Hyperpolarization Test
471
+ hyperpolarization_result_future = pool.apply_async(
472
+ hyperpolarization_test,
473
+ (cell.template_params, rheobase, out_dir)
474
+ )
463
475
 
464
- # Validation 7: Rin Test
465
- logger.debug("Running Rin test")
466
- rin_result = rin_test(rin)
476
+ # Validation 7: Rin Test
477
+ rin_result_future = pool.apply_async(
478
+ rin_test,
479
+ (rin,)
480
+ )
481
+
482
+ # # Validation 8: IV Test
483
+ iv_test_result_future = pool.apply_async(
484
+ iv_test,
485
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
486
+ )
467
487
 
468
- # # Validation 8: IV Test
469
- logger.debug("Running IV test")
470
- iv_test_result = iv_test(cell, rheobase, out_dir, spike_threshold_voltage)
488
+ # # Validation 9: FI Test
489
+ fi_test_result_future = pool.apply_async(
490
+ fi_test,
491
+ (cell.template_params, rheobase, out_dir, spike_threshold_voltage)
492
+ )
471
493
 
472
- # # Validation 9: FI Test
473
- logger.debug("Running FI test")
474
- fi_test_result = fi_test(cell, rheobase, out_dir, spike_threshold_voltage)
494
+ # Wait for all validations to complete
495
+ spiking_test_result = spiking_test_result_future.get()
496
+ depolarization_block_result = depolarization_block_result_future.get()
497
+ bpap_result = bpap_result_future.get()
498
+ ais_spiking_test_result = ais_spiking_test_result_future.get()
499
+ hyperpolarization_result = hyperpolarization_result_future.get()
500
+ rin_result = rin_result_future.get()
501
+ iv_test_result = iv_test_result_future.get()
502
+ fi_test_result = fi_test_result_future.get()
475
503
 
476
504
  return {
477
505
  "memodel_properties": {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bluecellulab
3
- Version: 2.6.50
3
+ Version: 2.6.52
4
4
  Summary: Biologically detailed neural network simulations and analysis.
5
5
  Author: Blue Brain Project, EPFL
6
6
  License: Apache2.0
@@ -1,5 +1,5 @@
1
1
  bluecellulab/__init__.py,sha256=1d_CKIJLIpon7o13h3lBnV_-33obZEPwa9KDTjlFPD8,880
2
- bluecellulab/circuit_simulation.py,sha256=dXaYblCWWMX2I7HhlL0chTyelePj-6Io2NNdGK5oB-o,35020
2
+ bluecellulab/circuit_simulation.py,sha256=zF72yNZnnGUN40Vp66VFvr2Rz4tVuD3iWtvao23GoHQ,39270
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
@@ -10,18 +10,18 @@ bluecellulab/plotwindow.py,sha256=ePU-EegZ1Sqk0SoYYiQ6k24ZO4s3Hgfpx10uePiJ5xM,27
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=hF1HJcera0oggetsfN5PNTRYCpFS0sQiZlVxw4jRd1g,17968
13
+ bluecellulab/tools.py,sha256=Cc-Pv_7ban1EA9aqj8mfnjq-187llqj5WVzuQdesqhc,20395
14
14
  bluecellulab/type_aliases.py,sha256=DvgjERv2Ztdw_sW63JrZTQGpJ0x5uMTFB5hcBHDb0WA,441
15
- bluecellulab/utils.py,sha256=SbOOkzw1YGjCKV3qOw0zpabNEy7V9BRtgMLsQJiFRq4,1526
15
+ bluecellulab/utils.py,sha256=0NhwlzyLnSi8kziSfDsQf7pokO4qDkMJVAO33kSX4O0,2227
16
16
  bluecellulab/verbosity.py,sha256=T0IgX7DrRo19faxrT4Xzb27gqxzoILQ8FzYKxvUeaPM,1342
17
17
  bluecellulab/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- bluecellulab/analysis/analysis.py,sha256=kZaSYCYOK3A4rrYFPfdsw6rsbjutP-cpFt_VxDx73cs,21270
18
+ bluecellulab/analysis/analysis.py,sha256=53g6SW_9ZbG3fbhuXJqurAPRR4VG1jsZYK6efq-EwfA,21229
19
19
  bluecellulab/analysis/inject_sequence.py,sha256=uU6Q6y0ErMDDEgdsTZBXCJ4LgwkATY7G8Fa6mmjpL98,12523
20
20
  bluecellulab/analysis/plotting.py,sha256=PqRoaZz33ULMw8A9YnZXXrxcUd84M_dwlYMTFhG7YT4,3999
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=BualY0WOtC0arKmvKzQ3sQ0fhc2jrtd6wqSd9M-e25E,33797
24
+ bluecellulab/cell/core.py,sha256=2dRx5ueElOJthhmnrijbBb9rH9xlJRp_hyZ73YHLGas,34478
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
@@ -36,7 +36,7 @@ bluecellulab/cell/ballstick/emodel.hoc,sha256=7WcuepK-wB9bASRvNdCwO9Woc9-SpBCFqB
36
36
  bluecellulab/cell/ballstick/morphology.asc,sha256=EO0VIRilJAwpiDP2hIevwusfvYptNYhvsu1f5GgbSQo,190
37
37
  bluecellulab/circuit/__init__.py,sha256=Khpa13nzNvDlDS2JduyoFTukEduEkWCc5ML_JwGpmZs,361
38
38
  bluecellulab/circuit/format.py,sha256=90gWOXg6HK0R9a4WFSnnRH8XezxmzOGk5dRpJHbvbbU,1674
39
- bluecellulab/circuit/iotools.py,sha256=h3nlPp1b30VVjkxg6hIfZibdXODxpFXXD1guR2fa0rg,1585
39
+ bluecellulab/circuit/iotools.py,sha256=Q65xYDaiensMtrulC3OLsS2_hcWr_Kje0nXFrAizMMo,1589
40
40
  bluecellulab/circuit/node_id.py,sha256=FdoFAGq0_sCyQySOuNI0chdbVr3L8R0w2Y1em5MyIDA,1265
41
41
  bluecellulab/circuit/simulation_access.py,sha256=keME58gzLVAPEg2nnWD_bwEm9V2Kjeqyfoj_55GPMCM,7061
42
42
  bluecellulab/circuit/synapse_properties.py,sha256=TvUMiXZAAeYo1zKkus3z1EUvrE9QCIQ3Ze-jSnPSJWY,6374
@@ -49,7 +49,7 @@ bluecellulab/circuit/config/__init__.py,sha256=aaoJXRKBJzpxxREo9NxKc-_CCPmVeuR1m
49
49
  bluecellulab/circuit/config/bluepy_simulation_config.py,sha256=V3eqOzskX7VrMDpl-nMQVEhDg8QWgRmRduyJBii5sgI,6974
50
50
  bluecellulab/circuit/config/definition.py,sha256=I1jd4KUX21mw03FEv1aYNsT0UFbDANY3YEPwwKXqe4k,2774
51
51
  bluecellulab/circuit/config/sections.py,sha256=QRnU44-OFvHxcF1LX4bAEP9dk3I6UKsuPNBbWkdfmRE,7151
52
- bluecellulab/circuit/config/sonata_simulation_config.py,sha256=7yEvuourzN2nRjzsA5CcDF8pcjEyjSJHitp3OH-ijgk,4829
52
+ bluecellulab/circuit/config/sonata_simulation_config.py,sha256=Rg1qEyGIh5s6kusE-9Z1r3LwZ4bxtF7aYr0-UeTOD6k,6536
53
53
  bluecellulab/hoc/Cell.hoc,sha256=z77qRQG_-afj-RLX0xN6V-K6Duq3bR7vmlDrGWPdh4E,16435
54
54
  bluecellulab/hoc/RNGSettings.hoc,sha256=okJBdqlPXET8BrpG1Q31GdaxHfpe3CE0L5P7MAhfQTE,1227
55
55
  bluecellulab/hoc/TDistFunc.hoc,sha256=WKX-anvL83xGuGPH9g1oIORB17UM4Pi3-iIXzKO-pUQ,2663
@@ -58,6 +58,7 @@ bluecellulab/hoc/fileUtils.hoc,sha256=LSM7BgyjYVqo2DGSOKvg4W8IIusbsL45JVYK0vgwit
58
58
  bluecellulab/simulation/__init__.py,sha256=P2ebt0SFw-08J3ihN-LeRn95HeF79tzA-Q0ReLm32dM,214
59
59
  bluecellulab/simulation/neuron_globals.py,sha256=iBjhg0-1YMP5LsVdtUDt24PEypkCL6mlyzEBZqoS8xo,4508
60
60
  bluecellulab/simulation/parallel.py,sha256=oQ_oV2EKr8gP4yF2Dq8q9MiblDyi89_wBgLzQkLV_U0,1514
61
+ bluecellulab/simulation/report.py,sha256=KA8Swdxy-52AIQFMUNHq4suIZhLxesOL32ZudeM-QBg,9298
61
62
  bluecellulab/simulation/simulation.py,sha256=VhftOMYU1Rfrphvud6f0U4kvbUivSviQ5TlVljuTb88,6486
62
63
  bluecellulab/stimulus/__init__.py,sha256=DgIgVaSyR-URf3JZzvO6j-tjCerzvktuK-ep8pjMRPQ,37
63
64
  bluecellulab/stimulus/circuit_stimulus_definitions.py,sha256=dTpwfRxH4c4yJDYyKrO6X-2Nqdy-QT3GxGQjfsRhNVY,17158
@@ -66,10 +67,10 @@ bluecellulab/stimulus/stimulus.py,sha256=a_hKJUtZmIgjiFjbJf6RzUPokELqn0IHCgIWGw5
66
67
  bluecellulab/synapse/__init__.py,sha256=RW8XoAMXOvK7OG1nHl_q8jSEKLj9ZN4oWf2nY9HAwuk,192
67
68
  bluecellulab/synapse/synapse_factory.py,sha256=NHwRMYMrnRVm_sHmyKTJ1bdoNmWZNU4UPOGu7FCi-PE,6987
68
69
  bluecellulab/synapse/synapse_types.py,sha256=zs_yBvGTH4QrbQF3nEViidyq1WM_ZcTSFdjUxB3khW0,16871
69
- bluecellulab/validation/validation.py,sha256=GxqyMCJ2wYXVJC-3TTqpQFF_uIlfs1lq9jeJmSCuopg,17038
70
- bluecellulab-2.6.50.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
71
- bluecellulab-2.6.50.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
72
- bluecellulab-2.6.50.dist-info/METADATA,sha256=-d8pVyjbpYrjC5xmEl1c1FGNJEk3lGI6ZCa8s9FmAyE,8259
73
- bluecellulab-2.6.50.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
74
- bluecellulab-2.6.50.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
75
- bluecellulab-2.6.50.dist-info/RECORD,,
70
+ bluecellulab/validation/validation.py,sha256=EN9HMty70VPwnEPhWUmN5F-MGWbMN3uU0DmNnjL0ucw,18209
71
+ bluecellulab-2.6.52.dist-info/licenses/AUTHORS.txt,sha256=EDs3H-2HXBojbma10psixk3C2rFiOCTIREi2ZAbXYNQ,179
72
+ bluecellulab-2.6.52.dist-info/licenses/LICENSE,sha256=dAMAR2Sud4Nead1wGFleKiwTZfkTNZbzmuGfcTKb3kg,11335
73
+ bluecellulab-2.6.52.dist-info/METADATA,sha256=dpyLb82oMOxH5zViSJr5Q9HbpmYDuGv8f_GgNJdOc8g,8259
74
+ bluecellulab-2.6.52.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
+ bluecellulab-2.6.52.dist-info/top_level.txt,sha256=VSyEP8w9l3pXdRkyP_goeMwiNA8KWwitfAqUkveJkdQ,13
76
+ bluecellulab-2.6.52.dist-info/RECORD,,