Architecture Driven Approach

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.

Interfaces

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 (Placement(transformation(extent={{-110,-10},{-90,10}})));
  Modelica.Blocks.Interfaces.RealOutput w "Absolute angular velocity of flange"
    annotation (Placement(transformation(extent={{100,-10},{120,10}})));
  annotation (Icon(graphics={Rectangle(extent={{-100,100},{
              100,-100}}, lineColor={128,255,0})}));
end Sensor;
Sensor interface

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 (Placement(transformation(extent={{90,-10},{110,10}})));
  Modelica.Mechanics.Rotational.Interfaces.Support housing
    "Connection to housing"
    annotation (Placement(transformation(extent={{90,-70},{110,-50}})));
  Modelica.Blocks.Interfaces.RealInput tau "Input torque command"
    annotation (Placement(transformation(extent={{-140,-20},{-100,20}})));
  annotation (Icon(graphics={Rectangle(extent={{-100,100},{
              100,-100}}, lineColor={255,85,85})}));
end Actuator;
Actuator interface

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 (Placement(transformation(extent={{90,-10},{110,10}})));
  Modelica.Mechanics.Rotational.Interfaces.Flange_a flange_a
    "Input shaft for plant"
    annotation (Placement(transformation(extent={{-110,-10},{-90,10}})));
  Modelica.Mechanics.Rotational.Interfaces.Support housing
    "Connection to mounting"
    annotation (Placement(transformation(extent={{-110,-70},{-90,-50}})));
  annotation (Icon(graphics={Rectangle(extent={{-100,100},{
              100,-100}}, lineColor={128,0,255})}));
end Plant;
Plant interface

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 (Placement(transformation(
        extent={{-20,-20},{20,20}},
        rotation=270,
        origin={0,120})));
  Modelica.Blocks.Interfaces.RealInput measured "Actual system response"
    annotation (Placement(transformation(
        extent={{-20,-20},{20,20}},
        rotation=180,
        origin={100,0})));
  Modelica.Blocks.Interfaces.RealOutput command "Command to send to actuator"
    annotation (Placement(transformation(
        extent={{-10,-10},{10,10}},
        rotation=180,
        origin={-110,0})));
  annotation (Icon(graphics={Rectangle(extent={{-100,100},{
              100,-100}}, lineColor={0,128,255})}));
end Controller;
Controller interface

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.

