model code - openmpp/openmpp.github.io GitHub Wiki

Home > Model Development Topics > Model Code

This topic contains general information about the source code of an OpenM++ model. It describes model source code in broad terms, the contents of the model source code folder, and the Default scenario. It also briefly outlines the build process which transforms model source code and a Default scenario into an executable and accompanying database.

Topic contents

Modgen-specific: References to Modgen in this documentation refer to the Statistics Canada Modgen↗ platform. In this wiki, a model with common source code from which either a Modgen executable or an OpenM++ executable can be built is called a cross-compatible model. Wiki content apecific to existing Modgen users, cross-compatible models, or models originally developed in Modgen is highlighted Modgen-specific in the text.

Coding a model

OpenM++ models are written in two languages: the OpenM++ language and the C++ language. The OpenM++ language is used to specify the declarative aspects of a model, for example the model's classifications, parameters, entities, attributes, events, tables, labels, and notes. The C++ language is used to specify the procedural aspects of a model, for example the sequentially executed statements which change an entity's attributes when an event occurs in the simulation.

The OpenM++ language

The OpenM++ language consists of declarative statements. The location and ordering of those statements in model source code files is arbitrary and has no effect on the model specification. This provides a high level of modularity in model source code which can be particularly useful in large and complex models.

A statement in the OpenM++ language starts with an opening keyword which specifies the nature of the declaration and ends with a closing ;. The syntax between the opening keyword and the closing ; depends on the nature of the declaration.

For example, the classification keyword is used to declare a named ordered list of symbolic values:

classification SEX //EN Sex
{
  //EN Male
  MALE,

  //EN Female
  FEMALE
};

This example declares an OpenM++ classification named SEX. It has two possible values MALE and FEMALE. The declaration of SEX means that SEX can be used as the dimension of a parameter or table, or as the type (characteristic) of an attribute of an entity in the simulation.

The OpenM++ language also recognizes specially formatted // and /* ... */ comments. Recognized comments are optional and do not affect the model specification. They contain textual information stored with the model which can be used to produce more human-readable input and output and a generated user interface for the model. OpenM++ is multilingual, and the human language of the textual information is specified inside the comment using a two-letter code.

The //EN comments in the example provide English-language labels for the SEX classification and its values. These labels will appear in the user interface of the model, for example as row or column headings and labels of multi-dimensional parameters and tables.

The C++ language in model code

The C++ language portion of model code consists mostly or entirely of C++ function definitions. Here's an example:

// The implement function of MortalityEvent
void Person::MortalityEvent()
{
    alive = false;

    // Remove the entity from the simulation.
    Finish();
}

This C++ model code defines the function which implements mortality in the simulation. The Person entity, its attribute alive, its event MortalityEvent, and the helper function Finish are all declared elsewhere in the OpenM++ language code of the model.

Typically only a small, limited portion of the C++ language is used in model code. Note that it is usually neither useful nor recommended for a model developer to create C++ classes and class hierarchies in model code. The C++ classes and objects required for simulation are pre-generated by OpenM++ from the model specification given in the OpenM++ language.

The C++ language elements most used in model code are expressions↗ to compute values, assignments↗ to store those values, if statements↗ to implement branching logic, and for statements↗ or range for↗ statements for iteration. C++ functions↗ are used to specify when events occur and what happens when they do. Functions are also used to compute derived parameters and derived tables. Functions can also be used facultatively to organize code in complex models.

The C++ standard library can be used in model code. It includes useful and powerful components such as array↗ and vector↗ in the containers↗ library, and supports string operations.

The limited dialect of C++ used for coding models can be explored by perusing the source code of existing models and referring to comprehensive C++ documentation↗ when necessary, or to the many C++ tutorials available on the web.

Modgen-specific: Unlike Modgen, OpenM++ does not modify the C++ language portions of model code. This provides logical clarity and allows an IDE and other tools to function correctly with the C++ code of a model.

Model symbols in OpenM++ and C++

