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 tools 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 implemented in Python and depends on ZeroMQ - high performance asynchronous messaging library.

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.

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

  • Possibility to run DoEs (design of experiments) based on parameter variation of an existing model.

  • Run models in different environments like Linux, Windows, docker or WSL.

  • Run compiled models without any dependency on OMC / ZMQ.

The classes which talk to OMC depend on an OpenModelica installation, because they start or connect to an OMC server. The classes which only run already compiled models - OMSessionRunner, ModelicaSystemRunner and OMPython.model_execution - do not, and are therefore usable on machines without OpenModelica.

Besides these main parts, additional helper functionality exists:

Each of the main sections listed above is differentiated in

  • OMPython.*_abc - abstract base classes holding the basic functionality which is shared by the two available implementations

  • OMPython.*_omc - run OpenModelica based on an OMC server

  • OMPython.*_runner - run simulations using pre-compiled binaries

The two available implementations mentioned above are OMPython.*_omc, which drives a full OpenModelica installation by sending commands to an OMC server, and OMPython.*_runner, which only executes a previously compiled model executable and therefore runs without OMC / ZeroMQ.

The following documentation is based on current OMPython version, which contains a compatibility layer supporting the main interface of OMPython v4.0.0. During a transition period, both options are available. The main differences between both interfaces as well as the limitations of the compatibility layer are described in Compatibility with OMPython v4.0.0.

Session Classes

OMPython provides a set of classes named OMCSession*. All of them use ZeroMQ to communicate with the OpenModelica Compiler (OMC) and they all offer the same interface, see Session API. The following options exist:

  • OMCSessionLocal(timeout=None, omhome=None) - the default; it starts an omc server as a child process of the current Python process. omhome is the OpenModelica installation directory, i.e. the directory which contains bin/omc. If it is not given, the installation is looked up in the OPENMODELICAHOME environment variable, and then via the omc command in PATH.

    import OMPython
    # the installation is looked up in $OPENMODELICAHOME and then in $PATH
    omc = OMPython.OMCSessionLocal()
    # or select the installation explicitly; a longer timeout for slow operations
    omc = OMPython.OMCSessionLocal(omhome="/opt/openmodelica", timeout=600)
    
  • OMCSessionPort(omc_port, timeout=None) - connects to an already running OMC server. omc_port is the connection string which the server reports, for example tcp://127.0.0.1:41613.

    import OMPython
    # start the server separately, e.g. in another terminal:
    #   omc --interactive=zmq
    #   port   ->  "tcp://127.0.0.1:41613"
    omc = OMPython.OMCSessionPort(omc_port="tcp://127.0.0.1:41613")
    print(omc.get_port())
    print(omc.sendExpression("getVersion()"))
    
  • OMCSessionDocker(timeout=None, docker=None, dockerExtraArgs=None, dockerOpenModelicaPath="omc", dockerNetwork=None, port=None) - runs the OMC server in a Docker container. docker is the image to use, dockerOpenModelicaPath the path of omc within the image and port the port to connect to. The container is started by OMPython and removed again when the session ends. This is the recommended way to get a reproducible compiler environment, independent of the Python installation.

    import OMPython
    omc = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython")
    print(omc.sendExpression("getVersion()"))
    print(omc.get_docker_container_id())
    
  • OMCSessionDockerContainer(timeout=None, dockerContainer=None, dockerExtraArgs=None, dockerOpenModelicaPath="omc", dockerNetwork=None, port=None) - runs the OMC server in an already existing Docker container which is identified by dockerContainer, i.e. by its container ID. In contrast to OMCSessionDocker, the container is not removed when the session is closed.

    import OMPython
    # a long living container, e.g. started via "docker run -d <image> sleep infinity"
    container_id = "b1e4e6a0f7c2"
    
    omc = OMPython.OMCSessionDockerContainer(dockerContainer=container_id)
    print(omc.sendExpression("getVersion()"))
    print(omc.get_docker_container_id())
    
    # alternatively, reuse the container of an OMCSessionDocker instance
    omc_docker = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython")
    omc_inner = OMPython.OMCSessionDockerContainer(dockerContainer=omc_docker.get_docker_container_id())
    print(omc_inner.sendExpression("getVersion()"))
    

    Each session starts its own omc server inside the given container, so several sessions can work with the same container without interfering. The container survives the session, which makes this the session of choice for repeated runs against a fixed compiler environment.

  • OMCSessionWSL(timeout=None, wsl_omc="omc", wsl_distribution=None, wsl_user=None) - runs the OMC server within the Windows Subsystem for Linux. The distribution and the user can be selected; wsl_omc is the path of omc within the WSL environment.

    import OMPython
    omc = OMPython.OMCSessionWSL()
    print(omc.sendExpression("getVersion()"))
    
  • OMSessionRunner(ompath_runner=OMPathRunnerLocal, timeout=None, version="1.27.0", cmd_prefix=None, model_execution_local=True) - the runner session described in Running a pre-compiled model; it runs a pre-compiled model executable directly, without using OMC at all. ompath_runner selects how the paths are resolved, either locally (OMPathRunnerLocal) or via a remote shell (OMPathRunnerBash), and cmd_prefix is the command prefix needed to enter that environment.

    import OMPython
    
    # a model executable which runs on the local machine
    runner = OMPython.OMSessionRunner(version="1.27.0")
    mod = OMPython.ModelicaSystemRunner(session=runner, work_directory="/path/to/build_dir")
    mod.setup(model_name="BouncingBall")
    mod.simulate()
    
    # a model executable which runs inside a Docker container; the command prefix is taken
    # from the Docker session which built the model
    docker_omc = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython")
    runner = OMPython.OMSessionRunner(
        version=docker_omc.get_version(),
        cmd_prefix=docker_omc.model_execution_prefix(cwd="/path/to/build_dir"),
        ompath_runner=OMPython.OMPathRunnerBash,
        model_execution_local=False,
    )
    mod = OMPython.ModelicaSystemRunner(session=runner, work_directory="/path/to/build_dir")
    

    Since no compiler is involved, the version passed to the constructor is only reported by get_version(); it does not have to match any real OMC server.

All sessions accept a timeout argument; see Session API.

The handling of any paths within the communication is covered by the OMCPath class. It is an implementation based on pathlib which uses OMC to run the different filesystem related commands. Therefore, it can be used also for remote / separated systems like docker or WSL. Because all paths are resolved by OMC and not by the local Python process, a path created via omc.omcpath(...) always refers to the file system seen by OMC, which is not necessarily the file system of the Python process. See File and directory access for details.

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

