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,201 @@
|
|
1
|
+
import pprint
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
ALLOWED_INDEPENDENT_VARS = ['cai', 'v']
|
5
|
+
|
6
|
+
# Assumptions:
|
7
|
+
# - Kinetic variables include the state variable name and
|
8
|
+
# the substrings inf or tau (e.g., minf, mtau). Order, case, and additional characters
|
9
|
+
# do not matter (e.g., mInf, tau_M are also valid).
|
10
|
+
# - The channel is either voltage or calcium dependent, and
|
11
|
+
# the independent variable is either v or cai.
|
12
|
+
# - Temperature adjustment coefficient is referred to as tadj and calculated as:
|
13
|
+
# tadj = q10^((celsius - temp)/10), where q10 is the temperature coefficient,
|
14
|
+
# temp is the reference temperature, and celsius is the current temperature.
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
class AbstracSyntaxTree():
|
19
|
+
"""
|
20
|
+
A class to represent the abstract syntax tree of a .mod file.
|
21
|
+
|
22
|
+
Attributes
|
23
|
+
----------
|
24
|
+
functions : List[Functional]
|
25
|
+
A list of Functional objects representing the FUNCTION blocks in the .mod file.
|
26
|
+
procedures : List[Functional]
|
27
|
+
A list of Functional objects representing the PROCEDURE blocks in the .mod file.
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, ast_dict: dict):
|
31
|
+
# ast_dict = {k: v[0]
|
32
|
+
# if len(v) == 1 and k not in ['FUNCTION', 'PROCEDURE'] else v
|
33
|
+
# for k, v in ast_dict.items()}
|
34
|
+
self.functions = [Functional(func, has_return=True)
|
35
|
+
for func in ast_dict.get('FUNCTION', [])]
|
36
|
+
self.procedures = [Functional(proc, has_return=False)
|
37
|
+
for proc in ast_dict.get('PROCEDURE', [])]
|
38
|
+
self._ast = ast_dict
|
39
|
+
|
40
|
+
def __getitem__(self, key):
|
41
|
+
return self._ast[key]
|
42
|
+
|
43
|
+
def __setitem__(self, key, value):
|
44
|
+
self._ast[key] = value
|
45
|
+
|
46
|
+
def __repr__(self):
|
47
|
+
return pprint.pformat(self._ast, sort_dicts=False)
|
48
|
+
|
49
|
+
@property
|
50
|
+
def title(self):
|
51
|
+
return ''.join(self['TITLE']).strip()
|
52
|
+
|
53
|
+
@property
|
54
|
+
def comment(self):
|
55
|
+
return ''.join(self['COMMENT']).strip()
|
56
|
+
|
57
|
+
# NEURON block
|
58
|
+
@property
|
59
|
+
def suffix(self):
|
60
|
+
if self['NEURON'] is not None:
|
61
|
+
return self['NEURON']['suffix']
|
62
|
+
|
63
|
+
@property
|
64
|
+
def ion(self):
|
65
|
+
if self['NEURON'].get('useion'):
|
66
|
+
ions = [ion['ion']
|
67
|
+
for ion in self['NEURON']['useion'] if ion.get('write', '')]
|
68
|
+
if len(ions) == 1:
|
69
|
+
return ions[0]
|
70
|
+
elif len(ions) == 0:
|
71
|
+
return None
|
72
|
+
else:
|
73
|
+
raise Exception('Multiple ions not supported')
|
74
|
+
else:
|
75
|
+
return None
|
76
|
+
|
77
|
+
# PARAMETER block
|
78
|
+
@property
|
79
|
+
def params(self):
|
80
|
+
"""
|
81
|
+
Returns a dictionary of the parameters in the PARAMETER block.
|
82
|
+
"""
|
83
|
+
return {param['name']: param['value'] for param in self['PARAMETER']}
|
84
|
+
|
85
|
+
@property
|
86
|
+
def range_params(self):
|
87
|
+
"""
|
88
|
+
Returns a dictionary of the range parameters in the PARAMETER block.
|
89
|
+
"""
|
90
|
+
return {k:v for k, v in self.params.items()
|
91
|
+
if k in self['NEURON']['range']}
|
92
|
+
|
93
|
+
|
94
|
+
# ASSIGNED block
|
95
|
+
@property
|
96
|
+
def assigned_vars(self):
|
97
|
+
"""
|
98
|
+
Returns a list of the assigned variables in the ASSIGNED block.
|
99
|
+
"""
|
100
|
+
return [assigned['name'] for assigned in self['ASSIGNED']]
|
101
|
+
|
102
|
+
@property
|
103
|
+
def independent_var_name(self):
|
104
|
+
"""
|
105
|
+
Returns the name of the independent variable.
|
106
|
+
Prefers 'cai' over 'v' if both are present.
|
107
|
+
"""
|
108
|
+
independent_vars = [var for var in self.assigned_vars
|
109
|
+
if any(indep_var in var.lower()
|
110
|
+
for indep_var in ALLOWED_INDEPENDENT_VARS)]
|
111
|
+
if 'cai' in independent_vars:
|
112
|
+
return 'cai'
|
113
|
+
elif 'v' in independent_vars:
|
114
|
+
return 'v'
|
115
|
+
raise Exception('Independent variable not found')
|
116
|
+
|
117
|
+
def is_voltage_dependent(self):
|
118
|
+
"""
|
119
|
+
Returns True if the mechanism is voltage dependent.
|
120
|
+
"""
|
121
|
+
for var in self.assigned_vars:
|
122
|
+
if 'v' in var.lower():
|
123
|
+
return True
|
124
|
+
return False
|
125
|
+
|
126
|
+
def is_ca_dependent(self):
|
127
|
+
"""
|
128
|
+
Returns True if the mechanism is calcium dependent.
|
129
|
+
"""
|
130
|
+
for var in self.assigned_vars:
|
131
|
+
if 'cai' in var.lower():
|
132
|
+
return True
|
133
|
+
return False
|
134
|
+
|
135
|
+
# STATE block
|
136
|
+
@property
|
137
|
+
def state_vars(self):
|
138
|
+
"""
|
139
|
+
Returns a dictionary of the state variables in the STATE block.
|
140
|
+
"""
|
141
|
+
return self['STATE']
|
142
|
+
|
143
|
+
|
144
|
+
class Functional():
|
145
|
+
"""
|
146
|
+
A class to represent abstract syntax tree of a
|
147
|
+
FUNCTION or PROCEDURE block in a .mod file.
|
148
|
+
|
149
|
+
Attributes
|
150
|
+
----------
|
151
|
+
has_return : bool
|
152
|
+
Whether the block has a return statement (is a FUNCTION block)
|
153
|
+
or not (is a PROCEDURE block).
|
154
|
+
"""
|
155
|
+
|
156
|
+
def __init__(self, func_ast, has_return=True):
|
157
|
+
self._ast = func_ast
|
158
|
+
self.has_return = has_return
|
159
|
+
|
160
|
+
def __getitem__(self, key):
|
161
|
+
return self._ast[key]
|
162
|
+
|
163
|
+
def get(self, key, default=None):
|
164
|
+
return self._ast.get(key, default)
|
165
|
+
|
166
|
+
def __setitem__(self, key, value):
|
167
|
+
self._ast[key] = value
|
168
|
+
|
169
|
+
def __repr__(self):
|
170
|
+
return pprint.pformat(self._ast, sort_dicts=False)
|
171
|
+
|
172
|
+
# Signature\
|
173
|
+
@property
|
174
|
+
def name(self):
|
175
|
+
return self['signature']['name']
|
176
|
+
|
177
|
+
@name.setter
|
178
|
+
def name(self, value):
|
179
|
+
self['signature']['name'] = value
|
180
|
+
|
181
|
+
@property
|
182
|
+
def params(self):
|
183
|
+
return [param['name'] for param in self['signature'].get('params', [])]
|
184
|
+
|
185
|
+
# Locals
|
186
|
+
@property
|
187
|
+
def local_vars(self):
|
188
|
+
local_vars = []
|
189
|
+
if self.has_return:
|
190
|
+
local_vars.append(self['signature']['name'])
|
191
|
+
local_vars.extend([arg['name'] for arg in self['signature'].get('args', [])])
|
192
|
+
local_vars.extend(self.get('locals', []))
|
193
|
+
return local_vars
|
194
|
+
|
195
|
+
@property
|
196
|
+
def signature(self):
|
197
|
+
return self['signature']
|
198
|
+
|
199
|
+
@property
|
200
|
+
def statements(self):
|
201
|
+
return self['statements']
|
@@ -0,0 +1,312 @@
|
|
1
|
+
import re
|
2
|
+
import os
|
3
|
+
from jinja2 import Template
|
4
|
+
|
5
|
+
from abc import ABC, abstractmethod
|
6
|
+
from jinja2 import Environment, FileSystemLoader
|
7
|
+
from dendrotweaks.utils import write_file
|
8
|
+
|
9
|
+
# Configure the Jinja2 environment
|
10
|
+
# env = Environment(
|
11
|
+
# loader=FileSystemLoader('static/data/templates'), # Load templates from the 'templates' directory
|
12
|
+
# trim_blocks=False, # Trim newlines after Jinja blocks
|
13
|
+
# lstrip_blocks=False, # Strip leading whitespace from Jinja blocks
|
14
|
+
# )
|
15
|
+
|
16
|
+
EQUILIBRIUM_POTENTIALS = {
|
17
|
+
'na': 60,
|
18
|
+
'k': -80,
|
19
|
+
'ca': 140
|
20
|
+
}
|
21
|
+
|
22
|
+
class CodeGenerator(ABC):
|
23
|
+
|
24
|
+
@abstractmethod
|
25
|
+
def generate(self, ast, path_to_template):
|
26
|
+
pass
|
27
|
+
|
28
|
+
def write_file(self, path_to_file):
|
29
|
+
write_file(self.content, path_to_file)
|
30
|
+
|
31
|
+
|
32
|
+
class PythonCodeGenerator(CodeGenerator):
|
33
|
+
""" A class to generate Python code from an AST using a Jinja2 template. """
|
34
|
+
|
35
|
+
def __init__(self):
|
36
|
+
self.content = None
|
37
|
+
|
38
|
+
# MAIN METHOD
|
39
|
+
|
40
|
+
def generate(self, ast, path_to_template):
|
41
|
+
"""
|
42
|
+
Generate a Python class from the AST using a Jinja2 template.
|
43
|
+
|
44
|
+
Parameters
|
45
|
+
----------
|
46
|
+
ast : dict
|
47
|
+
The AST representation of the channel
|
48
|
+
path_to_template : str
|
49
|
+
The path to the Jinja2 template file
|
50
|
+
|
51
|
+
Returns
|
52
|
+
-------
|
53
|
+
str
|
54
|
+
The Python code generated from the AST
|
55
|
+
"""
|
56
|
+
|
57
|
+
# Read the template file
|
58
|
+
with open(path_to_template, 'r') as file:
|
59
|
+
template_string = file.read()
|
60
|
+
|
61
|
+
# # Create a Jinja2 template from the string
|
62
|
+
template = Template(template_string)
|
63
|
+
# template = env.get_template(self.path_to_template)
|
64
|
+
|
65
|
+
# Define the variables for the template
|
66
|
+
variables = {
|
67
|
+
'title': ast.title,
|
68
|
+
# 'comment': ast.comment,
|
69
|
+
'class_name': ast.suffix,
|
70
|
+
'suffix': ast.suffix,
|
71
|
+
'ion': ast.ion,
|
72
|
+
'independent_var_name': ast.independent_var_name,
|
73
|
+
'channel_params': ast.params,
|
74
|
+
'range_params': ast.range_params,
|
75
|
+
'state_vars': ast.state_vars,
|
76
|
+
'functions': self._generate_functions(ast),
|
77
|
+
'procedures': self._generate_procedures(ast),
|
78
|
+
'procedure_calls': self._generate_procedure_calls(ast),
|
79
|
+
'E_ion': EQUILIBRIUM_POTENTIALS.get(ast.ion, None)
|
80
|
+
}
|
81
|
+
|
82
|
+
# Render the template with the variables
|
83
|
+
content = template.render(variables)
|
84
|
+
|
85
|
+
if re.search(r'\bjnp\b', template_string):
|
86
|
+
content = content.replace('np', 'jnp')
|
87
|
+
|
88
|
+
self.content = content
|
89
|
+
|
90
|
+
|
91
|
+
# HELPER METHODS
|
92
|
+
|
93
|
+
def _generate_functions(self, ast, indent=8):
|
94
|
+
functions = []
|
95
|
+
|
96
|
+
for function in ast.functions:
|
97
|
+
# Generate the signature
|
98
|
+
signature_str = self._generate_signature(function.signature)
|
99
|
+
|
100
|
+
# Generate the body
|
101
|
+
body_str = self._generate_body(function.statements)
|
102
|
+
for name in [function.name for function in ast.functions if function != function]:
|
103
|
+
body_str = re.sub(r'\b' + re.escape(name) + r'\b', f"self.{name}", body_str)
|
104
|
+
body_str = re.sub(r'\b' + re.escape('tadj') + r'\b', f"self.tadj", body_str)
|
105
|
+
body_str = re.sub(r'\b' + re.escape('celsius') + r'\b', f"self.temperature", body_str)
|
106
|
+
body_str += f"return {function.name}"
|
107
|
+
body_str = '\n'.join(' ' * indent + line
|
108
|
+
for line in body_str.splitlines())
|
109
|
+
|
110
|
+
# Find the parameters that are used in the body
|
111
|
+
params = [param for param in ast.params
|
112
|
+
if param not in function.local_vars
|
113
|
+
and param not in function.params
|
114
|
+
and re.search(r'\b' + re.escape(param) + r'\b', body_str)]
|
115
|
+
|
116
|
+
functions.append({
|
117
|
+
'signature': signature_str,
|
118
|
+
'params': params,
|
119
|
+
'body': body_str.strip()
|
120
|
+
})
|
121
|
+
|
122
|
+
return functions
|
123
|
+
|
124
|
+
def _generate_procedures(self, ast, indent=8):
|
125
|
+
|
126
|
+
if len(ast.procedures) != 1:
|
127
|
+
raise ValueError("Only one procedure is supported")
|
128
|
+
ast.procedures[0].name = 'compute_kinetic_variables'
|
129
|
+
|
130
|
+
procedures = []
|
131
|
+
|
132
|
+
for procedure in ast.procedures:
|
133
|
+
# Generate the signature
|
134
|
+
signature_str = self._generate_signature(procedure.signature,
|
135
|
+
is_method=True,
|
136
|
+
extra_params=['celsius'])
|
137
|
+
|
138
|
+
# Generate the body
|
139
|
+
body_str = self._generate_body(procedure.statements)
|
140
|
+
for name in [function.name for function in ast.functions]:
|
141
|
+
body_str = re.sub(r'\b' + re.escape(name) + r'\b', f"self.{name}", body_str)
|
142
|
+
body_str = re.sub(r'\b' + re.escape('tadj') + r'\b', f"self.tadj", body_str)
|
143
|
+
body_str = re.sub(r'\b' + re.escape('celsius') + r'\b', f"self.temperature", body_str)
|
144
|
+
body_str += 'return ' + ', '.join([f"{state_var}Inf, {state_var}Tau"
|
145
|
+
for state_var in ast.state_vars])
|
146
|
+
body_str = '\n'.join(' ' * indent + line
|
147
|
+
for line in body_str.splitlines())
|
148
|
+
|
149
|
+
# Find the parameters that are used in the body
|
150
|
+
params = [param for param in ast.params
|
151
|
+
if param not in procedure.local_vars
|
152
|
+
and re.search(r'\b' + re.escape(param) + r'\b', body_str)]
|
153
|
+
|
154
|
+
procedures.append({
|
155
|
+
'signature': signature_str,
|
156
|
+
'params': params,
|
157
|
+
'body': body_str.strip()
|
158
|
+
})
|
159
|
+
|
160
|
+
return procedures
|
161
|
+
|
162
|
+
def _generate_signature(self, signature, is_method=True, extra_params=None):
|
163
|
+
"""
|
164
|
+
Generate the signature string for a function using a Jinja2 template.
|
165
|
+
The function AST representation is used to retrieve the function name
|
166
|
+
and parameters:
|
167
|
+
>>> def f_name(self, arg1, arg2, ...):
|
168
|
+
|
169
|
+
Parameters
|
170
|
+
----------
|
171
|
+
signature : dict
|
172
|
+
The function signature as an AST dictionary
|
173
|
+
is_method : bool
|
174
|
+
Whether the function is a class method or not
|
175
|
+
"""
|
176
|
+
signature_template = (
|
177
|
+
"def {{ name }}({% if params %}{{ params | join(', ') }}{% endif %}):"
|
178
|
+
)
|
179
|
+
template = Template(signature_template)
|
180
|
+
name = signature['name']
|
181
|
+
params = [param['name'] for param in signature.get('params', [])]
|
182
|
+
if is_method:
|
183
|
+
params = ['self'] + params
|
184
|
+
|
185
|
+
return template.render(name=name, params=params)
|
186
|
+
|
187
|
+
def _generate_body(self, statements, indent=12, skip_vars=['tadj']):
|
188
|
+
python_code = ""
|
189
|
+
# Add statements
|
190
|
+
for statement in statements:
|
191
|
+
# If the statement is an if-else statement
|
192
|
+
if statement.get('condition', False):
|
193
|
+
python_code += self._generate_conditionals(statement)
|
194
|
+
else:
|
195
|
+
if statement['assigned_var'] in skip_vars:
|
196
|
+
continue
|
197
|
+
python_code += (f"{statement['assigned_var']} = {statement['expression']}\n")
|
198
|
+
|
199
|
+
return python_code
|
200
|
+
|
201
|
+
def _generate_conditionals(self, statement):
|
202
|
+
"""
|
203
|
+
Generate the conditional statement for an if-else block using a Jinja2 template.
|
204
|
+
"""
|
205
|
+
condition = statement['condition']
|
206
|
+
if_statements = statement['if_statements']
|
207
|
+
else_statements = statement.get('else_statements', [])
|
208
|
+
else_statements = {statement['assigned_var']: statement['expression'] for statement in else_statements}
|
209
|
+
|
210
|
+
conditional_code = ""
|
211
|
+
for if_statement in statement['if_statements']:
|
212
|
+
# Default to variable name if not in else_expressions
|
213
|
+
else_statement = else_statements.get(
|
214
|
+
if_statement['assigned_var'],
|
215
|
+
if_statement["assigned_var"]
|
216
|
+
)
|
217
|
+
|
218
|
+
# Use a Jinja2 template to generate the conditional code
|
219
|
+
conditional_template = (
|
220
|
+
"conditions = [{{ condition }}, ~({{ condition }})]"
|
221
|
+
"\nchoices = [{{ if_statement }}, {{ else_statement }}]"
|
222
|
+
"\n{{ assigned_var }} = np.select(conditions, choices)"
|
223
|
+
)
|
224
|
+
template = Template(conditional_template)
|
225
|
+
conditional_code += template.render(
|
226
|
+
condition=condition,
|
227
|
+
if_statement=if_statement['expression'],
|
228
|
+
else_statement=else_statement,
|
229
|
+
assigned_var=if_statement['assigned_var']
|
230
|
+
)
|
231
|
+
conditional_code += "\n" # Add a newline between blocks
|
232
|
+
|
233
|
+
return conditional_code
|
234
|
+
|
235
|
+
def _generate_procedure_calls(self, ast):
|
236
|
+
|
237
|
+
for procedure in ast.procedures:
|
238
|
+
|
239
|
+
name = procedure.signature['name']
|
240
|
+
params = [param['name'] for param in procedure.signature.get('params', [])]
|
241
|
+
state_vars = list(ast.state_vars.keys())
|
242
|
+
|
243
|
+
procedure_call_template = """{%- for state_var in state_vars -%}
|
244
|
+
{{ state_var }}Inf, {{ state_var }}Tau{% if not loop.last %}, {% endif -%}
|
245
|
+
{% endfor %} = self.{{ name }}({% if params %}{{ params | join(', ') }}{% endif %})
|
246
|
+
"""
|
247
|
+
template = Template(procedure_call_template.strip())
|
248
|
+
|
249
|
+
return template.render(
|
250
|
+
name=name,
|
251
|
+
params=params,
|
252
|
+
state_vars=state_vars
|
253
|
+
)
|
254
|
+
|
255
|
+
|
256
|
+
|
257
|
+
class NMODLCodeGenerator(CodeGenerator):
|
258
|
+
""" A class to generate NMODL code from a StandardIonChannel"""
|
259
|
+
|
260
|
+
def __init__(self):
|
261
|
+
self.content = None
|
262
|
+
|
263
|
+
def generate(self, channel,
|
264
|
+
path_to_template: str) -> None:
|
265
|
+
"""
|
266
|
+
Generate NMODL code for a standardized ion channel
|
267
|
+
using a Jinja2 template.
|
268
|
+
|
269
|
+
Parameters
|
270
|
+
----------
|
271
|
+
channel : StandardIonChannel
|
272
|
+
The standardized ion channel.
|
273
|
+
path_to_template : str
|
274
|
+
The path to the Jinja2 template file.
|
275
|
+
"""
|
276
|
+
|
277
|
+
# Read the template file
|
278
|
+
with open(path_to_template, 'r') as file:
|
279
|
+
template_string = file.read()
|
280
|
+
|
281
|
+
# Create a Jinja2 template from the string
|
282
|
+
template = Template(template_string)
|
283
|
+
|
284
|
+
def get_unit(param):
|
285
|
+
if param.startswith('vhalf_'): return 'mV'
|
286
|
+
elif param.startswith('sigma_'): return 'mV'
|
287
|
+
elif param.startswith('k_'): return '1/ms'
|
288
|
+
elif param.startswith('delta_'): return '1'
|
289
|
+
elif param.startswith('tau0_'): return 'ms'
|
290
|
+
elif param.startswith('temp'): return 'degC'
|
291
|
+
elif param.startswith('q10'): return '1'
|
292
|
+
elif param.startswith('gbar'): return 'S/cm2'
|
293
|
+
else: return '1'
|
294
|
+
|
295
|
+
# Define the variables for the template
|
296
|
+
variables = {
|
297
|
+
'suffix': channel.name,
|
298
|
+
'ion': channel.ion,
|
299
|
+
'range_params': [
|
300
|
+
(param, channel.params[param], get_unit(param))
|
301
|
+
for param in channel.params
|
302
|
+
],
|
303
|
+
'state_vars': {
|
304
|
+
var: params['power'] for var, params in channel._state_powers.items()
|
305
|
+
},
|
306
|
+
}
|
307
|
+
|
308
|
+
# Render the template with the variables
|
309
|
+
content = template.render(variables)
|
310
|
+
|
311
|
+
self.content = content
|
312
|
+
return content
|
@@ -0,0 +1,108 @@
|
|
1
|
+
from dendrotweaks.membrane.io.reader import MODFileReader
|
2
|
+
from dendrotweaks.membrane.io.parser import MODFileParser
|
3
|
+
from dendrotweaks.membrane.io.code_generators import PythonCodeGenerator
|
4
|
+
|
5
|
+
class MODFileConverter():
|
6
|
+
"""
|
7
|
+
Converts a MOD file to a Python file.
|
8
|
+
|
9
|
+
Attributes
|
10
|
+
----------
|
11
|
+
reader : MODFileReader
|
12
|
+
The MOD file reader.
|
13
|
+
parser : MODFileParser
|
14
|
+
The MOD file parser.
|
15
|
+
generator : PythonCodeGenerator
|
16
|
+
The Python code generator.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(self):
|
20
|
+
self.reader = MODFileReader()
|
21
|
+
self.parser = MODFileParser()
|
22
|
+
self.generator = PythonCodeGenerator()
|
23
|
+
|
24
|
+
@property
|
25
|
+
def mod_content(self):
|
26
|
+
"""
|
27
|
+
The content of the MOD file.
|
28
|
+
"""
|
29
|
+
return self.reader.content
|
30
|
+
|
31
|
+
@property
|
32
|
+
def blocks(self):
|
33
|
+
"""
|
34
|
+
The blocks of the MOD file corresponding to the
|
35
|
+
NMODL blocks e.g. NEURON, PARAMETER, ASSIGNED, etc.
|
36
|
+
"""
|
37
|
+
return self.reader.blocks
|
38
|
+
|
39
|
+
@property
|
40
|
+
def ast(self):
|
41
|
+
"""
|
42
|
+
The abstract syntax tree of the MOD file.
|
43
|
+
"""
|
44
|
+
return self.parser.ast
|
45
|
+
|
46
|
+
@property
|
47
|
+
def python_content(self):
|
48
|
+
"""
|
49
|
+
The content of the generated Python file.
|
50
|
+
"""
|
51
|
+
return self.code_generator.content
|
52
|
+
|
53
|
+
# def convert(self, path_to_mod, path_to_python, path_to_template):
|
54
|
+
# """ Converts a mod file to a python file.
|
55
|
+
|
56
|
+
# Parameters
|
57
|
+
# ----------
|
58
|
+
# path_to_mod : str
|
59
|
+
# The path to the mod file.
|
60
|
+
# path_to_python : str
|
61
|
+
# The path to the python file.
|
62
|
+
# path_to_template : str
|
63
|
+
# The path to the template file.
|
64
|
+
# """
|
65
|
+
|
66
|
+
# self.read_file(path_to_mod) # generates self.mod_content
|
67
|
+
# self.preprocess() # generates self.blocks
|
68
|
+
# self.parse() # generates self.ast
|
69
|
+
# self.generate_python(path_to_template) # generates self.python_content
|
70
|
+
# self.write_file(path_to_python) # writes self.python_content to path_to_python
|
71
|
+
|
72
|
+
def convert(self, path_to_mod_file: str,
|
73
|
+
path_to_python_file: str,
|
74
|
+
path_to_python_template: str,
|
75
|
+
path_to_json_file:str = None,
|
76
|
+
verbose: bool = False) -> None:
|
77
|
+
""" Converts a MOD file to a Python file.
|
78
|
+
|
79
|
+
Parameters
|
80
|
+
----------
|
81
|
+
path_to_mod : str
|
82
|
+
The path to the original MOD file.
|
83
|
+
path_to_python : str
|
84
|
+
The path to the output Python file.
|
85
|
+
path_to_template : str
|
86
|
+
The path to the jinja2 template file.
|
87
|
+
path_to_json : str, optional
|
88
|
+
The path to the json file to write the AST.
|
89
|
+
verbose : bool, optional
|
90
|
+
Whether to print the progress of the conversion.
|
91
|
+
"""
|
92
|
+
|
93
|
+
if verbose: print(f"READING")
|
94
|
+
self.reader.read_file(path_to_mod_file)
|
95
|
+
self.reader.preprocess()
|
96
|
+
blocks = self.reader.get_blocks(verbose)
|
97
|
+
|
98
|
+
if verbose: print(f"\nPARSING")
|
99
|
+
self.parser.parse(blocks, verbose)
|
100
|
+
self.parser.postprocess()
|
101
|
+
ast = self.parser.get_ast()
|
102
|
+
|
103
|
+
if path_to_json_file:
|
104
|
+
self.parser.write_file(path_to_json_file)
|
105
|
+
|
106
|
+
if verbose: print(f"\nGENERATING")
|
107
|
+
self.generator.generate(ast, path_to_python_template)
|
108
|
+
self.generator.write_file(path_to_python_file)
|