Skip to content

Commit 7419194

Browse files
committed
Merge branch 'ModelicaSystem_init' into v4.1.0-syntron
2 parents 8e10ff0 + e255d27 commit 7419194

7 files changed

Lines changed: 165 additions & 96 deletions

OMPython/ModelicaSystem.py

Lines changed: 86 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,12 @@ def __init__(
124124
self,
125125
session: OMCSessionZMQ,
126126
runpath: OMCPath,
127-
modelname: str,
127+
modelname: Optional[str] = None,
128128
timeout: Optional[float] = None,
129129
) -> None:
130+
if modelname is None:
131+
raise ModelicaSystemError("Missing model name!")
132+
130133
self._session = session
131134
self._runpath = runpath
132135
self._model_name = modelname
@@ -319,60 +322,25 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
319322
class ModelicaSystem:
320323
def __init__(
321324
self,
322-
fileName: Optional[str | os.PathLike] = None,
323-
modelName: Optional[str] = None,
324-
lmodel: Optional[list[str | tuple[str, str]]] = None,
325325
commandLineOptions: Optional[list[str]] = None,
326-
variableFilter: Optional[str] = None,
327326
customBuildDirectory: Optional[str | os.PathLike] = None,
328327
omhome: Optional[str] = None,
329328
omc_process: Optional[OMCProcess] = None,
330-
build: bool = True,
331329
) -> None:
332-
"""Initialize, load and build a model.
333-
334-
The constructor loads the model file and builds it, generating exe and
335-
xml files, etc.
330+
"""Create a ModelicaSystem instance. To define the model use model() or convertFmu2Mo().
336331
337332
Args:
338-
fileName: Path to the model file. Either absolute or relative to
339-
the current working directory.
340-
modelName: The name of the model class. If it is contained within
341-
a package, "PackageName.ModelName" should be used.
342-
lmodel: List of libraries to be loaded before the model itself is
343-
loaded. Two formats are supported for the list elements:
344-
lmodel=["Modelica"] for just the library name
345-
and lmodel=[("Modelica","3.2.3")] for specifying both the name
346-
and the version.
347333
commandLineOptions: List with extra command line options as elements. The list elements are
348334
provided to omc via setCommandLineOptions(). If set, the default values will be overridden.
349335
To disable any command line options, use an empty list.
350-
variableFilter: A regular expression. Only variables fully
351-
matching the regexp will be stored in the result file.
352-
Leaving it unspecified is equivalent to ".*".
353336
customBuildDirectory: Path to a directory to be used for temporary
354337
files like the model executable. If left unspecified, a tmp
355338
directory will be created.
356-
omhome: OPENMODELICAHOME value to be used when creating the OMC
357-
session.
339+
omhome: path to OMC to be used when creating the OMC session (see OMCSessionZMQ).
358340
omc_process: definition of a (local) OMC process to be used. If
359341
unspecified, a new local session will be created.
360-
build: Boolean controlling whether or not the model should be
361-
built when constructor is called. If False, the constructor
362-
simply loads the model without compiling.
363-
364-
Examples:
365-
mod = ModelicaSystem("ModelicaModel.mo", "modelName")
366-
mod = ModelicaSystem("ModelicaModel.mo", "modelName", ["Modelica"])
367-
mod = ModelicaSystem("ModelicaModel.mo", "modelName", [("Modelica","3.2.3"), "PowerSystems"])
368342
"""
369343

370-
if fileName is None and modelName is None and not lmodel: # all None
371-
raise ModelicaSystemError("Cannot create ModelicaSystem object without any arguments")
372-
373-
if modelName is None:
374-
raise ModelicaSystemError("A modelname must be provided (argument modelName)!")
375-
376344
self._quantities: list[dict[str, Any]] = []
377345
self._params: dict[str, str] = {} # even numerical values are stored as str
378346
self._inputs: dict[str, list | None] = {}
@@ -406,44 +374,82 @@ def __init__(
406374
for opt in commandLineOptions:
407375
self.setCommandLineOptions(commandLineOptions=opt)
408376

409-
if lmodel is None:
410-
lmodel = []
411-
412-
if not isinstance(lmodel, list):
413-
raise ModelicaSystemError(f"Invalid input type for lmodel: {type(lmodel)} - list expected!")
414-
415-
self._lmodel = lmodel # may be needed if model is derived from other model
416-
self._model_name = modelName # Model class name
417-
if fileName is not None:
418-
file_name = self._session.omcpath(fileName).resolve()
419-
else:
420-
file_name = None
421-
self._file_name: Optional[OMCPath] = file_name # Model file/package name
422377
self._simulated = False # True if the model has already been simulated
423378
self._result_file: Optional[OMCPath] = None # for storing result file
424-
self._variable_filter = variableFilter
425379

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

429-
# set default command Line Options for linearization as
430-
# linearize() will use the simulation executable and runtime
431-
# flag -l to perform linearization
432-
self.setCommandLineOptions("--linearizationDumpLanguage=python")
433-
self.setCommandLineOptions("--generateSymbolicLinearization")
382+
self._model_name: Optional[str] = None
383+
self._libraries: Optional[list[str | tuple[str, str]]] = None
384+
self._file_name: Optional[os.PathLike]
385+
self._variable_filter: Optional[str] = None
434386

435-
self._work_dir: OMCPath = self.setWorkDirectory(customBuildDirectory)
387+
def model(
388+
self,
389+
name: str,
390+
file: Optional[str | os.PathLike] = None,
391+
libraries: Optional[list[str | tuple[str, str]]] = None,
392+
variable_filter: Optional[str] = None,
393+
build: bool = True,
394+
) -> None:
395+
"""Load and build a Modelica model.
396+
397+
This method loads the model file and builds it if requested (build == True).
398+
399+
Args:
400+
file: Path to the model file. Either absolute or relative to
401+
the current working directory.
402+
name: The name of the model class. If it is contained within
403+
a package, "PackageName.ModelName" should be used.
404+
libraries: List of libraries to be loaded before the model itself is
405+
loaded. Two formats are supported for the list elements:
406+
lmodel=["Modelica"] for just the library name
407+
and lmodel=[("Modelica","3.2.3")] for specifying both the name
408+
and the version.
409+
variable_filter: A regular expression. Only variables fully
410+
matching the regexp will be stored in the result file.
411+
Leaving it unspecified is equivalent to ".*".
412+
build: Boolean controlling whether the model should be
413+
built when constructor is called. If False, the constructor
414+
simply loads the model without compiling.
436415
416+
Examples:
417+
mod = ModelicaSystem()
418+
# and then one of the lines below
419+
mod.model(name="modelName", file="ModelicaModel.mo", )
420+
mod.model(name="modelName", file="ModelicaModel.mo", libraries=["Modelica"])
421+
mod.model(name="modelName", file="ModelicaModel.mo", libraries=[("Modelica","3.2.3"), "PowerSystems"])
422+
"""
423+
424+
if self._model_name is not None:
425+
raise ModelicaSystemError("Can not reuse this instance of ModelicaSystem "
426+
f"defined for {repr(self._model_name)}!")
427+
428+
if not isinstance(name, str):
429+
raise ModelicaSystemError("A model name must be provided (argument name)!")
430+
431+
if libraries is None:
432+
libraries = []
433+
434+
if not isinstance(libraries, list):
435+
raise ModelicaSystemError(f"Invalid input type for libraries: {type(libraries)} - list expected!")
436+
437+
# set variables
438+
self._model_name = name # Model class name
439+
self._libraries = libraries # may be needed if model is derived from other model
440+
self._file_name = self.get_session().omcpath(file).resolve() if file is not None else None # Model file/package name
441+
self._variable_filter = variable_filter
442+
443+
if self._file_name is not None and not self._file_name.is_file(): # if file does not exist
444+
raise IOError(f"{self._file_name} does not exist!")
445+
446+
if self._libraries:
447+
self._loadLibrary(libraries=self._libraries)
437448
if self._file_name is not None:
438-
self._loadLibrary(lmodel=self._lmodel)
439449
self._loadFile(fileName=self._file_name)
440450

441-
# allow directly loading models from MSL without fileName
442-
elif fileName is None and modelName is not None:
443-
self._loadLibrary(lmodel=self._lmodel)
444-
445451
if build:
446-
self.buildModel(variableFilter)
452+
self.buildModel(variable_filter)
447453

448454
def get_session(self) -> OMCSessionZMQ:
449455
return self._session
@@ -460,9 +466,9 @@ def _loadFile(self, fileName: OMCPath):
460466
self.sendExpression(f'loadFile("{fileName.as_posix()}")')
461467

462468
# for loading file/package, loading model and building model
463-
def _loadLibrary(self, lmodel: list):
469+
def _loadLibrary(self, libraries: list):
464470
# load Modelica standard libraries or Modelica files if needed
465-
for element in lmodel:
471+
for element in libraries:
466472
if element is not None:
467473
if isinstance(element, str):
468474
if element.endswith(".mo"):
@@ -1580,9 +1586,13 @@ def _createCSVData(self, csvfile: Optional[OMCPath] = None) -> OMCPath:
15801586

15811587
return csvfile
15821588

1583-
def convertMo2Fmu(self, version: str = "2.0", fmuType: str = "me_cs",
1584-
fileNamePrefix: str = "<default>",
1585-
includeResources: bool = True) -> str:
1589+
def convertMo2Fmu(
1590+
self,
1591+
version: str = "2.0",
1592+
fmuType: str = "me_cs",
1593+
fileNamePrefix: Optional[str] = None,
1594+
includeResources: bool = True,
1595+
) -> str:
15861596
"""Translate the model into a Functional Mockup Unit.
15871597
15881598
Args:
@@ -1598,12 +1608,12 @@ def convertMo2Fmu(self, version: str = "2.0", fmuType: str = "me_cs",
15981608
'/tmp/tmpmhfx9umo/CauerLowPassAnalog.fmu'
15991609
"""
16001610

1601-
if fileNamePrefix == "<default>":
1611+
if fileNamePrefix is None:
1612+
if self._model_name is None:
1613+
raise ModelicaSystemError("Missing model name!")
16021614
fileNamePrefix = self._model_name
1603-
if includeResources:
1604-
includeResourcesStr = "true"
1605-
else:
1606-
includeResourcesStr = "false"
1615+
includeResourcesStr = "true" if includeResources else "false"
1616+
16071617
properties = (f'version="{version}", fmuType="{fmuType}", '
16081618
f'fileNamePrefix="{fileNamePrefix}", includeResources={includeResourcesStr}')
16091619
fmu = self._requestApi(apiName='buildModelFMU', entity=self._model_name, properties=properties)

tests/test_FMIExport.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66

77
def test_CauerLowPassAnalog():
8-
mod = OMPython.ModelicaSystem(modelName="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog",
9-
lmodel=["Modelica"])
8+
mod = OMPython.ModelicaSystem()
9+
mod.model(
10+
name="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog",
11+
libraries=["Modelica"],
12+
)
1013
tmp = pathlib.Path(mod.getWorkDirectory())
1114
try:
1215
fmu = mod.convertMo2Fmu(fileNamePrefix="CauerLowPassAnalog")
@@ -16,7 +19,11 @@ def test_CauerLowPassAnalog():
1619

1720

1821
def test_DrumBoiler():
19-
mod = OMPython.ModelicaSystem(modelName="Modelica.Fluid.Examples.DrumBoiler.DrumBoiler", lmodel=["Modelica"])
22+
mod = OMPython.ModelicaSystem()
23+
mod.model(
24+
name="Modelica.Fluid.Examples.DrumBoiler.DrumBoiler",
25+
libraries=["Modelica"],
26+
)
2027
tmp = pathlib.Path(mod.getWorkDirectory())
2128
try:
2229
fmu = mod.convertMo2Fmu(fileNamePrefix="DrumBoiler")

tests/test_ModelicaSystem.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,25 @@ def model_firstorder(tmp_path, model_firstorder_content):
3838
def test_ModelicaSystem_loop(model_firstorder):
3939
def worker():
4040
filePath = model_firstorder.as_posix()
41-
m = OMPython.ModelicaSystem(filePath, "M")
42-
m.simulate()
43-
m.convertMo2Fmu(fmuType="me")
41+
mod = OMPython.ModelicaSystem()
42+
mod.model(
43+
file=filePath,
44+
name="M",
45+
)
46+
mod.simulate()
47+
mod.convertMo2Fmu(fmuType="me")
4448
for _ in range(10):
4549
worker()
4650

4751

4852
def test_setParameters():
4953
omc = OMPython.OMCSessionZMQ()
5054
model_path = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels/"
51-
mod = OMPython.ModelicaSystem(model_path + "BouncingBall.mo", "BouncingBall")
55+
mod = OMPython.ModelicaSystem()
56+
mod.model(
57+
file=model_path + "BouncingBall.mo",
58+
name="BouncingBall",
59+
)
5260

5361
# method 1 (test depreciated variants)
5462
mod.setParameters("e=1.234")
@@ -78,7 +86,11 @@ def test_setParameters():
7886
def test_setSimulationOptions():
7987
omc = OMPython.OMCSessionZMQ()
8088
model_path = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels/"
81-
mod = OMPython.ModelicaSystem(fileName=model_path + "BouncingBall.mo", modelName="BouncingBall")
89+
mod = OMPython.ModelicaSystem()
90+
mod.model(
91+
file=model_path + "BouncingBall.mo",
92+
name="BouncingBall",
93+
)
8294

8395
# method 1
8496
mod.setSimulationOptions(stopTime=1.234)
@@ -111,7 +123,11 @@ def test_relative_path(model_firstorder):
111123
model_relative = str(model_file)
112124
assert "/" not in model_relative
113125

114-
mod = OMPython.ModelicaSystem(fileName=model_relative, modelName="M")
126+
mod = OMPython.ModelicaSystem()
127+
mod.model(
128+
file=model_relative,
129+
name="M",
130+
)
115131
assert float(mod.getParameters("a")[0]) == -1
116132
finally:
117133
model_file.unlink() # clean up the temporary file
@@ -121,11 +137,15 @@ def test_customBuildDirectory(tmp_path, model_firstorder):
121137
filePath = model_firstorder.as_posix()
122138
tmpdir = tmp_path / "tmpdir1"
123139
tmpdir.mkdir()
124-
m = OMPython.ModelicaSystem(filePath, "M", customBuildDirectory=tmpdir)
140+
mod = OMPython.ModelicaSystem(customBuildDirectory=tmpdir)
141+
mod.model(
142+
file=filePath,
143+
name="M",
144+
)
125145
assert pathlib.Path(m.getWorkDirectory()).resolve() == tmpdir.resolve()
126146
result_file = tmpdir / "a.mat"
127147
assert not result_file.exists()
128-
m.simulate(resultfile="a.mat")
148+
mod.simulate(resultfile="a.mat")
129149
assert result_file.is_file()
130150

131151

@@ -150,7 +170,11 @@ def test_getSolutions_docker(model_firstorder_content):
150170

151171
def test_getSolutions(model_firstorder):
152172
filePath = model_firstorder.as_posix()
153-
mod = OMPython.ModelicaSystem(filePath, "M")
173+
mod = OMPython.ModelicaSystem()
174+
mod.model(
175+
file=filePath,
176+
name="M",
177+
)
154178

155179
_run_getSolutions(mod)
156180

@@ -194,7 +218,11 @@ def test_getters(tmp_path):
194218
y = der(x);
195219
end M_getters;
196220
""")
197-
mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="M_getters")
221+
mod = OMPython.ModelicaSystem()
222+
mod.model(
223+
file=model_file.as_posix(),
224+
name="M_getters",
225+
)
198226

199227
q = mod.getQuantities()
200228
assert isinstance(q, list)
@@ -386,7 +414,11 @@ def test_simulate_inputs(tmp_path):
386414
y = x;
387415
end M_input;
388416
""")
389-
mod = OMPython.ModelicaSystem(fileName=model_file.as_posix(), modelName="M_input")
417+
mod = OMPython.ModelicaSystem()
418+
mod.model(
419+
file=model_file.as_posix(),
420+
name="M_input",
421+
)
390422

391423
simOptions = {"stopTime": 1.0}
392424
mod.setSimulationOptions(**simOptions)

tests/test_ModelicaSystemCmd.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ def model_firstorder(tmp_path):
1717

1818
@pytest.fixture
1919
def mscmd_firstorder(model_firstorder):
20-
mod = OMPython.ModelicaSystem(fileName=model_firstorder.as_posix(), modelName="M")
20+
mod = OMPython.ModelicaSystem()
21+
mod.model(
22+
file=model_firstorder.as_posix(),
23+
name="M",
24+
)
2125
mscmd = OMPython.ModelicaSystemCmd(
2226
session=mod.get_session(),
2327
runpath=mod.getWorkDirectory(),

0 commit comments

Comments
 (0)