by Marco Bonvini on Sunday October 23, 2016
This post is the second of a series of posts that cover some of the most important features provided by Modelica. Here I’ll describes the type system used by Modelica and how it helps avoiding trivial errors when writing equations. The post continues with a description of the Object-Oriented features provided by Modelica and how they help engineers model complex systems in a modular way.
When in the previous post, SW engineering meets mathematical modeling - Modelica basics,
I said that Modelica variables have types the story wasn’t complete. Modelica gives the ability to
associate units to variables. This feature is pretty simple but has a remarkable impact on the quality
of the code. Adding units to variables/parameters/constants increases the ability to spot
errors in the equations and makes the code more readable.
What is more clear
Real v or
The Modelica Standard Library (MSL available on Github) provides an
extensive list of types that have units and extend the primitive type
Real. This excerpt from the MSL shows
SpecificHeatCapacity types are defined.
Each type has a property called
unit that is used to check the validty of the equations.
Units can be simple like degrees Kelvin, denoted by the string
derived from others like the specific heat capacity,
J/(kg.K), that is the energy per unit of mass that causes a temperature change of one degree Kelvin.
The Modelica tool uses information about the type system and units to
check if the models are valid or not. For example the following model
is not valid because the units in equation
E = cp*T don’t match. The left side is energy
and its unit is Joule
J, the right side’s unit is Joule per Kelvin
J/kg. When the tool
parses and analyzes the model produces the following error
Units error in equation E=cp*T, J != J/kg. Such an error message makes it pretty easy to
understand that we forgot the mass from the correct equation
E = m*cp*T.
So far we’ve seen examples with few variables and simple equations. Real world systems are way more complicated than this. A-causality and types help keep the code more readabile and reduce bugs. This is just the tip of the iceberg, in order to model complex systems we need some extra features.
This is the where Object-Oriented modeling shines and where software engineering meets mathematical modeling.
The next example is this electric circuit, the series connection of a voltage source, a resistor and a capacitor (In case you’re not familiar with electrical circtuits bear with me, you’ll get the concepts anyway).
If we analyze the circuit using Kirchhof’s circuit laws we end up writing the following Modelica model
The model represents the physical system in a “flat” way, there’s no clear distinction between the electrical components that are part of the circuit. We did not took advantage of the a-causality provided by Modelica and we ended up with a Model that represents this specific circuit.
It’s more convenient to imagine the circuit as a network of components that are connected to each other.
The image above shows an exploded view of the circuit and its components. Each component, enclosed in a grey rectangle, represents an actual piece of hardware (e.g., resistors, capacitors, cables, etc.).
Wouldn’t be better to have a library of virtual electric components that can be connected to generate all kinds of virtual circuits? Yes, and Modelica allows to create modular models that can be assembled like construction blocks thanks to the so called connectors.
Modelica connectors are a construct that allows to connect models and let them exchange information. Connectors are inspired to real world physical interactions. Let’s have a look how actual electrical components work to gain some insight.
The pictures above show a resistor and a capacitor. They’re very different objects that behave according to different laws, however they share a common trait: they both have two wires (also known as terminal pins).
Resistors and capacitors don’t care in which part of the circuit are located, they behave in function of the voltages and currents provided to their terminals. Resistors and capacitors see the world through their terminals – and they work just fine.
When an engineer designs a circuit in which resistors and capacitor interact with each other, their terminals are welded. Welding electric terminals imposes two physical constraints, the voltage at their junction becomes the same and the current leaving one component enters the other.
Modelica connectors are the equivalent of electic terminals as they define boundary conditions for models.
In the electrical domain the boundary conditions that characterize the behaviour of components such resistors or capacitors are voltages and currents. This means that a Modelica connector for electrical components will look like
The connector defines two variables, the voltage
v and the current
So far we’ve seen that connectors can be used to define boundary conditions. Now we’ll see how to use connectors for building a self contained model representing a resistor.
The model has two electrical terminals
B, and a parameter
R (the value of the resistance).
The equations describe the behaviour of the component based on the boundary conditions provided by the
A.v - B.v = R*A.i states that the voltage difference between terminal A and B
is equal to the current entering from terminal A multiplied by the resistance R.
A.i + B.i = 0 states that the current entering from terminal A plus the current
entering from therminal B is equal to zero – i.e. the current entering from A leaves from B
(please note that currents are always measured as positive when entering the model).
In a similar way we can build component models for all of the remaining elements: source, capacitor and ground reference.
At this point we have defined models of all the electrical components we need. Each
component has terminals and in order to build a circuit we have to connect them.
Modelica has a construct called
connect(.,.) and it’s purpose is to connect connectors.
This is the new version of the Modelica model that represents the circuit.
This version of the model that uses components and connectors is more readable than the flat representation we started with. The first part of the model defines all the components we’re using while the second part defines how they are connected. Nedless to say that this model is more maintainable than the previous “flat” version. Modularity and reusability improve as well because each component can be replaced and they are ready to be reused in other circuits without any effort.
The model we just wrote is very readable but what happened to all the equations we used to write? And why is the connect statement included in the equation section?
Every time we connect two connectors with a
connect(., .) operator under the hood
the Modelica tool that analyzes the code adds some equations for us.
The equations that are automatically added impose the constraints
that are enforced when welding terminal pins in the real world
The image below shows two
Terminal connectors named
B and the equations that are
generated when they’re connected.
Again, less tedious work for us to do and less opportunities to introduce errors.
At this point the software engineer in the room should stop me and tell everyone “This is good, but I think we can do better than this”.
Creating mathematical models in Modelica (or Matlab, or whatever you like) is still software development. Software engineering practices and well understood design patterns should be leveraged when writing mathematical models.
This is particularly true for people who design Modelica libraries that are used by many and contain houndreds of components. The pleople who design these libraries must have in mind a clear vision on how the library should be structured, which connectors are needed, etc.
The guys who created Modelica know this and they baked into the language several features that are mostly inspired to Object-Oriented programming. One of the most useful features is the ability to create partial models.
Capacitor share quite a bit of code.
Each model defines the connectors
B as well the equation
A.i + B.i = 0.
This is against the DRY principle and fortunately
there’s a simple solution: partial models.
Partial models are models that allows to declare variables, parameters, and equations but don’t have to be complete. This means that a partial model can have more variables than equations. The missing equations will be provided by models that extend the partial model. In case you’re familiar with OO programming, partial models are the Modelica version of abstract classes. Let’s see how the models created so far can be refactored using this feature
The first step is to isolate the traits (parameters, connectors, equations, etc.) shared by
multiple models and collect them into a
partial model. In this case the partial model
represents a bipole, i.e. a generic electric component with two terminals where the sum of
the currents entering and leaving is zero.
The resistor, capacitor and source now become extensions of this basic model.
Again, readability and maintenability increases. Plus, if now we wish to create a new model for an inductor or a variable resistor there already is a partial model to extend that declares the basic structure.
In this post I walked you through two other important features provided by Modelica. We’ve learned that Modelica’s type system can help you catch small errors that would be otherwise difficult to find. Also, we’ve learned what Object-Oriented modeling means and how connectors and abstract models help the design of modular and reusable mathematical models.
If after reading this post you’re even more interested in Modelica I suggest you to look at the online book Modelica by examples by Michael Tiller, otherwise browse the website of the Modelica associations www.modelica.org.
If you’re interested in the work I’ve done with Modelica over the last years you can look here.
Special thanks to Daniel McQuillen for proof reading this post and providing helpful suggestions.