See code in GitLab.
Author: Edward Vigmond edward.vigmond@u-bordeaux.fr & Moritz Linder moritz.linder@kit.edu
CellML is a markup language for describing, amongst other things, ionic models. Many models are published and available from the CellML Model Repository. However, this format can only be processed by a machine. openCARP uses its own markup language called EasyML.
Conversion of a CellML file is performed using the cellml_converter.py
script:
usage: cellml_converter.py [-h] [--Vm VM] [--Istim ISTIM] [--out OUT]
[--trace-all] [--plugin] [--params]
[--keep-redundant]
CMLfile
Convert CellML 1.0 to model format
positional arguments:
CMLfile CellML file
optional arguments:
-h, --help show this help message and exit
--Vm VM transmembrane voltage variable [V]
--Istim ISTIM stimulus current variable [i_Stim]
--out OUT output file
--trace-all trace all variables
--plugin is a plug-in, not a whole cell model
--params parameterize all constants
--keep-redundant keep redundant differential expressions, eg alpha, beta
and diff)
The flags mean the following
Flag | Meaning |
---|---|
Vm | the variable name in the CellML file referring to transmembrane voltage. |
Istim | the CellML variable referring to the stimulus current. This will be removed. |
out | output file which will be processed by limpet_fe.py |
trace-all | add all variables to the trace so they can be monitored |
plugin | not a stand alone ionic model, but a plug-in |
params | make all constants modifiable as parameters |
keep-redundant | keep all differential equations, even if some are not needed and redundant |
The process of creating an EasyML file is not entirely automated. Manual intervention is always necessary. Generally, the process to create a model file is as follows:
Note that variable names may be changed since EasyML demands global uniqueness while CellML only demands section uniqueness. Thus, in CellML, there may be two a variables, but in two different channels. They will be renamed to avoid the conflict in EasyML, and variables are grouped together by section after conversion.
We will begin by converting a relatively simple model, the LuoRudy II model, which is shipped with this example in the file LuoRudy94.cellml
.
The python scripts in the following commands need to be called with their full path if they are not in your $PATH
environment variable.
They are shipped with the openCARP source code and located in the subfolder physics/limpet/src/python
. To compile new ionic models, we highly
recommend to build openCARP from source as described in the installation instructions on the webpage. You'll need to enable dynamic model support during
the build process to follow this example. In the following, we'll assume that the openCARP source code is located in /home/openCARP
but
this can be easily adjusted according to your local installation.
Run the converter to produce the EasyML model file:
/home/openCARP/physics/limpet/src/python/cellml_converter.py LuoRudy94.cellml > LuoRudy94.model
Run the model to C translator:
/home/openCARP/physics/limpet/src/python/limpet_fe.py LuoRudy94.model /home/openCARP/physics/limpet/models/imp_list.txt /home/openCARP/physics/limpet/src/imps_src
/home/openCARP/physics/limpet/src/make_dynamic_model.sh LuoRudy94
You will see the following output containing error messages:
Syntax error at line 79, token '{'
Warning, you should only use alpha/beta OR tau/infinity while formulating gates!
Using the tau infinity values.
Error with d
Warning, you should only use alpha/beta OR tau/infinity while formulating gates!
Using the tau infinity values.
Error with f
Traceback (most recent call last):
File "/home/openCARP/physics/limpet/src/python/limpet_fe.py", line 1581, in <module>
obj.printSource()
File "/home/openCARP/physics/limpet/src/python/limpet_fe.py", line 786, in printSource
declare_type = "double ",
File "/home/openCARP/physics/limpet/src/python/limpet_fe.py", line 201, in printEquations
for var in eqnmap.getList(target, valid):
File "/home/openCARP/physics/limpet/src/python/im.py", line 220, in getList
for d in self[sym].depend:
File "/home/openCARP/physics/limpet/src/python/basic.py", line 87, in __getitem__
return self._map[key]
KeyError: Var("I_st")
Open up LuoRudy94.model in the text editor of your choice. Based on the error output, look at line 79:
dV_dt = ((I_st - (i_Na+i_Ca_L+i_K+i_K1+i_Kp+i_NaCa+i_p_Ca+i_Na_b+i_Ca_b+i_NaK+i_ns_Ca))/Cm); .units(mV/ms);
I_st = ((({http://www.w3.org/1998/Math/MathML}rem:time,stimPeriod)<stimDuration) ? stimCurrent : 0.0); .units(uA/mm^2);
It appears that I_st is the stimulus current which we would like to remove. We can manually remove it by deleting line 79 and
eliminating it from line 78, or we can run /home/openCARP/physics/limpet/src/python/cellml_converter.py --Istim=I_st LuoRudy94.cellml > LuoRudy94.model
Now we are left with errors with gating variables d and f. Locate them around line 106 in the LuoRudy94.model
file. Note how alpha, beta, tau and _inf are
defined for all. By inspecting, we see that alpha_d and alpha_f are defined using the infinity and tau values so the alphas and betas can be removed.
Save the file, and rerun /home/openCARP/physics/limpet/src/python/limpet_fe.py LuoRudy94.model /home/openCARP/physics/limpet/models/imp_list.txt /home/openCARP/physics/limpet/src/imps_src
which should now have no errors.
Make the linkable library and run it to be sure:
/home/openCARP/physics/limpet/src/make_dynamic_model.sh LuoRudy94
bench --load ./LuoRudy94.so
Now, we will compile the Grandi (a.k.a. Grandi-Pasqualini-Bers) model (Grandi.cellml
),
which is quite complicated.
Run the EasyML generator:
/home/openCARP/physics/limpet/src/python/cellml_converter.py Grandi.cellml > Grandi.model
WARNING: stimulus current not found
WARNING: transmembrane voltage not found
So, not every author uses the same names for the same quantities. Inspecting the model file, we can see that transmembrane voltage is called V_m and the stimulating current is I_app. Rerun the converter:
/home/openCARP/physics/limpet/src/python/cellml_converter.py --Vm=V_m --Istim=I_app Grandi.cellml > Grandi.model
Generate the C code:
/home/openCARP/physics/limpet/src/python/limpet_fe.py Grandi.model /tmp/openCARP/physics/limpet/models/imp_list.txt /tmp/openCARP/physics/limpet/src/imps_src
which does not complain but when we try to run the model :
bench --load ./Grandi.so
we get NaNs which is not good. So, at this point we need to rely on intuition and a good debugger. First we reduce the time step and hope it is just an integration issue with stiff equations:
bench --load ./Grandi.so --dt=0.005
which luckily works. We do not want to be restricted to a 5 microsecond timestep so we need to investigate further. The most common issues are
In this particular case, the sodium channel, I_Na on line 170, is definitely a culprit. After inspection, we see diff_ defined as well as redundant alpha/beta/tau/_inf. This is what I finally changed it to:
#I_Na
I_Na = (I_Na_junc+I_Na_sl); .units(uA/uF);
I_Na_junc = (Fjunc*GNa*(m*m*m)*h*j*(V_m - ena_junc)); .units(uA/uF);
I_Na_sl = (Fsl*GNa*(m*m*m)*h*j*(V_m - ena_sl)); .units(uA/uF);
ah = ((V_m>=-40.) ? 0. : (0.057*exp((-(V_m+80.)/6.8)))); .units(unitless);
tau_h = (1./(ah+bh)); .units(ms);
bh = ((V_m>=-40.) ? (0.77/(0.13*(1.+exp((-(V_m+10.66)/11.1))))) : ((2.7*exp((0.079*V_m)))+(3.1e5*exp((0.3485*V_m))))); .units(unitless);
h_inf = (1./((1.+exp(((V_m+71.55)/7.43)))*(1.+exp(((V_m+71.55)/7.43))))); .units(unitless);
tau_m = ((0.1292*exp(-(((V_m+45.79)/15.54)*((V_m+45.79)/15.54))))+(0.06487*exp(-(((V_m - 4.823)/51.12)*((V_m - 4.823)/51.12))))); .units(ms);
m_inf = (1./((1.+exp((-(56.86+V_m)/9.03)))*(1.+exp((-(56.86+V_m)/9.03))))); .units(unitless);
bj = ((V_m>=-40.) ? ((0.6*exp((0.057*V_m)))/(1.+exp((-0.1*(V_m+32.))))) : ((0.02424*exp((-0.01052*V_m)))/(1.+exp((-0.1378*(V_m+40.14)))))); .units(unitless);
aj = ((V_m>=-40.) ? 0. : ((((-2.5428e4*exp((0.2444*V_m))) - (6.948e-6*exp((-0.04391*V_m))))*(V_m+37.78))/(1.+exp((0.311*(V_m+79.23)))))); .units(unitless);
tau_j = (1./(aj+bj)); .units(ms);
j_inf = (1./((1.+exp(((V_m+71.55)/7.43)))*(1.+exp(((V_m+71.55)/7.43))))); .units(unitless);
We try to run it again and we still have an issue. As mentioned above, Markov chains and calcium handling can be numercially stiff. We will try more expensive integration methods at the cost of some speed. I added the following code to Grandi.model :
group {
Ca_i;
Ca_j;
Ca_sl;
Ca_sr;
f_Ca_Bj;
f_Ca_Bsl;
}.method(cvode);
group {
Ry_Ri;
Ry_Ro;
Ry_Rr;
}.method(markov_be);
This time, we can compile and run it. However, we are not done. Let's try to improve performance by using a lookup table for the voltage functions. We change line 2 of the model file to be :
V_m; .external(Vm); .nodal();.lookup(-100,100,0.05);
We generate the C file, make the linkable library and when we try to run it:
bench --load ./Grandi.so --stim-start=100 --dt=0.005 -Oa
L2 : LUT WARNING: Grandi V_m=-5 produces -nan in entry number 1!
L2 : LUT WARNING: Grandi V_m=-5 produces -nan in entry number 2!
Outputting the following quantities at each time:
Time Vm Iion
[CVODE ERROR] CVode
At t = 283.5 and h = 1.90735e-08, the corrector convergence test failed repeatedly or with |h| = hmin.
bench: /home/openCARP/physics/limpet/src/imps_src/Grandi.cc:868: Grandi: Assertion `CVODE_flag == 0' failed.
Hmmm. Maybe the NaN in the lookup table triggers the error. Sure enough, we see that entry for the d-gate time constant of I_Ca, tau_d, has a singularity at -5 mV and that is where the simulation failed. We need to use L'Hopital's rule for that voltage:
tau_d = (V_m==-5.)?(-d_inf/6./0.035):((1.*d_inf*(1. - exp((-(V_m+5.)/6.))))/(0.035*(V_m+5.))); .units(ms);
d_inf = (1./(1.+exp((-(V_m+5.)/6.)))); .units(unitless);
This model file </downloads/Grandi.good.model>
now works!!!! For further practice, determine which subset of Ca variables can be removed from the group and/or
if another integration method can be used.
Another more advanced and complex model to convert is the Severi (a.k.a. Severi-Fantini-Charawi-DiFrancesco) model (severi_fantini_charawi_difrancesco_2012.cellml
).
The following steps will guide you through the process of model conversion.
Run the EasyML generator:
/home/openCARP/physics/limpet/src/python/cellml_converter.py severi_fantini_charawi_difrancesco_2012.cellml > Severi.model
WARNING: stimulus current not found
WARNING: transmembrane voltage not found
You are already famillar with the inconsistencies of naming for the same quantities by different authors. Thus, we need to inspect the model file in order to figure out that the transmembrane voltage is called V_ode and there is no stimulus current applied. Rerun the converter with the adapted command:
/home/openCARP/physics/limpet/src/python/cellml_converter.py --Vm=V_ode severi_fantini_charawi_difrancesco_2012.cellml > Severi.model
For now the warning about the stimulus current can be ignored.
The next step is trying to run the C translator:
/home/openCARP/physics/limpet/src/python/limpet_fe.py Severi.model /home/openCARP/physics/limpet/models/imp_list.txt /home/openCARP/physics/limpet/src/imps_src
/home/openCARP/physics/limpet/src/make_dynamic_model.sh Severi
The following error messages are received:
Warning, you should only use alpha/beta OR tau/infinity while formulating gates!
Using the tau infinity values.
Error with dL
Warning, you should only use alpha/beta OR tau/infinity while formulating gates!
Using the tau infinity values.
Error with n
Warning, you should only use alpha/beta OR tau/infinity while formulating gates!
Using the tau infinity values.
Error with a
Error, don't assign to variables marked as differential variables
{Var("pi")}
Some of these error messages are similar to the ones of the earlier examples, so let us tackle them step by step. First you need to open the (Severi.model
) file in a text editor of your choice.
Starting with the gating variables dL, n and a, we have to be more careful this time. Typically, tau is defined as 1/(alpha+beta). However, by taking a closer look at the definition used by
Severi et al. we can observe that tau_dL is specified differently. Locate tau_dL in around line 204. So, in this particular case, it is advised to rename alpha and beta while keeping tau
and infinity, otherwise the dimension of tau_dL will not be correct. This can be done, for example, by adding the prefix i_CaL_dL_gate_ to alpha_dL and beta_dL. For the n and a gate this
would not be necessary, but for consistency it is advised to proceed similar.
Since pi is marked as a differential variable related to the i_Kr_pi_gate, we also need to refactor the constant pi into e.g. PI. Similarly, the names time and do cannot be used, so just rename them into t and var_do, respectively. Afterwards, we can save the file and rerun the C translator:
/home/openCARP/physics/limpet/src/python/limpet_fe.py Severi.model /home/openCARP/physics/limpet/models/imp_list.txt /home/openCARP/physics/limpet/src/imps_src
/home/openCARP/physics/limpet/src/make_dynamic_model.sh Severi
We receive another error:
KeyError: Var("Kc")
By inspecting the supplementary material, we can see that somehow a mistake as occurred in the translation of the equations containing Kc. Refactoring Kc into Ko will sort that error out. Running the C translation again leaves us with 3 warnings about the deprecated *sprintf' function which we can ignore. Nevertheless, when you run the model and plot the transmembrane voltage no action potentials arise. This means that there is still something incorrect in this model file. We now reached a good point for recaptioning some theoretical background. Severi et al. modelled a sinoatrial node cell of a rabbit. The sinoatrial node is the origin of the heart beat and therefore action potentials should arise autonomously. Now a little bit more experience is needed to overcome the remaining issues.
In the cellML file, all time-based variables are implemented in seconds, but openCARP uses milliseconds to calculate the next step of the simulation. As there is no flag to set or other opportunity, the conversion from seconds into milliseconds has to be executed manually. The easiest way is to directly convert the constants defined in the beginning of the model file and use the converted values in the equations. Another difficulty is based on the unit C/mol of the constant F, which in si-units is equal to As/mol. F is defined approximately in line 8. We need to check the units of all summands of the equations containing F. For the variables diff_Ca_sub and diff_Nai, it appears to be necessary to multiply F by the factor 1000. In addition, the constant F is also used in a factor called RTONF, in which the seconds are reduced. For us, this results in no further time-related model adaptions.
Unforunately, we are still not finished. The last error results from the definition of the current Iion, which is located around line 130. Upon closer inspection, it is obvious that the units of Iion and i_tot are unequal. To overcome the last inconsistency, the unit of Iion should be changed to nA/uF.
Finally we can test our model. This time we will use a different option than in the previous examples. Put your .model file in your local physics/limpet/models
folder and execute the following commands in
root directory of openCARP:
cmake -S. -B_build -DUPDATE_IMPLIST=ON
cmake --build _build
This will configure CMake with updated imp_list.txt and run the CMake building process to generate the .h and .cc files for your model in physics/limpet/src/imps_src
.
The easiest way to check the correctness is plotting some variables of a simulation. Namely, this could be the transmembrane voltage which is V_ode as well as some currents and compare them to the graphs given in the paper. Coming back to the sinoatrial node cells and the fact that they excite without any stimulus current it is the final step to tell openCARP to simulate without a stimulus current. This can be realised by adding --stim-cur 0.0. If you keep the stimulus current the cell will arise action potentials shortly after the stimulus.
Please don't forget to add metadata to your newly generated .model file. See the shipped .model files <https://git.opencarp.org/openCARP/openCARP/-/tree/master/physics/limpet/models> for examples.
© Copyright 2020 openCARP project Supported by DFG and EuroHPC Contact Imprint and data protection