To get started, create an OMCSessionLocal object:

>>> import OMPython
>>> omc = OMPython.OMCSessionLocal()
>>> omc.sendExpression("getVersion()")
v1.28.0-dev.1116+g9ff6d75f32.cmake
>>> 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/jenkins/ws/OpenModelica/build/share/doc/omc/testmodels/BouncingBall.mo', False, 1, 1, 26, 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/jenkins/ws/OpenModelica/build/bin:/var/lib/jenkins/ws/OpenModelica/build/bin:/usr/local/cargo/bin:/opt/venv/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 6c65e5decd4e 6.8.0-117-generic #117-Ubuntu SMP PREEMPT_DYNAMIC Tue May  5 19:26:24 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux\n', 'RTLIBS': ' -lOpenModelicaRuntimeC -llapack -lblas -lm -lpthread -rdynamic --coverage ', 'C_COMPILER': 'cc', 'C_COMPILER_VERSION': 'cc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.  There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n', 'C_COMPILER_RESPONDING': True, 'CONFIGURE_CMDLINE': 'Configured  using arguments: '}

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-6, 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.001189092, 'timeBackend': 0.0036341100000000003, 'timeSimCode': 0.001169595, 'timeTemplates': 0.0023999340000000003, 'timeCompile': 0.11776831600000001, 'timeSimulation': 0.006179877, 'timeTotal': 0.132419601}
>>> omc.sendExpression("val(h , 2.0)")
0.04239430772884106

Import As Library

To use the module from within another python program, simply import the selected OMCSession* class from within the selected program.

For example:

# test.py
import OMPython
omc = OMPython.OMCSessionLocal()
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))

Session API

All OMCSession* classes derive from OMCSessionABC and share the same interface. The following methods are available on every OMC based session.

sendExpression(expr, parsed=True, raise_on_error=True) is the central method. It sends expr to the OMC server and returns the result, converted into a Python object by the parser described in Parsers - Parsing OMC Return Data. Some typical usages are:

omc.sendExpression("getVersion()")
omc.sendExpression("getClassNames()")

The parsed argument controls whether the returned string is run through the parser. Set it to False whenever you need the exact string that OMC produced, for example when you want to post-process the output yourself. The two calls which return free text which cannot be parsed, getErrorString() and getMessagesStringInternal(), always return the raw string and log a warning if parsed=True was requested. quit() closes the connection and returns None; it is called automatically when the session object is deleted.

By default, sendExpression() raises an OMSessionException if OMC emitted an error-level diagnostic during the call. Some OMC API calls, notably buildModel, can emit error-level diagnostics which are recoverable and do not actually prevent the call from succeeding. If your code has its own, more precise way of verifying success - for example by checking that the expected files were created - pass raise_on_error=False to log those messages instead of raising an exception:

try:
  omc.sendExpression("loadModel(Modelica)")
except OMPython.OMSessionException as ex:
  print("Modelica could not be loaded:", ex)

The remaining session methods are:

  • get_version() returns the version of the connected OMC server as a string.

  • set_timeout(timeout=None) sets the timeout which is used when waiting for an answer of the OMC server or for the model executable, and returns the timeout which was in effect before the call. The value of zero or less raises an OMSessionException. Passing None changes nothing, which makes set_timeout() a getter.

  • set_workdir(workdir) changes the working directory of the OMC server, i.e. the directory subsequent relative paths are resolved against. It is a no-op for OMSessionRunner.

  • omcpath(*path) creates an OMCPath object, see File and directory access.

  • omcpath_tempdir(tempdir_base=None) creates a uniquely named temporary directory as OMCPath. If tempdir_base is given, the directory is created inside that directory. This is how ModelicaSystem obtains its private build directory.

  • get_cmd_prefix() returns the command prefix needed to run commands in the environment which is defined by the session.

  • escape_str(value) escapes a string so that it can be embedded into an OMC expression: all backslashes and double quotes are escaped. Use it whenever you interpolate user data into a command.

In addition, the OMC based sessions provide get_port() which returns the ZeroMQ address of the OMC server and get_log() which returns the content of the OMC server log file, and the Docker based sessions provide get_server_address() and get_docker_container_id(). OMSessionRunner provides none of them, and its sendExpression() always raises an OMSessionException.

File and directory access

The OMCPath class gives you a pathlib-like interface to the file system which OMC sees. This is important when OMC does not run on the local machine, e.g. inside a Docker container or in WSL - in that case the Python process and OMC do not share a file system, and a plain pathlib.Path would silently operate on the wrong files.

Paths are always created via the session they belong to, so that the correct backend is used:

import OMPython
omc = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython")
mo_file = omc.omcpath("/work") / "BouncingBall.mo"
mo_file.write_text('model BouncingBall end BouncingBall;')
print(mo_file.is_file())
print(mo_file.read_text())
print(mo_file.size())

The following methods are available. They all have the same names and semantics as their pathlib.Path counterparts:

  • is_file() and is_dir() check the type of the path.

  • exists() is a shorthand for is_file() or is_dir().

  • is_absolute() checks whether the path is absolute. Windows and POSIX conventions are distinguished based on the environment defined by the session.

  • read_text() and write_text(data) read and write the file content. Both are always UTF-8 encoded; the other arguments of the pathlib methods are ignored.

  • mkdir() creates a directory. An existing directory raises a FileExistsError unless exist_ok=True is given.

  • unlink() deletes the file or the empty directory. A path which does not exist raises a FileNotFoundError unless missing_ok=True is given.

  • resolve() and absolute() convert a relative path into an absolute one. The path has to exist, because OMC can only resolve existing paths.

  • cwd() returns the current working directory of OMC.

  • size() returns the file size in bytes. It raises an OMSessionException if the path is not a file.

  • get_session() returns the session this path belongs to.

The path arithmetic of pathlib - the / operator, .parent, .name, .stem, .suffix and .as_posix() - is inherited unchanged and needs no OMC call.

Modelica System Classes

The ModelicaSystem class adds more functionality to OMPython. It provides methods to query information about models, to modify data (parameters, inputs, ...) and to simulate them. The corresponding API is described below.

Two implementations are available:

  • ModelicaSystemOMC compiles the model with OMC and can therefore do everything the OpenModelica compiler can do. This is the default choice and the one described below.

  • ModelicaSystemRunner only runs an already compiled model executable. It needs no OMC at runtime, see Running a pre-compiled model.

To get started, create a ModelicaSystem object:

>>> import OMPython
>>> mod = OMPython.ModelicaSystemOMC()

