.ModelicaDEVS.UsersGuide.ModelicaDEVS.Theory

Information

Concepts of the ModelicaDEVS Library

The subsequent sections provide a more "theoretical" background about ModelicaDEVS, discussing the problems that were encountered while implementing the DEVS Formalism in Modelica, due to the pecularities of the the Dymola/Modelica programming environment.
Note that the section about atomic models may also serve as a HOWTO for the translation of further DEVS models from C++ code (e.g. in the case of future developments within PowerDEVS) into Modelica.


Dymola Programming/Simulation Environment

This section presents some properties of Dymola/Modelica that are necessary for a thorough understanding of the subsequent sections in this chapter.

Simultaneous Equations
While C++ is an imperative programming language and lives on the principle of functions calling other functions, Modelica models are described mathematically by equations, and the simulation makes use of the simultaneous evaluation of these model equations. This means that in every simulation step, Dymola collects all equations of a model, even if they are spread over several blocks, and then tries to evaluate them. After it has found a value for each variable such that all equations are fulfilled, a new simulation step is started.
A direct consequence of this approach is the parallel update (in terms of simulation time) of the state variables. This can be exploited for the implementation of DEVS in Modelica, but at the same time causes some problems that do not have to be dealt with when using a programming language like C++.

Simulation Time
The simulation environment of Dymola comes with a global clock that is accessible through the variable time. Note that it is not possible to set the clock by assigning a certain value to time. The purpose of the variable time is only to give the programmer the opportunity to query the current simulation time.

State and Time Events
Another particularity of Dymola is the time or state events that are triggered by equations containing inequalities.

The scope of state events is to inhibit discontinuities during integration. For an example, consider the following Modelica model (see also [Dymola]) and the corresponding graph:
model EventTest
  input Real u;
  output Real y;<
equation
y = if u>0 then 1 else -1; end EventTest;
At the point u=0, the equation for y is discontinuous. Such discontinuities caused by a relation like u>0 raise a problem because the branch of the if-statement can be switched at a certain point in time, and all of a sudden y can take a completely new value, "artificially" assigned, that has nothing to do with the previous trajectory of y.
The solution of Dymola to this problem is to stop the integration at point $u=0$, select the appropriate branch of the if-statement and restart the simulation with new initial conditions. Note that there are situations when no state events have to or even must be triggered. This can be achieved by using the noEvent() operator. For more details see [Dymola] or [Modelica].

Inequalities that only depend on the variable time as the only continuous unknown variable may also trigger time events instead of state events. The following table illustrates which relations still cause state events - even if time is their only continuous variable - and which entail only time events:
State EventsTime Events
time <= [discrete expression]time >= [discrete expression]
time > [discrete expression]time < [discrete expression]
Whereby the discrete expression may itself be a function of constants, prameters, and discrete variables, but not continuous variables.

The main difference between time and state events is that Dymola does not have to iterate on time events but "jumps" directly to the point in time when the time event will arise. Thus, they may save computation time, and it is recommended to use time events instead of state events wherever it is possible.
ModelicaDEVS has been implemented using time events as much as possible, limiting the use of state events to those few situations only, where they could not be avoided. This approach is also described in [Fritzson04].


Atomic Models

The mapping of an atomic model from PowerDEVS to Modelica does not yet cause many difficulties but is rather a translation of the C++ code into Modelica - which however is not as straightforward as would be a translation to Java or Pascal, as we shall see.
Of course, the design of an atomic model has to provide the features requested by the simulation of a coupled model and therefore already involves the solutions to the issues that will be discussed in the subsequent sections. It is hence possible that the general block structure developed in this section is not yet fully understandable. However, the basic concepts should be clear and allow for a better understanding of the subsequent sections.

