Skip to content

Commit ed74d54

Browse files
committed
[ModelExecution*] create classes to handle model execution
* rename ModelicaSystemCmd => ModelExecutionCmd * rename OMCSessionRunData => ModelExecutionData * create class ModelExecutionException * move some code: * OMCSession.omc_run_data_update() => merge into ModelExecutionCmd.define() * OMCSession.run_model_executable() => ModelExecutionData.run()
1 parent 76b73e5 commit ed74d54

3 files changed

Lines changed: 203 additions & 222 deletions

File tree

OMPython/ModelicaSystem.py

Lines changed: 100 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import re
2323

2424
from OMPython.OMCSession import (
25+
ModelExecutionData,
26+
ModelExecutionException,
27+
2528
OMCSessionException,
26-
OMCSessionRunData,
2729
OMCSession,
2830
OMCSessionLocal,
2931
OMCPath,
@@ -35,7 +37,7 @@
3537

3638
class ModelicaSystemError(Exception):
3739
"""
38-
Exception used in ModelicaSystem and ModelicaSystemCmd classes.
40+
Exception used in ModelicaSystem classes.
3941
"""
4042

4143

@@ -90,7 +92,7 @@ def __getitem__(self, index: int):
9092
return {0: self.A, 1: self.B, 2: self.C, 3: self.D}[index]
9193

9294

93-
class ModelicaSystemCmd:
95+
class ModelExecutionCmd:
9496
"""
9597
All information about a compiled model executable. This should include data about all structured parameters, i.e.
9698
parameters which need a recompilation of the model. All non-structured parameters can be easily changed without
@@ -99,16 +101,22 @@ class ModelicaSystemCmd:
99101

100102
def __init__(
101103
self,
102-
session: OMCSession,
103-
runpath: OMCPath,
104-
modelname: Optional[str] = None,
104+
runpath: os.PathLike,
105+
cmd_prefix: list[str],
106+
cmd_local: bool = False,
107+
cmd_windows: bool = False,
108+
timeout: float = 10.0,
109+
model_name: Optional[str] = None,
105110
) -> None:
106-
if modelname is None:
107-
raise ModelicaSystemError("Missing model name!")
111+
if model_name is None:
112+
raise ModelExecutionException("Missing model name!")
108113

109-
self._session = session
110-
self._runpath = runpath
111-
self._model_name = modelname
114+
self._cmd_local = cmd_local
115+
self._cmd_windows = cmd_windows
116+
self._cmd_prefix = cmd_prefix
117+
self._runpath = pathlib.PurePosixPath(runpath)
118+
self._model_name = model_name
119+
self._timeout = timeout
112120

113121
# dictionaries of command line arguments for the model executable
114122
self._args: dict[str, str | None] = {}
@@ -153,26 +161,26 @@ def override2str(
153161
elif isinstance(oval, numbers.Number):
154162
oval_str = str(oval)
155163
else:
156-
raise ModelicaSystemError(f"Invalid value for override key {okey}: {type(oval)}")
164+
raise ModelExecutionException(f"Invalid value for override key {okey}: {type(oval)}")
157165

158166
return f"{okey}={oval_str}"
159167

160168
if not isinstance(key, str):
161-
raise ModelicaSystemError(f"Invalid argument key: {repr(key)} (type: {type(key)})")
169+
raise ModelExecutionException(f"Invalid argument key: {repr(key)} (type: {type(key)})")
162170
key = key.strip()
163171

164172
if isinstance(val, dict):
165173
if key != 'override':
166-
raise ModelicaSystemError("Dictionary input only possible for key 'override'!")
174+
raise ModelExecutionException("Dictionary input only possible for key 'override'!")
167175

168176
for okey, oval in val.items():
169177
if not isinstance(okey, str):
170-
raise ModelicaSystemError("Invalid key for argument 'override': "
171-
f"{repr(okey)} (type: {type(okey)})")
178+
raise ModelExecutionException("Invalid key for argument 'override': "
179+
f"{repr(okey)} (type: {type(okey)})")
172180

173181
if not isinstance(oval, (str, bool, numbers.Number, type(None))):
174-
raise ModelicaSystemError(f"Invalid input for 'override'.{repr(okey)}: "
175-
f"{repr(oval)} (type: {type(oval)})")
182+
raise ModelExecutionException(f"Invalid input for 'override'.{repr(okey)}: "
183+
f"{repr(oval)} (type: {type(oval)})")
176184

177185
if okey in self._arg_override:
178186
if oval is None:
@@ -194,7 +202,7 @@ def override2str(
194202
elif isinstance(val, numbers.Number):
195203
argval = str(val)
196204
else:
197-
raise ModelicaSystemError(f"Invalid argument value for {repr(key)}: {repr(val)} (type: {type(val)})")
205+
raise ModelExecutionException(f"Invalid argument value for {repr(key)}: {repr(val)} (type: {type(val)})")
198206

199207
if key in self._args:
200208
logger.warning(f"Override model executable argument: {repr(key)} = {repr(argval)} "
@@ -234,7 +242,7 @@ def get_cmd_args(self) -> list[str]:
234242

235243
return cmdl
236244

237-
def definition(self) -> OMCSessionRunData:
245+
def definition(self) -> ModelExecutionData:
238246
"""
239247
Define all needed data to run the model executable. The data is stored in an OMCSessionRunData object.
240248
"""
@@ -243,18 +251,50 @@ def definition(self) -> OMCSessionRunData:
243251
if not isinstance(result_file, str):
244252
result_file = (self._runpath / f"{self._model_name}.mat").as_posix()
245253

246-
omc_run_data = OMCSessionRunData(
247-
cmd_path=self._runpath.as_posix(),
254+
# as this is the local implementation, pathlib.Path can be used
255+
cmd_path = self._runpath
256+
257+
cmd_library_path = None
258+
if self._cmd_local and self._cmd_windows:
259+
cmd_library_path = ""
260+
261+
# set the process environment from the generated .bat file in windows which should have all the dependencies
262+
# for this pathlib.PurePosixPath() must be converted to a pathlib.Path() object, i.e. WindowsPath
263+
path_bat = pathlib.Path(cmd_path) / f"{self._model_name}.bat"
264+
if not path_bat.is_file():
265+
raise ModelExecutionException("Batch file (*.bat) does not exist " + str(path_bat))
266+
267+
content = path_bat.read_text(encoding='utf-8')
268+
for line in content.splitlines():
269+
match = re.match(pattern=r"^SET PATH=([^%]*)", string=line, flags=re.IGNORECASE)
270+
if match:
271+
cmd_library_path = match.group(1).strip(';') # Remove any trailing semicolons
272+
my_env = os.environ.copy()
273+
my_env["PATH"] = cmd_library_path + os.pathsep + my_env["PATH"]
274+
275+
cmd_model_executable = cmd_path / f"{self._model_name}.exe"
276+
else:
277+
# for Linux the paths to the needed libraries should be included in the executable (using rpath)
278+
cmd_model_executable = cmd_path / self._model_name
279+
280+
# define local(!) working directory
281+
cmd_cwd_local = None
282+
if self._cmd_local:
283+
cmd_cwd_local = cmd_path.as_posix()
284+
285+
omc_run_data = ModelExecutionData(
286+
cmd_path=cmd_path.as_posix(),
248287
cmd_model_name=self._model_name,
249288
cmd_args=self.get_cmd_args(),
250-
cmd_result_path=result_file,
289+
cmd_result_file=result_file,
290+
cmd_prefix=self._cmd_prefix,
291+
cmd_library_path=cmd_library_path,
292+
cmd_model_executable=cmd_model_executable.as_posix(),
293+
cmd_cwd_local=cmd_cwd_local,
294+
cmd_timeout=self._timeout,
251295
)
252296

253-
omc_run_data_updated = self._session.omc_run_data_update(
254-
omc_run_data=omc_run_data,
255-
)
256-
257-
return omc_run_data_updated
297+
return omc_run_data
258298

259299
@staticmethod
260300
def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | numbers.Number]]:
@@ -263,15 +303,19 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
263303
264304
The return data can be used as input for self.args_set().
265305
"""
266-
warnings.warn("The argument 'simflags' is depreciated and will be removed in future versions; "
267-
"please use 'simargs' instead", DeprecationWarning, stacklevel=2)
306+
warnings.warn(
307+
message="The argument 'simflags' is depreciated and will be removed in future versions; "
308+
"please use 'simargs' instead",
309+
category=DeprecationWarning,
310+
stacklevel=2,
311+
)
268312

269313
simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {}
270314

271315
args = [s for s in simflags.split(' ') if s]
272316
for arg in args:
273317
if arg[0] != '-':
274-
raise ModelicaSystemError(f"Invalid simulation flag: {arg}")
318+
raise ModelExecutionException(f"Invalid simulation flag: {arg}")
275319
arg = arg[1:]
276320
parts = arg.split('=')
277321
if len(parts) == 1:
@@ -283,12 +327,12 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
283327
for item in override.split(','):
284328
kv = item.split('=')
285329
if not 0 < len(kv) < 3:
286-
raise ModelicaSystemError(f"Invalid value for '-override': {override}")
330+
raise ModelExecutionException(f"Invalid value for '-override': {override}")
287331
if kv[0]:
288332
try:
289333
override_dict[kv[0]] = kv[1]
290334
except (KeyError, IndexError) as ex:
291-
raise ModelicaSystemError(f"Invalid value for '-override': {override}") from ex
335+
raise ModelExecutionException(f"Invalid value for '-override': {override}") from ex
292336

293337
simargs[parts[0]] = override_dict
294338

@@ -542,15 +586,17 @@ def buildModel(self, variableFilter: Optional[str] = None):
542586
logger.debug("OM model build result: %s", build_model_result)
543587

544588
# check if the executable exists ...
545-
om_cmd = ModelicaSystemCmd(
546-
session=self._session,
589+
om_cmd = ModelExecutionCmd(
547590
runpath=self.getWorkDirectory(),
548-
modelname=self._model_name,
591+
cmd_local=self._session.model_execution_local,
592+
cmd_windows=self._session.model_execution_windows,
593+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
594+
model_name=self._model_name,
549595
)
550596
# ... by running it - output help for command help
551597
om_cmd.arg_set(key="help", val="help")
552598
cmd_definition = om_cmd.definition()
553-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
599+
returncode = cmd_definition.run()
554600
if returncode != 0:
555601
raise ModelicaSystemError("Model executable not working!")
556602

@@ -1035,7 +1081,7 @@ def simulate_cmd(
10351081
result_file: OMCPath,
10361082
simflags: Optional[str] = None,
10371083
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
1038-
) -> ModelicaSystemCmd:
1084+
) -> ModelExecutionCmd:
10391085
"""
10401086
This method prepares the simulates model according to the simulation options. It returns an instance of
10411087
ModelicaSystemCmd which can be used to run the simulation.
@@ -1057,10 +1103,12 @@ def simulate_cmd(
10571103
An instance if ModelicaSystemCmd to run the requested simulation.
10581104
"""
10591105

1060-
om_cmd = ModelicaSystemCmd(
1061-
session=self._session,
1106+
om_cmd = ModelExecutionCmd(
10621107
runpath=self.getWorkDirectory(),
1063-
modelname=self._model_name,
1108+
cmd_local=self._session.model_execution_local,
1109+
cmd_windows=self._session.model_execution_windows,
1110+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
1111+
model_name=self._model_name,
10641112
)
10651113

10661114
# always define the result file to use
@@ -1166,7 +1214,7 @@ def simulate(
11661214
self._result_file.unlink()
11671215
# ... run simulation ...
11681216
cmd_definition = om_cmd.definition()
1169-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
1217+
returncode = cmd_definition.run()
11701218
# and check returncode *AND* resultfile
11711219
if returncode != 0 and self._result_file.is_file():
11721220
# check for an empty (=> 0B) result file which indicates a crash of the model executable
@@ -1769,10 +1817,12 @@ def linearize(
17691817
"use ModelicaSystem() to build the model first"
17701818
)
17711819

1772-
om_cmd = ModelicaSystemCmd(
1773-
session=self._session,
1820+
om_cmd = ModelExecutionCmd(
17741821
runpath=self.getWorkDirectory(),
1775-
modelname=self._model_name,
1822+
cmd_local=self._session.model_execution_local,
1823+
cmd_windows=self._session.model_execution_windows,
1824+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
1825+
model_name=self._model_name,
17761826
)
17771827

17781828
# See comment in simulate_cmd regarding override file and OM version
@@ -1819,7 +1869,7 @@ def linearize(
18191869
linear_file.unlink(missing_ok=True)
18201870

18211871
cmd_definition = om_cmd.definition()
1822-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
1872+
returncode = cmd_definition.run()
18231873
if returncode != 0:
18241874
raise ModelicaSystemError(f"Linearize failed with return code: {returncode}")
18251875
if not linear_file.is_file():
@@ -2005,7 +2055,7 @@ def __init__(
20052055
self._parameters = {}
20062056

20072057
self._doe_def: Optional[dict[str, dict[str, Any]]] = None
2008-
self._doe_cmd: Optional[dict[str, OMCSessionRunData]] = None
2058+
self._doe_cmd: Optional[dict[str, ModelExecutionData]] = None
20092059

20102060
def get_session(self) -> OMCSession:
20112061
"""
@@ -2124,7 +2174,7 @@ def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]:
21242174
"""
21252175
return self._doe_def
21262176

2127-
def get_doe_command(self) -> Optional[dict[str, OMCSessionRunData]]:
2177+
def get_doe_command(self) -> Optional[dict[str, ModelExecutionData]]:
21282178
"""
21292179
Get the definitions of simulations commands to run for this DoE.
21302180
"""
@@ -2170,13 +2220,13 @@ def worker(worker_id, task_queue):
21702220
if cmd_definition is None:
21712221
raise ModelicaSystemError("Missing simulation definition!")
21722222

2173-
resultfile = cmd_definition.cmd_result_path
2223+
resultfile = cmd_definition.cmd_result_file
21742224
resultpath = self.get_session().omcpath(resultfile)
21752225

21762226
logger.info(f"[Worker {worker_id}] Performing task: {resultpath.name}")
21772227

21782228
try:
2179-
returncode = self.get_session().run_model_executable(cmd_run_data=cmd_definition)
2229+
returncode = cmd_definition.run()
21802230
logger.info(f"[Worker {worker_id}] Simulation {resultpath.name} "
21812231
f"finished with return code: {returncode}")
21822232
except ModelicaSystemError as ex:

0 commit comments

Comments
 (0)