Implementing a model

In this section, an example of a full model implementation is given, using the 1977 paper “Reconstruction of the action potential of ventricular myocardial fibers” by Beeler and Reuter as our guide.

The complete article can be found here.

The model header

Every model definition starts with a header. This header consists of:

  • The model name between double brackets, to indicate the start of the model definition section in the file.

  • Any number of meta-properties

  • The model’s initial state values

Here, we’ll name the model br1977 and add a description and a reference as meta data. The initial values will be added as we go along.

[[model]]
name: Beeler-Reuter 1977
desc: The 1997 Beeler Reuter model of the AP in ventricular myocytes
ref: """
Beeler, Reuter (1977) Reconstruction of the action potential of ventricular
myocardial fibres
"""

Time

Every myokit model needs a variable to represent time. You can call this “t” or “time” or “bob” as long as its explicitly defined. For example:

[environment]
t = 0
    in [ms]

Now to tell the simulation engine to use this as a time variable, we’re going to bind it to the external variable “time”:

[environment]
t = 0 bind time
    in [ms]

This asserts that the time variable should be bound to the external variable “time”. If no such variable is provided by the simulation engine, the default value 0 will be used.

The membrane potential

Like most models, the 1977 Beeler-Reuter model calculates the change in membrane potential as a function of the sum all trans-membrane currents. In myokit, this will look something like this:

[membrane]
C = 1 [uF/cm^2] : The membrane capacitance
dot(V) = -(1/C) * ( ??? )
    in [mV]
    desc: Membrane potential

Here, the equation for V is taken from equation (8) in the paper. The ??? part will be replaced by the names of the currents as we fill them in.

We’ve defined V as a state variable here, so we’ll need to add an initial value. The updated header becomes:

[[model]]
name: Beeler-Reuter 1977
desc: The 1997 Beeler Reuter model of the AP in ventricular myocytes
ref: """
Beeler, Reuter (1977) Reconstruction of the action potential of ventricular
myocardial fibres
"""
# Initial values:
membrane.V = -80

The given value for V is a reasonable guess. Once the model is implemented we can pace for a large number of beats to obtain a new set of initial values on a steady orbit.

Adding currents

The BR1977 model uses a single equation template for all currents. This means we could implement it quickly by defining a template equation and using it for every current.

However, if you look at the table given at the bottom of page 181, you’ll see quite a few zeros and ones, which greatly simplifies the corresponding equations.

In this example, we’ll write every current’s equation out in full. Each current will be placed in its own component and given a description and a unit. The current’s “gates” will be added as state variables and their transition rates will be added as nested variables.

The fast Sodium current: INa

First off, we’ll create a component called ina (in lower case to save some typing effort):

[ina]

The current itself is given in equation (5):

[ina]
INa = (gNaBar * m^3 * h * j + gNaC) * (membrane.V - ENa)
    in [uA/cm^2]
    desc: The excitatory inward sodium current

For this to work we’ll need to add some of the missing variables. Some of these are listed just below equation (5):

gNaBar = 4 [mS/cm^2]
gNaC = 0.003 [mS/cm^2]
ENa = 50 [mV]

Note that we’re using a fixed Nernst potential and a slight leakage current!

Now let’s add the m-gate. This is given in the paper as a differential equation, so we add another state:

dot(m) = alpha * (1 - m) - beta * m

alpha = (membrane.V + 47) / (1 - exp(-0.1 * (membrane.V + 47))) beta = 40 * exp(-0.056 * (membrane.V + 72)) desc: The activation parameter

To reduce the number of times we have to type membrane.V, we define a local alias with:

use membrane.V as V

Our code for m now becomes:

dot(m) =  alpha * (1 - m) - beta * m
    alpha = (V + 47) / (1 - exp(-0.1 * (V + 47)))
    beta  = 40 * exp(-0.056 * (V + 72))
    desc: The activation parameter

The other two gates can be added in a similar fashion, giving us the final result for the ina component:

[ina]
use membrane.V as V
gNaBar = 4 [mS/cm^2]
gNaC = 0.003 [mS/cm^2]
ENa = 50 [mV]
INa = (gNaBar * m^3 * h * j + gNaC) * (V - ENa)
    in [uA/cm^2]
    desc: The excitatory inward sodium current
dot(m) =  alpha * (1 - m) - beta * m
    alpha = (V + 47) / (1 - exp(-0.1 * (V + 47)))
    beta  = 40 * exp(-0.056 * (V + 72))
    desc: The activation parameter
dot(h) =  alpha * (1 - h) - beta * h
    alpha = 0.126 * exp(-0.25 * (V + 77))
    beta  = 1.7 / (1 + exp(-0.082 * (V + 22.5)))
    desc: An inactivation parameter