Events/Ports
An event in PowerDEVS consists of four values: the event creation time and the coefficients to the first three terms (constant, linear and quadratic) of the function's Taylor series expansion. In other words, an event is described by the current function value, the first derivative of the function at the current time instant and its second derivative devided by two.
The higher-order approximated output of QSS2 and QSS3 models is then equal to the first-order or second-order Taylor series expansion: the output is given by
y[0]=y[0]+y[1]*(t-tlast) + y[2]* (t-tlast)^2
which, with the entries of the y-vector holding the coefficients to the first three terms of the Taylor series expansion, corresponds to the Taylor series up to second-order for QSS3, or first-order for QSS2 where y[2] is always 0.
Note that the coefficients to the linear and the quadratic terms of the Taylor series are only used for QSS2 and QSS3.
Since these values sufficiently describe an event, ModelicaDEVS simply adapts this structure to a large extent. The only difference is the replacement of the event creation time by a boolean variable that indicates whether a certain block is currently sending an event. Hence, whereas PowerDEVS port are represented by single vectors, ModelicaDEVS ports consist of a vector of size three (for the three input/output values) and the aforementioned boolean variable. The code snippet below shows the declaration of a ModelicaDEVS input port. Output ports are declared analogously:

connector DiscreteInPort "Discrete Connector"
  input Real signal[3];
  input Boolean event;
end DiscreteInPort;

Within the equation section of the Modelica model the parts of the input/output ports are referred to as uVal[1], uVal[2], uVal[3] and uEvent for input ports, and yVal[1], yVal[2], yVal[3] and yEvent for output ports. The name conversions from a vector named signal to a vector named uVal and a boolean variable event to a variable uEvent takes place in the block templates from which all ModelicaDEVS models are derived.

Remark: for simplicity reasons, we shall pretend that ModelicaDEVS input/output ports are represented by single vectors of size four, whereby the fourth entry is not of type real but of type boolean, instead of a vector of size three plus a boolean variable. In reality, it is of course not possible to have mixed arrays, but this assumption facilitates the writing and understanding of sections dealing with event-passing issues: it is no longer necessary to make a distinction between the vector part of the port and the boolean variable, but we can simply think of the boolean variable to be the fourth entry in the vectors that represent the input/output ports of ModelicaDEVS models.

Transitions
A DEVS model consists of the following subroutines (cf. the section about the DEVS formalism ): internal transition, external transition, time-advance, and lambda function. PowerDEVS components additionally contain an initialisation function. For ModelicaDEVS, this means that basically all these parts have to be explicitly or implicitly present (including the initialisation function).
As already mentioned, Modelica and C++ operate on different paradigms, (simultaneous equations vs. sequential processing) which impedes a direct translation without major modifications. With the help of some gimmicks however, respecting certain rules and restrictions, the C++ code can be transformed into Modelica rather easily. Note though that the code obtained by such a "mechanical" translation is only more or less a correct copy of the PowerDEVS model and has to be examined for possible intrinsic particularities that may require further block specific programming.
The following text discusses the way that has been chosen to create Modelica models with equal functionality to the PowerDEVS blocks.

The time-advance function is represented by a variable sigma. There is hence no reason for an explicit time-advance function anymore (PowerDEVS still implements it because it is needed for the hierarchic simulator framework). Note that even in the theoretical definition of a DEVS model it is a popular trick to represent the time-advance function by a variable called sigma.

A variable lastTime holds the time of the last execution of a transition (internal or external). This variable is used for the computation of the variable e (ε in the original DEVS definition)
e=time-pre(lastTime);
where e is the time the system has already spent in the current state.

Two boolean variables, dint and dext, are used to determine if there is currently any internal or external event to be processed.
An internal transition depends only on sigma (the time-advance function) and e (ε). Hence, the value of dint can be calculated as:
dint= time >= pre(lastTime)+pre(sigma);
The external transition of a block B attached to the output port of a block A is executed at the very moment when A generates an output event. This is where the fourth entry in the port vector comes in. The variable dext of a block B is defined as follows:
dext = uEvent;
where uEvent is the fourth entry of the input vector of B (A.yEvent = B.uEvent).

