Simulation tool developer¶
The code generation functionality in libCellML can be used to transform CellML models into procedural code for simulation. This example works through that process.
Contents
Parse an existing model from a file¶
The following code will read a file called simulationExample.cellml and deserialise its contents into a Model instance.
Note that both the Parser class and its opposite number, the Printer class, deal with strings rather than files.
You’ll need to read the file into a string, and then use the string as input to the Parser item.
// Parse a CellML file into a model.
// Read the file containing the CellML model into a string.
std::string inFileName = "simulationExample.cellml";
std::ifstream inFile(inFileName);
std::stringstream inFileContents;
inFileContents << inFile.rdbuf();
std::cout << "Opening the CellML file" << std::endl;
// Create a libCellML Parser, and use it to parse the fileContents
// string and convert it into a CellML Model structure.
auto parser = libcellml::Parser::create();
auto model = parser->parseModel(inFileContents.str());
printIssues(parser);
Full context: example_simulationToolDev.cpp
# Parse the model from a CellML file.
# Create a libCellML Parser, and use it to parse the fileContents
# string and convert it into a CellML Model structure.
read_file = open("resources/simulationExample.cellml", "r")
parser = Parser()
model = parser.parseModel(read_file.read())
print_issues_to_terminal(parser)
Full context: example_simulationToolDev.py
Resolve imports and flatten model¶
The import functionality for Units and Component items is key to enabling the reuse and sharing of models.
The import statements are basically a recipe for how these imported items can be combined to make the present model.
While models which contain import dependencies are perfectly valid, they cannot be used to generate runnable code.
The process of resolving the imports (telling libCellML where to look for these ingredients) and flattening the model (creating instances of the ingredients and removing the dependency) is necessary before code generation can happen.
The Importer class supports all functionality to do with imports, and contains its own logger which can be used to report anything that might have gone wrong.
// Resolve the import dependencies (if any) and flatten the model.
if(model->hasUnresolvedImports()) {
auto importer = libcellml::Importer::create();
// Submit the model to the importer and the absolute location
// against which the import reference paths will be resolved.
importer->resolveImports(model, "");
printIssues(importer);
// Print a list of dependencies for the current unflattened model.
printImportDependencies(model);
// Retrieve a "flattened" (ie: import-free) model from the importer,
// and use it to over-write the current model.
model = importer->flattenModel(model);
printImportDependencies(model);
}
Full context: example_simulationToolDev.cpp
# Resolve any import dependencies (if present) in the model.
if(model.hasUnresolvedImports()) {
# Create an Importer instance.
importer = Importer()
# Submit the model to the importer and the absolute location
# against which the import reference paths will be resolved.
importer.resolveModelImports(model, "resources/")
print_issues_to_terminal(importer)
# Print a list of sources that this model requires. This list will
# be empty after the model has been flattened.
print_import_dependencies(model)
# Retrieve a "flattened" (ie: import-free) model from the importer,
# and use it to over-write the current model.
model = importer.flattenModel(model)
}
Full context: example_simulationToolDev.py
Validate model¶
The Validator is the equivalent of a spelling checker: it can check that each item in a model has all of the information it needs, but it can’t check whether it means what you intend it to.
Thus even if a model is valid, it could still be the equivalent of correctly-spelled nonsense.
// Validate the model: check for syntactic and semantic errors.
// Create a Validator instance and pass the model for checking.
auto validator = libcellml::Validator::create();
validator->validateModel(model);
auto isValid = validator->errorCount() == 0;
printIssues(validator);
Full context: example_simulationToolDev.cpp
# Validate the model: check for syntactic and semantic errors.
# Create a Validator instance and pass the model for checking.
validator = Validator()
validator.validateModel(model)
print_issues_to_terminal(validator)
Full context: example_simulationToolDev.py
Once a model has been passed to a Validator instance, the validator’s internal logger will contain a list of any of the issues which have been encountered during the checking process.
A model can be said to be valid - that is, conforming to the CellML normative specification - if the validator’s logger contains no issues with a level of ERROR.
For more information on how to use any of the classes which record issues, please see the Get Issues section.
Analyse model¶
Before code can be generated from this model, its mathematics must be checked.
The Validator checks that the syntax of the model is correct; the Analyser checks that the maths is sensible, and that all the required information is available.
There are three steps to model analysis:
Create an
Analyseritem and submit the model for analysis.Address any issues reported (see the Get Issues section for details).
Use the
AnalyserModelitem that’s constructed as input to theGenerator(see next section).
// Analyse the model: check for mathematical and modelling errors.
auto analyser = libcellml::Analyser::create();
analyser->analyseModel(model);
printIssues(analyser);
Full context: example_simulationToolDev.cpp
# Analyse a model: check for mathematical and modelling errors.
analyser = Analyser()
analyser.analyseModel(model)
print_issues_to_terminal(analyser)
Full context: example_simulationToolDev.py
Generate code¶
Code generation is the process of representing the CellML model in another language format. At the time of writing, two profiles are available: C (default) and Python. There are four steps to code generation:
Create a
Generatoritem and select the profile language. (The default profile is C).Pass the
AnalyserModeloutput from theAnalyserto theGeneratorfor processing.Retrieve the generated implementation code. This is the contents of the
*.cfile (if C is the profile) or*.pyif Python is selected.(optional) Retrieve the generated interface code. This is the contents of the
*.hfile, and is not required for the Python profile.
// Generate runnable code in other language formats for this model.
// Create a Generator instance. Note that by default this uses the C language profile.
auto generator = libcellml::Generator::create();
// Pass the generator the model for processing.
generator->setModel(analyser->model());
printIssues(generator);
// Retrieve and write the interface code (*.h) and implementation code (*.c) to files.
std::ofstream outFile("sineComparisonExample.h");
outFile << generator->interfaceCode();
outFile.close();
outFile.open("sineComparisonExample.c");
outFile << generator->implementationCode();
outFile.close();
// If required, change the generator profile to Python.
auto profile = libcellml::GeneratorProfile::create(libcellml::GeneratorProfile::Profile::PYTHON);
generator->setProfile(profile);
// Retrieve and write the implementation code (*.py) to a file.
outFile.open("sineComparisonExample.py");
outFile << generator->implementationCode();
outFile.close();
Full context: example_simulationToolDev.cpp
# Generate runnable code in other language formats for this model.
# Create a Generator instance. Note that by default this is the C language.
generator = Generator()
# Pass the generator the analysed model for processing.
generator.processModel(analyser.model())
print_issues_to_terminal(generator)
# Retrieve and write the interface code (*.h) and implementation code (*.cpp) to files.
write_file = open("sineComparisonExample.h", "w")
write_file.write(generator.interfaceCode())
write_file.close()
write_file = open("sineComparisonExample.cpp", "w")
write_file.write(generator.implementationCode())
write_file.close()
# If required, change the generator profile to Python and reprocess the model.
profile = GeneratorProfile(GeneratorProfile.Profile.PYTHON)
generator.setProfile(profile)
generator.processModel(model)
# Retrieve and write the implementation code (*.py) to a file.
write_file = open("sineComparisonExample.py", "w")
write_file.write(generator.implementationCode())
write_file.close()
Full context: example_simulationToolDev.py
Use the generated code to simulate the model¶
Instructions for a simple solver to solve the model can be found on the Simulation tool developer: Solver page.