OpenModelica Python Interface and PySimulator

This chapter describes the OpenModelica Python integration facilities.

OMPython – OpenModelica Python Interface

OMPython – OpenModelica Python API is a free, open source, highly portable Python based interactive session handler for Modelica scripting. It provides the modeler with components for creating a complete Modelica modeling, compilation and simulation environment based on the latest OpenModelica library standard available. OMPython is architectured to combine both the solving strategy and model building. So domain experts (people writing the models) and computational engineers (people writing the solver code) can work on one unified tool that is industrially viable for optimization of Modelica models, while offering a flexible platform for algorithm development and research. OMPython is not a standalone package, it depends upon the OpenModelica installation.

OMPython is implemented in Python and depends either on the OmniORB and OmniORBpy - high performance CORBA ORBs for Python or ZeroMQ - high performance asynchronous messaging library and it supports the Modelica Standard Library version 3.2 that is included in starting with OpenModelica 1.9.2.

To install OMPython follow the instructions at https://github.com/OpenModelica/OMPython

Features of OMPython

OMPython provides user friendly features like:

  • Interactive session handling, parsing, interpretation of commands and Modelica expressions for evaluation, simulation, plotting, etc.

  • Interface to the latest OpenModelica API calls.

  • Optimized parser results that give control over every element of the output.

  • Helper functions to allow manipulation on Nested dictionaries.

  • Easy access to the library and testing of OpenModelica commands.

Test Commands

OMPython provides two classes for communicating with OpenModelica i.e., OMCSession and OMCSessionZMQ. Both classes have the same interface, the only difference is that OMCSession uses omniORB and OMCSessionZMQ uses ZeroMQ. All the examples listed down uses OMCSessionZMQ but if you want to test OMCSession simply replace OMCSessionZMQ with OMCSession. We recommend to use OMCSessionZMQ.

To test the command outputs, simply create an OMCSessionZMQ object by importing from the OMPython library within Python interepreter. The module allows you to interactively send commands to the OMC server and display their output.

To get started, create an OMCSessionZMQ object:

>>> from OMPython import OMCSessionZMQ
>>> omc = OMCSessionZMQ()
>>> omc.sendExpression("getVersion()")
OMCompiler v1.19.2-v1.19.2.2+g9baf633d57
>>> omc.sendExpression("cd()")
«DOCHOME»
>>> omc.sendExpression("loadModel(Modelica)")
True
>>> omc.sendExpression("loadFile(getInstallationDirectoryPath() + \"/share/doc/omc/testmodels/BouncingBall.mo\")")
True
>>> omc.sendExpression("instantiateModel(BouncingBall)")
class BouncingBall
  parameter Real e = 0.7 "coefficient of restitution";
  parameter Real g = 9.81 "gravity acceleration";
  Real h(start = 1.0, fixed = true) "height of ball";
  Real v(fixed = true) "velocity of ball";
  Boolean flying(start = true, fixed = true) "true, if ball is flying";
  Boolean impact;
  Real v_new(fixed = true);
  Integer foo;
equation
  impact = h <= 0.0;
  foo = if impact then 1 else 2;
  der(v) = if flying then -g else 0.0;
  der(h) = v;
  when {h <= 0.0 and v <= 0.0, impact} then
    v_new = if edge(impact) then -e * pre(v) else 0.0;
    flying = v_new > 0.0;
    reinit(v, v_new);
  end when;
end BouncingBall;

We get the name and other properties of a class:

>>> omc.sendExpression("getClassNames()")
('BouncingBall', 'ModelicaServices', 'Complex', 'Modelica')
>>> omc.sendExpression("isPartial(BouncingBall)")
False
>>> omc.sendExpression("isPackage(BouncingBall)")
False
>>> omc.sendExpression("isModel(BouncingBall)")
True
>>> omc.sendExpression("checkModel(BouncingBall)")
Check of BouncingBall completed successfully.
Class BouncingBall has 6 equation(s) and 6 variable(s).
1 of these are trivial equation(s).
>>> omc.sendExpression("getClassRestriction(BouncingBall)")
model
>>> omc.sendExpression("getClassInformation(BouncingBall)")
('model', '', False, False, False, '/var/lib/jenkins1/ws/OpenModelica_maintenance_v1.19/build/share/doc/omc/testmodels/BouncingBall.mo', False, 1, 1, 23, 17, (), False, False, '', '', False, '')
>>> omc.sendExpression("getConnectionCount(BouncingBall)")
0
>>> omc.sendExpression("getInheritanceCount(BouncingBall)")
0
>>> omc.sendExpression("getComponentModifierValue(BouncingBall,e)")
0.7
>>> omc.sendExpression("checkSettings()")
{'OPENMODELICAHOME': '«OPENMODELICAHOME»', 'OPENMODELICALIBRARY': '«OPENMODELICAHOME»/lib/omlibrary', 'OMC_PATH': '«OPENMODELICAHOME»/bin/omc', 'SYSTEM_PATH': '/var/lib/jenkins1/ws/OpenModelica_maintenance_v1.19/doc/UsersGuide/../..//build//bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'OMDEV_PATH': '', 'OMC_FOUND': True, 'MODELICAUSERCFLAGS': '', 'WORKING_DIRECTORY': '«DOCHOME»', 'CREATE_FILE_WORKS': True, 'REMOVE_FILE_WORKS': True, 'OS': 'linux', 'SYSTEM_INFO': 'Linux e3cb9b33a6dd 5.4.0-109-generic #123-Ubuntu SMP Fri Apr 8 09:10:54 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux\n', 'RTLIBS': ' -Wl,--no-as-needed -Wl,--disable-new-dtags -lOpenModelicaRuntimeC -llapack -lblas -lm -lomcgc -lpthread -rdynamic', 'C_COMPILER': 'clang', 'C_COMPILER_VERSION': 'clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)\nTarget: x86_64-pc-linux-gnu\nThread model: posix\nInstalledDir: /usr/bin\n', 'C_COMPILER_RESPONDING': True, 'HAVE_CORBA': True, 'CONFIGURE_CMDLINE': "Configured 2022-07-22 22:43:44 using arguments:  '--disable-option-checking' '--prefix=/var/lib/jenkins1/ws/OpenModelica_maintenance_v1.19/install' 'CC=clang' 'CXX=clang++' 'FC=gfortran' 'CFLAGS=-Os' '--with-cppruntime' '--without-omc' '--without-omlibrary' '--with-omniORB' '--enable-modelica3d' '--without-hwloc' '--with-ombuilddir=/var/lib/jenkins1/ws/OpenModelica_maintenance_v1.19/build' '--cache-file=/dev/null' '--srcdir=.'"}

The common combination of a simulation followed by getting a value and doing a plot:

>>> omc.sendExpression("simulate(BouncingBall, stopTime=3.0)")
{'resultFile': '«DOCHOME»/BouncingBall_res.mat', 'simulationOptions': "startTime = 0.0, stopTime = 3.0, numberOfIntervals = 500, tolerance = 1e-06, method = 'dassl', fileNamePrefix = 'BouncingBall', options = '', outputFormat = 'mat', variableFilter = '.*', cflags = '', simflags = ''", 'messages': 'LOG_SUCCESS       | info    | The initialization finished successfully without homotopy method.\nLOG_SUCCESS       | info    | The simulation finished successfully.\n', 'timeFrontend': 0.858514027, 'timeBackend': 0.010831665, 'timeSimCode': 0.003351478, 'timeTemplates': 0.194372272, 'timeCompile': 1.086766784, 'timeSimulation': 0.08183839500000001, 'timeTotal': 2.235880312}
>>> omc.sendExpression("val(h , 2.0)")
0.04239430772884106

Import As Library

To use the module from within another python program, simply import OMCSessionZMQ from within the using program.

For example:

# test.py
from OMPython import OMCSessionZMQ
omc = OMCSessionZMQ()
cmds = [
  'loadFile(getInstallationDirectoryPath() + "/share/doc/omc/testmodels/BouncingBall.mo")',
  "simulate(BouncingBall)",
  "plot(h)"
  ]
for cmd in cmds:
  answer = omc.sendExpression(cmd)
  print("\n{}:\n{}".format(cmd, answer))

Implementation

Client Implementation

The OpenModelica Python API Interface – OMPython, attempts to mimic the OMShell's style of operations.

OMPython is designed to,

  • Initialize the CORBA/ZeroMQ communication.

  • Send commands to the OMC server via the CORBA/ZeroMQ interface.

  • Receive the string results.

  • Use the Parser module to format the results.

  • Return or display the results.

Enhanced OMPython Features

Some more improvements are added to OMPython functionality for querying more information about the models and simulate them. A list of new user friendly API functionality allows user to extract information about models using python objects. A list of API functionality is described below.

To get started, create a ModelicaSystem object:

>>> from OMPython import OMCSessionZMQ
>>> omc = OMCSessionZMQ()
>>> model_path=omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels/"
>>> from OMPython import ModelicaSystem
>>> mod=ModelicaSystem(model_path + "BouncingBall.mo","BouncingBall")

The object constructor requires a minimum of 2 input arguments which are strings, and may need a third string input argument.

  • The first input argument must be a string with the file name of the Modelica code, with Modelica file extension ".mo". If the Modelica file is not in the current directory of Python, then the file path must also be included.

  • The second input argument must be a string with the name of the Modelica model including the namespace if the model is wrapped within a Modelica package.

  • The third input argument (optional) is used to specify the list of dependent libraries or dependent Modelica files e.g.,

>>> mod=ModelicaSystem(model_path + "BouncingBall.mo","BouncingBall",["Modelica"])
  • The fourth input argument (optional), is a keyword argument which is used to set the command line options e.g.,

>>> mod=ModelicaSystem(model_path + "BouncingBall.mo","BouncingBall",commandLineOptions="-d=newInst")
  • By default ModelicaSystem uses OMCSessionZMQ but if you want to use OMCSession then pass the argument useCorba=True to the constructor.

BuildModel

The buildModel API can be used after ModelicaSystem(), in case the model needs to be updated or additional simulationflags needs to be set using sendExpression()

>>> mod.buildModel()

Standard get methods

  • getQuantities()

  • getContinuous()

  • getInputs()

  • getOutputs()

  • getParameters()

  • getSimulationOptions()

  • getSolutions()

Three calling possibilities are accepted using getXXX() where "XXX" can be any of the above functions (eg:) getParameters().

  • getXXX() without input argument, returns a dictionary with names as keys and values as values.

  • getXXX(S), where S is a string of names.

  • getXXX(["S1","S2"]) where S1 and S1 are list of string elements

Usage of getMethods

>>> mod.getQuantities() // method-1, list of all variables from xml file
[{'aliasvariable': None, 'Name': 'height', 'Variability': 'continuous', 'Value': '1.0', 'alias': 'noAlias', 'Changeable': 'true', 'Description': None}, {'aliasvariable': None, 'Name': 'c', 'Variability': 'parameter', 'Value': '0.9', 'alias': 'noAlias', 'Changeable': 'true', 'Description': None}]
>>> mod.getQuantities("height") // method-2, to query information about single quantity
[{'aliasvariable': None, 'Name': 'height', 'Variability': 'continuous', 'Value': '1.0', 'alias': 'noAlias', 'Changeable': 'true', 'Description': None}]
>>> mod.getQuantities(["c","radius"]) // method-3, to query information about list of quantity
[{'aliasvariable': None, 'Name': 'c', 'Variability': 'parameter', 'Value': '0.9', 'alias': 'noAlias', 'Changeable': 'true', 'Description': None}, {'aliasvariable': None, 'Name': 'radius', 'Variability': 'parameter', 'Value': '0.1', 'alias': 'noAlias', 'Changeable': 'true', 'Description': None}]
>>> mod.getContinuous() // method-1, list of continuous variable
{'velocity': -1.825929609047952, 'der(velocity)': -9.8100000000000005, 'der(height)': -1.825929609047952, 'height': 0.65907039052943617}
>>> mod.getContinuous(["velocity","height"]) // method-2, get specific variable value information
(-1.825929609047952, 0.65907039052943617)
>>> mod.getInputs()
{}
>>>  mod.getOutputs()
{}
>>> mod.getParameters()  // method-1
{'c': 0.9, 'radius': 0.1}
>>> mod.getParameters(["c","radius"]) // method-2
[0.9, 0.1]
>>> mod.getSimulationOptions()  // method-1
{'stepSize': 0.002, 'stopTime': 1.0, 'tolerance': 1e-06, 'startTime': 0.0, 'solver': 'dassl'}
>>> mod.getSimulationOptions(["stepSize","tolerance"]) // method-2
[0.002, 1e-06]
The getSolution method can be used in two different ways.
  1. using default result filename

  2. use the result filenames provided by user

This provides a way to compare simulation results and perform regression testing

>>> mod.getSolutions() // method-1 returns list of simulation variables for which results are available
['time', 'height', 'velocity', 'der(height)', 'der(velocity)', 'c', 'radius']
>>> mod.getSolutions(["time","height"])  // return list of numpy arrays
>>> mod.getSolutions(resultfile="c:/tmpbouncingBall.mat") // method-2 returns list of simulation variables for which results are available , the resulfile location is provided by user
>>> mod.getSolutions(["time","height"],resultfile="c:/tmpbouncingBall.mat") // return list of array

Standard set methods

  • setInputs()

  • setParameters()

  • setSimulationOptions()

Two setting possibilities are accepted using setXXXs(),where "XXX" can be any of above functions.

  • setXXX("Name=value") string of keyword assignments

  • setXXX(["Name1=value1","Name2=value2","Name3=value3"]) list of string of keyword assignments

Usage of setMethods

>>> mod.setInputs(["cAi=1","Ti=2"]) // method-2
>>> mod.setParameters("radius=14") // method-1 setting parameter value
>>> mod.setParameters(["radius=14","c=0.5"]) // method-2 setting parameter value using second option
>>> mod.setSimulationOptions(["stopTime=2.0","tolerance=1e-08"]) // method-2

Simulation

An example of how to get parameter names and change the value of parameters using set methods and finally simulate the "BouncingBall.mo" model is given below.

>>>  mod.getParameters()
{'c': 0.9, 'radius': 0.1}
>>>  mod.setParameters(["radius=14","c=0.5"]) //setting parameter value

To check whether new values are updated to model , we can again query the getParameters().

>>> mod.getParameters()
{'c': 0.5, 'radius': 14}
The model can be simulated using the simulate API in the following ways,
  1. without any arguments

  2. resultfile (keyword argument) - (only filename is allowed and not the location)

  3. simflags (keyword argument) - runtime simulationflags supported by OpenModelica

>>> mod.simulate() // method-1 default result file name will be used
>>> mod.simulate(resultfile="tmpbouncingBall.mat")  // method-2 resultfile name provided by users
>>> mod.simulate(simflags="-noEventEmit -noRestart -override=e=0.3,g=9.71") // method-3 simulationflags provided by users

Linearization

The following methods are proposed for linearization.

  • linearize()

  • getLinearizationOptions()

  • setLinearizationOptions()

  • getLinearInputs()

  • getLinearOutputs()

  • getLinearStates()

Usage of Linearization methods

>>> mod.getLinearizationOptions()  // method-1
{'simflags': ' ', 'stepSize': 0.002, 'stopTime': 1.0, 'startTime': 0.0, 'numberOfIntervals': 500.0, 'tolerance': 1e-08}
>>> mod.getLinearizationOptions("startTime","stopTime") // method-2
[0.0, 1.0]
>>> mod.setLinearizationOptions(["stopTime=2.0","tolerance=1e-06"])
>>> mod.linearize()  //returns a tuple of 2D numpy arrays (matrices) A, B, C and D.
>>> mod.getLinearInputs()  //returns a list of strings of names of inputs used when forming matrices.
>>> mod.getLinearOutputs() //returns a list of strings of names of outputs used when forming matrices
>>> mod.getLinearStates() // returns a list of strings of names of states used when forming matrices.

PySimulator

PySimulator provides a graphical user interface for performing analyses and simulating different model types (currently Functional Mockup Units and Modelica Models are supported), plotting result variables and applying simulation result analysis tools like Fast Fourier Transform.

Read more about the PySimulator at https://github.com/PySimulator/PySimulator.