The lambda function and the internal/external transitions are represented by when-statements. Most blocks feature two when-statements: What is the reason for this perhaps not very intuitive structure? Why not just have two when-statements, one representing the internal transition and the lambda function, the other one representing the external transition? The reason for packing the internal and external transitions into a single when-statement is due to a rule of the Modelica language specification that states that equations in different when-statements may be evaluated simultaneously (Modelica cannot/does not check for mutually exclusive when-statements). Hence, if there are two when-statements each containing an expression for a variable X, X is considered overdetermined. This circumstance would cause a syntactical problem with variables that have to be updated both during the internal and the external transition and thus would have to appear in both when-statements. For this reason, we need to have a when-statement that is active if either dint or dext becomes true. An additional discrimination is done \textbf{within} the when-statement, determining whether it was an internal (dint is true) or an external transition (dext is true) that made the when-statement become active, and as the case may be, updating the variables with the appropriate value (note that most variables take different values during internal transitions compared to external ones).
A typical "dint-dext" when-statement looks like one of the following (assume three variables X, Y, Za and Zb where X is updated during the internal transition, Y during the external transition, Za during both transitions but to different values and Zb also during both transitions but to the same value):
when {dint, dext} then        when {dint, dext} then
  if dint then                  X = if dint then 4 else pre(X);
    X = 4;                      Y = if dint then pre(Y) else 9;
    Y = pre(Y);                 Za= if dint then 6 else 7;
    Za= 6;                      Zb= 2;
  else                        end when;
    X = pre(X);
    Y = 9;
    Za= 7;
  end if;
  Zb= 2;
end when;
Having now a place where equations for variables that are modified during the internal transition can go, what is the purpose of the second when-statement, which is also - though exclusively - active if dint becomes true? Its main justification is to separate the internal/external transitions from the output part. Furthermore, the output variables are only updated before an internal transition, thus, they do not necessarily need to appear within a dint-dext when-statement.
The typical "lambda-function" part (containing a when-statement and a separated instruction for the yEvent variable) looks as follows:
when dint then
  yVal[1]= ...;
  yVal[2]= ...;
  yVal[3]= ...;
end when;
  yEvent = edge(dint);
Note that the right hand side of the equations in the lambda function normally depends on pre() values of the used variables. This is due to the fact that the lambda function has to be executed prior to the internal transition. In ModelicaDEVS however, the two corresponding parts in the model are activated simultaneously (namely, when dint becomes true), so the temporal distinction between the lambda function and the internal transition has to be done via the pre() operator of Modelica.
The variable yEvent has to be true in the exact instant when an internal transition is executed and false otherwise. This behaviour is obtained by using the edge() operator of Modelica, where the edge() operator returns true at the time instant when the value of its argument switches from false to true and false at any other time instant.

Miscellaneous
For variables that are modified more than once during one simulation step, or that are updated by means of a complicated combination of if-then-else-statements, it is recommended to declare a function that contains an almost exact copy of the C++ code segment in question (an example where this method was applied is the ModelicaDEVS Integrator block).

A very important advice in this context is to always carefully check for occurrences of the sqrt() function and make sure that it is only called with a positive argument. In case the argument becomes negative, assign a value of infinity to the variables the values of which would have been computed using the sqrt() function. Otherwise, the Modelica models show an erroneous behaviour, which is often fairly illogical and thus hard to find. It is therefore more than just advisable to pay attention to this detail since from the beginning.
Note that returning infinity if sqrt() is called with a negative argument is what a C++ compiler does. Therefore, the current version of PowerDEVS which does not yet check for negative arguments to sqrt(), works adequately. This is however a poor programming style, and the sqrt() issue is subject to be corrected in future versions of PowerDEVS.

Basic Model Structure
The average block of the ModelicaDEVS library exhibits the following basic structure:
block SampleBlock
  extends ModelicaDEVS.Interfaces. ... ;
  parameter Real ... ;
protected
  discrete Real lastTime(start=0);
  discrete Real sigma(start=...);
  Real e;     //epsilon - how long has the system been in this state yet?
  Boolean dext;
  Boolean dint;
  [...other variable declarations...]

equation
  dext = uEvent;
  dint = time>=pre(lastTime)+pre(sigma);

  when {dint} then
    yVal[1]= ...;
    yVal[2]= ...;
    yVal[3]= ...;
  end when;
    yEvent = edge(dint);

  when {dext, dint} then
          e=time-pre(lastTime);
    if dint then
      [...internal transition behaviour...]
    else
      [...external transition behaviour...]
    end if;
    lastTime=time;
  end when;

