Hodgkin-Huxley 1: Creating a model using the API¶
By the time you have worked through this tutorial you will be able to:
Assemble a model using the API;
Use the diagnostic Validator class to identify errors in the model’s syntax;
Use the diagnostic Analyser class to identify errors in the model’s mathematical construction; and
Serialise the model to CellML format for output.
C++ resources
CMakeLists.txtThe CMake file for building this tutorial;createGateModel.cppEither the skeleton code, or ..createGateModel_completed.cppthe completed tutorial code; andutilities.cppandutilities.hHelper functions.
Python resources
createGateModel.pyEither the skeleton code, or ..createGateModel_completed.pythe completed tutorial code.
Overview¶
This is the first tutorial in a series designed to walk the user through some of libCellML’s functionality available in the API. Its goal is to create from scratch a voltage-independent ion gate model. The theory of this kind of gate can be found on the Ion Gate theory page.
One of the goals of the CellML format (which must be supported by libCellML) is the construction of models that are reusable. Throughout these tutorials we will create entities in such a way as to enable their reuse as easily as possible. The basic structure of this model highlights that too.
We will create a model with three components:
An equations component, which contains all of the working pieces and mathematics specific to the model;
A parameters component, which contains any hard-coded parameters or values specific to this model; and
A wrapper component, which is the parent of the other two.
This arrangement means that it’s simple to import this model into others, as well as to locate or over-ride parameter values or reuse equations. Each of the components created throughout this series of tutorials will follow this same structure.
Step 1: Set up the model¶
1.a The first step is to create a Model item which will later contain the component and the units it needs.
Use the create function to make a new instance.
1.b Each CellML element must have a name, which is set using the setName function.
Set the name of the model to be “GateModel”.
We’ll create a wrapper component whose only job is to encapsulate the other components. This makes is a lot easier for this model to be reused, as the connections between components internal to this one won’t need to be re-established. Note that the constructor for all named CellML entities is overloaded, so you can pass it the name string at the time of creation.
1.c Create a new Component item named “gate” using the overloaded constructor.
Finally we need to add the component to the model. This sets it at the top-level of the component encapsulation hierarchy. All other components will then need to be added to this component, rather than to the model.
1.d Add the component to the model using the addComponent function.
A utility function printModel(Model, bool) (C++) or print_model (Python) has been provided to help you to see what’s going on inside your model.
Setting the second optional parameter to true will also print the MathML content.
1.e Print your model to the terminal and check that the structure is what you’d expect.
Show C++ snippet
// 1.a
// The first step is to create a Model item which will later contain the component and
// the units it needs.
auto model = libcellml::Model::create();
// 1.b
// Each CellML element must have a name, which is set using the setName() function.
model->setName("GateModel");
// 1.c
// We'll create a wrapper component whose only job is to encapsulate the other components.
// This makes is a lot easier for this model to be reused, as the connections between
// components internal to this one won't need to be re-established.
// Note that the constructor for all named CellML entities is overloaded, so
// you can pass it the name string at the time of creation.
// Create a component named "gate".
auto gate = libcellml::Component::create("gate");
// 1.d
// Finally we need to add the component to the model. This sets it at the top-level of
// the components' encapsulation hierarchy. All other components need to be added
// to this component, rather than the model.
// Add the component to the model using the Model::addComponent() function.
model->addComponent(gate);
// 1.e
// Print the model to the terminal using the printModel helper function and
// check it is what you'd expect.
printModel(model);
Show Python snippet
# 1.a
# The first step is to create a Model item which will later contain the component and
# the units it needs.
model = Model()
# 1.b
# Each CellML element must have a name, which is set using the setName() function.
model.setName('GateModel')
# 1.c
# We'll create a wrapper component whose only job is to encapsulate the other components.
# This makes is a lot easier for this model to be reused, as the connections between
# components internal to this one won't need to be re-established.
# Note that the constructor for all named CellML entities is overloaded, so
# you can pass it the name string at the time of creation.
# Create a component named 'gate'.
gate = Component('gate')
# 1.d Finally we need to add the component to the model. This sets it at the top-level of
# the components' encapsulation hierarchy. All other components need to be added
# to this component, rather than the model.
# Add the component to the model using the Model::addComponent() function.
model.addComponent(gate)
# 1.e
# Print the model to the terminal using the print_model helper function and
# check it is what you'd expect.
print_model(model)
MODEL: 'GateModel'
UNITS: 0 custom units
COMPONENTS: 1 components
[0]: gate
VARIABLES: 0 variables
Step 2: Create the gateEquations component¶
Inside the wrapper component you created in Step 1 we need to create two more: an equations component, and a parameters component. In this step we’ll construct the equations component.
2.a Create a new equations component named “gateEquations”.
2.b Add the new gateEquations component to the gate component.
Show C++ snippet
// 2.a
// Create a gateEquations component and name it "gateEquations" .
auto gateEquations = libcellml::Component::create("gateEquations");
// 2.b
// Add the new gateEquations component to the gate component.
gate->addComponent(gateEquations);
Show Python snippet
# 2.a
# Create a gateEquations component and name it 'gateEquations'.
gateEquations = Component('gateEquations')
# 2.b
# Add the new gateEquations component to the gate component.
gate.addComponent(gateEquations)
Since this is an equations-flavoured component, it should contain the bulk of the calculations and mathematics for the gate. Maths is added using MathML2 (no other levels are supported) strings.
In this example we need to represent just one equation:
If you’re happy to write your own MathML2 string then please go ahead, but if you’d rather not you can use the code provided under the code toggles further down the page.
2.c Construct a string representing the MathML of the equation above.
You will need to enclose the string with the appropriate header and footer.
These are provided for you in the skeleton code, or simply copy them from below.
Use the setMath and appendMath functions to add your strings to the equations component.
2.d Print the model to the terminal and include the optional second argument of true to include the MathML.
Show C++ snippet
// 2.c
// Add the mathematics to the gateEquations component.
std::string equation =
" <apply><eq/>\n"
" <apply><diff/>\n"
" <bvar><ci>t</ci></bvar>\n"
" <ci>X</ci>\n"
" </apply>\n"
" <apply><minus/>\n"
" <apply><times/>\n"
" <ci>alpha_X</ci>\n"
" <apply><minus/>\n"
" <cn cellml:units=\"dimensionless\">1</cn>\n"
" <ci>X</ci>\n"
" </apply>\n"
" </apply>\n"
" <apply><times/>\n"
" <ci>beta_X</ci>\n"
" <ci>X</ci>\n"
" </apply>\n"
" </apply>\n"
" </apply>\n";
gateEquations->setMath(mathHeader);
gateEquations->appendMath(equation);
gateEquations->appendMath(mathFooter);
// 2.d
// Print the model to the terminal and include the optional second argument of true
// to include the MathML.
printModel(model, true);
Show Python snippet
# 2.c
# Add the mathematics to the gateEquations component.
equation = \
' <apply><eq/>\n'\
' <apply><diff/>\n'\
' <bvar><ci>t</ci></bvar>\n'\
' <ci>X</ci>\n'\
' </apply>\n'\
' <apply><minus/>\n'\
' <apply><times/>\n'\
' <ci>alpha_X</ci>\n'\
' <apply><minus/>\n'\
' <cn cellml:units="dimensionless">1</cn>\n'\
' <ci>X</ci>\n'\
' </apply>\n'\
' </apply>\n'\
' <apply><times/>\n'\
' <ci>beta_X</ci>\n'\
' <ci>X</ci>\n'\
' </apply>\n'\
' </apply>\n'\
' </apply>\n'
gateEquations.setMath(math_header)
gateEquations.appendMath(equation)
gateEquations.appendMath(math_footer)
# 2.d
# Print the model to the terminal using the print_model helper function and
# check it is what you'd expect. Include the second argument as True so that
# the maths is included.
print_model(model, True)
MODEL: 'GateModel'
UNITS: 0 custom units
COMPONENTS: 1 components
[0]: gate
VARIABLES: 0 variables
COMPONENT gate has 1 child components:
[0]: gateEquations
VARIABLES: 0 variables
Maths in the component is:
<math xmlns="http://www.w3.org/1998/Math/MathML" xmlns:cellml="http://www.cellml.org/cellml/2.0#">
<apply><eq/>
<apply><diff/>
<bvar><ci>t</ci></bvar>
<ci>X</ci>
</apply>
<apply><minus/>
<apply><times/>
<ci>alpha_X</ci>
<apply><minus/>
<cn cellml:units="dimensionless">1</cn>
<ci>X</ci>
</apply>
</apply>
<apply><times/>
<ci>beta_X</ci>
<ci>X</ci>
</apply>
</apply>
</apply>
</math>
Step 3: Validate the model¶
Once the mathematics has been added to the component, and the component to the model, we can make use of the diagnostic messages within the Validator class to tell us what else needs to be done.
3.a Create a Validator instance, and pass it your model for processing using the validateModel function.
Show C++ snippet
// 3.a
// Create a Validator instance, and pass it your model for processing using the
// validateModel function.
auto validator = libcellml::Validator::create();
validator->validateModel(model);
Show Python snippet
# 3.a
# Create a Validator instance, and pass it your model for processing using the
# validateModel function.
validator = Validator()
validator.validateModel(model)
Calling the validator does not return anything: we have to go looking for issues that it found during processing.
When a problem is found, an Issue item is created containing:
a description string explaining the problem;
a URL at which more information is available;
an std::any item relevant to the problem, if available;
a level indicator; and
a cause indicator relevant to the stored item.
We can use these issues as we need to. The simplest way is to print the descriptions to the terminal.
Two helper functions have been provided for this tutorial that will help printing the string equivalent of enumerated values to the terminal. These are:
C++
getIssueLevelFromEnum; andgetCellmlElementTypeFromEnum.
Python
get_issue_level_from_enum; andget_cellml_element_type_from_enum.
3.b Retrieve the number of issues encountered using the issueCount function on the validator, then retrieve the issue items from the validator using their index and the issue(index) function.
Print the information from each issue to the terminal.
Show C++ snippet
// 3.b
// Retrieve the number of issues encountered using the validator->issueCount() function,
// then retrieve the issue items from the validator using their index and the validator->issue(index)
// function. Print the information from each issue to the terminal.
std::cout << "The validator has found " << validator->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < validator->issueCount(); ++i) {
auto issue = validator->issue(i);
std::cout << "Issue " << i << ": " << issue->description() << std::endl;
std::cout << " reference: "<< issue->referenceHeading() << std::endl;
std::cout << " see: " << issue->url() << std::endl;
std::cout << " stored item type: " << getCellmlElementTypeFromEnum(issue->cellmlElementType()) << std::endl;
std::cout << std::endl;
}
Show Python snippet
# 3.b
# Retrieve the number of issues encountered using the validator.issueCount() function,
# then retrieve the issue items from the validator using their index and the validator.issue(index)
# function. Print the information from each issue to the terminal.
print('The validator has found {} issues.'.format(validator.issueCount()))
for i in range(0, validator.issueCount()):
issue = validator.issue(i)
ref = issue.referenceHeading()
print('Issue [{}] is {}:'.format(i, get_issue_level_from_enum(issue.level())))
print(' description: {}'.format(issue.description()))
if ref != '':
print(' see section {} in the CellML specification.'.format(ref))
print(' stored item type: {}'.format(get_cellml_element_type_from_enum(issue.cellmlElementType())))
The validator has found 6 issues.
Issue [0] is an ERROR:
description: MathML ci element has the child text 't' which does not correspond with any variable names present in component 'gateEquations'.
see section 2.12.3 in the CellML specification.
stored item type: MATHML
Issue [1] is an ERROR:
description: MathML ci element has the child text 'X' which does not correspond with any variable names present in component 'gateEquations'.
see section 2.12.3 in the CellML specification.
stored item type: MATHML
Issue [2] is an ERROR:
description: MathML ci element has the child text 'alpha_X' which does not correspond with any variable names present in component 'gateEquations'.
see section 2.12.3 in the CellML specification.
stored item type: MATHML
Issue [3] is an ERROR:
description: MathML ci element has the child text 'X' which does not correspond with any variable names present in component 'gateEquations'.
see section 2.12.3 in the CellML specification.
stored item type: MATHML
Issue [4] is an ERROR:
description: MathML ci element has the child text 'beta_X' which does not correspond with any variable names present in component 'gateEquations'.
see section 2.12.3 in the CellML specification.
stored item type: MATHML
Issue [5] is an ERROR:
description: MathML ci element has the child text 'X' which does not correspond with any variable names present in component 'gateEquations'.
see section 2.12.3 in the CellML specification.
stored item type: MATHML
Step 4: Add the variables¶
The issues reported by the validator are related to the MathML string that we entered in Step 2 requiring variables which don’t yet exist. These must be created, named, and added to their parent component.
4.a Create Variable items whose names match those listed above.
Add these missing variables to the equations component.
Show C++ snippet
// 4.a
// Create items for the missing variables and add them to the gateEquations component.
// You will need to be sure to give them names which match exactly those reported by the
// validator, or are present in the MathML string.
gateEquations->addVariable(libcellml::Variable::create("t"));
gateEquations->addVariable(libcellml::Variable::create("alpha_X"));
gateEquations->addVariable(libcellml::Variable::create("beta_X"));
gateEquations->addVariable(libcellml::Variable::create("X"));
Show Python snippet
# 4.a
# Create items for the missing variables and add them to the gateEquations component.
# You will need to be sure to give them names which match exactly those reported by the
# validator, or are present in the MathML string.
gateEquations.addVariable(Variable('t'))
gateEquations.addVariable(Variable('alpha_X'))
gateEquations.addVariable(Variable('beta_X'))
gateEquations.addVariable(Variable('X'))
Helper functions for retrieving and printing any issues from any of the logger classes:
printIssues(C++)print_issues(Python)
4.b Validate the model again.
Note that you can use the helper function printIssues with the validator as the argument to save repeating the code from 3.b each time.
Expect errors relating to missing units.
Show C++ snippet
// 4.b
// Validate again, and expect errors relating to missing units.
// Note that you can use the helper function printIssues(validator) to print your
// issues to the screen instead of repeating the code from 3.b.
validator->validateModel(model);
printIssues(validator);
Show Python snippet
# 4.b
# Validate again, and expect errors relating to missing units.
# Note that you can use the helper function print_issues(validator) to print your
# issues to the screen instead of repeating the code from 3.b.
validator.validateModel(model)
print_issues(validator)
Issue [0] is an ERROR:
description: CellML identifiers must contain one or more basic Latin alphabetic characters.
see section 1.3.1.1 in the CellML specification.
stored item type: UNDEFINED
Issue [1] is an ERROR:
description: Variable 't' in component 'gateEquations' does not have a valid units attribute. The attribute given is ''.
see section 2.8.1.2 in the CellML specification.
stored item type: VARIABLE
... etc ...
Step 5: Add the units¶
The validator has reported that the four variables are missing units attributes. In this example none of the units exist yet, so we need to create all of them.
The variables’ units should be:
t, time has units of milliseconds
X, gate status has units of dimensionless
alpha_X and beta_X, rates, have units of per millisecond.
5.a Create two new Units items with names of “ms” and “per_ms”.
These will represent units of milliseconds and per millisecond respectively.
Some basic units have been defined and built into libCellML, others you can define by combining the built-in ones using scaling factors and exponents, or you can define your own from scratch if need be.
Please read the Understanding units page for more detailed information, but for now let’s look at a simple example that defines a Units item representing millivolts.
// Declare, name, and define a millivolt units item.
auto mV = libcellml::Units::create("mV");
// The manner of specification here is agnostic: all three definitions are identical.
mV->addUnit("volt", "milli"); // reference unit, built-in prefix string
// OR
mV->addUnit("volt", -3); // reference unit, prefix as an integer
// OR
mV->addUnit("volt", 1.0, 1, 0.001); // reference unit, prefix, exponent, multiplier
from libcellml import Units
# Declare, name, and define a millivolt units item.
mV = Units("mV")
# The manner of specification here is agnostic: all three definitions are identical.
mV.addUnit("second", "milli") # reference unit and built-in prefix
# OR
mV.addUnit("second", -3) # reference unit, prefix
# OR
mV.addUnit("second", 1, 1.0, 0.001) # reference unit, prefix, exponent, multiplier
5.b Following the example above, define the units of millisecond and per millisecond by adding the appropriate unit items.
Show C++ snippet
// 5.a
// Create the units which will be needed by your variables and add them to the model.
auto ms = libcellml::Units::create("ms");
auto per_ms = libcellml::Units::create("per_ms");
// 5.b
// Add Unit items to the units you created to define them.
ms->addUnit("second", "milli");
per_ms->addUnit("second", "milli", -1);
Show Python snippet
# 5.a
# Create the units which will be needed by your variables and add them to the model.
ms = Units('ms')
per_ms = Units('per_ms')
# 5.b
# Add Unit items to the units you created to define them.
ms.addUnit('second', 'milli')
per_ms.addUnit('second', 'milli', -1)
5.c Add the units to the model (not the component) so that other components can make use of them too.
5.d Use the setUnits function to associate the units you’ve created with the appropriate variables.
5.e Validate again, and expect no errors.
5.f Print the model to the terminal and check it’s what you’d expect.
Show C++ snippet
// 5.c
// Add the Units to the model (not the component) so that other components can make
// use of them too.
model->addUnits(ms);
model->addUnits(per_ms);
// 5.d
// Use the setUnits function to associate them with the appropriate variables.
gateEquations->variable("t")->setUnits(ms);
gateEquations->variable("alpha_X")->setUnits(per_ms);
gateEquations->variable("beta_X")->setUnits(per_ms);
gateEquations->variable("X")->setUnits("dimensionless");
// 5.e
// Validate again, and expect no errors.
validator->validateModel(model);
printIssues(validator);
// 5.f
// Print the model to the terminal and include the optional second argument of true
// to include the MathML.
printModel(model, true);
Show Python snippet
# 5.c
# Add the Units to the model (not the component) so that other components can make
# use of them too.
model.addUnits(ms)
model.addUnits(per_ms)
# 5.d
# Use the setUnits function to associate them with the appropriate variables.
gateEquations.variable('t').setUnits(ms)
gateEquations.variable('alpha_X').setUnits(per_ms)
gateEquations.variable('beta_X').setUnits(per_ms)
gateEquations.variable('X').setUnits('dimensionless')
# 5.e
# Validate again, and expect no errors.
validator.validateModel(model)
print_issues(validator)
MODEL: 'GateModel'
UNITS: 2 custom units
[0]: ms
[1]: per_ms
COMPONENTS: 1 components
[0]: gate
VARIABLES: 0 variables
COMPONENT gate has 1 child components:
[0]: gateEquations
VARIABLES: 4 variables
[0]: t [ms]
[1]: alpha_X [per_ms]
[2]: beta_X [per_ms]
[3]: X [dimensionless]
Step 6: Analyse the mathematical construction of the model¶
6.a Create an Analyser item and submit the model for processing.
6.b Just like the Validator class, the Analyser class keeps track of issues.
Retrieve these and print to the terminal using the same helper function as earlier.
Expect errors related to un-computed variables and missing initial values.
Show C++ snippet
// 6.a
// Create an Analyser item and submit the model for processing.
auto analyser = libcellml::Analyser::create();
analyser->analyseModel(model);
// 6.b
// Just like the Validator class, the Analyser class keeps track of issues.
// Retrieve these and print to the terminal. Expect errors related to
// un-computed variables and missing initial values.
printIssues(analyser);
Show Python snippet
# 6.a
# Create an Analyser item and submit the model for processing.
analyser = Analyser()
analyser.analyseModel(model)
# 6.b
# Just like the Validator class, the Analyser class keeps track of issues.
# Retrieve these and print to the terminal. Expect errors related to
# un-computed variables and missing initial values.
print_issues(analyser)
Recorded 3 issues:
Issue [0] is an ERROR:
description: Variable 'X' in component 'gateEquations' is used in an ODE, but it is not initialised.
stored item type: VARIABLE
Issue [1] is an ERROR:
description: Variable 'alpha_X' in component 'gateEquations' is not computed.
stored item type: VARIABLE
Issue [2] is an ERROR:
description: Variable 'beta_X' in component 'gateEquations' is not computed.
stored item type: VARIABLE
In order to avoid hard-coding values here, we will need to connect to external values to initialise the “X” variable and provide the value for “alpha_X” and “beta_X”.
This means three things need to happen:
we need to create an external component to hold variable values;
we need to create external variables in that component;
we need to specify the connections between variables; and
we need to permit external connections on the variables.
This is the reason for the second internal component, the parameters component.
6.c Create a component which will store the hard-coded values for initialisation. Name it “gateParameters”, and add it to the top-level gate component as a sibling of the gateEquations component.
6.d Create appropriate variables in this component, and set their units.
Use the setInitialValue function to initialise the variables.
TODO What values to give?
Show C++ snippet
// 6.d
// Create appropriate variables in this component, and set their units.
// Use the setInitialValue function to initialise them.
auto X = libcellml::Variable::create("X");
X->setUnits("dimensionless");
X->setInitialValue(0);
gateParameters->addVariable(X);
auto alpha = libcellml::Variable::create("alpha");
alpha->setUnits(per_ms);
alpha->setInitialValue(0.1);
gateParameters->addVariable(alpha);
auto beta = libcellml::Variable::create("beta");
beta->setUnits(per_ms);
beta->setInitialValue(0.5);
gateParameters->addVariable(beta);
Show Python snippet
# 6.d
# Create appropriate variables in this component, and set their units.
# Use the setInitialValue function to initialise them.
X = Variable('X')
X.setUnits('dimensionless')
X.setInitialValue(0)
gateParameters.addVariable(X)
alpha = Variable('alpha')
alpha.setUnits(per_ms)
alpha.setInitialValue(0.1)
gateParameters.addVariable(alpha)
beta = Variable('beta')
beta.setUnits(per_ms)
beta.setInitialValue(0.5)
gateParameters.addVariable(beta)
So far in this tutorial we’ve only been creating items, defining them, and adding to their parent items. Now for the first time we will need to retrieve a child item from its parent. This can be done in one of two ways: either by the child’s index or by its name. There is more information about interacting with collections of items on the Understanding collections of items page.
Two particularly useful idioms are shown below.
// Retrieve Units named "myUnits" from a model and set as the units for a variable named "myVariable".
myVariable->setUnits(myModel->units("myUnits"));
// Retrieve a great-grandchild component by following the hierarchy of the encapsulation structure:
auto grandson = model->component("grandfather")->component("daddy")->component("son");
// Short-cut to retrieve the component with the given name from anywhere in the encapsulation hierarchy:
auto granddaughter = model->component("granddaughter", true);
# Retrieve Units named "myUnits" from a model and set as the units for a variable named "myVariable".
myVariable.setUnits(myModel.units('myUnits'))
# Retrieve a great-grandchild component by following the hierarchy of the encapsulation structure:
grandson = model->component('grandfather')->component('daddy')->component('son');
# Short-cut to retrieve the component with the given name from anywhere in the encapsulation hierarchy:
granddaughter = model->component('granddaughter', True)
6.e Specify a variable equivalence between the gate equations variables and the parameter variables of the same name. Validate the model again, expecting errors related to the variable interface types.
Show C++ snippet
// 6.e
// Specify a variable equivalence between the gateEquations variables and the parameter variables.
// Validate the model again, expecting errors related to the variable interface types.
libcellml::Variable::addEquivalence(gateEquations->variable("X"), gateParameters->variable("X"));
libcellml::Variable::addEquivalence(gateEquations->variable("alpha_X"), gateParameters->variable("alpha"));
libcellml::Variable::addEquivalence(gateEquations->variable("beta_X"), gateParameters->variable("beta"));
validator->validateModel(model);
printIssues(validator);
Show Python snippet
# 6.e
# Specify a variable equivalence between the gateEquations variables and the parameter variables.
# Validate the model again, expecting errors related to the variable interface types.
Variable.addEquivalence(gateEquations.variable('X'), gateParameters.variable('X'))
Variable.addEquivalence(gateEquations.variable('alpha_X'), gateParameters.variable('alpha'))
Variable.addEquivalence(gateEquations.variable('beta_X'), gateParameters.variable('beta'))
validator.validateModel(model)
print_issues(validator)
Recorded 6 issues:
Issue [0] is an ERROR:
description: Variable 'alpha_X' in component 'gateEquations' has no interface type set. The interface type required is 'public'.
see section 3.10.8 in the CellML specification.
stored item type: VARIABLE
... etc ...
6.f Set the variable interface type according to the recommendation from the validator.
This can either be done individually using the setInterfaceType function on each variable, or en masse for all the model’s variable interfaces using its fixVariableInterfaces function.
Validate and analyse again, expecting no errors.
Show C++ snippet
// 6.e
// Specify a variable equivalence between the gateEquations variables and the parameter variables.
// Validate the model again, expecting errors related to the variable interface types.
libcellml::Variable::addEquivalence(gateEquations->variable("X"), gateParameters->variable("X"));
libcellml::Variable::addEquivalence(gateEquations->variable("alpha_X"), gateParameters->variable("alpha"));
libcellml::Variable::addEquivalence(gateEquations->variable("beta_X"), gateParameters->variable("beta"));
validator->validateModel(model);
printIssues(validator);
// 6.f
// Set the variable interface type according to the recommendation from the validator.
// This can either be done individually using the Variable::setInterfaceType() function, or
// en masse for all the model's interfaces using the Model::fixVariableInterfaces() function.
// Validate and analyse again, expecting no errors.
model->fixVariableInterfaces();
validator->validateModel(model);
printIssues(validator);
analyser->analyseModel(model);
printIssues(analyser);
Show Python snippet
# 6.e
# Specify a variable equivalence between the gateEquations variables and the parameter variables.
# Validate the model again, expecting errors related to the variable interface types.
Variable.addEquivalence(gateEquations.variable('X'), gateParameters.variable('X'))
Variable.addEquivalence(gateEquations.variable('alpha_X'), gateParameters.variable('alpha'))
Variable.addEquivalence(gateEquations.variable('beta_X'), gateParameters.variable('beta'))
validator.validateModel(model)
print_issues(validator)
# 6.f
# Set the variable interface type according to the recommendation from the validator.
# This can either be done individually using the Variable::setInterfaceType() function, or
# en masse for all the model's interfaces using the Model::fixVariableInterfaces() function.
# Validate and analyse again, expecting no errors.
model.fixVariableInterfaces()
validator.validateModel(model)
print_issues(validator)
analyser.analyseModel(model)
print_issues(analyser)
Step 7: Sanity check¶
There’s a helper function provided for these tutorials which will print the model to the terminal.
Use printModel(Model, bool) (in C++) or print_model (in Python) function to print the contents of the given Model.
The second (optional) argument indicates whether or not to print the MathML strings in the components too.
7.a Print the model to the terminal using the helper function.
MODEL: 'GateModel'
UNITS: 2 custom units
[0]: ms
[1]: per_ms
COMPONENTS: 1 components
[0]: gate
VARIABLES: 0 variables
COMPONENT gate has 2 child components:
[0]: gateEquations
VARIABLES: 4 variables
[0]: t [ms]
[1]: alpha_X [per_ms]
└──> gateParameters:alpha [per_ms]
[2]: beta_X [per_ms]
└──> gateParameters:beta [per_ms]
[3]: X [dimensionless]
└──> gateParameters:X [dimensionless]
[1]: gateParameters
VARIABLES: 3 variables
[0]: X [dimensionless], initial = 0
└──> gateEquations:X [dimensionless]
[1]: alpha [per_ms], initial = 0.1
└──> gateEquations:alpha_X [per_ms]
[2]: beta [per_ms], initial = 0.5
└──> gateEquations:beta_X [per_ms]
Looking at the printout we see that the top-level component named “gate” has no variables. Even though this is clearly a valid situation (as proved by 6.f), it’s not going to make this model easy to reuse. We need to make sure that any input and output variables are also connected into the top-level gate component.
7.b Create intermediate variables for time, t and gate status, X in the gate component, and ensure they have a public and private interface to enable two-way connection. You will also need to set a public and private connection onto t and X in the equations component too, or repeat the call to fix the model’s interfaces as in step 6.f.
Show C++ snippet
// 7.b
// Create intermediate variables for time t and gate status X in the gate component,
// and ensure they have a public and private interface to enable two-way connection.
// You may also need to set a public and private connection onto t and X in the
// equations component too.
gate->addVariable(gateEquations->variable("t")->clone());
gate->addVariable(gateEquations->variable("X")->clone());
gate->variable("t")->setInterfaceType("public_and_private");
gate->variable("X")->setInterfaceType("public_and_private");
gateEquations->variable("t")->setInterfaceType("public_and_private");
gateEquations->variable("X")->setInterfaceType("public_and_private");
Show Python snippet
# 7.b
gate.addVariable(gateEquations.variable('t').clone())
gate.addVariable(gateEquations.variable('X').clone())
gate.variable('t').setInterfaceType('public_and_private')
gate.variable('X').setInterfaceType('public_and_private')
gateEquations.variable('t').setInterfaceType('public_and_private')
gateEquations.variable('X').setInterfaceType('public_and_private')
7.c Connect the intermediate variables to their respective partners in the equations component, and recheck the model.
Show C++ snippet
// 7.c
// Connect the intermediate variables to their respective partners in the equations
// component, and recheck the model.
libcellml::Variable::addEquivalence(gate->variable("t"), gateEquations->variable("t"));
libcellml::Variable::addEquivalence(gate->variable("X"), gateEquations->variable("X"));
validator->validateModel(model);
printIssues(validator);
analyser->analyseModel(model);
printIssues(analyser);
Show Python snippet
# 7.c
# Connect the intermediate variables to their respective partners in the equations
# component, and recheck the model.
Variable.addEquivalence(gate.variable('t'), gateEquations.variable('t'))
Variable.addEquivalence(gate.variable('X'), gateEquations.variable('X'))
validator.validateModel(model)
print_issues(validator)
analyser.analyseModel(model)
print_issues(analyser)
Step 8: Serialise and output the model¶
The Printer class in libCellML takes the stored instance of a Model item and creates a string representing its serialisation into CellML code.
8.a Create a Printer instance and use it to serialise the model into a string.
Write this string to a file called “GateModel.cellml”.
Show C++ snippet
// 8.a
// Create a Printer instance and use it to serialise the model. This creates a string
// containing the CellML-formatted version of the model. Write this to a file called
// "GateModel.cellml".
auto printer = libcellml::Printer::create();
std::ofstream outFile("GateModel.cellml");
outFile << printer->printModel(model);
outFile.close();
std::cout << "The created model has been written to GateModel.cellml" << std::endl;
Show Python snippet
# 8.a
# Create a Printer instance and use it to serialise the model. This creates a string
# containing the CellML-formatted version of the model. Write this to a file called
# 'GateModel.cellml'.
printer = Printer()
write_file = open('GateModel.cellml', 'w')
write_file.write(printer.printModel(model))
write_file.close()
print('The created model has been written to GateModel.cellml')