The constructor for a ModelicaSystemOMC object creates an OMCSessionLocal by default. If this is not desired or additional configuration is needed, several options exist:

  • command_line_options (optional) - a list of additional command line options for OMC. The list elements are provided to OMC via setCommandLineOptions(). If the option is set, the default command line options of OMC are overridden; pass an empty list to disable all of them. The default of OMPython itself sets --linearizationDumpLanguage=python and --generateSymbolicLinearization, which make linearize() fast and let the model executable be reused for a linearization:

  • work_directory (optional) - the directory which is used for the model build and for temporary files such as the model executable and the result file. If it is not given, a unique temporary directory is created for the instance, see The work directory.

  • omhome (optional) - the OpenModelica installation directory, i.e. the directory which contains bin/omc. It is only used when the session is created, so it has no effect in combination with session. If it is not given, the directory is taken from the OPENMODELICAHOME environment variable, and otherwise derived from the omc command in PATH.

  • session (optional) - an existing session to use. This is the way to combine ModelicaSystem with a Docker, WSL or port based session:

>>> docker_omc = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython")
>>> mod = OMPython.ModelicaSystemOMC(session=docker_omc)

After a ModelicaSystem object is created, the model can be defined:

>>> model_path = mod.get_session().sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels/"
>>> mod.model(model_name="BouncingBall", model_file=model_path + "BouncingBall.mo")

The class method model() allows several arguments:

  • model_name - The model name (as string). If the model is wrapped within a Modelica package, the namespace must also be included, e.g. "MyPackage.BouncingBall".

  • model_file - the path where to find the model file, either absolute or relative to the current working directory. The file should use the Modelica file extension ".mo". Because the path is resolved by OMC, a relative path refers to the working directory of the session, not to the working directory of the Python process.

  • libraries - A third input argument (optional) is used to specify the list of dependent libraries or dependent Modelica files. Here, it is possible to just provide the library name or a tuple of library name and version. The libraries are loaded before the model itself is loaded:

>>> mod.model(model_name="BouncingBall", model_file=model_path + "BouncingBall.mo", libraries=["Modelica"])
>>> mod.model(model_name="BouncingBall", model_file=model_path + "BouncingBall.mo", libraries=[("Modelica","3.2.3"), "PowerSystems"])
  • variable_filter - Optional string which sets a filter for the output variables. It is defined as a regular expression. Only variables fully matching the regexp will be stored in the result file. Leaving it unspecified is equivalent to ".*". A filter can also be changed later using set_variable_filter().

  • build - Optional boolean controlling whether the model should be built when model() is called. If False, the model is only loaded and buildModel() has to be called before the model can be simulated.

Build Model

The buildModel() API can either directly be executed on model definition (see above) or be called separately.

>>> mod.buildModel()

It accepts an optional variableFilter argument which sets the regular expression filter for the variables to be stored in the result file; without it the filter of model() or set_variable_filter() is used, and if that is unset as well then ".*". buildModel() translates the changes which were applied via sendExpression() - for example a changed parameter of the model - into a new build, and afterwards verifies that the model executable and the initialization file were really produced. If you only change parameters, input values or simulation options, you do not need to rebuild the model; just call simulate() again.

The following methods give direct access to the underlying OMC session, and to the identity of the model which is currently defined:

  • get_session() returns the session which is used by this instance.

  • get_model_name() returns the name of the model which was defined via model().

  • sendExpression(expr, parsed=True, raise_on_error=True) is a wrapper for OMCSession*.sendExpression().

  • set_command_line_options(command_line_option) sets a command line option for OMC via setCommandLineOptions(). Call buildModel() afterwards for it to take effect.

The work directory

Every ModelicaSystem instance owns a work directory. The model is built in that directory and all files which are produced by the instance - the model executable, the generated C code, the simulation result file and the input CSV file - are placed there.

  • getWorkDirectory() returns the directory as an OMCPath object.

  • setWorkDirectory(work_directory=None) changes the work directory. If called without argument, a new unique temporary directory is created.

mod = OMPython.ModelicaSystemOMC(work_directory="/tmp/my_build_dir")
mod.model(model_name="BouncingBall", model_file="BouncingBall.mo")
print(mod.getWorkDirectory())

Because the work directory is unique per instance, one ModelicaSystem instance corresponds to one model build. If you need several independent simulations of the same model, create one instance per simulation.

Standard get methods

The following methods read values from the currently defined model:

  • getQuantities(names=None) - a list of dictionaries describing every variable of the model.

  • getParameters(names=None) - the parameter values.

  • getInputs(names=None) - the input signal values.

  • getContinuous(names=None) - the values of the continuous signals.

  • getContinuousInitial(names=None) and getContinuousFinal(names=None) - the values of the continuous signals before and after the simulation.

  • getOutputs(names=None) - the values of the outputs.

  • getOutputsInitial(names=None) and getOutputsFinal(names=None) - the values of the outputs before and after the simulation.

  • getSimulationOptions(names=None), getLinearizationOptions(names=None) and getOptimizationOptions(names=None) - the options of the respective simulation mode.

  • getLinearInputs(), getLinearOutputs() and getLinearStates() - plain lists of the variable names which are used for linearization. These take no argument at all.

  • getSolutions(varList=None, resultfile=None) - the simulation results, see below.

All of them except the three linearization name lists and getSolutions() accept the same three forms of the names argument:

  • getParameters() - no argument; returns a dictionary which maps names to values.

  • getParameters("c") - a single name; returns a list with the one value.

  • getParameters(["c", "radius"]) - a list of names; returns a list of values in the order in which the names were requested.

The name has to exist, otherwise a KeyError is raised. The one exception is a list of names, where a name which does not exist is silently skipped. The type of the returned values depends on the method:

  • getParameters(), getSimulationOptions(), getLinearizationOptions() and getOptimizationOptions() always return strings, because a parameter or an option value can be an arbitrary Modelica expression which has not been evaluated yet. Note that even numerical option values such as tolerance are strings. Use float() if you need a number.

  • getQuantities() returns one dictionary per requested variable, with the keys alias, aliasvariable, causality, changeable, description, max, min, name, start, unit and variability.

  • getInputs() returns the start attribute of the input as a string while the model has not been changed, and a list of (time, value) tuples after setInputs() was called.

  • getContinuous(), getOutputs() and their Initial/Final variants return numpy.float64 values, or None for a variable which has no start value, for example a derivative such as der(height).

