Skip to content

Commit 4ae625b

Browse files
authored
(A014) [ModelExecution] update code for execution of a compiled model (#424)
[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() [test_ModelicaSystemCmd] update unittest [ModelExecutionData] include the original exception if reraised as ModelExecutionException [ModelicaSystem] fix usage of ModelicaSystemCmd
1 parent 2f8c6d2 commit 4ae625b

File tree

4 files changed

+211
-239
lines changed

4 files changed

+211
-239
lines changed

OMPython/ModelicaSystem.py

Lines changed: 101 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import numpy as np
2222

2323
from OMPython.OMCSession import (
24+
ModelExecutionData,
25+
ModelExecutionException,
26+
2427
OMCSessionException,
25-
OMCSessionRunData,
2628
OMCSession,
2729
OMCSessionLocal,
2830
OMCPath,
@@ -34,7 +36,7 @@
3436

3537
class ModelicaSystemError(Exception):
3638
"""
37-
Exception used in ModelicaSystem and ModelicaSystemCmd classes.
39+
Exception used in ModelicaSystem classes.
3840
"""
3941

4042

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

9193

92-
class ModelicaSystemCmd:
94+
class ModelExecutionCmd:
9395
"""
9496
All information about a compiled model executable. This should include data about all structured parameters, i.e.
9597
parameters which need a recompilation of the model. All non-structured parameters can be easily changed without
@@ -98,16 +100,22 @@ class ModelicaSystemCmd:
98100

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

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

112120
# dictionaries of command line arguments for the model executable
113121
self._args: dict[str, str | None] = {}
@@ -152,26 +160,26 @@ def override2str(
152160
elif isinstance(orval, numbers.Number):
153161
val_str = str(orval)
154162
else:
155-
raise ModelicaSystemError(f"Invalid value for override key {orkey}: {type(orval)}")
163+
raise ModelExecutionException(f"Invalid value for override key {orkey}: {type(orval)}")
156164

157165
return f"{orkey}={val_str}"
158166

159167
if not isinstance(key, str):
160-
raise ModelicaSystemError(f"Invalid argument key: {repr(key)} (type: {type(key)})")
168+
raise ModelExecutionException(f"Invalid argument key: {repr(key)} (type: {type(key)})")
161169
key = key.strip()
162170

163171
if isinstance(val, dict):
164172
if key != 'override':
165-
raise ModelicaSystemError("Dictionary input only possible for key 'override'!")
173+
raise ModelExecutionException("Dictionary input only possible for key 'override'!")
166174

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

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

176184
if okey in self._arg_override:
177185
if oval is None:
@@ -193,7 +201,7 @@ def override2str(
193201
elif isinstance(val, numbers.Number):
194202
argval = str(val)
195203
else:
196-
raise ModelicaSystemError(f"Invalid argument value for {repr(key)}: {repr(val)} (type: {type(val)})")
204+
raise ModelExecutionException(f"Invalid argument value for {repr(key)}: {repr(val)} (type: {type(val)})")
197205

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

234242
return cmdl
235243

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

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

252-
omc_run_data_updated = self._session.omc_run_data_update(
253-
omc_run_data=omc_run_data,
254-
)
255-
256-
return omc_run_data_updated
296+
return omc_run_data
257297

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

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

272314
args = [s for s in simflags.split(' ') if s]
273315
for arg in args:
274316
if arg[0] != '-':
275-
raise ModelicaSystemError(f"Invalid simulation flag: {arg}")
317+
raise ModelExecutionException(f"Invalid simulation flag: {arg}")
276318
arg = arg[1:]
277319
parts = arg.split('=')
278320
if len(parts) == 1:
@@ -284,12 +326,12 @@ def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | n
284326
for item in override.split(','):
285327
kv = item.split('=')
286328
if not 0 < len(kv) < 3:
287-
raise ModelicaSystemError(f"Invalid value for '-override': {override}")
329+
raise ModelExecutionException(f"Invalid value for '-override': {override}")
288330
if kv[0]:
289331
try:
290332
override_dict[kv[0]] = kv[1]
291333
except (KeyError, IndexError) as ex:
292-
raise ModelicaSystemError(f"Invalid value for '-override': {override}") from ex
334+
raise ModelExecutionException(f"Invalid value for '-override': {override}") from ex
293335

294336
simargs[parts[0]] = override_dict
295337

@@ -549,15 +591,17 @@ def buildModel(self, variableFilter: Optional[str] = None):
549591
logger.debug("OM model build result: %s", build_model_result)
550592

551593
# check if the executable exists ...
552-
om_cmd = ModelicaSystemCmd(
553-
session=self._session,
594+
om_cmd = ModelExecutionCmd(
554595
runpath=self.getWorkDirectory(),
555-
modelname=self._model_name,
596+
cmd_local=self._session.model_execution_local,
597+
cmd_windows=self._session.model_execution_windows,
598+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
599+
model_name=self._model_name,
556600
)
557601
# ... by running it - output help for command help
558602
om_cmd.arg_set(key="help", val="help")
559603
cmd_definition = om_cmd.definition()
560-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
604+
returncode = cmd_definition.run()
561605
if returncode != 0:
562606
raise ModelicaSystemError("Model executable not working!")
563607

@@ -1162,7 +1206,7 @@ def _parse_om_version(version: str) -> tuple[int, int, int]:
11621206

11631207
def _process_override_data(
11641208
self,
1165-
om_cmd: ModelicaSystemCmd,
1209+
om_cmd: ModelExecutionCmd,
11661210
override_file: OMCPath,
11671211
override_var: dict[str, str],
11681212
override_sim: dict[str, str],
@@ -1198,7 +1242,7 @@ def simulate_cmd(
11981242
result_file: OMCPath,
11991243
simflags: Optional[str] = None,
12001244
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
1201-
) -> ModelicaSystemCmd:
1245+
) -> ModelExecutionCmd:
12021246
"""
12031247
This method prepares the simulates model according to the simulation options. It returns an instance of
12041248
ModelicaSystemCmd which can be used to run the simulation.
@@ -1220,10 +1264,12 @@ def simulate_cmd(
12201264
An instance if ModelicaSystemCmd to run the requested simulation.
12211265
"""
12221266

1223-
om_cmd = ModelicaSystemCmd(
1224-
session=self._session,
1267+
om_cmd = ModelExecutionCmd(
12251268
runpath=self.getWorkDirectory(),
1226-
modelname=self._model_name,
1269+
cmd_local=self._session.model_execution_local,
1270+
cmd_windows=self._session.model_execution_windows,
1271+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
1272+
model_name=self._model_name,
12271273
)
12281274

12291275
# always define the result file to use
@@ -1312,7 +1358,7 @@ def simulate(
13121358
self._result_file.unlink()
13131359
# ... run simulation ...
13141360
cmd_definition = om_cmd.definition()
1315-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
1361+
returncode = cmd_definition.run()
13161362
# and check returncode *AND* resultfile
13171363
if returncode != 0 and self._result_file.is_file():
13181364
# check for an empty (=> 0B) result file which indicates a crash of the model executable
@@ -1915,10 +1961,12 @@ def linearize(
19151961
"use ModelicaSystem() to build the model first"
19161962
)
19171963

1918-
om_cmd = ModelicaSystemCmd(
1919-
session=self._session,
1964+
om_cmd = ModelExecutionCmd(
19201965
runpath=self.getWorkDirectory(),
1921-
modelname=self._model_name,
1966+
cmd_local=self._session.model_execution_local,
1967+
cmd_windows=self._session.model_execution_windows,
1968+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
1969+
model_name=self._model_name,
19221970
)
19231971

19241972
self._process_override_data(
@@ -1958,7 +2006,7 @@ def linearize(
19582006
linear_file.unlink(missing_ok=True)
19592007

19602008
cmd_definition = om_cmd.definition()
1961-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
2009+
returncode = cmd_definition.run()
19622010
if returncode != 0:
19632011
raise ModelicaSystemError(f"Linearize failed with return code: {returncode}")
19642012
if not linear_file.is_file():
@@ -2129,7 +2177,7 @@ def __init__(
21292177
self._parameters = {}
21302178

21312179
self._doe_def: Optional[dict[str, dict[str, Any]]] = None
2132-
self._doe_cmd: Optional[dict[str, OMCSessionRunData]] = None
2180+
self._doe_cmd: Optional[dict[str, ModelExecutionData]] = None
21332181

21342182
def get_session(self) -> OMCSession:
21352183
"""
@@ -2248,7 +2296,7 @@ def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]:
22482296
"""
22492297
return self._doe_def
22502298

2251-
def get_doe_command(self) -> Optional[dict[str, OMCSessionRunData]]:
2299+
def get_doe_command(self) -> Optional[dict[str, ModelExecutionData]]:
22522300
"""
22532301
Get the definitions of simulations commands to run for this DoE.
22542302
"""
@@ -2294,13 +2342,13 @@ def worker(worker_id, task_queue):
22942342
if cmd_definition is None:
22952343
raise ModelicaSystemError("Missing simulation definition!")
22962344

2297-
resultfile = cmd_definition.cmd_result_path
2345+
resultfile = cmd_definition.cmd_result_file
22982346
resultpath = self.get_session().omcpath(resultfile)
22992347

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

23022350
try:
2303-
returncode = self.get_session().run_model_executable(cmd_run_data=cmd_definition)
2351+
returncode = cmd_definition.run()
23042352
logger.info(f"[Worker {worker_id}] Simulation {resultpath.name} "
23052353
f"finished with return code: {returncode}")
23062354
except ModelicaSystemError as ex:

0 commit comments

Comments
 (0)