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
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
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
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;
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
Controller will require an input connector for the
setpoint, another input connector for the
measured and an output connector to send the
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
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,
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
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
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
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
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
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):
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
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.