Note the difference between the initial and the final variants: before a simulation, getContinuous() and getOutputs() return the initial values, after a simulation the values at stopTime. The explicit variants only ever return the initial respectively the final values, and the final ones raise a ModelicaSystemError if no simulation was run.

Usage of getMethods

The examples below show a BouncingBall model, once defined and built but not yet simulated, and once after simulate() was called.

>>> mod.getQuantities()
[{'alias': 'noAlias', 'aliasvariable': None, 'causality': 'local',
  'changeable': 'true', 'description': None, 'max': None, 'min': None,
  'name': 'height', 'start': '1.0', 'unit': None, 'variability': 'continuous'},
 # ...

>>> mod.getQuantities("height")
[{'alias': 'noAlias', 'aliasvariable': None, 'causality': 'local',
  'changeable': 'true', 'description': None, 'max': None, 'min': None,
  'name': 'height', 'start': '1.0', 'unit': None, 'variability': 'continuous'}]

>>> mod.getQuantities(["c", "radius"])
[{'alias': 'noAlias', ..., 'name': 'c', 'start': '0.9', ..., 'variability': 'parameter'},
 {'alias': 'noAlias', ..., 'name': 'radius', 'start': '0.1', ..., 'variability': 'parameter'}]

>>> mod.getParameters()
{'c': '0.9', 'radius': '0.1'}

>>> mod.getParameters(["c", "radius"])
['0.9', '0.1']

>>> mod.getContinuous()
{'height': 1.0, 'der(height)': None, 'velocity': 0.0, 'der(velocity)': None}

>>> mod.getContinuous(["velocity", "height"])
[0.0, 1.0]

>>> mod.getInputs()
{}

>>> mod.getOutputs()
{}

>>> mod.getSimulationOptions()
{'startTime': '0.0', 'stopTime': '2.0', 'stepSize': '0.002', 'tolerance': '1e-06',
 'solver': 'dassl', 'outputFormat': 'mat'}

>>> mod.getSimulationOptions(["stepSize", "tolerance"])
['0.002', '1e-06']

An empty dictionary here is correct: BouncingBall has no inputs and no outputs, because height has the causality local. Use getQuantities("height") to see the causality of a variable, and getContinuous() for its value.

After a simulation, getContinuous() and getOutputs() return the values at stopTime:

>>> mod.simulate()
>>> mod.getContinuous()
{'height': 0.6590703905294362, 'der(height)': -1.825929609047952,
 'velocity': -1.825929609047952, 'der(velocity)': -9.81}

>>> mod.getContinuousFinal("height")
[0.6590703905294362]

The getSolutions() method can be used in two different ways:

  • Without a varList it returns the names of the variables for which results are available.

  • With a varList it returns the data itself, as a two dimensional numpy array with one row per variable and one column per time point.

If no resultfile is given, the result file of the last simulate() call is used. This makes it possible to read results of an earlier simulation, and to compare simulations and perform regression testing:

>>> mod.getSolutions()
('time', 'height', 'velocity', 'der(height)', 'der(velocity)', 'c', 'radius')

>>> mod.getSolutions(["time", "height"])
array([[0.000e+00, 5.000e-04, 1.000e-03, ..., 2.000e+00],
       [1.000e+00, 9.999e-01, 9.997e-01, ..., 6.591e-01]])

>>> mod.getSolutions(["time", "height"], resultfile="/tmp/other_run.mat")

The method plot(plotdata, resultfile=None) passes the given expression to the plotting facility of OMC, for example mod.plot("height"). Because OMC itself creates the plot, it only works if the session is an OMCSessionLocal; Docker and WSL sessions have no access to a display.

Standard set methods

The following methods change the values of the currently defined model:

  • setParameters() sets the values of model parameters.

  • setContinuous() sets the initial values of continuous variables.

  • setSimulationOptions(), setLinearizationOptions() and setOptimizationOptions() set the options of the respective simulation mode.

  • setInputs() sets the time based input signals of the model, see below.

All of them take keyword arguments, i.e. the values are provided as a dictionary, and return True if the values were accepted. A name which does not exist, or which belongs to a different kind of variable, raises a ModelicaSystemError. The values are stored as strings, because a Modelica parameter can be an arbitrary expression which has not been evaluated yet:

mod.setParameters(radius=14)
mod.setParameters(radius=14, c=0.5)
mod.setParameters(**{"radius": 14, "c": 0.5})

Additionally, the following helper methods are available:

  • isParameterChangeable(name) returns whether the parameter can be changed without recompiling the model, i.e. whether its changeable attribute is not false. A parameter is not changeable if it is structural, final, protected, evaluated or has a non-constant binding.

  • set_variable_filter(variable_filter=None, escape=False) sets the regular expression which selects the variables to be stored in the result file. With escape=True all regular expression special characters in the filter are escaped, so that a literal string can be used. An invalid regular expression raises a ModelicaSystemError, and None removes the filter.

  • toInputs(data) converts a dictionary of lists - as returned by pandas.DataFrame.to_dict(orient='list') - into the input format used by setInputs(). The dictionary must contain a time key.

  • setInputsCSV(csvfile) reads the time based input data from a CSV file. The file has to contain a header row, the first column is used as time, and the header of the remaining columns defines the input names. Note that this file is read by the local Python process, so the path has to be a local path even if the session itself is a Docker, WSL or port based one. The method returns None.

Usage of setMethods

>>> mod.setInputs(cAi=1, Ti=2)            # set constant input signals

>>> mod.setParameters(radius=14)           # set one parameter

>>> mod.setParameters(radius=14, c=0.5)    # set several parameters at once

>>> mod.setContinuous(height=2.0)          # set an initial value of a continuous variable

>>> mod.setSimulationOptions(stopTime=2.0, tolerance=1e-08)

The input signals are the one exception to the "value as string" rule of the other set methods. A value may be given as a single number, which is then held constant over the whole simulation, or as a list of (time, value) tuples, which defines a piecewise linear signal. The time values have to be in increasing order and must not be smaller than startTime:

>>> mod.setSimulationOptions(startTime=0.0, stopTime=2.0)
>>> mod.setInputs(u=[(0.0, 0.0), (1.0, 1.0), (2.0, 0.5)])
>>> mod.setInputs(u=0.5)                   # constant over [0.0, 2.0]

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)

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

>>> mod.getParameters()
{'c': '0.5', 'radius': '14'}

Note that the values are strings, so the order of the dictionary is not sorted and the values have to be compared as strings.

The model can be simulated using the simulate API in the following ways:

  • without any arguments,

  • with a resultfile keyword argument,

  • with a simargs keyword argument, i.e. runtime simulation flags supported by OpenModelica.

