Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ccef1e4
[ModelicaSystem] split __init__()
syntron Aug 23, 2025
ff298bf
[ModelicaSystem] split __init__() - mypy fix in ModelicaSystemCmd
syntron Aug 23, 2025
f29db2c
[ModelicaSystem] split __init__() - mypy fix in convertMo2Fmu()
syntron Aug 23, 2025
6f23fb1
[ModelicaSystem] split __init__() - update unittest
syntron Aug 23, 2025
2e04836
[ModelicaSystem] rename model_definition() => model()
syntron Aug 23, 2025
27c356a
[ModelicaSystem] fix error message
syntron Aug 24, 2025
481bfa1
[ModelicaSystem.definition()] check if it was called before
syntron Aug 24, 2025
6c501c5
[ModelicaSystem] rename lmodel => libraries
syntron Aug 24, 2025
cde432c
[ModelicaSystem] update docstring for __init__() and model()
syntron Aug 25, 2025
12248f0
[test_ModelicaSystem] test_relative_path will fail at the moment; a f…
syntron Nov 4, 2025
c6087c3
[test_ModelicaSystem] fix rebase fallout
syntron Nov 5, 2025
01c4815
[test_ModelicaSystem] fix rebase fallout (2)
syntron Nov 5, 2025
5fc6c53
[ModelicaSystemDoE] fix usage of ModelicaSystem
syntron Nov 5, 2025
d8228d2
[ModelicaSystem] fix mypy
syntron Nov 5, 2025
6dbcedb
[ModelicaSystem] fix default value for fileNamePrefix
syntron Nov 10, 2025
79b93d3
[ModelicaSystem] update convertMo2Fmu() and convertFmu2Mo() to use pa…
syntron Aug 24, 2025
23bbf53
[ModelicaSystem] define convertFmu2Mo() as entry point like definition()
syntron Aug 24, 2025
08f0ded
[ModelicaSystem] fix definition of fmu_path
syntron Aug 24, 2025
5580c05
[ModelicaSystem] fix path in convertFmu2Mo()
syntron Aug 24, 2025
e7dc621
[test_FMIImport] running example / test for convertFmu2Mo()
syntron Aug 25, 2025
f179b0d
[ModelicaSystem] replace pathlib by OMCPath
syntron Nov 4, 2025
aedbd6d
[ModelicaSystem] fix _getconn => _session
syntron Nov 6, 2025
f362cb0
[ModelicaSystem] update convertMo2Fmu() and convertFmu2Mo() to use pa…
syntron Aug 24, 2025
d808984
[ModelicaSystem] consider relativ path if ModelicaSystem is run locally
syntron Oct 31, 2025
3694047
[ModelicaSystem] fix filename for model based on imported FMU
syntron Nov 5, 2025
e96f7c4
Revert "[test_ModelicaSystem] test_relative_path will fail at the mom…
syntron Nov 4, 2025
118594c
[test_ModelicaSystem] fix test_setParameters & test_setSimulationOptions
syntron Nov 4, 2025
815692e
[test_ModelicaSystem] fix tests - use local file as it is copied if n…
syntron Nov 5, 2025
6c9b626
[test_ModelicSystemDoE] simplify test_ModelicaSystemDoE_local
syntron Oct 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 134 additions & 89 deletions OMPython/ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import logging
import numbers
import os
import pathlib
import queue
import textwrap
import threading
Expand Down Expand Up @@ -124,9 +125,12 @@ def __init__(
self,
session: OMCSessionZMQ,
runpath: OMCPath,
modelname: str,
modelname: Optional[str] = None,
timeout: Optional[float] = None,
) -> None:
if modelname is None:
raise ModelicaSystemError("Missing model name!")

self._session = session
self._runpath = runpath
self._model_name = modelname
Expand Down Expand Up @@ -321,60 +325,25 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
class ModelicaSystem:
def __init__(
self,
fileName: Optional[str | os.PathLike] = None,
modelName: Optional[str] = None,
lmodel: Optional[list[str | tuple[str, str]]] = None,
commandLineOptions: Optional[list[str]] = None,
variableFilter: Optional[str] = None,
customBuildDirectory: Optional[str | os.PathLike] = None,
omhome: Optional[str] = None,
omc_process: Optional[OMCProcess] = None,
build: bool = True,
) -> None:
"""Initialize, load and build a model.

The constructor loads the model file and builds it, generating exe and
xml files, etc.
"""Create a ModelicaSystem instance. To define the model use model() or convertFmu2Mo().