Many of the named symbols declared in the OpenM++ code of a model are transformed by OpenM++ into identically named C++ symbols for use in the C++ code of the model. The alive attribute of the Person entity in the previous example is such a symbol. These C++ symbols can usually be used transparently in C++ model code even though they may be implemented as more complex C++ objects 'under the hood'. So, when alive is assigned the value false in the example, the C++ symbol alive will silently implement side-effects to update any tables, derived attributes, or events which depend on the change in its value. Incidentally, these wrapped objects have no memory overhead (the alive attribute consumes a single byte of memory) and little computational overhead.

There are some situations where the objects which implement entity attributes can produce unexpected C++ compiler error messages in C++ model code. For more on this issue and how to address it, see Entity Attributes in C++.

Model functions in OpenM++ and C++

OpenM++ ignores function definitions in the C++ language portions of model code, with several exceptions:

  • Event time function definitions in model code are parsed by OpenM++ to determine which attributes can affect the event time. An event time function will be called to recompute the event time if any of those attributes change value.
  • PreSimulation function definitions are recognized by OpenM++ and will be called before the simulation starts. PreSimulation functions are used to validate input parameters and assign values to derived parameters.
  • UserTables function definitions are recognized by OpenM++ and will be called after the simulation completes. UserTables functions are used to compute the values of derived tables.

[back to topic contents]

Code folder and source files

The source code of an OpenM++ model is in one or more source files (also called modules) located in a single model code folder, eg Alpha2/code for the Alpha2 model. Each model source file has a name and extension which determine its language and role when the model is built, as follows:

  • *.h C++ header files included by other source files.
  • *.cpp C++ source files, can also contain OpenM++ code NOT YET IMPLEMENTED
  • *.mpp OpenM++ source files, can also contain C++ code
  • *.ompp OpenM++ source files, can also contain C++ code
  • Modgen-specific: modgen_*.mpp Modgen source files explicitly ignored by OpenM++

Modgen-specific: Only model source files with the .mpp extension are recognized by Modgen. The names and extensions *.ompp and modgen_*.mpp allow selected model source code files to be processed exclusively by OpenM++ or exclusively by Modgen. This can be useful in cross-compatible models. For example, tables which use the median statistic (which is not supported by Modgen) could be declared in a model source file named OrdinalStatistics.ompp. Those tables would be present in the OpenM++ version of the model, but absent in the Modgen version. Declaring those tables in a file with extension .ompp means that they will not cause Modgen to stop with a syntax error when building the Modgen version of the model.

The following model-specific source files must be present:

  • custom.h C++ header file containing model-specific declarations.
  • custom_early.h C++ header file containing model-specific declarations early in header file inclusion order.

The following model source files are present, by convention:

  • ompp_framework.ompp Model-specific source file containing use statements which specify the names of framework source code modules to be incorporated when the model is built. Framework source code modules are supplied with OpenM++ and are located in the OM_ROOT/use folder. For more information, see OpenM++ Framework Library.
  • ompp_options.ompp Model-specific source file containing options statements which specify commonly modified model options, such as Model Documentation options.

Some source files in the OpenM++ model code folder have fixed names and fixed content. Typically a model developer copies them to the model code folder from an example model in the OpenM++ distribution, for example from OM_ROOT/models/NewCaseBased/code or OM_ROOT/models/NewTimeBased/code. They are:

  • case_based.h Model-independent declaration of a structure present in case-based models, included in custom.h.
  • Modgen-specific: modgen_case_based.mpp Model-independent implementation of the simulation core of a case-based Modgen model.
  • Modgen-specific: modgen_time_based.mpp Model-independent implementation of the simulation core of a time-based Modgen model.

[back to topic contents]

Doc folder and documentation files

