5. Software Architecture

5.1. Overall software architecture

Fig. 5.1 shows the overall software architecture of SOEP.

The Application on top of the figure may be an equipment sales tool that uses an open, or a proprietary, Modelica model library of their product line, which allows a sales person to test and size equipment for a particular customer.

The HVAC Systems Editor allows drag and drop of components, which are read dynamically from the Modelica Library AST, similar to what can be done in today’s OS schematic editor. In contrast, the Schematic Editor allows to freely place and graphically connect Modelica components. Once models have been manipulated in the Schematic Editor, OS can only simulate them, but not apply OS Measures to it, as models may have become incompatible with the OS Measures. (This could in the future be changed by parsing the AST of the model.)

In the Schematic Editor, user-provided Modelica libraries can be loaded (for example, if a company has their own control sequence library), manipulated and stored again in the user-provided Modelica library. This functionality is also needed for the OpenBuildingControl project.

Currently, the OpenStudio Model Library is compiled C++ code. Our integration will generate a representation of the Modelica library that allows OpenStudio to dynamic load models for the SOEP mode. Note that the JModelica distribution includes a C++ compiler.

title Overall software architecture

scale max 1024 width

skinparam componentStyle uml2

package OpenStudio {
interface API
API - [Core]

package Legacy-Mode {
database "Legacy\nModel Library"
[Core] -> [Legacy\nModel Library]: integrates
[Core] -> [HVAC Systems Editor\n(Legacy Mode)]: integrates
[Core] -> [EnergyPlus\nSimulator Interface]: integrates
}

package SOEP-Mode {

[Core] --> [Model Library]: integrates
[Core] --> [HVAC Systems Editor\n(SOEP Mode)]: integrates
[Core] --> [SOEP\nSimulator Interface]: integrates
}
}

package SOEP {
database "Modelica\nLibrary AST" as mod_AST
database "Modelica\nBuildings Library"

[Model Library] --> mod_AST : parses json\nAST

[HVAC Systems Editor\n(SOEP Mode)] ..> mod_AST : parses json\nAST

[Conversion Script] .> [JModelica]: parses\nAST
[SOEP\nSimulator Interface] .> [JModelica] : writes inputs,\nruns simulation,\nreads outputs

[Conversion Script] -> mod_AST: generates
[JModelica] -> [Modelica\nBuildings Library]: imports
}


actor Developer as epdev
[Legacy\nModel Library] <.. epdev : updates

actor "Developer or User" as modev
[Conversion Script] <.. modev : invokes

actor Developer as budev
[Modelica\nBuildings Library] <.. budev : adds annotations

actor User as mouse
[User-Provided\nModelica Library] <.. mouse : adds annotations


[Application] ..> () API : uses
[Measures] ..> () API : uses

database "User-Provided\nModelica Library"
[JModelica] --> [User-Provided\nModelica Library]: imports

EnergyPlus <.. [EnergyPlus\nSimulator Interface]: writes inputs,\nruns simulation,\nreads outputs

package EnergyPlus {
  [EnergyPlus.exe]
}

note left of mod_AST
  Used as an intermediate format and
  to verify incompatible changes.
end note

note bottom of [User-Provided\nModelica Library]
  Allows companies to use
  their own Modelica libraries
  with custom HVAC systems and
  control sequences, or
  to integrate an electronic
  equipment catalog or a
  library used for an equipment
  sales tool.
end note

note right of Application
  Application that uses
  the OpenStudio SDK.
end note

Fig. 5.1 : Overall software architecture.

5.2. Coupling of EnergyPlus envelope and room with Modelica-based HVAC and control

This section describes the refactoring of the EnergyPlus room model, which will remain in the C/C++ implementation of EnergyPlus, to a form that exposes the time derivative of its room model. EnergyPlus will be exported as an FMU for model exchange.

The time integration of the room air temperature, moisture and trace substance concentrations will be done by the master algorithm.

EnergyPlus will synchronize the room model and the envelope model.

We will use the following terminology: By envelope model, we mean the model for the heat and moisture transfer through opaque constructions and through windows. By room model, we mean the room air heat, mass and trace substance balance. By HVAC model, we mean the HVAC and control model.

The physical quantities that need to be exchanged are as follows. For a convective HVAC system, the convective and latent heat gain added by the HVAC system, the mass flow rates of trace substances such as CO2 and VoC, and the state of the return air, e.g., temperature, relative humidity and pressure. For radiant systems, the temperature of the radiant surface, and the heat flow rates due to conduction, short-wave and long-wave radiation.

5.2.1. Assumptions and limitations

For the current implementation, we will make the following assumption:

  1. Only the lumped room air model will be refactored, not the room model with stratified room air. The reason is to keep focus and make progress before increasing complexity.

  2. The HVAC and the pressure driven air exchange (airflow network) are either in legacy EnergyPlus or in FMUs of the SOEP. The two methods cannot be combined. The reason is that the legacy EnergyPlus computes in its “predictor/corrector” the room temperature as follows:

    1. It computes the HVAC power required to meet the temperature set point.
    2. It simulates the HVAC system to see whether it can meet this load.
    3. It updates the room temperature using the HVAC power from step (b).

    This is fundamentally different from the ODE solver used by SOEP who sets the new room temperature and time, requests its time derivative, and then recomputes the new time step.

  3. In each room, mass, as opposed to volume, is conserved. The reason is that this does not induce an air flow in the HVAC system if the room temperature changes. Hence, it decouples the thermal and the mass balance.

5.2.2. Partitioning of the models

To link the envelope model, the room model and the HVAC model, we partition the simulation as shown in Figure 2.

_images/envelop-room-hvac.svg

Fig. 5.2 : Partitioning of the envelope, room and HVAC model.

The EnergyPlus FMU is for model exchange and contains the envelope and the room surfaces. All of the HVAC system and other pressure driven mass flow rates, such as infiltration due to wind pressure or static pressure differences, are computed in the HVAC FMUs. There can be one or several HVAC FMUs, which is irrelevant as EnergyPlus will only see one set of variables that it exchanges with the master algorithm.

5.2.3. Data exchange

The following parameters are sent from EnergyPlus to Modelica. These are sent only once during the initialization for each thermal zone.

Variable Dimension Quantity Unit
From EnergyPlus to Modelica
V \(1\) Volume of the zone air m3
AFlo \(1\) Floor area of the zone m2
mSenFac \(1\) Factor for scaling the sensible thermal mass of the zone air volume 1

The following time-dependent variables are exchanged between EnergyPlus and Modelica during the time integration for each thermal zone.

