Architecture Driven Approach

# 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;


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;


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;


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;


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:

## 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";

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}})));
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));
end BasicPlant;


### 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}})));
Modelica.Blocks.Interfaces.RealInput tau "Input torque command"
annotation (Placement(transformation(extent={{-140,-20},{-100,20}})));
protected
Modelica.Mechanics.Rotational.Sources.Torque torque(useSupport=true)
annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
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));
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 (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));
end LimitedActuator;


### 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));
end PID_Controller;


### 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));
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 (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_time=sample_time)
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));
end SampleHoldSensor;


## 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_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:

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