jaxley 0.1.0__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 (75) hide show
  1. jaxley/__init__.py +13 -0
  2. jaxley/build_branched_tridiag.py +63 -0
  3. jaxley/channels/__init__.py +6 -0
  4. jaxley/channels/channel.py +81 -0
  5. jaxley/channels/hh.py +98 -0
  6. jaxley/channels/pospischil.py +377 -0
  7. jaxley/connect.py +190 -0
  8. jaxley/integrate.py +183 -0
  9. jaxley/modules/__init__.py +8 -0
  10. jaxley/modules/base.py +1921 -0
  11. jaxley/modules/branch.py +198 -0
  12. jaxley/modules/cell.py +387 -0
  13. jaxley/modules/compartment.py +143 -0
  14. jaxley/modules/network.py +649 -0
  15. jaxley/optimize/__init__.py +5 -0
  16. jaxley/optimize/optimizer.py +77 -0
  17. jaxley/optimize/transforms.py +130 -0
  18. jaxley/optimize/utils.py +11 -0
  19. jaxley/solver_gate.py +67 -0
  20. jaxley/solver_voltage.py +485 -0
  21. jaxley/stimulus.py +51 -0
  22. jaxley/synapses/__init__.py +7 -0
  23. jaxley/synapses/ionotropic.py +68 -0
  24. jaxley/synapses/synapse.py +99 -0
  25. jaxley/synapses/tanh_rate.py +49 -0
  26. jaxley/synapses/test.py +52 -0
  27. jaxley/utils/__init__.py +2 -0
  28. jaxley/utils/cell_utils.py +396 -0
  29. jaxley/utils/colors.py +51 -0
  30. jaxley/utils/debug_solver.py +219 -0
  31. jaxley/utils/jax_utils.py +76 -0
  32. jaxley/utils/misc_utils.py +35 -0
  33. jaxley/utils/plot_utils.py +40 -0
  34. jaxley/utils/swc.py +320 -0
  35. jaxley/utils/syn_utils.py +42 -0
  36. jaxley-0.1.0.dist-info/LICENSE +202 -0
  37. jaxley-0.1.0.dist-info/METADATA +25 -0
  38. jaxley-0.1.0.dist-info/RECORD +75 -0
  39. jaxley-0.1.0.dist-info/WHEEL +5 -0
  40. jaxley-0.1.0.dist-info/top_level.txt +2 -0
  41. tests/__init__.py +2 -0
  42. tests/jaxley_identical/__init__.py +5 -0
  43. tests/jaxley_identical/test_basic_modules.py +263 -0
  44. tests/jaxley_identical/test_grad.py +106 -0
  45. tests/jaxley_identical/test_radius_and_length.py +237 -0
  46. tests/jaxley_identical/test_swc.py +148 -0
  47. tests/jaxley_vs_neuron/__init__.py +5 -0
  48. tests/jaxley_vs_neuron/test_branch.py +268 -0
  49. tests/jaxley_vs_neuron/test_cell.py +123 -0
  50. tests/jaxley_vs_neuron/test_comp.py +107 -0
  51. tests/swc_files/asc2swc.py +12 -0
  52. tests/test_api_equivalence.py +188 -0
  53. tests/test_cell_matches_branch.py +77 -0
  54. tests/test_channels.py +166 -0
  55. tests/test_clamp.py +72 -0
  56. tests/test_composability_of_modules.py +94 -0
  57. tests/test_connection.py +203 -0
  58. tests/test_data_feeding.py +84 -0
  59. tests/test_distance.py +54 -0
  60. tests/test_grad.py +85 -0
  61. tests/test_groups.py +158 -0
  62. tests/test_indexing.py +230 -0
  63. tests/test_license.py +27 -0
  64. tests/test_make_trainable.py +421 -0
  65. tests/test_moving.py +204 -0
  66. tests/test_optimize.py +123 -0
  67. tests/test_pickle.py +32 -0
  68. tests/test_plotting_api.py +155 -0
  69. tests/test_record_and_stimulate.py +123 -0
  70. tests/test_shared_state.py +216 -0
  71. tests/test_solver.py +19 -0
  72. tests/test_swc.py +246 -0
  73. tests/test_syn.py +54 -0
  74. tests/test_synapse_indexing.py +217 -0
  75. tests/test_transforms.py +78 -0