end SampleBlock;



Coupled Models

Time-advance Mechanism
In Dymola, there is already a clock present, and it is recommended to use it as the actual simulation clock, since the graphical output (e.g. the values of state variables) will be plotted against time (on the x-axis), and if the simulation is not synchronized with the Modelica simulation clock, the graphical output may not be very illustrative anymore.

Direct Block Interaction
The solution that was found for ModelicaDEVS to enable the required block interaction for coupled models is to pass a boolean signal along with the compulsory event values. The boolean signal is used to inform a block B about the occurrence of an output event at block A (given that block B is connected to the output port of block A).

Let us consider a small example. Assume a two-block system consisting of block A and block B, where block B is connected to the output port of block A. Every block features a variable dext accompanied by an equation
 dext = uEvent;
where uEvent is the boolean component of the input vector that represents events. Suppose now that block A produces an event at time $t=3$. As already pointed out, an event consists of three state-related values plus a boolean variable. At the precise instant when A generates an event, it updates its output vector with the appropriate values and sets yEvent to true:
when dint then
  yVal[1]= ...; //new output value 1
  yVal[2]= ...; //new output value 2
  yVal[3]= ...; //new output value 3
end when;
  yEvent = edge(dint);
Still at time t=3, block B notices that now uEvent has become true (note that B.uEvent = A.yEvent) and therefore dext has become true, too. Block B is then going to execute its external transition.

Concurrent Events
Two events that are generated at one and the same time are called concurrent events. There are four types of concurrent events, differing from each other by the location where in the system they occur: Apparently, concurrent events only require additional attention if they occur at the same block. If the given block has two input ports, it has to provide a solution how to treat the two signals by itself. If it has only one input port, it will have to choose between the external event or the internal transition to process first. As already mentioned, this is related to the priority issue of the DEVS formalism and the problem of loops - depending on how the concurrent events have been generated.

Loops
Loops occur if a variable X is being used (either on the right hand side of an equation or in a when-statement) to update a variable Y that is used to update the variable X. Due to the fact that the view of Modelica is model-wide (it treats all equations that appear in the components of a model equally), such a loop may also be spread over several blocks: a variable X of block A is used to update another variable Y in a block B that is used again to update the variable X of the block A (this corresponds to the problem in the section about priorities).
Such cycles are called algebraic loops. They may be broken by applying the pre() operator on certain variables of the loop: the pre() operator characterizes the left limit of a variable at a given time instant and therefore may break the loop by inserting an infinitesimally small time delay between two updates of a loop-variable (see [Dymola] for more details).

Once the components are programmed correctly in terms of breaking potential loops by inserting the pre() operator in appropriate places, there is a second type of cycles that may occur: components that set their local variable sigma to zero during the external transition have to execute an internal transition immediately after (actually contemporarily to) the current external transition. Dymola solves such loops automatically by iterating until a consistent restart condition is found (using the infinitesimally small time delay induced by the pre() operator for variables that have to be updated twice or more times within one simulation step).

Priorities
In fact, there is only one single situation where the importance of priority settings between components comes in: two connected blocks, where both block A and block B have to execute an internal transition at the same time.
Block A actually does not even feel the presence of the concurrent events, and just processes the internal transition. This however causes a priority problem in block B which was about to go through an internal transition itself and now receives an external event at the same time, since the internal transition of block A entails an output that appears as an external event at block B.

In our simple two-block-example there are only two possible priority orderings with the following consequences: The problem of block priorities can be solved in two ways: by an explicit, absolute ordering of all components in a model, or by letting every block determine itself whether it processes the external or the internal event first, in case both of them occur at the same time. While PowerDEVS makes use of the former approach, ModelicaDEVS implements the latter: because of the block-to-block nature of the communication in coupled models, a global priority list of all components would be useless or at least associated with additional effort for checks on the priority relation between two active components. It is therefore much more convenient to take this decision locally by simply determining whether to execute the internal or the external transition first.

