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.

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

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

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

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:

  1. Create an Analyser item and submit the model for analysis.

  2. Address any issues reported (see the Get Issues section for details).

  3. Use the AnalyserModel item that’s constructed as input to the Generator (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

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:

  1. Create a Generator item and select the profile language. (The default profile is C).

  2. Pass the AnalyserModel output from the Analyser to the Generator for processing.

  3. Retrieve the generated implementation code. This is the contents of the *.c file (if C is the profile) or *.py if Python is selected.

  4. (optional) Retrieve the generated interface code. This is the contents of the *.h file, 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

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.