>>> mod.simulate()      # default result file name will be used
>>> mod.simulate(resultfile="tmpbouncingBall.mat")
>>> mod.simulate(simargs={"noEventEmit": None, "noRestart": None, "override": {"e": 0.3, "g": 10}})

All changes which were made via the set methods - parameters, continuous values, simulation options and inputs - are passed to the model executable as command line arguments when simulate() is called. override is a special key: it takes a dictionary of variable names and values which is translated into the corresponding -override runtime flag. Setting an override value to None removes it again. The remaining keys of simargs are the runtime flags of the compiled model, where a key without value (None) becomes a flag and a key with a value becomes -key=value.

Note that the simargs dictionary is passed on unchanged, i.e. it is not validated against the model. An unknown key results in an error from the model executable, which OMPython reports as a ModelicaSystemError.

simulate() returns nothing on success. The result file is available in the work directory under the name <model_name>_res.mat, unless a resultfile was given.

If you only need the command line of a simulation - for example to run it later, on a different machine, or in parallel - use simulate_cmd() instead of simulate(). It applies all pending changes and returns a ModelExecutionConfig object, see Execute Compiled Models, which can be turned into a runnable command:

from OMPython import ModelExecutionConfig

cmd = mod.simulate_cmd(result_file=mod.getWorkDirectory() / "MyRes.mat",
                       simargs={"noRestart": None})
execution = cmd.definition()   # -> ModelExecutionRun
print(" ".join(execution.get_cmd()))
returncode = execution.run()    # run the simulation

simulate_cmd() is the basis of the DoE functionality, see Design of Experiments.

Linearization

The following methods are used for linearization.

  • linearize()

  • getLinearizationOptions()

  • setLinearizationOptions()

  • getLinearInputs()

  • getLinearOutputs()

  • getLinearStates()

>>> mod.getLinearizationOptions()
{'startTime': '0.0', 'stopTime': '1.0', 'stepSize': '0.002', 'tolerance': '1e-08'}

>>> mod.getLinearizationOptions(["startTime", "stopTime"])
['0.0', '1.0']

>>> mod.setLinearizationOptions(stopTime=2.0, tolerance=1e-06)

>>> mod.linearize()      # returns a LinearizationResult object

>>> mod.getLinearInputs()    # list of the input names used when forming the matrices
['u']

>>> mod.getLinearOutputs()   # list of the output names used when forming the matrices
['y']

>>> mod.getLinearStates()    # list of the state names used when forming the matrices
['x']

linearize() accepts an optional lintime argument to override the stopTime of the linearization, and an optional simargs argument to pass runtime flags such as an input file:

mod.linearize(lintime=2.0)
mod.linearize(simargs={"csvInput": "my_input.csv"})

The returned LinearizationResult can be used in three ways, depending on how much information you need:

# (a) unpack just the matrices
A, B, C, D = mod.linearize()

# (b) access all attributes by name
result = mod.linearize()
print(result.A, result.B, result.C, result.D)
print(result.n, result.m, result.p)          # number of states, inputs, outputs
print(result.x0, result.u0)                  # fixed point and the input at the fixed point
print(result.stateVars, result.inputVars, result.outputVars)

# (c) index access, for backwards compatibility with the tuple which linearize() returned before
A = mod.linearize()[0]

Optimization

Besides simulating and linearizing a model, ModelicaSystemOMC can also run a model-based optimization. The optimization problem is defined in the model itself via an optimize algorithm annotation; ModelicaSystem only controls the simulation options of that run.

>>> mod.getOptimizationOptions()
{'startTime': '0.0', 'stopTime': '1.0', 'numberOfIntervals': '500',
 'stepSize': '0.002', 'tolerance': '1e-08'}

>>> mod.setOptimizationOptions(stopTime=2.0, numberOfIntervals=1000)

>>> mod.optimize()

optimize() returns a dictionary. Besides the path to the result file, it contains the options which were used and the time which was spent in the different compilation and simulation phases:

result = mod.optimize()
print(result['resultFile'])
print(result['simulationOptions'])  # -> startTime = 0.0, stopTime = 1.0, ...
print(result['timeFrontend'], result['timeTotal'])

Note that optimize() sets the compiler flag -g=Optimica via set_command_line_options() in order to generate the simulation code of the optimization problem. The flag stays set, so a model which was optimized once keeps the Optimica backend for all following buildModel() calls of the same instance.

Reading the result file works in the same way as after a simulation:

mod.getSolutions(resultfile=result['resultFile'])
mod.getSolutions("y", resultfile=result['resultFile'])

Plotting

plot(plotdata, resultfile=None) forwards the plot to the OMC plot() API call and displays the result in the plot window of the OMC installation:

mod.plot("height")

Because the plot is rendered by OMC, which needs access to a local display, plot() only works for a local session. It is not available when the session runs OMC in Docker or in WSL. To plot results in that case, read them with getSolutions() and plot them with the plotting library of your choice.

Running a pre-compiled model

If the model has already been compiled elsewhere and you only want to run the resulting executable - for example on a machine without an OpenModelica installation, inside a CI job, or in a container image which only contains the model binary - use ModelicaSystemRunner. It does not need an OMC server and does not use ZeroMQ.

import OMPython

mod = OMPython.ModelicaSystemRunner(work_directory="/path/to/build_dir")
mod.setup(model_name="BouncingBall", variable_filter=".*")
mod.setParameters(radius=14, c=0.5)
mod.setSimulationOptions(stopTime=2.0)
mod.simulate()
print(mod.getWorkDirectory() / "BouncingBall_res.mat")

setup() replaces model() because there is nothing to load or to compile. It expects the files which OMC produced for the model to be present in the work directory, which must be passed via the work_directory argument of the constructor:

  • the model executable, either as <model_name> or <model_name>.exe. On Windows an additional <model_name>.bat file is expected; OMPython reads the library path from it.

  • the model initialization file <model_name>_init.xml, which provides the model structure. The quantities of the model - and therefore all get methods - are read from this file.

The constructor also accepts a session argument, but it has to be an OMSessionRunner; any other session raises a ModelicaSystemError, because running a model executable does not need a compiler.

All get and set methods, simulate(), simulate_cmd() and linearize() behave as described above. The methods which need OMC are not available on this class, because they are defined in ModelicaSystemOMC only. This affects:

An ModelicaSystemRunner instance cannot be reused for a second model; a second call of setup() raises a ModelicaSystemError, so create a new instance instead.

Design of Experiments

