Tutorial 2: Debugging, issue checking and validating¶
In this tutorial you’ll explore how the Validator object can be put to use as you create and interact with your models to help you debug and find issues ahead of submitting it for simulation.
You’ll be taking a CellML2.0 file which has some mistakes in it, using the validation functionality to identify them, and then using the access and editing functions to fix them.
By the time you have worked through Tutorial 2 you will be able to:
Use the
Parserto report issues encountered when reading a file or deserialising a string; andUse the
Validatorto check for issues related to a model’s description as compared to the CellML2.0 specifications.
This tutorial assumes that you can already:
Read and deserialise a CellML model from a file (as in Tutorial 1);
Retrieve the name and id of models, components, and variables;
Navigate through the hierarchy of the model and its subsidiary elements (components, variables, units and MathML blocks); and
Serialise and print a
Modelstructure to a CellML file.
Requirements¶
Either download the whole folder, or:
C++
CMakeLists.txtThe CMake file for building;tutorial2.cppThe skeleton code ortutorial2_complete.cppthe completed code; andutilities.cppandutilities.hUtility functions for use in the tutorials.
Python
tutorial2.pyThe skeleton code ortutorial2_complete.pyor the completed code; andutilities.pyUtility functions for use in the tutorials.
Resources
tutorial2.cellmlthe input CellML file.
Step 0: Set-up¶
The instructions here build and test the skeleton code.
If you’d rather not work through the tutorial yourself and want to skip to the completed version, please rename the files removing the _complete from their names.
Navigate into the directory and check that you can build the template against the libCellML library successfully:
cmake -DINSTALL_PREFIX=../../install
make -j
Running the template:
./tutorial2
… should give the output:
-----------------------------------------------
TUTORIAL 2: ERROR CHECKING AND VALIDATION
-----------------------------------------------
Confirm that you’re able to run the tutorial2.py template against the libCellML library.
python3 tutorial2.py
This should give the output:
------------------------------------------------------------
TUTORIAL 2: ERROR CHECKING AND VALIDATION
------------------------------------------------------------
Step 1: Parse a CellML file into a model¶
Utility functions (C++)
printModel
Utility functions (Python)
print_model
1.a As you did in Tutorial 1, create a Parser and use it to create a model from the tutorial2.cellml file provided.
Instead of duplicating the work you did throughout the middle steps of Tutorial 1, we’ve provided you with a set of utility functions in the utilities.[cpp,h] files which will help with some of the repeated bits of these tutorials.
1.b Use the utility function printModel(yourModelHere) (in C++) or print_model(your_model_here) to output the contents of the model you just created to the terminal so that you can see it all properly.
MODEL: 'tutorial_2_model', id: 'tutorial 2 id has spaces'
UNITS: 1 custom units
[0]: i_am_a_units_item
COMPONENTS: 1 components
[0]: i_am_a_component id: my_component_id
VARIABLES: 4 variables
[0]: 1st [dimensionless]
[1]: b
[2]: c [dimensionless], initial = this_variable_doesnt_exist
[3]: d [i_dont_exist]
Maths in the component is:
<math xmlns="http://www.w3.org/1998/Math/MathML">
<apply>
<eq/>
<ci>a</ci>
<apply>
<plus/>
<ci>b</ci>
<ci>c</ci>
</apply>
</apply>
</math>
Step 2: Validate the model¶
Within the libCellML library is the Validator class.
This has one job: to make sure that what you give it is valid in terms of its CellML2.0 compliance.
Does it mean that your simulations will work the way you expect?
Nope, you can still write rubbish!
You can think of the validation being the spell-checker for your model.
It doesn’t judge the meaning of what you’ve written, just the nitty-gritty of how you’ve written it.
The validator can also help you as you create and edit models by pointing out what’s missing or incorrect as you go. This is really easy:
auto validator = libcellml::Validator::create();
validator->validateModel(yourModelHere);
from libcellml import Validator
validator = Validator()
validator.validateModel(your_model_here)
2.a Create a validator instance and pass your model to it, as above.
When you’ve created a Validator object and called it to check a model, a record of any issues is stored inside the validator.
To figure out what’s going on, you need to retrieve the pointers to these Issue objects.
As in Tutorial 1, we can call a count function (in the case of a validator, this is the issueCount() function to determine whether any issues have been raised.
Note that an issue a can have different levels: errors, warnings, hints, and messages.
In the Validator, only those issues which are errors indicate validation problems, but it’s usually a good idea to check all the issues anyway.
2.b Retrieve the number of issues from the validator, and print it to the terminal.
Show C++ snippet
// 2.a
// Create a Validator and pass the model into it.
auto validator = libcellml::Validator::create();
validator->validateModel(model);
// 2.b
// Check the number of issues returned from the validator.
int numberOfValidationIssues = validator->issueCount();
if (numberOfValidationIssues != 0) {
std::cout << "The validator has found " << numberOfValidationIssues
<< " issues!" << std::endl;
Show Python snippet
# 2.a
# Create a Validator and pass the model into it.
validator = Validator()
validator.validateModel(model)
# 2.b
# Check the number of issues returned from the validator.
num_validation_issues = validator.issueCount()
print('The validator has found {} issues.'.format(num_validation_issues))
The validator has found 5 issues!
Now we need to create an iterative loop to retrieve all the issues (and there should be a few in this particular model!) from the validator. Again following the same retrieval idiom as in Tutorial 1 for items in sets, we can access the issues using an index:
auto theFifteenthIssue = validator->issue(14);
the_15th_issue = validator.issue(14)
Utility functions (C++)
getCellmlElementTypeFromEnum
getIssueLevelFromEnum
Utility functions (Python)
get_cellml_element_type_from_enum
get_issue_level_from_enum
Inside an Issue structure are three fields which are really useful.
These are the description (which does what you’d think) and the referenceHeading, which points you to the section in the CellML2.0 specification document for reference, and a url with links to a relevant website for more information.
Each issue also has a level indicator, one of:
ERROR,WARNING,HINT, andMESSAGE.
As well as storing text-based information, the issue also keeps track of which item has the problem.
This can be accessed using the item function, and has a type given by the enumeration in cellmlElementType function.
The CellmlElementType enumeration contains:
COMPONENT,COMPONENT_REF,CONNECTION,ENCAPSULATION,IMPORT,MAP_VARIABLES,MATH,MODEL,RESET,RESET_VALUE,TEST_VALUE,UNDEFINED,UNIT,UNITS, andVARIABLE.
Two utility functions have been provided which will convert the enums for error level or element type into a string for printing.
2.c Create a loop (to the number of issues found in 2.c) to retrieve each issue pointer. For each issue, retrieve and print as much information as you can.
Show C++ snippet
// 2.c
// Retrieve the issues, and print their description, url, reference, and
// type of item stored to the terminal. The type of stored item is
// available as an enum, which can be turned into a string for output using
// the utility function, getItemTypeFromEnum(type).
for (size_t e = 0; e < numberOfValidationIssues; ++e) {
libcellml::IssuePtr validatorIssue = validator->issue(e);
std::string issueSpecificationReference =
validatorIssue->referenceHeading();
std::cout << " Validator issue[" << e << "]:" << std::endl;
std::cout << " Description: " << validatorIssue->description()
<< std::endl;
std::cout << " Type of item stored: " << getCellmlElementTypeFromEnum(validatorIssue->cellmlElementType()) << std::endl;
std::cout << " URL: " << validatorIssue->url() << std::endl;
if (issueSpecificationReference != "") {
std::cout << " See section " << issueSpecificationReference
<< " in the CellML specification." << std::endl;
}
}
}
Show Python snippet
# 2.c
# Retrieve the issues, and print their description, url, reference, and
# type of item stored to the terminal. The type of stored item is
# available as an enum, which can be turned into a string for output using
# the utility function, getItemTypeFromEnum(type).
for e in range(0, num_validation_issues):
issue = validator.issue(e)
reference = issue.referenceHeading()
print(' Validator issue[{}]:'.format(e))
print(' Description: {}'.format(issue.description()))
print(' Type of item stored: {}'.format(get_cellml_element_type_from_enum(issue.cellmlElementType())))
print(' URL: {}'.format(issue.url()))
if reference != '':
print(' See section {} in the CellML specification.'.format(reference))
Validator issue[0]:
Description: Variable '1st' in component 'i_am_a_component' does not have a valid name attribute. CellML identifiers must not begin with a European numeric character [0-9].
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.1.1
See section 2.8.1.1 in the CellML specification.
Validator issue[1]:
Description: Variable 'b' in component 'i_am_a_component' does not have any units specified.
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.1.2
See section 2.8.1.2 in the CellML specification.
Validator issue[2]:
Description: Variable 'c' in component 'i_am_a_component' has an invalid initial value 'this_variable_doesnt_exist'. Initial values must be a real number string or a variable reference.
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.2.2
See section 2.8.2.2 in the CellML specification.
Validator issue[3]:
Description: Variable 'd' in component 'i_am_a_component' has a units reference 'i_dont_exist' which is neither standard nor defined in the parent model.
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.1.2
See section 2.8.1.2 in the CellML specification.
Validator issue[4]:
Description: MathML ci element has the child text 'a' which does not correspond with any variable names present in component 'i_am_a_component'.
Type of item stored: MATH
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB12.html?issue=2.12.3
See section 2.12.3 in the CellML specification.
Step 3: Fix the issues raised¶
Now that we know what’s wrong with the model the next steps are to fix it!
A useful feature of the Issue items is as well as the textual information (which is more valuable to a reader), we also have a pointer to the item itself (which is more valuable to a programmer or user).
This section will work through the issues reported by the validator, and demonstrate different ways of accessing and repairing each of the problems.
component(name, True) Retrieving a component by its name with the optional second argument true will search the entire component tree for the component name.
The first issue raised involves the name of a variable.
Note that even though the name is invalid (as per CellML specification), it can still be used to access the item.
Our first step is to retrieve the badly named variable from the model, then we can use the setName function to repair it.
You’ll notice that the name of the component is given too.
Because component names are unique in the model, this means that we can use the combination of component name and variable name to retrieve the variable.
The component function of the Model class takes an optional second argument: this is a boolean indicating whether to search for the given component name in the model’s top level components (false, the default), or the entirety of the component tree (true).
Validator issue[0]:
Description: Variable '1st' in component 'i_am_a_component' does not have a valid name attribute. CellML identifiers must not begin with a European numeric character [0-9].
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.1.1
See section 2.8.1.1 in the CellML specification.
3.a Retrieve the variable named “1st” from the component named “i_am_a_component” and change its name to “a”.
Show C++ snippet
// 3.a
// Retrieve the variable named '1st' from the component named 'i_am_a_component' and change its name
// to 'a'.
auto iAmAComponent = model->component("i_am_a_component", true);
auto a = iAmAComponent->variable("1st");
a->setName("a");
// This could be done in a chain without instantiating the component and variable:
// model->component("i_am_a_component", true)->variable("1st")->setName("a");
Show Python snippet
# 3.a
# Retrieve the variable named '1st' from the component named 'i_am_a_component' and change its name
# to 'a'.
component = model.component('i_am_a_component', True)
a = component.variable('1st')
a.setName('a')
# This could be done in a chain without instantiating the component and variable:
# model.component('i_am_a_component', True).variable('1st').setName('a')
Validator issue[1]:
Description: Variable 'b' in component 'i_am_a_component' does not have any units specified.
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.1.2
See section 2.8.1.2 in the CellML specification.
Inside the Issue class are helper functions which allow you to access the item which needs to be fixed.
The naming of these functions is pretty straightforward, but there’s a catch.
Not all of the “items” returned actually exist as independent libCellML entities; some are referenced by their parent item instead.
For example, calling the math() function on an issue which reports storing an item with type MATH returns a pointer to the component item that the maths sits within.
The functions and the types they return are shown below.
enumeration value |
function to call |
type returned from function |
COMPONENT |
component() |
|
COMPONENT_REF |
componentRef() |
|
CONNECTION |
connection() |
|
ENCAPSULATION |
encapsulation() |
|
IMPORT |
importSource() |
|
MAP_VARIABLES |
mapVariables() |
|
MODEL |
model() |
|
RESET |
reset() |
|
RESET_VALUE |
resetValue() |
|
TEST_VALUE |
testValue() |
|
UNIT |
unit() |
|
UNITS |
units() |
|
VARIABLE |
variable() |
|
3.b Retrieve the variable directly from the issue using the variable function.
Set its units to be “dimensionless”.
Show C++ snippet
// 3.b
// Retrieve the variable directly from the issue using the Issue::variable() function to return it.
// Note that we can only do this because we know that the item type stored is a VARIABLE.
// Set its units to be "dimensionless".
auto issue1 = validator->issue(1);
auto b = issue1->variable();
b->setUnits("dimensionless");
// This can be done in a chain too: validator->issue(1)->variable()->setUnits("dimensionless");
Show Python snippet
# 3.b
# Retrieve the variable directly from the issue using the Issue.variable() function to return it.
# Note that we can only do this because we know that the item type stored is a VARIABLE.
# Set its units to be 'dimensionless'.
issue1 = validator.issue(1)
b = issue1.variable()
b.setUnits('dimensionless')
# This can be done in a chain too: validator.issue(1).variable().setUnits('dimensionless')
Validator issue[2]:
Description: Variable 'c' in component 'i_am_a_component' has an invalid initial value 'this_variable_doesnt_exist'. Initial values must be a real number string or a variable reference.
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.2.2
See section 2.8.2.2 in the CellML specification.
For this next issue we’re going to show how to use the generic item() function on an issue.
This differs between C++ and Python so please refer to the appropriate tab for information.
In C++ we need to know the types of everything we’re dealing with, all the time.
A recent workaround is the provision of the std::any type, which can be used to store an object of arbitrary type.
The caveat is that in order to use it, you need to cast it back into its original type using std::any_cast.
The items in the Issue class are stored as std::any objects, and can either be retrieved and cast in one step using the functions listed above; or the std::any pointer itself can be retrieved using the item() function.
You will need to also call the cellmlElementType() function to verify the correct type to cast the item to.
// Retrieve an issue pointer from the validator.
auto myFirstIssue = validator->issue(0);
// Retrieve the std::any item from the issue.
auto anyItem = myFirstIssue->item();
// Check the type of the item stored. If you don't know ahead of time this would be a
// switch statement to check them all.
assert(myFirstIssue->cellmlElementType() == libcellml::CellmlElementType::VARIABLE);
// Cast into a VariablePtr for use as normal.
auto myVariable = std::any_cast<libcellml::VariablePtr>(anyItem);
Since Python doesn’t care about types the same way that C++ does, the item() function will return the correct item.
No casting is needed!
It can still be useful to know the type that’s returned, as your options for how to deal with it may vary.
# Retrieve an issue pointer from the validator.
my_first_issue = validator.issue(0)
# Retrieve the item from the issue.
item = my_first_issue.item()
# Check the type of the item stored. If you don't know ahead of time this would be a
# switch statement to check them all.
assert(my_first_issue->cellmlElementType() == CellmlElementType.VARIABLE)
# The item is available for use as a variable already.
3.c Retrieve the third issue and its item from the validator.
This should be a VARIABLE item, so in C++ you will need to cast it appropriately.
Set the variable’s initial conditions to 20.
Show C++ snippet
// 3.c
// Use the item() function to retrieve a std::any cast of the item from the third issue.
// Use the cellmlElementType() to check that its type is a VARIABLE, and then cast
// into a VariablePtr using std::any_cast so that you can use it as normal.
// Set its initial value to 20.
auto issue2 = validator->issue(2);
auto item = issue2->item();
assert(issue2->cellmlElementType() == libcellml::CellmlElementType::VARIABLE);
auto c = std::any_cast<libcellml::VariablePtr>(item);
c->setInitialValue(20.0);
Show Python snippet
# 3.c
# Use the item() function to retrieve a std.any cast of the item from the third issue.
# Use the cellmlElementType() to check that its type is a VARIABLE, and then cast
# into a VariablePtr using std.any_cast so that you can use it as normal.
# Set its initial value to 20.
issue2 = validator.issue(2)
c = issue2.item()[1] # TODO clarify this after issue #759 is clarified.
assert(issue2.cellmlElementType() == CellmlElementType.VARIABLE)
c.setInitialValue(20.0)
Validator issue[3]:
Description: Variable 'd' in component 'i_am_a_component' has a units reference 'i_dont_exist' which is neither standard nor defined in the parent model.
Type of item stored: VARIABLE
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB08.html?issue=2.8.1.2
See section 2.8.1.2 in the CellML specification.
This error is similar in implication to that in 3.b: the validator is reporting that it can’t find the units required by a variable. It could be fixed in two different ways: either by supplying units called “i_dont_exist”; or by changing the name of the units which the variable requires to one that is available.
3.d Retrieve the units named “i_am_a_units_item” from the model, and set them to be used by variable “d”.
Show C++ snippet
// 3.d
// Change the name of the units required by variable 'd' to be those which are called 'i_am_a_units_item'.
// You will need to retrieve these units from the model in order to pass them to the variable.
auto iAmAUnitsItem = model->units("i_am_a_units_item");
validator->issue(3)->variable()->setUnits(iAmAUnitsItem);
Show Python snippet
# 3.d
# Change the name of the units required by variable 'd' to be those which are called 'i_am_a_units_item'.
# You will need to retrieve these units from the model in order to pass them to the variable.
iAmAUnitsItem = model.units('i_am_a_units_item')
validator.issue(3).variable().setUnits(iAmAUnitsItem)
This issue was actually also caught by the parser, which, like the validator, is a Logger class.
This means that it will keep track of anything it encounters when parsing a model.
You can try calling the issueCount and issue functions on the parser and iterating through them (just like in 2.c) to see what you find.
Validator issue[4]:
Description: MathML ci element has the child text 'a' which does not correspond with any variable names present in component 'i_am_a_component'.
Type of item stored: MATH
URL: https://cellml-specification.readthedocs.io/en/latest/reference/formal_and_informative/specB12.html?issue=2.12.3
See section 2.12.3 in the CellML specification.
As discussed earlier, the type of item stored doesn’t always match the type of item returned.
In this final example, the type stored is MATH but, according to the table above, the type returned from both the math() and item() functions is (after casting, if required) a ComponentPtr.
We don’t need to take action to resolve this issue, since our earlier change of the variable name to become “a” will have sorted out the problem already.
Step 4: Check and output the model¶
Now that (we hope) the issues have been resolved, it’s time to check that the model is free of validation errors.
4.a Validate the model again, and check that there are no more issues.
4.b Print the corrected model to the terminal so that you can see your changes.
4.c Just as you have done in Tutorial 1, create a Printer instance and use it to serialise the model into a string.
Print the string to a .cellml file.
Show C++ snippet
// 4.a
// Validate the corrected model again and check that there are no more issues.
validator->validateModel(model);
std::cout << "The validator found " << validator->issueCount()
<< " issues in the model." << std::endl;
// 4.b
// Print the corrected model to the terminal.
printModel(model, true);
// 4.c
// Print corrected model to a file.
auto printer = libcellml::Printer::create();
std::string serialisedModelString = printer->printModel(model);
std::string outFileName = "tutorial2_printed.cellml";
std::ofstream outFile(outFileName);
outFile << serialisedModelString;
outFile.close();
Show Python snippet
# 4.a
# Validate the corrected model again and check that there are no more issues.
validator.validateModel(model)
print('The validator found {} issues in the model.'.format(validator.issueCount()))
# 4.b
# Print the corrected model to the terminal.
print_model(model, True)
# 4.c
# Print corrected model to a file.
printer = Printer()
serialised_model = printer.printModel(model)
write_file = open('tutorial2_printed.cellml', 'w')
write_file.write(serialised_model)
4.d Go and have a cuppa, you’re done!