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,574 @@
1
+ from typing import Dict
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+
6
+
7
+ class Mechanism():
8
+ """
9
+ A class representing a mechanism in a neuron model.
10
+
11
+ A mechanism is a set of differential equations that
12
+ describe the kinetics of a channel
13
+ or a pump in the neuron membrane
14
+
15
+ Parameters
16
+ ----------
17
+ name : str
18
+ The name of the mechanism.
19
+
20
+ Attributes
21
+ ----------
22
+ name : str
23
+ The name of the mechanism.
24
+ params : dict
25
+ A dictionary of the parameters of the mechanism.
26
+ range_params : dict
27
+ A dictionary of the range parameters of the mechanism added
28
+ under the RANGE statement in the MOD file.
29
+ """
30
+
31
+ def __init__(self, name):
32
+ self.name = name
33
+ self.params = {}
34
+ self.range_params = {}
35
+
36
+ @property
37
+ def params_with_suffix(self):
38
+ """
39
+ The parameters of the mechanism with the suffix
40
+ — the name of the mechanism.
41
+
42
+ Returns
43
+ -------
44
+ dict
45
+ A dictionary of the parameters of the mechanism with the suffix and their values.
46
+ """
47
+ return {f"{param}_{self.name}":value for param, value in self.params.items()}
48
+
49
+ @property
50
+ def range_params_with_suffix(self):
51
+ """
52
+ The range parameters of the mechanism with the suffix
53
+ — the name of the mechanism. The range parameters are the parameters
54
+ defined in the RANGE block of the NMODL file.
55
+
56
+ Returns
57
+ -------
58
+ dict
59
+ A dictionary of the range parameters of the mechanism with the suffix and their values.
60
+ """
61
+ return {f"{param}_{self.name}":value for param, value in self.range_params.items()}
62
+
63
+ def to_dict(self):
64
+ """
65
+ Return the mechanism as a dictionary.
66
+ """
67
+ return {
68
+ 'name': self.name,
69
+ 'params': self.params
70
+ }
71
+
72
+ def __repr__(self):
73
+ return f"<Mechnaism({self.name})>"
74
+
75
+
76
+
77
+
78
+ class IonChannel(Mechanism):
79
+ """
80
+ A class representing an ion channel in a neuron model.
81
+
82
+ Parameters
83
+ ----------
84
+ name : str
85
+ The name of the channel.
86
+
87
+ Attributes
88
+ ----------
89
+ independent_var_name : str
90
+ The name of the independent variable for the channel kinetics e.g. 'v', 'cai'.
91
+ params : dict
92
+ A dictionary of the parameters of the channel kinetics and distribution.
93
+ range_params : dict
94
+ A dictionary of the range parameters of the channel kinetics added
95
+ under the RANGE statement in the MOD file.
96
+ temperature : float
97
+ The temperature in degrees Celsius.
98
+ tadj : float
99
+ The temperature adjustment factor for the channel kinetics.
100
+ """
101
+
102
+ def __init__(self, name):
103
+ super().__init__(name)
104
+ self.tadj = 1
105
+
106
+ def set_tadj(self, temperature):
107
+ """
108
+ Set the temperature adjustment factor for the channel kinetics.
109
+
110
+ Parameters
111
+ ----------
112
+ temperature : float
113
+ The temperature in degrees Celsius.
114
+
115
+ Notes
116
+ -----
117
+ The temperature adjustment factor is calculated as:
118
+ tadj = q10 ** ((temperature - reference_temp) / 10)
119
+ where q10 is the temperature coefficient and reference_temp is the
120
+ temperature at which the channel kinetics were measured.
121
+ """
122
+ q10 = self.params.get("q10", 2.3)
123
+ reference_temp = self.params.get("temp", temperature)
124
+ self.tadj = q10 ** ((temperature - reference_temp) / 10)
125
+
126
+ def get_data(self, x=None, temperature: float = 37, verbose=True) -> Dict[str, Dict[str, float]]:
127
+ """
128
+ Get the data for the channel kinetics as a dictionary. The data
129
+ includes the steady state values and time constants of the channel,
130
+ as well as the independent variable values.
131
+
132
+ Parameters
133
+ ----------
134
+ x : np.array, optional
135
+ The independent variable for the channel kinetics. If None, the
136
+ default values will be used. The default is None.
137
+ temperature : float, optional
138
+ The temperature in degrees Celsius. The default is 37.
139
+
140
+ Returns
141
+ -------
142
+ Dict[str, Dict[str, np.ndarray]]
143
+ A dictionary of states with their steady state values and time constants:
144
+ {
145
+ 'state1': {'inf': np.array, 'tau': np.array},
146
+ 'state2': {'inf': np.array, 'tau': np.array},
147
+ ...
148
+ 'x': np.array
149
+ }
150
+ """
151
+
152
+ if x is None:
153
+ if self.independent_var_name == 'v':
154
+ x = np.linspace(-100, 100, 100)
155
+ elif self.independent_var_name == 'cai':
156
+ x = np.logspace(-6, 2, 100)
157
+
158
+ self.set_tadj(temperature)
159
+ self.temperature = temperature
160
+ states = self.compute_kinetic_variables(x)
161
+ # TODO: Fix the issue with returning state as a constant
162
+ # for some channels (e.g. tau in Poirazi Na_soma)
163
+ data = {
164
+ state_name: {
165
+ 'inf': np.full_like(x, states[i]) if np.isscalar(states[i]) else states[i],
166
+ 'tau': np.full_like(x, states[i + 1]) if np.isscalar(states[i + 1]) else states[i + 1]
167
+ }
168
+ for i, state_name in zip(range(0, len(states), 2),
169
+ self.states)
170
+ }
171
+ data.update({'x': x})
172
+ if verbose: print(f'Got data for {self.independent_var_name} '
173
+ f'in range {x[0]} to {x[-1]} at {temperature}°C')
174
+ return data
175
+
176
+ def plot_kinetics(self, ax=None, linestyle='solid', **kwargs) -> None:
177
+ """
178
+ Plot the kinetics of the channel.
179
+
180
+ Parameters
181
+ ----------
182
+ ax : matplotlib.axes.Axes, optional
183
+ The axes to plot the kinetics on. If None, a new figure
184
+ will be created. The default is None.
185
+ linestyle : str, optional
186
+ The line style for the plots. The default is 'solid'.
187
+ **kwargs : dict
188
+ Additional keyword arguments to pass to the get_data method.
189
+ """
190
+
191
+ if ax is None:
192
+ fig, ax = plt.subplots(1, 2, figsize=(10, 5))
193
+
194
+ data = self.get_data(**kwargs)
195
+ x = data.pop('x')
196
+
197
+ for state_name, state in data.items():
198
+ ax[0].plot(x, state['inf'], label=f'{state_name}Inf', linestyle=linestyle)
199
+ ax[1].plot(x, state['tau'], label=f'{state_name}Tau', linestyle=linestyle)
200
+
201
+ ax[0].set_title('Steady state')
202
+ ax[1].set_title('Time constant')
203
+ ax[0].set_xlabel('Voltage (mV)' if self.independent_var_name == 'v' else 'Ca2+ concentration (mM)')
204
+ ax[1].set_xlabel('Voltage (mV)' if self.independent_var_name == 'v' else 'Ca2+ concentration (mM)')
205
+ ax[0].set_ylabel('Open probability (1)')
206
+ ax[1].set_ylabel('Time constant (ms)')
207
+ ax[0].legend()
208
+ ax[1].legend()
209
+
210
+
211
+
212
+
213
+ class StandardIonChannel(IonChannel):
214
+ """
215
+ A class representing a voltage-gated ion channel with a standard
216
+ set of kinetic parameters and equations grounded in the transition-state
217
+ theory. The model is based on the Hodgkin-Huxley formalism.
218
+
219
+ Parameters
220
+ ----------
221
+ name : str
222
+ The name of the channel.
223
+ state_powers : dict
224
+ A dictionary of the state variables and their powers in the
225
+ differential equations of the channel kinetics.
226
+ ion : str, optional
227
+ The ion that the channel is permeable to. The default is None.
228
+
229
+
230
+ Attributes
231
+ ----------
232
+ ion : str
233
+ The ion that the channel is permeable to e.g. 'na', 'k'.
234
+ independent_var_name : str
235
+ The name of the independent variable for the channel kinetics e.g. 'v', 'cai'.
236
+ params : dict
237
+ A dictionary of the parameters of the channel kinetics and distribution.
238
+ range_params : dict
239
+ A dictionary of the range parameters of the channel kinetics added
240
+ under the RANGE statement in the MOD file.
241
+ temperature : float
242
+ The temperature in degrees Celsius.
243
+ """
244
+
245
+ STANDARD_PARAMS = [
246
+ 'vhalf', 'sigma', 'k', 'delta', 'tau0'
247
+ ]
248
+
249
+ @staticmethod
250
+ def steady_state(v, vhalf, sigma):
251
+ """
252
+ Compute the steady state value of the channel.
253
+
254
+ Parameters
255
+ ----------
256
+ v : np.array
257
+ The voltage values to compute the steady state value for.
258
+ vhalf : float
259
+ The half-activation voltage.
260
+ sigma : float
261
+ The slope factor.
262
+
263
+ Returns
264
+ -------
265
+ np.array
266
+ The steady state value of the channel at the given voltage values.
267
+ """
268
+ return 1 / (1 + np.exp(-(v - vhalf) / sigma))
269
+
270
+ def time_constant(self, v, vhalf, sigma, k, delta, tau0):
271
+ """
272
+ Compute the time constant of the channel.
273
+
274
+ Parameters
275
+ ----------
276
+ v : np.array
277
+ The voltage values to compute the time constant for.
278
+ vhalf : float
279
+ The half-activation voltage.
280
+ sigma : float
281
+ The slope factor.
282
+ k : float
283
+ The maximum rate parameter.
284
+ delta : float
285
+ The skew parameter of the time constant curve (unitless)
286
+ tau0 : float
287
+ The rate-limiting factor (minimum time constant)
288
+
289
+ Returns
290
+ -------
291
+ np.array
292
+ The time constant of the channel at the given voltage values.
293
+ """
294
+ return 1 / (self.alpha_prime(v, vhalf, sigma, k, delta) + self.beta_prime(v, vhalf, sigma, k, delta)) + tau0
295
+
296
+ @staticmethod
297
+ def alpha_prime(v, vhalf, sigma, k, delta):
298
+ return k * np.exp(delta * (v - vhalf) / sigma)
299
+
300
+ @staticmethod
301
+ def beta_prime(v, vhalf, sigma, k, delta):
302
+ return k * np.exp(-(1 - delta) * (v - vhalf) / sigma)
303
+
304
+ @staticmethod
305
+ def t_adj(temperature, q10=2.3, reference_temp=23):
306
+ """
307
+ Compute the temperature adjustment factor for the channel kinetics.
308
+
309
+ Parameters
310
+ ----------
311
+ temperature : float
312
+ The temperature in degrees Celsius.
313
+ q10 : float, optional
314
+ The temperature coefficient. The default is 2.3.
315
+ reference_temp : float, optional
316
+ The reference temperature at which the channel kinetics were measured.
317
+ The default is 23.
318
+
319
+ Returns
320
+ -------
321
+ float
322
+ The temperature adjustment factor.
323
+ """
324
+ return q10 ** ((temperature - reference_temp) / 10)
325
+
326
+ def compute_state(self, v, vhalf, sigma, k, delta, tau0, tadj=1):
327
+ """
328
+ Compute the steady state value and time constant of the channel
329
+ for the given voltage values.
330
+
331
+ Parameters
332
+ ----------
333
+ v : np.array
334
+ The voltage values to compute the channel kinetics for.
335
+ vhalf : float
336
+ The half-activation voltage.
337
+ sigma : float
338
+ The slope factor.
339
+ k : float
340
+ The maximum rate parameter.
341
+ delta : float
342
+ The skew parameter of the time constant curve (unitless)
343
+ tau0 : float
344
+ The rate-limiting factor (minimum time constant)
345
+ tadj : float, optional
346
+ The temperature adjustment factor. The default is 1.
347
+
348
+ Returns
349
+ -------
350
+ np.array
351
+ A list of steady state values and time constants for the channel.
352
+ """
353
+ inf = self.steady_state(v, vhalf, sigma)
354
+ tau = self.time_constant(v, vhalf, sigma, k, delta, tau0) / tadj
355
+ return inf, tau
356
+
357
+
358
+ def __init__(self, name, state_powers, ion=None):
359
+ super().__init__(name)
360
+
361
+ self.ion = ion
362
+ self.independent_var_name = 'v'
363
+
364
+ self._state_powers = state_powers
365
+
366
+ # self.range_params = [f'{param}_{state}' for state in state_powers
367
+ # for param in self.STANDARD_PARAMS]
368
+
369
+ self.params = {
370
+ f'{param}_{state}': None
371
+ for state in state_powers
372
+ for param in self.STANDARD_PARAMS
373
+ }
374
+ self.params.update({
375
+ 'gbar': 0.0,
376
+ })
377
+ self.range_params = self.params.copy()
378
+
379
+ self.temperature = 37
380
+
381
+
382
+ @property
383
+ def states(self):
384
+ """
385
+ A list of state variable names of the channel.
386
+ """
387
+ return [state for state in self._state_powers]
388
+
389
+
390
+ def compute_kinetic_variables(self, v):
391
+ """
392
+ Compute the steady state values and time constants of the channel
393
+ for the given voltage values.
394
+
395
+ Parameters
396
+ ----------
397
+ v : np.array
398
+ The voltage values to compute the channel kinetics for.
399
+
400
+ Returns
401
+ -------
402
+ list
403
+ A list of steady state values and time constants for each state
404
+ of the channel.
405
+ """
406
+
407
+ results = []
408
+
409
+ for state in self.states:
410
+
411
+ vhalf = self.params[f'vhalf_{state}']
412
+ sigma = self.params[f'sigma_{state}']
413
+ k = self.params[f'k_{state}']
414
+ delta = self.params[f'delta_{state}']
415
+ tau0 = self.params[f'tau0_{state}']
416
+
417
+ inf = self.steady_state(v, vhalf, sigma)
418
+ tau = self.time_constant(v, vhalf, sigma, k, delta, tau0) / self.tadj
419
+
420
+ results.extend([inf, tau])
421
+
422
+ return results
423
+
424
+
425
+ def fit(self, data, prioritized_inf=True, round_params=3):
426
+ """
427
+ Fit the standardized set of parameters of the model to the data
428
+ of the channel kinetics.
429
+
430
+ Parameters
431
+ ----------
432
+ data : dict
433
+ A dictionary containing the data for the channel kinetics. The
434
+ dictionary should have the following structure:
435
+ {
436
+ 'x': np.array, # The independent variable
437
+ 'state1': {'inf': np.array, 'tau': np.array},
438
+ 'state2': {'inf': np.array, 'tau': np.array},
439
+ ...
440
+ }
441
+ prioritized_inf : bool, optional
442
+ Whether to prioritize the fit to the 'inf' data. If True, an
443
+ additional fit will be performed to the 'inf' data only. The
444
+ default is True.
445
+ round_params : int, optional
446
+ The number of decimal places to round the fitted parameters to.
447
+ The default is 3.
448
+ """
449
+ from symfit import exp, variables, parameters, Model, Fit
450
+
451
+ x = data.pop('x')
452
+
453
+ for state, state_data in data.items():
454
+ v, inf, tau = variables('v, inf, tau')
455
+ initial_values = [1, 0.5, 0, 10, 0] if state_data['inf'][0] < state_data['inf'][-1] else [1, 0.5, 0, -10, 0]
456
+ k, delta, vhalf, sigma, tau0 = parameters('k, delta, vhalf, sigma, tau0', value=initial_values)
457
+
458
+ model = Model({
459
+ inf: 1 / (1 + exp(-(v - vhalf) / sigma)),
460
+ tau: 1 / (k * exp(delta * (v - vhalf) / sigma) + k * exp(-(1 - delta) * (v - vhalf) / sigma)) + tau0,
461
+ })
462
+
463
+ fit = Fit(model, v=x, inf=state_data['inf'], tau=state_data['tau'])
464
+ fit_result = fit.execute()
465
+
466
+ if prioritized_inf:
467
+ vhalf.value, sigma.value = fit_result.params['vhalf'], fit_result.params['sigma']
468
+ model_inf = Model({inf: 1 / (1 + exp(-(v - vhalf) / sigma))})
469
+ fit_inf = Fit(model_inf, v=x, inf=state_data['inf'])
470
+ fit_result_inf = fit_inf.execute()
471
+ fit_result.params.update(fit_result_inf.params)
472
+
473
+ if round_params:
474
+ fit_result.params = {key: round(value, round_params) for key, value in fit_result.params.items()}
475
+
476
+ for param in ['k', 'delta', 'tau0', 'vhalf', 'sigma']:
477
+ self.params[f'{param}_{state}'] = fit_result.params[param]
478
+
479
+ self.range_params = self.params.copy()
480
+
481
+
482
+ def to_dict(self):
483
+ """
484
+ Return the mechanism as a dictionary.
485
+ """
486
+ return {
487
+ 'suffix': self.name,
488
+ 'ion': self.ion,
489
+ 'range_params': [
490
+ (param, self.params[param], get_unit(param))
491
+ for param in self.params
492
+ ],
493
+ 'state_vars': {
494
+ var: power for var, power in self._state_powers.items()
495
+ },
496
+ }
497
+
498
+ @staticmethod
499
+ def get_unit(param):
500
+ """
501
+ Get the unit of a parameter based on its name.
502
+
503
+ Parameters
504
+ ----------
505
+ param : str
506
+ The name of the parameter.
507
+
508
+ Returns
509
+ -------
510
+ str
511
+ The unit of the parameter.
512
+ """
513
+ if param.startswith('vhalf_'): return 'mV'
514
+ elif param.startswith('sigma_'): return 'mV'
515
+ elif param.startswith('k_'): return '1/ms'
516
+ elif param.startswith('delta_'): return '1'
517
+ elif param.startswith('tau0_'): return 'ms'
518
+
519
+
520
+ class LeakChannel(Mechanism):
521
+ """
522
+ A class representing a leak channel in a neuron model.
523
+
524
+ Parameters
525
+ ----------
526
+ name : str
527
+ The name of the channel.
528
+
529
+ Attributes
530
+ ----------
531
+ params : dict
532
+ A dictionary of the parameters of the channel kinetics and distribution.
533
+ range_params : dict
534
+ A dictionary of the range parameters of the channel kinetics added
535
+ under the RANGE statement in the MOD file.
536
+ """
537
+
538
+ def __init__(self):
539
+ super().__init__(name='Leak')
540
+ self.params = {'gbar': 0.0, 'e': -70}
541
+ self.range_params = {'gbar': 0.0, 'e': -70}
542
+
543
+
544
+ class CaDynamics(Mechanism):
545
+ """
546
+ A class representing a calcium dynamics mechanism in a neuron model.
547
+
548
+ Attributes
549
+ ----------
550
+ params : dict
551
+ A dictionary of the parameters of the calcium dynamics mechanism.
552
+ range_params : dict
553
+ A dictionary of the range parameters of the calcium dynamics mechanism
554
+ added under the RANGE statement in the MOD file.
555
+ """
556
+
557
+ def __init__(self):
558
+ super().__init__('CaDyn')
559
+ self.params = {
560
+ 'depth': 0.1, # um: Depth of calcium shell
561
+ 'taur': 80, # ms: Time constant for calcium removal
562
+ 'cainf': 1e-4, # mM: Steady-state calcium concentration
563
+ 'gamma': 0.05,
564
+ 'kt': 0.0,
565
+ 'kd': 0.0
566
+ }
567
+ self.range_params = {
568
+ 'depth': 0.1,
569
+ 'taur': 80,
570
+ 'cainf': 1e-4,
571
+ 'gamma': 0.05,
572
+ 'kt': 0.0,
573
+ 'kd': 0.0
574
+ }