Args:
fileName: Path to the model file. Either absolute or relative to
the current working directory.
modelName: The name of the model class. If it is contained within
a package, "PackageName.ModelName" should be used.
lmodel: List of libraries to be loaded before the model itself is
loaded. Two formats are supported for the list elements:
lmodel=["Modelica"] for just the library name
and lmodel=[("Modelica","3.2.3")] for specifying both the name
and the version.
commandLineOptions: List with extra command line options as elements. The list elements are
provided to omc via setCommandLineOptions(). If set, the default values will be overridden.
To disable any command line options, use an empty list.
variableFilter: A regular expression. Only variables fully
matching the regexp will be stored in the result file.
Leaving it unspecified is equivalent to ".*".
customBuildDirectory: Path to a directory to be used for temporary
files like the model executable. If left unspecified, a tmp
directory will be created.
omhome: OPENMODELICAHOME value to be used when creating the OMC
session.
omhome: path to OMC to be used when creating the OMC session (see OMCSessionZMQ).
omc_process: definition of a (local) OMC process to be used. If
unspecified, a new local session will be created.
build: Boolean controlling whether or not the model should be
built when constructor is called. If False, the constructor
simply loads the model without compiling.

Examples:
mod = ModelicaSystem("ModelicaModel.mo", "modelName")
mod = ModelicaSystem("ModelicaModel.mo", "modelName", ["Modelica"])
mod = ModelicaSystem("ModelicaModel.mo", "modelName", [("Modelica","3.2.3"), "PowerSystems"])
"""

if fileName is None and modelName is None and not lmodel: # all None
raise ModelicaSystemError("Cannot create ModelicaSystem object without any arguments")

if modelName is None:
raise ModelicaSystemError("A modelname must be provided (argument modelName)!")

self._quantities: list[dict[str, Any]] = []
self._params: dict[str, str] = {} # even numerical values are stored as str
self._inputs: dict[str, list | None] = {}
Expand Down Expand Up @@ -408,44 +377,98 @@ def __init__(
for opt in commandLineOptions:
self.setCommandLineOptions(commandLineOptions=opt)

if lmodel is None:
lmodel = []

if not isinstance(lmodel, list):
raise ModelicaSystemError(f"Invalid input type for lmodel: {type(lmodel)} - list expected!")

self._lmodel = lmodel # may be needed if model is derived from other model
self._model_name = modelName # Model class name
if fileName is not None:
file_name = self._session.omcpath(fileName).resolve()
else:
file_name = None
self._file_name: Optional[OMCPath] = file_name # Model file/package name
self._simulated = False # True if the model has already been simulated
self._result_file: Optional[OMCPath] = None # for storing result file
self._variable_filter = variableFilter

if self._file_name is not None and not self._file_name.is_file(): # if file does not exist
raise IOError(f"{self._file_name} does not exist!")
self._work_dir: OMCPath = self.setWorkDirectory(customBuildDirectory)

# set default command Line Options for linearization as
# linearize() will use the simulation executable and runtime
# flag -l to perform linearization
self.setCommandLineOptions("--linearizationDumpLanguage=python")
self.setCommandLineOptions("--generateSymbolicLinearization")
self._model_name: Optional[str] = None
self._libraries: Optional[list[str | tuple[str, str]]] = None
self._file_name: Optional[OMCPath] = None
self._variable_filter: Optional[str] = None

self._work_dir: OMCPath = self.setWorkDirectory(customBuildDirectory)
def model(
self,
name: Optional[str] = None,
file: Optional[str | os.PathLike] = None,
libraries: Optional[list[str | tuple[str, str]]] = None,
variable_filter: Optional[str] = None,
build: bool = True,
) -> None:
"""Load and build a Modelica model.