Variable Dimension Quantity Unit
From Modelica to EnergyPlus
T \(1\) Temperature of the zone air degC
X \(1\) Water vapor mass fraction per total air mass of the zone kg/kg
mInlets_flow \(1\) Sum of positive mass flow rates into the zone for all air inlets (including infiltration) kg/s
TInlet \(1\) Average of inlets medium temperatures carried by the mass flow rates degC
QGaiRad_flow \(1\) Radiative sensible heat gain added to the zone W
t \(1\) Model time at which the above inputs are valid, with \(t=0\) defined as January 1, 0 am local time, and with no correction for daylight savings time s
From EnergyPlus to Modelica
TRad \(1\) Average radiative temperature in the room degC
QConSen_flow \(1\) Convective sensible heat added to the zone, e.g., as entered in the EnergyPlus’ People or Equipment schedule W
QLat_flow \(1\) Latent heat gain added to the zone, e.g., from mass transfer with moisture buffering material and from EnergyPlus’ People schedule W
QPeo_flow \(1\) Heat gain due to people (only to be used to optionally compute CO2 emitted by people) W
nextEventTime \(1\) Model time \(t\) when EnergyPlus needs to be called next (typically the next zone time step) s

Note that the EnergyPlus object ZoneAirContaminantBalance either allows CO2 concentration modeling, or a generic contaminant modeling (such as from material outgasing), or no contaminant modeling, or both. To allow ambiguities regarding what contaminant is being modeled, we do not receive the contaminant emission from EnergyPlus. Instead, we obtain the heat gain due to people, which is then used to optionally computed the CO2 emitted by people.

For this coupling, all zones of EnergyPlus will be accessed from Modelica. For example, if a building has two zones, then both zones need to be modeled in Modelica.

5.2.4. Time synchronization

As shown in Figure 2, the EnergyPlus FMU is invoked at a variable time step. Internally, it samples its heat conduction model at the envelope time step \(\Delta t_z\). EnergyPlus needs to report this to the FMI interface. To report such time events, the FMI interface uses a C structure called fmi2EventInfo which is implemented as follows:

typedef struct{
  fmi2Boolean newDiscreteStatesNeeded;
  fmi2Boolean terminateSimulation;
  fmi2Boolean nominalsOfContinuousStatesChanged;
  fmi2Boolean valuesOfContinuousStatesChanged;
  fmi2Boolean nextEventTimeDefined;
  fmi2Real
  nextEventTime; // next event if nextEventTimeDefined=fmi2True
  } fmi2EventInfo;

The variable nextEventTime needs to be set to the time when the next event happens in EnergyPlus. This is, for example, whenever the envelope model advances time, or when a schedule changes its value and this change affects the variables that are sent from the EnergyPlus FMU to the master algorithm. Such a schedule could for example be a time schedule for internal heat gains, which may change at times that do not coincide with the zone time step \(\Delta t_z\).

5.2.5. Requirements for Exporting EnergyPlus as an FMU for model exchange

To export EnergyPlus as an FMU for model exchange, EnergyPlus must be compiled as a shared library. The shared library must export the functions which are described in the next section. These functions are then used by the FMI for model exchange wrapper that LBL is developing.

Note

In the current implementation, we assume that EnergyPlus does not support roll back in time. This will otherwise require EnergyPlus to be able to save and restore its complete internal state. This internal state consists especially of the values of the continuous-time states, iteration variables, parameter values, input values, delay buffers, file identifiers and internal status information. This limitation is indicated in the model description file with the capability flag canGetAndSetFMUstate being set to false. If this capability were supported, then EnergyPlus could be used with ODE solvers which can reject and repeat steps. Rejecting steps is needed by ODE solvers such as DASSL or even Euler with step size control (but not for QSS) as they may reject a step size if the error is too large. Also, rejecting steps is needed to identify state events (but not for QSS solvers).

In the remainder of this section, we note that time is

  • the variable described as t in the table of section Section 5.2.3,
  • a monotonically increasing variable.

Note

Monotonically increasing means that if a function has as argument time and is called at time t1, then its next call must happen at time t2 with t2 >= t1. For efficiency reasons, if a function which updates internal variables is called at the same time instant multiple times, then only the first call will update the variables, subsequent calls will cause the functions to return the same variable values.

5.2.5.1. Instantiation

unsigned int instantiate(const char const *input,
                         const char const *weather,
                         const char const *idd,
                         const char const *instanceName,
                         const char ** parameterNames,
                         const unsigned int parameterValueReferences[],
                         size_t nPar,
                         const char ** inputNames,
                         const unsigned int inputValueReferences[],
                         size_t nInp,
                         const char ** outputNames,
                         const unsigned int outputValueReferences[],
                         size_t nOut,
                         const char *log);
  • input: Absolute or relative path to an EnergyPlus input file with file name.
  • weather: Absolute or relative path to an EnergyPlus weather file with file name.
  • idd: Absolute or relative path to an EnergyPlus idd file with file name.
  • instanceName: String to uniquely identify an EnergyPlus instance. This string must be non-empty and will be used for logging message.
  • parameterNames: A vector of nPar strings that identifies the names of the parameters that are to be retrieved from EnergyPlus.
  • parameterValueReferences: A vector of value references for the quantities in parameterNames. Value references uniquely identify the variables, and are used in the setVariables and getVariables calls below.
  • nPar: Number of elements of the parameter vector, e.g., length of parameterNames and parameterValueReferences.
  • inputNames: A vector of nInp strings that identifies the names of the inputs sent to EnergyPlus.
  • inputValueReferences: A vector of value references for the quantities in inputNames.
  • nInp: Number of elements of the input vector, e.g., length of inputNames and inputValueReferences.
  • outputNames: A vector of nOut strings that identifies the names of the outputs to be retrieved from EnergyPlus.
  • outputValueReferences: A vector of value references for the quantities in outputNames.
  • nOut: Number of elements of the output vector, e.g., length of outputNames and outputValueReferences.
  • log: Logging message returned on error.

For example, if a building has two zones called basement and office, then the parameter names are

5.2.5.2. Configuring variables to be exchanged

To configure the variables to be exchanged, the following data structures will be used.

const char ** parameterNames = {"basement,V", "basement,AFlo", "basement,mSenFac", "office,V", "office,AFlo", "office,mSenFac"};
const unsigned int parameterValueReferences[] = {0, 1, 2, 3, 4, 5, 6};

The inputs into EnergyPlus will be

const char ** inputNames = {"basement,T", "basement,X", "basement,mInlets_flow", "basement,TInlet", "basement,QGaiRad_flow",
                            "office,T", "office,X", "office,mInlets_flow", "office,TInlet", "office,QGaiRad_flow"};
const unsigned int inputValueReferences[] = {7, 8, 9, 10, 11, 12, 13, 14, 15, 16};

The outputs of EnergyPlus will be