A design of experiments (DoE) is a systematic way of running a model many times with different parameter values, and of collecting the results. ModelicaDoE takes a model which was defined with ModelicaSystem and expands a dictionary of parameter value lists into all combinations of these values, runs the corresponding simulations, and reports which results are available where.

Two implementations are available, matching the two ModelicaSystem implementations:

  • ModelicaDoEOMC for a model defined by ModelicaSystemOMC.

  • ModelicaDoERunner for a model defined by ModelicaSystemRunner.

The difference matters for the structural parameters, see below.

The following example defines a small model, varies four parameters and runs the resulting eight simulations:

import OMPython
import pathlib

mypath = pathlib.Path('.')

model = mypath / "M.mo"
model.write_text(
    "model M\n"
    "  parameter Integer p=1;\n"
    "  parameter Integer q=1;\n"
    "  parameter Real a = -1;\n"
    "  parameter Real b = -1;\n"
    "  Real x[p];\n"
    "  Real y[q];\n"
    "equation\n"
    "  der(x) = a * fill(1.0, p);\n"
    "  der(y) = b * fill(1.0, q);\n"
    "end M;\n"
)

param = {
    # structural
    'p': [1, 2],
    'q': [3, 4],
    # non-structural
    'a': [5, 6],
    'b': [7, 8],
}

resdir = mypath / 'DoE'
resdir.mkdir(exist_ok=True)

mod = OMPython.ModelicaSystemOMC()
mod.model(model_name="M", model_file=model.as_posix())
doe_mod = OMPython.ModelicaDoEOMC(
    mod=mod,
    parameters=param,
    resultpath=resdir,
    simargs={"override": {'stopTime': 1.0}},
)
doe_mod.prepare()
doe_def = doe_mod.get_doe_definition()
doe_mod.simulate()
doe_sol = doe_mod.get_doe_solutions()

The constructor of ModelicaDoEOMC takes the following arguments:

  • mod - the ModelicaSystemOMC instance which holds the model. The type is checked, so a ModelicaSystemRunner has to be combined with ModelicaDoERunner.

  • parameters - a dictionary which maps a parameter name to a list of values to be used for that parameter. All combinations of the lists are simulated, so the example above defines 2x2x2x2 = 8 simulations. A name which does not exist in the model raises an error in prepare().

  • resultpath - the directory in which the result files are stored. It has to exist already, because it is resolved but not created; otherwise a ModelicaSystemError is raised. The default is a temporary directory.

  • simargs - the runtime flags which are used for every simulation, in the same format as the simargs argument of simulate().

Two further methods complete the interface: get_session() returns the session in use and get_resultpath() returns the directory the results are written to. get_doe_command() returns the prepared simulations as a dictionary which maps each result file name to a ModelExecutionRun object, which is useful to run the DoE somewhere else; simulate() simply runs these commands in parallel.

The workflow always consists of the same four steps.

1. prepare() evaluates the parameters and builds the list of simulations. It returns the number of simulations which were defined. This is the step which distinguishes the two implementations:

  • A structural parameter - for example an array size - changes the equations of the model, so the model has to be recompiled for every value it takes. prepare() therefore creates one build per combination of the structural parameters.

  • A non-structural parameter - for example a resistance or a spring constant - does not change the equations. All of its values can be passed to the same model executable at runtime.

The two kinds are told apart with isParameterChangeable(), so the classification is derived from the model and not from the names in the parameters dictionary.

For ModelicaDoEOMC the structural parameters are handled by sending setParameterValue() to OMC and rebuilding the model for every combination, which is why a DoE on structural parameters is comparatively slow. Note that prepare() changes the work directory of the ModelicaSystem instance to the per-combination build directory, so do not reuse that instance for something else afterwards.

ModelicaDoERunner cannot recompile at all, so it can only vary non-structural parameters; passing a structural parameter raises a ModelicaSystemError.

2. get_doe_definition() returns the DoE as a dictionary, where each key is a result file name and the value is a dictionary of the simulation settings, including the structural and non-structural parameter values of that run. The three fixed keys of that dictionary are DICT_ID_STRUCTURE, DICT_ID_NON_STRUCTURE and DICT_RESULT_AVAILABLE; the last one is set to True by simulate() for every run which produced a result file. The data converts directly to a pandas dataframe:

import pandas as pd

doe_df = pd.DataFrame.from_dict(data=doe_mod.get_doe_definition(), orient='index')
print(doe_df)

3. simulate(num_workers=3) runs the simulations which were defined by prepare(), using the given number of worker threads. It returns True if all simulations finished successfully, and False otherwise, so a partial failure does not abort your script. The number of workers is a pure performance setting; increase it to use more CPU cores, and set it to 1 for a deterministic, easy to debug run. A simulation which fails is logged as a warning, so enable the OMPython.modelica_doe_abc logger to see the details. Calling simulate() without a preceding prepare() raises a ModelicaSystemError.

4. get_doe_solutions(var_list=None) is only available on ModelicaDoEOMC, because it needs OMC to read the result files. It returns a dictionary which maps each result file name to a dictionary with the keys 'msg' and 'data'; the 'data' entry contains one numpy array per variable. A run whose result file is missing or unreadable is reported in 'msg' with an empty 'data' dictionary instead of raising, so a single failed simulation does not cost you all results:

doe_sol = doe_mod.get_doe_solutions()
# {'DOE_000000000_000000000.mat': {'msg': 'Simulation available',
#                                 'data': {'time': array([...]), 'x': array([...])}},
#  ...}

doe_sol = doe_mod.get_doe_solutions(["time", "x"])   # restrict the variables

The underlying function OMPython.modelica_doe_omc.doe_get_solutions() can also be called directly, which is useful if the DoE definition is stored somewhere else. The data converts to a pandas dataframe per run:

import pandas as pd

for name, run in doe_sol.items():
    run['df'] = pd.DataFrame.from_dict(data=run['data']) if run['data'] else None

Execute Compiled Models

Everything OMPython does with a simulation boils down to running the model executable. The module OMPython.model_execution implements this step, independently of OMC, and is therefore usable on its own. It is the layer below ModelicaSystem.simulate(), and it is what makes it possible to run a model executable which lives in a different environment than the Python process.

ModelExecutionConfig

ModelExecutionConfig collects everything which is needed to run a compiled model: the directory the model was built in, the command prefix needed to reach that environment, the model name, and the command line arguments. Because the arguments are stored separately from the environment, the same configuration can be inspected, modified and executed independently.

from OMPython import ModelExecutionConfig

