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.
Files changed (56) hide show
  1. dendrotweaks/__init__.py +10 -0
  2. dendrotweaks/analysis/__init__.py +11 -0
  3. dendrotweaks/analysis/ephys_analysis.py +482 -0
  4. dendrotweaks/analysis/morphometric_analysis.py +106 -0
  5. dendrotweaks/membrane/__init__.py +6 -0
  6. dendrotweaks/membrane/default_mod/AMPA.mod +65 -0
  7. dendrotweaks/membrane/default_mod/AMPA_NMDA.mod +100 -0
  8. dendrotweaks/membrane/default_mod/CaDyn.mod +54 -0
  9. dendrotweaks/membrane/default_mod/GABAa.mod +65 -0
  10. dendrotweaks/membrane/default_mod/Leak.mod +27 -0
  11. dendrotweaks/membrane/default_mod/NMDA.mod +72 -0
  12. dendrotweaks/membrane/default_mod/vecstim.mod +76 -0
  13. dendrotweaks/membrane/default_templates/NEURON_template.py +354 -0
  14. dendrotweaks/membrane/default_templates/default.py +73 -0
  15. dendrotweaks/membrane/default_templates/standard_channel.mod +87 -0
  16. dendrotweaks/membrane/default_templates/template_jaxley.py +108 -0
  17. dendrotweaks/membrane/default_templates/template_jaxley_new.py +108 -0
  18. dendrotweaks/membrane/distributions.py +324 -0
  19. dendrotweaks/membrane/groups.py +103 -0
  20. dendrotweaks/membrane/io/__init__.py +11 -0
  21. dendrotweaks/membrane/io/ast.py +201 -0
  22. dendrotweaks/membrane/io/code_generators.py +312 -0
  23. dendrotweaks/membrane/io/converter.py +108 -0
  24. dendrotweaks/membrane/io/factories.py +144 -0
  25. dendrotweaks/membrane/io/grammar.py +417 -0
  26. dendrotweaks/membrane/io/loader.py +90 -0
  27. dendrotweaks/membrane/io/parser.py +499 -0
  28. dendrotweaks/membrane/io/reader.py +212 -0
  29. dendrotweaks/membrane/mechanisms.py +574 -0
  30. dendrotweaks/model.py +1916 -0
  31. dendrotweaks/model_io.py +75 -0
  32. dendrotweaks/morphology/__init__.py +5 -0
  33. dendrotweaks/morphology/domains.py +100 -0
  34. dendrotweaks/morphology/io/__init__.py +5 -0
  35. dendrotweaks/morphology/io/factories.py +212 -0
  36. dendrotweaks/morphology/io/reader.py +66 -0
  37. dendrotweaks/morphology/io/validation.py +212 -0
  38. dendrotweaks/morphology/point_trees.py +681 -0
  39. dendrotweaks/morphology/reduce/__init__.py +16 -0
  40. dendrotweaks/morphology/reduce/reduce.py +155 -0
  41. dendrotweaks/morphology/reduce/reduced_cylinder.py +129 -0
  42. dendrotweaks/morphology/sec_trees.py +1112 -0
  43. dendrotweaks/morphology/seg_trees.py +157 -0
  44. dendrotweaks/morphology/trees.py +567 -0
  45. dendrotweaks/path_manager.py +261 -0
  46. dendrotweaks/simulators.py +235 -0
  47. dendrotweaks/stimuli/__init__.py +3 -0
  48. dendrotweaks/stimuli/iclamps.py +73 -0
  49. dendrotweaks/stimuli/populations.py +265 -0
  50. dendrotweaks/stimuli/synapses.py +203 -0
  51. dendrotweaks/utils.py +239 -0
  52. dendrotweaks-0.3.1.dist-info/METADATA +70 -0
  53. dendrotweaks-0.3.1.dist-info/RECORD +56 -0
  54. dendrotweaks-0.3.1.dist-info/WHEEL +5 -0
  55. dendrotweaks-0.3.1.dist-info/licenses/LICENSE +674 -0
  56. 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
+