In ModelicaDEVS, there is only a hard coded version of the priority handling implemented: an internal transition is always prior to an external one.
The reason for this choice - the fixed ordering as well as the priority order of internal before external - lies in the way events are passed from block to block and in the inability of Dymola/Modelica to solve algebraic loops. Assume the following abstract code snippet containing the basic variables and when-statements to enable a block to recognise internal and external events and to produce output events.
equation
  dext = uEvent;
  dint = time>=pre(lastTime)+pre(sigma);

  when {dint} then
    yVal[1]=...;
    yVal[2]=...;
    yVal[3]=...;
  end when;
    yEvent=edge(dint);

  when {dext, dint} then
    if dint then
      sigma = ...;  //something...
    else
      sigma = ...;  //something else...
    end if;
    lastTime=time;
  end when;
There are two important facts to take into consideration: a) the boolean variable dext depends on uEvent, which is actually the output (the yEvent variable) of another block, and b) the variable yEvent of any component depends on the variable dint. Note that the way shown above is not the only one to program a block, but in any case the statements a) and b) hold, which is the critical point to the fixed priority order problem.
Consider now a model that - in case of a concurrent occurrence of two events - attempts to execute the external transition before the internal transition. Then, the first when-statement in the code above would have to be changed into
 when (dint and not dext) then
and yEvent would have to be modified into
 yEvent = edge(dint and not dext)

Note that the above assignment is actually invalid since the edge() operator expects a single variable as an argument. It however exactly demonstrates the meaning of the required modification. In correct Modelica, an auxiliary variable would have to be created, taking the value of "dint and not dext", that then can be passed to the edge() operator.
This however would entail the update of the output variable yEvent to be dependent not only on dint, but on dext, too. Assume now a toy-scenario of a loop of two blocks A and B that execute their external transaction prior to the internal transaction and therefore have been programmed as described above, with the modified when-statement.


Because of the cycle between the variables yEvent, uEvent and dext (the output yEvent of one block becomes the input uEvent of the other block), such a scenario results in an algebraic-loop-error message of Dymola.
More generally, we can say that the output of a block must not depend directly on the input, because a number n of blocks that are connected in a cycle (the output of block n is connected to the input of block 1) could cause the following dependency loop : output 1 depends on input 1; input 2 depends on output 1; output 2 depends on input 2; input 3 depends on output 2; ... ; input n depends on output n-1; output n depends on input n; input 1 depends on output n.

The cause for this issue is Modelica's principle of simultaneous equations and thus the problem cannot be avoided. For this reason, yEvent has to remain independent of dext and has to be updated in a way such that it depends solely on dint. Hence an internal transition has to be treated prior to an external one, because if dint becomes true, also yEvent does so. This however indicates the generation of an output event, and every output event has to be followed by an internal transition. If the external transition were treated with priority to the internal transition, it could give rise to a situation where the external transition is executed and an output is generated, which would be an illegal behaviour according to the DEVS formalism.


Hierarchic Models

Since Dymola/Modelica is already aimed at object-oriented modelling, which includes the reuse of autonomous models as parts of larger models, the issue of hierarchic systems did not need any particular treatment.
Furthermore, because of the equation-based simulation mechanism, Dymola/Modelica does not handle hierarchical models differently to flat models: while simulating a model, Dymola collects the equations of all components constituting the model and evaluates them simultaneously. A second-level submodel contributes just some additional equations to the global equation "pool", but does not cause any further inconveniences, like in PowerDEVS, where hierarchical models entail a larger amount of messages passed between the hierarchy levels.


Time/State Events

As explained before, Dymola can trigger two types of events: The only expressions that are responsible for activating the when-statements in the models,
 dext = uEvent;
and
 dint = time>=pre(lastTime)+pre(sigma);
both trigger time events and hence avoid the more expensive (in terms of computation time) state events.

A previous version of ModelicaDEVS used an approach that triggered only state events. According to performance comparisons carried out between the two versions, it has been found that the time event approach is roughly four times faster than its state event counterpart.


Generated at 2024-04-28T18:16:21Z by OpenModelicaOpenModelica 1.22.3 using GenerateDoc.mos