Architecture

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 (choicesAllMatching=true,
      Placement(transformation(extent={{-10,-50},{10,-30}})));
  replaceable Interfaces.Actuator actuator
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{-50,-50},{-30,-30}})));
  replaceable Interfaces.Sensor sensor
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{30,-50},{50,-30}})));
  replaceable Interfaces.Controller controller
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{-10,-10},{10,10}})));
  Modelica.Blocks.Sources.Trapezoid setpoint(period=1.0)
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{-60,30},{-40,50}})));
equation
  connect(actuator.shaft, plant.flange_a) annotation (Line(
      points={{-30,-40},{-10,-40}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(actuator.housing, plant.housing) annotation (Line(
      points={{-30,-46},{-10,-46}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(plant.flange_b, sensor.shaft) annotation (Line(
      points={{10,-40},{30,-40}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(controller.measured, sensor.w) annotation (Line(
      points={{10,0},{70,0},{70,-40},{51,-40}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(controller.command, actuator.tau) annotation (Line(
      points={{-11,0},{-70,0},{-70,-40},{-52,-40}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(setpoint.y, controller.setpoint) annotation (Line(
      points={{-39,40},{0,40},{0,12}},
      color={0,0,127},
      smooth=Smooth.None));
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:

Top down architecture

Implementations

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).

Plant Models

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 (Placement(transformation(extent={{-110,-70},{-90,-50}})));
  Modelica.Mechanics.Rotational.Interfaces.Flange_a flange_a
    "Input shaft for plant"
    annotation (Placement(transformation(extent={{-110,-10},{-90,10}})));
  Modelica.Mechanics.Rotational.Interfaces.Flange_b flange_b
    "Output shaft of plant"
    annotation (Placement(transformation(extent={{90,-10},{110,10}})));
protected
  Modelica.Mechanics.Rotational.Components.Fixed fixed
    annotation (Placement(transformation(extent={{-10,-80},{10,-60}})));
  Modelica.Mechanics.Rotational.Components.Inertia inertia(J=J_a)
    annotation (Placement(transformation(extent={{-40,-10},{-20,10}})));
  Modelica.Mechanics.Rotational.Components.Inertia inertia1(J=J_b)
    annotation (Placement(transformation(extent={{20,-10},{40,10}})));
  Modelica.Mechanics.Rotational.Components.SpringDamper springDamper(c=c, d=
        d_shaft)
    annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
  Modelica.Mechanics.Rotational.Components.Damper damper(d=d_load)
    annotation (Placement(transformation(extent={{20,-40},{40,-20}})));
equation
  connect(springDamper.flange_a, inertia.flange_b) annotation (Line(
      points={{-10,0},{-20,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(springDamper.flange_b, inertia1.flange_a) annotation (Line(
      points={{10,0},{20,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(damper.flange_b, inertia1.flange_b) annotation (Line(
      points={{40,-30},{50,-30},{50,0},{40,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(damper.flange_a, fixed.flange) annotation (Line(
      points={{20,-30},{0,-30},{0,-70}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(inertia1.flange_b, flange_b) annotation (Line(
      points={{40,0},{100,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(inertia.flange_a, flange_a) annotation (Line(
      points={{-40,0},{-100,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(fixed.flange, housing) annotation (Line(
      points={{0,-70},{0,-60},{-100,-60}},
      color={0,0,0},
      smooth=Smooth.None));
  annotation (Icon(graphics={
        Rectangle(

Actuator Models

within ModelicaByExample.Architectures.SensorComparison.Implementation;
model IdealActuator "An implementation of an ideal actuator"
  Modelica.Mechanics.Rotational.Interfaces.Flange_b shaft "Output shaft"
    annotation (Placement(transformation(extent={{90,-10},{110,10}})));
  Modelica.Mechanics.Rotational.Interfaces.Support housing
    "Connection to housing"
    annotation (Placement(transformation(extent={{90,-70},{110,-50}})));
protected
  Modelica.Mechanics.Rotational.Sources.Torque torque(useSupport=true)
    annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
  Modelica.Blocks.Interfaces.RealInput tau "Input torque command"
    annotation (Placement(transformation(extent={{-140,-20},{-100,20}})));
equation
  connect(torque.flange, shaft) annotation (Line(
      points={{10,0},{100,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(torque.support, housing) annotation (Line(
      points={{0,-10},{0,-60},{100,-60}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(torque.tau, tau) annotation (Line(
      points={{-12,0},{-120,0}},
      color={0,0,127},
      smooth=Smooth.None));
  annotation (Icon(graphics={
        Rectangle(
          extent={{-100,100},{100,-100}},
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 (Placement(transformation(extent={{30,-10},{50,10}})));
  Modelica.Blocks.Nonlinear.Limiter limiter(uMax=uMax)
    annotation (Placement(transformation(extent={{-18,-10},{2,10}})));
  Modelica.Blocks.Nonlinear.FixedDelay lag(delayTime=delayTime)
    annotation (Placement(transformation(extent={{-70,-10},{-50,10}})));
equation
  connect(torque.flange, shaft) annotation (Line(
      points={{50,0},{100,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(torque.support, housing) annotation (Line(
      points={{40,-10},{40,-60},{100,-60}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(limiter.y, torque.tau) annotation (Line(
      points={{3,0},{28,0}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(lag.u, tau) annotation (Line(
      points={{-72,0},{-120,0}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(lag.y, limiter.u) annotation (Line(
      points={{-49,0},{-20,0}},
      color={0,0,127},
      smooth=Smooth.None));
  annotation (Icon(graphics={
        Rectangle(
          extent={{-100,100},{100,-100}},
          lineColor={0,0,0},
          fillColor={255,85,85},

Controller Models

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 (Placement(transformation(
        extent={{-20,-20},{20,20}},
        rotation=270, origin={0,120})));
  Modelica.Blocks.Interfaces.RealInput measured "Actual system response"
    annotation (Placement(transformation(
        extent={{-20,-20},{20,20}},
        rotation=180, origin={100,0})));
  Modelica.Blocks.Interfaces.RealOutput command "Command to send to actuator"
    annotation (Placement(transformation(
        extent={{-10,-10},{10,10}},
        rotation=180, origin={-110,0})));
protected
  Modelica.Blocks.Math.Gain gain(k=k)
    annotation (Placement(transformation(
        extent={{-10,-10},{10,10}},
        rotation=180, origin={-50,0})));
  Modelica.Blocks.Math.Feedback feedback
    annotation (Placement(transformation(
        extent={{10,-10},{-10,10}})));
equation
  connect(feedback.y, gain.u) annotation (Line(
      points={{-9,0},{2,0},{2,0},{-38,0}},
      color={0,0,127}, smooth=Smooth.None));
  connect(feedback.u1, setpoint) annotation (Line(
      points={{8,0},{40,0},{40,60},{0,60},{0,120}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(gain.y, command) annotation (Line(
      points={{-61,0},{-80.5,0},{-80.5,0},{-110,0}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(measured, feedback.u2) annotation (Line(
      points={{100,0},{60,0},{60,-40},{0,-40},{0,-8}},
      color={0,0,127}, smooth=Smooth.None));
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 (Placement(transformation(
        extent={{10,-10},{-10,10}})));
equation
  connect(setpoint, PID.u_s) annotation (Line(
      points={{0,120},{0,60},{40,60},{40,0},{12,0}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(measured, PID.u_m) annotation (Line(
      points={{100,0},{60,0},{60,-40},{0,-40},{0,-12}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(PID.y, command) annotation (Line(
      points={{-11,0},{-110,0}},
      color={0,0,127},
      smooth=Smooth.None));
  annotation (Icon(graphics={
        Rectangle(
          extent={{-100,100},{100,-100}},

Sensor Models

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 (Placement(transformation(extent={{-110,-10},{-90,10}})));
  Modelica.Blocks.Interfaces.RealOutput w "Absolute angular velocity of flange"
    annotation (Placement(transformation(extent={{100,-10},{120,10}})));
protected
  Modelica.Mechanics.Rotational.Sensors.SpeedSensor idealSpeedSensor
    "An ideal speed sensor" annotation (Placement(transformation(
        extent={{-10,-10},{10,10}})));
equation
  connect(idealSpeedSensor.flange, shaft) annotation (Line(
      points={{-10,0},{-100,0}},
      color={0,0,0},
      smooth=Smooth.None));
  connect(idealSpeedSensor.w, w) annotation (Line(
      points={{11,0},{110,0}},
      color={0,0,127},
      smooth=Smooth.None));
  annotation (Icon(graphics={
        Rectangle(
within ModelicaByExample.Architectures.SensorComparison.Implementation;
model SampleHoldSensor "Implementation of a sample hold sensor"
  parameter Modelica.SIunits.Time sample_rate(min=Modelica.Constants.eps);
  Modelica.Mechanics.Rotational.Interfaces.Flange_a shaft
    "Flange of shaft from which sensor information shall be measured"
    annotation (Placement(transformation(extent={{-110,-10},{-90,10}})));
  Modelica.Blocks.Interfaces.RealOutput w "Absolute angular velocity of flange"
    annotation (Placement(transformation(extent={{100,-10},{120,10}})));
protected
  Components.SpeedMeasurement.Components.SampleHold sampleHoldSensor(
      sample_rate=sample_rate)
    annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
equation
  connect(sampleHoldSensor.w, w) annotation (Line(
      points={{11,0},{110,0}},
      color={0,0,127},
      smooth=Smooth.None));
  connect(sampleHoldSensor.flange, shaft) annotation (Line(
      points={{-10,0},{-100,0}},
      color={0,0,0},
      smooth=Smooth.None));
  annotation ( Icon(graphics={
        Rectangle(

Variations

Baseline Configuration

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 (choicesAllMatching=true,
      Placement(transformation(extent={{-10,-50},{10,-30}})));
  replaceable Implementation.IdealActuator actuator constrainedby
    Interfaces.Actuator
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{-50,-50},{-30,-30}})));
  replaceable Implementation.IdealSensor sensor constrainedby Interfaces.Sensor
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{30,-50},{50,-30}})));
  replaceable Implementation.ProportionalController controller constrainedby
    Interfaces.Controller
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{-10,-10},{10,10}})));
  replaceable Modelica.Blocks.Sources.Trapezoid setpoint(period=1.0) constrainedby
    Modelica.Blocks.Interfaces.SO
    annotation (choicesAllMatching=true,
      Placement(transformation(extent={{-60,30},{-40,50}})));

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_rate=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_rate=0.01));
end Variant2_tuned;

Then, we will get even better simulation results:

Conclusion

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.