This method loads the model file and builds it if requested (build == True).

Args:
file: Path to the model file. Either absolute or relative to
the current working directory.
name: The name of the model class. If it is contained within
a package, "PackageName.ModelName" should be used.
libraries: List of libraries to be loaded before the model itself is
loaded. Two formats are supported for the list elements:
lmodel=["Modelica"] for just the library name
and lmodel=[("Modelica","3.2.3")] for specifying both the name
and the version.
variable_filter: A regular expression. Only variables fully
matching the regexp will be stored in the result file.
Leaving it unspecified is equivalent to ".*".
build: Boolean controlling whether the model should be
built when constructor is called. If False, the constructor
simply loads the model without compiling.

Examples:
mod = ModelicaSystem()
# and then one of the lines below
mod.model(name="modelName", file="ModelicaModel.mo", )
mod.model(name="modelName", file="ModelicaModel.mo", libraries=["Modelica"])
mod.model(name="modelName", file="ModelicaModel.mo", libraries=[("Modelica","3.2.3"), "PowerSystems"])
"""

if self._model_name is not None:
raise ModelicaSystemError("Can not reuse this instance of ModelicaSystem "
f"defined for {repr(self._model_name)}!")

if name is None or not isinstance(name, str):
raise ModelicaSystemError("A model name must be provided!")

if libraries is None:
libraries = []

if not isinstance(libraries, list):
raise ModelicaSystemError(f"Invalid input type for libraries: {type(libraries)} - list expected!")

# set variables
self._model_name = name # Model class name
self._libraries = libraries # may be needed if model is derived from other model
self._variable_filter = variable_filter

if self._libraries:
self._loadLibrary(libraries=self._libraries)

self._file_name = None
if file is not None:
file_path = pathlib.Path(file)
# special handling for OMCProcessLocal - consider a relative path
if isinstance(self._session.omc_process, OMCProcessLocal) and not file_path.is_absolute():
file_path = pathlib.Path.cwd() / file_path
if not file_path.is_file():
raise IOError(f"Model file {file_path} does not exist!")

self._file_name = self.getWorkDirectory() / file_path.name
if (isinstance(self._session.omc_process, OMCProcessLocal)
and file_path.as_posix() == self._file_name.as_posix()):
pass
elif self._file_name.is_file():
raise IOError(f"Simulation model file {self._file_name} exist - not overwriting!")
else:
content = file_path.read_text(encoding='utf-8')
self._file_name.write_text(content)

if self._file_name is not None:
self._loadLibrary(lmodel=self._lmodel)
self._loadFile(fileName=self._file_name)

# allow directly loading models from MSL without fileName
elif fileName is None and modelName is not None:
self._loadLibrary(lmodel=self._lmodel)

if build:
self.buildModel(variableFilter)
self.buildModel(variable_filter)

def session(self) -> OMCSessionZMQ:
"""
Expand All @@ -465,9 +488,9 @@ def _loadFile(self, fileName: OMCPath):
self.sendExpression(f'loadFile("{fileName.as_posix()}")')

# for loading file/package, loading model and building model
def _loadLibrary(self, lmodel: list):
def _loadLibrary(self, libraries: list):
# load Modelica standard libraries or Modelica files if needed
for element in lmodel:
for element in libraries:
if element is not None:
if isinstance(element, str):
if element.endswith(".mo"):
Expand Down Expand Up @@ -1587,9 +1610,13 @@ def _createCSVData(self, csvfile: Optional[OMCPath] = None) -> OMCPath:

return csvfile

def convertMo2Fmu(self, version: str = "2.0", fmuType: str = "me_cs",
fileNamePrefix: str = "<default>",
includeResources: bool = True) -> str:
def convertMo2Fmu(
self,
version: str = "2.0",
fmuType: str = "me_cs",
fileNamePrefix: Optional[str] = None,
includeResources: bool = True,
) -> OMCPath:
"""Translate the model into a Functional Mockup Unit.

Args:
Expand All @@ -1606,24 +1633,29 @@ def convertMo2Fmu(self, version: str = "2.0", fmuType: str = "me_cs",
'/tmp/tmpmhfx9umo/CauerLowPassAnalog.fmu'
"""

