An equation is part of a class definition. A scalar equation relates scalar variables, i.e., constrains the values that these variables can take simultaneously. When $n$-1 variables of an equation containing $n$ variables are known, the value of the $n$th variable can be inferred (solved for). In contrast to a statement in an algorithm section, an equation does not define for which of its variable it is to be solved. Special cases are: initial equations, instantaneous equations, declaration equations.
An equation or statement is instantaneous if it holds only at events, i.e., at single points in time. The equations and statements of a when-clause are instantaneous, see section 8.3.5 and section 11.2.7.
Equations in Modelica can be classified into different categories depending on the syntactic context in which they occur:
Normal equality equations occurring in equation sections, including connect-equations and other equation types of special syntactic form (section 8.3).
Declaration equations, which are part of variable, parameter, or constant declarations (section 4.4.2.1).
Modification equations, which are commonly used to modify attributes of classes (section 7.2).
Binding equations, which include both declaration equations and element modification for the value of the variable itself. These are considered equations when appearing outside functions, and then a component with a binding equation has its value bound to some expression. (Binding equations can also appear in functions, see section 12.4.4.)
Initial equations, which are used to express equations for solving initialization problems (section 8.6).
A flattened equation is identical to the corresponding nonflattened equation.
Names in an equation shall be found by looking up in the partially flattened enclosing class of the equation.
An equation section is comprised of the keyword equation followed by a sequence of equations. The formal syntax is as follows:
The following kinds of equations may occur in equation sections. The syntax is defined as follows:
No statements are allowed in equation sections, including the assignment statement using the := operator.
Simple equality equations are the traditional kinds of equations known from mathematics that express an equality relation between two expressions. There are two syntactic forms of such equations in Modelica. The first form below is equality equations between two expressions, whereas the second form is used when calling a function with several results. The syntax for simple equality equations is as follows:
The types of the left-hand-side and the right-hand-side of an equation need to be compatible in the same way as two arguments of binary operators (section 6.7).
Three examples:
simple_expr1 = expr2;
(if pred then alt1 else alt2) = expr2;
(out1, out2, out3) = function_name(inexpr1, inexpr2);
[Note: According to the grammar the if-then-else expression in the second example needs to be enclosed in parentheses to avoid parsing ambiguities. Also compare with section 11.2.1.1 about calling functions with several results in assignment statements.]
The syntax of a for-equation is as follows:
For-equations may optionally use several iterators (for-indices), see section 11.2.2.3 for more information:
The following is one example of a prefix of a for-equation:
The expression of a for-equation shall be a vector expression, where more general array expressions are treated as vector of vectors or vector of matrices. It is evaluated once for each for-equation, and is evaluated in the scope immediately enclosing the for-equation. The expression of a for-equation shall be a parameter expression. The iteration range of a for-equation can also be specified as Boolean or as an enumeration type, see section 11.2.2.2 for more information. The loop-variable (IDENT) is in scope inside the loop-construct and shall not be assigned to. For each element of the evaluated vector expression, in the normal order, the loop-variable gets the value of that element and that is used to evaluate the body of the for-loop.
[Example:
The loop-variable may hide other variables as in the following example. Using another name for the loop-variable is, however, strongly recommended.
]
The iteration range of a loop-variable may sometimes be inferred from its use as an array index. See section 11.2.2.1 for more information.
[Example:
]
A connect-equation has the following syntax:
These can be placed inside for-equations and if-equations; provided the indices of the for-loop and conditions of the if-clause are parameter expressions that do not depend on cardinality, rooted, Connections.rooted, or Connections.isRoot. The for-equations/if-equations are expanded. Connect-equations are described in detail in section 9.1.
The same restrictions apply to Connections.branch, Connections.root, and Connections.potentialRoot; which after expansion are handled according to section 9.4.
If-equations have the following syntax:
The expression of an if- or elseif-clause must be a scalar Boolean expression. One if-clause, and zero or more elseif-clauses, and an optional else-clause together form a list of branches. One or zero of the bodies of these if-, elseif- and else-clauses is selected, by evaluating the conditions of the if- and elseif-clauses sequentially until a condition that evaluates to true is found. If none of the conditions evaluate to true the body of the else-clause is selected (if an else-clause exists, otherwise no body is selected). In an equation section, the equations in the body are seen as equations that must be satisfied. The bodies that are not selected have no effect on that model evaluation.
If-equations in equation sections which do not have exclusively parameter expressions as switching conditions shall have the same number of equations in each branch (a missing else is counted as zero equations and the number of equations is defined after expanding the equations to scalar equations).
[If this condition is violated, the single assignment rule would not hold, because the number of equations may change during simulation although the number of unknowns remains the same.]
When-equations have the following syntax:
The expression of a when-equation shall be a discrete-time Boolean scalar or vector expression. The statements within a when-equation are activated when the scalar expression or any of the elements of the vector expression becomes true.
[Example: The order between the equations in a when-equation does not matter, e.g.
]
A when-equation:
is conceptually equivalent to the following equations containing special if-expressions
[The equivalence is conceptual since pre($\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$) of a non discrete-time Real variable or expression can only be used within a when-clause. Example:
Here, x is a discrete-time variable (whether it is declared with the discrete prefix or not), but u and y cannot be discrete-time variables (since they are not assigned in when-clauses). However, pre(u) is legal within the when-clause, since the body of the when-clause is only evaluated at events, and thus all expressions are discrete-time expressions.]
The start-values of the introduced Boolean variables are defined by the taking the start-value of the when-condition, as above where b is a parameter variable. The start-value of the special functions initial, terminal, and sample is false.
When-equations shall not occur inside initial equations.
When-equations cannot be nested.
When-equations can only occur within if-equations and for-equations if the controlling expressions are exclusively parameter expressions.
[Example: The following when-equation is invalid:
]
The equations within the when-equation must have one of the following forms:
v = expr;
(out1, out2, out3, $\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$) = function_call_name(in1, in2, $\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$);
Operators assert, terminate, reinit.
For- and if-equations if the equations within the for- and if-equations satisfy these requirements.
The different branches of when/elsewhen must have the same set of component references on the left-hand side.
The branches of an if-then-else clause inside when-equations must have the same set of component references on the left-hand side, unless the if-then-else have exclusively parameter expressions as switching conditions.
Any left hand side reference, (v, out1, …), in a when-clause must be a component reference, and any indices must be parameter expressions.
[The needed restrictions on equations within a when-equation becomes apparent with the following example:
When the equations of the when-equation are not activated it is not clear which variable to hold constant, either x or y. A corrected version of this example is:
Here, variable y is held constant when the when-equation is deactivated and x is computed from the first equation using the value of y from the previous event instant.]
[Example: The restrictions for if-equations mean that both of the following variants are illegal:
whereas the restriction to parameter-expression is intended to allow:
]
The Modelica single-assignment rule (section 8.4) has implications for when-equations:
Two when-equations shall not define the same variable.
[Without this rule this may actually happen for the erroneous model DoubleWhenConflict below, since there are two equations (close = true; close = false;) defining the same variable close. A conflict between the equations will occur if both conditions would become true at the same time instant.
One way to resolve the conflict would be to give one of the two when-equations higher priority. This is possible by rewriting the when-equation using elsewhen, as in the WhenPriority model below or using the statement version of the when-construct, see section 11.2.7.]
When-equations involving elsewhen-parts can be used to resolve assignment conflicts since the first of the when/elsewhen parts are given higher priority than later ones:
[Below it is well defined what happens if both conditions become true at the same time instant since condition1 with associated conditional equations has a higher priority than condition2.
]
reinit can only be used in the body of a when-equation. It has the following syntax:
The operator reinitializes x with expr at an event instant. x is a Real variable (or an array of Real variables) that must be selected as a state (resp., states), i.e. reinit on x implies stateSelect = StateSelect.always on x. expr needs to be type-compatible with x. reinit can for the same variable (resp. array of variables) only be applied (either as an individual variable or as part of an array of variables) in one equation (having reinit of the same variable in when and else-when of the same variable is allowed). In case of reinit active during initialization (due to when initial), see section 8.6.
reinit does not break the single assignment rule, because reinit(x, expr) in equations evaluates expr to a value, then at the end of the current event iteration step it assigns this value to x (this copying from values to reinitialized state(s) is done after all other evaluations of the model and before copying x to pre(x)).
[Example: If a higher index system is present, i.e., constraints between state variables, some state variables need to be redefined to non-state variables. During simulation, non-state variables should be chosen in such a way that variables with an applied reinit are selected as states at least when the corresponding when-clauses become active. If this is not possible, an error occurs, since otherwise reinit would be applied to a non-state variable.
Example for the usage of reinit (bouncing ball):
]
An equation or statement of one of the following forms is an assertion:
Here, condition is a Boolean expression, message is a String expression, and assertionLevel is an optional parameter expression of the built-in enumeration type AssertionLevel. It can be used in equation sections or algorithm sections.
[This means that assert can be called as if it were a function with three formal parameters, the third formal parameter has the name level and the default value AssertionLevel.error.]
[A parameter expression is required for level since it shall be evaluated at compile time.]
If the condition of an assertion is true, message is not evaluated and the procedure call is ignored. If the condition evaluates to false, different actions are taken depending on the level input:
level = AssertionLevel.error: The current evaluation is aborted. The simulation may continue with another evaluation. If the simulation is aborted, message indicates the cause of the error.
[Ways to continue simulation with another evaluation include using a shorter step-size, or changing the values of iterationvariables.]
Failed assertions take precedence over successful termination, such that if the model first triggers the end of successful analysis by reaching the stop-time or explicitly with terminate, but the evaluation with terminal()=true triggers an assert, the analysis failed.
level = AssertionLevel.warning: The current evaluation is not aborted. message indicates the cause of the warning.
[It is recommended to report the warning only once when the condition becomes false, and it is reported that the condition is no longer violated when the condition returns to true. The assert statement shall have no influence on the behavior of the model. For example, by evaluating the condition and reporting the message only after accepted integrator steps. condition needs to be implicitly treated with noEvent since otherwise events might be triggered that can lead to slightly changed simulation results.]
[The AssertionLevel.error case can be used to avoid evaluating a model outside its limits of validity; for instance, a function to compute the saturated liquid temperature cannot be called with a pressure lower than the triple point value.
The AssertionLevel.warning case can be used when the boundary of validity is not hard: for instance, a fluid property model based on a polynomial interpolation curve might give accurate results between temperatures of 250 K and 400 K, but still give reasonable results in the range 200 K and 500 K. When the temperature gets out of the smaller interval, but still stays in the largest one, the user should be warned, but the simulation should continue without any further action. The corresponding code would be:
]
The terminate equation or statement (using function syntax) successfully terminates the analysis which was carried out, see also section 8.3.7. The termination is not immediate at the place where it is defined since not all variable results might be available that are necessary for a successful stop. Instead, the termination actually takes place when the current integrator step is successfully finalized or at an event instant after the event handling has been completed before restarting the integration.
The terminate statement has a string argument indicating the reason for the success.
[Example: The intention of the terminate statement is to give more complex stopping criteria than a fixed point in time:
]
See section 9.4 for a description of this topic.
Modelica is based on the synchronous data flow principle and the single assignment rule, which are defined in the following way:
Discrete-time variables keep their values until these variables are explicitly changed. Differentiated variables have der(x) corresponding to the time-derivative of x, and x is continuous, except when reinit is triggered, see section 8.3.6. Variable values can be accessed at any time instant during continuous integration and at event instants.
At every time instant, during continuous integration and at event instants, the equations express relations between variables which have to be fulfilled concurrently.
Computation and communication at an event instant does not take time.
[If computation or communication time has to be simulated, this property has to be explicitly modeled.]
There must exist a perfect matching of variables to equations after flattening, where a variable can only be matched to equations that can contribute to solving for the variable (perfect matching rule – previously called single assignment rule); see also globally balanced section 4.7.
An event is something that occurs instantaneously at a specific time or when a specific condition occurs. Events are for example defined by the condition occurring in a when-clause, if-clause, or if-expression.
The integration is halted and an event occurs whenever an event generation expression, e.g. x > 2 o or floor(x), changes its value. An event generating expression has an internal buffer, and the value of the expression can only be changed at event instants. If the evaluated expression is inconsistent with the buffer, that will trigger an event and the buffer will be updated with a new value at the event instant. During continuous integration event generation expression has the constant value of the expression from the last event instant.
[A root finding mechanism is needed which determines a small time interval in which the expression changes its value; the event occurs at the right side of this interval.]
[Example:
During continuous integration always the same if-branch is evaluated. The integration is halted whenever u-uMax or u-uMin crosses zero. At the event instant, the correct if-branch is selected and the integration is restarted.
Numerical integration methods of order $n$ ($n\mathrm{\ge}\mathrm{1}$) require continuous model equations which are differentiable up to order $n$. This requirement can be fulfilled if Real elementary relations are not treated literally but as defined above, because discontinuous changes can only occur at event instants and no longer during continuous integration.]
[It is a quality of implementation issue that the following special relations
trigger a time event at time = discrete expression, i.e., the event instant is known in advance and no iteration is needed to find the exact event instant.]
Relations are taken literally also during continuous integration, if the relation or the expression in which the relation is present, are the argument of noEvent. smooth also allows relations used as argument to be taken literally. The noEvent feature is propagated to all subrelations in the scope of the noEvent application. For smooth the liberty to not allow literal evaluation is propagated to all subrelations, but the smoothness property itself is not propagated.
[Example:
In this case x = y = z, but a tool might generate events for z. The if-expression is taken literally without inducing state events.
The smooth operator is useful, if e.g. the modeler can guarantee that the used if-clauses fulfill at least the continuity requirement of integrators. In this case the simulation speed is improved, since no state event iterations occur during integration. The noEvent operator is used to guard against outside domain errors, e.g. y = if noEvent(x >= 0) then sqrt(x) else 0.]
All equations and assignment statements within when-clauses and all assignment statements within function classes are implicitly treated with noEvent, i.e., relations within the scope of these operators never induce state or time events.
[Using state events in when-clauses is unnecessary because the body of a when-clause is not evaluated during continuous integration.]
[Example:
]
Modelica is based on the synchronous data flow principle (section 8.4).
[The rules for the synchronous data flow principle guarantee that variables are always defined by a unique set of equations. It is not possible that a variable is e.g. defined by two equations, which would give rise to conflicts or non-deterministic behavior. Furthermore, the continuous and the discrete parts of a model are always automatically “synchronized”. Example:
This is not a valid model because rule 4 is violated since there are two equations for the single unknown variable close. If this would be a valid model, a conflict occurs when both conditions become true at the same time instant, since no priorities between the two equations are assigned. To become valid, the model has to be changed to:
Here, it is well-defined if both conditions become true at the same time instant (condition1 has a higher priority than condition2).]
There is no guarantee that two different events occur at the same time instant.
[As a consequence, synchronization of events has to be explicitly programmed in the model, e.g. via counters. Example:
The slowSample when-clause is evaluated at every 5th occurrence of the fastSample when-clause.]
[The single assignment rule and the requirement to explicitly program the synchronization of events allow a certain degree of model verification already at compile time.]
Before any operation is carried out with a Modelica model (e.g., simulation or linearization), initialization takes place to assign consistent values for all variables present in the model. During this phase, called the initialization problem, also the derivatives (der), and the pre-variables (pre), are interpreted as unknown algebraic variables. The initialization uses all equations and algorithms that are utilized in the intended operation (such as simulation or linearization).
The equations of a when-clause are active during initialization, if and only if they are explicitly enabled with initial(), and only in one of the two forms when initial() then or when {$\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$, initial(), $\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$} then (and similarly for elsewhen and algorithms see below). In this case, the when-clause equations remain active during the whole initialization phase. In case of a reinit(x, expr) being active during initialization (due to being inside when initial()) this is interpreted as adding x = expr (the reinit-equation) as an initial equation.
[If a when-clause equation v = expr; is not active during the initialization phase, the equation v = pre(v) is added for initialization. This follows from the mapping rule of when-clause equations. If the condition of the when-clause contains initial(), but not in one of the specific forms, the when-clause is not active during initialization: when not initial() then print("simulation started"); end when;]
The algorithmic statements within a when-statement are active during initialization, if and only they are explicitly enabled with initial(), and only in one of the two forms when initial() then or when {$\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$, initial(), $\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$} then. In this case, the algorithmic statements within the when-statement remain active during the whole initialization phase.
An active when-clause inactivates the following elsewhen (similarly as for when-clauses during simulation), but apart from that the first elsewhen initial() then or elsewhen {$\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$, initial(), $\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$} then is similarly active during initialization as when initial() then or when {$\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$, initial(), $\colorbox[rgb]{1,1,1}{$\mathrm{\dots}$}$} then.
[That means that any subsequent elsewhen initial() has no effect, similarly as when false then.]
[There is no special handling of inactive when-statements during initialization, instead variables assigned in when-statements are initialized using v := pre(v) before the body of the algorithm (since they are discrete), see section 11.1.2.]
Further constraints, necessary to determine the initial values of all variables, can be defined in the following ways:
As equations in an initial equation section or as assignments in an initial algorithm section. The equations and assignments in these initial sections are purely algebraic, stating constraints between the variables at the initial time instant. It is not allowed to use when-clauses in these sections.
For a non-discrete (that is continuous-time) Real variable vc, the equation pre(vc) = vc is added to the initialization equations.
[If pre(vc) is not present in the flattened model, a tool may choose not to introduce this equation, or if it was introduced it can eliminate it (to avoid the introduction of many dummy variables pre(vc)).]
Implicitly by using the start attribute for variables with fixed = true. With start given by startExpression:
For a non-discrete-time (that is continuous-time) Real variable vc, the equation vc = startExpression is added to the initialization equations.
For a discrete-time variable vd, the equation pre(vd) = startExpression is added to the initialization equations.
For a variable declared as constant or parameter, no equation is added to the initialization equations.
For constants and parameters, the attribute fixed defaults to true, which is the only allowed value for a constant. For other variables, fixed defaults to false.
start-values of variables having fixed = false can be used as initial guesses, in case iterative solvers are used in the initialization phase.
[In case of iterative solver failure, it is recommended to specially report those variables for which the solver needs an initial guess, but which only have the default value of the start attribute as defined in section 4.8, since the lack of appropriate initial guesses is a likely cause of the solver failure.]
If a parameter has a modifier for the start attribute, does not have fixed = false, and neither has a binding equation nor is part of a record having a binding equation, the modifier for the start attribute can be used to add a parameter binding equation assigning the parameter to that start value. In this case a diagnostic message is recommended in a simulation model.
[This is used in libraries to give non-zero defaults so that users can quickly combine models and simulate without setting parameters; but still easily find the parameters that need to be set.]
All variables declared as parameter having fixed = false are treated as unknowns during the initialization phase, i.e. there must be additional equations for them – and the start-value can be used as a guess-value during initialization.
[In the case a parameter has both a binding equation and fixed = false a diagnostics is recommended, but the parameter should be solved from the binding equation.
Non-discrete (that is continuous-time) Real variables vc have exactly one initialization value since the rules above assure that during initialization vc = pre(vc) = vc.startExpression (if fixed = true).
Before the start of the integration, it must be guaranteed that for all variables v, v = pre(v). If this is not the case for some variables vi, pre(vi) := vi must be set and an event iteration at the initial time must follow, so the model is re-evaluated, until this condition is fulfilled.
A Modelica translator may first transform the continuous equations of a model, at least conceptually, to state space form. This may require to differentiate equations for index reduction, i.e., additional equations and, in some cases, additional unknown variables are introduced. This whole set of equations, together with the additional constraints defined above, should lead to an algebraic system of equations where the number of equations and the number of all variables (including der and pre variables) is equal. Often, this is a nonlinear system of equations and therefore it may be necessary to provide appropriate guess values (i.e., start values and fixed = false) in order to compute a solution numerically.
It may be difficult for a user to figure out how many initial equations have to be added, especially if the system has a higher index. A tool may add or remove initial equations automatically such that the resulting system is structurally nonsingular. In these cases diagnostics are appropriate since the result is not unique and not necessarily what the user expects. A missing initial value of a discrete-time variable which does not influence the simulation result, may be automatically set to the start value or its default without informing the user. For example, variables assigned in a when-clause which are not accessed outside of the when-clause and where pre is not explicitly used on these variables, do not have an effect on the simulation.]
[Example: Continuous time controller initialized in steady-state:
[Example: Continuous time controller initialized either in steady-state or by providing a start value for state y:
This can also be written as follows (this form is less clear):
]
[Example: Discrete time controller initialized in steady-state:
This leads to the following equations during initialization:
with the solution:
]
[In general, for the case of a pure (first order) ordinary differential equation (ODE) system with $n$ state variables and $m$ output variables, we will have $n\mathrm{+}m$ unknowns in the simulation problem. The ODE initialization problem has $n$ additional unknowns corresponding to the derivative variables. At initialization of an ODE we will need to find the values of $\mathrm{2}\mathit{}n\mathrm{+}m$ variables, in contrast to just $n\mathrm{+}m$ variables to be solved for during simulation.]
[Example: Consider the following simple equation system:
Here we have three variables with unknown values: two dynamic variables that also are state variables, x1 and x2, i.e., $n\mathrm{=}\mathrm{2}$, one output variable y, i.e., $m\mathrm{=}\mathrm{1}$, and one input variable u with known value. A consistent solution of the initial value problem providing initial values for x1, x2, der(x1), der(x2), and y needs to be found. Two additional initial equations thus need to be provided to solve the initialization problem.
Regarding DAEs, only that at most $n$ additional equations are needed to arrive at $\mathrm{2}\mathit{}n\mathrm{+}m$ equations in the initialization system. The reason is that in a higher index DAE problem the number of dynamic continuous-time state variables might be less than the number of state variables $n$. As noted in section 8.6 a tool may add/remove initial equations to fulfill this requirement, if appropriate diagnostics are given.]
In general many variables have start-values that are not fixed and selecting a sub-set of these can give a consistent set of start-values close to the user-expectations. The following gives a non-normative procedure for finding such a sub-set.
[A model has a hierarchical component structure. Each component of a model can be given a unique model component hierarchy level number. The top level model has a level number of 1. The level number increases by 1 for each level down in the model component hierarchy. The model component hierarchy level number is used to give start values a confidence number, where a lower number means that the start value is more confident. Loosely, if the start value is set or modified on level i then the confidence number is i. If a start value is set by a possibly hierarchical modifier at the top level, then this start value has the highest confidence, namely 1 irrespectively on what level, the variable itself is declared.]