The Hodgkin-Huxley model¶
Here we examine the canonical Hodgkin-Huxley model (Hodgkin and Huxley, 1952). An uncertainty analysis of this model has been performed previously (Valderrama et al., 2015), and we here we repeat a part of that study using Uncertainpy.
The here used version of the Hodgkin-Huxley model has 11 parameters:
Parameter | Value | Unit | Meaning |
---|---|---|---|
\(V_{0}\) | -10 | mV | Initial voltage |
\(C_\mathrm{m}\) | 1 | \(\text{F}/\text{cm}^2\) | Membrane capacitance |
\(\bar{g}_{\mathrm{Na}}\) | 120 | \(\text{mS/cm}^2\) | Sodium (Na) conductance |
\(\bar{g}_{\mathrm{K}}\) | 36 | \(\text{mS/cm}^2\) | Potassium (K) conductance |
\(\bar{g}_{\mathrm{I}}\) | 0.3 | \(\text{mS/cm}^2\) | Leak current conductance |
\(E_\mathrm{Na}\) | 112 | mV | Sodium equilibrium potential |
\(E_\mathrm{K}\) | -12 | mV | Potassium equilibrium potential |
\(E_\mathrm{I}\) | 10.613 | mV | Leak current equilibrium potential |
\(n_0\) | 0.0011 | Initial potassium activation gating variable | |
\(m_0\) | 0.0003 | Initial sodium activation gating variable | |
\(h_0\) | 0.9998 | Initial sodium inactivation gating variable |
As in the previous study, we assume each of these parameters have a uniform distribution in the range \(\pm 10\%\) around their original value.
We use uncertainty quantification and sensitivity analysis to explore how this parameter uncertainty affect the model output, i.e., the action potential response of the neural membrane potential \(V_m\) to an external current injection. The model was exposed to a continuous external stimulus of \(140 \mu \mathrm{A/cm}^2\) starting at \(t = 0\), and we examined the membrane potential in the time window between \(t\) = 5 and 15 ms
As in the cooling coffee cup example,
we implement the Hodgkin-Huxley model as a Python function
(found in /examples/valderrama/valderrama.py
):
import uncertainpy as un
import numpy as np
from scipy.integrate import odeint
# External stimulus
def I(time):
return 140 # micro A/cm**2
def valderrama(V_0=-10,
C_m=1,
gbar_Na=120,
gbar_K=36,
gbar_L=0.3,
E_Na=112,
E_K=-12,
E_l=10.613,
m_0=0.0011,
n_0=0.0003,
h_0=0.9998):
# Setup time
end_time = 15 # ms
dt = 0.025 # ms
time = np.arange(0, end_time + dt, dt)
# K channel
def alpha_n(V):
return 0.01*(10 - V)/(np.exp((10 - V)/10.) - 1)
def beta_n(V):
return 0.125*np.exp(-V/80.)
def n_f(n, V):
return alpha_n(V)*(1 - n) - beta_n(V)*n
def n_inf(V):
return alpha_n(V)/(alpha_n(V) + beta_n(V))
# Na channel (activating)
def alpha_m(V):
return 0.1*(25 - V)/(np.exp((25 - V)/10.) - 1)
def beta_m(V):
return 4*np.exp(-V/18.)
def m_f(m, V):
return alpha_m(V)*(1 - m) - beta_m(V)*m
def m_inf(V):
return alpha_m(V)/(alpha_m(V) + beta_m(V))
# Na channel (inactivating)
def alpha_h(V):
return 0.07*np.exp(-V/20.)
def beta_h(V):
return 1/(np.exp((30 - V)/10.) + 1)
def h_f(h, V):
return alpha_h(V)*(1 - h) - beta_h(V)*h
def h_inf(V):
return alpha_h(V)/(alpha_h(V) + beta_h(V))
def dXdt(X, t):
V, h, m, n = X
g_Na = gbar_Na*(m**3)*h
g_K = gbar_K*(n**4)
g_l = gbar_L
dmdt = m_f(m, V)
dhdt = h_f(h, V)
dndt = n_f(n, V)
dVdt = (I(t) - g_Na*(V - E_Na) - g_K*(V - E_K) - g_l*(V - E_l))/C_m
return [dVdt, dhdt, dmdt, dndt]
initial_conditions = [V_0, h_0, m_0, n_0]
X = odeint(dXdt, initial_conditions, time)
values = X[:, 0]
# Only return from 5 seconds onwards, as in the Valderrama paper
values = values[time > 5]
time = time[time > 5]
# Add info needed by certain spiking features and efel features
info = {"stimulus_start": time[0], "stimulus_end": time[-1]}
return time, values, info
We use this function when we perform the uncertainty quantification and
sensitivity analysis
(found in /examples/valderrama/uq_valderrama.py
).
We first initialize our model:
# Initialize the model
model = un.Model(run=valderrama,
labels=["Time (ms)", "Membrane potential (mV)"])
Then we create the set of parameters:
# Define a parameter dictionary
parameters = {"V_0": -10,
"C_m": 1,
"gbar_Na": 120,
"gbar_K": 36,
"gbar_L": 0.3,
"m_0": 0.0011,
"n_0": 0.0003,
"h_0": 0.9998,
"E_Na": 112,
"E_K": -12,
"E_l": 10.613}
# Create the parameters
parameters = un.Parameters(parameters)
We use set_all_distributions()
and
uniform()
to give all parameters a uniform
distribution in the range \(\pm 10\%\) around their fixed value.
# Set all parameters to have a uniform distribution
# within a 20% interval around their fixed value
parameters.set_all_distributions(un.uniform(0.2))
set_all_distributions
sets the distribution of all parameters.
If it receives a function as input,
it gives that function the fixed value of each parameter,
and expects to receive Chaospy functions.
uniform
is a closure.
It takes interval as input and returns a function which takes the
fixed_value of each parameter as input and returns a Chaospy distribution with this
interval around the fixed_value.
Ultimately the distribution of each parameter is set to interval around their
fixed_value:
cp.Uniform(fixed_value - abs(interval/2.*fixed_value),
fixed_value + abs(interval/2.*fixed_value)).
We can now use polynomial chaos expansions with point collocation to calculate the uncertainty and sensitivity of the model. We also set the seed to easier be able to reproduce the result.
# Perform the uncertainty quantification
UQ = un.UncertaintyQuantification(model,
parameters=parameters)
# We set the seed to easier be able to reproduce the result
data = UQ.quantify(seed=10)
The complete code for the uncertainty quantification and sensitivity becomes:
import uncertainpy as un
import chaospy as cp
from valderrama import valderrama
# Initialize the model
model = un.Model(run=valderrama,
labels=["Time (ms)", "Membrane potential (mV)"])
# Define a parameter dictionary
parameters = {"V_0": -10,
"C_m": 1,
"gbar_Na": 120,
"gbar_K": 36,
"gbar_L": 0.3,
"m_0": 0.0011,
"n_0": 0.0003,
"h_0": 0.9998,
"E_Na": 112,
"E_K": -12,
"E_l": 10.613}
# Create the parameters
parameters = un.Parameters(parameters)
# Set all parameters to have a uniform distribution
# within a 20% interval around their fixed value
parameters.set_all_distributions(un.uniform(0.2))
# Perform the uncertainty quantification
UQ = un.UncertaintyQuantification(model,
parameters=parameters)
# We set the seed to easier be able to reproduce the result
data = UQ.quantify(seed=10)