So far in this section, we started with a flat approach and gradually adapted it to use the architectural features of Modelica. Let’s start to build out our system from the top down using an architectural approach where we define the structure of our system first and then add specific subsystem implementations after that.
We’d like to start by building our top level architecture model that describes the subsystems we have and how they are connected. However, we need something to put into this architecture to represent our subsystems. We don’t (yet) want to get down to the job of creating implementations for all of our subsystem models. So where do we start?
The answer is that we start by describing the interfaces of our
subsystems. Remember, from earlier in this section, that
redeclarations depend on a constraining type and that all
implementations must conform to that constraining type? Well an
interface is essentially a formal definition of the constraining type
(i.e., the expected public interface) but without any implementation
details. Since it has no implementation details (i.e., no equations
or sub-components), it will end up being a partial
model. But
that’s fine for our purposes.
Let’s start by considering the interface for our sensor
subsystem. We have already discussed the public interface in detail.
Here is a Modelica definition and icon for that interface:
within ModelicaByExample.Architectures.SensorComparison.Interfaces;
partial model Sensor "Interface for sensor"
Modelica.Mechanics.Rotational.Interfaces.Flange_a shaft
"Flange of shaft from which sensor information shall be measured"
annotation ...
Modelica.Blocks.Interfaces.RealOutput w "Absolute angular velocity of flange"
annotation ...
annotation ...
end Sensor;
A few things to note about this model
definition. The first is,
as we mentioned a moment ago, that this model is partial
. This is
because it has connectors but no equations to help solve for the
variables on those connectors. Another thing worth noting is the fact
that it contains annotations. The annotations associated with the
connector declarations specify how those connectors should be
rendered. These annotations will be inherited by any model that
extends
from this one. So it is important to choose locations
that will make sense across all implementations. It also includes an
Icon
annotation. This essentially defines a “backdrop” for the
final implementations icon. In other words, any model that extends
from this model will be able to draw additional graphics on top of
what is defined by the Sensor
model.
The interface definition for the actuator model is almost identical
except that it includes two rotational connectors, one to apply the
commanded torque to and the other that handles the reaction torque.
If, for example, our actuator model were an electric motor, the former
would be the connector for the rotor and the latter would be the
connector for the stator. Otherwise, it is very similar to our
Sensor
definition:
within ModelicaByExample.Architectures.SensorComparison.Interfaces;
partial model Actuator "Interface for actuator"
Modelica.Mechanics.Rotational.Interfaces.Flange_b shaft "Output shaft"
annotation ...
Modelica.Mechanics.Rotational.Interfaces.Support housing
"Connection to housing"
annotation ...
Modelica.Blocks.Interfaces.RealInput tau "Input torque command"
annotation ...
annotation ...
end Actuator;
The Plant
interface has three rotational connectors. One for the
“input” side (where the actuator will be connected), one for the
“output” side (where the sensor will be connected) and the last one
for the “support” side (so it can be “mounted” to something):
within ModelicaByExample.Architectures.SensorComparison.Interfaces;
partial model Plant "Interface for plant model"
Modelica.Mechanics.Rotational.Interfaces.Flange_b flange_b
"Output shaft of plant"
annotation ...
Modelica.Mechanics.Rotational.Interfaces.Flange_a flange_a
"Input shaft for plant"
annotation ...
Modelica.Mechanics.Rotational.Interfaces.Support housing
"Connection to mounting"
annotation ...
annotation ...
end Plant;
Finally, we have the Controller
interface definition:
within ModelicaByExample.Architectures.SensorComparison.Interfaces;
partial model Controller "Interface for controller subsystem"
Modelica.Blocks.Interfaces.RealInput setpoint "Desired system response"
annotation ...
Modelica.Blocks.Interfaces.RealInput measured "Actual system response"
annotation ...
Modelica.Blocks.Interfaces.RealOutput command "Command to send to actuator"
annotation ...
annotation ...
end Controller;
Regardless of how it is implemented (e.g., proportional control, PID
control), the Controller
will require an input connector for the
desired setpoint, setpoint
, another input connector for the
measured speed, measured
and an output connector to send the
commanded torque, command
to the actuator.
With the interfaces specified, our architecture model can be written as follows:
within ModelicaByExample.Architectures.SensorComparison.Examples;
partial model SystemArchitecture
"A system architecture built from subsystem interfaces"
replaceable Interfaces.Plant plant
annotation ...
replaceable Interfaces.Actuator actuator
annotation ...
replaceable Interfaces.Sensor sensor
annotation ...
replaceable Interfaces.Controller controller
annotation ...
Modelica.Blocks.Sources.Trapezoid setpoint(period=1.0)
annotation ...
equation
connect(actuator.shaft, plant.flange_a) annotation ...
connect(actuator.housing, plant.housing) annotation ...
connect(plant.flange_b, sensor.shaft) annotation ...
connect(controller.measured, sensor.w) annotation ...
connect(controller.command, actuator.tau) annotation ...
connect(setpoint.y, controller.setpoint) annotation ...
end SystemArchitecture;
We can see from the Modelica code that our architecture consists of
four replaceable
subsystems: plant
, actuator
, sensor
and setpoint
. Each of these declarations includes only one type.
As we learned earlier in this section, that type will be used as both
the default type and the constraining type (which is what we want).
This model also includes all the connections between the subsystems.
In this way, it is a complete description of the subsystems and the
connections between them. All that is missing are the
implementation choices for each subsystem.
Note that our SystemArchitecture
model is, itself, partial
.
This is precisely because the default types for all the subsystems are
partial
and we have not yet specified implementations. In other
words, this model has no implementation so it cannot (yet) be
simulated. We indicate this by marking it as partial
.
Like our interface specifications, this model also contains graphical annotations. This is because, in addition to specifying the subsystems, we are also specifying the locations of the subsystems and the paths of the connections. When rendered, our system architecture looks like this:
Now that we have the interfaces and the architecture, we need to create some implementations that we can then “inject” into the architecture. These implementations can choose to either inherit from the existing interfaces (thus avoiding redundant code) or simply ensure that the declarations in the implementation make it plug-compatible with the interface. Obviously, inheriting from the interface is, in general, a much better approach. But we have included examples of both simply to demonstrate that it isn’t strictly required to inherit from the interface.
Here are some of the implementations we’ll be using over the remainder of this section. It is important to keep in mind that although these models include many lines of Modelica source code, they can be very quickly implemented in a graphical Modelica environment (i.e., one does not normally type in such models by hand).
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model BasicPlant "Implementation of the basic plant model"
parameter Modelica.SIunits.Inertia J_a=0.1 "Moment of inertia";
parameter Modelica.SIunits.Inertia J_b=0.3 "Moment of inertia";
parameter Modelica.SIunits.RotationalSpringConstant c=100 "Spring constant";
parameter Modelica.SIunits.RotationalDampingConstant d_shaft=3
"Shaft damping constant";
parameter Modelica.SIunits.RotationalDampingConstant d_load=4
"Load damping constant";
Modelica.Mechanics.Rotational.Interfaces.Support housing
"Connection to mounting"
annotation ...
Modelica.Mechanics.Rotational.Interfaces.Flange_a flange_a
"Input shaft for plant"
annotation ...
Modelica.Mechanics.Rotational.Interfaces.Flange_b flange_b
"Output shaft of plant"
annotation ...
protected
Modelica.Mechanics.Rotational.Components.Fixed fixed
annotation ...
Modelica.Mechanics.Rotational.Components.Inertia inertia(J=J_a)
annotation ...
Modelica.Mechanics.Rotational.Components.Inertia inertia1(J=J_b)
annotation ...
Modelica.Mechanics.Rotational.Components.SpringDamper springDamper(c=c, d=
d_shaft)
annotation ...
Modelica.Mechanics.Rotational.Components.Damper damper(d=d_load)
annotation ...
equation
connect(springDamper.flange_a, inertia.flange_b) annotation ...
connect(springDamper.flange_b, inertia1.flange_a) annotation ...
connect(damper.flange_b, inertia1.flange_b) annotation ...
connect(damper.flange_a, fixed.flange) annotation ...
connect(inertia1.flange_b, flange_b) annotation ...
connect(inertia.flange_a, flange_a) annotation ...
connect(fixed.flange, housing) annotation ...
end BasicPlant;
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model IdealActuator "An implementation of an ideal actuator"
Modelica.Mechanics.Rotational.Interfaces.Flange_b shaft "Output shaft"
annotation ...
Modelica.Mechanics.Rotational.Interfaces.Support housing
"Connection to housing"
annotation ...
Modelica.Blocks.Interfaces.RealInput tau "Input torque command"
annotation ...
protected
Modelica.Mechanics.Rotational.Sources.Torque torque(useSupport=true)
annotation ...
equation
connect(torque.flange, shaft) annotation ...
connect(torque.support, housing) annotation ...
connect(torque.tau, tau) annotation ...
end IdealActuator;
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model LimitedActuator "An actuator with lag and saturation"
extends Interfaces.Actuator;
parameter Modelica.SIunits.Time delayTime
"Delay time of output with respect to input signal";
parameter Real uMax "Upper limits of input signals";
protected
Modelica.Mechanics.Rotational.Sources.Torque torque(useSupport=true)
annotation ...
Modelica.Blocks.Nonlinear.Limiter limiter(uMax=uMax)
annotation ...
Modelica.Blocks.Nonlinear.FixedDelay lag(delayTime=delayTime)
annotation ...
equation
connect(torque.flange, shaft) annotation ...
connect(torque.support, housing) annotation ...
connect(limiter.y, torque.tau) annotation ...
connect(lag.u, tau) annotation ...
connect(lag.y, limiter.u) annotation ...
end LimitedActuator;
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model ProportionalController "Implementation of a proportional controller"
parameter Real k=20 "Controller gain";
Modelica.Blocks.Interfaces.RealInput setpoint "Desired system response"
annotation ...
Modelica.Blocks.Interfaces.RealInput measured "Actual system response"
annotation ...
Modelica.Blocks.Interfaces.RealOutput command "Command to send to actuator"
annotation ...
protected
Modelica.Blocks.Math.Gain gain(k=k)
annotation ...
Modelica.Blocks.Math.Feedback feedback
annotation ...
equation
connect(feedback.y, gain.u) annotation ...
connect(feedback.u1, setpoint) annotation ...
connect(gain.y, command) annotation ...
connect(measured, feedback.u2) annotation ...
end ProportionalController;
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model PID_Controller "Controller subsystem implemented using a PID controller"
extends Interfaces.Controller;
parameter Real k "Gain of controller";
parameter Modelica.SIunits.Time Ti "Time constant of Integrator block";
parameter Modelica.SIunits.Time Td "Time constant of Derivative block";
parameter Real yMax "Upper limit of output";
protected
Modelica.Blocks.Continuous.LimPID PID(k=k, Ti=Ti, Td=Td, yMax=yMax)
annotation ...
equation
connect(setpoint, PID.u_s) annotation ...
connect(measured, PID.u_m) annotation ...
connect(PID.y, command) annotation ...
end PID_Controller;
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model IdealSensor "Implementation of an ideal sensor"
Modelica.Mechanics.Rotational.Interfaces.Flange_a shaft
"Flange of shaft from which sensor information shall be measured"
annotation ...
Modelica.Blocks.Interfaces.RealOutput w "Absolute angular velocity of flange"
annotation ...
protected
Modelica.Mechanics.Rotational.Sensors.SpeedSensor idealSpeedSensor
"An ideal speed sensor" annotation ...
equation
connect(idealSpeedSensor.flange, shaft) annotation ...
connect(idealSpeedSensor.w, w) annotation ...
end IdealSensor;
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model SampleHoldSensor "Implementation of a sample hold sensor"
parameter Modelica.SIunits.Time sample_time(min=Modelica.Constants.eps);
Modelica.Mechanics.Rotational.Interfaces.Flange_a shaft
"Flange of shaft from which sensor information shall be measured"
annotation ...
Modelica.Blocks.Interfaces.RealOutput w "Absolute angular velocity of flange"
annotation ...
protected
Components.SpeedMeasurement.Components.SampleHold sampleHoldSensor(
sample_time=sample_time)
annotation ...
equation
connect(sampleHoldSensor.w, w) annotation ...
connect(sampleHoldSensor.flange, shaft) annotation ...
end SampleHoldSensor;
With these implementations at hand, we can create a number of
different implementations of our complete system. For example, to
implement the behavior of our original FlatSystem
model, we simply
extend from the SystemArchitecture
model and redeclare each of the
subsystems so that the implementations match the subsystem
implementations in FlatSystem
, i.e.,
within ModelicaByExample.Architectures.SensorComparison.Examples;
model BaseSystem "System architecture with base implementations"
extends SystemArchitecture(
redeclare replaceable Implementation.ProportionalController controller,
redeclare replaceable Implementation.IdealActuator actuator,
redeclare replaceable Implementation.BasicPlant plant,
redeclare replaceable Implementation.IdealSensor sensor);
end BaseSystem;
Here we see the real power of Modelica for specifying configurations.
Note how each redeclaration includes the replaceable
qualifier.
By doing so, we ensure that subsequent redeclarations are possible.
If we had wanted our SystemArchitecture
model to specify these
implementations as the default but still use the interfaces as the
constraining types, we could have declared the subsystems in
SystemArchitecture
as follows:
replaceable Implementation.BasicPlant plant constrainedby Interfaces.Plant
annotation ...
replaceable Implementation.IdealActuator actuator constrainedby
Interfaces.Actuator
annotation ...
replaceable Implementation.IdealSensor sensor constrainedby Interfaces.Sensor
annotation ...
replaceable Implementation.ProportionalController controller constrainedby
Interfaces.Controller
annotation ...
replaceable Modelica.Blocks.Sources.Trapezoid setpoint(period=1.0) constrainedby
Modelica.Blocks.Interfaces.SO
annotation ...
Variation1
¶If we wish to create a variation of the BaseSystem
model, we can
use inheritance and modifiers to create them, e.g.,
within ModelicaByExample.Architectures.SensorComparison.Examples;
model Variant1 "Creating sample-hold variant using system architecture"
extends BaseSystem(redeclare replaceable
Implementation.SampleHoldSensor sensor(sample_time=0.01));
end Variant1;
Note how this model extends from BaseSystem
configuration but then
changes only the sensor
model. If we simulate this system, we
see that the performance matches that of our original
Flat System.
However, if we instead create a configuration with the longer hold time, we find that the system becomes unstable (exactly as it did in the Flat System as well):
Variation2
¶Note how, even with an adequate sensor, the controller in our
Variant1
configuration seems to be converging to the wrong
steady state speed. This is because we are only using a proportional
gain controller. However, if we extend from the Variant1
model
and add a PID controller and a more realistic actuator with
limitations on the amount of torque it can supply, i.e.,
within ModelicaByExample.Architectures.SensorComparison.Examples;
model Variant2 "Adds PID control and realistic actuator subsystems"
extends Variant1(
redeclare replaceable Implementation.PID_Controller controller(
yMax=15, Td=0.1, k=20, Ti=0.1),
redeclare replaceable Implementation.LimitedActuator actuator(
delayTime=0.005, uMax=10));
end Variant2;
we will get the following simulation results:
Furthermore, if we take some time to tune the gains in the PID controller, i.e.,
within ModelicaByExample.Architectures.SensorComparison.Examples;
model Variant2_tuned "A tuned up version of variant 2"
extends Variant2(
controller(yMax=50, Ti=0.07, Td=0.01, k=4),
actuator(uMax=50),
sensor(sample_time=0.01));
end Variant2_tuned;
Then, we will get even better simulation results:
This concludes our discussion of this particular architecture. The key point in this example is that by using architectures, we make it very easy to explore alternative configurations for our system. But in addition to being easier (in the sense that we are able to do these things very quickly), we are also able to ensure a degree of correctness as well because no additional connecting is required for each configuration. Instead, the user simply needs to specify which implementation they would like to use for each subsystem and, even then, the Modelica compiler can check to make sure that their choices are plug-compatible with the constraining types used to specify the architecture.