if fileNamePrefix == "<default>":
fileNamePrefix = self._model_name
if includeResources:
includeResourcesStr = "true"
else:
includeResourcesStr = "false"
if fileNamePrefix is None:
if self._model_name is None:
fileNamePrefix = "<default>"
else:
fileNamePrefix = self._model_name
includeResourcesStr = "true" if includeResources else "false"

properties = (f'version="{version}", fmuType="{fmuType}", '
f'fileNamePrefix="{fileNamePrefix}", includeResources={includeResourcesStr}')
fmu = self._requestApi(apiName='buildModelFMU', entity=self._model_name, properties=properties)
fmu_path = self._session.omcpath(fmu)

# report proper error message
if not os.path.exists(fmu):
raise ModelicaSystemError(f"Missing FMU file: {fmu}")
if not fmu_path.is_file():
raise ModelicaSystemError(f"Missing FMU file: {fmu_path.as_posix()}")

return fmu
return fmu_path

# to convert FMU to Modelica model
def convertFmu2Mo(self, fmuName): # 20
def convertFmu2Mo(
self,
fmu: os.PathLike,
) -> OMCPath:
"""
In order to load FMU, at first it needs to be translated into Modelica model. This method is used to generate
Modelica model from the given FMU. It generates "fmuName_me_FMU.mo".
Expand All @@ -1632,13 +1664,24 @@ def convertFmu2Mo(self, fmuName): # 20
>>> convertFmu2Mo("c:/BouncingBall.Fmu")
"""

fileName = self._requestApi(apiName='importFMU', entity=fmuName)
fmu_path = self._session.omcpath(fmu)

if not fmu_path.is_file():
raise ModelicaSystemError(f"Missing FMU file: {fmu_path.as_posix()}")

filename = self._requestApi(apiName='importFMU', entity=fmu_path.as_posix())
filepath = self.getWorkDirectory() / filename

# report proper error message
if not os.path.exists(fileName):
raise ModelicaSystemError(f"Missing file {fileName}")
if not filepath.is_file():
raise ModelicaSystemError(f"Missing file {filepath.as_posix()}")

return fileName
self.model(
name=f"{fmu_path.stem}_me_FMU",
file=filepath,
)

return filepath

def optimize(self) -> dict[str, Any]:
"""Perform model-based optimization.
Expand Down Expand Up @@ -1903,15 +1946,17 @@ def __init__(
"""

self._mod = ModelicaSystem(
fileName=fileName,
modelName=modelName,
lmodel=lmodel,
commandLineOptions=commandLineOptions,
variableFilter=variableFilter,
customBuildDirectory=customBuildDirectory,
omhome=omhome,
omc_process=omc_process,
)
self._mod.model(
file=fileName,
name=modelName,
libraries=lmodel,
variable_filter=variableFilter,
)

self._model_name = modelName

Expand Down
13 changes: 10 additions & 3 deletions tests/test_FMIExport.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@


def test_CauerLowPassAnalog():
mod = OMPython.ModelicaSystem(modelName="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog",
lmodel=["Modelica"])
mod = OMPython.ModelicaSystem()
mod.model(
name="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog",
libraries=["Modelica"],
)
tmp = pathlib.Path(mod.getWorkDirectory())
try:
fmu = mod.convertMo2Fmu(fileNamePrefix="CauerLowPassAnalog")
Expand All @@ -16,7 +19,11 @@ def test_CauerLowPassAnalog():


def test_DrumBoiler():
mod = OMPython.ModelicaSystem(modelName="Modelica.Fluid.Examples.DrumBoiler.DrumBoiler", lmodel=["Modelica"])
mod = OMPython.ModelicaSystem()
mod.model(
name="Modelica.Fluid.Examples.DrumBoiler.DrumBoiler",
libraries=["Modelica"],
)
tmp = pathlib.Path(mod.getWorkDirectory())
try:
fmu = mod.convertMo2Fmu(fileNamePrefix="DrumBoiler")
Expand Down
Loading