We will start with a simple example that demonstrates time events. We will revisit the thermal model introduced previously in the section on Physical Types. However, this time we will introduce a disturbance to that system. Specifically, we will trigger an abrupt decrease in the ambient temperature after half a second of simulation. This revised model is written as follows:
model NewtonCoolingDynamic
"Cooling example with fluctuating ambient conditions"
// Types
type Temperature=Real(unit="K", min=0);
type ConvectionCoefficient=Real(unit="W/(m2.K)", min=0);
type Area=Real(unit="m2", min=0);
type Mass=Real(unit="kg", min=0);
type SpecificHeat=Real(unit="J/(K.kg)", min=0);
// Parameters
parameter Temperature T0=363.15 "Initial temperature";
parameter ConvectionCoefficient h=0.7 "Convective cooling coefficient";
parameter Area A=1.0 "Surface area";
parameter Mass m=0.1 "Mass of thermal capacitance";
parameter SpecificHeat c_p=1.2 "Specific heat";
// Variables
Temperature T_inf "Ambient temperature";
Temperature T "Temperature";
initial equation
T = T0 "Specify initial value for T";
equation
if time<=0.5 then
T_inf = 298.15 "Constant temperature when time<=0.5";
else
T_inf = 298.15-20*(time-0.5) "Otherwise, decreasing";
end if;
m*c_p*der(T) = h*A*(T_inf-T) "Newton's law of cooling";
end NewtonCoolingDynamic;
The highlight lines show an if
statement. This particular if
statement provides two different equations for computing T_inf
.
Time
You will note in this model the variable time
is not declared
within our model. This is because time
is a built-in variable
in all Modelica models.
The decision about which of the two equations will actually be used
depends on the conditional expression time<=0.5
. It is because
this expression only depends on time
and not any other variables
in our model that we can characterize the transition between these two
equations as a “time event”. The key point is that when integrating
these equations, we can tell the solver that integrates our system of
equations to stop precisely at 0.5 seconds and then resume again using
a different equation. We’ll see examples of other state events where
this would not be possible, in the next section when we present the
classic Bouncing Ball example.
But for now, let us continue with our cooling example. If we simulate this model for one second, we get the following temperature trajectory:
As you can see in these results, the ambient temperature does indeed start to decrease after half a second. In studying the dynamic response of the temperature itself, we see two distinct phases. The first phase is the initial transient response toward equilibrium (to match the ambient temperature). The second phase is the tracking of the ambient temperature as it decreases.
It is worth noting that this is a very common issue in modeling. Frequently, you wish to model the systems response to some disturbance (like the ambient temperature decrease in this case). However, if you don’t start your system in some kind of equilibrium state, the system response will also include some kind of initial transient (like the one shown here). In order to distinguish these two responses clearly, we want to avoid any overlap between them. The simplest way to do that is to start the simulation in an equilibrium condition (as discussed previous in our discussion of Steady State Initialization). This avoids the initial transient altogether and allows us to focus entirely on the disturbance that we are interested in.
As we learned during our discussion of Initialization, we can
solve this problem of initial transients by simply including an
initial equation that will determine a value for T
such that our
system starts in an equilibrium state, i.e.,
model NewtonCoolingSteadyThenDynamic
"Dynamic cooling example with steady state conditions"
type Temperature=Real(unit="K", min=0);
type ConvectionCoefficient=Real(unit="W/(m2.K)", min=0);
type Area=Real(unit="m2", min=0);
type Mass=Real(unit="kg", min=0);
type SpecificHeat=Real(unit="J/(K.kg)", min=0);
parameter ConvectionCoefficient h=0.7 "Convective cooling coefficient";
parameter Area A=1.0 "Surface area";
parameter Mass m=0.1 "Mass of thermal capacitance";
parameter SpecificHeat c_p=1.2 "Specific heat";
Temperature T_inf "Ambient temperature";
Temperature T "Temperature";
initial equation
der(T) = 0 "Steady state initial conditions";
equation
if time<=0.5 then
T_inf = 298.15 "Constant temperature when time<=0.5";
else
T_inf = 298.15-20*(time-0.5) "Otherwise, decreasing";
end if;
m*c_p*der(T) = h*A*(T_inf-T) "Newton's law of cooling";
end NewtonCoolingSteadyThenDynamic;
The only thing we’ve changed is the initial equation. Instead of starting our system at some fixed temperature, we start it at a temperature such that the change in temperature (at least initially, prior to our disturbance) is zero. Now the temperature response no longer includes any initial transient and we can focus only on the response to the disturbance:
One issue with if
statements is that they can make relatively
simple changes in behavior appear quite complicated. There are a
couple of alternative constructs we can use to get the same behavior
with fewer lines of code.
The first approach is to use an if
expression. Whereas an
if
statement includes “branches” containing equations, an if
expression has branches that contain only expressions. Furthermore,
the syntax for an if
expression is also less verbose. If we had
chosen to use an if
expression our equation
could have
been simplified to:
equation
T_inf = 298.15 - (if time<0.5 then 0 else 20*(time-0.5));
m*c_p*der(T) = h*A*(T_inf-T) "Newton's law of cooling";
Alternatively, we could use one of the many built-in Modelica
functions, like max
, to represent the change in the ambient
temperature, e.g.,
equation
T_inf = 298.15 - max(0, 20*(time-0.5));
m*c_p*der(T) = h*A*(T_inf-T) "Newton's law of cooling";
We’ve seen several ways to express the fact that there is an abrupt change in the behavior of our system. But it’s important to point out that we are not just describing a change in the ambient temperature, we are also specifying when it changes. This a subtle, but very important, point.
Consider the last example, where our system began in an equilibrium state. At the start of the simulation, there are no significant dynamics. Since nothing is really changing in the system, the integrator is unlikely to accumulate significant integration error. So, in order to minimize the amount of time required to complete the simulation, variable time step integrators will, in such circumstances, increase their step size.
There is, however, a risk in doing this. The risk is that the integrator may get “blind-sided” by a sudden disturbance in the system. If such a disturbance were to occur, the integrator’s assumptions that a large step will not lead to significant integration error would not be true.
The question then becomes, how can the integrator know when it can take a large time step and when it cannot. Typically, these integration schemes use a kind of trial and error approach. They try to take large step and then estimate the amount of error introduced by that step. If it is less than some threshold, then they accept the state (or perhaps try a larger step). If, on the other hand, the step introduces too much error, then they try a smaller step. But they cannot know how small a step will be required to get under the error threshold, which means they will continue to blindly try smaller and smaller steps.
But Modelica is about much more than integrating the underlying system. Modelica compilers study the structure of the problem. In all of our examples, the compiler can see that there is a distinct change in behavior. Not only that, it can see that this change in behavior is a time event, i.e., an event whose time is known a priori without any knowledge of the solution trajectory.
So, what a Modelica compiler will do is inform the underlying integrator that there will be an abrupt change in behavior at 0.5 seconds and it will instruct the integrator to simply integrate exactly up to that point and no further. As a result, the abrupt change never occurs within a time step. Instead, the integrator will simply restart on the other side of the event. This completely avoids the blind searching for the cutoff time that minimizes the error in the step. Instead, the integrator will integrate right up to that point automatically and then restart after that point.
This is one of many examples of features in Modelica that optimize the way a simulation is carried out. A more detailed discussion of this kind of handling can be found in the upcoming section on Events. In the coming sections, we’ll also see more complex examples of events that depend on the solution variables.