Developer Overview

This page presents an extended overview of the Maestro 2 approach to co-simulation and the different components. The figures presented on this page is to outline the implementation and not does not correspond one-to-one. Furthermore, the figures mainly presents sunny-day scenarios.

An initial MaBL specification is passed to Maestro. A MaBL specification can be non-expanded in the sense that it relies on expansion plugins to expand statements until the specification has been fully expanded, in which case it should be executable. This is similar in nature to macro expansion.

The example below shows a non-expanded MaBL specification, as it contains statements marked with external, which means they have to be expanded.

simulation
import FixedStep;
import TypeConverter;
import InitializerUsingCOE;
import FMI2;
import CSV;
{
    real START_TIME = 10.0;
    real END_TIME = 10.0;
    real STEP_SIZE = 0.1;

    FMI2 tankcontroller = load("FMI2", "{8c4e810f-3df3-4a00-8276-176fa3c9f000}", "src/test/resources/watertankcontroller-c.fmu");
    FMI2 SingleWatertank = load("FMI2", "{cfc65592-9ece-4563-9705-1581b6e7071c}",  "src/test/resources/singlewatertank-20sim.fmu");
    FMI2Component crtlInstance = tankcontroller.instantiate("crtlInstance", false, false);;
    FMI2Component wtInstance = SingleWatertank.instantiate("wtInstance", false, false);;

    IFmuComponent components[2]={wtInstance,crtlInstance};

    external initialize(components,START_TIME, END_TIME);

    external fixedStepCsv(components,STEP_SIZE,0.0,END_TIME,"mm.csv");

    tankcontroller.freeInstance(crtlInstance);
    SingleWatertank.freeInstance(wtInstance);

    unload(tankcontroller);
    unload(SingleWatertank);
}

The imports FixedStep, TypeConverter and InitializerUsingCOE refer to expansion plugins. Expansion plugins export functions as Function Declarations, from which the types can be derived.

The imports FMI2 and CSV refer to interpreter plugins. Interpreter plugins come with a companion MaBL modules that gives the type definitions. This is elaborated on below in Executing a Specification.

In this example, there are two statements to expand: external initialize(components,START_TIME, END_TIME) and external fixedStepCsv(components,STEP_SIZE,0.0,END_TIME,"mm.csv"). ... initialize(...) is implemented in the plugin InitializerUsingCOE, and .. fixedStepCsv(...) is implemented in FixedStep. Upon expanding these statements new external statements can appear, and the process repeats. For example, the initialize and fixedStepCsv possibly makes use of the TypeConverter expansion plugin to convert types.

It is also necessary to supply an environment configuration (environment.json) containing the FMUs to use in the given co-simulation and the connections between instances of the FMUs. Furthermore, if the expansion plugins used in a MaBL specification require configuration in order to perform expansion, then it is supplied within a plugin configuration file (configuration.json).

Parsing and Expanding a Specification

The process of parsing and expanding is presented below.

Once the MaBL specification has been parsed into an AST it is time to perform the expansion. The expansion plugins are located via (1) classes that implement the interface IMaestroExpansionPlugin, (2) support a certain Framework (currently only FMI2 is supported) via an annotation @SimulationFramework(framework = Framework.FMI2), and (3) are imported in the MaBL specification. The external function calls are then matched with the functions exported by the expansion plugins located as described above.

Verifying a Specification

The verification a specification consists of two concepts: Type checking and verification plugins. The diagram below continues from where the diagram above ended, where AST represents a fully-expanded AST.

Executing a Specification

The execution is carried out via interpretation of the AST and by utilising the interpretation plugins.

As mentioned, an interpreter plugin has a companion MaBL module that defines the available functions of the module.

As an example, consider the FMI2 and FMI2Component interpreter plugins below:

module FMI2 {
    FMI2Component instantiate(string name, bool logging);
    void freeInstance(FMI2Component comp);
}

module FMI2Component {
    int setupExperiment( bool toleranceDefined, real tolerance, real startTime, bool stopTimeDefined, real stopTime);
    int doStep(real currentCommunicationPoint, real communicationStepSize, bool noSetFMUStatePriorToCurrentPoint);
    ...
}

Currently, an interpreter plugin has to be loaded with the load expression, which returns a value of the given module type. For example, FMI2 tankcontroller = load("FMI2", "{8c4e810f-3df3-4a00-8276-176fa3c9f000}", "src/test/resources/watertankcontroller-c.fmu"); returns a value of type FMI2. This is also how the interpreter maps the MaBL load expression to the FMI2 interpreter plugin. It is now possible to call the function instantiate on tankcontroller, which then returns a value of type FMI2Component.

Another example of an interpreter plugin is the CSV plugin:

module CSV {
     CSVFile open( string path);
     CSVFile close(CSVFile file);
 }

 module CSVFile {
     void writeHeader( string[] columnTitles);
     void writeRow(real time,  ?[] values);
 }

The FMI2 and CSV interpreter plugins are currently hardcoded within the interpreter evaluation of the load expression, but it is envisioned that the approach will be similar to the approach used for expansion plugins - see https://github.com/INTO-CPS-Association/maestro/issues/48.