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,354 @@
|
|
1
|
+
"""
|
2
|
+
This file was automatically generated by the DendroTweaks toolbox (https://dendrotweaks.dendrites.gr/).
|
3
|
+
It provides a plain (Python) NEURON code representation of a biophysical model of a single neuron
|
4
|
+
built using DendroTweaks, which can be run independently of DendroTweaks.
|
5
|
+
|
6
|
+
Classes:
|
7
|
+
Cell: Represents a biophysical NEURON model of a single neuron with methods to:
|
8
|
+
- Load morphology from an SWC file.
|
9
|
+
- Distribute passive properties.
|
10
|
+
- Insert ion channel mechanisms.
|
11
|
+
- Distribute parameters across the neuron.
|
12
|
+
- Set the number of segments for each section.
|
13
|
+
- Add stimuli and recordings.
|
14
|
+
|
15
|
+
Functions:
|
16
|
+
load_mechanisms: Load NEURON mechanisms from a specified directory.
|
17
|
+
init_simulation: Initialize a NEURON simulation with specified parameters.
|
18
|
+
run: Run a NEURON simulation for a given duration.
|
19
|
+
get_domain: Retrieve the domain of a given segment.
|
20
|
+
linear: Compute a linear function based on distance.
|
21
|
+
sinusoidal: Compute a sinusoidal function based on distance.
|
22
|
+
exponential: Compute an exponential function based on distance.
|
23
|
+
sigmoid: Compute a sigmoid function based on distance.
|
24
|
+
gaussian: Compute a Gaussian function based on distance.
|
25
|
+
step: Compute a step function based on distance.
|
26
|
+
inherit: Inherit a parameter value from the parent segment.
|
27
|
+
"""
|
28
|
+
|
29
|
+
import os
|
30
|
+
|
31
|
+
import numpy as np
|
32
|
+
from numpy import polyval
|
33
|
+
|
34
|
+
import neuron
|
35
|
+
from neuron import h
|
36
|
+
h.load_file('stdrun.hoc')
|
37
|
+
|
38
|
+
|
39
|
+
class Cell():
|
40
|
+
"""
|
41
|
+
A class representing a biophysical NEURON model of a single neuron.
|
42
|
+
"""
|
43
|
+
|
44
|
+
def __init__(self, path_to_swc_file: str):
|
45
|
+
self.name = os.path.basename(path_to_swc_file).replace('.swc', '')
|
46
|
+
self.load_morphology(path_to_swc_file)
|
47
|
+
self.distribute_passive()
|
48
|
+
self.set_geom_nseg()
|
49
|
+
self.insert_mechanisms()
|
50
|
+
self.distribute_parameters()
|
51
|
+
|
52
|
+
@property
|
53
|
+
def all_segments(self):
|
54
|
+
return [seg for sec in self.all for seg in sec]
|
55
|
+
|
56
|
+
### Morphology methods ###
|
57
|
+
|
58
|
+
def load_morphology(self, path_to_swc_file: str) -> None:
|
59
|
+
if path_to_swc_file.endswith('.swc'):
|
60
|
+
self._load_swc(path_to_swc_file)
|
61
|
+
elif path_to_swc_file.endswith('.asc'):
|
62
|
+
self._load_asc(path_to_swc_file)
|
63
|
+
else:
|
64
|
+
raise ValueError(f"File type not supported: {path_to_swc_file}")
|
65
|
+
|
66
|
+
def _load_swc(self, path_to_swc_file: str) -> None:
|
67
|
+
h.load_file('import3d.hoc')
|
68
|
+
swc_importer = h.Import3d_SWC_read()
|
69
|
+
swc_importer.input(path_to_swc_file)
|
70
|
+
imported_cell = h.Import3d_GUI(swc_importer, False)
|
71
|
+
imported_cell.instantiate(self)
|
72
|
+
|
73
|
+
def _load_asc(self, path_to_asc_file: str) -> None:
|
74
|
+
h.load_file('import3d.hoc')
|
75
|
+
asc_importer = h.Import3d_Neurolucida3()
|
76
|
+
asc_importer.input(path_to_asc_file)
|
77
|
+
imported_cell = h.Import3d_GUI(asc_importer, False)
|
78
|
+
imported_cell.instantiate(self)
|
79
|
+
|
80
|
+
def distance(self, seg, from_seg=None):
|
81
|
+
if from_seg is None:
|
82
|
+
from_seg = self.soma[0](0.5)
|
83
|
+
return h.distance(from_seg, seg)
|
84
|
+
|
85
|
+
def domain_distance(self, seg):
|
86
|
+
parent = self._find_parent_with_different_domain(seg.sec)
|
87
|
+
if parent:
|
88
|
+
return h.distance(parent(1), seg)
|
89
|
+
return 0
|
90
|
+
|
91
|
+
def _find_parent_with_different_domain(self, sec):
|
92
|
+
parentseg = sec.parentseg()
|
93
|
+
if not parentseg:
|
94
|
+
return None
|
95
|
+
parent = parentseg.sec
|
96
|
+
while parent:
|
97
|
+
if get_domain(parent(0.5)) != get_domain(sec(0.5)):
|
98
|
+
return parent
|
99
|
+
parentseg = parent.parentseg()
|
100
|
+
if not parentseg:
|
101
|
+
return None
|
102
|
+
parent = parentseg.sec
|
103
|
+
return None
|
104
|
+
|
105
|
+
### Segmentation methods ###
|
106
|
+
|
107
|
+
def set_geom_nseg(self, d_lambda:float=0.1, f:float=100):
|
108
|
+
for sec in self.all:
|
109
|
+
sec.nseg = int((sec.L/(d_lambda*h.lambda_f(f, sec=sec)) + 0.9)/2)*2 + 1
|
110
|
+
|
111
|
+
### Mechanism methods ###
|
112
|
+
|
113
|
+
def insert_mechanisms(self):
|
114
|
+
{% for domain, mechanisms in domains_to_mechs.items() %}
|
115
|
+
for sec in self.{{ domains_to_NEURON[domain] }}:
|
116
|
+
{% for mechanism in mechanisms %}
|
117
|
+
sec.insert('{{ mechanism }}')
|
118
|
+
{%- endfor %}
|
119
|
+
{% endfor %}
|
120
|
+
|
121
|
+
### Parameter distribution methods ###
|
122
|
+
|
123
|
+
def set_param(self, seg, param: str, mech: str, value: float) -> None:
|
124
|
+
if param == 'Ra':
|
125
|
+
setattr(seg.sec, param, value)
|
126
|
+
if param == 'cm':
|
127
|
+
setattr(seg, param, value)
|
128
|
+
else:
|
129
|
+
if seg.sec.has_membrane(mech):
|
130
|
+
setattr(seg, param, value)
|
131
|
+
else:
|
132
|
+
if param in ['ena', 'ek', 'eca']:
|
133
|
+
if hasattr(seg, param):
|
134
|
+
setattr(seg, param, value)
|
135
|
+
|
136
|
+
def distribute_passive(self):
|
137
|
+
|
138
|
+
for seg in self.all_segments:
|
139
|
+
|
140
|
+
domain = get_domain(seg)
|
141
|
+
distance = self.distance(seg)
|
142
|
+
domain_distance = self.domain_distance(seg)
|
143
|
+
diam = seg.diam
|
144
|
+
section_diam = seg.sec.diam
|
145
|
+
|
146
|
+
{% for param, mech in params_to_mechs.items() -%}
|
147
|
+
{% if param in ['cm', 'Ra']%}
|
148
|
+
{% set groups = param_dict[param] -%}
|
149
|
+
{% for group_name, distribution in groups.items() -%}
|
150
|
+
{% set group = groups_dict[group_name] -%}
|
151
|
+
if domain in {{ params_to_valid_domains[param][group_name] }}:
|
152
|
+
{% if group.select_by -%}
|
153
|
+
{% set min_val = group.min_value if group.min_value is not none else 0 -%}
|
154
|
+
{% if group.max_value is not none -%}
|
155
|
+
if {{ min_val }} < {{ group.select_by }} <= {{ group.max_value }}:
|
156
|
+
{% else -%}
|
157
|
+
if {{ min_val }} < {{ group.select_by }}:
|
158
|
+
{% endif %}
|
159
|
+
{% if distribution.function_name == "constant" -%}
|
160
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.parameters.value }})
|
161
|
+
{% elif distribution.function_name == "linear" -%}
|
162
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", linear(distance, slope={{ distribution.parameters.slope }}, intercept={{ distribution.parameters.intercept }}))
|
163
|
+
{% elif distribution.function_name == "polynomial" -%}
|
164
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", polyval([{% for coeff in distribution.parameters.coeffs %}{{ coeff }}{% if not loop.last %}, {% endif %}{% endfor %}], distance))
|
165
|
+
{% else -%}
|
166
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.function_name }}(distance, **{{ distribution.parameters }}))
|
167
|
+
{% endif %}
|
168
|
+
{% else -%}
|
169
|
+
{% if distribution.function_name == "constant" -%}
|
170
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.parameters.value }})
|
171
|
+
{% elif distribution.function_name == "linear" -%}
|
172
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", linear(distance, slope={{ distribution.parameters.slope }}, intercept={{ distribution.parameters.intercept }}))
|
173
|
+
{% elif distribution.function_name == "polynomial" -%}
|
174
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", polyval([{% for coeff in distribution.parameters.coeffs %}{{ coeff }}{% if not loop.last %}, {% endif %}{% endfor %}], distance))
|
175
|
+
{% else -%}
|
176
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.function_name }}(distance, **{{ distribution.parameters }}))
|
177
|
+
{% endif %}
|
178
|
+
{% endif %}
|
179
|
+
{% endfor -%}
|
180
|
+
{% endif -%}
|
181
|
+
{% endfor %}
|
182
|
+
|
183
|
+
def distribute_parameters(self):
|
184
|
+
|
185
|
+
for seg in self.all_segments:
|
186
|
+
|
187
|
+
domain = get_domain(seg)
|
188
|
+
distance = self.distance(seg)
|
189
|
+
domain_distance = self.domain_distance(seg)
|
190
|
+
diam = seg.diam
|
191
|
+
section_diam = seg.sec.diam
|
192
|
+
|
193
|
+
{% for param, mech in params_to_mechs.items() -%}
|
194
|
+
{% if param not in ['cm', 'Ra']%}
|
195
|
+
{% set groups = param_dict[param] -%}
|
196
|
+
{% for group_name, distribution in groups.items() -%}
|
197
|
+
{% set group = groups_dict[group_name] -%}
|
198
|
+
if domain in {{ params_to_valid_domains[param][group_name] }}:
|
199
|
+
{% if group.select_by -%}
|
200
|
+
{% set min_val = group.min_value if group.min_value is not none else 0 -%}
|
201
|
+
{% if group.max_value is not none -%}
|
202
|
+
if {{ min_val }} < {{ group.select_by }} <= {{ group.max_value }}:
|
203
|
+
{% else -%}
|
204
|
+
if {{ min_val }} < {{ group.select_by }}:
|
205
|
+
{% endif %}
|
206
|
+
{% if distribution.function_name == "constant" -%}
|
207
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.parameters.value }})
|
208
|
+
{% elif distribution.function_name == "linear" -%}
|
209
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", linear(distance, slope={{ distribution.parameters.slope }}, intercept={{ distribution.parameters.intercept }}))
|
210
|
+
{% elif distribution.function_name == "polynomial" -%}
|
211
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", polyval([{% for coeff in distribution.parameters.coeffs %}{{ coeff }}{% if not loop.last %}, {% endif %}{% endfor %}], distance))
|
212
|
+
{% else -%}
|
213
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.function_name }}(distance, **{{ distribution.parameters }}))
|
214
|
+
{% endif %}
|
215
|
+
{% else -%}
|
216
|
+
{% if distribution.function_name == "constant" -%}
|
217
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.parameters.value }})
|
218
|
+
{% elif distribution.function_name == "linear" -%}
|
219
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", linear(distance, slope={{ distribution.parameters.slope }}, intercept={{ distribution.parameters.intercept }}))
|
220
|
+
{% elif distribution.function_name == "polynomial" -%}
|
221
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", polyval([{% for coeff in distribution.parameters.coeffs %}{{ coeff }}{% if not loop.last %}, {% endif %}{% endfor %}], distance))
|
222
|
+
{% else -%}
|
223
|
+
self.set_param(seg, "{{ param }}", "{{ mech }}", {{ distribution.function_name }}(distance, **{{ distribution.parameters }}))
|
224
|
+
{% endif %}
|
225
|
+
{% endif %}
|
226
|
+
{% endfor -%}
|
227
|
+
{% endif -%}
|
228
|
+
{% endfor %}
|
229
|
+
|
230
|
+
### Stimulation methods ###
|
231
|
+
|
232
|
+
def add_stimuli(self):
|
233
|
+
self.add_iclamps()
|
234
|
+
self.add_synapses()
|
235
|
+
|
236
|
+
def add_recordings(self):
|
237
|
+
recordings = []
|
238
|
+
{% for seg, rec in recordings.items() %}
|
239
|
+
rec = h.Vector()
|
240
|
+
rec.record(self.{{seg._section.domain}}[{{seg._section.domain_idx}}]({{seg.x}})._ref_v)
|
241
|
+
recordings.append(rec)
|
242
|
+
{% endfor %}
|
243
|
+
return recordings
|
244
|
+
|
245
|
+
def add_iclamps(self):
|
246
|
+
iclamps = []
|
247
|
+
{% for seg, iclamp in iclamps.items() %}
|
248
|
+
iclamp = h.IClamp(self.{{seg._section.domain}}[{{seg._section.domain_idx}}]({{seg.x}}))
|
249
|
+
iclamp.delay = {{ iclamp.delay }}
|
250
|
+
iclamp.dur = {{ iclamp.dur }}
|
251
|
+
iclamp.amp = {{ iclamp.amp }}
|
252
|
+
iclamps.append(iclamp)
|
253
|
+
{% endfor %}
|
254
|
+
return iclamps
|
255
|
+
|
256
|
+
|
257
|
+
# =================================================================================================
|
258
|
+
# SIMULATION FUNCTIONS
|
259
|
+
# =================================================================================================
|
260
|
+
|
261
|
+
def load_mechanisms(path_to_mod: str, recompile:bool=False) -> None:
|
262
|
+
"""
|
263
|
+
Load NEURON mechanisms from a directory.
|
264
|
+
|
265
|
+
Parameters
|
266
|
+
----------
|
267
|
+
path_to_mod : str
|
268
|
+
The path to the directory containing the mod files.
|
269
|
+
recompile : bool
|
270
|
+
Whether to recompile the mod files before loading
|
271
|
+
"""
|
272
|
+
|
273
|
+
if recompile:
|
274
|
+
cwd = os.getcwd()
|
275
|
+
os.chdir(path_to_mod)
|
276
|
+
os.system('nrnivmodl')
|
277
|
+
os.chdir(cwd)
|
278
|
+
print(f'Compiled mod files in "{path_to_mod}"')
|
279
|
+
|
280
|
+
neuron.load_mechanisms(path_to_mod)
|
281
|
+
print(f'Loaded mod files from "{path_to_mod}"')
|
282
|
+
|
283
|
+
def init_simulation(temperature:float=37, dt:float=0.025, v_init:float=-65, cvode:bool=False) -> None:
|
284
|
+
"""
|
285
|
+
Initialize a NEURON simulation.
|
286
|
+
|
287
|
+
Parameters
|
288
|
+
----------
|
289
|
+
temperature : float
|
290
|
+
The temperature in degrees Celsius.
|
291
|
+
dt : float
|
292
|
+
The time step of the simulation in ms.
|
293
|
+
v_init : float
|
294
|
+
The initial membrane potential in mV.
|
295
|
+
cvode : bool
|
296
|
+
Whether to use the CVode variable time step integrator.
|
297
|
+
"""
|
298
|
+
h.CVode().active(cvode)
|
299
|
+
h.celsius = temperature
|
300
|
+
h.dt = dt
|
301
|
+
h.stdinit()
|
302
|
+
h.init()
|
303
|
+
h.finitialize(v_init)
|
304
|
+
if h.cvode.active():
|
305
|
+
h.cvode.re_init()
|
306
|
+
else:
|
307
|
+
h.fcurrent()
|
308
|
+
h.frecord_init()
|
309
|
+
|
310
|
+
def run(duration:float=300, **kwargs) -> None:
|
311
|
+
"""
|
312
|
+
Run a NEURON simulation.
|
313
|
+
|
314
|
+
Parameters
|
315
|
+
----------
|
316
|
+
duration : float
|
317
|
+
The duration of the simulation in ms.
|
318
|
+
"""
|
319
|
+
init_simulation(**kwargs)
|
320
|
+
h.continuerun(duration)
|
321
|
+
|
322
|
+
# =================================================================================================
|
323
|
+
# HELPER FUNCTIONS
|
324
|
+
# =================================================================================================
|
325
|
+
|
326
|
+
def get_domain(seg) -> str:
|
327
|
+
sec = seg.sec
|
328
|
+
sec_name = sec.name()
|
329
|
+
domain = sec_name.split('.')[-1].split('[')[0]
|
330
|
+
return domain
|
331
|
+
|
332
|
+
def linear(distance: float, slope:float = 0, intercept: float = 0) -> float:
|
333
|
+
return slope * distance + intercept
|
334
|
+
|
335
|
+
def sinusoidal(distance: float, amplitude: float = 0, frequency: float = 1, phase: float = 0) -> float:
|
336
|
+
return amplitude * np.sin(frequency * distance + phase)
|
337
|
+
|
338
|
+
def exponential(distance: float, vertical_shift:float = 0, scale_factor: float =1, growth_rate: float=1, horizontal_shift: float = 0) -> float:
|
339
|
+
return vertical_shift + scale_factor * np.exp(growth_rate * (distance - horizontal_shift))
|
340
|
+
|
341
|
+
def sigmoid(distance: float, vertical_shift: float=0, scale_factor: float=1, growth_rate: float=1, horizontal_shift: float=0) -> float:
|
342
|
+
return vertical_shift + scale_factor / (1 + np.exp(-growth_rate*(distance - horizontal_shift)))
|
343
|
+
|
344
|
+
def gaussian(distance: float, amplitude: float, mean: float, std: float) -> float:
|
345
|
+
return amplitude * np.exp(-((distance - mean) ** 2) / (2 * std ** 2))
|
346
|
+
|
347
|
+
def step(distance: float, max_value: float, min_value: float, start: float, end: float) -> float:
|
348
|
+
if start < distance < end:
|
349
|
+
return max_value
|
350
|
+
else:
|
351
|
+
return min_value
|
352
|
+
|
353
|
+
def inherit(seg, param: str) -> float:
|
354
|
+
return getattr(seg.sec.parentseg(), param, np.nan)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# This Python channel class was automatically generated from a MOD file
|
2
|
+
# using DendroTweaks toolbox, dendrotweaks.dendrites.gr
|
3
|
+
|
4
|
+
import sys
|
5
|
+
|
6
|
+
from dendrotweaks.membrane.mechanisms import IonChannel
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
class {{ class_name }}(IonChannel):
|
10
|
+
"""
|
11
|
+
{{ title }}
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, name="{{ class_name }}"):
|
15
|
+
super().__init__(name=name)
|
16
|
+
self.params = {
|
17
|
+
{% for param, value in channel_params.items() -%}
|
18
|
+
"{{ param }}": {{ value }}
|
19
|
+
{%- if not loop.last -%},
|
20
|
+
{%- endif %}
|
21
|
+
{% endfor -%}
|
22
|
+
}
|
23
|
+
self.range_params = {
|
24
|
+
{% for param, value in range_params.items() -%}
|
25
|
+
"{{ param }}": {{ value }}
|
26
|
+
{%- if not loop.last -%},
|
27
|
+
{%- endif %}
|
28
|
+
{% endfor -%}
|
29
|
+
}
|
30
|
+
self.states = {
|
31
|
+
{% for state in state_vars -%}
|
32
|
+
"{{ state }}": 0.0
|
33
|
+
{%- if not loop.last %},
|
34
|
+
{%- endif %}
|
35
|
+
{% endfor -%}
|
36
|
+
}
|
37
|
+
self._state_powers = {
|
38
|
+
{% for state, power in state_vars.items() -%}
|
39
|
+
"{{ state }}": {{ power }}
|
40
|
+
{%- if not loop.last %},
|
41
|
+
{%- endif %}
|
42
|
+
{% endfor -%}
|
43
|
+
}
|
44
|
+
self.ion = "{{ ion }}"
|
45
|
+
self.current_name = "i_{{ ion }}"
|
46
|
+
self.independent_var_name = "{{ independent_var_name }}"
|
47
|
+
self.temperature = 37
|
48
|
+
|
49
|
+
def __getitem__(self, item):
|
50
|
+
return self.params[item]
|
51
|
+
|
52
|
+
def __setitem__(self, item, value):
|
53
|
+
self.params[item] = value
|
54
|
+
|
55
|
+
{% for procedure in procedures %}
|
56
|
+
{{ procedure['signature'] }}
|
57
|
+
{% for param in procedure['params'] -%}
|
58
|
+
{{ param }} = self.params["{{ param }}"]
|
59
|
+
{% endfor %}
|
60
|
+
{{ procedure['body'] }}
|
61
|
+
{%- if not loop.last %}
|
62
|
+
{% endif %}{% endfor %}
|
63
|
+
|
64
|
+
{% for function in functions %}
|
65
|
+
{{ function['signature'] }}
|
66
|
+
{% for param in function['params'] -%}
|
67
|
+
{{ param }} = self.params["{{ param }}"]
|
68
|
+
{% endfor %}
|
69
|
+
{{ function['body'] }}
|
70
|
+
{%- if not loop.last %}
|
71
|
+
{% endif %}{% endfor -%}
|
72
|
+
|
73
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
TITLE standardized {{ suffix }} channel
|
2
|
+
|
3
|
+
COMMENT
|
4
|
+
Standardized and templated by DendroTweaks.
|
5
|
+
This NMODL file defines a model for a {{ ion }} ion channel.
|
6
|
+
ENDCOMMENT
|
7
|
+
|
8
|
+
NEURON {
|
9
|
+
SUFFIX {{ suffix }}
|
10
|
+
{% if ion is none %}
|
11
|
+
NONSPECIFIC_CURRENT i
|
12
|
+
{% else %}
|
13
|
+
USEION {{ ion }} READ e{{ ion }} WRITE i{{ ion }}
|
14
|
+
{% endif %}
|
15
|
+
RANGE gbar, i{% for param, _, _ in range_params %}, {{ param }}{% endfor %}
|
16
|
+
}
|
17
|
+
|
18
|
+
UNITS {
|
19
|
+
(mA) = (milliamp)
|
20
|
+
(mV) = (millivolt)
|
21
|
+
(S) = (siemens)
|
22
|
+
(um) = (micron)
|
23
|
+
}
|
24
|
+
|
25
|
+
PARAMETER {
|
26
|
+
{% for key, value, unit in range_params %}{{ "%-7s"|format(key) }} = {{ value }} ({{ unit }}){% if not loop.last %}
|
27
|
+
{% endif %}{% endfor %}
|
28
|
+
}
|
29
|
+
|
30
|
+
ASSIGNED {
|
31
|
+
v (mV) : membrane voltage
|
32
|
+
i (mA/cm2) : current density
|
33
|
+
i{{ "%-7s"|format(ion) }} (mA/cm2) : current density of {{ ion }} ion
|
34
|
+
g{{ "%-7s"|format(ion) }} (S/cm2) : conductance of {{ ion }} ion
|
35
|
+
e{{ "%-7s"|format(ion) }} (mV) : reversal potential of {{ ion }} ion
|
36
|
+
{% for state in state_vars %}
|
37
|
+
{{ state }}_inf (1) : steady state value of {{ state }}
|
38
|
+
tau_{{ state }} (ms) : time constant of {{ state }}
|
39
|
+
{% endfor %}
|
40
|
+
tadj (1) : temperature adjustment factor
|
41
|
+
celsius (degC) : simulation temperature in celsius
|
42
|
+
}
|
43
|
+
|
44
|
+
STATE { {% for variable in state_vars %}{{ variable }}{% if not loop.last %} {% endif %}{% endfor %} }
|
45
|
+
|
46
|
+
BREAKPOINT {
|
47
|
+
SOLVE states METHOD cnexp
|
48
|
+
g{{ ion }} = tadj * gbar{% for state, power in state_vars.items() %} * {% if power > 1 %}pow({{ state }}, {{ power }}){% else %}{{ state }}{% endif %}{% endfor %}
|
49
|
+
i = g{{ ion }} * (v - e{{ ion }})
|
50
|
+
i{{ ion }} = i
|
51
|
+
}
|
52
|
+
|
53
|
+
DERIVATIVE states {
|
54
|
+
rates(v)
|
55
|
+
{% for state in state_vars %}{{ state }}' = ({{ state }}_inf - {{ state }}) / tau_{{ state }}
|
56
|
+
{% endfor %}
|
57
|
+
}
|
58
|
+
|
59
|
+
INITIAL {
|
60
|
+
tadj = q10^((celsius - temp)/10(degC))
|
61
|
+
rates(v)
|
62
|
+
{% for state in state_vars %}{{ state }} = {{ state }}_inf
|
63
|
+
{% endfor %}
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
FUNCTION alpha_prime(v (mV), k (1/ms), delta (1), vhalf (mV), sigma (mV)) (1/ms) {
|
68
|
+
alpha_prime = k * exp(delta * (v - vhalf) / sigma)
|
69
|
+
}
|
70
|
+
|
71
|
+
FUNCTION beta_prime(v (mV), k (1/ms), delta (1), vhalf (mV), sigma (mV)) (1/ms) {
|
72
|
+
beta_prime = k * exp(-(1 - delta) * (v - vhalf) / sigma)
|
73
|
+
}
|
74
|
+
|
75
|
+
PROCEDURE rates(v(mV)) {
|
76
|
+
LOCAL {% for state in state_vars %}alpha_{{ state }}, beta_{{ state }}{% if not loop.last %}, {% endif %}{% endfor %}
|
77
|
+
|
78
|
+
{% for state in state_vars %}
|
79
|
+
{{ state }}_inf = 1 / (1 + exp(-(v - vhalf_{{ state }}) / sigma_{{ state }}))
|
80
|
+
alpha_{{ state }} = alpha_prime(v, k_{{ state }}, delta_{{ state }}, vhalf_{{ state }}, sigma_{{ state }})
|
81
|
+
beta_{{ state }} = beta_prime(v, k_{{ state }}, delta_{{ state }}, vhalf_{{ state }}, sigma_{{ state }})
|
82
|
+
tau_{{ state }} = (1 / (alpha_{{ state }} + beta_{{ state }}) + tau0_{{ state }}) / tadj
|
83
|
+
{% endfor %}
|
84
|
+
|
85
|
+
}
|
86
|
+
|
87
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# This Python channel class was automatically generated from a MOD file
|
2
|
+
# using DendroTweaks toolbox, dendrotweaks.dendrites.gr
|
3
|
+
|
4
|
+
from jaxley.channels import Channel
|
5
|
+
from jaxley.solver_gate import exponential_euler
|
6
|
+
import jax.numpy as jn
|
7
|
+
|
8
|
+
class {{ class_name }}(Channel):
|
9
|
+
"""
|
10
|
+
{{ title }}
|
11
|
+
"""
|
12
|
+
|
13
|
+
def __init__(self, name="{{ class_name }}"):
|
14
|
+
super().__init__(name=name)
|
15
|
+
self.channel_params = {
|
16
|
+
{% for param, value in channel_params.items() -%}
|
17
|
+
"{{ class_name }}_{{ param }}": {{ value }}
|
18
|
+
{%- if not loop.last -%},
|
19
|
+
{%- endif %}
|
20
|
+
{% endfor -%}
|
21
|
+
}
|
22
|
+
self.channel_states = {
|
23
|
+
{% for state in state_vars -%}
|
24
|
+
"{{class_name}}_{{ state }}": 0.0
|
25
|
+
{%- if not loop.last %},
|
26
|
+
{%- endif %}
|
27
|
+
{% endfor -%}
|
28
|
+
}
|
29
|
+
self._state_powers = {
|
30
|
+
{% for state, power in state_vars.items() -%}
|
31
|
+
"{{class_name}}_{{ state }}": {{ power }}
|
32
|
+
{%- if not loop.last %},
|
33
|
+
{%- endif %}
|
34
|
+
{% endfor -%}
|
35
|
+
}
|
36
|
+
self.ion = "{{ ion }}"
|
37
|
+
self.current_name = "i_{{ ion }}"
|
38
|
+
|
39
|
+
self.independent_var_name = "{{ independent_var_name }}"
|
40
|
+
|
41
|
+
# @property
|
42
|
+
# def tadj(self):
|
43
|
+
# return self.tadj = q10 ** ((celsius - temp) / 10)
|
44
|
+
|
45
|
+
def __getitem__(self, item):
|
46
|
+
return self.channel_params[item]
|
47
|
+
|
48
|
+
def __setitem__(self, item, value):
|
49
|
+
self.channel_params[item] = value
|
50
|
+
|
51
|
+
{% for function in functions %}
|
52
|
+
{{ function['signature'] }}
|
53
|
+
{%- for param in function['params'] -%}
|
54
|
+
{{ param }} = self.channel_params.get("{{ class_name }}_{{ param }}", 1)
|
55
|
+
{% endfor %}
|
56
|
+
{{ function['body'] }}
|
57
|
+
{% if not loop.last %}
|
58
|
+
{% endif %}{% endfor -%}
|
59
|
+
{% for procedure in procedures %}
|
60
|
+
{{ procedure['signature'] }}
|
61
|
+
{% for param in procedure['params'] -%}
|
62
|
+
{{ param }} = self.channel_params.get("{{ class_name }}_{{ param }}", 1)
|
63
|
+
{% endfor %}
|
64
|
+
{{ procedure['body'] }}
|
65
|
+
{%- if not loop.last %}
|
66
|
+
{% endif %}{% endfor %}
|
67
|
+
|
68
|
+
def update_states(self, states, dt, v, params):
|
69
|
+
{% for state, state_params in state_vars.items() -%}
|
70
|
+
{{state}} = states['{{class_name}}_{{state}}']
|
71
|
+
{%- if not loop.last %}
|
72
|
+
{%- endif %}
|
73
|
+
{% endfor -%}
|
74
|
+
{{- procedure_calls}}
|
75
|
+
{% for state in state_vars.keys() %}new_{{state}} = exponential_euler({{state}}, dt, {{state}}Inf, {{state}}Tau){% if not loop.last %}
|
76
|
+
{% endif %}{% endfor %}
|
77
|
+
return {
|
78
|
+
{% for state in state_vars -%}
|
79
|
+
"{{class_name}}_{{state}}": new_{{state}}
|
80
|
+
{%- if not loop.last %},
|
81
|
+
{%- endif %}
|
82
|
+
{% endfor -%}
|
83
|
+
}
|
84
|
+
|
85
|
+
def compute_current(self, states, v, params):
|
86
|
+
{% for state in state_vars.keys() -%}
|
87
|
+
{{state}} = states['{{class_name}}_{{state}}']
|
88
|
+
{%- if not loop.last %}
|
89
|
+
{%- endif %}
|
90
|
+
{% endfor -%}
|
91
|
+
gbar = params["{{class_name}}_gbar"]
|
92
|
+
# E = params["E_{{ ion }}"]
|
93
|
+
E = {{ E_ion }}
|
94
|
+
{{ procedure_calls}}
|
95
|
+
g = self.tadj * gbar *{% for state, power in state_vars.items()%} {{state}}**{{power}} {% if not loop.last %}*{% endif %}{% endfor %}* 1000
|
96
|
+
return g * (v - E)
|
97
|
+
|
98
|
+
def init_state(self, states, v, params, delta_t):
|
99
|
+
{{ procedure_calls}}
|
100
|
+
return {
|
101
|
+
{% for state in state_vars.keys() -%}
|
102
|
+
"{{class_name}}_{{state}}": {{state}}Inf
|
103
|
+
{%- if not loop.last %},
|
104
|
+
{%- endif %}
|
105
|
+
{% endfor -%}
|
106
|
+
}
|
107
|
+
|
108
|
+
|