bsb-arbor 0.0.0b1__py2.py3-none-any.whl → 0.0.0b3__py2.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 bsb-arbor might be problematic. Click here for more details.
- bsb_arbor/__init__.py +12 -12
- bsb_arbor/adapter.py +443 -364
- bsb_arbor/cell.py +81 -84
- bsb_arbor/connection.py +67 -68
- bsb_arbor/device.py +45 -48
- bsb_arbor/devices/__init__.py +3 -3
- bsb_arbor/devices/poisson_generator.py +23 -23
- bsb_arbor/devices/probe.py +55 -55
- bsb_arbor/devices/spike_recorder.py +43 -43
- bsb_arbor/simulation.py +23 -25
- {bsb_arbor-0.0.0b1.dist-info → bsb_arbor-0.0.0b3.dist-info}/METADATA +3 -1
- bsb_arbor-0.0.0b3.dist-info/RECORD +15 -0
- bsb_arbor-0.0.0b1.dist-info/RECORD +0 -15
- {bsb_arbor-0.0.0b1.dist-info → bsb_arbor-0.0.0b3.dist-info}/LICENSE +0 -0
- {bsb_arbor-0.0.0b1.dist-info → bsb_arbor-0.0.0b3.dist-info}/WHEEL +0 -0
- {bsb_arbor-0.0.0b1.dist-info → bsb_arbor-0.0.0b3.dist-info}/entry_points.txt +0 -0
bsb_arbor/adapter.py
CHANGED
|
@@ -1,364 +1,443 @@
|
|
|
1
|
-
import itertools
|
|
2
|
-
import itertools as it
|
|
3
|
-
import time
|
|
4
|
-
import typing
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from bsb
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def
|
|
169
|
-
model = self.
|
|
170
|
-
return model
|
|
171
|
-
|
|
172
|
-
def
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1
|
+
import itertools
|
|
2
|
+
import itertools as it
|
|
3
|
+
import time
|
|
4
|
+
import typing
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
import arbor
|
|
8
|
+
from bsb import (
|
|
9
|
+
MPI,
|
|
10
|
+
AdapterError,
|
|
11
|
+
Chunk,
|
|
12
|
+
SimulationData,
|
|
13
|
+
SimulatorAdapter,
|
|
14
|
+
UnknownGIDError,
|
|
15
|
+
report,
|
|
16
|
+
warn,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if typing.TYPE_CHECKING:
|
|
20
|
+
from .simulation import ArborSimulation
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ArborSimulationData(SimulationData):
|
|
24
|
+
def __init__(self, simulation):
|
|
25
|
+
super().__init__(simulation)
|
|
26
|
+
self.arbor_sim: "arbor.simulation" = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ReceiverCollection(list):
|
|
30
|
+
"""
|
|
31
|
+
Receiver collections store the incoming connections and deduplicate them into multiple
|
|
32
|
+
targets.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._endpoint_counters = {}
|
|
38
|
+
|
|
39
|
+
def append(self, rcv):
|
|
40
|
+
endpoint = str(rcv.loc_on)
|
|
41
|
+
id = self._endpoint_counters.get(endpoint, 0)
|
|
42
|
+
self._endpoint_counters[endpoint] = id + 1
|
|
43
|
+
rcv.index = id
|
|
44
|
+
super().append(rcv)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SingleReceiverCollection(list):
|
|
48
|
+
"""
|
|
49
|
+
The single receiver collection redirects all incoming connections to the same receiver
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def append(self, rcv):
|
|
53
|
+
rcv.index = 0
|
|
54
|
+
super().append(rcv)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Population:
|
|
58
|
+
def __init__(self, simdata, cell_model, offset):
|
|
59
|
+
self._model = cell_model
|
|
60
|
+
self._simdata = simdata
|
|
61
|
+
ps = cell_model.get_placement_set(simdata.chunks)
|
|
62
|
+
self._ranges = self._get_ranges(simdata.chunks, ps, offset)
|
|
63
|
+
self._offset = offset
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def model(self):
|
|
67
|
+
return self._model
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def offset(self):
|
|
71
|
+
return self._offset
|
|
72
|
+
|
|
73
|
+
def __len__(self):
|
|
74
|
+
return sum(stop - start for start, stop in self._ranges)
|
|
75
|
+
|
|
76
|
+
def __contains__(self, i):
|
|
77
|
+
return any(start <= i < stop for start, stop in self._ranges)
|
|
78
|
+
|
|
79
|
+
def copy(self):
|
|
80
|
+
return Population(self._simdata, self._model, self._offset)
|
|
81
|
+
|
|
82
|
+
def __getitem__(self, item):
|
|
83
|
+
# Boolean masking, kind of
|
|
84
|
+
if getattr(item, "dtype", None) == bool or _all_bools(item):
|
|
85
|
+
if len(item) != len(self):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Dimension mismatch between population ({len(self)}) and mask ({len(item)})"
|
|
88
|
+
)
|
|
89
|
+
return self._subpop_np(np.array(self)[item])
|
|
90
|
+
elif getattr(item, "dtype", None) == int or _all_ints(item):
|
|
91
|
+
if getattr(item, "ndim", None) == 0:
|
|
92
|
+
return self._subpop_one(item)
|
|
93
|
+
return self._subpop_np(np.array(self)[item])
|
|
94
|
+
elif isinstance(item, slice):
|
|
95
|
+
return self._subpop_np(np.array(self)[item])
|
|
96
|
+
else:
|
|
97
|
+
return self._subpop_one(item)
|
|
98
|
+
|
|
99
|
+
def _get_ranges(self, chunks, ps, offset):
|
|
100
|
+
stats = ps.get_chunk_stats()
|
|
101
|
+
ranges = []
|
|
102
|
+
for chunk, len_ in sorted(
|
|
103
|
+
stats.items(), key=lambda k: Chunk.from_id(int(k[0]), None).id
|
|
104
|
+
):
|
|
105
|
+
if chunk in chunks:
|
|
106
|
+
ranges.append((offset, offset + len_))
|
|
107
|
+
offset += len_
|
|
108
|
+
return ranges
|
|
109
|
+
|
|
110
|
+
def _subpop_np(self, arr):
|
|
111
|
+
pop = self.copy()
|
|
112
|
+
if not len(pop):
|
|
113
|
+
return pop
|
|
114
|
+
ranges = []
|
|
115
|
+
prev = None
|
|
116
|
+
start, stop = self._ranges[0]
|
|
117
|
+
for i in arr:
|
|
118
|
+
if prev is None:
|
|
119
|
+
start += i
|
|
120
|
+
stop = start + 1
|
|
121
|
+
elif i == prev + 1:
|
|
122
|
+
stop += 1
|
|
123
|
+
else:
|
|
124
|
+
ranges.append((start, stop))
|
|
125
|
+
start = i
|
|
126
|
+
stop = i + 1
|
|
127
|
+
prev = i
|
|
128
|
+
pop._ranges = ranges
|
|
129
|
+
return pop
|
|
130
|
+
|
|
131
|
+
def _subpop_one(self, item):
|
|
132
|
+
if item >= len(self):
|
|
133
|
+
raise IndexError(f"Index {item} out of bounds for size {len(self)}")
|
|
134
|
+
pop = self.copy()
|
|
135
|
+
ptr = 0
|
|
136
|
+
for start, stop in self._ranges:
|
|
137
|
+
if item < (ptr + stop - start):
|
|
138
|
+
pop._ranges = [(start + ptr - item, start + ptr - item + 1)]
|
|
139
|
+
return pop
|
|
140
|
+
else:
|
|
141
|
+
ptr += stop - start
|
|
142
|
+
|
|
143
|
+
def __iter__(self):
|
|
144
|
+
yield from itertools.chain.from_iterable(
|
|
145
|
+
range(r[0], r[1]) for r in self._ranges
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class GIDManager:
|
|
150
|
+
def __init__(self, simulation, simdata):
|
|
151
|
+
self._gid_offsets = {}
|
|
152
|
+
self._model_order = self.sort_models(simulation.cell_models.values())
|
|
153
|
+
ctr = 0
|
|
154
|
+
for model in self._model_order:
|
|
155
|
+
self._gid_offsets[model] = ctr
|
|
156
|
+
ctr += len(model.get_placement_set())
|
|
157
|
+
self._populations = [
|
|
158
|
+
Population(simdata, model, offset)
|
|
159
|
+
for model, offset in self._gid_offsets.items()
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
def sort_models(self, models):
|
|
163
|
+
return sorted(
|
|
164
|
+
models,
|
|
165
|
+
key=lambda model: len(model.get_placement_set()),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def lookup_offset(self, gid):
|
|
169
|
+
model = self.lookup_model(gid)
|
|
170
|
+
return self._gid_offsets[model]
|
|
171
|
+
|
|
172
|
+
def lookup_kind(self, gid):
|
|
173
|
+
return self._lookup(gid).model.get_cell_kind(gid)
|
|
174
|
+
|
|
175
|
+
def lookup_model(self, gid):
|
|
176
|
+
return self._lookup(gid).model
|
|
177
|
+
|
|
178
|
+
def _lookup(self, gid):
|
|
179
|
+
try:
|
|
180
|
+
return next(c for c in self._populations if gid in c)
|
|
181
|
+
except StopIteration:
|
|
182
|
+
raise UnknownGIDError(f"Can't find gid {gid}.") from None
|
|
183
|
+
|
|
184
|
+
def all(self):
|
|
185
|
+
yield from itertools.chain.from_iterable(self._populations)
|
|
186
|
+
|
|
187
|
+
def get_populations(self):
|
|
188
|
+
return {pop.model: pop for pop in self._populations}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ArborRecipe(arbor.recipe):
|
|
192
|
+
def __init__(self, simulation, simdata):
|
|
193
|
+
super().__init__()
|
|
194
|
+
self._simulation = simulation
|
|
195
|
+
self._simdata = simdata
|
|
196
|
+
self._global_properties = arbor.neuron_cable_properties()
|
|
197
|
+
self._global_properties.set_property(Vm=-65, tempK=300, rL=35.4, cm=0.01)
|
|
198
|
+
self._global_properties.set_ion(ion="na", int_con=10, ext_con=140, rev_pot=50)
|
|
199
|
+
self._global_properties.set_ion(ion="k", int_con=54.4, ext_con=2.5, rev_pot=-77)
|
|
200
|
+
self._global_properties.set_ion(
|
|
201
|
+
ion="ca", int_con=0.0001, ext_con=2, rev_pot=132.5
|
|
202
|
+
)
|
|
203
|
+
self._global_properties.set_ion(
|
|
204
|
+
ion="h", valence=1, int_con=1.0, ext_con=1.0, rev_pot=-34
|
|
205
|
+
)
|
|
206
|
+
self._global_properties.catalogue = self._get_catalogue()
|
|
207
|
+
|
|
208
|
+
def _get_catalogue(self):
|
|
209
|
+
catalogue = arbor.default_catalogue()
|
|
210
|
+
prefixes = set()
|
|
211
|
+
for model in self._simulation.cell_models.values():
|
|
212
|
+
prefix, model_catalogue = model.get_prefixed_catalogue()
|
|
213
|
+
if model_catalogue is not None and prefix not in prefixes:
|
|
214
|
+
prefixes.add(prefix)
|
|
215
|
+
catalogue.extend(model_catalogue, "")
|
|
216
|
+
|
|
217
|
+
return catalogue
|
|
218
|
+
|
|
219
|
+
def global_properties(self, kind):
|
|
220
|
+
return self._global_properties
|
|
221
|
+
|
|
222
|
+
def num_cells(self):
|
|
223
|
+
return sum(
|
|
224
|
+
len(model.get_placement_set())
|
|
225
|
+
for model in self._simulation.cell_models.values()
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def cell_kind(self, gid):
|
|
229
|
+
return self._simdata.gid_manager.lookup_kind(gid)
|
|
230
|
+
|
|
231
|
+
def cell_description(self, gid):
|
|
232
|
+
model = self._simdata.gid_manager.lookup_model(gid)
|
|
233
|
+
return model.get_description(gid)
|
|
234
|
+
|
|
235
|
+
def connections_on(self, gid):
|
|
236
|
+
return [
|
|
237
|
+
arbor.connection(rcv.from_(), rcv.on(), rcv.weight, rcv.delay)
|
|
238
|
+
for rcv in self._simdata.connections_on[gid]
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
def gap_junctions_on(self, gid):
|
|
242
|
+
return [
|
|
243
|
+
c.model.gap_junction(c) for c in self._simdata.gap_junctions_on.get(gid, [])
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
def probes(self, gid):
|
|
247
|
+
devices = self._simdata.devices_on[gid]
|
|
248
|
+
_ntag = 0
|
|
249
|
+
probes = []
|
|
250
|
+
for device in devices:
|
|
251
|
+
device_probes = device.implement_probes(self._simdata, gid)
|
|
252
|
+
for tag in range(_ntag, _ntag + len(device_probes)):
|
|
253
|
+
device.register_probe_id(gid, tag)
|
|
254
|
+
probes.extend(device_probes)
|
|
255
|
+
return probes
|
|
256
|
+
|
|
257
|
+
def event_generators(self, gid):
|
|
258
|
+
devices = self._simdata.devices_on[gid]
|
|
259
|
+
generators = []
|
|
260
|
+
for device in devices:
|
|
261
|
+
device_generators = device.implement_generators(self._simdata, gid)
|
|
262
|
+
generators.extend(device_generators)
|
|
263
|
+
return generators
|
|
264
|
+
|
|
265
|
+
def _name_of(self, gid):
|
|
266
|
+
return self._simdata.gid_manager.lookup_model(gid).cell_type.name
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class ArborAdapter(SimulatorAdapter):
|
|
270
|
+
def __init__(self):
|
|
271
|
+
super().__init__()
|
|
272
|
+
self.simdata: typing.Dict["ArborSimulation", "SimulationData"] = {}
|
|
273
|
+
|
|
274
|
+
def get_rank(self):
|
|
275
|
+
return MPI.get_rank()
|
|
276
|
+
|
|
277
|
+
def get_size(self):
|
|
278
|
+
return MPI.get_size()
|
|
279
|
+
|
|
280
|
+
def broadcast(self, data, root=0):
|
|
281
|
+
return MPI.bcast(data, root)
|
|
282
|
+
|
|
283
|
+
def barrier(self):
|
|
284
|
+
return MPI.barrier()
|
|
285
|
+
|
|
286
|
+
def prepare(self, simulation: "ArborSimulation", comm=None):
|
|
287
|
+
simdata = self._create_simdata(simulation)
|
|
288
|
+
try:
|
|
289
|
+
context = arbor.context(arbor.proc_allocation(threads=simulation.threads))
|
|
290
|
+
if MPI.get_size() > 1:
|
|
291
|
+
if not arbor.config()["mpi4py"]:
|
|
292
|
+
warn(
|
|
293
|
+
f"Arbor does not seem to be built with MPI support, running"
|
|
294
|
+
"duplicate simulations on {MPI.get_size()} nodes."
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
context = arbor.context(
|
|
298
|
+
arbor.proc_allocation(threads=simulation.threads),
|
|
299
|
+
mpi=comm or MPI.get_communicator(),
|
|
300
|
+
)
|
|
301
|
+
if simulation.profiling:
|
|
302
|
+
if arbor.config()["profiling"]:
|
|
303
|
+
report("enabling profiler", level=2)
|
|
304
|
+
arbor.profiler_initialize(context)
|
|
305
|
+
else:
|
|
306
|
+
raise RuntimeError(
|
|
307
|
+
"Arbor must be built with profiling support to use the `profiling` flag."
|
|
308
|
+
)
|
|
309
|
+
simdata.gid_manager = self.get_gid_manager(simulation, simdata)
|
|
310
|
+
simdata.populations = simdata.gid_manager.get_populations()
|
|
311
|
+
report("preparing simulation", level=1)
|
|
312
|
+
report("MPI processes:", context.ranks, level=2)
|
|
313
|
+
report("Threads per process:", context.threads, level=2)
|
|
314
|
+
recipe = self.get_recipe(simulation, simdata)
|
|
315
|
+
# Gap junctions are required for domain decomposition
|
|
316
|
+
self.domain = arbor.partition_load_balance(recipe, context)
|
|
317
|
+
self.gids = set(it.chain.from_iterable(g.gids for g in self.domain.groups))
|
|
318
|
+
simdata.arbor_sim = arbor.simulation(recipe, context, self.domain)
|
|
319
|
+
self.prepare_samples(simulation, simdata)
|
|
320
|
+
report("prepared simulation", level=1)
|
|
321
|
+
return simdata
|
|
322
|
+
except Exception:
|
|
323
|
+
del self.simdata[simulation]
|
|
324
|
+
raise
|
|
325
|
+
|
|
326
|
+
def get_gid_manager(self, simulation, simdata):
|
|
327
|
+
return GIDManager(simulation, simdata)
|
|
328
|
+
|
|
329
|
+
def prepare_samples(self, simulation, simdata):
|
|
330
|
+
for device in simulation.devices.values():
|
|
331
|
+
device.prepare_samples(simdata)
|
|
332
|
+
|
|
333
|
+
def run(self, *simulations):
|
|
334
|
+
if len(simulations) != 1:
|
|
335
|
+
raise RuntimeError(
|
|
336
|
+
"Can not run multiple simultaneous simulations. Composition not implemented."
|
|
337
|
+
)
|
|
338
|
+
simulation = simulations[0]
|
|
339
|
+
try:
|
|
340
|
+
simdata = self.simdata[simulation]
|
|
341
|
+
arbor_sim = simdata.arbor_sim
|
|
342
|
+
except KeyError:
|
|
343
|
+
raise AdapterError(
|
|
344
|
+
f"Can't run unprepared simulation '{simulation.name}'"
|
|
345
|
+
) from None
|
|
346
|
+
try:
|
|
347
|
+
if not MPI.get_rank():
|
|
348
|
+
arbor_sim.record(arbor.spike_recording.all)
|
|
349
|
+
|
|
350
|
+
start = time.time()
|
|
351
|
+
report("running simulation", level=1)
|
|
352
|
+
arbor_sim.run(simulation.duration, dt=simulation.resolution)
|
|
353
|
+
report(f"completed simulation. {time.time() - start:.2f}s", level=1)
|
|
354
|
+
if simulation.profiling and arbor.config()["profiling"]:
|
|
355
|
+
report("printing profiler summary", level=2)
|
|
356
|
+
report(arbor.profiler_summary(), level=1)
|
|
357
|
+
return [simdata.result]
|
|
358
|
+
finally:
|
|
359
|
+
del self.simdata[simulation]
|
|
360
|
+
|
|
361
|
+
def get_recipe(self, simulation, simdata=None):
|
|
362
|
+
if simdata is None:
|
|
363
|
+
simdata = self._create_simdata(simulation)
|
|
364
|
+
self._cache_gap_junctions(simulation, simdata)
|
|
365
|
+
self._cache_connections(simulation, simdata)
|
|
366
|
+
self._cache_devices(simulation, simdata)
|
|
367
|
+
return ArborRecipe(simulation, simdata)
|
|
368
|
+
|
|
369
|
+
def _create_simdata(self, simulation):
|
|
370
|
+
self.simdata[simulation] = simdata = SimulationData(simulation)
|
|
371
|
+
self._assign_chunks(simulation, simdata)
|
|
372
|
+
return simdata
|
|
373
|
+
|
|
374
|
+
def _cache_gap_junctions(self, simulation, simdata):
|
|
375
|
+
simdata.gap_junctions_on = {}
|
|
376
|
+
for conn_model in simulation.connection_models.values():
|
|
377
|
+
if conn_model.gap:
|
|
378
|
+
conn_set = conn_model.get_connectivity_set()
|
|
379
|
+
conns = conn_set.load_connections().to(simdata.chunks).as_globals()
|
|
380
|
+
conn_model.create_gap_junctions_on(simdata.gap_junctions_on, conns)
|
|
381
|
+
|
|
382
|
+
def _cache_connections(self, simulation, simdata):
|
|
383
|
+
simdata.connections_on = {
|
|
384
|
+
gid: simdata.gid_manager.lookup_model(gid).make_receiver_collection()
|
|
385
|
+
for gid in simdata.gid_manager.all()
|
|
386
|
+
}
|
|
387
|
+
simdata.connections_from = {gid: [] for gid in simdata.gid_manager.all()}
|
|
388
|
+
for conn_model in simulation.connection_models.values():
|
|
389
|
+
if conn_model.gap:
|
|
390
|
+
continue
|
|
391
|
+
conn_set = conn_model.get_connectivity_set()
|
|
392
|
+
pop_pre, pop_post = None, None
|
|
393
|
+
for model in simulation.cell_models.values():
|
|
394
|
+
if model.cell_type is conn_set.pre_type:
|
|
395
|
+
pop_pre = simdata.populations[model]
|
|
396
|
+
if model.cell_type is conn_set.post_type:
|
|
397
|
+
pop_post = simdata.populations[model]
|
|
398
|
+
# Get the arriving connection iterator
|
|
399
|
+
conns_on = conn_set.load_connections().to(simdata.chunks).as_globals()
|
|
400
|
+
# Create the arriving connections
|
|
401
|
+
conn_model.create_connections_on(
|
|
402
|
+
simdata.connections_on, conns_on, pop_pre, pop_post
|
|
403
|
+
)
|
|
404
|
+
# Get the outgoing connection iterator
|
|
405
|
+
conns_from = conn_set.load_connections().from_(simdata.chunks).as_globals()
|
|
406
|
+
# Create the outgoing connections
|
|
407
|
+
conn_model.create_connections_from(
|
|
408
|
+
simdata.connections_from, conns_from, pop_pre, pop_post
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def _cache_devices(self, simulation, simdata):
|
|
412
|
+
simdata.devices_on = {gid: [] for gid in simdata.gid_manager.all()}
|
|
413
|
+
for device in simulation.devices.values():
|
|
414
|
+
targets = device.targetting.get_targets(self, simulation, simdata)
|
|
415
|
+
for target in itertools.chain.from_iterable(targets.values()):
|
|
416
|
+
simdata.devices_on[target].append(device)
|
|
417
|
+
|
|
418
|
+
def _assign_chunks(self, simulation, simdata):
|
|
419
|
+
chunk_stats = simulation.scaffold.storage.get_chunk_stats()
|
|
420
|
+
size = MPI.get_size()
|
|
421
|
+
all_chunks = [Chunk.from_id(int(chunk), None) for chunk in chunk_stats.keys()]
|
|
422
|
+
simdata.node_chunk_alloc = [all_chunks[rank::size] for rank in range(0, size)]
|
|
423
|
+
simdata.chunk_node_map = {}
|
|
424
|
+
for node, chunks in enumerate(simdata.node_chunk_alloc):
|
|
425
|
+
for chunk in chunks:
|
|
426
|
+
simdata.chunk_node_map[chunk] = node
|
|
427
|
+
simdata.chunks = simdata.node_chunk_alloc[MPI.get_rank()]
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _all_bools(arr):
|
|
431
|
+
try:
|
|
432
|
+
return all(isinstance(b, bool) for b in arr)
|
|
433
|
+
except TypeError:
|
|
434
|
+
# Not iterable
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _all_ints(arr):
|
|
439
|
+
try:
|
|
440
|
+
return all(isinstance(b, int) for b in arr)
|
|
441
|
+
except TypeError:
|
|
442
|
+
# Not iterable
|
|
443
|
+
return False
|