In this section, we’ll summarize how component models are different from the previous models we’ve created. This discussion will be broken into two parts. The first part will focus on acausal modeling and how it provides a framework for schematic-based, component-oriented modeling where conservation equations are automatically generated and enforced. The second part will provide an overview of how the topics in this chapter impact, mostly syntactically, the definition of component models.
However, before we dive into that discussion, it is worth taking some time to talk about terminology. In this chapter, we’ve created two different types of models. The first type represent individual effects (e.g., resistors, capacitors, springs, dampers). The other type represent more complex assemblies (e.g., circuits, mechanisms).
Before we discuss some of the differences between these different types of models, let’s introduce some terminology so we can refer to them precisely. A component model is a model that is used to encapsulate equations into a reusable form. By creating such a model, an instance of the component can be used in place of the equations it contains. A subsystem model is a model that is composed of components or other subsystems. In other words, it doesn’t (generally) include equations. Instead, it represents an assembly of other components. Typically, these subsystem models are created by dragging, dropping and connecting component and other subsystem models schematically. While component models are “flat” (they don’t contain other components or subsystems, only equations), subsystem models are hierarchical.
We’ll often refer to a subsystem model as a system model. A system
model is a model that we expect to simulate. In simulating it, the
Modelica compiler will traverse the hierarchy of the model and note
all the variables and equations present throughout the hierarchy.
These are the variables and equations that will be used in the
simulation. Of course, in order for there to be a unique solution,
the system model (like any non-partial
model), must be
balanced.
Note that a subsystem model can include equations. There is no rule
against it in Modelica. But most of the time models tend to be
composed either of equations or other components/subsystems. It is
actually a good idea to avoid putting equations in models containing
subcomponents or subsystems because doing so means that some information
about the model will be “invisible” when looking at a diagram of the
subsystem. One possible exception to this could be the use of
initial equation
sections in subsystems.
With that discussion of terminology out of the way, let’s dive into discussions about component models.
We’ll start with a discussion about acausal modeling. We touched on this topic very briefly in the chapter on Connectors. Here we will provide a more comprehensive discussion about acausal modeling.
There are two very big advantages to acausal modeling. The first is composability. In this context, composability means the ability to drag, drop and connect component instances in nearly any configuration we wish without having to concern ourselves with “compatibility”. This is because acausal connectors are designed around the idea of physical compatibility, not causal compatibility. This is possible because acausal connector definitions focus on physical information exchanged, not the direction that information flows. The result is that we can create component models around the idea of physical interactions without requiring any a priori knowledge about the nature (i.e., directionality) of the information exchange.
But there are other implications to this composability. Not only can we easily create systems by dragging, dropping and connecting components, but we can also easily reconfigure them. Replacing a voltage source in an electrical circuit with a current source can have a profound impact on the mathematical representation of that system (e.g., if the system is represented as a block diagram). But such a change has no significant impact when using an acausal approach. Although the underlying mathematical representation still changes, sometimes profoundly, there is no impact on the user, because that representation is generated automatically as part of the compilation process.
Finally, another aspect of composability is in the support for multi-domain systems. In fact, Modelica not only supports different engineering domains (electrical, thermal, hydraulic), it supports multiple modeling formalisms. Model developers have created libraries for block diagrams, state charts, petri nets, etc. Instead of requiring special tools or editors in each case, all of these different domains and formalisms can be freely combined in Modelica as appropriate.
The other advantage of acausal modeling is the amount of automatic
“accounting” performed with this approach. To understand exactly what
accounting is performed, let’s consider the following rotational
connector
definitions from the Modelica Standard Library:
connector Flange_a "1-dim. rotational flange of a shaft (filled square icon)"
Modelica.SIunits.Angle phi "Absolute rotation angle of flange";
flow Modelica.SIunits.Torque tau "Cut torque in the flange";
annotation ...
end Flange_a;
connector Flange_b "1-dim. rotational flange of a shaft (filled square icon)"
Modelica.SIunits.Angle phi "Absolute rotation angle of flange";
flow Modelica.SIunits.Torque tau "Cut torque in the flange";
annotation ...
end Flange_b;
As we’ve discussed previously, an acausal connector includes two
different types of variables, across variables and through variables.
The through variable is indicated by the presence of the flow
qualifier. In the case of the Rotational
connector, the across
variable is phi
, the angular position, and the through variable is
tau
, the torque.
Also recall from our previous discussion that Modelica models should
observe the following convention: a positive value for the flow
variable on a connector represents the flow of that quantity into
the component that the connector is connected to. This is an
important sign convention not only because it make sure all the
accounting is correct, but it also helps with composability as well by
allowing (inherently symmetric) components like springs, dampers,
etc. to be flipped over and still function identically.
Before we can get into the details of the accounting performed by the compiler, we need to introduce the concept of a connection set. To demonstrate what a connection set is, consider the following schematic:
Note that there are 8 connections in this model:
equation
connect(ground.flange_a, damper2.flange_b);
connect(ground.flange_a, spring2.flange_b);
connect(damper2.flange_a, inertia2.flange_b);
connect(spring2.flange_a, inertia2.flange_b);
connect(inertia2.flange_a, damper1.flange_b);
connect(inertia2.flange_a, spring1.flange_b);
connect(damper1.flange_a, inertia1.flange_b);
connect(spring1.flange_a, inertia1.flange_b);
If two connect statements have one connector in common, they belong to the same connection set. If a connector is not connected to any other connectors, then it belongs to a connection set that includes only itself. Using this rule, we can organize the connectors into connection sets as follows:
- Connection Set #1
ground.flange_a
damper2.flange_b
spring2.flange_b
- Connection Set #2
damper2.flange_a
spring2.flange_a
inertia2.flange_b
- Connection Set #3
inertia2.flange_a
damper1.flange_b
spring1.flange_b
- Connection Set #4
inertia1.flange_b
damper1.flange_a
spring1.flange_a
- Connection Set #5
inertia1.flange_a
Note that these connection sets appear from right to left in the
diagram. It may be useful to take the time to match the connectors in
the diagram with those listed in the connection sets to understand
what a connection set intuitively is. Note that the flange_a
connectors are filled circles whereas the flange_b
ones are only
outlined.
This is where the “accounting” starts. For each connection set, special equations are automatically generated. The first set of automatic equations are related to the across variables. We need to impose the constraint, mathematically speaking, that all across variables must have the same value. Furthermore, we also introduce an equation that states that the sum of all through variables in the connection set must sum to zero.
In the case of the connection sets above, the following equations will be automatically generated:
// Connection Set #1
// Equality Equations:
ground.flange_a.phi = damper2.flange_b.phi;
damper2.flange_b.phi = spring2.flange_b.phi;
// Conservation Equation:
ground.flange_a.tau + damper2.flange_b.tau + spring2.flange_b.tau = 0;
// Connection Set #2
// Equality Equations:
damper2.flange_a.phi = spring2.flange_a.phi;
spring2.flange_a.phi = inertia2.flange_b.phi;
// Conservation Equation:
damper2.flange_a.tau + spring2.flange_a.tau + inertia2.flange_b.tau = 0;
// Connection Set #3
// Equality Equations:
inertia2.flange_a.phi = damper1.flange_b.phi;
damper1.flange_b.phi = spring1.flange_b.phi;
// Conservation Equation:
inertia2.flange_a.tau + damper1.flange_b.tau + spring1.flange_b.tau = 0;
// Connection Set #4
// Equality Equations:
inertia1.flange_b.phi = damper1.flange_a.phi;
damper1.flange_a.phi = spring1.flange_a.phi;
// Conservation Equation:
inertia1.flange_b.tau + damper1.flange_a.tau + spring1.flange_a.tau = 0;
// Connection Set #5
// Equality Equations: NONE
// Conservation Equation:
inertia1.flange_a.tau = 0;
Note that for an empty connection set (i.e., Connection Set #5), there is only one across variable in the set, so no equality equations are generated. The conservation equation is still generated but it contains only one term. So it amounts to a statement that nothing can flow out of an unconnected connector. This makes intuitive physical sense as well.
What does all this mean physically? In the case of an electrical connection, this implies that each connection can be treated as a “perfect short” between the connectors. In the case of a mechanical system, connections are treated as perfectly rigid shafts with zero inertia. The bottom line is that a connection means that the across variables on each connector will be equal and that any conserved quantity that leaves one component must enter another one. Nothing can get lost or stored between components.
There are two important consequences to these equations. The first is
that the flow
variable is automatically conserved. Typical
flow
variables are current, torque, mass flow rate, etc. Since
these are all the time derivative of a conserved quantity (i.e.,
charge, angular momentum and mass, respectively), such equations are
automatically conserving these quantities.
But something else is being implicitly conserved as well. Specifically, we can ensure that energy is conserved as well. For all of these domains, the power flow through a connector can be represented by the product of the through variable and either the across variable or a derivative of the across variable. As a result, for each domain we can easily derive a power conservation equation from the equations automatically generated for the connection set. From our example above, we know that in the first connection set we have the following equations:
ground.flange_a.phi = damper2.flange_b.phi;
damper2.flange_b.phi = spring2.flange_b.phi;
ground.flange_a.tau + damper2.flange_b.tau + spring2.flange_b.tau = 0;
If we multiply the last equation by der(ground.flange_a.phi)
, the
angular velocity of the ground.flange_a
connector, we get:
der(ground.flange_a.phi)*ground.flange_a.tau
+ der(ground.flange_a.phi)*damper2.flange_b.tau
+ der(ground.flange_a.phi)*spring2.flange_b.tau = 0;
However, we also know that all the across variables in the connection set are equal. As a result, their derivatives must also be equal. This means that we can substitute any one of them for another. Making two such substitutions yields:
der(ground.flange_a.phi)*ground.flange_a.tau
+ der(damper2.flange_b.phi)*damper2.flange_b.tau
+ der(spring2.flange_b.phi)*spring2.flange_b.tau = 0;
The first term in the equation above is the power flowing into the
ground
component through flange_a
. The second term is the
power flowing into the damper2
component through flange_b
.
The last term is the power flowing into the spring2
component
through flange_b
. Since these represent the power flowing through
all connectors in the connection set, this implies that power is
conserved by that connection set (i.e., all power that flows out of
one component must flow into another, nothing is lost or stored).
If we look carefully at the previous discussion on equations generated involving acausal variables in connection sets, we’ll see something very interesting. But to see it, we first need to review a few things we’ve learned about connectors and connector sets:
- A connection can only belong to one connection set.
- As we learned in our previous discussion on Acausal Variables, for every through variable in a connector (i.e., a variable declared with the
flow
qualifier), there must be a matching across variable (i.e., a variable without any qualifier).- The number of equations generated in a connection set is equal to the number of connectors in the connection set multiplied by the number of through-across pairs in the connector.
Remember that acausal variables come in pairs. Equations for half of those variables (one per pair) will be generated automatically via connections. That means the remaining half of the equations must come from the component models themselves.
Keep in mind that this discussion is focused only on acausal variables in connectors. We also need to take into account two other cases:
- Variables declared within a component model (as opposed to on a connector).
- Causal variables on connectors (i.e., those qualified by either
input
oroutput
).
Modelica requires that any non-partial
model be balanced. But
what does that mean? It means that the component should provide the
proper number of equations (no more and no less than necessary). The question is how to compute the number of equations
required?
We already have a start based on our discussion about acausal
variables. Since half of the equations needed for acausal variables
come from generated equations, the other half must come from within
component models containing these connectors. Specifically, the
component must provide one equation for every through-across pair in
each of its connectors. In addition, it should also provide one
equation for every variable on its connectors that has the output
qualifier (note, the component does not have to provide equations for
any variables on its connectors with the input
qualifier). The
rationale here is that a component can assume that all input
signals are known (specified externally) and that it is responsible
for computing any output
signals it advertises. Finally, any
(non-parameter
) variable declared within the component must also
have an equation.
In summary, the number of equations that a component must provide is the sum of:
- The number of through-across pairs across all its connectors
- The number of non-
parameter
variables declared in the component model.- The number of
output
variables across all its connectors.
Note that these equations can (and frequently do) originate in a
partial
model that is inherited.
If the number of equations provided by a component equals the number of equations required, then the component model is said to be balanced.
In this chapter we’ve discussed how to create component models. Fundamentally, nothing has changed since we first discussed what a Model Definition should include. But it is worth emphasizing a few things about component models.
First, in the discussion on Block Diagram Components we introduced the
idea of a block
. A block
is a special kind of model
where
the connectors contain only input
and output
signals.
Another thing we saw in our discussion of the Optional Ground Connector was the ability to make a declaration conditional. The expression on which the conditional declaration depends cannot change as a function of time (i.e., the variable cannot appear and disappear during the simulation). Instead, it must be a function of parameters and constants so that the compiler or simulation runtime can determine whether the variable should be present prior to simulation. As we saw, the syntax for such a declaration is:
VariableType variableName(/* modifications /*) if conditional_expression;
In other words, by including the if
keyword and a conditional
expression immediately after the name of the variable (and any
modifications that are applied to the variable), we can make the
declaration of that variable conditional. When the conditional
expression is true
, the conditional variable will be present.
When it is false
, it will not be present.
assert
¶To understand how to enforce model limitations, we must first explain
the assert
function. The syntax of a call to the assert
function is:
assert(conditional_expression, "Explanation of failure", assertLevel);
where conditional_expression
is an expression that yields either
true
or false
. A value of false
indicates a failure of
the assertion. We’ll discuss the consequences of that momentarily.
The second argument must be a String
that describes the reason
that the assertion failed. The last argument, assertLevel
, is of
type AssertionLevel
(which was defined in our previous discussion
on enumerations
). This last argument is optional and has the
default value of AssertionalLevel.error
.
Now that we know how to use the assert
function, let’s examine the
consequences of assertions during simulation to understand why they
are important.
When creating a component model
(or any model
, for that
matter), it is useful to incorporate any limitations on the equations
in a model by including them directly in the model. This is done by
adding assert
calls in either the equation
or algorithm
section. As their name implies, these assertions assert that certain
conditions must always be true.
If the equations within a model are only accurate or applicable under certain conditions, it is essential that these conditions be included in the model via assertions. Otherwise, the model may silently yield an incorrect solution. If not uncovered, this could lead to bad decisions based on model solutions. If it is uncovered, it will undermine the trust people have in the models. So always try to capture such model limitations.
It is worth taking a moment to understand what impact such an assertion has during simulation. Part of the simulation process is the generation of so-called candidate solutions. These solutions may, or may not, end up being actual solutions. They are usually generated as the underlying solvers propose solutions and then check to make sure that the solutions are accurate to within some numerical tolerance. Those candidate solutions that are found to be inaccurate are typically refined in some way until a sufficiently accurate solution is found.
If a candidate solution violates an assertion, then it is
automatically considered to be inaccurate. The violated assertion
will automatically trigger the refinement process in an attempt to
find a solution that is more accurate and, hopefully, doesn’t violate
the assertion. However, if these refinement processes lead to a
solution that is sufficiently accurate (i.e., satisfies the accuracy
requirements to within the acceptable tolerance), but that solution
still violates any assertions in the system, then the simulation
environment will do one of two things. If the level
argument in
the assert
call is AssertionLevel.error
then the simulation is
terminated. If, on the other hand, the level
argument is
AssertionLevel.warning
, then the assertion description will be
used to generate a warning message to the user. How this message is
delivered is specific to each simulation environment. Recall that the
default value for the level
argument (if none is provided in the
call to assert
) is AssertionLevel.error
.