arborize 7.2.2__tar.gz → 7.3.0__tar.gz
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.
- {arborize-7.2.2 → arborize-7.3.0}/PKG-INFO +1 -1
- {arborize-7.2.2 → arborize-7.3.0}/arborize/builders/_neuron.py +173 -5
- {arborize-7.2.2 → arborize-7.3.0}/arborize/exceptions.py +4 -0
- {arborize-7.2.2 → arborize-7.3.0}/pyproject.toml +1 -1
- {arborize-7.2.2 → arborize-7.3.0}/LICENSE +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/README.md +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/__init__.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/_util.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/builders/__init__.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/builders/_arbor.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/builders/_bluepyopt.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/constraints.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/definitions.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/parameter.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/schematic.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/schematics/__init__.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/schematics/_bsb.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/schematics/_file.py +0 -0
- {arborize-7.2.2 → arborize-7.3.0}/arborize/synapse.py +0 -0
|
@@ -4,6 +4,7 @@ import typing
|
|
|
4
4
|
import warnings
|
|
5
5
|
from collections import deque
|
|
6
6
|
from collections.abc import Mapping, Sequence
|
|
7
|
+
from functools import cache
|
|
7
8
|
|
|
8
9
|
import errr
|
|
9
10
|
|
|
@@ -11,6 +12,7 @@ from .._util import get_arclengths, get_location_name
|
|
|
11
12
|
from ..constraints import Constraint
|
|
12
13
|
from ..definitions import CableProperties, Ion, Mechanism, MechId, mechdict
|
|
13
14
|
from ..exceptions import (
|
|
15
|
+
ProxyWarning,
|
|
14
16
|
TransmitterError,
|
|
15
17
|
UnconnectedPointInSpaceWarning,
|
|
16
18
|
UnknownLocationError,
|
|
@@ -213,7 +215,8 @@ def neuron_build(schematic: "Schematic"):
|
|
|
213
215
|
# Connect the proxy to its parent
|
|
214
216
|
_connect_section_or_proxy(section, branch_map.get(branch.parent))
|
|
215
217
|
|
|
216
|
-
_collapse_proxies(proxies)
|
|
218
|
+
proxy_locations = _collapse_proxies(proxies, locations)
|
|
219
|
+
locations.update(proxy_locations)
|
|
217
220
|
|
|
218
221
|
return NeuronModel(sections, locations, [*schematic.get_cable_types().keys()])
|
|
219
222
|
|
|
@@ -256,7 +259,9 @@ def _connect_section_or_proxy(section, parent):
|
|
|
256
259
|
section.connect(parent)
|
|
257
260
|
|
|
258
261
|
|
|
259
|
-
def _collapse_proxies(
|
|
262
|
+
def _collapse_proxies(
|
|
263
|
+
proxies: list[_SinglePointProxy], real_locations: dict["Location", "LocationAccessor"]
|
|
264
|
+
):
|
|
260
265
|
"""
|
|
261
266
|
Removes proxies from the schematic and connects the real sections to each
|
|
262
267
|
other.
|
|
@@ -273,11 +278,14 @@ def _collapse_proxies(proxies: list[_SinglePointProxy]):
|
|
|
273
278
|
iteration and can be skipped.
|
|
274
279
|
"""
|
|
275
280
|
|
|
281
|
+
locations = {}
|
|
282
|
+
|
|
276
283
|
for proxy in proxies:
|
|
277
284
|
if proxy.reified:
|
|
278
285
|
# This proxy has already been reified/collapsed in a previous
|
|
279
286
|
# iteration as a downstream of another proxy.
|
|
280
287
|
continue
|
|
288
|
+
|
|
281
289
|
if not proxy.parent:
|
|
282
290
|
# Root proxies
|
|
283
291
|
if len(proxy.children) == 0:
|
|
@@ -305,13 +313,15 @@ def _collapse_proxies(proxies: list[_SinglePointProxy]):
|
|
|
305
313
|
# eventually point to.
|
|
306
314
|
true_children = _reify_proxy_children(proxy)
|
|
307
315
|
|
|
308
|
-
|
|
316
|
+
# The proxied electric location is the start of the first child
|
|
317
|
+
electric_proxy = (true_children[0], (0, 0))
|
|
318
|
+
|
|
309
319
|
# We loop only over *additional* children, so that this
|
|
310
320
|
# algorithm does nothing in the case of a single child
|
|
311
321
|
# connected to a single point root proxy, where the root
|
|
312
322
|
# proxy point can simply be discarded.
|
|
313
323
|
for child in true_children[1:]:
|
|
314
|
-
child.connect(
|
|
324
|
+
child.connect(electric_proxy[0], 0)
|
|
315
325
|
else:
|
|
316
326
|
# Proxies with a parent, connect the proxy children to the parent.
|
|
317
327
|
|
|
@@ -323,11 +333,76 @@ def _collapse_proxies(proxies: list[_SinglePointProxy]):
|
|
|
323
333
|
" Please report this bug."
|
|
324
334
|
)
|
|
325
335
|
|
|
336
|
+
# The proxied electric location is the end of the proxy parent.
|
|
337
|
+
electric_proxy = (proxy.parent, (1, 1))
|
|
338
|
+
|
|
326
339
|
# The children might be proxies themselves, so we reify them.
|
|
327
340
|
true_children = _reify_proxy_children(proxy)
|
|
328
341
|
|
|
329
342
|
for child in true_children:
|
|
330
|
-
_connect_section_or_proxy(child,
|
|
343
|
+
_connect_section_or_proxy(child, electric_proxy[0])
|
|
344
|
+
|
|
345
|
+
# The only branches of the control flow above that can reach this
|
|
346
|
+
# part of the code have exactly 1 point. Check defensively
|
|
347
|
+
if len(proxy.branch.points) != 1:
|
|
348
|
+
raise RuntimeError("Proxied location should have exactly one point.")
|
|
349
|
+
|
|
350
|
+
# Even though we've already figured out where to connect everything
|
|
351
|
+
# electrically, we now need to hand back a location accessor to the user
|
|
352
|
+
# that behaves as unsurprising as possible. We use this heuristic to try
|
|
353
|
+
# to find a good candidate that will hopefully serve users well.
|
|
354
|
+
proxied_loc, proxied_section, proxied_arcs = _guess_best_proxy_accessor(
|
|
355
|
+
proxy, true_children
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
loc = proxy.branch.points[0].loc
|
|
359
|
+
locations[loc] = ProxiedLocationAccessor(
|
|
360
|
+
loc,
|
|
361
|
+
proxy.branch.labels,
|
|
362
|
+
real_locations[proxied_loc],
|
|
363
|
+
proxied_section,
|
|
364
|
+
proxied_arcs,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return locations
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _guess_best_proxy_accessor(
|
|
371
|
+
proxy: _SinglePointProxy, true_children: list["Section"]
|
|
372
|
+
) -> tuple["Location", "Section", tuple[float, float]]:
|
|
373
|
+
"""
|
|
374
|
+
Simple heuristic to try to make proxied locations behave like normal. There
|
|
375
|
+
are 2 rules in order of importance:
|
|
376
|
+
|
|
377
|
+
* If a section has the same labels as the proxy means it has the same
|
|
378
|
+
cable properties and mechanisms and works for the most part the same.
|
|
379
|
+
* The tip of the parent is better than a child, because the tip of a branch
|
|
380
|
+
is already considered a 0 surface area point in the rest of Arborize.
|
|
381
|
+
|
|
382
|
+
This means in order of preference we have: a parent with the same labels, a
|
|
383
|
+
child with the same labels, a parent, or a child.
|
|
384
|
+
"""
|
|
385
|
+
# Labels in Arborize are sorted according to their insertion order in the
|
|
386
|
+
# definitions, with the last appearing key considered "most specific" and
|
|
387
|
+
# having the final say. Since both Arborize and alphabetic ordering are
|
|
388
|
+
# deterministic and list equality in Python is order-sensitive, we sort
|
|
389
|
+
# the labels alphabetically here to be sure we don't miss any equalities.
|
|
390
|
+
proxy_labels = sorted(proxy.branch.labels)
|
|
391
|
+
if proxy.parent and sorted(proxy.parent.labels) == proxy_labels:
|
|
392
|
+
# Best case: Return the same labeled parent
|
|
393
|
+
return proxy.parent.locations[-1], proxy.parent, (1, 1)
|
|
394
|
+
else:
|
|
395
|
+
for child in true_children:
|
|
396
|
+
if sorted(child.labels) == proxy_labels:
|
|
397
|
+
# Not so best case: Return the same labeled child
|
|
398
|
+
return child.locations[0], child, (0, 0)
|
|
399
|
+
else:
|
|
400
|
+
if proxy.parent:
|
|
401
|
+
# Worse case: Return mislabeled parent
|
|
402
|
+
return proxy.parent.locations[-1], proxy.parent, (1, 1)
|
|
403
|
+
else:
|
|
404
|
+
# Worst case: Return mislabeled first child
|
|
405
|
+
return true_children[0].locations[0], true_children[0], (0, 0)
|
|
331
406
|
|
|
332
407
|
|
|
333
408
|
def _reify_proxy_children(proxy):
|
|
@@ -409,6 +484,10 @@ class LocationAccessor:
|
|
|
409
484
|
"Parameters cannot be changed yet after cell construction."
|
|
410
485
|
)
|
|
411
486
|
|
|
487
|
+
@property
|
|
488
|
+
def location(self):
|
|
489
|
+
return self._loc
|
|
490
|
+
|
|
412
491
|
@property
|
|
413
492
|
def section(self):
|
|
414
493
|
return self._section
|
|
@@ -420,3 +499,92 @@ class LocationAccessor:
|
|
|
420
499
|
def arc(self, x=0):
|
|
421
500
|
a0, a1 = self._arcs
|
|
422
501
|
return (a1 - a0) * x + a0
|
|
502
|
+
|
|
503
|
+
def describe(self):
|
|
504
|
+
return f"{self._loc}[{','.join(self.section.labels)}]"
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@cache
|
|
508
|
+
def _mechanisms(proxy_accessor):
|
|
509
|
+
# Cache function outside the Accessor class to avoid
|
|
510
|
+
# memory leaks, see B019
|
|
511
|
+
warnings.warn(
|
|
512
|
+
f"Location {proxy_accessor.describe()} was constructed from less "
|
|
513
|
+
"than 2 points and has 0 surface area, which NEURON does not "
|
|
514
|
+
f"support. Proxied location {proxy_accessor._proxied_loc.describe()} "
|
|
515
|
+
"mechanisms were used instead. Double-check you do not create "
|
|
516
|
+
"conflicting instructions.",
|
|
517
|
+
category=ProxyWarning,
|
|
518
|
+
stacklevel=1,
|
|
519
|
+
)
|
|
520
|
+
return proxymechdict(
|
|
521
|
+
proxy_accessor, proxy_accessor._proxied_loc, proxy_accessor._mechs
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class ProxiedLocationAccessor(LocationAccessor):
|
|
526
|
+
def __init__(self, loc, labels, proxied_loc, section, arcs):
|
|
527
|
+
super().__init__(loc, section, proxied_loc.mechanisms, arcs)
|
|
528
|
+
self._labels = labels
|
|
529
|
+
self._proxied_loc = proxied_loc
|
|
530
|
+
|
|
531
|
+
@property
|
|
532
|
+
def proxied_loc(self):
|
|
533
|
+
return self._proxied_loc
|
|
534
|
+
|
|
535
|
+
@property
|
|
536
|
+
def section(self):
|
|
537
|
+
warnings.warn(
|
|
538
|
+
f"Location {self.describe()} was constructed from less "
|
|
539
|
+
"than 2 points and has 0 surface area, which NEURON does not "
|
|
540
|
+
f"support. Proxied location {self._proxied_loc.describe()} section "
|
|
541
|
+
"was used instead. Double-check you do not create conflicting "
|
|
542
|
+
"instructions.",
|
|
543
|
+
category=ProxyWarning,
|
|
544
|
+
stacklevel=1,
|
|
545
|
+
)
|
|
546
|
+
return self._section
|
|
547
|
+
|
|
548
|
+
@property
|
|
549
|
+
def mechanisms(self) -> Mapping["MechId", "MechAccessor"]:
|
|
550
|
+
return _mechanisms(self)
|
|
551
|
+
|
|
552
|
+
def describe(self):
|
|
553
|
+
return f"{self._loc}[{','.join(self._labels)}]"
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class proxymechdict(mechdict):
|
|
557
|
+
def __init__(self, loc, proxied_loc, mechs):
|
|
558
|
+
self._loc = loc
|
|
559
|
+
self._proxied_loc = proxied_loc
|
|
560
|
+
super().__init__(mechs)
|
|
561
|
+
|
|
562
|
+
def __getitem__(self, item):
|
|
563
|
+
try:
|
|
564
|
+
return super().__getitem__(item)
|
|
565
|
+
except KeyError:
|
|
566
|
+
raise ProxyKeyError(
|
|
567
|
+
item,
|
|
568
|
+
(
|
|
569
|
+
f"Location {self._loc.describe()} proxies "
|
|
570
|
+
f"{self._proxied_loc.describe()}. Mechanism '{item}' not found "
|
|
571
|
+
"on proxied location."
|
|
572
|
+
),
|
|
573
|
+
) from None
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
class ProxyKeyError(KeyError):
|
|
577
|
+
"""
|
|
578
|
+
KeyError with extra steps.
|
|
579
|
+
|
|
580
|
+
Key errors might be inspected to find out which key errored, but they
|
|
581
|
+
don't show much info, this does both.
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
def __init__(self, key, message):
|
|
585
|
+
self.key = key
|
|
586
|
+
self.message = message
|
|
587
|
+
super().__init__(key) # pass just the key to parent
|
|
588
|
+
|
|
589
|
+
def __str__(self):
|
|
590
|
+
return f"{self.key!r} - {self.message}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|