bluecellulab 2.3.7__py3-none-any.whl → 2.5.0__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/__init__.py +9 -1
- bluecellulab/analysis/__init__.py +0 -0
- bluecellulab/analysis/inject_sequence.py +130 -0
- bluecellulab/cell/core.py +95 -93
- bluecellulab/cell/injector.py +20 -16
- bluecellulab/cell/recording.py +8 -0
- bluecellulab/cell/template.py +38 -62
- bluecellulab/hoc/RNGSettings.hoc +2 -6
- bluecellulab/importer.py +5 -7
- bluecellulab/rngsettings.py +40 -22
- bluecellulab/simulation/__init__.py +0 -1
- bluecellulab/simulation/neuron_globals.py +34 -3
- bluecellulab/simulation/simulation.py +3 -11
- bluecellulab/ssim.py +11 -15
- bluecellulab/synapse/synapse_factory.py +3 -3
- bluecellulab/synapse/synapse_types.py +26 -27
- bluecellulab/tools.py +57 -382
- bluecellulab/type_aliases.py +4 -1
- bluecellulab/utils.py +36 -1
- bluecellulab/verbosity.py +49 -0
- {bluecellulab-2.3.7.dist-info → bluecellulab-2.5.0.dist-info}/METADATA +1 -1
- {bluecellulab-2.3.7.dist-info → bluecellulab-2.5.0.dist-info}/RECORD +26 -22
- {bluecellulab-2.3.7.dist-info → bluecellulab-2.5.0.dist-info}/WHEEL +1 -1
- {bluecellulab-2.3.7.dist-info → bluecellulab-2.5.0.dist-info}/AUTHORS.txt +0 -0
- {bluecellulab-2.3.7.dist-info → bluecellulab-2.5.0.dist-info}/LICENSE +0 -0
- {bluecellulab-2.3.7.dist-info → bluecellulab-2.5.0.dist-info}/top_level.txt +0 -0
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
|
-
"""
|
|
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
|
|
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,
|
|
@@ -103,23 +60,23 @@ def calculate_SS_voltage(
|
|
|
103
60
|
template_format: str,
|
|
104
61
|
emodel_properties: EmodelProperties | None,
|
|
105
62
|
step_level: float,
|
|
63
|
+
check_for_spiking=False,
|
|
64
|
+
spike_threshold=-20.0,
|
|
106
65
|
) -> float:
|
|
107
|
-
"""Calculate the steady state voltage at a certain current step.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
pool.terminate()
|
|
66
|
+
"""Calculate the steady state voltage at a certain current step."""
|
|
67
|
+
with IsolatedProcess() as runner:
|
|
68
|
+
SS_voltage = runner.apply(
|
|
69
|
+
calculate_SS_voltage_subprocess,
|
|
70
|
+
[
|
|
71
|
+
template_path,
|
|
72
|
+
morphology_path,
|
|
73
|
+
template_format,
|
|
74
|
+
emodel_properties,
|
|
75
|
+
step_level,
|
|
76
|
+
check_for_spiking,
|
|
77
|
+
spike_threshold,
|
|
78
|
+
],
|
|
79
|
+
)
|
|
123
80
|
return SS_voltage
|
|
124
81
|
|
|
125
82
|
|
|
@@ -129,8 +86,8 @@ def calculate_SS_voltage_subprocess(
|
|
|
129
86
|
template_format: str,
|
|
130
87
|
emodel_properties: EmodelProperties | None,
|
|
131
88
|
step_level: float,
|
|
132
|
-
check_for_spiking
|
|
133
|
-
spike_threshold
|
|
89
|
+
check_for_spiking: bool,
|
|
90
|
+
spike_threshold: float,
|
|
134
91
|
) -> float:
|
|
135
92
|
"""Subprocess wrapper of calculate_SS_voltage.
|
|
136
93
|
|
|
@@ -197,13 +154,10 @@ def holding_current(
|
|
|
197
154
|
ssim = bluecellulab.SSim(circuit_path)
|
|
198
155
|
|
|
199
156
|
cell_kwargs = ssim.fetch_cell_kwargs(cell_id)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
holding_current_subprocess, [v_hold, enable_ttx, cell_kwargs]
|
|
205
|
-
)
|
|
206
|
-
pool.terminate()
|
|
157
|
+
with IsolatedProcess() as runner:
|
|
158
|
+
i_hold, v_control = runner.apply(
|
|
159
|
+
holding_current_subprocess, [v_hold, enable_ttx, cell_kwargs]
|
|
160
|
+
)
|
|
207
161
|
|
|
208
162
|
return i_hold, v_control
|
|
209
163
|
|
|
@@ -296,22 +250,20 @@ def detect_spike_step(
|
|
|
296
250
|
step_level: float,
|
|
297
251
|
) -> bool:
|
|
298
252
|
"""Detect if there is a spike at a certain step level."""
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
314
|
-
pool.terminate()
|
|
253
|
+
with IsolatedProcess() as runner:
|
|
254
|
+
spike_detected = runner.apply(
|
|
255
|
+
detect_spike_step_subprocess,
|
|
256
|
+
[
|
|
257
|
+
template_path,
|
|
258
|
+
morphology_path,
|
|
259
|
+
template_format,
|
|
260
|
+
emodel_properties,
|
|
261
|
+
hyp_level,
|
|
262
|
+
inj_start,
|
|
263
|
+
inj_stop,
|
|
264
|
+
step_level,
|
|
265
|
+
],
|
|
266
|
+
)
|
|
315
267
|
return spike_detected
|
|
316
268
|
|
|
317
269
|
|
|
@@ -355,321 +307,44 @@ def detect_spike(voltage: np.ndarray) -> bool:
|
|
|
355
307
|
return bool(np.max(voltage) > -20) # bool not np.bool_
|
|
356
308
|
|
|
357
309
|
|
|
358
|
-
def search_threshold_current(
|
|
359
|
-
|
|
310
|
+
def search_threshold_current(
|
|
311
|
+
template_name: str | Path,
|
|
312
|
+
morphology_path: str | Path,
|
|
313
|
+
template_format: str,
|
|
314
|
+
emodel_properties: EmodelProperties | None,
|
|
315
|
+
hyp_level: float,
|
|
316
|
+
inj_start: float,
|
|
317
|
+
inj_stop: float,
|
|
318
|
+
min_current: float,
|
|
319
|
+
max_current: float,
|
|
320
|
+
):
|
|
360
321
|
"""Search current necessary to reach threshold."""
|
|
361
322
|
med_current = min_current + abs(min_current - max_current) / 2
|
|
362
323
|
logger.info("Med current %d" % med_current)
|
|
363
324
|
|
|
364
325
|
spike_detected = detect_spike_step(
|
|
365
|
-
template_name,
|
|
366
|
-
med_current
|
|
326
|
+
template_name, morphology_path, template_format, emodel_properties,
|
|
327
|
+
hyp_level, inj_start, inj_stop, med_current
|
|
328
|
+
)
|
|
367
329
|
logger.info("Spike threshold detection at: %f nA" % med_current)
|
|
368
330
|
|
|
369
331
|
if abs(max_current - min_current) < .01:
|
|
370
332
|
return max_current
|
|
371
333
|
elif spike_detected:
|
|
372
|
-
return search_threshold_current(template_name,
|
|
334
|
+
return search_threshold_current(template_name, morphology_path,
|
|
335
|
+
template_format, emodel_properties,
|
|
373
336
|
hyp_level, inj_start, inj_stop,
|
|
374
337
|
min_current, med_current)
|
|
375
338
|
else:
|
|
376
|
-
return search_threshold_current(template_name,
|
|
339
|
+
return search_threshold_current(template_name, morphology_path,
|
|
340
|
+
template_format, emodel_properties,
|
|
377
341
|
hyp_level, inj_start, inj_stop,
|
|
378
342
|
med_current, max_current)
|
|
379
343
|
|
|
380
344
|
|
|
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
345
|
def check_empty_topology() -> bool:
|
|
655
346
|
"""Return true if NEURON simulator topology command is empty."""
|
|
656
347
|
with CaptureOutput() as stdout:
|
|
657
348
|
neuron.h.topology()
|
|
658
349
|
|
|
659
350
|
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]
|
bluecellulab/type_aliases.py
CHANGED
|
@@ -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()
|