Model debugger¶
This example walks through the process of using the Validator and Analyser classes to check a CellML model’s syntax and mathematical composition.
Parse an existing CellML model¶
Here we use the Parser class to read the file, and deserialise the CellML into a Model object.
The CellML file used in this example makes use of imports, so we need to have the main file (debugAnalysisExample.cellml) as well as the file which it imports (debugAnalysisExampleImport.cellml).
// Parse an existing CellML model from a file.
std::string inFileName = "debugAnalysisExample.cellml";
std::ifstream inFile(inFileName);
std::stringstream inFileContents;
inFileContents << inFile.rdbuf();
auto parser = libcellml::Parser::create();
auto model = parser->parseModel(inFileContents.str());
# Parse an existing CellML model from a file.
read_file = open('debugAnalysisExample.cellml', 'r')
parser = Parser()
model = parser.parseModel(read_file.read())
Resolve the imports and flatten the model¶
At present the Analyser class ignores the contents of imported items.
This example shows a model which has imports, so we need to resolve these and flatten the model before any analysis can happen.
For detailed information on debugging models with imports, please see the Import debugging examples.
// Resolve any imports and flatten the model for analysis.
auto importer = libcellml::Importer::create();
// Resolve the imports.
importer->resolveImports(model, "");
// Check for issues.
std::cout << "The importer found "<<importer->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < importer->issueCount(); ++i) {
auto issue = importer->issue(i);
std::cout << issue->description() << std::endl;
}
// Flatten the model.
model = importer->flattenModel(model);
# Resolve any imports and flatten the model for analysis.
importer = Importer()
# Resolve the imports.
importer.resolveImports(model, '')
# Check for issues.
print('The importer found {} issues.'.format(importer.issueCount()))
for i in range(0, importer.issueCount()):
issue = importer.issue(i)
print(issue.description())
# Flatten the model.
model = importer.flattenModel(model)
Validate the model¶
The Validator class process is like a spelling checker: it will check the syntax of the model ahead of analysing its mathematical formulation in the Analyser later.
Here we create a validator, use it to check the model, and retrieve the descriptions of any issues it found.
// Create an Validator instance and pass the model to it for processing.
auto validator = libcellml::Validator::create();
validator->validateModel(model);
// Print any issues to the terminal.
std::cout << "The validator found "<<validator->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < validator->issueCount(); ++i) {
auto issue = validator->issue(i);
std::cout << issue->description() << std::endl;
}
# Create an Validator instance and pass the model to it for processing.
validator = Validator()
validator.validateModel(model)
# Print any issues to the terminal.
print('The validator found {} issues.'.format(validator.issueCount()))
for i in range(0, validator.issueCount()):
issue = validator.issue(i)
print(issue.description())
The validation issues raised can be used to fix any “spelling” problems with the model.
// Fix the validation errors.
// Add units to the variable 'b' in component 'validationErrors'.
model->component("validationErrors")->variable("b")->setUnits("dimensionless");
// Change the name of the variable 'iShouldBeNamed_c' to be 'c'.
model->component("validationErrors")->variable("iShouldBeNamed_c")->setName("c");
// Check again.
validator->validateModel(model);
std::cout << "The validator found "<<validator->issueCount() << " issues." << std::endl;
# Fix the validation errors.
# Add units to the variable 'b' in component 'validationErrors'.
model.component('validationErrors').variable('b').setUnits('dimensionless')
# Change the name of the variable 'iShouldBeNamed_c' to be 'c'.
model.component('validationErrors').variable('iShouldBeNamed_c').setName('c')
# Check again.
validator.validateModel(model)
print('The validator found {} issues.'.format(validator.issueCount()))
Analyse the model¶
If the Validator is the spell-checker then the Analyser is the grammar-checker.
It will check for errors of logic or mathematical definition in the model formulation, and also make sure that all of the information which a solver needs is available.
The Analyser works in the same way as the other service classes: we create an instance, pass a model for checking, and then retrieve any issues it may have found.
// The Analyser will find errors in the logic or mathematical formulation of the model's equations,
// so may return issues even when the model is valid (syntactically correct).
auto analyser = libcellml::Analyser::create();
analyser->analyseModel(model);
std::cout << "The analyser found "<<analyser->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < analyser->issueCount(); ++i) {
auto issue = analyser->issue(i);
std::cout << issue->description() << std::endl;
}
# The Analyser will find errors in the logic or mathematical formulation of the model's equations,
# so may return issues even when the model is valid (syntactically correct).
analyser = Analyser()
analyser.analyseModel(model)
print('The analyser found {} issues.'.format(analyser.issueCount()))
for i in range(0, analyser.issueCount()):
issue = analyser.issue(i)
print(issue.description())
In some situations both the Validator and Analyser classes may encounter errors that mean they’re unable to continue processing the model.
For this reason, you may need several iterations of checking and fixing before all of the issues are addressed.
// Fix the analysis errors. This may need several iterations of checking before all errors have been
// resolved.
// Variable 'd' in component 'importedComponent' is initialised using variable 'e', but it is not a constant.
model->component("importedComponent")->variable("d")->setInitialValue(22);
analyser->analyseModel(model);
std::cout << "The analyser found "<<analyser->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < analyser->issueCount(); ++i) {
auto issue = analyser->issue(i);
std::cout << issue->description() << std::endl;
}
// Variable 'x' in component 'nonInitialisedStateVariable' is used in an ODE, but it is not initialised.
model->component("nonInitialisedStateVariable", true)->variable("x")->setInitialValue(0.0);
// Variable 'x' in component 'overconstrainedModel' is computed more than once.
std::string mathml =
"<math xmlns=\"http://www.w3.org/1998/Math/MathML\" xmlns:cellml=\"http://www.cellml.org/cellml/2.0#\">\n"
" <apply>\n"
" <eq/>\n"
" <ci>x</ci>\n"
" <cn cellml:units=\"dimensionless\">3</cn>\n"
" </apply>\n"
"</math>\n";
model->component("overconstrainedModel", true)->setMath(mathml);
// Variable 'x' in component 'uncomputedVariable' is not computed.
model->component("uncomputedVariable")->setMath(mathml);
// Variable 'a' in component 'validationErrors' is not computed.
// Variable 'c' in component 'validationErrors' is not computed.
model->component("validationErrors")->variable("c")->setInitialValue(1.0);
// Check again.
validator->validateModel(model);
analyser->analyseModel(model);
std::cout << "The validator found "<<validator->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < validator->issueCount(); ++i) {
auto issue = validator->issue(i);
std::cout << issue->description() << std::endl;
}
std::cout << "The analyser found "<<analyser->issueCount() << " issues." << std::endl;
for(size_t i = 0; i < analyser->issueCount(); ++i) {
auto issue = analyser->issue(i);
std::cout << issue->description() << std::endl;
}
# Fix the analysis errors. This may need several iterations of checking before all errors have been
# resolved.
# Variable 'd' in component 'importedComponent' is initialised using variable 'e', but it is not a constant.
model.component('importedComponent').variable('d').setInitialValue(22)
analyser.analyseModel(model)
print('The analyser found {} issues.'.format(analyser.issueCount()))
for i in range(0, analyser.issueCount()):
issue = analyser.issue(i)
print(issue.description())
# Variable 'x' in component 'nonInitialisedStateVariable' is used in an ODE, but it is not initialised.
model.component('nonInitialisedStateVariable', True).variable('x').setInitialValue(0.0)
# Variable 'x' in component 'overconstrainedModel' is computed more than once.
mathml = \
'<math xmlns="http://www.w3.org/1998/Math/MathML" xmlns:cellml="http://www.cellml.org/cellml/2.0#">\n'\
' <apply>\n'\
' <eq/>\n'\
' <ci>x</ci>\n'\
' <cn cellml:units="dimensionless">3</cn>\n'\
' </apply>\n'\
'</math>'
model.component('overconstrainedModel', True).setMath(mathml)
# Variable 'x' in component 'uncomputedVariable' is not computed.
model.component('uncomputedVariable').setMath(mathml)
# Variable 'a' in component 'validationErrors' is not computed.
# Variable 'c' in component 'validationErrors' is not computed.
model.component('validationErrors').variable('c').setInitialValue(1.0)
# Check again.
validator.validateModel(model)
print('The validator found {} issues.'.format(validator.issueCount()))
for i in range(0, validator.issueCount()):
issue = validator.issue(i)
print(issue.description())
analyser.analyseModel(model)
print('The analyser found {} issues.'.format(analyser.issueCount()))
for i in range(0, analyser.issueCount()):
issue = analyser.issue(i)
print(issue.description())
Print the repaired model to a CellML file¶
Finally, we can serialised the repaired model for output to a CellML file using the Printer class.
Note that this prints the flattened model, so will not contain the import dependencies of the original one.
// Write the flattened, validated, analysed model to a serialised CellML string.
auto printer = libcellml::Printer::create();
std::string serialisedModelString = printer->printModel(model);
// Write the serialised string to a file.
std::string outFileName = "debugAnalysisExampleFixed.cellml";
std::ofstream outFile(outFileName);
outFile << serialisedModelString;
outFile.close();
# Write the flattened, validated, analysed model to a serialised CellML string.
printer = Printer()
model_string = printer.printModel(model)
# Write the serialised string to a file.
write_file = open("debugAnalysisExampleFixed.cellml", "w")
write_file.write(model_string)