There are many applications where we need to model the interaction between continuous behavior and discrete behavior. For this section, we’ll look at techniques used to measure the speed of a rotating shaft. For our discussion here, we will reuse the mechanical example we discussed previously in our discussion of Basic Equations:
model SecondOrderSystem "A second order rotational system"
type Angle=Real(unit="rad");
type AngularVelocity=Real(unit="rad/s");
type Inertia=Real(unit="kg.m2");
type Stiffness=Real(unit="N.m/rad");
type Damping=Real(unit="N.m.s/rad");
parameter Inertia J1=0.4 "Moment of inertia for inertia 1";
parameter Inertia J2=1.0 "Moment of inertia for inertia 2";
parameter Stiffness c1=11 "Spring constant for spring 1";
parameter Stiffness c2=5 "Spring constant for spring 2";
parameter Damping d1=0.2 "Damping for damper 1";
parameter Damping d2=1.0 "Damping for damper 2";
Angle phi1 "Angle for inertia 1";
Angle phi2 "Angle for inertia 2";
AngularVelocity omega1 "Velocity of inertia 1";
AngularVelocity omega2 "Velocity of inertia 2";
initial equation
phi1 = 0;
phi2 = 1;
omega1 = 0;
omega2 = 0;
equation
// Equations for inertia 1
omega1 = der(phi1);
J1*der(omega1) = c1*(phi2-phi1)+d1*der(phi2-phi1);
// Equations for inertia 2
omega2 = der(phi2);
J2*der(omega2) = c1*(phi1-phi2)+d1*der(phi1-phi2)-c2*phi2-d2*der(phi2);
end SecondOrderSystem;
We will reuse this model by adding an extends
clause to our
model. This essentially imports everything from the model we are
extending from. We’ll talk more about the extends
clause later when we
discuss Packages. For now, just think of it as copying the
contents of another model into the current model.
Recall the solution for the SecondOrderSystem
model looks like
this:
In this case, we are simply plotting the solution that we computed. But in a real system, we can’t directly know the rotational velocity of a shaft. Instead, we have to measure it. But measurement introduces error and each measurement technique introduces different kinds of errors. In this section, we’ll look at how we can model different kinds of measurement techniques.
The first type of measurement we will examine is a sample and hold
approach to measurement. Some speed sensors have circuits for
measuring the rotational speed of the system. But instead of
providing a continuous value for the speed, they sample it at a given
point in time and then store it somewhere. This is called “sample and
hold”. The following model demonstrates how to implement a sample
and hold approach to measuring the angular velocity omega1
:
model SampleAndHold "Measure speed and hold"
extends BasicEquations.RotationalSMD.SecondOrderSystem;
parameter Real sample_time(unit="s")=0.125;
discrete Real omega1_measured;
equation
when sample(0,sample_time) then
omega1_measured = omega1;
end when;
end SampleAndHold;
Note the presence of the discrete
qualifier in the declaration of
omega1_measured
. This special qualifier indicates that the
specified variable does not have a continuous solution. Instead, the
value of this variable will make (only) discrete jumps during the
simulation. It is not required to include the discrete
keyword
but it is useful because it provides additional information about the
intent of the model that the compiler can check (e.g., making sure
we never request the derivative of that variable).
Let us now examine the solution generated by this model:
The important thing to note in the solution is how the measured value
is piecewise-constant. This is because the value of
omega1_measured
is set only when the when
statement becomes
active. The sample
function is a special built-in function that
first becomes true at the time indicated by the first argument (0
in this case) and then at regular intervals after that. The duration
of these regular intervals is indicated by the second argument
(sample_time
in this case).
In the previous example, we weren’t actually making any estimates for
the speed, we were simply reporting the value of the variable
omega1
only at specific times. In other words, at the moment that
we sampled omega1
our sample was completely accurate. But by
“holding” our measured value (instead of continuing to track
omega1
), we introduced some artifact in the measurement.
In the remaining examples, we will focus on techniques used to estimate the speed of a rotating shaft. In these cases, we will never make direct use of the actual speed in our measurement. Instead, we will respond to events generated by the physical system and attempt to use these events to reconstruct an estimate of the rotational speed.
The events that we will be responding to are generated by the discrete elements attached to the spinning shaft. For example, a typical way to produce these events is to use a “tooth wheel encoder”. A tooth wheel encoder includes a gear on the rotating shaft. On either side of the gear, we place a light source and a light sensor. As the gear teeth pass in front of the light source, they block the light. The result is that the signal from the light sensor will include an approximate square wave. The leading edge of these square waves are the events we will be responding to.
The first estimation method we will examine computes the speed of the shaft by measuring the time interval that passes between events. Knowing that these events occur whenever the shaft has rotated by an angle of , we can estimate the speed as:
This technique for speed estimation can be represented in Modelica as:
model IntervalMeasure "Measure interval between teeth"
extends BasicEquations.RotationalSMD.SecondOrderSystem;
parameter Integer teeth=200;
parameter Real tooth_angle(unit="rad")=2*3.14159/teeth;
Real next_phi, prev_phi;
Real last_time;
Real omega1_measured;
initial equation
next_phi = phi1+tooth_angle;
prev_phi = phi1-tooth_angle;
last_time = time;
equation
when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
omega1_measured = tooth_angle/(time-pre(last_time));
next_phi = phi1+tooth_angle;
prev_phi = phi1-tooth_angle;
last_time = time;
end when;
end IntervalMeasure;
where tooth_angle
represents . Note how
tooth_angle
is not something the user needs to specify. Instead,
the user specifies the number of teeth using the teeth
parameter.
The tooth_angle
parameter is then computed using the value of
teeth
(note that while we have hand coded the value of
here, we’ll learn how to avoid this later in the book when we talk
about Constants).
Let’s take a close look at the when
statement in this model:
equation
when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
omega1_measured = tooth_angle/(time-pre(last_time));
next_phi = phi1+tooth_angle;
prev_phi = phi1-tooth_angle;
last_time = time;
end when;
Here, we use the vector expression syntax used previously in the
Bouncing Ball example. Recall that the when
statement
becomes active if any of the conditions become true. In this
case, the when
statement becomes active if the angle, phi1
,
becomes greater than next_phi
or less than prev_phi
.
Another thing to note is the use of the pre
operator throughout
the when
statement. When an event occurs in a model, there is a
chance that the value of some variables may change discontinuously.
During an event, while we are trying to resolve what values all the
variables should have as a result of the event, the pre
operator
allows us to reference the value of a variable prior to the event.
The pre
operator is used in this model to refer to the previous
(pre-event) values of next_phi
, prev_phi
and last_time
.
The pre
operator is necessary because all of these variables are
affected by the statements inside the when
statement. So, for
example, last_time
(without the pre
operator) refers to the
value of last_time
at the conclusion of the event while
pre(last_time)
refers to the value of last_time
prior to any
event occurring.
Use of the pre
operator
In general, if a variable changes as a result of a when
statement becoming active, you almost always want to use the
pre
operator when referring to that variable in the
conditional expression associated with the when
statement (as we
have done in the previous example). This makes it clear that you
are responding to what was happening before the when
statement
was triggered.
Let’s take a look at the speed estimates provided by this approach:
There are two important properties of this estimation algorithm that
we can immediately see in these results. The first is that the
estimate is unsigned. In other words, we cannot tell from a device
like a tooth wheel encoder which direction the shaft is rotating.
Also, low speeds and changes in rotational direction can degrade the
accuracy of the estimate significantly. The results are also very
sensitive the number of teeth involved. If we were to reduce the
number of teeth used in our encoder by setting teeth
to 20, we’d
get very different results.
To understand exactly why the measured signal is so inaccurate, it
helps to consider the following plot which shows how the angle,
phi1
compares to the angles associated with the adjacent teeth,
next_phi
and prev_phi
.
In this plot, we can clearly see how relatively low speeds and speed reversals create irregular events that introduce significant estimation error.
The interval measuring technique mentioned above requires hardware that can perform speed calculations on hardware interrupts. Another approach to estimating speed is the count how many events occur within a given (fixed) time interval and use that as an estimate of speed. Using this method, only the summation of events occurs when the events occur and the calculations are deferred to a regularly scheduled update.
Building on the previous examples in this section, the following seems like a natural way to create a model that implements this estimation technique:
model Counter "Count teeth in a given interval"
extends BasicEquations.RotationalSMD.SecondOrderSystem;
parameter Real sample_time(unit="s")=0.125;
parameter Integer teeth=200;
parameter Real tooth_angle(unit="rad")=2*3.14159/teeth;
Real next_phi, prev_phi;
Integer count;
Real omega1_measured;
initial equation
next_phi = phi1+tooth_angle;
prev_phi = phi1-tooth_angle;
count = 0;
equation
when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
next_phi = phi1+tooth_angle;
prev_phi = phi1-tooth_angle;
count = pre(count) + 1;
end when;
when sample(0,sample_time) then
omega1_measured = pre(count)*tooth_angle/sample_time;
count = 0 "Another equation for count?";
end when;
end Counter;
However, there is a problem in this model. Note that there are
actually two equations for count
. Trying to compile such a
model will lead to a situation where there are more equations than
variables (i.e., the problem is singular).
So what can we do about this? We need two different equations because
the updates to count
occur in response to different events. We could
try to formulate everything under a single when
statement, like this:
when {phi1>=pre(next_phi),phi1<=pre(prev_phi),sample(0,sample_time)} then
if sample(0,sample_time) then
omega1_measured := pre(count)*tooth_angle/sample_time;
count :=0;
else
next_phi := phi1 + tooth_angle;
prev_phi := phi1 - tooth_angle;
count :=pre(count) + 1;
end if;
end when;
But this kind of code quickly becomes hard to read. Fortunately, we
can address this situation by placing all the when
statements in an
algorithm
section.
The nature of an algorithm
section is that it is treated as one
single equation for any variables that are assigned within it. This
allows multiple assignments to count
, for example. When using an
algorithm
section, it is very important to understand that the
order of assignment becomes important. If a conflict should arise (e.g., a
variable is assigned two values within the same algorithm
section), the last one is the one that will be used. Another thing to
note about algorithm
sections is that you cannot write general
equations. Instead, you must write assignment statements.
In this way, an algorithm
section is very much like the way most
programming languages work. The statements in the algorithm section
are executed in order and each statement isn’t interpreted as an
equation, but rather as an assignment of an expression to a variable.
The familiarity of assignment statements may make using algorithm
sections attractive to people with a programming background who find
the otherwise equation oriented aspects of Modelica disorienting and
unfamiliar. But be aware that one big reason to avoid algorithm
sections is because they interfere with the symbolic manipulation
performed by the Modelica compiler. This can result in both poor
simulation performance and a loss of flexibility in how you compose
your models. So it is best to use an equation
section whenever
possible.
In our case, there are no significant consequences to using the
algorithm
section. Here is an example of how the previous
estimation algorithm could be refactored using an algorithm
section:
model CounterWithAlgorithm "Count teeth in a given interval using an algorithm"
extends BasicEquations.RotationalSMD.SecondOrderSystem;
parameter Real sample_time(unit="s")=0.125;
parameter Integer teeth=200;
parameter Real tooth_angle(unit="rad")=2*3.14159/teeth;
Real next_phi, prev_phi;
Integer count;
Real omega1_measured;
initial equation
next_phi = phi1+tooth_angle;
prev_phi = phi1-tooth_angle;
count = 0;
algorithm
when {phi1>=pre(next_phi),phi1<=pre(prev_phi)} then
next_phi := phi1 + tooth_angle;
prev_phi := phi1 - tooth_angle;
count := pre(count) + 1;
end when;
when sample(0,sample_time) then
omega1_measured := pre(count)*tooth_angle/sample_time;
count :=0;
end when;
end CounterWithAlgorithm;
The simulated results of this estimation technique can be seen in the following plot:
Again, we see that this approach cannot determine the direction of rotation. With the following plot, we can get a sense of how many events occur within each sample interval:
In general, the higher the count gets in an interval, the more accurate the estimate.
This section demonstrates how we can use the when
construct to
respond to physical events that occur in our system. These kinds of
events and the impact they have on our system are just as important as
the continuous dynamics we’ve covered previously. The ability to
capture and respond to these physical events is an important part of
why Modelica is so well suited to model complete systems since those
system frequently include both continuous and discrete behavior.