cmd = ModelExecutionConfig(
    runpath="/tmp/tmpxxxx",
    cmd_prefix=[],          # e.g. ['docker', 'exec', '--user', '1000', '<container_id>']
    cmd_local=True,         # is the environment the local one?
    cmd_windows=False,      # is the environment a Windows one?
    model_name="BouncingBall",
    timeout=300.0,          # optional, default is 300 s
)

runpath, cmd_prefix and model_name are required; model_name is needed because it determines the name of the executable and of the Windows batch file. Note that runpath has to be the path as seen from within the environment, so for a Docker or WSL environment it is the path inside the container, not a local path.

The arguments are managed with four methods:

  • arg_set(key, val=None) sets one argument. A value of None results in a plain flag -key; any other value results in -key=value. Setting a key which is already set replaces the value and logs a warning.

  • arg_get(key) returns the value of one argument, or None if it is not set.

  • args_set(args) sets several arguments at once from a dictionary. This is the same format as the simargs argument of simulate().

  • get_cmd_args() returns the resulting argument list as a list of strings, sorted by argument name.

The override key is treated specially: its value is a dictionary of variable names and values which is sorted by name and joined into the single -override=name=value,name=value argument that the model executable expects. Values are converted to their Modelica representation, so Python True becomes true and numbers are formatted accordingly.

cmd.arg_set("noRestart", None)
cmd.arg_set("r", "MyRes.mat")
cmd.arg_set("override", {"e": 0.3, "g": 10, "s": "false"})
cmd.get_cmd_args()
# ['-noRestart', '-override=e=0.3,g=10,s=false', '-r=MyRes.mat']
cmd.args_set({"noEventEmit": None, "override": {"e": 0.3}})
print(cmd.arg_get("noEventEmit"))  # -> None (a flag has no value)

definition() freezes the configuration into a ModelExecutionRun object, which is what finally knows about the model executable and the command line to run.

ModelExecutionRun

ModelExecutionRun is a data class holding the resolved command line:

  • cmd_path - the directory the model was built in, as seen by the environment.

  • cmd_model_name - the name of the model.

  • cmd_prefix - the command prefix needed to re-enter the environment, e.g. for Docker.

  • cmd_model_executable - the full path of the executable, including the .exe

    suffix on Windows.

  • cmd_args - the command line arguments.

  • cmd_result_file - the result file of the run. If no -r argument was set, it

    defaults to <model_name>.mat in the build directory.

  • cmd_timeout - the timeout for the run, in seconds.

  • cmd_library_path - an additional library search path, which is only needed for a local

    Windows environment. OMPython derives it from the generated *.bat file.

  • cmd_cwd_local - the working directory to use on the local system. This is only set when

    the environment is the local one, because in Docker or WSL the local directory has no meaning.

The two methods of this class are:

  • get_cmd() returns the complete command line as a list of strings: prefix, executable and arguments. This is the form expected by subprocess.run(), and it is a convenient way to log, print or verify the command which is executed.

  • run() executes the command and returns its return code. The standard output of the executable is logged, an error on standard error or a non-zero exit status raises a ModelExecutionException, and a run which exceeds cmd_timeout also raises a ModelExecutionException.

execution = cmd.definition()
print(" ".join(execution.get_cmd()))
execution.run()

Because this layer only starts a process, it imposes no restrictions on the model. You can use it to run models that OMPython did not build, and to run a model in a Docker container or in WSL by passing the corresponding cmd_prefix.

Parsers - Parsing OMC Return Data

The OMC server answers every request with a string. OMPython converts that string into a Python object before handing it to you, and this conversion is done by the parsers of this section. You can also use them directly, for example to post-process a raw OMC answer.

sendExpression(expr, parsed=True) first tries the typed parser and, if that fails, falls back to the basic parser. So the default gives you the most detailed result which the two parsers can produce, and only a string which neither of them understands is returned unchanged. With parsed=False the unmodified OMC answer is returned.

OMParser - the basic parser

OMParser.om_parser_basic(string) converts the most common Modelica literals into Python values:

  • Integers, floating point numbers and numbers in scientific notation become int and float.

  • true and false become True and False.

  • Sets in the form {1,2,3} become {'SET1': {'Set1': [1, 2, 3]}}. The wrapper keys SET1 and Set1 reflect the "set" semantics of the Modelica notation.

  • Strings are returned including their quotes.

  • Anything the parser does not understand is returned unchanged as a string. It never raises.

from OMPython.OMParser import om_parser_basic

om_parser_basic("1")        # -> 1
om_parser_basic("1.0e-3")   # -> 0.001
om_parser_basic("true")     # -> True
om_parser_basic("{1,2,3}")  # -> {'SET1': {'Set1': [1, 2, 3]}}
om_parser_basic('"abc"')    # -> '"abc"'
om_parser_basic("1,2,3")    # -> '1,2,3'   (unchanged, not an error)

Besides om_parser_basic(), the module provides the helper functions which the parser is built from, such as typeCheck(), bool_from_string() and formatSimRes(). They are useful if you need to post-process an OMC result in the same way as OMPython does.

OMTypedParser - the typed parser

OMTypedParser.om_parser_typed(string) is built with the pyparsing library and converts a wider range of Modelica values, using a strict grammar:

  • Integers, floating point numbers and booleans as above.

  • Strings without their quotes, so "abc" becomes abc.

  • Tuples, i.e. both the array form {1,2} and the tuple form (1,2), become a Python tuple.

  • Array dimensions may be given as an expression, so {1+1, 2*3} becomes (2, 6).

  • Modelica records of the form record R a=1, b="x" end R; become a Python dict.

  • SOME(x) is unwrapped to x, and NONE() becomes None. Note that the parentheses are part of the syntax: a bare NONE is returned as the string 'NONE'.

  • The empty string and the empty tuple () are recognized as None and ().

  • Anything the grammar does not match raises a pyparsing.ParseException.

from OMPython.OMTypedParser import om_parser_typed

om_parser_typed("1")                 # -> 1
om_parser_typed('"abc"')             # -> 'abc'   (no quotes!)
om_parser_typed("{1,2,3}")           # -> (1, 2, 3)
om_parser_typed("{1+1, 2*3}")        # -> (2, 6)
om_parser_typed('record R a=1 end R;')  # -> {'a': 1}
om_parser_typed("SOME(1.0)")         # -> 1.0
om_parser_typed("NONE()")            # -> None
om_parser_typed("NONE")              # -> 'NONE'  (the string, not None!)
om_parser_typed("Modelica.Blocks")  # -> 'Modelica.Blocks'
om_parser_typed("1,2,3")             # -> raises ParseException
om_parser_typed("Modelica.Units.SI.Frequency(1.0)")  # -> raises ParseException