const char ** outputNames = {"basement,TRad", "basement,QConSen_flow", "basement,QLat_flow", "basement,QPeo_flow",
                             "office,TRad", "office,QConSen_flow", "office,QLat_flow", "office,QPeo_flow"};
const unsigned int outputValueReferences[] = {17, 18, 19, 20, 21, 22, 23, 24};

This function will read the idf file and sets up the data structure in EnergyPlus.

It returns zero if there was no error, or else a positive non-zero integer.

5.2.5.3. Setting up the experiment time

unsigned int setupExperiment(double tStart,
                             const char *log);
  • tStart: Start of simulation in seconds.
  • log: Logging message returned on error.

This functions sets the start time of EnergyPlus to the value tStart. There is no warm-up simulation. EnergyPlus will continue the simulation until terminate(const char *) (see below) is called.

It returns zero if there was no error, or else a positive non-zero integer.

Note

The EnergyPlus start time is as retrieved from the argument of setupExperiment(...). The RunPeriod in the idf file is only used to determine the day of the week.

Possible complication:

Users may set tStart to a time other than midnight. We think EnergyPlus cannot yet handle an arbitrary start time. In this case, it should return an error.

5.2.5.4. Setting the current time

unsigned int setTime(double time,
                     const char *log);
  • time: Model time.
  • log: Logging message returned on error.

This function sets a new time in EnergyPlus. This time becomes the current model time.

It returns zero if there was no error, or else a positive non-zero integer.

5.2.5.5. Sending parameters and variables to EnergyPlus

unsigned int setVariables(const unsigned int valueReferences[],
                          const double* const variablePointers[],
                          size_t nVars1,
                          const char *log);
  • valueReferences: Vector of value references.
  • variablePointers: Vector of pointers to variables.
  • nVars1: Number of elements of valueReferences, and variablePointers.
  • log: Logging message returned on error.

This function sets the value of variables in EnergyPlus. The vector valueReferences could be a subset of the pointer inputValueReferences that was setup in instantiate(...), i.e., nVars1 <= nInp (to allow updating only specific variables as needed by QSS).

It returns zero if there was no error, or else a positive non-zero integer.

5.2.5.6. Retrieving parameters and variables from EnergyPlus

unsigned int getVariables(const unsigned int valueReferences[],
                          const double* variablePointers[],
                          size_t nVars2,
                          const char *log);
  • valueReferences: Vector of value references.
  • variablePointers: Vector of pointers to variables.
  • nVars2: Number of elements of valueReferences, and variablePointers.
  • log: Logging message returned on error.

This function gets the value of variables in EnergyPlus. EnergyPlus must write the values to the elements that are setup in outputValueReferences during the instantiate(...) call. nVars2 <= nOut if only certain output variables are required.

It returns zero if there was no error, or else a positive non-zero integer.

5.2.5.7. Retrieving the next event time

unsigned int getNextEventTime(fmi2EventInfo *eventInfo,
                              const char *log);
  • eventInfo: A structure with event info as defined in section Time synchronization
  • log: Logging message returned on error.

This function writes a structure which contains among its variables a non-zero flag to indicate that the next event time is defined, and the next event time in EnergyPlus.

It returns zero if there was no error, or else a positive non-zero integer.

5.2.5.8. Terminating the EnergyPlus simulation

unsigned int terminate(const char *log);
  • log: Logging message returned on error.

This function must free all allocated variables in EnergyPlus.

Any further call to the EnergyPlus shared library is prohibited after call to this function.

It returns zero if there was no error, or else a positive non-zero integer.

5.2.5.9. Writing EnergyPlus output files

unsigned int writeOutputFiles(const char *log);
  • log: Logging message returned on error.

This function writes the output to the EnergyPlus output files.

It returns zero if there was no error, or else a positive non-zero integer.

5.2.6. Pseudo Code Example

In the next section, the usage of the FMI functions along with the equivalent EnergyPlus functions are used in a typical calling sequence. This should clarify the need of the EnergyPlus equivalent functions and show how these functions will be used in a simulation environment. In the pseudo code, -> points to the EnergyPlus equivalent FMI functions. NA indicates that the FMI functions do not require EnergyPlus equivalent.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
 // instantiate
    m = M_fmi2Instantiate(...) -> instantiate(...)

    tStart = 0
    tEnd   = 10
    dt     = 0.01

    // set the start time
    time  = tStart

    // set the input values and the initial values for the states at time = tStart
    M_fmi2SetReal(m, ...) -> setVariables (...)

    // initialize. Note that tEnd is not set in M_fmi2SetupExperiment
    M_fmi2SetupExperiment(m, fmi2False, 0.0, Tstart, fmi2False, 0.0) -> setupExperiment(...)
    M_fmi2EnterInitializationMode(m) -> NA
    M_fmi2ExitInitializationMode(m) -> NA

    initialEventMode = fmi2True
    enterEventMode = fmi2False
    timeEvent = fmi2False
    stateEvent = fmi2False
    previous_z = zeros(nz)

    // The time in EnergyPlus is now at Tstart

    do
      // handle events
      if initialEventMode or enterEventMode or timeEvent or stateEvent then
        if not initialEventMode then
          M_fmi2EnterEventMode(...) -> NA
        end if

        // event iteration
        eventInfo.newDiscreteStatesNeeded = fmi2True;
        M_valuesOfContinuousStatesChanged = fmi2False;
         while eventInfo.newDiscreteStatesNeeded loop
           // update discrete states
           M_fmi2NewDiscreteStates(eventInfo, ...) -> getNextEventTime(eventInfo, ...)
           // See specification on page 80
           M_fmi2SetReal(...) -> setVariables (...)
           M_fmi2GetReal(...) -> getVariables (...)
           if eventInfo.terminateSimulation then goto TERMINATE_MODEL
        end while

        // enter Continuous-Time Mode
        M_fmi2EnterContinuousTimeMode(m) -> NA

        // retrieve solution at simulation (re)start
        M_fmi2GetReal(...) -> getVariables (...)
        if initialEventMode or eventInfo.valuesOfContinuousStatesChanged then
          //the model signals a value change of states, retrieve them
          M_fmi2GetContinuousStates(...) -> NA
        end if

        if initialEventMode or eventInfo.nominalsOfContinuousStatesChanged then
          //the meaning of states has changed; retrieve new nominal values
          M_fmi2GetNominalsOfContinuousStates(...) -> NA
        end if

        if eventInfo.nextEventTimeDefined then
           tNext = min(eventInfo.nextEventTime, tEnd)
        else
           tNext = tEnd
        end if

        initialEventMode = fmi2False
      end if

      if time >= tEnd then
        goto TERMINATE_MODEL
      end if

      // compute derivatives
      M_fmi2GetDerivatives(...) -> NA
      // Note we might have to compute time derivatives at different time instants
      // to approximate higher order derivatives for QSS integration methods.

      // Note that a forward Euler method with event detection, or more sophisticated
      // methods such as dassl, will not work with EnergyPlus.
      // The reason is that EnergyPlus does not allow rollback (and this limitation
      // is indicated in the model description file with the capability flag
      // canGetAndSetFMUstate=false.

      // When using QSS numerical methods,
      // compute minimal next quantization time tq
      time_old = time
      time = min (tq, tNext) // tq is the next predicted quantization time

      M_fmi2SetTime(...) -> setTime(...)

      // set inputs at t = time
      M_fmi2SetReal(m, ...) -> setVariables (...)

      // do a time integration up to t = time
      x = integral(x, der_x, time_old, time)
      M_fmi2SetContinuousStates(...) -> NA

      // get event indicators for state events detection at t = time
      M_fmi2GetEventIndicators(...) -> NA

      // EnergyPlus has no zero crossing functions, hence
      // there is no state event detection for this FMU.

      // inform the model about an accepted step
      // To ensure that EnergyPlus call getNextEventTime(...),
      // enterEventMode will be set to true in M_fmi2CompletedIntegratorStep().
      // Furthermore, the capability flag completedIntegratorStepNotNeeded
      // will be set to false to ensure that this function is called
      // after an accepted step.
      M_fmi2CompletedIntegratorStep(enterEventMode...) -> writeOutputFiles(...)

      // get the outputs
      M_fmi2GetReal(m, ...) -> getVariables (...)

    until terminateSimulation

    // terminate simulation and retrieve final values
    TERMINATE_MODEL:
    M_fmi2GetReal(m, ...) -> getVariables()
    M_fmi2Terminate(m) -> terminate(...)


    // cleanup
    M_fmi2FreeInstance(m) -> NA

