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.
- Coding a model
- Code folder and source files
- Doc folder and documentation files
- Source file content
- Default scenario
- Model build
- Hiding syntactic islands
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.
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 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 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.
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++.
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.
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 containinguse
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 theOM_ROOT/use
folder. For more information, see OpenM++ Framework Library. -
ompp_options.ompp
Model-specific source file containingoptions
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 incustom.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.
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 symbolSymbolName
in English. -
NOTE.SymbolName.FR.md
Markdown file documenting the model symbolSymbolName
in French. -
LABEL.SymbolName.EN.txt
Text file containing the label of the model symbolSymbolName
in English. -
LABEL.SymbolName.FR.txt
Text file containing the label of the model symbolSymbolName
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 topicTopicName
in English. -
TopicName.FR.md
Markdown file containing the autonomous authored topicTopicName
in English. -
*.pdf
Auxiliary downloadable PDF documentation file
See Model Documentation for more about model documentation.
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.
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.
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.
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.
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.
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.