Skip to content

Commit eec4e23

Browse files
syntronadeas31
andauthored
ModelicaSystem - use OMCPath (#322)
* [ModelicaSystem] fix rebase fallout 2 * [test_ModelicaSystem] fix test_customBuildDirectory() * [ModelicaSystem] fix blank lines (flake8) * [test_optimization] fix due to OMCPath usage * [test_FMIExport] fix due to OMCPath usage * [ModelicaSystem] improve definition of getSolution * allow different ways to define the path * [ModelicaSystem] use OMCPath for nearly all file system interactions remove pathlib - use OMCPath and (for type hints) os.PathLike * [ModelicaSystem] improve result file handling in simulate() --------- Co-authored-by: Adeel Asghar <[email protected]>
1 parent fce7b6b commit eec4e23

File tree

4 files changed

+58
-39
lines changed

4 files changed

+58
-39
lines changed

OMPython/ModelicaSystem.py

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,15 @@
3838
import numbers
3939
import numpy as np
4040
import os
41-
import pathlib
4241
import platform
4342
import re
4443
import subprocess
45-
import tempfile
4644
import textwrap
4745
from typing import Optional, Any
4846
import warnings
4947
import xml.etree.ElementTree as ET
5048

51-
from OMPython.OMCSession import OMCSessionException, OMCSessionZMQ, OMCProcessLocal
49+
from OMPython.OMCSession import OMCSessionException, OMCSessionZMQ, OMCProcessLocal, OMCPath
5250

5351
# define logger using the current module name as ID
5452
logger = logging.getLogger(__name__)
@@ -114,8 +112,8 @@ def __getitem__(self, index: int):
114112
class ModelicaSystemCmd:
115113
"""A compiled model executable."""
116114

117-
def __init__(self, runpath: pathlib.Path, modelname: str, timeout: Optional[float] = None) -> None:
118-
self._runpath = pathlib.Path(runpath).resolve().absolute()
115+
def __init__(self, runpath: OMCPath, modelname: str, timeout: Optional[float] = None) -> None:
116+
self._runpath = runpath
119117
self._model_name = modelname
120118
self._timeout = timeout
121119

@@ -229,7 +227,7 @@ def args_set(
229227
for arg in args:
230228
self.arg_set(key=arg, val=args[arg])
231229

232-
def get_exe(self) -> pathlib.Path:
230+
def get_exe(self) -> OMCPath:
233231
"""Get the path to the compiled model executable."""
234232
if platform.system() == "Windows":
235233
path_exe = self._runpath / f"{self._model_name}.exe"
@@ -349,7 +347,7 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
349347
class ModelicaSystem:
350348
def __init__(
351349
self,
352-
fileName: Optional[str | os.PathLike | pathlib.Path] = None,
350+
fileName: Optional[str | os.PathLike] = None,
353351
modelName: Optional[str] = None,
354352
lmodel: Optional[list[str | tuple[str, str]]] = None,
355353
commandLineOptions: Optional[list[str]] = None,
@@ -446,15 +444,25 @@ def __init__(
446444

447445
self._lmodel = lmodel # may be needed if model is derived from other model
448446
self._model_name = modelName # Model class name
449-
self._file_name = pathlib.Path(fileName).resolve() if fileName is not None else None # Model file/package name
447+
if fileName is not None:
448+
file_name = self._getconn.omcpath(fileName).resolve()
449+
else:
450+
file_name = None
451+
self._file_name: Optional[OMCPath] = file_name # Model file/package name
450452
self._simulated = False # True if the model has already been simulated
451-
self._result_file: Optional[pathlib.Path] = None # for storing result file
453+
self._result_file: Optional[OMCPath] = None # for storing result file
452454
self._variable_filter = variableFilter
453455

454456
if self._file_name is not None and not self._file_name.is_file(): # if file does not exist
455457
raise IOError(f"{self._file_name} does not exist!")
456458

457-
self._work_dir: pathlib.Path = self.setWorkDirectory(customBuildDirectory)
459+
# set default command Line Options for linearization as
460+
# linearize() will use the simulation executable and runtime
461+
# flag -l to perform linearization
462+
self.setCommandLineOptions("--linearizationDumpLanguage=python")
463+
self.setCommandLineOptions("--generateSymbolicLinearization")
464+
465+
self._work_dir: OMCPath = self.setWorkDirectory(customBuildDirectory)
458466

459467
if self._file_name is not None:
460468
self._loadLibrary(lmodel=self._lmodel)
@@ -474,7 +482,7 @@ def setCommandLineOptions(self, commandLineOptions: str):
474482
exp = f'setCommandLineOptions("{commandLineOptions}")'
475483
self.sendExpression(exp)
476484

477-
def _loadFile(self, fileName: pathlib.Path):
485+
def _loadFile(self, fileName: OMCPath):
478486
# load file
479487
self.sendExpression(f'loadFile("{fileName.as_posix()}")')
480488

@@ -502,17 +510,17 @@ def _loadLibrary(self, lmodel: list):
502510
'1)["Modelica"]\n'
503511
'2)[("Modelica","3.2.3"), "PowerSystems"]\n')
504512

505-
def setWorkDirectory(self, customBuildDirectory: Optional[str | os.PathLike] = None) -> pathlib.Path:
513+
def setWorkDirectory(self, customBuildDirectory: Optional[str | os.PathLike] = None) -> OMCPath:
506514
"""
507515
Define the work directory for the ModelicaSystem / OpenModelica session. The model is build within this
508516
directory. If no directory is defined a unique temporary directory is created.
509517
"""
510518
if customBuildDirectory is not None:
511-
workdir = pathlib.Path(customBuildDirectory).absolute()
519+
workdir = self._getconn.omcpath(customBuildDirectory).absolute()
512520
if not workdir.is_dir():
513521
raise IOError(f"Provided work directory does not exists: {customBuildDirectory}!")
514522
else:
515-
workdir = pathlib.Path(tempfile.mkdtemp()).absolute()
523+
workdir = self._getconn.omcpath_tempdir().absolute()
516524
if not workdir.is_dir():
517525
raise IOError(f"{workdir} could not be created")
518526

@@ -525,7 +533,7 @@ def setWorkDirectory(self, customBuildDirectory: Optional[str | os.PathLike] = N
525533
# ... and also return the defined path
526534
return workdir
527535

528-
def getWorkDirectory(self) -> pathlib.Path:
536+
def getWorkDirectory(self) -> OMCPath:
529537
"""
530538
Return the defined working directory for this ModelicaSystem / OpenModelica session.
531539
"""
@@ -546,7 +554,7 @@ def buildModel(self, variableFilter: Optional[str] = None):
546554
buildModelResult = self._requestApi(apiName="buildModel", entity=self._model_name, properties=var_filter)
547555
logger.debug("OM model build result: %s", buildModelResult)
548556

549-
xml_file = pathlib.Path(buildModelResult[0]).parent / buildModelResult[1]
557+
xml_file = self._getconn.omcpath(buildModelResult[0]).parent / buildModelResult[1]
550558
self._xmlparse(xml_file=xml_file)
551559

552560
def sendExpression(self, expr: str, parsed: bool = True) -> Any:
@@ -578,7 +586,7 @@ def _requestApi(
578586

579587
return self.sendExpression(exp)
580588

581-
def _xmlparse(self, xml_file: pathlib.Path):
589+
def _xmlparse(self, xml_file: OMCPath):
582590
if not xml_file.is_file():
583591
raise ModelicaSystemError(f"XML file not generated: {xml_file}")
584592

@@ -998,7 +1006,7 @@ def getOptimizationOptions(self, names: Optional[str | list[str]] = None) -> dic
9981006

9991007
def simulate_cmd(
10001008
self,
1001-
result_file: pathlib.Path,
1009+
result_file: OMCPath,
10021010
simflags: Optional[str] = None,
10031011
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
10041012
timeout: Optional[float] = None,
@@ -1102,10 +1110,15 @@ def simulate(
11021110
if resultfile is None:
11031111
# default result file generated by OM
11041112
self._result_file = self.getWorkDirectory() / f"{self._model_name}_res.mat"
1105-
elif os.path.exists(resultfile):
1106-
self._result_file = pathlib.Path(resultfile)
1113+
elif isinstance(resultfile, OMCPath):
1114+
self._result_file = resultfile
11071115
else:
1108-
self._result_file = self.getWorkDirectory() / resultfile
1116+
self._result_file = self._getconn.omcpath(resultfile)
1117+
if not self._result_file.is_absolute():
1118+
self._result_file = self.getWorkDirectory() / resultfile
1119+
1120+
if not isinstance(self._result_file, OMCPath):
1121+
raise ModelicaSystemError(f"Invalid result file path: {self._result_file} - must be an OMCPath object!")
11091122

11101123
om_cmd = self.simulate_cmd(
11111124
result_file=self._result_file,
@@ -1124,15 +1137,19 @@ def simulate(
11241137
# check for an empty (=> 0B) result file which indicates a crash of the model executable
11251138
# see: https://github.com/OpenModelica/OMPython/issues/261
11261139
# https://github.com/OpenModelica/OpenModelica/issues/13829
1127-
if self._result_file.stat().st_size == 0:
1140+
if self._result_file.size() == 0:
11281141
self._result_file.unlink()
11291142
raise ModelicaSystemError("Empty result file - this indicates a crash of the model executable!")
11301143

11311144
logger.warning(f"Return code = {returncode} but result file exists!")
11321145

11331146
self._simulated = True
11341147

1135-
def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Optional[str] = None) -> tuple[str] | np.ndarray:
1148+
def getSolutions(
1149+
self,
1150+
varList: Optional[str | list[str]] = None,
1151+
resultfile: Optional[str | os.PathLike] = None,
1152+
) -> tuple[str] | np.ndarray:
11361153
"""Extract simulation results from a result data file.
11371154
11381155
Args:
@@ -1169,7 +1186,7 @@ def getSolutions(self, varList: Optional[str | list[str]] = None, resultfile: Op
11691186
raise ModelicaSystemError("No result file found. Run simulate() first.")
11701187
result_file = self._result_file
11711188
else:
1172-
result_file = pathlib.Path(resultfile)
1189+
result_file = self._getconn.omcpath(resultfile)
11731190

11741191
# check if the result file exits
11751192
if not result_file.is_file():
@@ -1461,7 +1478,7 @@ def setInputs(
14611478

14621479
return True
14631480

1464-
def _createCSVData(self, csvfile: Optional[pathlib.Path] = None) -> pathlib.Path:
1481+
def _createCSVData(self, csvfile: Optional[OMCPath] = None) -> OMCPath:
14651482
"""
14661483
Create a csv file with inputs for the simulation/optimization of the model. If csvfile is provided as argument,
14671484
this file is used; else a generic file name is created.
@@ -1628,7 +1645,6 @@ def linearize(
16281645
* `result = linearize(); A = result[0]` mostly just for backwards
16291646
compatibility, because linearize() used to return `[A, B, C, D]`.
16301647
"""
1631-
16321648
if len(self._quantities) == 0:
16331649
# if self._quantities has no content, the xml file was not parsed; see self._xmlparse()
16341650
raise ModelicaSystemError(
@@ -1642,15 +1658,15 @@ def linearize(
16421658
timeout=timeout,
16431659
)
16441660

1645-
overrideLinearFile = self.getWorkDirectory() / f'{self._model_name}_override_linear.txt'
1646-
1647-
with open(file=overrideLinearFile, mode="w", encoding="utf-8") as fh:
1648-
for key1, value1 in self._override_variables.items():
1649-
fh.write(f"{key1}={value1}\n")
1650-
for key2, value2 in self._linearization_options.items():
1651-
fh.write(f"{key2}={value2}\n")
1661+
override_content = (
1662+
"\n".join([f"{key}={value}" for key, value in self._override_variables.items()])
1663+
+ "\n".join([f"{key}={value}" for key, value in self._linearization_options.items()])
1664+
+ "\n"
1665+
)
1666+
override_file = self.getWorkDirectory() / f'{self._model_name}_override_linear.txt'
1667+
override_file.write_text(override_content)
16521668

1653-
om_cmd.arg_set(key="overrideFile", val=overrideLinearFile.as_posix())
1669+
om_cmd.arg_set(key="overrideFile", val=override_file.as_posix())
16541670

16551671
if self._inputs:
16561672
for key in self._inputs:
@@ -1678,7 +1694,7 @@ def linearize(
16781694
returncode = om_cmd.run()
16791695
if returncode != 0:
16801696
raise ModelicaSystemError(f"Linearize failed with return code: {returncode}")
1681-
if not linear_file.exists():
1697+
if not linear_file.is_file():
16821698
raise ModelicaSystemError(f"Linearization failed: {linear_file} not found!")
16831699

16841700
self._simulated = True

tests/test_FMIExport.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import OMPython
22
import shutil
33
import os
4+
import pathlib
45

56

67
def test_CauerLowPassAnalog():
78
mod = OMPython.ModelicaSystem(modelName="Modelica.Electrical.Analog.Examples.CauerLowPassAnalog",
89
lmodel=["Modelica"])
9-
tmp = mod.getWorkDirectory()
10+
tmp = pathlib.Path(mod.getWorkDirectory())
1011
try:
1112
fmu = mod.convertMo2Fmu(fileNamePrefix="CauerLowPassAnalog")
1213
assert os.path.exists(fmu)
@@ -16,7 +17,7 @@ def test_CauerLowPassAnalog():
1617

1718
def test_DrumBoiler():
1819
mod = OMPython.ModelicaSystem(modelName="Modelica.Fluid.Examples.DrumBoiler.DrumBoiler", lmodel=["Modelica"])
19-
tmp = mod.getWorkDirectory()
20+
tmp = pathlib.Path(mod.getWorkDirectory())
2021
try:
2122
fmu = mod.convertMo2Fmu(fileNamePrefix="DrumBoiler")
2223
assert os.path.exists(fmu)

tests/test_ModelicaSystem.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def test_customBuildDirectory(tmp_path, model_firstorder):
105105
tmpdir = tmp_path / "tmpdir1"
106106
tmpdir.mkdir()
107107
m = OMPython.ModelicaSystem(filePath, "M", customBuildDirectory=tmpdir)
108-
assert m.getWorkDirectory().resolve() == tmpdir.resolve()
108+
assert pathlib.Path(m.getWorkDirectory()).resolve() == tmpdir.resolve()
109109
result_file = tmpdir / "a.mat"
110110
assert not result_file.exists()
111111
m.simulate(resultfile="a.mat")

tests/test_optimization.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def test_optimization_example(tmp_path):
4747

4848
r = mod.optimize()
4949
# it is necessary to specify resultfile, otherwise it wouldn't find it.
50-
time, f, v = mod.getSolutions(["time", "f", "v"], resultfile=r["resultFile"])
50+
resultfile_str = r["resultFile"]
51+
resultfile_omcpath = mod._getconn.omcpath(resultfile_str)
52+
time, f, v = mod.getSolutions(["time", "f", "v"], resultfile=resultfile_omcpath.as_posix())
5153
assert np.isclose(f[0], 10)
5254
assert np.isclose(f[-1], -10)
5355

0 commit comments

Comments
 (0)