dendrotweaks 0.4.4__py3-none-any.whl → 0.4.6__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 +1 -1
- dendrotweaks/analysis/__init__.py +2 -1
- dendrotweaks/analysis/ephys_analysis.py +140 -62
- dendrotweaks/biophys/default_mod/vecstim.mod +1 -11
- dendrotweaks/biophys/default_templates/jaxley.py +131 -0
- dendrotweaks/biophys/distributions.py +3 -3
- dendrotweaks/biophys/io/converter.py +4 -0
- dendrotweaks/biophys/mechanisms.py +11 -1
- dendrotweaks/model.py +151 -1088
- dendrotweaks/model_io.py +736 -39
- dendrotweaks/model_simulation.py +326 -0
- dendrotweaks/morphology/io/factories.py +2 -2
- dendrotweaks/morphology/io/reader.py +12 -3
- dendrotweaks/morphology/point_trees.py +1 -1
- dendrotweaks/path_manager.py +2 -2
- dendrotweaks/prerun.py +63 -0
- dendrotweaks/utils.py +148 -40
- {dendrotweaks-0.4.4.dist-info → dendrotweaks-0.4.6.dist-info}/METADATA +1 -1
- {dendrotweaks-0.4.4.dist-info → dendrotweaks-0.4.6.dist-info}/RECORD +22 -19
- {dendrotweaks-0.4.4.dist-info → dendrotweaks-0.4.6.dist-info}/WHEEL +0 -0
- {dendrotweaks-0.4.4.dist-info → dendrotweaks-0.4.6.dist-info}/licenses/LICENSE +0 -0
- {dendrotweaks-0.4.4.dist-info → dendrotweaks-0.4.6.dist-info}/top_level.txt +0 -0
dendrotweaks/__init__.py
CHANGED
@@ -8,4 +8,5 @@ from dendrotweaks.analysis.ephys_analysis import plot_passive_properties
|
|
8
8
|
from dendrotweaks.analysis.ephys_analysis import calculate_voltage_attenuation
|
9
9
|
from dendrotweaks.analysis.ephys_analysis import plot_voltage_attenuation
|
10
10
|
from dendrotweaks.analysis.ephys_analysis import calculate_dendritic_nonlinearity
|
11
|
-
from dendrotweaks.analysis.ephys_analysis import plot_dendritic_nonlinearity
|
11
|
+
from dendrotweaks.analysis.ephys_analysis import plot_dendritic_nonlinearity
|
12
|
+
from dendrotweaks.analysis.ephys_analysis import calculate_sag_ratio
|
@@ -38,6 +38,11 @@ def get_somatic_data(model):
|
|
38
38
|
def calculate_input_resistance(model):
|
39
39
|
"""
|
40
40
|
Calculate the input resistance of the neuron model.
|
41
|
+
|
42
|
+
This function determines the input resistance by calculating the ratio
|
43
|
+
between the voltage change and the injected current. The voltage change
|
44
|
+
is measured as the difference between the membrane potential at the onset
|
45
|
+
and offset of the current injection.
|
41
46
|
|
42
47
|
Parameters
|
43
48
|
----------
|
@@ -52,13 +57,11 @@ def calculate_input_resistance(model):
|
|
52
57
|
|
53
58
|
v, t, dt, iclamp = get_somatic_data(model)
|
54
59
|
|
55
|
-
v_min = np.min(v)
|
56
|
-
|
57
60
|
amp = iclamp.amp
|
58
|
-
start_ts = iclamp.delay / dt
|
59
|
-
|
61
|
+
start_ts = int(iclamp.delay / dt)
|
62
|
+
stop_ts = int((iclamp.delay + iclamp.dur) / dt)
|
60
63
|
v_onset = v[int(start_ts)]
|
61
|
-
v_offset = v[int(
|
64
|
+
v_offset = v[int(stop_ts)]
|
62
65
|
|
63
66
|
R_in = (v_onset - v_offset) / amp
|
64
67
|
print(f"Input resistance: {R_in:.2f} MOhm")
|
@@ -71,44 +74,61 @@ def calculate_input_resistance(model):
|
|
71
74
|
}
|
72
75
|
|
73
76
|
|
74
|
-
def
|
75
|
-
return
|
76
|
-
|
77
|
+
def _double_exp_decay(t, A1, tau1, A2, tau2):
|
78
|
+
return A1 * np.exp(-t / tau1) + A2 * np.exp(-t / tau2)
|
77
79
|
|
78
80
|
def calculate_time_constant(model):
|
79
81
|
"""
|
80
|
-
|
82
|
+
Estimate the passive membrane time constant (τm) by fitting
|
83
|
+
a double exponential to the somatic voltage decay and selecting
|
84
|
+
the slowest τ component.
|
81
85
|
|
82
86
|
Parameters
|
83
87
|
----------
|
84
88
|
model : Model
|
85
|
-
The neuron model.
|
89
|
+
The neuron model (assumes Ih is disabled).
|
86
90
|
|
87
91
|
Returns
|
88
92
|
-------
|
89
93
|
dict
|
90
|
-
A dictionary
|
94
|
+
A dictionary with τm (slowest), both τs, and fit details.
|
91
95
|
"""
|
92
96
|
v, t, dt, iclamp = get_somatic_data(model)
|
93
97
|
|
94
98
|
start_ts = int(iclamp.delay / dt)
|
95
99
|
stop_ts = int((iclamp.delay + iclamp.dur) / dt)
|
96
100
|
min_ts = np.argmin(v[start_ts:stop_ts]) + start_ts
|
97
|
-
v_min =
|
101
|
+
v_min = v[min_ts]
|
98
102
|
v_decay = v[start_ts: min_ts] - v_min
|
99
103
|
t_decay = t[start_ts: min_ts] - t[start_ts]
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
+
|
105
|
+
# Fit double exponential
|
106
|
+
try:
|
107
|
+
popt, _ = curve_fit(
|
108
|
+
_double_exp_decay, t_decay, v_decay,
|
109
|
+
p0=[1, 10, 0.5, 100],
|
110
|
+
bounds=(0, [np.inf, 1000, np.inf, 1000])
|
111
|
+
)
|
112
|
+
A1, tau1, A2, tau2 = popt
|
113
|
+
tau_slowest = max(tau1, tau2)
|
114
|
+
except RuntimeError:
|
115
|
+
print("Fit failed. Could not estimate time constant.")
|
116
|
+
return None
|
117
|
+
|
118
|
+
print(f"Time constant {tau_slowest:.2f} ms. Estimated from double exp fit (slowest component)")
|
119
|
+
|
104
120
|
return {
|
105
|
-
'time_constant':
|
106
|
-
'A': A,
|
121
|
+
'time_constant': tau_slowest,
|
107
122
|
'start_time': start_ts * dt,
|
123
|
+
'tau1': tau1,
|
124
|
+
'tau2': tau2,
|
125
|
+
'A1': A1,
|
126
|
+
'A2': A2,
|
108
127
|
'decay_time': t_decay,
|
109
128
|
'decay_voltage': v_decay
|
110
129
|
}
|
111
130
|
|
131
|
+
|
112
132
|
def calculate_passive_properties(model):
|
113
133
|
"""
|
114
134
|
Calculate the passive properties of the neuron model.
|
@@ -140,7 +160,6 @@ def plot_passive_properties(data, ax=None):
|
|
140
160
|
v_offset = data['offset_voltage']
|
141
161
|
t_decay = data['decay_time']
|
142
162
|
v_decay = data['decay_voltage']
|
143
|
-
A = data['A']
|
144
163
|
start_t = data['start_time']
|
145
164
|
|
146
165
|
ax.set_title(f"R_in: {R_in:.2f} MOhm, Tau: {tau:.2f} ms")
|
@@ -148,8 +167,10 @@ def plot_passive_properties(data, ax=None):
|
|
148
167
|
ax.axhline(v_offset, color='gray', linestyle='--', label='V offset')
|
149
168
|
|
150
169
|
# Shift the exp_decay output along the y-axis
|
151
|
-
|
152
|
-
|
170
|
+
fit_curve = _double_exp_decay(t_decay, data['A1'], data['tau1'], data['A2'], data['tau2']) + v_offset
|
171
|
+
label = f'Double exp fit (tau1 = {data["tau1"]:.1f} ms, tau2 = {data["tau2"]:.1f} ms)'
|
172
|
+
|
173
|
+
ax.plot(t_decay + start_t, fit_curve, color='red', label='Exp. fit', linestyle='--')
|
153
174
|
ax.legend()
|
154
175
|
|
155
176
|
|
@@ -236,7 +257,7 @@ def plot_somatic_spikes(data, ax=None, show_metrics=False):
|
|
236
257
|
ax.plot([t - 10*w/2, t + 10*w/2], [v - a/2, v - a/2], color='lawngreen', linestyle='--')
|
237
258
|
|
238
259
|
|
239
|
-
def calculate_fI_curve(model, duration=1000, min_amp=0, max_amp=1, n=5, **kwargs):
|
260
|
+
def calculate_fI_curve(model, duration=1000, prerun_time=0, min_amp=0, max_amp=1, n=5, **kwargs):
|
240
261
|
"""
|
241
262
|
Calculate the frequency-current (f-I) curve of the neuron model.
|
242
263
|
|
@@ -268,7 +289,7 @@ def calculate_fI_curve(model, duration=1000, min_amp=0, max_amp=1, n=5, **kwargs
|
|
268
289
|
vs = {}
|
269
290
|
for amp in amps:
|
270
291
|
iclamp.amp = amp
|
271
|
-
model.
|
292
|
+
model.run(duration=duration, prerun_time=prerun_time)
|
272
293
|
spike_data = detect_somatic_spikes(model, **kwargs)
|
273
294
|
n_spikes = len(spike_data['spike_times'])
|
274
295
|
rate = n_spikes / iclamp.dur * 1000
|
@@ -283,7 +304,22 @@ def calculate_fI_curve(model, duration=1000, min_amp=0, max_amp=1, n=5, **kwargs
|
|
283
304
|
}
|
284
305
|
|
285
306
|
|
286
|
-
def plot_fI_curve(data, ax=None, **kwargs):
|
307
|
+
def plot_fI_curve(data, ax=None, vshift=200, **kwargs):
|
308
|
+
"""
|
309
|
+
Plot the f-I curve and somatic voltage traces.
|
310
|
+
|
311
|
+
Parameters
|
312
|
+
----------
|
313
|
+
data : dict
|
314
|
+
A dictionary containing the current amplitudes, firing rates, and voltages.
|
315
|
+
Can be obtained from `calculate_fI_curve`.
|
316
|
+
ax : matplotlib.axes.Axes, optional
|
317
|
+
The axes to plot on. If two axes are provided, the first will show the somatic voltage traces and the second will show the f-I curve.
|
318
|
+
If a single axis is provided, it will show the f-I curve only.
|
319
|
+
If None, a new figure will be created.
|
320
|
+
vshift : int, optional
|
321
|
+
The vertical shift for the somatic voltage traces. Default is 200.
|
322
|
+
"""
|
287
323
|
|
288
324
|
if ax is None:
|
289
325
|
_, ax = plt.subplots(1, 2, figsize=(5, 5))
|
@@ -293,25 +329,20 @@ def plot_fI_curve(data, ax=None, **kwargs):
|
|
293
329
|
vs = data['voltages']
|
294
330
|
t = data['time']
|
295
331
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
ax[0].spines['right'].set_visible(False)
|
304
|
-
ax[0].spines['bottom'].set_visible(False)
|
305
|
-
ax[0].spines['left'].set_visible(False)
|
306
|
-
ax[0].set_xticks([])
|
307
|
-
ax[0].set_yticks([])
|
332
|
+
if isinstance(ax, (list, np.ndarray)):
|
333
|
+
for i, (amp, v) in enumerate(vs.items()):
|
334
|
+
ax[0].plot(t, np.array(v) - i*vshift, label=f'{amp} nA')
|
335
|
+
ax[0].set_title('Somatic spikes')
|
336
|
+
ax[0].legend()
|
337
|
+
ax[0].axis('off')
|
338
|
+
ax = ax[1]
|
308
339
|
|
309
|
-
ax
|
340
|
+
ax.plot(amps, rates, color='gray', zorder=0)
|
310
341
|
for a, r in zip(amps, rates):
|
311
|
-
ax
|
312
|
-
ax
|
313
|
-
ax
|
314
|
-
ax
|
342
|
+
ax.plot(a, r, 'o', zorder=1)
|
343
|
+
ax.set_xlabel('Current (nA)')
|
344
|
+
ax.set_ylabel('Firing rate (Hz)')
|
345
|
+
ax.set_title('f-I curve')
|
315
346
|
|
316
347
|
|
317
348
|
# =============================================================================
|
@@ -365,16 +396,18 @@ def calculate_voltage_attenuation(model):
|
|
365
396
|
|
366
397
|
|
367
398
|
# Calculate voltage displacement from the resting potential
|
368
|
-
delta_v_at_stimulated = voltage_at_stimulated[0] - np.min(voltage_at_stimulated)
|
369
|
-
delta_vs = [v[0] - np.min(v) for v in voltages]
|
399
|
+
delta_v_at_stimulated = voltage_at_stimulated[0] - voltage_at_stimulated[-2]# np.min(voltage_at_stimulated)
|
400
|
+
delta_vs = [v[0] - v[-2] for v in voltages] # np.min(v) for v in voltages]
|
370
401
|
|
371
402
|
min_voltages = [np.min(v) for v in voltages]
|
403
|
+
end_voltages = [v[-2] for v in voltages]
|
372
404
|
|
373
405
|
attenuation = [dv / delta_v_at_stimulated for dv in delta_vs]
|
374
406
|
|
375
407
|
return {
|
376
408
|
'path_distances': path_distances,
|
377
409
|
'min_voltages': min_voltages,
|
410
|
+
'end_voltages': end_voltages,
|
378
411
|
'attenuation': attenuation
|
379
412
|
}
|
380
413
|
|
@@ -393,7 +426,7 @@ def plot_voltage_attenuation(data, ax=None):
|
|
393
426
|
ax.set_ylabel('Voltage attenuation')
|
394
427
|
ax.set_title('Voltage attenuation')
|
395
428
|
|
396
|
-
def calculate_dendritic_nonlinearity(model, duration=1000, max_weight=None, n=None):
|
429
|
+
def calculate_dendritic_nonlinearity(model, duration=1000, prerun_time=0, max_weight=None, n=None):
|
397
430
|
"""Calculate the expected and observed voltage changes for a range of synaptic weights.
|
398
431
|
|
399
432
|
Parameters
|
@@ -437,7 +470,7 @@ def calculate_dendritic_nonlinearity(model, duration=1000, max_weight=None, n=No
|
|
437
470
|
|
438
471
|
for w in weights:
|
439
472
|
population.update_input_params(weight=w)
|
440
|
-
model.
|
473
|
+
model.run(duration=duration, prerun_time=prerun_time)
|
441
474
|
v = np.array(model.simulator.recordings['v'][seg])
|
442
475
|
v_start = v[start_ts]
|
443
476
|
v_max = np.max(v[start_ts:])
|
@@ -456,7 +489,22 @@ def calculate_dendritic_nonlinearity(model, duration=1000, max_weight=None, n=No
|
|
456
489
|
}
|
457
490
|
|
458
491
|
|
459
|
-
def plot_dendritic_nonlinearity(data, ax=None, **kwargs):
|
492
|
+
def plot_dendritic_nonlinearity(data, ax=None, vshift=200, **kwargs):
|
493
|
+
"""
|
494
|
+
Plot the dendritic nonlinearity based on expected and observed voltage changes.
|
495
|
+
|
496
|
+
Parameters
|
497
|
+
----------
|
498
|
+
data : dict
|
499
|
+
A dictionary containing the expected and observed voltage changes.
|
500
|
+
Can be obtained from `calculate_dendritic_nonlinearity`.
|
501
|
+
ax : matplotlib.axes.Axes, optional
|
502
|
+
The axes to plot on. If two axes are provided, the first will show the voltage traces and the second will show the dendritic nonlinearity.
|
503
|
+
If a single axis is provided, it will show the dendritic nonlinearity only.
|
504
|
+
If None, a new figure will be created.
|
505
|
+
vshift : int, optional
|
506
|
+
The vertical shift for the voltage traces.
|
507
|
+
"""
|
460
508
|
|
461
509
|
if ax is None:
|
462
510
|
_, ax = plt.subplots(1, 2, figsize=(10, 5))
|
@@ -466,23 +514,53 @@ def plot_dendritic_nonlinearity(data, ax=None, **kwargs):
|
|
466
514
|
vs = data['voltages']
|
467
515
|
t = data['time']
|
468
516
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
ax
|
478
|
-
ax
|
479
|
-
|
480
|
-
|
481
|
-
ax
|
482
|
-
ax
|
483
|
-
ax
|
484
|
-
ax[1].set_title('Dendritic nonlinearity')
|
517
|
+
if isinstance(ax, (list, np.ndarray)):
|
518
|
+
for i, (weight, v) in enumerate(vs.items()):
|
519
|
+
ax[0].plot(t, np.array(v) - i*vshift, label=f'{weight} synapses')
|
520
|
+
ax[0].set_title('Voltage traces')
|
521
|
+
ax[0].legend()
|
522
|
+
ax[0].axis('off')
|
523
|
+
ax = ax[1]
|
524
|
+
|
525
|
+
ax.plot(expected_delta_vs, delta_vs, zorder=1)
|
526
|
+
ax.plot(expected_delta_vs, expected_delta_vs, color='gray', linestyle='--', zorder=0)
|
527
|
+
for ep, ob in zip(expected_delta_vs, delta_vs):
|
528
|
+
ax.plot(ep, ob, 'o', zorder=2)
|
529
|
+
ax.set_xlabel('Expected voltage change (mV)')
|
530
|
+
ax.set_ylabel('Observed voltage change (mV)')
|
531
|
+
ax.set_title('Dendritic nonlinearity')
|
485
532
|
|
486
533
|
|
534
|
+
def calculate_sag_ratio(model):
|
535
|
+
"""
|
536
|
+
Calculate the sag ratio of the neuron model.
|
537
|
+
|
538
|
+
Parameters
|
539
|
+
----------
|
540
|
+
model : Model
|
541
|
+
The neuron model.
|
487
542
|
|
543
|
+
Returns
|
544
|
+
-------
|
545
|
+
dict
|
546
|
+
A dictionary containing the sag ratio and intermediate values.
|
547
|
+
"""
|
548
|
+
v, t, dt, iclamp = get_somatic_data(model)
|
549
|
+
|
550
|
+
start_ts = int(iclamp.delay / dt)
|
551
|
+
stop_ts = int((iclamp.delay + iclamp.dur) / dt)
|
552
|
+
min_ts = np.argmin(v[start_ts:stop_ts]) + start_ts
|
553
|
+
v_min = np.min(v[start_ts: min_ts])
|
554
|
+
|
555
|
+
a = v[stop_ts] - v_min
|
556
|
+
b = v[start_ts] - v_min
|
557
|
+
|
558
|
+
sag_ratio = a / b if b != 0 else np.nan
|
559
|
+
|
560
|
+
print(f"Sag ratio: {a:.2f}/{b:.2f} = {sag_ratio:.2f}")
|
561
|
+
return {
|
562
|
+
'a': a,
|
563
|
+
'b': b,
|
564
|
+
'sag_ratio': sag_ratio,
|
565
|
+
}
|
488
566
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
: Vector stream of events
|
2
2
|
|
3
3
|
NEURON {
|
4
|
+
THREADSAFE
|
4
5
|
ARTIFICIAL_CELL VecStim
|
5
6
|
RANGE delay
|
6
7
|
}
|
@@ -12,13 +13,8 @@ ASSIGNED {
|
|
12
13
|
delay
|
13
14
|
}
|
14
15
|
|
15
|
-
PARAMETER {
|
16
|
-
:delay = 0.0
|
17
|
-
}
|
18
|
-
|
19
16
|
INITIAL {
|
20
17
|
index = 0
|
21
|
-
:delay = 0
|
22
18
|
element()
|
23
19
|
if (index > 0) {
|
24
20
|
net_send(delay + etime - t, 1)
|
@@ -35,12 +31,6 @@ NET_RECEIVE (w) {
|
|
35
31
|
}
|
36
32
|
}
|
37
33
|
|
38
|
-
VERBATIM
|
39
|
-
extern double* vector_vec();
|
40
|
-
extern int vector_capacity();
|
41
|
-
extern void* vector_arg();
|
42
|
-
ENDVERBATIM
|
43
|
-
|
44
34
|
PROCEDURE element() {
|
45
35
|
VERBATIM
|
46
36
|
{ void* vv; int i, size; double* px;
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# This Python channel class was automatically generated from a MOD file
|
2
|
+
# using DendroTweaks toolbox, dendrotweaks.dendrites.gr
|
3
|
+
|
4
|
+
|
5
|
+
from jaxley.channels import Channel
|
6
|
+
from jaxley.solver_gate import exponential_euler
|
7
|
+
import jax.numpy as np
|
8
|
+
|
9
|
+
class {{ class_name }}(Channel):
|
10
|
+
"""
|
11
|
+
{{ title }}
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, name="{{ class_name }}"):
|
15
|
+
self.current_is_in_mA_per_cm2 = True
|
16
|
+
super().__init__(name=name)
|
17
|
+
self.channel_params = {
|
18
|
+
{% for param, value in channel_params.items() -%}
|
19
|
+
"{{ param }}_{{ class_name }}": {{ value }}
|
20
|
+
{%- if not loop.last -%},
|
21
|
+
{%- endif %}
|
22
|
+
{% endfor -%}
|
23
|
+
}
|
24
|
+
self.channel_states = {
|
25
|
+
{% for state in state_vars -%}
|
26
|
+
"{{ state }}_{{class_name}}": 0.0
|
27
|
+
{%- if not loop.last %},
|
28
|
+
{%- endif %}
|
29
|
+
{% endfor -%}
|
30
|
+
}
|
31
|
+
self._state_powers = {
|
32
|
+
{% for state, power in state_vars.items() -%}
|
33
|
+
"{{ state }}_{{class_name}}": {{ power }}
|
34
|
+
{%- if not loop.last %},
|
35
|
+
{%- endif %}
|
36
|
+
{% endfor -%}
|
37
|
+
}
|
38
|
+
self.ion = "{{ ion }}"
|
39
|
+
self.current_name = "i_{{ ion }}"
|
40
|
+
|
41
|
+
self.independent_var_name = "{{ independent_var_name }}"
|
42
|
+
self.tadj = 1
|
43
|
+
|
44
|
+
def set_tadj(self, temperature):
|
45
|
+
"""
|
46
|
+
Set the temperature adjustment factor for the channel kinetics.
|
47
|
+
|
48
|
+
Parameters
|
49
|
+
----------
|
50
|
+
temperature : float
|
51
|
+
The temperature in degrees Celsius.
|
52
|
+
|
53
|
+
Notes
|
54
|
+
-----
|
55
|
+
The temperature adjustment factor is calculated as:
|
56
|
+
tadj = q10 ** ((temperature - reference_temp) / 10)
|
57
|
+
where q10 is the temperature coefficient and reference_temp is the
|
58
|
+
temperature at which the channel kinetics were measured.
|
59
|
+
"""
|
60
|
+
q10 = self.channel_params.get(f"q10_{{ class_name }}")
|
61
|
+
reference_temp = self.channel_params.get(f"temp_{{ class_name }}")
|
62
|
+
if q10 is None or reference_temp is None:
|
63
|
+
self.tadj = 1
|
64
|
+
print(f"Warning: q10 or reference temperature not set for {self.name}. Using default tadj = 1.")
|
65
|
+
else:
|
66
|
+
self.tadj = q10 ** ((temperature - reference_temp) / 10)
|
67
|
+
|
68
|
+
def __getitem__(self, item):
|
69
|
+
return self.channel_params[item]
|
70
|
+
|
71
|
+
def __setitem__(self, item, value):
|
72
|
+
self.channel_params[item] = value
|
73
|
+
|
74
|
+
{% for function in functions %}
|
75
|
+
{{ function['signature'] }}
|
76
|
+
{%- for param in function['params'] -%}
|
77
|
+
{{ param }} = self.channel_params.get("{{ param }}_{{ class_name }}", 1)
|
78
|
+
{% endfor %}
|
79
|
+
{{ function['body'] }}
|
80
|
+
{% if not loop.last %}
|
81
|
+
{% endif %}{% endfor -%}
|
82
|
+
{% for procedure in procedures %}
|
83
|
+
{{ procedure['signature'] }}
|
84
|
+
{% for param in procedure['params'] -%}
|
85
|
+
{{ param }} = self.channel_params.get("{{ param }}_{{ class_name }}", 1)
|
86
|
+
{% endfor %}
|
87
|
+
{{ procedure['body'] }}
|
88
|
+
{%- if not loop.last %}
|
89
|
+
{% endif %}{% endfor %}
|
90
|
+
|
91
|
+
def update_states(self, states, dt, v, params):
|
92
|
+
{% for state, state_params in state_vars.items() -%}
|
93
|
+
{{state}} = states['{{ state }}_{{class_name}}']
|
94
|
+
{%- if not loop.last %}
|
95
|
+
{%- endif %}
|
96
|
+
{% endfor -%}
|
97
|
+
{{- procedure_calls}}
|
98
|
+
{% for state in state_vars.keys() %}new_{{state}} = exponential_euler({{state}}, dt, {{state}}Inf, {{state}}Tau){% if not loop.last %}
|
99
|
+
{% endif %}{% endfor %}
|
100
|
+
return {
|
101
|
+
{% for state in state_vars -%}
|
102
|
+
"{{ state }}_{{class_name}}": new_{{state}}
|
103
|
+
{%- if not loop.last %},
|
104
|
+
{%- endif %}
|
105
|
+
{% endfor -%}
|
106
|
+
}
|
107
|
+
|
108
|
+
def compute_current(self, states, v, params):
|
109
|
+
{% for state in state_vars.keys() -%}
|
110
|
+
{{state}} = states['{{ state }}_{{class_name}}']
|
111
|
+
{%- if not loop.last %}
|
112
|
+
{%- endif %}
|
113
|
+
{% endfor -%}
|
114
|
+
gbar = params["gbar_{{class_name}}"]
|
115
|
+
# E = params["E_{{ ion }}"]
|
116
|
+
E = {{ E_ion }}
|
117
|
+
{{ procedure_calls}}
|
118
|
+
g = self.tadj * gbar *{% for state, power in state_vars.items()%} {{state}}**{{power['power']}} {% if not loop.last %}*{% endif %}{% endfor %}
|
119
|
+
return g * (v - E)
|
120
|
+
|
121
|
+
def init_state(self, states, v, params, delta_t):
|
122
|
+
{{ procedure_calls}}
|
123
|
+
return {
|
124
|
+
{% for state in state_vars.keys() -%}
|
125
|
+
"{{ state }}_{{class_name}}": {{state}}Inf
|
126
|
+
{%- if not loop.last %},
|
127
|
+
{%- endif %}
|
128
|
+
{% endfor -%}
|
129
|
+
}
|
130
|
+
|
131
|
+
|
@@ -131,16 +131,16 @@ def gaussian(distance: float, amplitude: float, mean: float, std: float) -> floa
|
|
131
131
|
return amplitude * exp(-((distance - mean) ** 2) / (2 * std ** 2))
|
132
132
|
|
133
133
|
|
134
|
-
def step(distance: float,
|
134
|
+
def step(distance: float, start: float, end: float, min_value: float, max_value: float) -> float:
|
135
135
|
"""
|
136
136
|
Step distribution function.
|
137
137
|
|
138
138
|
Args:
|
139
139
|
distance (float): The distance parameter.
|
140
|
-
min_value (float): The minimum value parameter.
|
141
|
-
max_value (float): The maximum value parameter.
|
142
140
|
start (float): The start parameter.
|
143
141
|
end (float): The end parameter.
|
142
|
+
min_value (float): The minimum value parameter.
|
143
|
+
max_value (float): The maximum value parameter.
|
144
144
|
|
145
145
|
Returns:
|
146
146
|
The result of the step equation: min_value if distance < start, max_value if distance > end, and a linear interpolation between min_value and max_value if start <= distance <= end.
|
@@ -94,6 +94,10 @@ class MODFileConverter():
|
|
94
94
|
self.reader.read_file(path_to_mod_file)
|
95
95
|
self.reader.preprocess()
|
96
96
|
blocks = self.reader.get_blocks(verbose)
|
97
|
+
if blocks.get('KINETIC'):
|
98
|
+
raise NotImplementedError(
|
99
|
+
"Conversion aborted: MOD files containing KINETIC blocks are not supported by DendroTweaks."
|
100
|
+
)
|
97
101
|
|
98
102
|
if verbose: print(f"\nPARSING")
|
99
103
|
self.parser.parse(blocks, verbose)
|
@@ -575,4 +575,14 @@ class CaDynamics(Mechanism):
|
|
575
575
|
'gamma': 0.05,
|
576
576
|
'kt': 0.0,
|
577
577
|
'kd': 0.0
|
578
|
-
}
|
578
|
+
}
|
579
|
+
|
580
|
+
|
581
|
+
class FallbackChannel(IonChannel):
|
582
|
+
"""
|
583
|
+
Fallback channel class in case of import failure.
|
584
|
+
"""
|
585
|
+
def __init__(self, name):
|
586
|
+
super().__init__(name=name)
|
587
|
+
self.params = {'gbar': 0.0}
|
588
|
+
self.range_params = {'gbar': 0.0}
|