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.
- jaxley/__init__.py +13 -0
- jaxley/build_branched_tridiag.py +63 -0
- jaxley/channels/__init__.py +6 -0
- jaxley/channels/channel.py +81 -0
- jaxley/channels/hh.py +98 -0
- jaxley/channels/pospischil.py +377 -0
- jaxley/connect.py +190 -0
- jaxley/integrate.py +183 -0
- jaxley/modules/__init__.py +8 -0
- jaxley/modules/base.py +1921 -0
- jaxley/modules/branch.py +198 -0
- jaxley/modules/cell.py +387 -0
- jaxley/modules/compartment.py +143 -0
- jaxley/modules/network.py +649 -0
- jaxley/optimize/__init__.py +5 -0
- jaxley/optimize/optimizer.py +77 -0
- jaxley/optimize/transforms.py +130 -0
- jaxley/optimize/utils.py +11 -0
- jaxley/solver_gate.py +67 -0
- jaxley/solver_voltage.py +485 -0
- jaxley/stimulus.py +51 -0
- jaxley/synapses/__init__.py +7 -0
- jaxley/synapses/ionotropic.py +68 -0
- jaxley/synapses/synapse.py +99 -0
- jaxley/synapses/tanh_rate.py +49 -0
- jaxley/synapses/test.py +52 -0
- jaxley/utils/__init__.py +2 -0
- jaxley/utils/cell_utils.py +396 -0
- jaxley/utils/colors.py +51 -0
- jaxley/utils/debug_solver.py +219 -0
- jaxley/utils/jax_utils.py +76 -0
- jaxley/utils/misc_utils.py +35 -0
- jaxley/utils/plot_utils.py +40 -0
- jaxley/utils/swc.py +320 -0
- jaxley/utils/syn_utils.py +42 -0
- jaxley-0.1.0.dist-info/LICENSE +202 -0
- jaxley-0.1.0.dist-info/METADATA +25 -0
- jaxley-0.1.0.dist-info/RECORD +75 -0
- jaxley-0.1.0.dist-info/WHEEL +5 -0
- jaxley-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +2 -0
- tests/jaxley_identical/__init__.py +5 -0
- tests/jaxley_identical/test_basic_modules.py +263 -0
- tests/jaxley_identical/test_grad.py +106 -0
- tests/jaxley_identical/test_radius_and_length.py +237 -0
- tests/jaxley_identical/test_swc.py +148 -0
- tests/jaxley_vs_neuron/__init__.py +5 -0
- tests/jaxley_vs_neuron/test_branch.py +268 -0
- tests/jaxley_vs_neuron/test_cell.py +123 -0
- tests/jaxley_vs_neuron/test_comp.py +107 -0
- tests/swc_files/asc2swc.py +12 -0
- tests/test_api_equivalence.py +188 -0
- tests/test_cell_matches_branch.py +77 -0
- tests/test_channels.py +166 -0
- tests/test_clamp.py +72 -0
- tests/test_composability_of_modules.py +94 -0
- tests/test_connection.py +203 -0
- tests/test_data_feeding.py +84 -0
- tests/test_distance.py +54 -0
- tests/test_grad.py +85 -0
- tests/test_groups.py +158 -0
- tests/test_indexing.py +230 -0
- tests/test_license.py +27 -0
- tests/test_make_trainable.py +421 -0
- tests/test_moving.py +204 -0
- tests/test_optimize.py +123 -0
- tests/test_pickle.py +32 -0
- tests/test_plotting_api.py +155 -0
- tests/test_record_and_stimulate.py +123 -0
- tests/test_shared_state.py +216 -0
- tests/test_solver.py +19 -0
- tests/test_swc.py +246 -0
- tests/test_syn.py +54 -0
- tests/test_synapse_indexing.py +217 -0
- 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
|