5.2.7. Tool for Exporting EnergyPlus as an FMU

To export EnergyPlus as an FMU, a utility is needed which will get as inputs the paths to the EnergyPlus idf, idd, and weather files. The utility will parse the idf file and write an XML model description file which contains the inputs, outputs, and states of EnergyPlus to be exposed through the FMI interface. The utility will compile the EnergyPlus FMI functions into a shared library, and package the library with the idf, idd, and weather file in the resources folder of the FMU. An approach to develop such a utility is to extend EnergyPlusToFMU (http://simulationresearch.lbl.gov/fmu/EnergyPlus/export/index.html) to support FMI 2.0 for model exchange. Another approach is to extend SimulatorToFMU (https://github.com/LBNL-ETA/SimulatorToFMU) to support the export of EnergyPlus.

5.3. JModelica Integration

This section describes the integration of the QSS solver in JModelica.

We will first introduce terminology. Consider the code-snippet

...
when (x > a) then
  ...
end when;
...

We will say that \(z = a - x\) is the event indicator.

For the discussion, we consider a system of initial value ODEs of the form

(5.1)\[ \begin{align}\begin{aligned}\left[\dot x_c(t), x_d(t)\right] = f(x_c(t), x_d(t^-), u_c(t), u_d(t), p, t),\\\begin{split}\left[y_c(t), y_d(t)\right] = g(x_c(t), x_d(t), u_c(t), u_d(t), p, t),\\\end{split}\\\begin{split}0 = z(x_c(t), x_d(t), u_c(t), u_d(t), p, t),\\\end{split}\\\left[x_c(t_0), x_d(t_0)\right] = [x_{c,0}, x_{d,0}],\end{aligned}\end{align} \]

where \(x(\cdot)\) is the continuous-time state vector, with superscript \(c\) denoting continuous-time states and \(d\) denoting discrete variables or states, \(u(\cdot)\) is the external input, \(p\) are parameters, \(f(\cdot, \cdot, \cdot, \cdot, \cdot, \cdot)\) is the state transitions function, \(g(\cdot, \cdot, \cdot, \cdot, \cdot, \cdot)\) is the output function, \(z(\cdot, \cdot, \cdot, \cdot, \cdot, \cdot)\) is the event indicator (sometimes called zero crossing function).

Because we anticipate that the FMU can have direct feed-through from the input \(u(t)\) to the output \(y(t)\), we use FMI for Model-Exchange (FMI-ME) version 2.0, because the Co-Simulation standard does not allow a zero time step size as needed for direct feed-through.

Fig. 5.3 shows the software architecture with the extended FMI API. For simplicity the figure only shows single FMUs, but we anticipated having multiple interconnected FMUs.

title Software architecture for QSS integration with JModelica with extended FMI API

skinparam componentStyle uml2

package FMU-QSS {
  [QSS solver] as qss_sol
  [FMU-ME] as FMU_QSS
}

package PyFMI {
[Master algorithm] -> qss_sol : "inputs, time"
[Master algorithm] <- qss_sol : "next event time, discrete states"
[Master algorithm] - [Sundials]
}

[FMU-ME] as ode

[Sundials] -> ode : "(x, t)"
[Sundials] <- ode : "dx/dt"

package Optimica {
[JModelica compiler] as jmc
}

jmc -l-> FMU_QSS

FMU_QSS -down-> qss_sol : "derivatives"
qss_sol -down-> FMU_QSS : "inputs, time, states"

Fig. 5.3 : Software architecture for QSS integration with JModelica with extended FMI API.

Note

We still need to design how to handle algebraic loops inside the FMU (see also Cellier’s and Kofman’s book) and algebraic loops that cross multiple FMUs.

The QSS solvers require the derivatives shown in Table 5.1.

Table 5.1 Derivatives required by QSS algorithms. One asteriks indicates that they are provided by FMI-ME 2.0, and two asteriks indicate that they can optionally be computed exactly if directional derivatives are provided by the FMU. The others cannot be provided through the FMI API.
Type of QSS Continuous-time state derivative event indicator derivative
QSS1 \(dx_c/dt\) * \(dz/dt\)
QSS2 \(dx_c/dt\) * , \(d^2x_c/dt^2\) ** \(dz/dt\) , \(d^2z/dt^2\)
QSS3 \(dx_c/dt\) * , \(d^2x_c/dt^2\) ** , \(d^3x_c/dt^3\) \(dz/dt\) , \(d^2z/dt^2\), \(d^3z/dt^3\)

Because the FMI API does not provide access to the required derivatives, and FMI has limited support for QSS, we discuss extensions that are needed for an efficient implementation of QSS.

5.3.1. FMI Changes for QSS

QSS generally requires to only update a subset of the continuous-time state vector. We therefore propose to use the function

fmi2Status fmi2SetReal(fmi2Component c,
                       const fmi2Real x[],
                       const fmi2ValueReference vr[],
                       size_t nx);

to set a subset of the continuous-time state vector. This function exists in FMI-ME 2.0, but the standard only allows to call it for continuous-time state variables during the initialization.

We therefore propose that the standard is being changed as follows:

fmi2SetReal can be called during the continuous time mode and during event mode not only for inputs, as is allowed in FMI-ME 2.0, but also for continuous-time states.

To retrieve individual state derivatives, we introduce the extensions shown in Listing 5.1 to the <Derivatives> element of the modelDescription.xml file.

Listing 5.1 Extensions for obtaining higher order state derivatives. XML additions are marked yellow.
 <ModelVariables> <!-- Remains unchanged -->
   ...
   <ScalarVariable name="x",      ...> ... </ScalarVariable> <!-- index="5" -->
   ...
   <ScalarVariable name="der(x)", ...> ... </ScalarVariable> <!-- index="8" -->
 </ModelVariables>

 <Derivatives>
   <!-- The ScalarVariable with index 8 is der(x) -->
   <Unknown     index="8" dependencies="6" />
   <!-- index 5 is the index of the state variable x -->
   <HigherOrder index="5" order="2" valueReference="124" /> <!-- This is d^2 x/dt^2 -->
   <HigherOrder index="5" order="3" valueReference="125" /> <!-- This is d^3 x/dt^3 -->
 </Derivatives>

5.3.1.1. Event Handling

5.3.1.1.1. State Events

For efficiency, QSS requires to know the dependencies of event indicators. Also, it will need to have access to, or else approximate numerically, the time derivatives of the event indicator. FMI 2.0 outputs an array of real-valued event indicators, but no variable dependencies.

Therefore, we introduce the following xml section, which assumes we have three event indicators.

<ModelStructure>
  <EventIndicators>
    <!-- This is z[0] which depends on ScalarVariable with index 2 and 3 -->
    <Element index="1" order="0" dependencies="2 3" valueReference="200" />
    <!-- This is z[1] which declares no dependencies, hence it may depend on everything -->
    <Element index="2" order="0" valueReference="201" />
     <!-- This is z[2] which declares that it depends only on time -->
    <Element index="3" order="0" dependencies="" valueReference="202" />

    <!-- With order > 0, higher order derivatives can be specified. -->
    <!-- This is dz[0]/dt -->
    <Element index="1" order="1" valueReference="210" />
  </EventIndicators>
</ModelStructure>

The attribute dependencies is optional. However, if it is not specified, a tool shall assume that the event indicator depends on all variables. Write dependencies="" to declare that this event indicator depends on no variable (other than possibly time). Note that for performance reasons, for QSS dependencies should be declared for order="0". For higher order, QSS does not use the dependencies.

Note

The index uses in the <EventIndicators> element is different from the index used in the <ModelVariables> element. The first event indicator has an index 1, the second has an index 2, and so on. A new index is introduced because the event indicators do not show up in the list of <ModelVariables>.

The dependencies variables of event indicators are

  • inputs (variables with causality = “input”)
  • continuous-time states
  • independent variable (usually time; causality = “independent”)

Consider the following model

within QSS.Docs;
model StateEvent1 "This model tests state event detection"
  extends Modelica.Icons.Example;
  Real x1(start=0.0, fixed=true) "State variable";
  Real x2(start=0.5, fixed=true) "State variable";
  discrete Modelica.Blocks.Interfaces.RealOutput y(start=1.0, fixed=true)
    "Ouput variable";
equation
  der(x1) = y + 1;
  der(x2) = x2;
  when (x1 > 0.5 and x2 > 1.0) then
    y = -1.0;
  end when;
  annotation (experiment(StopTime=1), Documentation(info="<html>
<p>
This model has 2 state events, one at t=0.25
and one at t=0.6924 when simulated from 0 to 1 s.
</p>
</html>"));
end StateEvent1;

For efficiency reason, QSS requires the FMU which exports this model to indicate in its model description file the dependency of der(x1) on y. This allows der(x1) to update when y changes. However, y can only change when an event indicator changes its domain. Hence, rather than declaring the dependency on y, it suffices to declare the dependency on the event indicator. Therefore, we propose to include event indicators to the list of state derivative dependencies.

However, FMI states on page 61 that dependencies are optional attributes defining the dependencies of the unknown (directly or indirectly via auxiliary variables) with respect to known. For state derivatives and outputs, known variables are

  • inputs (variables with causality = “input”)
  • continuous-time states
  • independent variable (usually time; causality = “independent”)

Therefore we require to extend the <Derivatives> element with a new attribute eventIndicatorsDependencies which lists all event indicator variables which trigger changes to state derivative trajectories.

An excerpt of such a <Derivatives> element with the new addition is shown in Listing 5.2.

Listing 5.2 Extensions with inclusion of a new attribute for event indicator dependencies. XML additions are marked yellow.
  <Derivatives>
    <!-- The ScalarVariable with index 8 is der(x) -->
    <!-- eventIndicatorsDependencies="1" declares that der(x)
         depends on the event indicator with index 1  -->
    <Unknown     index="8" dependencies="6" eventIndicatorsDependencies="1"/>
    <!-- index 5 is the index of the state variable x -->
    <HigherOrder index="5" order="2" valueReference="124" /> <!-- This is d^2 x/dt^2 -->
    <HigherOrder index="5" order="3" valueReference="125" /> <!-- This is d^3 x/dt^3 -->
  </Derivatives>

For the elements Unknown and HigherOrder, the attributes dependencies and eventIndicatorsDependencies are optional. However, if dependencies is not declared, a tool shall assume that they depend on all variables. Similarly, if eventIndicatorsDependencies is not declared, a tool shall assume that they depend on all event indicators. Write dependencies="" to declare that this derivative depends on no variable. Similarly, write eventIndicatorsDependencies="" to declare that this derivative depends on no event indicator. Note that for performance reasons, for QSS dependencies and eventIndicatorsDependencies should be declared for Unknown. For HigherOrder, QSS does not use the dependencies and eventIndicatorsDependencies.

In Listing 5.2, the higher order derivatives depend on the event indicator.

Note

The indexes of eventIndicatorsDependencies are the indexes of the event indicators specified in the <EventIndicators> element.

5.3.1.1.2. Event indicators that depend on the input

Consider following model

within QSS.Docs;
model StateEvent2 "This model tests state event detection"
  extends Modelica.Icons.Example;
  Real x(start=-0.5, fixed=true) "State variable";
  discrete Real y(start=1.0, fixed=true "Discrete variable");
  Modelica.Blocks.Interfaces.RealInput u "Input variable"
    annotation (Placement(transformation(extent={{-140,-20},{-100,20}})));
equation
  der(x) = y;
  when (u > x) then
    y = -1.0;
  end when;
  annotation (experiment(StopTime=1), Documentation(info="<html>
<p>
This model has a state event
when u becomes bigger than x.
</p>
</html>"));
end StateEvent2;

This model has one event indicator \(z = u-x\). The derivative of the event indicator is \({dz}/{dt} = {du}/{dt} - {dx}/{dt}\).

Hence, a tool requires the derivative of the input u to compute the derivative of the event indicator. Since the derivative of the input u is unkown in the FMU, we propose for cases where the event indicator has a direct feedthrough on the input to exclude event indicator derivatives from the <EventIndicators> element. In this situation, the QSS solver will detect the missing information from the XML file and numerically approximate the event indicator derivatives.

5.3.1.1.3. Handling of variables reinitialized with reinit()

Consider following model

within QSS.Docs;
model StateEvent3 "This model tests state event detection"
  extends Modelica.Icons.Example;
  Real x1(start=0.0, fixed=true) "State variable";
  Real x2(start=0.0, fixed=true) "State variable";
equation
  der(x1) = 1;
  der(x2) = x1;
when time > 0.5 then
  reinit(x1, 0);
end when;
  annotation (experiment(StopTime=1), Documentation(info="<html>
<p>
This model has 1 state event at t=0.5s
when simulated from 0 to 1s.
</p>
</html>"));
end StateEvent3;

This model has a variable x1 which is reinitialized with the reinit() function. Such variables have in the model description file an attribute reinit which can be set to true or false depending on whether they can be reinitialized at an event or not. Since a reinit() statement is only valid in a when-equation block, we propose for variables with reinit set to true, that at every state event, the QSS solver gets the value of the variable, updates variables which depend on it, and proceeds with its calculation.

5.3.1.1.4. Workaround for implementing event indicators

While waiting for the implementation of the FMI extensions in JModelica, LBNL will refactor some Modelica models to expose event indicators and their first derivatives as FMU output variables.

The names of event indicators variables will start with __zc_. The names of derivatives of event indicators will start with __zc_der_. As an example, __zc_z1 and __zc_der_z1 are the names of the event indicator z1 with its derivative der_z1.

If the number of event indicators is equal to the numberOfEventIndicators attribute, then only __zc_ and __zc_der_ need to be used by QSS. If the number of event indicators does not match, the FMU shall be rejected with an error message.

Note

Per design, Dymola (2018) generates twice as many event indicators as actually existing in the model. Hence the master algorithm shall detect if the tool which exported the FMU is Dymola, and if it is, the number of event indicators shall be equal to half the value of the numberOfEventIndicators attribute.

5.3.1.1.5. Time Events

This section discusses additional requirements for handling time events with QSS.

Consider following model

within QSS.Docs;
model TimeEvent "This model tests time event detection"
  extends Modelica.Icons.Example;
  Real x1(start=0.0, fixed=true) "State variable";
  Real x2(start=0.0, fixed=true) "State variable";
  discrete Modelica.Blocks.Interfaces.RealOutput y(start=1.0, fixed=true)
    "Output variable";
equation
  der(x1) = y + 1;
  der(x2) = -x2;
  when (time >= 0.5) then
    y = x2;
  end when;
  annotation (experiment(StopTime=1), Documentation(info="<html>
<p>
This model has 1 time event at t=0.5s
when simulated from 0 to 1s.
</p>
</html>"));
end TimeEvent;

This model has a time event at \(t \ge 0.5\). For efficiency, QSS requires the FMU which exports this model to indicate in its model description file the dependency of der(x1) on y. However, since y updates when a time event happens but a time event is not described with event indicator, it is not possible to use the same approach as done for StateEvent1 without further modificaton.

We therefore propose that JModelica turns all time events into state events, add new event indicators generated by those state events to the <EventIndicators> element, and include those event indicators to the eventIndicatorsDependencies attribute of state derivatives which depend on them.

Note

All proposed XML changes will be initially implemented in the VendorAnnotation element of the model description file until they got approved and included in the FMI standard.

5.3.1.2. SmoothToken for QSS

This section discusses a proposal for a new data type which should be used for input and output variables of FMU-QSS. FMU-QSS is an FMU for Model Exchange (FMU-ME) which uses QSS to integrate an imported FMU-ME. We propose that FMU-QSS communicates with other FMUs using a SmoothToken data type.

A smooth token is a time-stamped event that has a real value (approximated as a double) that represents the current sample of a real-valued smooth signal. But in addition to the sample value, the smooth token contains zero or more time-derivatives of the signal at the stored time stamp. For example, in the figure below, FMU-ME has a real input \(u\) and a real output \(y\). A smooth token of the input variable will be a variable \(u^* \triangleq [u, n, du/dt, ..., d^n u/dt^n, t_u]\), where \(n \in \{0, 1, 2, \ldots \}\) is a parameter that defines the number of time derivatives that are present in the smooth token and \(t_u\) is the timestamp of the smooth token. If \(u^*\) has a discontinuity at \(t_u\), then the derivatives are the derivatives from above, e.g., \(du/dt \triangleq \lim_{s \downarrow 0} (u(t_u+s)-u(t_u))/s\).

At simulation time \(t\), FMU-QSS will receive \(u^*\) and convert it to a real signal using the Taylor expansion

\[y_s(t) = \frac{u^{(n)}(t_u)}{n!} \, (t-t_u)^n,\]

where \(u^{(n)}\) denotes the \(n\)-th derivative. As shown in Fig. 5.4, the FMU-ME will receive the value \(y_s(t)\).

_images/fmu-qss.png

Fig. 5.4 : Conversion of input signal between FMU-QSS and FMU-ME.

To avoid frequent changes in the input signal, each input signal will have a quantum defined. The quantum \(\delta q\) will be computed at runtime as

\[\delta q = \max(\epsilon_{rel} \, |u^-|, \epsilon_{abs}),\]

where \(\epsilon_{rel}\) is the relative tolerance, \(u^-\) is the last value seen at the input of FMU-ME, and \(\epsilon_{abs} \triangleq \epsilon_{rel} \, |u_{nom}|\), where \(u_{nom}\) is the nominal value of the variable \(u\). During initialization, we set \(u^- = u_0\). The input signal will be updated only if it has changed by more than a quantum,

\[|y_s(t) - u^-| \ge \delta q.\]

To parametrize the smooth token, we propose to extend the FMI for model exchange specification to include fmi2SetRealInputDerivatives and fmi2GetRealOutputDerivatives. These two functions exist in the FMI for co-simulation API.

Note

  • If a tool can not provide the derivative of an output variable with respect to time, fmi2GetRealOutputDerivatives then a master algorithm could approximate the output derivative as follows:

    • If there was no event, then the time derivatives from below and above are equal, and hence past and current output values can be used, e.g.,

      \[dy/dt \approx \frac{y(t_k)-y(t_{k-1})}{t_k - t_{k-1}}.\]
    • If there was an event, then the derivative from above need to be approximated. This could be done by assuming first \(dy/dt =0\) and then building up derivative information in the next step, or by evaluating the FMU for \(t=t_k+\epsilon\), where \(\epsilon\) is a small number, and then computing

      \[dy/dt \approx \frac{y(t_k+\epsilon)-y(t_{k})}{\epsilon}.\]
  • For FMU-ME, if there is a direct feedthrough, e.g., \(y=f(u)\), then \(dy/dt\) cannot be computed, because by the chain rule,

    \[\frac{df(u)}{dt} = \frac{df(u)}{du} \, \frac{du}{dt}\]

    but \(du/dt\) is not available in the FMU.

5.3.1.3. Summary of Proposed Changes

Here is a list with a summary of proposed changes

  • fmi2SetReal can be called during the continuous, and event time mode for continuous-time states.
  • The <Derivatives> element of the model description file will be extended to include higher order derivatives information.
  • A new <EventIndicators> element wil be added to the model description file. This element will expose event indicators with their dependencies and time derivatives.
  • If a model has an event indicator, and the event indicator has a direct feedthrough on an input variable, then JModelica will exclude the derivatives of that event indicator from the model description file.
  • A new dependency attribute eventIndicatorsDependencies will be added to state derivatives listed in the <Derivatives> element to include event indicators on which the state derivatives depend on.
  • JModelica will convert time event into state events, generate event indicators for those state events, and add those event indicators to the eventIndicatorsDependencies of state derivatives which depend on them.
  • A new function fmi2SetRealInputDerivatives will be included to parametrize smooth token.
  • A new function fmi2GetRealOutputDerivatives will be included to parametrize smooth token.
  • All proposed XML changes will be initially implemented in the VendorAnnotation element of the model description file until they got approved and included in the FMI standard.

Note

  • We need to determine when to efficiently call fmi2CompletedIntegratorStep() to signalize that an integrator step is complete.
  • We need to determine how an FMU deals with state selection, detect it, and reject it on the QSS side.

5.3.1.4. Open Topics

This section includes a list of measures which could further improve the efficiency of QSS. Some of the measures should be implemented and benchmarked to ensure their necessity for QSS.

5.3.1.4.1. Atomic API

A fundamental property of QSS is that variables are advanced at different time rates. To make this practically efficient with FMUs, an API for individual values and derivatives is essential.

5.3.1.4.2. XML/API

All variables with non-constant values probably need to be exposed via the xml with all their interdependencies. The practicality and benefit of trying to hide some variables such as algebraic variables by short-circuiting their dependencies in the xml (or doing this short-circuiting on the QSS side) should be considered for efficiency reasons.

5.3.1.4.3. Higher Derivatives

Numerical differentiation significantly complicates and slows the QSS code: automatic differentiation provided by the FMU will be a major improvement and allows practical development of 3rd order QSS solvers.

5.3.1.4.4. Input Variables
  • Input functions with discontinuities up to the QSS order need to be exposed to QSS and provide next event time access to the master algorithm.
  • Input functions need to be true (non-path-dependent) functions for QSS use or at least provide a way to evaluate without “memory” to allow numeric differentiation and event trigger stepping.
5.3.1.4.5. Annotations

Some per-variable annotations that will allow for more efficient solutions by overriding global settings (which are also needed as annotations) include:

  • Various time steps: dt_min, dt_max, dt_inf, …
  • Various flags: QSS method/order (or traditional ODE method for mixed solutions), inflection point requantization, …
  • Extra variability flags: constant, linear, quadratic, cubic, variable, …
5.3.1.4.6. Conditional Expressions and Event Indicators
  • How to reliably get the FMU to detect an event at the time QSS predicts one?

    QSS predicts zero-crossing event times that, even with Newton refinement, may be slightly off. To get the FMU to “detect” these events with its “after the fact” event indicators the QSS currently bumps the time forward by some epsilon past the predicted event time in the hopes that the FMU will detect it. Even if made smarter about how big a step to take this will never be robust. Missing events can invalidate simulations. If there is no good and efficient FMU API solution we may need to add the capability for the QSS to handle “after the fact” detected events but with the potential for large QSS time steps and without rollback capability this would give degraded results at best.

  • How much conditional structure should we expose to QSS?

    Without full conditional structure information the QSS must fire events that aren’t relevant in the model/FMU. This will be inefficient for models with many/complex conditionals. These non-event events also alter the QSS trajectories so, for example, adding a conditional clause that never actually fires will change the solution somewhat, which is non-ideal.

  • QSS needs a mechanism similar to zero crossing functions to deal with Modelica’s event generating functions (such as div(x,y) See http://book.xogeny.com/behavior/discrete/events/#event-generating-functions) to avoid missing solution discontinuities.

  • Discrete and non-smooth internal variables need to be converted to input variables or exposed (with dependencies) to QSS.

  • QSS need dependency information for algebraic and boolean/discrete variables either explicitly or short-circuited through the exposed variables for those the QSS won’t track.

  • The xml needs to expose the structure of each conditional block: if or when, sequence order of if/when/else conditionals, and all the (continuous and discrete/boolean) variables appearing in each conditional.

  • Non-input boolean/discrete/integer variables should ideally be altered only by event indicator handlers or time events that are exposed by the FMU (during loop or by direct query?). Are there other ways that such variables can change that are only detectable after the fact? If so, this leaves the QSS with the bad choices of late detection (due to large time steps) or forcing regular time step value checks on them.

  • QSS needs the dependencies of conditional expressions on variables appearing in them.

  • QSS needs the dependencies of variables altered when each conditional fires on the conditional expression variables.

  • It is not robust for the QSS to try and guess a time point where the FMU will reliably detect a zero crossing so we need an API to tell the FMU that a zero crossing occurred at a given time (and maybe with crossing direction information).

  • If the xml can expose the zero crossing directions of interest that will allow for more efficiency.

5.4. OpenStudio integration

Note

This section needs to be revised in view of the move towards a json-driven architecture.

5.4.1. Automated Documentation/AST Support Tool

This section describes how to generate automated SOEP documentation and abstract syntax trees from the Modelica Buildings Library (MBL), or from other Modelica libraries that users may use with SOEP. The goal is to expose the abstract syntax tree (AST) of the MBL, and make it available to other tools via one or several json files. These json files can then be used by OpenStudio to discover available models and their parameters and other metadata for use from the OpenStudio interface for SOEP.

5.4.1.1. Background

Modelica source code contains a lot of meta-data in addition to the mathematical model itself. An annotation system exists within Modelica and custom annotations can be written to pass data to tools. Annotations were designed to be an extensible mechanism for capturing meta-data related to a model including html-documentation, vendor-specific data, and graphical annotations. Parameters, variables, equations, and models can contain documentation strings and annotations. Furthermore, packages can be documented and the display order of sub-packages, models, classes, functions, connectors and other objects can be specified. Modelica libraries have a hierarchical structure consisting mainly of packages which contain models and other objects as well as sub-packages. Models themselves can be composed of multiple, possibly replaceable, components. Much of this information will be required by the OpenStudio tool in order to understand which component models are available, where they reside in the library’s package structure which determines their fully qualified name, what parameters they have, how they can be configured, how to display them, and what other meta-data are available, such as documentation strings and type of connectors that can be connected with each other.

This section is concerned about making the AST of Modelica source files, including the metadata mentioned above, available to OpenStudio. We will specifically focus on the Modelica Buildings Library (MBL), but other Modelica libraries can be used as well.

The intent of the AST representation is primarily to be consumed by the OpenStudio application for identification of models, model documentation, model connecting points, inputs/outputs, and parameters and related meta-data.

An OpenStudio application will consume some or all of the above information, use it to provide an interface to the user, and ultimately write out a Modelica file which connects the various library components, configures various components, and assigns values to parameters.

To create models, information from other libraries, most notably, the Modelica Standard Library (MSL), may need to be made available. Typical applications and examples from the MBL use component models from both the MSL and MBL. There is also interest in being able to support additional third party libraries.

5.4.1.2. Requirements

This section will describe the requirements of a batch-process program that will transform any Modelica input file or library into a json file. The json file shall contain:

  • identification of which models, classes, connectors, functions, etc. exist
  • identification of the relative hierarchy of the above components within the package structure of the library
  • for each package,
    • what models are available
    • what connection “ports” are available
    • for fluid ports, which ports carry the same fluid (so that media assignments can be propagated through a circuit) [1]
    • what control inputs/outputs are available
    • what parameters are available
    • what configuration management options exist (i.e., replaceable components and packages and which parameters belong to which component)
    • meta-data and attributes for all the above such as graphics annotations, vendor annotations, html documentation, etc.

For integration with OpenStudio, the tool should reduce dependencies that complicate the distribution to users.

The tool shall also have options to exclude certain packages. For example, OpenStudio may give access to the package Modelica.Blocks but not to Modelica.Magnetic.

The tool shall also allow to perform a “diff” (i.e., logical differences) between two generated json files. The purpose of the diff tool would be to detect non-trivial differences between two generated json files that hold the AST of a Modelica library and report those changes. The content of the “difference” file would show the differences between the two input manifests.

The diff tool would be of use in particular when new versions of the MBL are released and the OpenStudio team would like to check if there are non-trivial changes they need to integrate.

The following two lists attempt to list examples of changes that cannot be ignored as well as changes that can be ignored.

Significant Changes (backward-incompatible)

  • changes to names of any public package, model, block, function, record, as well as public constants, parameters, variables, connectors, or subcomponents
  • addition of any public parameters that has no default value, variables that have no equation (in a partial model), connectors (except for output connectors), or subcomponents that require changes to models that extend or instantiate these components
  • addition/subtraction of packages/models/blocks/functions meant to be consumed by OpenStudio
  • removing of any public constant, parameter, or class (model, block, function, type etc.)
  • moving a class between packages (i.e., changing the path)

Changes that may be ignored (backward-compatible)

  • changes in style without changes in meaning (addition or removal of whitespace, reordering within a section)
  • changes to documentation strings
  • changes to text in embedded HTML documentation
  • changes to revision notes
  • changes to graphical annotations
  • changes in ordering of models/blocks/functions within a package
  • changes to protected sections of code
  • changes to equation or algorithm sections of code
  • changes to some models/blocks/functions may be completely ignored if, by convention, we deem certain paths as “not directly consumable” by OpenStudio. For example, we may wish to not consume classes in the packages BaseClasses, Examples and Validations.

To summarize, the creation of or changes to the “public API” (public parameters, variables, subcomponents, connectors) of models, blocks, or functions must be compared for change detection. Documentation (html)/ documentation strings/graphics annotations will not affect the model interface or semantics. Changes to protected properties or equations will not affect the interface (but may affect the actual numeric output quantities).

5.4.1.3. Literature review

There have been several attempts to represent or use XML in relation to Modelica in the past ([Lan14], [Fri03], [PF03], [PSAssmannF05], and [RKH+06]).

In particular, Landin [Lan14] did work with Modelon using JModelica to export XML for the purpose of model exchange, which is similar to our use case. Unfortunately, this work deals only with “flattened” models – Modelica models that have been instantiated with all of the hierarchy removed. For our use case, the hierarchy must be preserved so that the OpenStudio team can build a new model through instantiation of models from the MBL. For this purpose, JModelica also provides access to the the source AST and instance AST (see the JModelica user guide).

Reisenbichler [RKH+06] motivates the usage of XML in association with Modelica without getting into specifics. The remaining work by Pop and Fritzson is thus the only comprehensive work on an XML representation of Modelica source AST that appears in the literature ([PF03], [PSAssmannF05], and [Fri03]). The purpose of the XML work by Pop and Fritzson was to create a complete XML representation of the entire Modelica source.

ANTLR (ANother Tool for Language Recognition) is a parser generator for reading, processing, executing, or translating structured text or binary files. It is widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build parse trees and also generates a listener interface (or visitor) that makes it easy to respond to the recognition of phrases of interest. For ANTLR, a Modelica grammar is available at https://github.com/antlr/grammars-v4/blob/master/modelica/modelica.g4.

5.4.1.4. Implementation

Work started on the implementation of a modelica-json translator. The development page is https://github.com/lbl-srg/modelica-json

To illustrate the translation, consider the following simple model:

within FromModelica;
block BlockWithBlock1 "A block that instantiates another public and protected block"
  Block1 bloPub "A public block";
protected
  Block1 bloPro "A protected block";
end BlockWithBlock1;

When parsed to json, the output is:

[
  {
    "modelicaFile": "./BlockWithBlock1.mo",
    "within": "FromModelica",
    "topClassName": "FromModelica.BlockWithBlock1",
    "comment": "A block that instantiates another public and protected block",
    "public": {
      "models": [
        {
          "className": "Block1",
          "name": "bloPub",
          "comment": "A public block"
        }
      ]
    },
    "protected": {
      "models": [
        {
          "className": "Block1",
          "name": "bloPro",
          "comment": "A protected block"
        }
      ]
    }
  }
]

Footnotes

[1]We anticipate that the MBL will be redesigned so that users no longer need to assign media.