dot(j) =  alpha * (1 - j) - beta * j
    alpha = 0.055 * exp(-0.25 * (V + 78)) / (1 + exp(-0.2 * (V + 78)))
    beta  = 0.3 / (1 + exp(-0.1 * (V + 32)))
    desc: An nactivation parameter

The state variables m, h and j need initial values. None are provided in the paper so we make another educated guess:

[[model]]
name: Beeler-Reuter 1977
desc: The 1997 Beeler Reuter model of the AP in ventricular myocytes
ref: """
Beeler, Reuter (1976) Reconstruction of the action potential of ventricular
myocardial fibres
"""
# Initial values:
membrane.V = -80
ina.m      = 0.01
ina.h      = 0.99
ina.j      = 0.99

The calcium current

Next up, we add the calcium current following equation (6). Again, we define an alias for membrane.V and make a guess for the initial values. The intracellular calcium also varies depending on the current according to equation (9). To this end we add another state variable and set the initial value to 2e-7, within the range described on page 190:

[isi]
use membrane.V as V
gsBar = 0.09
Es = -82.3 - 13.0287 * log(Cai)
    in [mV]
Isi = gsBar * d * f * (V - Es)
    in [uA/cm^2]
    desc: """
    The slow inward current, primarily carried by calcium ions. Called
    either "iCa" or "is" in the paper.
    """
dot(d) =  alpha * (1 - d) - beta * d
    alpha = 0.095 * exp(-0.01 * (V + -5)) / (exp(-0.072 * (V + -5)) + 1)
    beta  = 0.07 * exp(-0.017 * (V + 44)) / (exp(0.05 * (V + 44)) + 1)
dot(f) = alpha * (1 - f) - beta * f
    alpha = 0.012 * exp(-0.008 * (V + 28)) / (exp(0.15 * (V + 28)) + 1)
    beta  = 0.0065 * exp(-0.02 * (V + 30)) / (exp(-0.2 * (V + 30)) + 1)
dot(Cai) = -1e-7 * Isi + 0.07 * (1e-7 - Cai)
    desc: The intracellular Calcium concentration
    in [mol/L]

The Potassium currents

BR1977 contains a time-independent Potassium current, given by equation (2):

[ik1]
use membrane.V as V
IK1 = 0.35 * (
        4 * (exp(0.04 * (V + 85)) - 1)
        / (exp(0.08 * (V + 53)) + exp(0.04 * (V + 53)))
        + 0.2 * (V + 23)
        / (1 - exp(-0.04 * (V + 23)))
    )
    in [uA/cm^2]
    desc: """A time-independent outward potassium current exhibiting
          inward-going rectification"""

A second Potassium current, ix1, does have some time dependence:

[ix1]
use membrane.V as V
Ix1 = x1 * 0.8 * (exp(0.04 * (V + 77)) - 1) / exp(0.04 * (V + 35))
    in [uA/cm^2]
    desc: """A voltage- and time-dependent outward current, primarily
          carried by potassium ions"""
dot(x1) = alpha * (1 - x1) - beta * x1
    alpha = 0.0005 * exp(0.083 * (V + 50)) / (exp(0.057 * (V + 50)) + 1)
    beta  = 0.0013 * exp(-0.06 * (V + 20)) / (exp(-0.04 * (V + 333)) + 1)

Updating the membrane potential

Now that we have all currents, we can update the equation for the membrane potential:

[membrane]
C = 1 [uF/cm^2] : The membrane capacitance
dot(V) = -(1/C) * (ik1.IK1 + ix1.Ix1 + ina.INa + isi.Isi)
    in [mV]
    desc: Membrane potential

Adding a stimulus

The one thing still missing is a stimulus current. We’ll add this by binding to the pacing mechanism, which is exposed in the standard simulations as “pace”. This variable will be set by the simulation engine using whatever protocol we pass it:

[stimulus]
amplitude = 25 [uA/cm^2]
IStim = pace * amplitude
pace = 0 bind pace

Now we need to set a protocol for our model. To this end, we add a protocol section at the bottom of the file:

[[protocol]]
# Level  Start    Length   Period   Multiplier
1.0      100      2        1000     0

This describes a simple block pulse. Initially, it’s value will be 0.0. But at t=100, this will change: our protocol defines a level of 1.0 at this point that lasts for 2 time units before going down again.

To create a period signal, a period of 1000 is set. Using the multiplier field we could limit the number of pulses, but in this case we’ll set it to 0 to specify an infinite pulse train.

Finalizing the definitions

Following equation (8) we add the new stimulus current to the membrane and provide the last few missing initial values:

[[model]]
name: Beeler-Reuter 1977
desc: The 1997 Beeler Reuter model of the AP in ventricular myocytes
ref: """
Beeler, Reuter (1976) Reconstruction of the action potential of ventricular
myocardial fibres
"""
# Initial values:
membrane.V = -80
ina.m      = 0.01
ina.h      = 0.99
ina.j      = 0.99
isi.d      = 0.01
isi.f      = 0.99
ix1.x1     = 0.0005
isi.Cai    = 2e-7

[environment]
t = 0 bind time
    in [ms]

[stimulus]
amplitude = 25 [uA/cm^2]
IStim = pace * amplitude
pace = 0 bind pace

[membrane]
C = 1 [uF/cm^2] : The membrane capacitance
dot(V) = -(1/C) * (ik1.IK1 + ix1.Ix1 + ina.INa + isi.Isi - stimulus.IStim)
    in [mV]
    desc: Membrane potential

[ina]
use membrane.V as V
gNaBar = 4 [mS/cm^2]
gNaC = 0.003 [mS/cm^2]
ENa = 50 [mV]
INa = (gNaBar * m^3 * h * j + gNaC) * (V - ENa)
    in [uA/cm^2]
    desc: The excitatory inward sodium current
dot(m) =  alpha * (1 - m) - beta * m
    alpha = (V + 47) / (1 - exp(-0.1 * (V + 47)))
    beta  = 40 * exp(-0.056 * (V + 72))
    desc: The activation parameter
dot(h) =  alpha * (1 - h) - beta * h
    alpha = 0.126 * exp(-0.25 * (V + 77))
    beta  = 1.7 / (1 + exp(-0.082 * (V + 22.5)))
    desc: An inactivation parameter
dot(j) =  alpha * (1 - j) - beta * j
    alpha = 0.055 * exp(-0.25 * (V + 78)) / (1 + exp(-0.2 * (V + 78)))
    beta  = 0.3 / (1 + exp(-0.1 * (V + 32)))
    desc: An inactivation parameter

[isi]
use membrane.V as V
gsBar = 0.09
Es = -82.3 - 13.0287 * log(Cai)
    in [mV]
Isi = gsBar * d * f * (V - Es)
    in [uA/cm^2]
    desc: """
    The slow inward current, primarily carried by calcium ions. Called
    either "iCa" or "is" in the paper.
    """
dot(d) =  alpha * (1 - d) - beta * d
    alpha = 0.095 * exp(-0.01 * (V + -5)) / (exp(-0.072 * (V + -5)) + 1)
    beta  = 0.07 * exp(-0.017 * (V + 44)) / (exp(0.05 * (V + 44)) + 1)
dot(f) = alpha * (1 - f) - beta * f
    alpha = 0.012 * exp(-0.008 * (V + 28)) / (exp(0.15 * (V + 28)) + 1)
    beta  = 0.0065 * exp(-0.02 * (V + 30)) / (exp(-0.2 * (V + 30)) + 1)
dot(Cai) = -1e-7 * Isi + 0.07 * (1e-7 - Cai)
    desc: The intracellular Calcium concentration
    in [mol/L]

[ik1]
use membrane.V as V
IK1 = 0.35 * (
        4 * (exp(0.04 * (V + 85)) - 1)
        / (exp(0.08 * (V + 53)) + exp(0.04 * (V + 53)))
        + 0.2 * (V + 23)
        / (1 - exp(-0.04 * (V + 23)))
    )
    in [uA/cm^2]
    desc: """A time-independent outward potassium current exhibiting
          inward-going rectification"""

[ix1]
use membrane.V as V
Ix1 = x1 * 0.8 * (exp(0.04 * (V + 77)) - 1) / exp(0.04 * (V + 35))
    in [uA/cm^2]
    desc: """A voltage- and time-dependent outward current, primarily
          carried by potassium ions"""
dot(x1) = alpha * (1 - x1) - beta * x1
    alpha = 0.0005 * exp(0.083 * (V + 50)) / (exp(0.057 * (V + 50)) + 1)
    beta  = 0.0013 * exp(-0.06 * (V + 20)) / (exp(-0.04 * (V + 333)) + 1)

[[protocol]]
# Level  Start    Length   Period   Multiplier
1.0      100      2        1000     0

Adding a plot script

All that’s left to do now is to add a simple plot script. This is written in plain python using the “magic” methods get_model() and get_protocol() to get the model and protocol from the gui. Then, a simulation is created and run:

[[script]]
import myokit
import matplotlib.pyplot as plt

# Get model & protocol from magic methods
m = get_model()
p = get_protocol()

# Create simulation
s = myokit.Simulation(m, p)

# Run simulation
d = s.run(1000)

# Display the result
t = d['environment.t']
v = d['membrane.V']

plt.figure()
plt.plot(t, v)
plt.show()

The resulting file can now be saved as a plain-text .mmt file and run using the GUI or myokit run. This example can be downloaded from here: br-1977.mmt

../_images/br-1977.png

Result! A single action potential.