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,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.params = {
16
+ {% for param, value in channel_params.items() -%}
17
+ "{{{{ param }}": {{ value }}
18
+ {%- if not loop.last -%},
19
+ {%- endif %}
20
+ {% endfor -%}
21
+ }
22
+ self.states = {
23
+ {% for state in state_vars -%}
24
+ "{{ 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
+
@@ -0,0 +1,324 @@
1
+ from typing import Callable, Dict, List
2
+ from numpy import ndarray, full_like
3
+ from numpy import exp, sin, polyval
4
+ import warnings
5
+ # Define simple functions and store them alongside their defaults in FUNCTIONS
6
+ def constant(position, value=0):
7
+ """
8
+ Constant function that returns a constant value for any position.
9
+
10
+ Parameters
11
+ ----------
12
+ position : float or numpy.ndarray
13
+ The position at which to evaluate the function.
14
+ value : float
15
+ The constant value to return.
16
+
17
+ Returns
18
+ -------
19
+ float or numpy.ndarray
20
+ The value of the constant function at the given position.
21
+ """
22
+ if isinstance(position, ndarray):
23
+ return full_like(position, value)
24
+ else:
25
+ return value
26
+
27
+
28
+ def uniform(position, value=0):
29
+ """
30
+ Constant function that returns a constant value for any position.
31
+
32
+ Parameters
33
+ ----------
34
+ position : float or numpy.ndarray
35
+ The position at which to evaluate the function.
36
+ value : float
37
+ The constant value to return.
38
+
39
+ Returns
40
+ -------
41
+ float or numpy.ndarray
42
+ The value of the constant function at the given position.
43
+ """
44
+ if isinstance(position, ndarray):
45
+ return full_like(position, value)
46
+ else:
47
+ return value
48
+
49
+
50
+ def linear(position, slope=1, intercept=0):
51
+ """
52
+ Linear function that returns a linearly changing value for any position.
53
+
54
+ Parameters
55
+ ----------
56
+ position : float or numpy.ndarray
57
+ The position at which to evaluate the function.
58
+ slope : float
59
+ The slope of the linear function.
60
+ intercept : float
61
+ The intercept of the linear function.
62
+
63
+ Returns
64
+ -------
65
+ float or numpy.ndarray
66
+ The value of the linear function at the given position.
67
+ """
68
+ return slope * position + intercept
69
+
70
+ def exponential(distance: float, vertical_shift:float = 0, scale_factor: float =1, growth_rate: float=1, horizontal_shift: float = 0) -> float:
71
+ """
72
+ Exponential distribution function.
73
+
74
+ Args:
75
+ distance (float): The distance parameter.
76
+ vertical_shift (float): The vertical shift parameter.
77
+ scale_factor (float): The scale factor parameter.
78
+ growth_rate (float): The growth rate parameter.
79
+ horizontal_shift (float): The horizontal shift parameter.
80
+
81
+ Returns:
82
+ The result of the exponential equation: vertical_shift + scale_factor * exp(growth_rate * (distance - horizontal_shift)).
83
+ """
84
+ return vertical_shift + scale_factor * exp(growth_rate * (distance - horizontal_shift))
85
+
86
+ def sigmoid(distance: float, vertical_shift=0, scale_factor=1, growth_rate=1, horizontal_shift=0) -> float:
87
+ """
88
+ Sigmoid distribution function.
89
+
90
+ Args:
91
+ distance (float): The distance parameter.
92
+ vertical_shift (float): The vertical shift parameter.
93
+ scale_factor (float): The scale factor parameter.
94
+ growth_rate (float): The growth rate parameter.
95
+ horizontal_shift (float): The horizontal shift parameter.
96
+
97
+ Returns:
98
+ The result of the sigmoid equation: vertical_shift + scale_factor / (1 + exp(-growth_rate * (distance - horizontal_shift))).
99
+ """
100
+ return vertical_shift + scale_factor / (1 + exp(-growth_rate*(distance - horizontal_shift)))
101
+
102
+
103
+ def sinusoidal(distance: float, amplitude: float, frequency: float, phase: float) -> float:
104
+ """
105
+ Sinusoidal distribution function.
106
+
107
+ Args:
108
+ distance (float): The distance parameter.
109
+ amplitude (float): The amplitude parameter.
110
+ frequency (float): The frequency parameter.
111
+ phase (float): The phase parameter.
112
+
113
+ Returns:
114
+ The result of the sinusoidal equation: amplitude * sin(frequency * distance + phase).
115
+ """
116
+ return amplitude * sin(frequency * distance + phase)
117
+
118
+ def gaussian(distance: float, amplitude: float, mean: float, std: float) -> float:
119
+ """
120
+ Gaussian distribution function.
121
+
122
+ Args:
123
+ distance (float): The distance parameter.
124
+ amplitude (float): The amplitude parameter.
125
+ mean (float): The mean parameter.
126
+ std (float): The standard deviation parameter.
127
+
128
+ Returns:
129
+ The result of the gaussian equation: amplitude * exp(-((distance - mean) ** 2) / (2 * std ** 2)).
130
+ """
131
+ return amplitude * exp(-((distance - mean) ** 2) / (2 * std ** 2))
132
+
133
+
134
+ def step(distance: float, max_value: float, min_value: float, start: float, end: float) -> float:
135
+ """
136
+ Step distribution function.
137
+
138
+ Args:
139
+ distance (float): The distance parameter.
140
+ min_value (float): The minimum value parameter.
141
+ max_value (float): The maximum value parameter.
142
+ start (float): The start parameter.
143
+ end (float): The end parameter.
144
+
145
+ Returns:
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.
147
+ """
148
+ if start < distance < end:
149
+ return max_value
150
+ else:
151
+ return min_value
152
+
153
+ def polynomial(distance: float, coeffs: List[float]) -> float:
154
+ """
155
+ Polynomial distribution function.
156
+
157
+ Args:
158
+ distance (float): The distance parameter.
159
+ coefficients (List[float]): The coefficients of the polynomial.
160
+
161
+ Returns:
162
+ The result of the polynomial equation: sum(coefficients[i] * distance ** i for i in range(len(coefficients))).
163
+ """
164
+ return polyval(coeffs, distance)
165
+
166
+ # aka ParametrizedFunction
167
+ class Distribution:
168
+ """
169
+ A callable class for creating and managing distribution functions.
170
+
171
+ Parameters
172
+ ----------
173
+ function_name : str
174
+ The name of the function to use.
175
+ \**parameters
176
+ The parameters to use for the function.
177
+
178
+ Attributes
179
+ ----------
180
+ function : Callable
181
+ The function to use for evaluation.
182
+ parameters : dict
183
+ The parameters to use for the function.
184
+
185
+ Examples
186
+ --------
187
+
188
+ >>> func = Distribution('uniform', value=0)
189
+ >>> func(5)
190
+ 0
191
+ """
192
+
193
+ FUNCTIONS = {
194
+ 'constant': {'func': constant, 'defaults': {'value': 0}},
195
+ 'uniform': {'func': uniform, 'defaults': {'value': 0}},
196
+ 'linear': {'func': linear, 'defaults': {'slope': 1, 'intercept': 0}},
197
+ 'exponential': {'func': exponential, 'defaults': {'vertical_shift': 0, 'scale_factor': 1, 'growth_rate': 1, 'horizontal_shift': 0}},
198
+ 'sigmoid': {'func': sigmoid, 'defaults': {'vertical_shift': 0, 'scale_factor': 1, 'growth_rate': 1, 'horizontal_shift': 0}},
199
+ 'sinusoidal': {'func': sinusoidal, 'defaults': {'amplitude': 1, 'frequency': 1, 'phase': 0}},
200
+ 'gaussian': {'func': gaussian, 'defaults': {'amplitude': 1, 'mean': 0, 'std': 1}},
201
+ 'step': {'func': step, 'defaults': {'max_value': 1, 'min_value': 0, 'start': 0, 'end': 1}},
202
+ 'polynomial': {'func': polynomial, 'defaults': {'coeffs': [1, 0]}},
203
+
204
+ }
205
+
206
+ @staticmethod
207
+ def from_dict(data: Dict[str, any]) -> 'Distribution':
208
+ """
209
+ Creates a new Distribution from a dictionary.
210
+
211
+ Parameters
212
+ ----------
213
+ data : dict
214
+ The dictionary containing the function data.
215
+
216
+ Returns
217
+ -------
218
+ Distribution
219
+ The new Distribution instance.
220
+ """
221
+ return Distribution(data['function'], **data['parameters'])
222
+
223
+ def __init__(self, function_name: str, **parameters: Dict[str, float]) -> None:
224
+ """
225
+ Creates a new parameterized function.
226
+
227
+ Parameters
228
+ ----------
229
+ function_name : str
230
+ The name of the function to use.
231
+ \**parameters
232
+ The parameters to use for the function.
233
+ """
234
+ func_data = self.FUNCTIONS[function_name]
235
+ self.function = func_data['func']
236
+ # Merge defaults with user parameters
237
+ valid_params = {k: v for k, v in parameters.items()
238
+ if k in func_data['defaults']}
239
+ self.parameters = {**func_data['defaults'], **valid_params}
240
+
241
+ def __repr__(self):
242
+ """
243
+ Returns a string representation of the function.
244
+
245
+ Returns
246
+ -------
247
+ str
248
+ The string representation of the function.
249
+ """
250
+ return f'{self.function.__name__}({self.parameters})'
251
+
252
+ def __call__(self, position):
253
+ """
254
+ Calls the function with a given position.
255
+
256
+ Parameters
257
+ ----------
258
+ position : float or numpy.ndarray
259
+ The position at which to evaluate the function.
260
+
261
+ Returns
262
+ -------
263
+ float or numpy.ndarray
264
+ The value of the function at the given position.
265
+ """
266
+ return self.function(position, **self.parameters)
267
+
268
+ @property
269
+ def function_name(self):
270
+ """
271
+ Returns the name of the function.
272
+
273
+ Returns
274
+ -------
275
+ str
276
+ The name of the function.
277
+ """
278
+ return self.function.__name__
279
+
280
+ @property
281
+ def degree(self):
282
+ """
283
+ Returns the degree of the polynomial function (if applicable).
284
+
285
+ Returns
286
+ -------
287
+ int
288
+ The degree of the function.
289
+ """
290
+ if self.function_name == 'polynomial':
291
+ return len(self.parameters['coeffs']) - 1
292
+ else:
293
+ return None
294
+
295
+ def update_parameters(self, **new_params):
296
+ """
297
+ Updates the parameters of the function.
298
+
299
+ Parameters
300
+ ----------
301
+ \**new_params
302
+ The new parameters to update the function with.
303
+ """
304
+ valid_params = {k: v for k, v in new_params.items()
305
+ if k in self.parameters}
306
+ # if any of the new parameters are invalid, raise an error
307
+ if len(valid_params) != len(new_params):
308
+ invalid_params = set(new_params) - set(valid_params)
309
+ warnings.warn(f'\nIgnoring invalid parameters: {invalid_params}.\nSupported parameters: {list(self.parameters.keys())}')
310
+ self.parameters.update(valid_params)
311
+
312
+ def to_dict(self):
313
+ """
314
+ Exports the function to a dictionary format.
315
+
316
+ Returns
317
+ -------
318
+ dict
319
+ A dictionary representation of the function.
320
+ """
321
+ return {
322
+ 'function': self.function.__name__,
323
+ 'parameters': self.parameters
324
+ }
@@ -0,0 +1,103 @@
1
+ from typing import List, Callable, Dict
2
+
3
+ from dendrotweaks.morphology.trees import Node
4
+ from dendrotweaks.morphology.sec_trees import Section
5
+ from dendrotweaks.morphology.seg_trees import Segment
6
+ from dendrotweaks.membrane.mechanisms import Mechanism
7
+ from dendrotweaks.membrane.distributions import Distribution
8
+ from dendrotweaks.utils import timeit
9
+ from dataclasses import dataclass, field, asdict
10
+ from typing import List, Tuple, Dict, Optional
11
+
12
+
13
+ @dataclass
14
+ class SegmentGroup:
15
+ """
16
+ A group of segments that share a common property.
17
+
18
+ Parameters
19
+ ----------
20
+ name : str
21
+ The name of the segment group.
22
+ domains : List[str]
23
+ The domains the segments belong to.
24
+ select_by : Optional[str]
25
+ The property to select the segments by. Can be:
26
+ - 'diam': the diameter of the segment.
27
+ - 'section_diam': the diameter of the section the segment belongs to.
28
+ - 'distance': the distance of the segment from the root.
29
+ - 'domain_distance': the distance of the segment from the root within the domain.
30
+ min_value : Optional[float]
31
+ The minimum value of the property.
32
+ max_value : Optional[float]
33
+ The maximum value of the property.
34
+
35
+ Examples
36
+ --------
37
+ Create a segment group that selects segments by diameter:
38
+
39
+ >>> group = SegmentGroup('group1', domains=['dend'], select_by='diam', min_value=1, max_value=2)
40
+ >>> group
41
+ SegmentGroup("group1", domains=['dend'], diam(1, 2))
42
+
43
+ Check if a segment is in the group:
44
+
45
+ >>> segment in group
46
+ True
47
+ """
48
+ name: str
49
+ domains: List[str]
50
+ select_by: Optional[str] = None
51
+ min_value: Optional[float] = None
52
+ max_value: Optional[float] = None
53
+
54
+ def _get_segment_value(self, segment) -> Optional[float]:
55
+ if self.select_by == 'diam':
56
+ return segment.diam
57
+ elif self.select_by == 'section_diam':
58
+ return segment._section._ref.diam
59
+ elif self.select_by == 'distance':
60
+ return segment.path_distance()
61
+ elif self.select_by == 'domain_distance':
62
+ return segment.path_distance(within_domain=True)
63
+ return None
64
+
65
+ def __contains__(self, segment) -> bool:
66
+ if segment.domain not in self.domains:
67
+ return False
68
+ if self.select_by is None:
69
+ return True
70
+
71
+ segment_value = self._get_segment_value(segment)
72
+ return (
73
+ (self.min_value is None or segment_value > self.min_value) and
74
+ (self.max_value is None or segment_value <= self.max_value)
75
+ )
76
+
77
+ def __repr__(self):
78
+ filters = (
79
+ f"{self.select_by}({self.min_value}, {self.max_value})"
80
+ if self.select_by is not None and (self.min_value is not None or self.max_value is not None) else ""
81
+ )
82
+ return f'SegmentGroup("{self.name}", domains={self.domains}' + (f", {filters}" if filters else "") + ')'
83
+
84
+ def to_dict(self) -> Dict:
85
+ """
86
+ Convert the SegmentGroup to a dictionary.
87
+ """
88
+ result = {
89
+ 'name': self.name,
90
+ 'domains': self.domains,
91
+ 'select_by': self.select_by,
92
+ 'min_value': self.min_value,
93
+ 'max_value': self.max_value,
94
+ }
95
+ return {k: v for k, v in result.items() if v is not None}
96
+
97
+ @staticmethod
98
+ def from_dict(data: Dict) -> 'SegmentGroup':
99
+ """
100
+ Create a SegmentGroup from a dictionary.
101
+ """
102
+ return SegmentGroup(**data)
103
+
@@ -0,0 +1,11 @@
1
+ from dendrotweaks.membrane.io.loader import MODFileLoader
2
+ from dendrotweaks.membrane.io.converter import MODFileConverter
3
+
4
+ from dendrotweaks.membrane.io.reader import MODFileReader
5
+ from dendrotweaks.membrane.io.parser import MODFileParser
6
+ from dendrotweaks.membrane.io.code_generators import PythonCodeGenerator
7
+ from dendrotweaks.membrane.io.code_generators import NMODLCodeGenerator
8
+
9
+ from dendrotweaks.membrane.io.factories import create_channel
10
+ from dendrotweaks.membrane.io.factories import create_standard_channel
11
+ from dendrotweaks.membrane.io.factories import standardize_channel