UML Class and Instance Diagrams

OpenModelica draws two kinds of UML diagrams of Modelica code:

  • A class diagram shows the classes as they are written: a class, the classes it extends and the classes it uses, each once, with the modifiers declared in it. It is made from the code alone, without instantiating it, so it works for packages, partial classes and functions too.

  • An instance diagram shows a model as it is instantiated: the model and its components, each instance as a box of its own, with the values its parameters have in that instance, and the connections between them.

Both are made by the compiler with the scripting functions getClassDiagram and getInstanceDiagram, so they can be used from scripts, OMShell, OMNotebook and the other clients. OMEdit shows the class diagram of a class in its Class Diagram window.

Formats

Both functions return the diagram as text, or write it to the file given as fileName and return the file name. format is one of:

  • "plantuml" (the default): PlantUML text, to be rendered with PlantUML.

  • "mermaid": Mermaid text. GitHub and GitLab show a mermaid code block in Markdown as a diagram, which is handy for issues, pull requests and the README of a library.

  • "drawio": a draw.io (diagrams.net) file, which can be opened, rearranged and edited in draw.io. In it every class and every line in the box of a class links to the code, e.g. modelica://Tanks.Tank?lineNumber=8&element=k; OMEdit follows these links.

The examples below use this package:

package Tanks
  partial model Vessel
    parameter Real area = 1 "Base area";
    Real level(start = 0);
  end Vessel;
  model Tank
    extends Vessel(area = 2);
    parameter Real k = 0.5 "Outflow coefficient";
    Real qIn, qOut;
  equation
    area*der(level) = qIn - qOut;
    qOut = k*sqrt(max(level, 0));
  end Tank;
  model TwoTanks
    parameter Real k = 0.4;
    Tank t1(k = k), t2(area = 3);
  equation
    t1.qIn = 1;
    t2.qIn = t1.qOut;
  end TwoTanks;
end Tanks;

Class Diagrams

>>> getClassDiagram(Tanks.TwoTanks, fileName = "TwoTanks-class.mmd", format = "mermaid")
"TwoTanks-class.mmd"
Listing 1 TwoTanks-class.mmd
classDiagram
  class Tanks_TwoTanks["Tanks.TwoTanks"] {
    <<model>>
    + parameter Real k = 0.4
    + Tank t1#40;k = k#41;
    + Tank t2#40;area = 3#41;
  }
  class Tanks_Tank["Tanks.Tank"] {
    <<model>>
    + parameter Real k = 0.5
    + Real qIn
    + Real qOut
  }
  class Tanks_Vessel["Tanks.Vessel"] {
    <<partial model>>
    + parameter Real area = 1
    + Real level#40;start = 0#41;
  }
  Tanks_TwoTanks *-- Tanks_Tank : t1
  Tanks_TwoTanks *-- Tanks_Tank : t2
  Tanks_Vessel <|-- Tanks_Tank : #40;area = 2#41;

Mermaid reads a member with parentheses as a method, so the parentheses of modifiers are written as the codes #40; and #41;, which Mermaid shows as ( and ). TwoTanks is composed of two Tank components, labelled with their names, and Tank is a generalization of the partial Vessel, labelled with the modifiers of its extends. Each class is shown once, with the components and modifiers declared in it, so area = 2 and area = 3 appear where they are written, not as the values of the instances.

The options of getClassDiagram:

  • depth (default 1): how many levels of used classes to include, i.e. the types of the components and the classes replaceable classes default to, are constrained by or are redeclared as. With depth = 0 the diagram has only the class and its base classes, which are always included.

  • exclude (default {"Modelica.Icons"}): classes and packages to leave out.

  • showModifiers (default true): false leaves out the modifiers and bindings.

_images/uml-class-diagram-pid.png

Figure 34 getClassDiagram(Modelica.Blocks.Continuous.PID, showModifiers = false) rendered with PlantUML.

Replaceable classes, redeclarations and class extends (redeclare model extends) are shown as well: the stereotype of a class tells how it is declared, e.g. «replaceable package», a class extends is a generalization of the class it replaces, and constraining classes and redeclarations are dependencies. In this package, modelled after Modelica.Media, Pipe has a replaceable medium, System redeclares it as Water, and Water redefines the function h of the partial medium with a class extends:

package R
  partial package PartialMedium
    constant Integer nX = 1;
    replaceable function h
      input Real x;
      output Real y = x;
    end h;
  end PartialMedium;
  package Water
    extends PartialMedium(nX = 2);
    redeclare function extends h
    algorithm
      y := 2*x;
    end h;
  end Water;
  model Pipe
    replaceable package Medium = PartialMedium;
    Real z[Medium.nX] = fill(Medium.h(1), Medium.nX);
  end Pipe;
  model System
    Pipe p(redeclare package Medium = Water);
  end System;
end R;
_images/uml-class-diagram-redeclare.png

Figure 35 getClassDiagram(R.System) rendered with PlantUML: the dashed arrows are the replaceable package and its redeclaration, R.Water.h extends the function it redeclares, and the nested classes are attached to the package they are declared in.

This makes the diagram useful to understand the structure of a library such as Modelica.Media:

getClassDiagram(Modelica.Media.Water.StandardWater, fileName = "Water.drawio",
                format = "drawio", depth = 2)

Instance Diagrams

>>> getInstanceDiagram(Tanks.TwoTanks, fileName = "TwoTanks-instance.mmd", format = "mermaid")
"TwoTanks-instance.mmd"
Listing 2 TwoTanks-instance.mmd
classDiagram
  class I_Tanks_TwoTanks["Tanks.TwoTanks"] {
    <<model>>
    + parameter Real k = 0.4
  }
  class I_t1["t1 : Tanks.Tank"] {
    <<model>>
    + parameter Real area = 2
    + parameter Real k = k #40;= 0.4#41;
  }
  class I_t2["t2 : Tanks.Tank"] {
    <<model>>
    + parameter Real area = 3
    + parameter Real k = 0.5
  }
  I_Tanks_TwoTanks *-- I_t1 : t1
  I_Tanks_TwoTanks *-- I_t2 : t2

Each component that is an instance of a class is a box named after its path and class, e.g. t1 : Tanks.Tank. The box has the parameters and constants of the instance, including the inherited ones, with their binding as written and, when it is not a literal, the value it evaluates to in this instance, e.g. k = k (= 0.4). Variables are not shown.

The options of getInstanceDiagram:

  • depth (default 1): how many levels of components are drawn as boxes of their own. Components below that, and connectors, are lines in the box of the instance they are in.

  • exclude: classes and packages whose instances are left out.

  • showConnections (default true): the connect equations are drawn as associations between the boxes of the components they connect, labelled with the connectors.

  • showProtected (default false): also show the protected elements, marked with -.

  • expandArrays (default false): an array of components is one box, e.g. r[10] : Resistor; with true each element gets a box of its own with its own values, for arrays of known size with at most 100 elements.

For a model built from library components, depth = 1 shows the components of the model, and a higher depth the components inside them:

loadModel(Modelica, {"4.1.0"});
getInstanceDiagram(Modelica.Electrical.Analog.Examples.ChuaCircuit,
                   fileName = "ChuaCircuit.puml")
_images/uml-instance-diagram-chua.png

Figure 36 The instance diagram of Modelica.Electrical.Analog.Examples.ChuaCircuit rendered with PlantUML: the values of the parameters in each component, and the connections labelled with the connectors.