dendrotweaks 0.3.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.
- dendrotweaks/__init__.py +10 -0
- dendrotweaks/analysis/__init__.py +11 -0
- dendrotweaks/analysis/ephys_analysis.py +482 -0
- dendrotweaks/analysis/morphometric_analysis.py +106 -0
- dendrotweaks/membrane/__init__.py +6 -0
- dendrotweaks/membrane/default_mod/AMPA.mod +65 -0
- dendrotweaks/membrane/default_mod/AMPA_NMDA.mod +100 -0
- dendrotweaks/membrane/default_mod/CaDyn.mod +54 -0
- dendrotweaks/membrane/default_mod/GABAa.mod +65 -0
- dendrotweaks/membrane/default_mod/Leak.mod +27 -0
- dendrotweaks/membrane/default_mod/NMDA.mod +72 -0
- dendrotweaks/membrane/default_mod/vecstim.mod +76 -0
- dendrotweaks/membrane/default_templates/NEURON_template.py +354 -0
- dendrotweaks/membrane/default_templates/default.py +73 -0
- dendrotweaks/membrane/default_templates/standard_channel.mod +87 -0
- dendrotweaks/membrane/default_templates/template_jaxley.py +108 -0
- dendrotweaks/membrane/default_templates/template_jaxley_new.py +108 -0
- dendrotweaks/membrane/distributions.py +324 -0
- dendrotweaks/membrane/groups.py +103 -0
- dendrotweaks/membrane/io/__init__.py +11 -0
- dendrotweaks/membrane/io/ast.py +201 -0
- dendrotweaks/membrane/io/code_generators.py +312 -0
- dendrotweaks/membrane/io/converter.py +108 -0
- dendrotweaks/membrane/io/factories.py +144 -0
- dendrotweaks/membrane/io/grammar.py +417 -0
- dendrotweaks/membrane/io/loader.py +90 -0
- dendrotweaks/membrane/io/parser.py +499 -0
- dendrotweaks/membrane/io/reader.py +212 -0
- dendrotweaks/membrane/mechanisms.py +574 -0
- dendrotweaks/model.py +1916 -0
- dendrotweaks/model_io.py +75 -0
- dendrotweaks/morphology/__init__.py +5 -0
- dendrotweaks/morphology/domains.py +100 -0
- dendrotweaks/morphology/io/__init__.py +5 -0
- dendrotweaks/morphology/io/factories.py +212 -0
- dendrotweaks/morphology/io/reader.py +66 -0
- dendrotweaks/morphology/io/validation.py +212 -0
- dendrotweaks/morphology/point_trees.py +681 -0
- dendrotweaks/morphology/reduce/__init__.py +16 -0
- dendrotweaks/morphology/reduce/reduce.py +155 -0
- dendrotweaks/morphology/reduce/reduced_cylinder.py +129 -0
- dendrotweaks/morphology/sec_trees.py +1112 -0
- dendrotweaks/morphology/seg_trees.py +157 -0
- dendrotweaks/morphology/trees.py +567 -0
- dendrotweaks/path_manager.py +261 -0
- dendrotweaks/simulators.py +235 -0
- dendrotweaks/stimuli/__init__.py +3 -0
- dendrotweaks/stimuli/iclamps.py +73 -0
- dendrotweaks/stimuli/populations.py +265 -0
- dendrotweaks/stimuli/synapses.py +203 -0
- dendrotweaks/utils.py +239 -0
- dendrotweaks-0.3.1.dist-info/METADATA +70 -0
- dendrotweaks-0.3.1.dist-info/RECORD +56 -0
- dendrotweaks-0.3.1.dist-info/WHEEL +5 -0
- dendrotweaks-0.3.1.dist-info/licenses/LICENSE +674 -0
- dendrotweaks-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
from typing import List, Tuple
|
4
|
+
|
5
|
+
|
6
|
+
from dendrotweaks.membrane.io.converter import MODFileConverter
|
7
|
+
from dendrotweaks.membrane.io.code_generators import NMODLCodeGenerator
|
8
|
+
from dendrotweaks.membrane.mechanisms import Mechanism, IonChannel, StandardIonChannel
|
9
|
+
|
10
|
+
|
11
|
+
def create_channel(path_to_mod_file: str,
|
12
|
+
path_to_python_file: str,
|
13
|
+
path_to_python_template: str,
|
14
|
+
verbose: bool = False) -> IonChannel:
|
15
|
+
"""
|
16
|
+
Creates an ion channel from a .mod file.
|
17
|
+
|
18
|
+
Parameters
|
19
|
+
----------
|
20
|
+
path_to_mod_file : str
|
21
|
+
The full path to the .mod file containing the channel mechanism.
|
22
|
+
path_to_python_file : str
|
23
|
+
The path to the output Python file to be generated.
|
24
|
+
path_to_python_template : str
|
25
|
+
The path to the jinja2 template file for the Python file.
|
26
|
+
verbose : bool, optional
|
27
|
+
Whether to print verbose output.
|
28
|
+
|
29
|
+
Returns
|
30
|
+
-------
|
31
|
+
IonChannel
|
32
|
+
The instantiated ion channel.
|
33
|
+
"""
|
34
|
+
# Convert mod to python
|
35
|
+
converter = MODFileConverter()
|
36
|
+
converter.convert(path_to_mod_file,
|
37
|
+
path_to_python_file,
|
38
|
+
path_to_python_template,
|
39
|
+
verbose=verbose)
|
40
|
+
|
41
|
+
# Import and instantiate the channel
|
42
|
+
class_name = os.path.basename(path_to_python_file).replace('.py', '')
|
43
|
+
module_name = class_name
|
44
|
+
package_path = os.path.dirname(path_to_python_file)
|
45
|
+
|
46
|
+
if package_path not in sys.path:
|
47
|
+
sys.path.append(package_path)
|
48
|
+
|
49
|
+
# Dynamic import
|
50
|
+
from importlib import import_module
|
51
|
+
module = import_module(module_name)
|
52
|
+
ChannelClass = getattr(module, class_name)
|
53
|
+
|
54
|
+
return ChannelClass()
|
55
|
+
|
56
|
+
|
57
|
+
def standardize_channel(channel: IonChannel,
|
58
|
+
path_to_mod_template: str = None,
|
59
|
+
path_to_standard_mod_file: str = None) -> StandardIonChannel:
|
60
|
+
"""
|
61
|
+
Standardize a channel and optionally generate a MOD file.
|
62
|
+
|
63
|
+
Parameters
|
64
|
+
----------
|
65
|
+
channel : IonChannel
|
66
|
+
The channel to standardize.
|
67
|
+
path_to_mod_template : str, optional
|
68
|
+
The path to the jinja2 template file for the standard MOD file.
|
69
|
+
path_to_standard_mod_file : str, optional
|
70
|
+
The path to save the standardized MOD file.
|
71
|
+
|
72
|
+
Returns
|
73
|
+
-------
|
74
|
+
StandardIonChannel
|
75
|
+
A standardized version of the input channel.
|
76
|
+
|
77
|
+
Note
|
78
|
+
----
|
79
|
+
Temperature-dependence is taken into account by performing
|
80
|
+
a fit to the data at the temperature specified in the parameters
|
81
|
+
of the original channel model (the `temp` parameter). If no
|
82
|
+
temperature is specified, the default temperature of 23 degrees
|
83
|
+
Celsius is used.
|
84
|
+
"""
|
85
|
+
standard_channel = StandardIonChannel(name=f"std{channel.name}",
|
86
|
+
state_powers=channel._state_powers,
|
87
|
+
ion=channel.ion)
|
88
|
+
|
89
|
+
standard_channel.params.update({'q10': channel.params.get('q10'),
|
90
|
+
'temp': channel.params.get('temp')})
|
91
|
+
|
92
|
+
fit_temperature = channel.params.get('temp') or 23
|
93
|
+
|
94
|
+
standard_channel.set_tadj(fit_temperature)
|
95
|
+
# Fit the standard channel to the data
|
96
|
+
data = channel.get_data(temperature=fit_temperature)
|
97
|
+
standard_channel.fit(data)
|
98
|
+
|
99
|
+
# Optionally generate a MOD file
|
100
|
+
|
101
|
+
generator = NMODLCodeGenerator()
|
102
|
+
content = generator.generate(standard_channel, path_to_mod_template)
|
103
|
+
generator.write_file(path_to_standard_mod_file)
|
104
|
+
|
105
|
+
return standard_channel
|
106
|
+
|
107
|
+
|
108
|
+
def create_standard_channel(path_to_mod_file: str,
|
109
|
+
path_to_python_file: str,
|
110
|
+
path_to_python_template: str,
|
111
|
+
path_to_mod_template: str,
|
112
|
+
path_to_standard_mod_file: str,
|
113
|
+
verbose: bool = False) -> StandardIonChannel:
|
114
|
+
"""
|
115
|
+
Creates a standardized channel and fits it to the data of the unstandardized channel.
|
116
|
+
|
117
|
+
Parameters
|
118
|
+
----------
|
119
|
+
path_to_mod_file : str
|
120
|
+
The path to the original MOD file for an unstandardized channel.
|
121
|
+
path_to_python_file : str
|
122
|
+
The path to the output Python file to be generated.
|
123
|
+
path_to_python_template : str
|
124
|
+
The path to the jinja2 template file for the Python file.
|
125
|
+
path_to_mod_template : str
|
126
|
+
The path to the jinja2 template file for the standard MOD file.
|
127
|
+
path_to_standard_mod_file : str
|
128
|
+
The path to the output standardized MOD file.
|
129
|
+
verbose : bool, optional
|
130
|
+
Whether to print verbose output.
|
131
|
+
|
132
|
+
Returns
|
133
|
+
-------
|
134
|
+
StandardIonChannel
|
135
|
+
The standardized ion channel.
|
136
|
+
"""
|
137
|
+
# First create the regular channel
|
138
|
+
channel = create_channel(path_to_mod_file,
|
139
|
+
path_to_python_file,
|
140
|
+
path_to_python_template,
|
141
|
+
verbose=verbose)
|
142
|
+
|
143
|
+
# Then standardize it
|
144
|
+
return standardize_channel(channel, path_to_mod_template, path_to_standard_mod_file)
|
@@ -0,0 +1,417 @@
|
|
1
|
+
import pyparsing as pp
|
2
|
+
|
3
|
+
pp.ParserElement.enablePackrat()
|
4
|
+
|
5
|
+
from pyparsing import (alphas, alphanums, nums)
|
6
|
+
from pyparsing import (Char, Word, Empty, Literal, Regex, Keyword)
|
7
|
+
from pyparsing import (infix_notation, opAssoc)
|
8
|
+
|
9
|
+
from pyparsing import (Group, Combine, Dict, Suppress, delimitedList, Optional)
|
10
|
+
from pyparsing import (ZeroOrMore, OneOrMore, oneOf)
|
11
|
+
from pyparsing import Forward
|
12
|
+
from pyparsing import (restOfLine, SkipTo)
|
13
|
+
from pyparsing import pyparsing_common
|
14
|
+
from pyparsing import LineEnd
|
15
|
+
from pyparsing import infixNotation, opAssoc, And
|
16
|
+
from pyparsing import NotAny
|
17
|
+
|
18
|
+
# Symbols
|
19
|
+
LBRACE, RBRACE, LPAREN, RPAREN, EQUAL, COLON = map(Suppress, "{}()=:")
|
20
|
+
|
21
|
+
# Keywords
|
22
|
+
|
23
|
+
## Block keywords
|
24
|
+
TITLE = Suppress(Keyword('TITLE', caseless=False))
|
25
|
+
COMMENT = Suppress(Keyword('COMMENT', caseless=False))
|
26
|
+
ENDCOMMENT = Suppress(Keyword('ENDCOMMENT', caseless=False))
|
27
|
+
NEURON = Suppress(Keyword('NEURON', caseless=False))
|
28
|
+
UNITS = Suppress(Keyword('UNITS', caseless=False))
|
29
|
+
PARAMETER = Suppress(Keyword('PARAMETER', caseless=False))
|
30
|
+
ASSIGNED = Suppress(Keyword('ASSIGNED', caseless=False))
|
31
|
+
STATE = Suppress(Keyword('STATE', caseless=False))
|
32
|
+
BREAKPOINT = Suppress(Keyword('BREAKPOINT', caseless=False))
|
33
|
+
INITIAL = Suppress(Keyword('INITIAL', caseless=False))
|
34
|
+
DERIVATIVE = Suppress(Keyword('DERIVATIVE', caseless=False))
|
35
|
+
FUNCTION = Suppress(Keyword('FUNCTION', caseless=False))
|
36
|
+
PROCEDURE = Suppress(Keyword('PROCEDURE', caseless=False))
|
37
|
+
INDEPENDENT = Suppress(Keyword('INDEPENDENT', caseless=False))
|
38
|
+
FROM = Suppress(Keyword('FROM', caseless=False))
|
39
|
+
TO = Suppress(Keyword('TO', caseless=False))
|
40
|
+
KINETIC = Suppress(Keyword('KINETIC', caseless=False))
|
41
|
+
STEADYSTATE = Suppress(Keyword('STEADYSTATE', caseless=False))
|
42
|
+
|
43
|
+
block_to_keep = TITLE | COMMENT | NEURON | UNITS | PARAMETER | ASSIGNED | STATE | BREAKPOINT | INITIAL | DERIVATIVE | FUNCTION | PROCEDURE
|
44
|
+
|
45
|
+
## Misc keywords
|
46
|
+
VERBATIM = Suppress(Keyword('VERBATIM', caseless=False))
|
47
|
+
ENDVERBATIM = Suppress(Keyword('ENDVERBATIM', caseless=False))
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
# TITLE
|
52
|
+
|
53
|
+
title = Combine(TITLE + restOfLine('block'))
|
54
|
+
|
55
|
+
comment = Combine(COLON + restOfLine)("comment")
|
56
|
+
|
57
|
+
|
58
|
+
comment_block = Combine(COMMENT + SkipTo(ENDCOMMENT) + ENDCOMMENT)('block')
|
59
|
+
|
60
|
+
# VERBATIM
|
61
|
+
verbatim = VERBATIM + SkipTo(ENDVERBATIM) + ENDVERBATIM
|
62
|
+
|
63
|
+
## Block keywords
|
64
|
+
FARADAY = Keyword('FARADAY', caseless=False)
|
65
|
+
R = Keyword('R', caseless=False)
|
66
|
+
|
67
|
+
number = pyparsing_common.number
|
68
|
+
identifier = Word(alphas, alphanums + "_")
|
69
|
+
# unit = Combine(LPAREN + Word(alphas + nums + "/") + RPAREN)
|
70
|
+
unit = Combine(LPAREN
|
71
|
+
+ ( Combine(Word(alphas + "23") + "/" + Word(alphas + "23"))
|
72
|
+
| Combine("/" + Word(alphas + "23") + "/" + Word(alphas + "23"))
|
73
|
+
| Combine("/" + Word(alphas + "23"))
|
74
|
+
| Combine(Word(nums) + "/" + Word(alphas + "23"))
|
75
|
+
| Word(alphas + "23"))
|
76
|
+
+ RPAREN)
|
77
|
+
dimensionless = LPAREN + Literal("1") + RPAREN
|
78
|
+
|
79
|
+
faraday_constant = Dict(Group(FARADAY + EQUAL + LPAREN + Suppress(Literal('faraday')) + RPAREN + Optional(unit)))
|
80
|
+
gas_constant = Dict(Group(R + EQUAL + LPAREN + Suppress(Literal('k-mole')) + RPAREN + Optional(unit)))
|
81
|
+
|
82
|
+
constant = faraday_constant | gas_constant
|
83
|
+
|
84
|
+
quantity = And([number + Suppress(unit)])
|
85
|
+
|
86
|
+
value_range = Suppress(Literal('<')) + Suppress(number) + Suppress(Literal(',')) + Suppress(number) + Suppress(Literal('>'))
|
87
|
+
|
88
|
+
from_to = FROM + number("from") + TO + number("to")
|
89
|
+
|
90
|
+
# NEURON block
|
91
|
+
|
92
|
+
## Block keywords
|
93
|
+
SUFFIX = Suppress(Keyword('SUFFIX', caseless=False))
|
94
|
+
NONSPECIFIC_CURRENT = Suppress(Keyword('NONSPECIFIC_CURRENT', caseless=False))
|
95
|
+
USEION = Suppress(Keyword('USEION', caseless=False))
|
96
|
+
READ = Suppress(Keyword('READ', caseless=False))
|
97
|
+
WRITE = Suppress(Keyword('WRITE', caseless=False))
|
98
|
+
VALENCE = Suppress(Keyword('VALENCE', caseless=False))
|
99
|
+
RANGE = Suppress(Keyword('RANGE', caseless=False))
|
100
|
+
GLOBAL = Suppress(Keyword('GLOBAL', caseless=False))
|
101
|
+
|
102
|
+
## Block statements
|
103
|
+
suffix_stmt = SUFFIX + identifier("suffix")
|
104
|
+
nonspecific_current_stmt = NONSPECIFIC_CURRENT + identifier("nonspecific_current")
|
105
|
+
useion_stmt = (Group(
|
106
|
+
USEION
|
107
|
+
+ identifier('ion')
|
108
|
+
+ Group(READ + delimitedList(identifier))("read")
|
109
|
+
+ Optional(Group(WRITE + delimitedList(identifier))("write"))
|
110
|
+
+ Optional(VALENCE + number("valence"))
|
111
|
+
))("useion*")
|
112
|
+
range_stmt = Group(RANGE + delimitedList(identifier))("range")
|
113
|
+
global_stmt = Group(GLOBAL + delimitedList(identifier))("global")
|
114
|
+
|
115
|
+
neuron_stmt = suffix_stmt | nonspecific_current_stmt | useion_stmt | range_stmt | global_stmt
|
116
|
+
|
117
|
+
## Block definition
|
118
|
+
neuron_block = Group(
|
119
|
+
NEURON
|
120
|
+
+ LBRACE
|
121
|
+
+ OneOrMore(neuron_stmt)
|
122
|
+
+ RBRACE
|
123
|
+
)("block")
|
124
|
+
|
125
|
+
|
126
|
+
# UNITS block
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
## Block statements
|
131
|
+
unit_definition = Dict(Group(unit + EQUAL + unit)) | constant
|
132
|
+
|
133
|
+
## Block definition
|
134
|
+
units_block = Group(
|
135
|
+
UNITS
|
136
|
+
+ LBRACE
|
137
|
+
+ OneOrMore(unit_definition)
|
138
|
+
+ RBRACE
|
139
|
+
)("block")
|
140
|
+
|
141
|
+
# units_blocks = ZeroOrMore(units_block)("units_block")
|
142
|
+
|
143
|
+
# PARAMETER block
|
144
|
+
|
145
|
+
## Block statements
|
146
|
+
parameter_stmt = Group(
|
147
|
+
identifier('name')
|
148
|
+
+ EQUAL
|
149
|
+
+ number('value')
|
150
|
+
+ Optional(unit | dimensionless)('unit')
|
151
|
+
+ Optional(value_range('value_range'))
|
152
|
+
)
|
153
|
+
|
154
|
+
## Block definition
|
155
|
+
parameter_block = Group(
|
156
|
+
PARAMETER
|
157
|
+
+ LBRACE
|
158
|
+
+ OneOrMore(parameter_stmt)
|
159
|
+
+ RBRACE
|
160
|
+
)("block")
|
161
|
+
|
162
|
+
parameter_block = parameter_block.ignore(comment)
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
# ASSIGNED block
|
167
|
+
|
168
|
+
|
169
|
+
## Block statements
|
170
|
+
assigned_stmt = Group(
|
171
|
+
identifier('name')
|
172
|
+
+ Optional(unit | dimensionless)('unit')
|
173
|
+
)
|
174
|
+
|
175
|
+
## Block definition
|
176
|
+
assigned_block = Group(
|
177
|
+
ASSIGNED
|
178
|
+
+ LBRACE
|
179
|
+
+ OneOrMore(assigned_stmt)
|
180
|
+
+ RBRACE
|
181
|
+
)("block")
|
182
|
+
|
183
|
+
assigned_block = assigned_block.ignore(comment)
|
184
|
+
|
185
|
+
|
186
|
+
# STATE block
|
187
|
+
|
188
|
+
## Block definition
|
189
|
+
state_var = Word(alphas) + Suppress(Optional(unit | dimensionless)) + Suppress(Optional(from_to))
|
190
|
+
# state_var = Group(identifier('name') + Optional(unit | dimensionless)('unit') + Optional(comment))
|
191
|
+
state_block = Group(
|
192
|
+
STATE
|
193
|
+
+ LBRACE
|
194
|
+
+ OneOrMore(state_var)
|
195
|
+
+ RBRACE
|
196
|
+
)('block')
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
# breakpoint_block = BREAKPOINT + SkipTo(block_to_keep)
|
202
|
+
# breakpoint_block = Suppress(breakpoint_block)
|
203
|
+
|
204
|
+
# DERIVATIVE block (not used)
|
205
|
+
# derivative_block = DERIVATIVE + SkipTo(block_to_keep)
|
206
|
+
# derivative_block = Suppress(derivative_block)
|
207
|
+
|
208
|
+
# INDEPENDENT block (not used)
|
209
|
+
independent_block = INDEPENDENT + SkipTo(block_to_keep)
|
210
|
+
independent_block = Suppress(independent_block)
|
211
|
+
|
212
|
+
kinetic_block = KINETIC + SkipTo(block_to_keep)
|
213
|
+
kinetic_block = Suppress(kinetic_block)
|
214
|
+
|
215
|
+
derivative_block = DERIVATIVE + SkipTo(block_to_keep)
|
216
|
+
derivative_block = Suppress(kinetic_block)
|
217
|
+
|
218
|
+
# Functional blocks
|
219
|
+
|
220
|
+
## Signature
|
221
|
+
|
222
|
+
param = Group(identifier('name') + Optional(unit('unit') | dimensionless('unit')))
|
223
|
+
param_list = delimitedList(param)('params')
|
224
|
+
signature = Group(
|
225
|
+
identifier('name')
|
226
|
+
+ LPAREN
|
227
|
+
+ Optional(param_list)
|
228
|
+
+ RPAREN
|
229
|
+
+ Optional(unit)('returned_unit')
|
230
|
+
)('signature')
|
231
|
+
|
232
|
+
## Local
|
233
|
+
LOCAL = Keyword("LOCAL", caseless=False)
|
234
|
+
LOCAL = Suppress(LOCAL)
|
235
|
+
local_stmt = LOCAL + delimitedList(identifier)
|
236
|
+
|
237
|
+
# Expression
|
238
|
+
expr = Forward()
|
239
|
+
parenth_expr = LPAREN + expr + RPAREN
|
240
|
+
|
241
|
+
## Function call with arguments
|
242
|
+
arg = expr | identifier | number
|
243
|
+
arg_list = delimitedList(arg)('args')
|
244
|
+
func_call_with_args = Group(identifier + LPAREN + Optional(arg_list) + RPAREN)
|
245
|
+
def func_call_with_args_action(tokens):
|
246
|
+
function_name = tokens[0][0]
|
247
|
+
function_args = tokens[0][1:]
|
248
|
+
return {function_name: function_args}
|
249
|
+
func_call_with_args.setParseAction(func_call_with_args_action)
|
250
|
+
|
251
|
+
## Function call with expression
|
252
|
+
func_call_with_expr = Group(identifier('name') + LPAREN + expr + RPAREN)
|
253
|
+
def func_call_with_expr_action(tokens):
|
254
|
+
function_name = tokens[0][0]
|
255
|
+
function_expr = tokens[0][1]
|
256
|
+
return {function_name: function_expr}
|
257
|
+
func_call_with_expr.setParseAction(func_call_with_expr_action)
|
258
|
+
|
259
|
+
## Operands
|
260
|
+
func_operand = func_call_with_args | func_call_with_expr
|
261
|
+
operand = func_operand | quantity | number | identifier # the order is important!
|
262
|
+
operand = operand | LPAREN + operand + RPAREN
|
263
|
+
|
264
|
+
## Operators
|
265
|
+
signop = Literal('-')
|
266
|
+
plusop = oneOf('+ -')
|
267
|
+
mulop = oneOf('* /')
|
268
|
+
orderop = oneOf('< > <= >= ==')
|
269
|
+
powop = Literal('^')
|
270
|
+
|
271
|
+
# def sign_action(tokens):
|
272
|
+
# tokens = tokens[0]
|
273
|
+
# return {tokens[0]: tokens[1]}
|
274
|
+
|
275
|
+
# def op_action(tokens):
|
276
|
+
# tokens = tokens[0]
|
277
|
+
# return {tokens[1]: [tokens[0], tokens[2]]}
|
278
|
+
|
279
|
+
def sign_action(tokens):
|
280
|
+
tokens = tokens[0]
|
281
|
+
return {tokens[0]: [tokens[1]]}
|
282
|
+
|
283
|
+
def op_action(tokens):
|
284
|
+
tokens = tokens[0]
|
285
|
+
while len(tokens) > 3:
|
286
|
+
tokens = [{tokens[1]: [tokens[0], tokens[2]]}] + tokens[3:]
|
287
|
+
return {tokens[1]: [tokens[0], tokens[2]]}
|
288
|
+
|
289
|
+
## Expression
|
290
|
+
expr <<= infix_notation(
|
291
|
+
operand,
|
292
|
+
[
|
293
|
+
(signop, 1, opAssoc.RIGHT, sign_action),
|
294
|
+
(powop, 2, opAssoc.RIGHT, op_action),
|
295
|
+
(mulop, 2, opAssoc.LEFT, op_action),
|
296
|
+
(plusop, 2, opAssoc.LEFT, op_action),
|
297
|
+
(orderop, 2, opAssoc.LEFT, op_action),
|
298
|
+
]
|
299
|
+
)
|
300
|
+
|
301
|
+
|
302
|
+
# expr = expr | LPAREN + expr + RPAREN
|
303
|
+
|
304
|
+
|
305
|
+
## Assignment
|
306
|
+
assignment_stmt = Group(
|
307
|
+
identifier('assigned_var')
|
308
|
+
+ EQUAL
|
309
|
+
+ expr('expression')
|
310
|
+
)
|
311
|
+
|
312
|
+
# BREAKPOINT block (not used)
|
313
|
+
SOLVE = Suppress(Keyword('SOLVE', caseless=False))
|
314
|
+
METHOD = Suppress(Keyword('METHOD', caseless=False))
|
315
|
+
STEADYSTATE = Suppress(Keyword('STEADYSTATE', caseless=False))
|
316
|
+
|
317
|
+
solve_stmt = Group(
|
318
|
+
SOLVE
|
319
|
+
+ identifier("solve")
|
320
|
+
+ (METHOD | STEADYSTATE)
|
321
|
+
+ identifier("method")
|
322
|
+
)("solve_stmt")
|
323
|
+
|
324
|
+
breakpoint_block = Group(
|
325
|
+
BREAKPOINT
|
326
|
+
+ LBRACE
|
327
|
+
+ solve_stmt
|
328
|
+
+ ZeroOrMore(assignment_stmt)("statements")
|
329
|
+
+ RBRACE
|
330
|
+
)("block")
|
331
|
+
|
332
|
+
initial_stmt = (solve_stmt | assignment_stmt | func_call_with_args )
|
333
|
+
|
334
|
+
initial_block = Group(
|
335
|
+
INITIAL
|
336
|
+
+ LBRACE
|
337
|
+
# + OneOrMore(func_call_with_args)("func_calls")
|
338
|
+
# + OneOrMore(assignment_stmt)("statements")
|
339
|
+
+ OneOrMore(initial_stmt)("statements")
|
340
|
+
+ RBRACE
|
341
|
+
)("block")
|
342
|
+
|
343
|
+
derivative_assignment_stmt = Group(
|
344
|
+
identifier('assigned_var')
|
345
|
+
+ "'"
|
346
|
+
+ EQUAL
|
347
|
+
+ expr('expression')
|
348
|
+
)
|
349
|
+
|
350
|
+
derivative_block = Group(
|
351
|
+
DERIVATIVE
|
352
|
+
+ Word(alphas)("name")
|
353
|
+
+ LBRACE
|
354
|
+
+ OneOrMore(func_call_with_args)("func_calls")
|
355
|
+
+ OneOrMore(derivative_assignment_stmt)("statements")
|
356
|
+
+ RBRACE
|
357
|
+
)("block")
|
358
|
+
|
359
|
+
# FUNCTION block
|
360
|
+
|
361
|
+
## IF-ELSE statement
|
362
|
+
IF = Keyword("if", caseless=False)
|
363
|
+
IF = Suppress(IF)
|
364
|
+
ELSE = Keyword("else", caseless=False)
|
365
|
+
ELSE = Suppress(ELSE)
|
366
|
+
|
367
|
+
if_else_stmt = Group(
|
368
|
+
IF + LPAREN + expr('condition') + RPAREN
|
369
|
+
+ LBRACE
|
370
|
+
+ OneOrMore(assignment_stmt)('if_statements')
|
371
|
+
+ RBRACE
|
372
|
+
+ Optional(ELSE + LBRACE + OneOrMore(assignment_stmt)('else_statements') + RBRACE)
|
373
|
+
)
|
374
|
+
|
375
|
+
|
376
|
+
# if_else_stmt = if_else_stmt('if_else_statements*')
|
377
|
+
# assignment_stmt = assignment_stmt('assignment_statements*')
|
378
|
+
|
379
|
+
stmt = (assignment_stmt | if_else_stmt)
|
380
|
+
|
381
|
+
## Block definition
|
382
|
+
function_block = Group(
|
383
|
+
FUNCTION
|
384
|
+
+ signature('signature')
|
385
|
+
+ LBRACE
|
386
|
+
+ ZeroOrMore(local_stmt)('locals')
|
387
|
+
# + ZeroOrMore(if_else_stmt)('if_else_statements')
|
388
|
+
# + ZeroOrMore(assignment_stmt)('statements')
|
389
|
+
+ OneOrMore(stmt)("statements")
|
390
|
+
+ RBRACE)("block")
|
391
|
+
|
392
|
+
function_blocks = OneOrMore(function_block)("function_blocks")
|
393
|
+
|
394
|
+
|
395
|
+
# PROCEDURE block
|
396
|
+
|
397
|
+
# stmt = (if_else_stmt('if_else_statements*') | assignment_stmt('statements*'))
|
398
|
+
|
399
|
+
|
400
|
+
|
401
|
+
## Block definition
|
402
|
+
procedure_block = Group(
|
403
|
+
PROCEDURE
|
404
|
+
+ signature('signature')
|
405
|
+
+ LBRACE
|
406
|
+
+ ZeroOrMore(local_stmt)('locals')
|
407
|
+
# + ZeroOrMore(if_else_stmt)('if_else_statements')
|
408
|
+
# + OneOrMore(assignment_stmt)('statements')
|
409
|
+
+ OneOrMore(stmt)('statements')
|
410
|
+
+ RBRACE)("block")
|
411
|
+
|
412
|
+
procedure_blocks = OneOrMore(procedure_block)("procedure_blocks")
|
413
|
+
|
414
|
+
# MOD file
|
415
|
+
|
416
|
+
block = kinetic_block | independent_block | breakpoint_block | initial_block | derivative_block | procedure_blocks | function_blocks | title | comment_block | neuron_block | units_block | parameter_block | assigned_block | state_block
|
417
|
+
grammar = Group(ZeroOrMore(block))('mod_file')
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
import subprocess
|
4
|
+
import neuron
|
5
|
+
from neuron import h
|
6
|
+
|
7
|
+
from pprint import pprint
|
8
|
+
|
9
|
+
class MODFileLoader():
|
10
|
+
|
11
|
+
def __init__(self):
|
12
|
+
self._loaded_mechanisms = set()
|
13
|
+
self.verbose = False
|
14
|
+
|
15
|
+
# LOADING METHODS
|
16
|
+
|
17
|
+
def _get_mechanism_dir(self, path_to_mod_file: str) -> str:
|
18
|
+
"""
|
19
|
+
Get the subdirectory for the given mod file.
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
path_to_mod_file : str
|
24
|
+
Path to the .mod file.
|
25
|
+
|
26
|
+
Returns
|
27
|
+
-------
|
28
|
+
str
|
29
|
+
Path to the subdirectory for the mechanism.
|
30
|
+
"""
|
31
|
+
mechanism_name = os.path.basename(path_to_mod_file).replace('.mod', '')
|
32
|
+
parent_dir = os.path.dirname(path_to_mod_file)
|
33
|
+
return os.path.join(parent_dir, mechanism_name)
|
34
|
+
|
35
|
+
def load_mechanism(self, path_to_mod_file: str,
|
36
|
+
recompile: bool = False) -> None:
|
37
|
+
"""
|
38
|
+
Load a mechanism from the specified mod file.
|
39
|
+
Uses the NEURON neuron.load_mechanisms method to make
|
40
|
+
the mechanism available in the hoc interpreter.
|
41
|
+
Creates a temporary directory for the mechanism files
|
42
|
+
to be able to dynamically load mechanisms.
|
43
|
+
|
44
|
+
Parameters
|
45
|
+
----------
|
46
|
+
path_to_mod_file : str
|
47
|
+
Path to the .mod file.
|
48
|
+
recompile : bool
|
49
|
+
Force recompilation even if already compiled.
|
50
|
+
"""
|
51
|
+
mechanism_name = os.path.basename(path_to_mod_file).replace('.mod', '')
|
52
|
+
mechanism_dir = self._get_mechanism_dir(path_to_mod_file)
|
53
|
+
x86_64_dir = os.path.join(mechanism_dir, 'x86_64')
|
54
|
+
|
55
|
+
if self.verbose: print(f"{'=' * 60}\nLoading mechanism {mechanism_name} to NEURON...\n{'=' * 60}")
|
56
|
+
|
57
|
+
if mechanism_name in self._loaded_mechanisms:
|
58
|
+
if self.verbose: print(f'Mechanism "{mechanism_name}" already loaded')
|
59
|
+
return
|
60
|
+
|
61
|
+
if recompile and os.path.exists(mechanism_dir):
|
62
|
+
shutil.rmtree(mechanism_dir)
|
63
|
+
|
64
|
+
if not os.path.exists(x86_64_dir):
|
65
|
+
if self.verbose: print(f'Compiling mechanism "{mechanism_name}"...')
|
66
|
+
os.makedirs(mechanism_dir, exist_ok=True)
|
67
|
+
shutil.copy(path_to_mod_file, mechanism_dir)
|
68
|
+
self._compile_files(mechanism_dir)
|
69
|
+
|
70
|
+
if hasattr(h, mechanism_name):
|
71
|
+
if self.verbose: print(f'Mechanism "{mechanism_name}" already exists in hoc')
|
72
|
+
else:
|
73
|
+
try:
|
74
|
+
neuron.load_mechanisms(mechanism_dir)
|
75
|
+
except Exception as e:
|
76
|
+
print(f"Failed to load mechanism {mechanism_name}: {e}")
|
77
|
+
return
|
78
|
+
self._loaded_mechanisms.add(mechanism_name)
|
79
|
+
if self.verbose: print(f'Loaded mechanism "{mechanism_name}"')
|
80
|
+
|
81
|
+
# HELPER METHODS
|
82
|
+
|
83
|
+
|
84
|
+
def _compile_files(self, path: str) -> None:
|
85
|
+
"""Compile the MOD files in the specified directory."""
|
86
|
+
try:
|
87
|
+
subprocess.run(["nrnivmodl"], cwd=path, check=True)
|
88
|
+
except subprocess.CalledProcessError as e:
|
89
|
+
print(f"Compilation failed: {e}")
|
90
|
+
|