Choosing a parser

The two parsers differ in a way that matters when you post-process OMC answers:

  • Use om_parser_basic() when you want a result which never fails and where keeping the exact text of everything unrecognised is more useful than rejecting it.

  • Use om_parser_typed() when you want real Python types - in particular unquoted strings and tuples instead of the nested SET1 dictionaries - and you are prepared to handle a parse error.

The parsers are a moving target: sendExpression() currently uses the typed parser for its default path, and the session classes are being moved to it. As a consequence, the exact type of a value returned by sendExpression() is an implementation detail. If your code depends on a specific type, ask for it explicitly rather than relying on the default:

omc.sendExpression("getVersion()", parsed=False)             # exact string from OMC
om_parser_typed(omc.sendExpression("getVersion()", parsed=False))

Compatibility with OMPython v4.0.0

The data above describes the current OMPython interface, which reorganized the API of OMPython v4.0.0. During a transition period both interfaces are available: the new one documented in this chapter, and a compatibility layer which keeps the old class and method names working.

Every compatibility class issues a DeprecationWarning when it is instantiated, and it will be removed in a future version. Existing scripts therefore keep running, but the warnings point out what to change. The warning is only shown by default if warnings are enabled for DeprecationWarning, which Python hides outside of __main__; run your script with python -W default::DeprecationWarning to see them.

The following table lists the v4.0.0 names and their replacements.

OMPython v4.0.0

OMPython current

OMCSessionZMQ

OMCSessionLocal

OMCProcessLocal

OMCSessionLocal

OMCProcessPort

OMCSessionPort

OMCProcessDocker

OMCSessionDocker

OMCProcessDockerContainer

OMCSessionDockerContainer

OMCProcessWSL

OMCSessionWSL

OMCSessionCmd

OMCSession*.sendExpression()

OMCSessionException

OMSessionException

ModelicaSystem

ModelicaSystemOMC

ModelicaSystemCmd

ModelicaSystem.simulate_cmd()

ModelicaSystemDoE

ModelicaDoEOMC / ModelicaDoERunner

parse_simflags

the simargs dictionary of simulate()

All of these names are still re-exported from the package root, so OMPython.OMCSessionZMQ and friends keep working. Only ModelicaSystemCmd has to be imported from its module:

from OMPython.ModelicaSystem import ModelicaSystemCmd

Note that OMPython.OMCSessionException still refers to the compatibility subclass, while the exception which the current code raises is OMPython.OMSessionException. Change your except clauses accordingly, otherwise they will not catch anything:

# OMPython v4.0.0
try:
    omc.sendExpression("getVersion()")
except OMPython.OMCSessionException:
    pass

# current
try:
    omc.sendExpression("getVersion()")
except OMPython.OMSessionException:
    pass

The most important changes to be aware of are the following.

Sessions and the omc_process argument

The v4.0.0 code passed the OMC process definition as omc_process, and OMCSessionZMQ was the one class which combined the session and the process. Both are now the same object, and the argument is called session:

# OMPython v4.0.0
omc = OMPython.OMCSessionZMQ(omc_process=OMPython.OMCProcessDocker())

# current
omc = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.27.0-ompython")

The constructor argument OMCSessionZMQ.omc_process and the parameter of the same name in the compatibility class ModelicaSystem are still accepted; ModelicaSystemOMC only takes session.

Defining a model

In v4.0.0 the model was passed to the constructor of ModelicaSystem as fileName and modelName. It is now defined by a separate model() call, which also makes it possible to load libraries first:

# OMPython v4.0.0
mod = OMPython.ModelicaSystem("BouncingBall.mo", "BouncingBall", lmodel=["Modelica"])

# current
mod = OMPython.ModelicaSystemOMC()
mod.model(model_name="BouncingBall", model_file="BouncingBall.mo", libraries=["Modelica"])

The constructor keywords fileName, modelName, lmodel, commandLineOptions, variableFilter and customBuildDirectory are still accepted, and map to model_file, model_name, libraries, command_line_options, variable_filter and work_directory respectively.

Setting values

The v4.0.0 set methods took either a single "name=value" string or a list of such strings. They now take keyword arguments, which removes the need to escape the values and gives a proper error message on typos:

# OMPython v4.0.0
mod.setParameters(["radius=14", "c=0.5"])

# current
mod.setParameters(radius=14, c=0.5)

The string form still works on the compatibility class and issues a DeprecationWarning. The difference is not only cosmetic: the v4.0.0 form had to split the string on =, which made a value containing = impossible and silently dropped everything after a second =. It also removed all spaces from the string, so a value like "a + b" was mangled into "a+b". The current implementation keeps a value as it was given and reports a space in a key or value as an error, and it names an unknown key in the error message instead of doing nothing.

Runtime flags

simflags was a single string holding the runtime flags of the model executable, which had to be quoted and escaped by hand. simargs is a dictionary which is translated into the correct command line, including the special handling of the override flag:

# OMPython v4.0.0
mod.simulate(simflags="-noEventEmit -noRestart -override=e=0.3,g=9.71")

# current
mod.simulate(simargs={"noEventEmit": None, "noRestart": None, "override": {"e": 0.3, "g": 9.71}})

The simflags argument is still accepted by the compatibility class, in simulate(), simulate_cmd() and linearize(). parse_simflags() is available as well, but there is no need for it any more.

Linearization results

linearize() used to return the list [A, B, C, D]. It now returns a LinearizationResult object which carries the matrices together with the dimensions and the variable names. Unpacking and indexing still work, so most existing code continues to run:

A, B, C, D = mod.linearize()      # still works
A = mod.linearize()[0]            # still works
result = mod.linearize()          # new
print(result.n, result.stateVars)

Limitations of the compatibility layer

The compatibility layer covers the naming and the call signatures, not the behaviour:

  • The work directory and the result file are unique per instance in the current version, so an instance cannot be reused for a second model build.

  • Setting a structural parameter via setParameters() raises a ModelicaSystemError in the current version, because the model would have to be recompiled. Use sendExpression() followed by buildModel() to change such a parameter.

  • The compatibility classes only wrap the constructors and the methods which changed their signature. Attributes of the v4.0.0 objects, for example the omc_process attribute of OMCSessionZMQ, are not guaranteed to stay available; use get_session().

  • Only the new classes are documented and tested. Behaviour which is only reachable through the compatibility layer may change without further notice.