The human language documentation of an OpenM++ model can be embedded in the model source code in Label and Note comments or in files in a single model documentation folder, e.g. RiskPaths/doc for the RiskPaths model. Each model documentation file has a name and extension which determine its language and role when model documentation is built, for example:

  • NOTE.SymbolName.EN.md Markdown file documenting the model symbol SymbolName in English.
  • NOTE.SymbolName.FR.md Markdown file documenting the model symbol SymbolName in French.
  • LABEL.SymbolName.EN.txt Text file containing the label of the model symbol SymbolName in English.
  • LABEL.SymbolName.FR.txt Text file containing the label of the model symbol SymbolName in French.
  • Home.EN.md Markdown file containing the Home topic of the Authored Model Documentation in English.
  • Home.FR.md Markdown file containing the Home topic of the Authored Model Documentation in French.
  • TopicName.EN.md Markdown file containing the autonomous authored topic TopicName in English.
  • TopicName.FR.md Markdown file containing the autonomous authored topic TopicName in English.
  • *.pdf Auxiliary downloadable PDF documentation file

See Model Documentation for more about model documentation.

[back to topic contents]

Source file content

A model source file can contain only C++ content, only OpenM++ language content, or a mixture of both. OpenM++ uses keywords at the outermost level of code to recognize OpenM++ syntactic islands which contain declarative information about the model. Here's an example of an OpenM++ syntactic island in a model source file:

parameters 
{
    //EN Annual hazard of death
    double MortalityHazard;
    /* NOTE(MortalityHazard, EN)
        A constant hazard of death results in an exponential
        survival function.
    */
};

This syntactic island starts with the OpenM++ keyword parameters and ends with the terminating ;.

All code outside of a syntactic island is C++ code. When processing .mpp and .ompp model code files, OpenM++ extracts all C++ code found outside of syntactic islands and assembles it into the single C++ file src/om_developer.cpp for subsequent processing by the C++ compiler. By default, OpenM++ inserts #line directives↗ into this file so that any errors or warnings from the C++ compiler will refer back to the original model source file and line rather than to the assembled file src/om_developer.cpp.

When processing a .cpp model code file, OpenM++ processes any syntactic islands, but does not extract C++ code outside of syntactic islands. This lets one organize all model code into .cpp files in the model code folder, and pass those files directly to the C++ compiler in Step 2 of the model build process (see below). Alternatively one could organize all OpenM++ language content in .ompp files, and all C++ language content in .cpp files. NOT YET IMPLEMENTED

C++ directives can be inserted into model code to improve the usability of an IDE. For more information, see the subtopic Hiding syntactic islands.

Modgen-specific: Modgen processes only .mpp files, not .cpp files.

[back to topic contents]

Default scenario

The model build process requires a starting scenario containing values for all model input parameters, which is normally named Default. The parameter values for the Default scenario are in the model subfolder parameters/Default. It is also possible to publish multiple scenarios, not just the Default scenario, when a model is built, see Model Run: How model finds input parameters.

Selected Default parameters can be made invariant and incorporated directly into the model executable. This is done either by placing parameter files into the model subfolder parameters/Fixed, or using parameters_retain or parameters_suppress statements in model code.

The following file types for input parameters are recognized:

  • .dat Contains values for one or more parameters in Modgen format
  • .odat Contains values for one or more parameters in Modgen format
  • .csv Contains values for one parameter in csv format
  • .tsv Contains values for one parameter in tsv format

Modgen-specific: Only parameter files with the .dat extension are recognized by Modgen. The .odat extension lets a selected parameter file be processed only by OpenM++. This can be useful in cross-compatible models. It is used in OpenM++ sample cross-compatible models to provide values for parameters which are implemented by scenario properties in Modgen. For example, for the NewCaseBased model, the parameter input file OM_ROOT/models/NewCaseBased/parameters/Default/Framework.odat provides values for the SimulationSeed and SimulationCases parameters. The file OM_ROOT/models/NewCaseBased/parameters/Default/scenario_info.odat contains no parameters but provides a label and note for the scenario. Those structured comments would generate an error in Modgen if they were in a .dat file.

[back to topic contents]

