bluecellulab 2.3.7__py3-none-any.whl → 2.4.1__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/tools.py CHANGED
@@ -11,17 +11,12 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Static tools for BluecellulabError."""
14
+ """Module for calculating certain properties of Neurons."""
15
15
 
16
16
 
17
17
  from __future__ import annotations
18
- import json
19
- import math
20
- import multiprocessing
21
- import multiprocessing.pool
22
- import os
23
18
  from pathlib import Path
24
- from typing import Any, Optional, Tuple
19
+ from typing import Optional, Tuple
25
20
  import logging
26
21
 
27
22
  import neuron
@@ -30,48 +25,10 @@ import numpy as np
30
25
  import bluecellulab
31
26
  from bluecellulab.circuit.circuit_access import EmodelProperties
32
27
  from bluecellulab.exceptions import UnsteadyCellError
33
- from bluecellulab.utils import CaptureOutput
28
+ from bluecellulab.utils import CaptureOutput, IsolatedProcess
34
29
 
35
30
  logger = logging.getLogger(__name__)
36
31
 
37
- VERBOSE_LEVEL = 0
38
- ENV_VERBOSE_LEVEL: Optional[str] = None
39
-
40
-
41
- def set_verbose(level: int = 1) -> None:
42
- """Set the verbose level of BluecellulabError.
43
-
44
- Parameters
45
- ----------
46
- level :
47
- Verbose level, the higher the more verbosity.
48
- Level 0 means 'completely quiet', except if some very serious
49
- errors or warnings are encountered.
50
- """
51
- bluecellulab.VERBOSE_LEVEL = level
52
-
53
- if level <= 0:
54
- logging.getLogger('bluecellulab').setLevel(logging.CRITICAL)
55
- elif level == 1:
56
- logging.getLogger('bluecellulab').setLevel(logging.ERROR)
57
- elif level == 2:
58
- logging.getLogger('bluecellulab').setLevel(logging.WARNING)
59
- elif level > 2 and level <= 5:
60
- logging.getLogger('bluecellulab').setLevel(logging.INFO)
61
- else:
62
- logging.getLogger('bluecellulab').setLevel(logging.DEBUG)
63
-
64
-
65
- def set_verbose_from_env() -> None:
66
- """Get verbose level from environment."""
67
- bluecellulab.ENV_VERBOSE_LEVEL = os.environ.get('BLUECELLULAB_VERBOSE_LEVEL')
68
-
69
- if bluecellulab.ENV_VERBOSE_LEVEL is not None:
70
- set_verbose(int(bluecellulab.ENV_VERBOSE_LEVEL))
71
-
72
-
73
- set_verbose_from_env()
74
-
75
32
 
76
33
  def calculate_input_resistance(
77
34
  template_path: str | Path,
@@ -104,22 +61,18 @@ def calculate_SS_voltage(
104
61
  emodel_properties: EmodelProperties | None,
105
62
  step_level: float,
106
63
  ) -> float:
107
- """Calculate the steady state voltage at a certain current step.
108
-
109
- The use of Pool is safe here since it will just run a single task.
110
- """
111
- pool = multiprocessing.Pool(processes=1)
112
- SS_voltage = pool.apply(
113
- calculate_SS_voltage_subprocess,
114
- [
115
- template_path,
116
- morphology_path,
117
- template_format,
118
- emodel_properties,
119
- step_level,
120
- ],
121
- )
122
- pool.terminate()
64
+ """Calculate the steady state voltage at a certain current step."""
65
+ with IsolatedProcess() as runner:
66
+ SS_voltage = runner.apply(
67
+ calculate_SS_voltage_subprocess,
68
+ [
69
+ template_path,
70
+ morphology_path,
71
+ template_format,
72
+ emodel_properties,
73
+ step_level,
74
+ ],
75
+ )
123
76
  return SS_voltage
124
77
 
125
78
 
@@ -197,13 +150,10 @@ def holding_current(
197
150
  ssim = bluecellulab.SSim(circuit_path)
198
151
 
199
152
  cell_kwargs = ssim.fetch_cell_kwargs(cell_id)
200
-
201
- # using a pool with NEURON here is safe since it'll run one task only
202
- pool = multiprocessing.Pool(processes=1)
203
- i_hold, v_control = pool.apply(
204
- holding_current_subprocess, [v_hold, enable_ttx, cell_kwargs]
205
- )
206
- pool.terminate()
153
+ with IsolatedProcess() as runner:
154
+ i_hold, v_control = runner.apply(
155
+ holding_current_subprocess, [v_hold, enable_ttx, cell_kwargs]
156
+ )
207
157
 
208
158
  return i_hold, v_control
209
159
 
@@ -296,22 +246,20 @@ def detect_spike_step(
296
246
  step_level: float,
297
247
  ) -> bool:
298
248
  """Detect if there is a spike at a certain step level."""
299
- # Here it is safe to use a pool with NEURON since it'll run one task only
300
- pool = multiprocessing.Pool(processes=1)
301
- spike_detected = pool.apply(
302
- detect_spike_step_subprocess,
303
- [
304
- template_path,
305
- morphology_path,
306
- template_format,
307
- emodel_properties,
308
- hyp_level,
309
- inj_start,
310
- inj_stop,
311
- step_level,
312
- ],
313
- )
314
- pool.terminate()
249
+ with IsolatedProcess() as runner:
250
+ spike_detected = runner.apply(
251
+ detect_spike_step_subprocess,
252
+ [
253
+ template_path,
254
+ morphology_path,
255
+ template_format,
256
+ emodel_properties,
257
+ hyp_level,
258
+ inj_start,
259
+ inj_stop,
260
+ step_level,
261
+ ],
262
+ )
315
263
  return spike_detected
316
264
 
317
265
 
@@ -355,321 +303,44 @@ def detect_spike(voltage: np.ndarray) -> bool:
355
303
  return bool(np.max(voltage) > -20) # bool not np.bool_
356
304
 
357
305
 
358
- def search_threshold_current(template_name, morphology_name, hyp_level,
359
- inj_start, inj_stop, min_current, max_current):
306
+ def search_threshold_current(
307
+ template_name: str | Path,
308
+ morphology_path: str | Path,
309
+ template_format: str,
310
+ emodel_properties: EmodelProperties | None,
311
+ hyp_level: float,
312
+ inj_start: float,
313
+ inj_stop: float,
314
+ min_current: float,
315
+ max_current: float,
316
+ ):
360
317
  """Search current necessary to reach threshold."""
361
318
  med_current = min_current + abs(min_current - max_current) / 2
362
319
  logger.info("Med current %d" % med_current)
363
320
 
364
321
  spike_detected = detect_spike_step(
365
- template_name, morphology_name, hyp_level, inj_start, inj_stop,
366
- med_current)
322
+ template_name, morphology_path, template_format, emodel_properties,
323
+ hyp_level, inj_start, inj_stop, med_current
324
+ )
367
325
  logger.info("Spike threshold detection at: %f nA" % med_current)
368
326
 
369
327
  if abs(max_current - min_current) < .01:
370
328
  return max_current
371
329
  elif spike_detected:
372
- return search_threshold_current(template_name, morphology_name,
330
+ return search_threshold_current(template_name, morphology_path,
331
+ template_format, emodel_properties,
373
332
  hyp_level, inj_start, inj_stop,
374
333
  min_current, med_current)
375
334
  else:
376
- return search_threshold_current(template_name, morphology_name,
335
+ return search_threshold_current(template_name, morphology_path,
336
+ template_format, emodel_properties,
377
337
  hyp_level, inj_start, inj_stop,
378
338
  med_current, max_current)
379
339
 
380
340
 
381
- def detect_threshold_current(template_name, morphology_name, hyp_level,
382
- inj_start, inj_stop):
383
- """Search current necessary to reach threshold."""
384
- return search_threshold_current(template_name, morphology_name,
385
- hyp_level, inj_start, inj_stop, 0.0, 1.0)
386
-
387
-
388
- def calculate_SS_voltage_replay(blueconfig, gid, step_level, start_time=None,
389
- stop_time=None, ignore_timerange=False,
390
- timeout=600):
391
- """Calculate the steady state voltage at a certain current step."""
392
- pool = multiprocessing.Pool(processes=1)
393
- # print "Calculate_SS_voltage_replay %f" % step_level
394
- result = pool.apply_async(calculate_SS_voltage_replay_subprocess,
395
- [blueconfig, gid, step_level, start_time,
396
- stop_time, ignore_timerange])
397
-
398
- try:
399
- output = result.get(timeout=timeout)
400
- # (SS_voltage, (time, voltage)) = result.get(timeout=timeout)
401
- except multiprocessing.TimeoutError:
402
- output = (float('nan'), (None, None))
403
-
404
- # (SS_voltage, voltage) = calculate_SS_voltage_replay_subprocess(
405
- # blueconfig, gid, step_level)
406
- pool.terminate()
407
- return output
408
-
409
-
410
- def calculate_SS_voltage_replay_subprocess(blueconfig, gid, step_level,
411
- start_time=None, stop_time=None,
412
- ignore_timerange=False):
413
- """Subprocess wrapper of calculate_SS_voltage."""
414
- process_name = multiprocessing.current_process().name
415
- ssim = bluecellulab.SSim(blueconfig)
416
- if ignore_timerange:
417
- tstart = 0
418
- tstop = int(ssim.circuit_access.config.duration)
419
- else:
420
- tstart = start_time
421
- tstop = stop_time
422
- # print "%s: Calculating SS voltage of step level %f nA" %
423
- # (process_name, step_level)
424
- # print "Calculate_SS_voltage_replay_subprocess instantiating gid ..."
425
- ssim.instantiate_gids(
426
- [gid], add_synapses=True, add_minis=True, add_stimuli=True, add_replay=True)
427
- # print "Calculate_SS_voltage_replay_subprocess instantiating gid done"
428
-
429
- ssim.cells[gid].add_ramp(0, tstop, step_level, step_level)
430
- ssim.run(t_stop=tstop)
431
- time = ssim.get_time_trace()
432
- voltage = ssim.get_voltage_trace(gid)
433
- SS_voltage = np.mean(voltage[np.where(
434
- (time < tstop) & (time > tstart))])
435
- logger.info("%s: Calculated SS voltage for gid %d "
436
- "with step level %f nA: %s mV" %
437
- (process_name, gid, step_level, SS_voltage))
438
- # print "Calculate_SS_voltage_replay_subprocess voltage:%f" % SS_voltage
439
-
440
- return (SS_voltage, (time, voltage))
441
-
442
-
443
- class NoDaemonProcess(multiprocessing.Process):
444
- """Class that represents a non-daemon process."""
445
-
446
- def _get_daemon(self):
447
- """Get daemon flag."""
448
- return False
449
-
450
- def _set_daemon(self, value):
451
- """Set daemon flag."""
452
- pass
453
- daemon = property(_get_daemon, _set_daemon) # type:ignore
454
-
455
-
456
- # We sub-class multiprocessing.pool.Pool instead of multiprocessing.Pool
457
- # because the latter is only a wrapper function, not a proper class.
458
-
459
-
460
- class NestedPool(multiprocessing.pool.Pool):
461
- """Class that represents a MultiProcessing nested pool."""
462
- Process = NoDaemonProcess
463
-
464
-
465
- def search_hyp_current_replay(blueconfig, gid, target_voltage=-80,
466
- min_current=-1.0, max_current=0.0,
467
- precision=.5,
468
- max_nestlevel=10,
469
- nestlevel=1,
470
- start_time=500, stop_time=2000,
471
- return_fullrange=True,
472
- timeout=600):
473
- """Search current to bring cell to target_voltage in a network replay."""
474
- process_name = multiprocessing.current_process().name
475
-
476
- if nestlevel > max_nestlevel:
477
- return (float('nan'), (None, None))
478
- elif nestlevel == 1:
479
- logger.info("%s: Searching for current to bring gid %d to %f mV" %
480
- (process_name, gid, target_voltage))
481
- med_current = min_current + abs(min_current - max_current) / 2
482
- (new_target_voltage, (time, voltage)) = \
483
- calculate_SS_voltage_replay(blueconfig, gid, med_current,
484
- start_time=start_time,
485
- stop_time=stop_time, timeout=timeout)
486
- if math.isnan(new_target_voltage):
487
- return (float('nan'), (None, None))
488
- if abs(new_target_voltage - target_voltage) < precision:
489
- if return_fullrange:
490
- # We're calculating the full voltage range,
491
- # just reusing calculate_SS_voltage_replay for this
492
- # Variable names that start with full_ point to values that are
493
- # related to the full voltage range
494
- (full_SS_voltage, (full_time, full_voltage)) = \
495
- calculate_SS_voltage_replay(
496
- blueconfig, gid, med_current,
497
- start_time=start_time, timeout=timeout,
498
- ignore_timerange=True)
499
- if math.isnan(full_SS_voltage):
500
- return (float('nan'), (None, None))
501
- return (med_current, (full_time, full_voltage))
502
- else:
503
- return (med_current, (time, voltage))
504
- elif new_target_voltage > target_voltage:
505
- return search_hyp_current_replay(blueconfig, gid, target_voltage,
506
- min_current=min_current,
507
- max_current=med_current,
508
- precision=precision,
509
- nestlevel=nestlevel + 1,
510
- start_time=start_time,
511
- stop_time=stop_time,
512
- max_nestlevel=max_nestlevel,
513
- return_fullrange=return_fullrange)
514
- elif new_target_voltage < target_voltage:
515
- return search_hyp_current_replay(blueconfig, gid, target_voltage,
516
- min_current=med_current,
517
- max_current=max_current,
518
- precision=precision,
519
- nestlevel=nestlevel + 1,
520
- start_time=start_time,
521
- stop_time=stop_time,
522
- max_nestlevel=max_nestlevel,
523
- return_fullrange=return_fullrange)
524
-
525
-
526
- class search_hyp_function:
527
- """Function object."""
528
-
529
- def __init__(self, blueconfig, **kwargs):
530
- self.blueconfig = blueconfig
531
- self.kwargs = kwargs
532
-
533
- def __call__(self, gid):
534
- return search_hyp_current_replay(self.blueconfig, gid, **self.kwargs)
535
-
536
-
537
- class search_hyp_function_gid:
538
- """Function object, return a tuple (gid, results)"""
539
-
540
- def __init__(self, blueconfig, **kwargs):
541
- self.blueconfig = blueconfig
542
- self.kwargs = kwargs
543
-
544
- def __call__(self, gid):
545
- return (
546
- gid,
547
- search_hyp_current_replay(
548
- self.blueconfig,
549
- gid,
550
- **self.kwargs))
551
-
552
-
553
- def search_hyp_current_replay_gidlist(blueconfig, gid_list, **kwargs):
554
- """Search, using bisection, for the current necessary to bring a cell to
555
- target_voltage in a network replay for a list of gids. This function will
556
- use multiprocessing to parallelize the task, running one gid per available
557
- core.
558
-
559
- Parameters
560
- ----------
561
- blueconfig : string
562
- Path to simulation BlueConfig
563
- gid_list : list of integers
564
- List of the gids
565
- target_voltage : float
566
- Voltage you want to bring to cell to
567
- min_current, max_current : float
568
- The algorithm will search in
569
- ]min_current, max_current[
570
- precision: float
571
- The algorithm stops when
572
- abs(calculated_voltage - target_voltage) < precision
573
- max_nestlevel : integer
574
- The maximum number of nested levels the algorithm explores
575
- start_time, stop_time : float
576
- The time range for which the voltage is simulated
577
- and average for comparison against target_voltage
578
- return_fullrange: boolean
579
- Defaults to True. Set to False if you don't want to
580
- return the voltage in full time range of the large
581
- simulation, but rather the time between
582
- start_time, stop_time
583
-
584
- Returns
585
- -------
586
- result: dictionary
587
- A dictionary where the keys are gids, and the values tuples of the
588
- form (detected_level, time_voltage).
589
- time_voltage is a tuple of the time and voltage trace at the
590
- current injection level (=detected_level) that matches the target
591
- target_voltage within user specified precision.
592
-
593
- If the algorithm reaches max_nestlevel+1 iterations without
594
- converging to the requested precision, (nan, None) is returned
595
- for that gid.
596
- """
597
-
598
- pool = NestedPool(multiprocessing.cpu_count())
599
- results = pool.map(search_hyp_function(blueconfig, **kwargs), gid_list)
600
- pool.terminate()
601
-
602
- currentlevels_timevoltagetraces = {}
603
- for gid, result in zip(gid_list, results):
604
- currentlevels_timevoltagetraces[gid] = result
605
-
606
- return currentlevels_timevoltagetraces
607
-
608
-
609
- def search_hyp_current_replay_imap(blueconfig, gid_list, timeout=600,
610
- cpu_count=None, **kwargs):
611
- """Same functionality as search_hyp_current_gidlist(), except that this
612
- function returns an unordered generator.
613
-
614
- Loop over this generator will return the unordered results one by
615
- one. The results returned will be of the form (gid, (current_step,
616
- (time, voltage))) When there are results that take more that
617
- 'timeout' time to retrieve, these results will be (None, None). The
618
- user should stop iterating the generating after receiving this
619
- (None, None) result. In this case also probably a broke pipe error
620
- from some of the parallel process will be shown on the stdout, these
621
- can be ignored.
622
- """
623
- if cpu_count is None:
624
- pool = NestedPool(multiprocessing.cpu_count())
625
- else:
626
- pool = NestedPool(cpu_count)
627
-
628
- results = pool.imap_unordered(search_hyp_function_gid(
629
- blueconfig, **kwargs), gid_list)
630
- for _ in gid_list:
631
- try:
632
- (gid, result) = results.next(timeout=timeout)
633
- yield (gid, result)
634
- except multiprocessing.TimeoutError:
635
- pool.terminate()
636
- yield (None, None)
637
- pool.terminate()
638
-
639
-
640
- class NumpyEncoder(json.JSONEncoder):
641
- def default(self, obj):
642
- if isinstance(obj, (np.int_, np.intc, np.intp, np.int8,
643
- np.int16, np.int32, np.int64, np.uint8,
644
- np.uint16, np.uint32, np.uint64)):
645
- return int(obj)
646
- elif isinstance(obj, (np.float_, np.float16, np.float32,
647
- np.float64)):
648
- return float(obj)
649
- elif isinstance(obj, np.ndarray):
650
- return obj.tolist()
651
- return json.JSONEncoder.default(self, obj)
652
-
653
-
654
341
  def check_empty_topology() -> bool:
655
342
  """Return true if NEURON simulator topology command is empty."""
656
343
  with CaptureOutput() as stdout:
657
344
  neuron.h.topology()
658
345
 
659
346
  return stdout == ['', '']
660
-
661
-
662
- class Singleton(type):
663
- """Singleton metaclass implementation.
664
-
665
- Source: https://stackoverflow.com/a/6798042/1935611
666
- """
667
- _instances: dict[Any, Any] = {}
668
-
669
- def __call__(cls, *args, **kwargs):
670
- if cls not in cls._instances:
671
- cls._instances[cls] = super(Singleton, cls).__call__(
672
- *args, **kwargs)
673
- else: # to run init on the same object
674
- cls._instances[cls].__init__(*args, **kwargs)
675
- return cls._instances[cls]
@@ -1,5 +1,6 @@
1
1
  """Type aliases used within the package."""
2
-
2
+ from __future__ import annotations
3
+ from typing import Dict
3
4
  from typing_extensions import TypeAlias
4
5
  from neuron import h as hoc_type
5
6
 
@@ -8,3 +9,5 @@ NeuronRNG: TypeAlias = hoc_type
8
9
  NeuronVector: TypeAlias = hoc_type
9
10
  NeuronSection: TypeAlias = hoc_type
10
11
  TStim: TypeAlias = hoc_type
12
+
13
+ SectionMapping = Dict[str, NeuronSection]
bluecellulab/utils.py CHANGED
@@ -1,6 +1,11 @@
1
- """Utility functions."""
1
+ """Utility functions used within BlueCellulab."""
2
+ from __future__ import annotations
2
3
  import contextlib
3
4
  import io
5
+ import json
6
+ from multiprocessing.pool import Pool
7
+
8
+ import numpy as np
4
9
 
5
10
 
6
11
  def run_once(func):
@@ -23,3 +28,33 @@ class CaptureOutput(list):
23
28
  def __exit__(self, exc_type, exc_val, exc_tb):
24
29
  self._redirect_stdout.__exit__(exc_type, exc_val, exc_tb)
25
30
  self.extend(self._stringio.getvalue().splitlines())
31
+
32
+
33
+ class NumpyEncoder(json.JSONEncoder):
34
+ def default(self, obj):
35
+ if isinstance(obj, (np.int_, np.intc, np.intp, np.int8,
36
+ np.int16, np.int32, np.int64, np.uint8,
37
+ np.uint16, np.uint32, np.uint64)):
38
+ return int(obj)
39
+ elif isinstance(obj, (np.float_, np.float16, np.float32,
40
+ np.float64)):
41
+ return float(obj)
42
+ elif isinstance(obj, np.ndarray):
43
+ return obj.tolist()
44
+ return json.JSONEncoder.default(self, obj)
45
+
46
+
47
+ class IsolatedProcess(Pool):
48
+ """Multiprocessing Pool that restricts a worker to run max 1 process.
49
+
50
+ Use this when running isolated NEURON simulations. Running 2 NEURON
51
+ simulations on a single process is to be avoided.
52
+ """
53
+ def __init__(self, processes: int | None = 1):
54
+ """Initialize the IsolatedProcess pool.
55
+
56
+ Args:
57
+ processes: The number of processes to use for running the stimuli.
58
+ If set to None, then the number returned by os.cpu_count() is used.
59
+ """
60
+ super().__init__(processes=processes, maxtasksperchild=1)
@@ -0,0 +1,49 @@
1
+ """Functions for configuring the verbosity of BlueCelluLab."""
2
+
3
+
4
+ import logging
5
+ import os
6
+ from typing import Optional
7
+
8
+ import bluecellulab
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ VERBOSE_LEVEL = 0
14
+ ENV_VERBOSE_LEVEL: Optional[str] = None
15
+
16
+
17
+ def set_verbose(level: int = 1) -> None:
18
+ """Set the verbose level of BluecellulabError.
19
+
20
+ Parameters
21
+ ----------
22
+ level :
23
+ Verbose level, the higher the more verbosity.
24
+ Level 0 means 'completely quiet', except if some very serious
25
+ errors or warnings are encountered.
26
+ """
27
+ bluecellulab.VERBOSE_LEVEL = level
28
+
29
+ if level <= 0:
30
+ logging.getLogger('bluecellulab').setLevel(logging.CRITICAL)
31
+ elif level == 1:
32
+ logging.getLogger('bluecellulab').setLevel(logging.ERROR)
33
+ elif level == 2:
34
+ logging.getLogger('bluecellulab').setLevel(logging.WARNING)
35
+ elif level > 2 and level <= 5:
36
+ logging.getLogger('bluecellulab').setLevel(logging.INFO)
37
+ else:
38
+ logging.getLogger('bluecellulab').setLevel(logging.DEBUG)
39
+
40
+
41
+ def set_verbose_from_env() -> None:
42
+ """Get verbose level from environment."""
43
+ bluecellulab.ENV_VERBOSE_LEVEL = os.environ.get('BLUECELLULAB_VERBOSE_LEVEL')
44
+
45
+ if bluecellulab.ENV_VERBOSE_LEVEL is not None:
46
+ set_verbose(int(bluecellulab.ENV_VERBOSE_LEVEL))
47
+
48
+
49
+ set_verbose_from_env()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bluecellulab
3
- Version: 2.3.7
3
+ Version: 2.4.1
4
4
  Summary: The Pythonic Blue Brain simulator access
5
5
  Home-page: https://github.com/BlueBrain/BlueCelluLab
6
6
  Author: Blue Brain Project, EPFL