nrn-patch 4.0.0__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.
- nrn_patch-4.0.0.dist-info/LICENSE +21 -0
- nrn_patch-4.0.0.dist-info/METADATA +107 -0
- nrn_patch-4.0.0.dist-info/RECORD +13 -0
- nrn_patch-4.0.0.dist-info/WHEEL +5 -0
- nrn_patch-4.0.0.dist-info/entry_points.txt +3 -0
- patch/__init__.py +65 -0
- patch/core.py +136 -0
- patch/error_handler.py +188 -0
- patch/exceptions.py +38 -0
- patch/extensions/__init__.py +9 -0
- patch/extensions/mod/VecStim.mod +159 -0
- patch/interpreter.py +513 -0
- patch/objects.py +752 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Robin De Schepper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nrn-patch
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: Quality of life patch for the NEURON simulator.
|
|
5
|
+
Author-email: Robin De Schepper <robingilbert.deschepper@unipv.it>
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Dist: nmodl-glia[neuron]~=4.0
|
|
14
|
+
Requires-Dist: errr~=1.2
|
|
15
|
+
Requires-Dist: numpy~=1.21
|
|
16
|
+
Requires-Dist: NEURON>=8.0,<10.0
|
|
17
|
+
Requires-Dist: nrn-patch[test, docs] ; extra == "dev"
|
|
18
|
+
Requires-Dist: pre-commit~=3.0 ; extra == "dev"
|
|
19
|
+
Requires-Dist: black~=24.0 ; extra == "dev"
|
|
20
|
+
Requires-Dist: sphinx~=7.2 ; extra == "docs"
|
|
21
|
+
Requires-Dist: helveg--sphinx-code-tabs~=0.2 ; extra == "docs"
|
|
22
|
+
Requires-Dist: sphinx-rtd-theme~=2.0 ; extra == "docs"
|
|
23
|
+
Requires-Dist: mpi4py~=3.0 ; extra == "parallel"
|
|
24
|
+
Requires-Dist: coverage~=7.0 ; extra == "test"
|
|
25
|
+
Project-URL: Home, https://github.com/dbbs-lab/patch
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Provides-Extra: parallel
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
[](https://codecov.io/gh/Helveg/patch)
|
|
33
|
+
[](https://github.com/psf/black)
|
|
34
|
+
[](https://patch.readthedocs.io/en/latest/?badge=latest)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
_*No ducks were punched during the construction of this monkey patch._
|
|
40
|
+
|
|
41
|
+
# Installation
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install nrn-patch
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Minimal requirements
|
|
48
|
+
|
|
49
|
+
* Python 3.8+
|
|
50
|
+
* NEURON 8.0+
|
|
51
|
+
|
|
52
|
+
# Philosophy
|
|
53
|
+
|
|
54
|
+
Pythonic reinvention of the NEURON Python interface:
|
|
55
|
+
|
|
56
|
+
- **Drop-in replacement:** No breaking changes between Patch and NEURON. All
|
|
57
|
+
your code works as is. Patch automagically fixes bugs and reduces
|
|
58
|
+
complexity in NEURON by having an opinion, and adds new convenient methods.
|
|
59
|
+
- **Loud errors:** silent failures and gotchas are caught and raise
|
|
60
|
+
loud errors; so that when it runs, it runs.
|
|
61
|
+
- **Strong referencing:** Objects connected to each other will never disappear
|
|
62
|
+
until you disconnect them.
|
|
63
|
+
- **Demystified:** The simplest form of each instruction does the most obvious thing.
|
|
64
|
+
Patch frequently replaces 5 or more undocumented mystical voodoo steps by 1 clearly named function.
|
|
65
|
+
- **Just add water:** Serial or parallel, doesn't matter, `p.run()` will run your
|
|
66
|
+
simulation with no extra steps. No `load_file`, `finitialize`, `fadvance`, `run`,
|
|
67
|
+
`continuerun`, `tstop`, `set_maxstep`, `setup_transfer`, `setgid2node`, `gid_connect`,
|
|
68
|
+
`cell`, `outputcell`, `psolve` required.
|
|
69
|
+
|
|
70
|
+
# Basic usage
|
|
71
|
+
|
|
72
|
+
Use it like you would use NEURON. The wrapper doesn't make any changes to the interface,
|
|
73
|
+
it just patches up some of the more frequent and ridiculous gotchas.
|
|
74
|
+
|
|
75
|
+
Patch supplies a new interpreter `p`, the `PythonHocInterpreter` which wraps the
|
|
76
|
+
standard HOC interpreter `h` provided by NEURON. Any objects returned will either be
|
|
77
|
+
`PythonHocObject`'s wrapping their corresponding NEURON object, or whatever NEURON
|
|
78
|
+
returns.
|
|
79
|
+
|
|
80
|
+
When using just Patch the difference between NEURON and Patch objects is handled
|
|
81
|
+
transparently, but if you wish to mix interpreters you can transform all Patch objects
|
|
82
|
+
back to NEURON objects with `obj.__neuron__()`, or the `transform` function.
|
|
83
|
+
|
|
84
|
+
## Example
|
|
85
|
+
|
|
86
|
+
``` python
|
|
87
|
+
from patch import p
|
|
88
|
+
import glia as g
|
|
89
|
+
|
|
90
|
+
section = p.Section()
|
|
91
|
+
point_process = g.insert(section, "AMPA")
|
|
92
|
+
point_process.stimulate(start=10, number=5, interval=10, weight=0.04)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`.stimulate` creates both the `NetCon` and `NetStim`, and sets their properties, and
|
|
96
|
+
stores references to eachother so that Python doesn't garbage collect anything
|
|
97
|
+
until we're done with it.
|
|
98
|
+
|
|
99
|
+
Even when you forget to set values, the Patch defaults have a visible effect.
|
|
100
|
+
Here we forget to set the duration of a voltage clamp, but by default it will
|
|
101
|
+
be active the first 100ms of the simulation, so that you can see that the voltage
|
|
102
|
+
clamp is in fact inserted and working, but you forgot to set the duration:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
section.vclamp(holding=-70, voltage=20)
|
|
106
|
+
```
|
|
107
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
patch/__init__.py,sha256=RP-z1eF-iRlUZmTSQo3HgIU8jOQJ-Bq8KwTqB0yUy5c,1862
|
|
2
|
+
patch/core.py,sha256=Ik-oX8yknVsaah-ojQSThcEEIKUnSo9t5XvdYTBKDWY,3780
|
|
3
|
+
patch/error_handler.py,sha256=8YK7z4aDj417pF3TyIwf9fRX2ldWdoER04EwD9-ZS7U,6453
|
|
4
|
+
patch/exceptions.py,sha256=21y1rV6FPu7X8KfSPY691zh9nOXbpP-u-3QmbkVzfDs,1099
|
|
5
|
+
patch/interpreter.py,sha256=qprHpqiOqC7imqeIceBL3uzAWm70fGs33HWCNe7vr5k,20298
|
|
6
|
+
patch/objects.py,sha256=RNqWwY3pnBruWE3JQJnLOuT1Us1fWeoMWEIOh5INbZ8,24542
|
|
7
|
+
patch/extensions/__init__.py,sha256=_wRFsm-XntEtqgDUnuOoro-GfXeuALdPlwQhAT7II2Y,232
|
|
8
|
+
patch/extensions/mod/VecStim.mod,sha256=0NSBaol7vnQBTARDpyVK5orL_iPdypxWUIqloSYFUn8,2741
|
|
9
|
+
nrn_patch-4.0.0.dist-info/entry_points.txt,sha256=1EWv69vzvdN9wt4upC_zWSiB2TSa-RP4Kc9rXslzzo8,52
|
|
10
|
+
nrn_patch-4.0.0.dist-info/LICENSE,sha256=tJ_zaKC2tNV_HCEYSG7iwXiJjC_IYQpDiJYsgWkCMdI,1095
|
|
11
|
+
nrn_patch-4.0.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
|
12
|
+
nrn_patch-4.0.0.dist-info/METADATA,sha256=gUtgnxMATlLhAC3g2twj_2Gx9zwmRpAlAadKRhbnAPo,4339
|
|
13
|
+
nrn_patch-4.0.0.dist-info/RECORD,,
|
patch/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quality of life patch for the NEURON simulator.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from functools import cache, cached_property
|
|
7
|
+
except ImportError: # pragma: nocover
|
|
8
|
+
import functools
|
|
9
|
+
|
|
10
|
+
def cached_property(f):
|
|
11
|
+
return property(functools.lru_cache()(f))
|
|
12
|
+
|
|
13
|
+
functools.cache = cache = functools.lru_cache()
|
|
14
|
+
functools.cached_property = cached_property
|
|
15
|
+
|
|
16
|
+
from .core import (
|
|
17
|
+
is_density_mechanism,
|
|
18
|
+
is_nrn_scalar,
|
|
19
|
+
is_point_process,
|
|
20
|
+
is_section,
|
|
21
|
+
is_segment,
|
|
22
|
+
transform,
|
|
23
|
+
transform_arc,
|
|
24
|
+
transform_netcon,
|
|
25
|
+
transform_record,
|
|
26
|
+
)
|
|
27
|
+
from .exceptions import NotConnectableError, NotConnectedError
|
|
28
|
+
from .interpreter import PythonHocInterpreter
|
|
29
|
+
|
|
30
|
+
__version__ = "4.0.0"
|
|
31
|
+
p: "PythonHocInterpreter"
|
|
32
|
+
h: "PythonHocInterpreter"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def __getattr__(attr):
|
|
36
|
+
if attr == "p" or attr == "h":
|
|
37
|
+
return _get_interpreter()
|
|
38
|
+
else:
|
|
39
|
+
raise AttributeError(f"module {__name__} has no attribute {attr}.")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@cache
|
|
43
|
+
def _get_interpreter():
|
|
44
|
+
p = PythonHocInterpreter()
|
|
45
|
+
PythonHocInterpreter._process_registration_queue()
|
|
46
|
+
return p
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def connection(source, target, strict=True):
|
|
50
|
+
if not hasattr(source, "_connections"):
|
|
51
|
+
raise NotConnectableError(
|
|
52
|
+
f"Source {source} is not connectable. It lacks attribute _connections "
|
|
53
|
+
"required to form NetCons."
|
|
54
|
+
)
|
|
55
|
+
if not hasattr(target, "_connections"):
|
|
56
|
+
raise NotConnectableError(
|
|
57
|
+
f"Target {target} is not connectable. It lacks attribute _connections "
|
|
58
|
+
"required to form NetCons."
|
|
59
|
+
)
|
|
60
|
+
reverse = source in target._connections
|
|
61
|
+
if target not in source._connections:
|
|
62
|
+
if reverse and not strict:
|
|
63
|
+
return target._connections[source]
|
|
64
|
+
raise NotConnectedError("Source is not connected to target.")
|
|
65
|
+
return source._connections[target]
|
patch/core.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from .exceptions import NotConnectableError
|
|
5
|
+
|
|
6
|
+
if typing.TYPE_CHECKING:
|
|
7
|
+
from neuron.hoc import HocObject
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def transform(obj):
|
|
11
|
+
"""
|
|
12
|
+
Transform an object to its NEURON representation, if the ``__neuron__`` magic
|
|
13
|
+
method is present.
|
|
14
|
+
"""
|
|
15
|
+
if hasattr(obj, "__neuron__"):
|
|
16
|
+
return obj.__neuron__()
|
|
17
|
+
return obj
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def transform_netcon(obj):
|
|
21
|
+
"""
|
|
22
|
+
Transform an object into the pointer that should be connected, if the ``__netcon__``
|
|
23
|
+
magic method is present.
|
|
24
|
+
"""
|
|
25
|
+
if hasattr(obj, "__netcon__"):
|
|
26
|
+
return obj.__netcon__()
|
|
27
|
+
return transform(obj)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def transform_record(obj):
|
|
31
|
+
"""
|
|
32
|
+
Transform an object into the pointer that should be recorded, if the ``__record__``
|
|
33
|
+
magic method is present.
|
|
34
|
+
"""
|
|
35
|
+
if hasattr(obj, "__record__"):
|
|
36
|
+
return obj.__record__()
|
|
37
|
+
return transform(obj)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def transform_arc(obj, *args, **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
Get an arclength object on a NEURON object. Calls the ``__arc__`` magic method
|
|
43
|
+
on the callable object if present, otherwise returns the transformed object.
|
|
44
|
+
"""
|
|
45
|
+
if hasattr(obj, "__arc__"):
|
|
46
|
+
return transform(obj(obj.__arc__(*args, **kwargs)))
|
|
47
|
+
else:
|
|
48
|
+
return transform(obj)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _is_sequence(obj):
|
|
52
|
+
t = type(obj)
|
|
53
|
+
return hasattr(t, "__len__") and hasattr(t, "__getitem__")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def assert_connectable(obj, label=None):
|
|
57
|
+
"""
|
|
58
|
+
Assert whether an object could be used as a :class:`~.objects.Connectable`.
|
|
59
|
+
|
|
60
|
+
:param label: Optional label to display to describe the object if the assertion fails.
|
|
61
|
+
:type label: str
|
|
62
|
+
"""
|
|
63
|
+
if not hasattr(obj, "_connections"):
|
|
64
|
+
raise NotConnectableError(
|
|
65
|
+
(label if label is not None else str(obj))
|
|
66
|
+
+ " is not connectable."
|
|
67
|
+
+ "It lacks attribute `_connections` required to form NetCons."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_section(obj):
|
|
72
|
+
"""
|
|
73
|
+
Check if an object is a section.
|
|
74
|
+
"""
|
|
75
|
+
cls = type(transform(obj))
|
|
76
|
+
return cls.__module__ == "nrn" and cls.__name__ == "Section"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_segment(obj):
|
|
80
|
+
"""
|
|
81
|
+
Check if an object is a segment.
|
|
82
|
+
"""
|
|
83
|
+
cls = type(transform(obj))
|
|
84
|
+
return cls.__module__ == "nrn" and cls.__name__ == "Segment"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_nrn_scalar(obj):
|
|
88
|
+
cls = type(transform(obj))
|
|
89
|
+
if cls.__module__ != "hoc" or cls.__name__ != "HocObject":
|
|
90
|
+
return False
|
|
91
|
+
try:
|
|
92
|
+
return "pointer to hoc scalar" in str(obj)
|
|
93
|
+
except Exception:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_point_process(obj: Union[str, "HocObject"]):
|
|
98
|
+
"""
|
|
99
|
+
Check if obj is a (name of a) PointProcess on the ``HocInterpreter``.
|
|
100
|
+
|
|
101
|
+
:param obj: HocObject or name of the PointProcess to look for. Needs to be a known
|
|
102
|
+
attribute of ``neuron.h``.
|
|
103
|
+
:rtype: bool
|
|
104
|
+
"""
|
|
105
|
+
from neuron import h, hoc
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
if not isinstance(obj, hoc.HocObject):
|
|
109
|
+
obj = getattr(h, obj)
|
|
110
|
+
except Exception:
|
|
111
|
+
return False
|
|
112
|
+
return all(k in dir(obj) for k in ["get_loc", "has_loc", "loc", "get_segment"])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_density_mechanism(obj: Union[str, "HocObject"]):
|
|
116
|
+
"""
|
|
117
|
+
Check if obj is a (name of a) DensityMechanism on the ``HocInterpreter``.
|
|
118
|
+
|
|
119
|
+
:param obj: HocObject or name of the DensityMechanism to look for. Needs to be a known
|
|
120
|
+
attribute of ``neuron.h``.
|
|
121
|
+
:rtype: bool
|
|
122
|
+
"""
|
|
123
|
+
import nrn
|
|
124
|
+
from neuron import h, hoc
|
|
125
|
+
|
|
126
|
+
if isinstance(obj, nrn.Mechanism):
|
|
127
|
+
return True
|
|
128
|
+
try:
|
|
129
|
+
if not isinstance(obj, hoc.HocObject):
|
|
130
|
+
obj = getattr(h, obj)
|
|
131
|
+
hname = str(obj)
|
|
132
|
+
return "neuron.DensityMechanism" in hname
|
|
133
|
+
except TypeError as e:
|
|
134
|
+
return "mechanism" in str(e)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return False
|
patch/error_handler.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from patch.exceptions import (
|
|
7
|
+
ErrorHandlingError,
|
|
8
|
+
HocConnectError,
|
|
9
|
+
HocError,
|
|
10
|
+
HocRecordError,
|
|
11
|
+
HocSectionAccessError,
|
|
12
|
+
PatchError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@contextmanager
|
|
17
|
+
def _suppress_nrn(stream=None, close=False):
|
|
18
|
+
"""
|
|
19
|
+
Makes NEURON (mostly) shut the fuck up.
|
|
20
|
+
"""
|
|
21
|
+
if stream is None:
|
|
22
|
+
stream = open(os.devnull, "w")
|
|
23
|
+
close = True
|
|
24
|
+
old_stdout = sys.stdout
|
|
25
|
+
old_stderr = sys.stderr
|
|
26
|
+
sys.stdout = stream
|
|
27
|
+
sys.stderr = stream
|
|
28
|
+
try:
|
|
29
|
+
yield
|
|
30
|
+
finally:
|
|
31
|
+
if close:
|
|
32
|
+
stream.close()
|
|
33
|
+
sys.stdout = old_stdout
|
|
34
|
+
sys.stderr = old_stderr
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def catch_hoc_error(*args, **context):
|
|
39
|
+
with io.StringIO() as error_stream:
|
|
40
|
+
try:
|
|
41
|
+
with _suppress_nrn(error_stream):
|
|
42
|
+
yield
|
|
43
|
+
except RuntimeError as e:
|
|
44
|
+
error = error_stream.getvalue()
|
|
45
|
+
try:
|
|
46
|
+
for handler in args:
|
|
47
|
+
handler(error, context)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise e from None
|
|
50
|
+
# Uncaught HocError
|
|
51
|
+
if "hoc error" in str(e) or "hocobj_call error" in str(e):
|
|
52
|
+
raise HocError(error) from None
|
|
53
|
+
# Actual RuntimeError
|
|
54
|
+
raise e from None # pragma: nocover
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ErrorHandler:
|
|
58
|
+
"""
|
|
59
|
+
Base error handler class.
|
|
60
|
+
|
|
61
|
+
The class object is "callable" and takes the arguments that an error handle callable
|
|
62
|
+
should take. This means that when the class is passed in the argument list of
|
|
63
|
+
``catch_hoc_error`` that we'll construct an object;
|
|
64
|
+
|
|
65
|
+
The constructor checks whether the context contains enough information for us to
|
|
66
|
+
handle the error and calls its ``catch`` method that you need to override. Inside of
|
|
67
|
+
the catch method you can analyze the error message and context and raise a polished
|
|
68
|
+
version of the error. All raised errors must inherit from ``patch.object.PatchError``
|
|
69
|
+
or they'll be treated as a failure of the error handler and an ``ErrorHandlingError``
|
|
70
|
+
will be raised on top of it.
|
|
71
|
+
|
|
72
|
+
To specify the required items of the context create a class attribute list
|
|
73
|
+
``required``::
|
|
74
|
+
|
|
75
|
+
class A(ErrorHandler):
|
|
76
|
+
required = ["info_i_need_to_operate"]
|
|
77
|
+
|
|
78
|
+
Whenever the error handler class ``A`` is used, the ``catch_hoc_error`` call will
|
|
79
|
+
have the specify the keyword argument ``info_i_need_to_operate``.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, error, context):
|
|
83
|
+
if not hasattr(self.__class__, "required"):
|
|
84
|
+
raise ErrorHandlingError(
|
|
85
|
+
"Error context requirements missing for {}".format(
|
|
86
|
+
self.__class__.__name__
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
for required_item in self.__class__.required:
|
|
90
|
+
if required_item not in context:
|
|
91
|
+
raise ErrorHandlingError(
|
|
92
|
+
"Required error context item '{}' for {} is missing".format(
|
|
93
|
+
required_item, self.__class__.__name__
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
self.__dict__.update(context)
|
|
97
|
+
try:
|
|
98
|
+
self.catch(error, context)
|
|
99
|
+
except PatchError:
|
|
100
|
+
raise
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise ErrorHandlingError(
|
|
103
|
+
f"{self.__class__.__name__} errored out during error handling."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def catch(self, error, context):
|
|
107
|
+
raise ErrorHandlingError(
|
|
108
|
+
"Catch method not implemented for {}".format(self.__class__.__name__)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def detector(error):
|
|
113
|
+
"""
|
|
114
|
+
Pass this the error message and it returns a lambda function that you can pass a
|
|
115
|
+
string. Returns ``True`` if the string occurs in the error message, ``False``
|
|
116
|
+
otherwise.
|
|
117
|
+
"""
|
|
118
|
+
return lambda trigger: error.lower().find(trigger) != -1
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class CatchNetCon(ErrorHandler):
|
|
122
|
+
"""
|
|
123
|
+
Catches a variety of errors that can occur when using ``h.NetCon`` and raises
|
|
124
|
+
``HocConnectError``.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
required = ["nrn_source", "nrn_target"]
|
|
128
|
+
|
|
129
|
+
def catch(self, error, context):
|
|
130
|
+
e = detector(error)
|
|
131
|
+
if e("must be a point process or nullobject"):
|
|
132
|
+
if e("arg 1"):
|
|
133
|
+
raise HocConnectError(
|
|
134
|
+
"Source is not a point process. Transformed type: '{}'".format(
|
|
135
|
+
type(self.nrn_source)
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
if e("arg 2"):
|
|
139
|
+
raise HocConnectError(
|
|
140
|
+
"Target is not a point process. Transformed type: '{}'".format(
|
|
141
|
+
type(self.nrn_target)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
if e("interpreter stack type error"):
|
|
145
|
+
raise HocConnectError(
|
|
146
|
+
"Incorrect types passed to NetCon. Source: {}, target: {}".format(
|
|
147
|
+
type(self.nrn_source), type(self.nrn_target)
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Section access errors can't be triggered without NEURON exiting:
|
|
153
|
+
# https://github.com/neuronsimulator/nrn/issues/769
|
|
154
|
+
class CatchSectionAccess(ErrorHandler): # pragma: nocover
|
|
155
|
+
"""
|
|
156
|
+
Catches errors that occur when the Section stack is empty and accessed, raises
|
|
157
|
+
``HocSectionAccessError``.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
required = []
|
|
161
|
+
|
|
162
|
+
def catch(self, error, context):
|
|
163
|
+
e = detector(error)
|
|
164
|
+
if e("Section access unspecified"):
|
|
165
|
+
raise HocSectionAccessError(
|
|
166
|
+
"This operation requires a Section on the stack or perhaps a `sec` keyword argument."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class CatchRecord(ErrorHandler):
|
|
171
|
+
"""
|
|
172
|
+
Catches a variety of errors that occur when using ``h.Vector().record``, raises
|
|
173
|
+
``HocRecordError``.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
required = ["target"]
|
|
177
|
+
|
|
178
|
+
def catch(self, error, context):
|
|
179
|
+
e = detector(error)
|
|
180
|
+
if e("first arg is not a point_process") or e("interpreter stack type error"):
|
|
181
|
+
raise HocRecordError(
|
|
182
|
+
f"Can't record {self.target}, its record pointer is not a point process."
|
|
183
|
+
)
|
|
184
|
+
# Encountered this error locally, most likely on NEURON 7.7, don't cover
|
|
185
|
+
if e("number was provided instead of a pointer"): # pragma: nocover
|
|
186
|
+
raise HocRecordError(
|
|
187
|
+
f"Can't record {self.target}, its record pointer is a value. Make sure that you're recording `y._ref_x` rather than `y.x`."
|
|
188
|
+
)
|
patch/exceptions.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from errr.tree import exception as _e
|
|
4
|
+
from errr.tree import make_tree as _make_tree
|
|
5
|
+
|
|
6
|
+
_make_tree(
|
|
7
|
+
globals(),
|
|
8
|
+
PatchError=_e(
|
|
9
|
+
NotConnectableError=_e(),
|
|
10
|
+
NotConnectedError=_e(),
|
|
11
|
+
TransformError=_e(),
|
|
12
|
+
HocError=_e(
|
|
13
|
+
HocConnectError=_e(), HocRecordError=_e(), HocSectionAccessError=_e()
|
|
14
|
+
),
|
|
15
|
+
SimulationError=_e(),
|
|
16
|
+
UninitializedError=_e(),
|
|
17
|
+
ErrorHandlingError=_e(),
|
|
18
|
+
ParallelError=_e(
|
|
19
|
+
ParallelConnectError=_e(),
|
|
20
|
+
BroadcastError=_e(),
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
PatchError: Type[Exception]
|
|
26
|
+
NotConnectableError: Type[PatchError]
|
|
27
|
+
NotConnectedError: Type[PatchError]
|
|
28
|
+
TransformError: Type[PatchError]
|
|
29
|
+
HocError: Type[PatchError]
|
|
30
|
+
HocConnectError: Type[HocError]
|
|
31
|
+
HocRecordError: Type[HocError]
|
|
32
|
+
HocSectionAccessError: Type[HocError]
|
|
33
|
+
SimulationError: Type[PatchError]
|
|
34
|
+
UninitializedError: Type[PatchError]
|
|
35
|
+
ErrorHandlingError: Type[PatchError]
|
|
36
|
+
ParallelError: Type[PatchError]
|
|
37
|
+
ParallelConnectError: Type[ParallelError]
|
|
38
|
+
BroadcastError: Type[ParallelError]
|