Model build

The model build process uses the model source code and the Default scenario to construct an executable and accompanying database which implement the model. The model build process can be launched by issuing a command inside an Integrated Development Environment (IDE) such as Visual Studio on Windows, or Visual Studio Code on Linux or MacOS. The build process can also be launched by a command line utility such as msbuild on Windows or make in Linux. For more information please see Model development in OpenM++. The model build process consists of two steps. Bpth steps can produce warning and error messages. These messages explain the nature of the warning or error and contain the file and line in the model source code. In an IDE, these messages can usually be clicked to navigate directly to the error or wanring location in the IDE code editor.

Many aspects of the OpenM++ framework can be adapted or replaced to work differently or to support other environments. It is also possible to publish models to an existing database and to move or copy published models and scenarios from one database to another. For more information, see subtopics at Home.

Step 1: OpenM++ build

OpenM++ reads and parses all files in the model source subfolder code and the files for the Default scenario in parameters\Default (and possibly in parameters\Fixed), checks for errors, and performs the following steps:

  • Extracts the C++ portions of model code from all .mpp and .ompp files and assembles them into a single C++ source file.
  • Generates several C++ header files and a C++ source file which implements the model specification.
  • Generates a C++ source file which contains the values of invariant parameters.
  • Creates a new empty database for the model.
  • Publishes the model's metadata to the database, including classifications, parameter properties, table properties, parameter and table hierarchies, labels and notes, etc.
  • Publishes the Default scenario to the database, ie values of all modifiable parameters in the Default scenario.

Step 2: C++ build

After Step 1 completes, the C++ compiler is invoked. The input to the C++ compiler consists of all C++ files in the model source code folder (*.cpp, *.h), together with the C++ files generated by OpenM++ in Step 1. Additional general purpose code is included from the OpenM++ distribution and from the C++ standard library.

The results of the C++ compilation are linked with standard C++ libraries and an OpenM++ support library to create the model executable. Because OpenM++ integrates with C++, it is possible to link in other components such as a math library, or even a complete additional model, possibly written in a different language like Fortran.

[back to topic contents]

Hiding syntactic islands

Modern IDEs have powerful abilities to parse and navigate C++ code, e.g. context sensitive popup menus which identify all uses of a symbol in a project. However, these abilities require that the project consist of valid C++. OpenM++ syntactic islands are not valid C++, and will cause errors when processed by an IDE (or an extenral tool like doxygen). Syntactic islands can be hidden from a C++ compiler or IDE by using C++ preprocessor conditional inclusion↗ directives. Here's an example showing how the syntactic island in the earlier example can be hidden from the C++ compiler or IDE.

#if 0 // Hide from C++ compiler or IDE
parameters
{
    //EN Annual hazard of death
    double MortalityHazard;
    /* NOTE(MortalityHazard, EN)
        A constant hazard of death results in an exponential
        survival function.
    */
};
#endif // Hide from C++ compiler or IDE

OpenM++ will still process the syntactic island because it ignores C++ preprocessor directives.

An IDE may display a hidden syntactic island differently as a visual cue that it's an inactive code block, for example by reducing the opacity of characters in the block to make them fade into the background compared to normal characters. That can make it more difficult to read and edit code in syntactic islands.

To change the display of inactive code blocks in Visual Studio 2022, do
Tools > Options > Text Editor > C/C++ > View
and modify the settings in 'Inactive Code' as desired.

C++ code in model code files will not be considered valid by a C++ compiler or IDE if a required master header file is missing. That's because C++ requires that a symbol be declared before being used in code. That requirement can be met by including the optional include file omc/optional_IDE_helper.h at the top of the model code file, as follows:

#include "omc/optional_IDE_helper.h" // help an IDE editor recognize model symbols

Modgen-specific: The optional helper include file omc/optional_IDE_helper.h is x-compatible and will not interfere with a Modgen build.

[back to topic contents]

⚠️ **GitHub.com Fallback** ⚠️