jaxley/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ # This file is part of Jaxley, a differentiable neuroscience simulator. Jaxley is
2
+ # licensed under the Apache License Version 2.0, see <https://www.apache.org/licenses/>
3
+
4
+ from jaxley.connect import (
5
+ connect,
6
+ connectivity_matrix_connect,
7
+ fully_connect,
8
+ sparse_connect,
9
+ )
10
+ from jaxley.integrate import integrate
11
+ from jaxley.modules import *
12
+ from jaxley.optimize import ParamTransform
13
+ from jaxley.stimulus import datapoint_to_step_currents, step_current
@@ -0,0 +1,63 @@
1
+ # This file is part of Jaxley, a differentiable neuroscience simulator. Jaxley is
2
+ # licensed under the Apache License Version 2.0, see <https://www.apache.org/licenses/>
3
+
4
+ from math import pi
5
+
6
+ import jax.numpy as jnp
7
+ from jax import lax, vmap
8
+
9
+
10
+ def define_all_tridiags(
11
+ voltages: jnp.ndarray,
12
+ voltage_terms: jnp.asarray,
13
+ i_ext: jnp.ndarray,
14
+ num_branches: int,
15
+ coupling_conds_upper: float,
16
+ coupling_conds_lower: float,
17
+ summed_coupling_conds: float,
18
+ dt: float,
19
+ ):
20
+ """
21
+ Set up tridiagonal system for each branch.
22
+ """
23
+ voltages = jnp.reshape(voltages, (num_branches, -1))
24
+
25
+ voltage_terms = jnp.reshape(voltage_terms, (num_branches, -1))
26
+ i_ext = jnp.reshape(i_ext, (num_branches, -1))
27
+
28
+ lowers, diags, uppers, solves = vmap(
29
+ _define_tridiag_for_branch, in_axes=(0, 0, 0, None, 0, 0, 0)
30
+ )(
31
+ voltages,
32
+ voltage_terms,
33
+ i_ext,
34
+ dt,
35
+ coupling_conds_upper,
36
+ coupling_conds_lower,
37
+ summed_coupling_conds,
38
+ )
39
+
40
+ return (lowers, diags, uppers, solves)
41
+
42
+
43
+ def _define_tridiag_for_branch(
44
+ voltages: jnp.ndarray,
45
+ voltage_terms: jnp.ndarray,
46
+ i_ext: jnp.ndarray,
47
+ dt: float,
48
+ coupling_conds_upper: float,
49
+ coupling_conds_lower: float,
50
+ summed_coupling_conds: float,
51
+ ):
52
+ """
53
+ Defines the tridiagonal system to solve for a single branch.
54
+ """
55
+
56
+ # Diagonal and solve.
57
+ a_v = 1.0 + dt * voltage_terms + dt * summed_coupling_conds
58
+ b_v = voltages + dt * i_ext
59
+
60
+ # Subdiagonals.
61
+ upper = jnp.asarray(-dt * coupling_conds_upper)
62
+ lower = jnp.asarray(-dt * coupling_conds_lower)
63
+ return lower, a_v, upper, b_v
@@ -0,0 +1,6 @@
1
+ # This file is part of Jaxley, a differentiable neuroscience simulator. Jaxley is
2
+ # licensed under the Apache License Version 2.0, see <https://www.apache.org/licenses/>
3
+
4
+ from jaxley.channels.channel import Channel
5
+ from jaxley.channels.hh import HH
6
+ from jaxley.channels.pospischil import CaL, CaT, K, Km, Leak, Na
@@ -0,0 +1,81 @@
1
+ # This file is part of Jaxley, a differentiable neuroscience simulator. Jaxley is
2
+ # licensed under the Apache License Version 2.0, see <https://www.apache.org/licenses/>
3
+
4
+ from abc import ABC, abstractmethod
5
+ from typing import Dict, Optional, Tuple
6
+
7
+ import jax.numpy as jnp
8
+
9
+
10
+ class Channel:
11
+ """Channel base class. All channels inherit from this class.
12
+
13
+ As in NEURON, a `Channel` is considered a distributed process, which means that its
14
+ conductances are to be specified in `S/cm2` and its currents are to be specified in
15
+ `uA/cm2`."""
16
+
17
+ _name = None
18
+ channel_params = None
19
+ channel_states = None
20
+ current_name = None
21
+
22
+ def __init__(self, name: Optional[str] = None):
23
+ self._name = name if name else self.__class__.__name__
24
+
25
+ @property
26
+ def name(self) -> Optional[str]:
27
+ """The name of the channel (by default, this is the class name)."""
28
+ return self._name
29
+
30
+ def change_name(self, new_name: str):
31
+ """Change the channel name.
32
+
33
+ Args:
34
+ new_name: The new name of the channel.
35
+
36
+ Returns:
37
+ Renamed channel, such that this function is chainable.
38
+ """
39
+ old_prefix = self._name + "_"
40
+ new_prefix = new_name + "_"
41
+
42
+ self._name = new_name
43
+ self.channel_params = {
44
+ (
45
+ new_prefix + key[len(old_prefix) :]
46
+ if key.startswith(old_prefix)
47
+ else key
48
+ ): value
49
+ for key, value in self.channel_params.items()
50
+ }
51
+
52
+ self.channel_states = {
53
+ (
54
+ new_prefix + key[len(old_prefix) :]
55
+ if key.startswith(old_prefix)
56
+ else key
57
+ ): value
58
+ for key, value in self.channel_states.items()
59
+ }
60
+ return self
61
+
62
+ def update_states(
63
+ self, states, dt, v, params
64
+ ) -> Tuple[jnp.ndarray, Tuple[jnp.ndarray, jnp.ndarray]]:
65
+ """Return the updated states."""
66
+ raise NotImplementedError
67
+
68
+ def compute_current(
69
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
70
+ ):
71
+ """Given channel states and voltage, return the current through the channel.
72
+
73
+ Args:
74
+ states: All states of the compartment.
75
+ v: Voltage of the compartment in mV.
76
+ params: Parameters of the channel (conductances in `S/cm2`).
77
+
78
+ Returns:
79
+ Current in `uA/cm2`.
80
+ """
81
+ raise NotImplementedError
jaxley/channels/hh.py ADDED
@@ -0,0 +1,98 @@
1
+ # This file is part of Jaxley, a differentiable neuroscience simulator. Jaxley is
2
+ # licensed under the Apache License Version 2.0, see <https://www.apache.org/licenses/>
3
+
4
+ from typing import Dict, Optional
5
+
6
+ import jax.numpy as jnp
7
+
8
+ from jaxley.channels import Channel
9
+ from jaxley.solver_gate import save_exp, solve_gate_exponential
10
+
11
+
12
+ class HH(Channel):
13
+ """Hodgkin-Huxley channel."""
14
+
15
+ def __init__(self, name: Optional[str] = None):
16
+ super().__init__(name)
17
+ prefix = self._name
18
+ self.channel_params = {
19
+ f"{prefix}_gNa": 0.12,
20
+ f"{prefix}_gK": 0.036,
21
+ f"{prefix}_gLeak": 0.0003,
22
+ f"{prefix}_eNa": 50.0,
23
+ f"{prefix}_eK": -77.0,
24
+ f"{prefix}_eLeak": -54.3,
25
+ }
26
+ self.channel_states = {
27
+ f"{prefix}_m": 0.2,
28
+ f"{prefix}_h": 0.2,
29
+ f"{prefix}_n": 0.2,
30
+ }
31
+ self.current_name = f"i_HH"
32
+
33
+ def update_states(
34
+ self,
35
+ states: Dict[str, jnp.ndarray],
36
+ dt,
37
+ v,
38
+ params: Dict[str, jnp.ndarray],
39
+ ):
40
+ """Return updated HH channel state."""
41
+ prefix = self._name
42
+ m, h, n = states[f"{prefix}_m"], states[f"{prefix}_h"], states[f"{prefix}_n"]
43
+ new_m = solve_gate_exponential(m, dt, *self.m_gate(v))
44
+ new_h = solve_gate_exponential(h, dt, *self.h_gate(v))
45
+ new_n = solve_gate_exponential(n, dt, *self.n_gate(v))
46
+ return {f"{prefix}_m": new_m, f"{prefix}_h": new_h, f"{prefix}_n": new_n}
47
+
48
+ def compute_current(
49
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
50
+ ):
51
+ """Return current through HH channels."""
52
+ prefix = self._name
53
+ m, h, n = states[f"{prefix}_m"], states[f"{prefix}_h"], states[f"{prefix}_n"]
54
+
55
+ # Multiply with 1000 to convert Siemens to milli Siemens.
56
+ gNa = params[f"{prefix}_gNa"] * (m**3) * h * 1000 # mS/cm^2
57
+ gK = params[f"{prefix}_gK"] * n**4 * 1000 # mS/cm^2
58
+ gLeak = params[f"{prefix}_gLeak"] * 1000 # mS/cm^2
59
+
60
+ return (
61
+ gNa * (v - params[f"{prefix}_eNa"])
62
+ + gK * (v - params[f"{prefix}_eK"])
63
+ + gLeak * (v - params[f"{prefix}_eLeak"])
64
+ )
65
+
66
+ def init_state(self, v, params):
67
+ """Initialize the state such at fixed point of gate dynamics."""
68
+ prefix = self._name
69
+ alpha_m, beta_m = self.m_gate(v)
70
+ alpha_h, beta_h = self.h_gate(v)
71
+ alpha_n, beta_n = self.n_gate(v)
72
+ return {
73
+ f"{prefix}_m": alpha_m / (alpha_m + beta_m),
74
+ f"{prefix}_h": alpha_h / (alpha_h + beta_h),
75
+ f"{prefix}_n": alpha_n / (alpha_n + beta_n),
76
+ }
77
+
78
+ @staticmethod
79
+ def m_gate(v):
80
+ alpha = 0.1 * _vtrap(-(v + 40), 10)
81
+ beta = 4.0 * save_exp(-(v + 65) / 18)
82
+ return alpha, beta
83
+
84
+ @staticmethod
85
+ def h_gate(v):
86
+ alpha = 0.07 * save_exp(-(v + 65) / 20)
87
+ beta = 1.0 / (save_exp(-(v + 35) / 10) + 1)
88
+ return alpha, beta
89
+
90
+ @staticmethod
91
+ def n_gate(v):
92
+ alpha = 0.01 * _vtrap(-(v + 55), 10)
93
+ beta = 0.125 * save_exp(-(v + 65) / 80)
94
+ return alpha, beta
95
+
96
+
97
+ def _vtrap(x, y):
98
+ return x / (save_exp(x / y) - 1.0)
@@ -0,0 +1,377 @@
1
+ # This file is part of Jaxley, a differentiable neuroscience simulator. Jaxley is
2
+ # licensed under the Apache License Version 2.0, see <https://www.apache.org/licenses/>
3
+
4
+ from typing import Dict, Optional
5
+
6
+ import jax.numpy as jnp
7
+
8
+ from jaxley.channels import Channel
9
+ from jaxley.solver_gate import (
10
+ save_exp,
11
+ solve_gate_exponential,
12
+ solve_inf_gate_exponential,
13
+ )
14
+
15
+ # This is an implementation of Pospischil channels:
16
+ # Leak, Na, K, Km, CaT, CaL
17
+ # [Pospischil et al. Biological Cybernetics (2008)]
18
+
19
+ __all__ = ["Leak", "Na", "K", "Km", "CaT", "CaL"]
20
+
21
+
22
+ # Helper function
23
+ def efun(x):
24
+ """x/[exp(x)-1]
25
+
26
+ Args:
27
+ x (float): _description_
28
+
29
+ Returns:
30
+ float: x/[exp(x)-1]
31
+ """
32
+ return x / (save_exp(x) - 1.0)
33
+
34
+
35
+ class Leak(Channel):
36
+ """Leak current"""
37
+
38
+ def __init__(self, name: Optional[str] = None):
39
+ super().__init__(name)
40
+ prefix = self._name
41
+ self.channel_params = {
42
+ f"{prefix}_gLeak": 1e-4,
43
+ f"{prefix}_eLeak": -70.0,
44
+ }
45
+ self.channel_states = {}
46
+ self.current_name = f"i_{prefix}"
47
+
48
+ def update_states(
49
+ self,
50
+ states: Dict[str, jnp.ndarray],
51
+ dt,
52
+ v,
53
+ params: Dict[str, jnp.ndarray],
54
+ ):
55
+ """No state to update."""
56
+ return {}
57
+
58
+ def compute_current(
59
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
60
+ ):
61
+ """Return current."""
62
+ prefix = self._name
63
+ # Multiply with 1000 to convert Siemens to milli Siemens.
64
+ gLeak = params[f"{prefix}_gLeak"] * 1000 # mS/cm^2
65
+ return gLeak * (v - params[f"{prefix}_eLeak"])
66
+
67
+ def init_state(self, v, params):
68
+ return {}
69
+
70
+
71
+ class Na(Channel):
72
+ """Sodium channel"""
73
+
74
+ def __init__(self, name: Optional[str] = None):
75
+ super().__init__(name)
76
+ prefix = self._name
77
+ self.channel_params = {
78
+ f"{prefix}_gNa": 50e-3,
79
+ "eNa": 50.0,
80
+ "vt": -60.0, # Global parameter, not prefixed with `Na`.
81
+ }
82
+ self.channel_states = {f"{prefix}_m": 0.2, f"{prefix}_h": 0.2}
83
+ self.current_name = f"i_Na"
84
+
85
+ def update_states(
86
+ self,
87
+ states: Dict[str, jnp.ndarray],
88
+ dt,
89
+ v,
90
+ params: Dict[str, jnp.ndarray],
91
+ ):
92
+ """Update state."""
93
+ prefix = self._name
94
+ m, h = states[f"{prefix}_m"], states[f"{prefix}_h"]
95
+ new_m = solve_gate_exponential(m, dt, *self.m_gate(v, params["vt"]))
96
+ new_h = solve_gate_exponential(h, dt, *self.h_gate(v, params["vt"]))
97
+ return {f"{prefix}_m": new_m, f"{prefix}_h": new_h}
98
+
99
+ def compute_current(
100
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
101
+ ):
102
+ """Return current."""
103
+ prefix = self._name
104
+ m, h = states[f"{prefix}_m"], states[f"{prefix}_h"]
105
+
106
+ # Multiply with 1000 to convert Siemens to milli Siemens.
107
+ gNa = params[f"{prefix}_gNa"] * (m**3) * h * 1000 # mS/cm^2
108
+
109
+ current = gNa * (v - params["eNa"])
110
+ return current
111
+
112
+ def init_state(self, v, params):
113
+ """Initialize the state such at fixed point of gate dynamics."""
114
+ prefix = self._name
115
+ alpha_m, beta_m = self.m_gate(v, params["vt"])
116
+ alpha_h, beta_h = self.h_gate(v, params["vt"])
117
+ return {
118
+ f"{prefix}_m": alpha_m / (alpha_m + beta_m),
119
+ f"{prefix}_h": alpha_h / (alpha_h + beta_h),
120
+ }
121
+
122
+ @staticmethod
123
+ def m_gate(v, vt):
124
+ v_alpha = v - vt - 13.0
125
+ alpha = 0.32 * efun(-0.25 * v_alpha) / 0.25
126
+
127
+ v_beta = v - vt - 40.0
128
+ beta = 0.28 * efun(0.2 * v_beta) / 0.2
129
+ return alpha, beta
130
+
131
+ @staticmethod
132
+ def h_gate(v, vt):
133
+ v_alpha = v - vt - 17.0
134
+ alpha = 0.128 * save_exp(-v_alpha / 18.0)
135
+
136
+ v_beta = v - vt - 40.0
137
+ beta = 4.0 / (save_exp(-v_beta / 5.0) + 1.0)
138
+ return alpha, beta
139
+
140
+
141
+ class K(Channel):
142
+ """Potassium channel"""
143
+
144
+ def __init__(self, name: Optional[str] = None):
145
+ super().__init__(name)
146
+ prefix = self._name
147
+ self.channel_params = {
148
+ f"{prefix}_gK": 5e-3,
149
+ "eK": -90.0,
150
+ "vt": -60.0, # Global parameter, not prefixed with `Na`.
151
+ }
152
+ self.channel_states = {f"{prefix}_n": 0.2}
153
+ self.current_name = f"i_K"
154
+
155
+ def update_states(
156
+ self,
157
+ states: Dict[str, jnp.ndarray],
158
+ dt,
159
+ v,
160
+ params: Dict[str, jnp.ndarray],
161
+ ):
162
+ """Update state."""
163
+ prefix = self._name
164
+ n = states[f"{prefix}_n"]
165
+ new_n = solve_gate_exponential(n, dt, *self.n_gate(v, params["vt"]))
166
+ return {f"{prefix}_n": new_n}
167
+
168
+ def compute_current(
169
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
170
+ ):
171
+ """Return current."""
172
+ prefix = self._name
173
+ n = states[f"{prefix}_n"]
174
+
175
+ # Multiply with 1000 to convert Siemens to milli Siemens.
176
+ gK = params[f"{prefix}_gK"] * (n**4) * 1000 # mS/cm^2
177
+
178
+ return gK * (v - params["eK"])
179
+
180
+ def init_state(self, v, params):
181
+ """Initialize the state such at fixed point of gate dynamics."""
182
+ prefix = self._name
183
+ alpha_n, beta_n = self.n_gate(v, params["vt"])
184
+ return {f"{prefix}_n": alpha_n / (alpha_n + beta_n)}
185
+
186
+ @staticmethod
187
+ def n_gate(v, vt):
188
+ v_alpha = v - vt - 15.0
189
+ alpha = 0.032 * efun(-0.2 * v_alpha) / 0.2
190
+
191
+ v_beta = v - vt - 10.0
192
+ beta = 0.5 * save_exp(-v_beta / 40.0)
193
+ return alpha, beta
194
+
195
+
196
+ class Km(Channel):
197
+ """Slow M Potassium channel"""
198
+
199
+ def __init__(self, name: Optional[str] = None):
200
+ super().__init__(name)
201
+ prefix = self._name
202
+ self.channel_params = {
203
+ f"{prefix}_gKm": 0.004e-3,
204
+ f"{prefix}_taumax": 4000.0,
205
+ f"eK": -90.0,
206
+ }
207
+ self.channel_states = {f"{prefix}_p": 0.2}
208
+ self.current_name = f"i_K"
209
+
210
+ def update_states(
211
+ self,
212
+ states: Dict[str, jnp.ndarray],
213
+ dt,
214
+ v,
215
+ params: Dict[str, jnp.ndarray],
216
+ ):
217
+ """Update state."""
218
+ prefix = self._name
219
+ p = states[f"{prefix}_p"]
220
+ new_p = solve_inf_gate_exponential(
221
+ p, dt, *self.p_gate(v, params[f"{prefix}_taumax"])
222
+ )
223
+ return {f"{prefix}_p": new_p}
224
+
225
+ def compute_current(
226
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
227
+ ):
228
+ """Return current."""
229
+ prefix = self._name
230
+ p = states[f"{prefix}_p"]
231
+
232
+ # Multiply with 1000 to convert Siemens to milli Siemens.
233
+ gKm = params[f"{prefix}_gKm"] * p * 1000 # mS/cm^2
234
+ return gKm * (v - params["eK"])
235
+
236
+ def init_state(self, v, params):
237
+ """Initialize the state such at fixed point of gate dynamics."""
238
+ prefix = self._name
239
+ alpha_p, beta_p = self.p_gate(v, params[f"{prefix}_taumax"])
240
+ return {f"{prefix}_p": alpha_p / (alpha_p + beta_p)}
241
+
242
+ @staticmethod
243
+ def p_gate(v, taumax):
244
+ v_p = v + 35.0
245
+ p_inf = 1.0 / (1.0 + save_exp(-0.1 * v_p))
246
+
247
+ tau_p = taumax / (3.3 * save_exp(0.05 * v_p) + save_exp(-0.05 * v_p))
248
+
249
+ return p_inf, tau_p
250
+
251
+
252
+ class CaL(Channel):
253
+ """L-type Calcium channel"""
254
+
255
+ def __init__(self, name: Optional[str] = None):
256
+ super().__init__(name)
257
+ prefix = self._name
258
+ self.channel_params = {
259
+ f"{prefix}_gCaL": 0.1e-3,
260
+ "eCa": 120.0,
261
+ }
262
+ self.channel_states = {f"{prefix}_q": 0.2, f"{prefix}_r": 0.2}
263
+ self.current_name = f"i_Ca"
264
+
265
+ def update_states(
266
+ self,
267
+ states: Dict[str, jnp.ndarray],
268
+ dt,
269
+ v,
270
+ params: Dict[str, jnp.ndarray],
271
+ ):
272
+ """Update state."""
273
+ prefix = self._name
274
+ q, r = states[f"{prefix}_q"], states[f"{prefix}_r"]
275
+ new_q = solve_gate_exponential(q, dt, *self.q_gate(v))
276
+ new_r = solve_gate_exponential(r, dt, *self.r_gate(v))
277
+ return {f"{prefix}_q": new_q, f"{prefix}_r": new_r}
278
+
279
+ def compute_current(
280
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
281
+ ):
282
+ """Return current."""
283
+ prefix = self._name
284
+ q, r = states[f"{prefix}_q"], states[f"{prefix}_r"]
285
+
286
+ # Multiply with 1000 to convert Siemens to milli Siemens.
287
+ gCaL = params[f"{prefix}_gCaL"] * (q**2) * r * 1000 # mS/cm^2
288
+
289
+ return gCaL * (v - params["eCa"])
290
+
291
+ def init_state(self, v, params):
292
+ """Initialize the state such at fixed point of gate dynamics."""
293
+ prefix = self._name
294
+ alpha_q, beta_q = self.q_gate(v)
295
+ alpha_r, beta_r = self.r_gate(v)
296
+ return {
297
+ f"{prefix}_q": alpha_q / (alpha_q + beta_q),
298
+ f"{prefix}_r": alpha_r / (alpha_r + beta_r),
299
+ }
300
+
301
+ @staticmethod
302
+ def q_gate(v):
303
+ v_alpha = -v - 27.0
304
+ alpha = 0.055 * efun(v_alpha / 3.8) * 3.8
305
+
306
+ v_beta = -v - 75.0
307
+ beta = 0.94 * save_exp(v_beta / 17.0)
308
+ return alpha, beta
309
+
310
+ @staticmethod
311
+ def r_gate(v):
312
+ v_alpha = -v - 13.0
313
+ alpha = 0.000457 * save_exp(v_alpha / 50)
314
+
315
+ v_beta = -v - 15.0
316
+ beta = 0.0065 / (save_exp(v_beta / 28.0) + 1)
317
+ return alpha, beta
318
+
319
+
320
+ class CaT(Channel):
321
+ """T-type Calcium channel"""
322
+
323
+ def __init__(self, name: Optional[str] = None):
324
+ super().__init__(name)
325
+ prefix = self._name
326
+ self.channel_params = {
327
+ f"{prefix}_gCaT": 0.4e-4,
328
+ f"{prefix}_vx": 2.0,
329
+ "eCa": 120.0, # Global parameter, not prefixed with `CaT`.
330
+ }
331
+ self.channel_states = {f"{prefix}_u": 0.2}
332
+ self.current_name = f"i_Ca"
333
+
334
+ def update_states(
335
+ self,
336
+ states: Dict[str, jnp.ndarray],
337
+ dt,
338
+ v,
339
+ params: Dict[str, jnp.ndarray],
340
+ ):
341
+ """Update state."""
342
+ prefix = self._name
343
+ u = states[f"{prefix}_u"]
344
+ new_u = solve_inf_gate_exponential(
345
+ u, dt, *self.u_gate(v, params[f"{prefix}_vx"])
346
+ )
347
+ return {f"{prefix}_u": new_u}
348
+
349
+ def compute_current(
350
+ self, states: Dict[str, jnp.ndarray], v, params: Dict[str, jnp.ndarray]
351
+ ):
352
+ """Return current."""
353
+ prefix = self._name
354
+ u = states[f"{prefix}_u"]
355
+ s_inf = 1.0 / (1.0 + save_exp(-(v + params[f"{prefix}_vx"] + 57.0) / 6.2))
356
+
357
+ # Multiply with 1000 to convert Siemens to milli Siemens.
358
+ gCaT = params[f"{prefix}_gCaT"] * (s_inf**2) * u * 1000 # mS/cm^2
359
+
360
+ return gCaT * (v - params["eCa"])
361
+
362
+ def init_state(self, v, params):
363
+ """Initialize the state such at fixed point of gate dynamics."""
364
+ prefix = self._name
365
+ alpha_u, beta_u = self.u_gate(v, params[f"{prefix}_vx"])
366
+ return {f"{prefix}_u": alpha_u / (alpha_u + beta_u)}
367
+
368
+ @staticmethod
369
+ def u_gate(v, vx):
370
+ v_u1 = v + vx + 81.0
371
+ u_inf = 1.0 / (1.0 + save_exp(v_u1 / 4))
372
+
373
+ tau_u = (30.8 + (211.4 + save_exp((v + vx + 113.2) / 5.0))) / (
374
+ 3.7 * (1 + save_exp((v + vx + 84.0) / 3.2))
375
+ )
376
+
377
+ return u_inf, tau_u