Skip to content

Commit 2c728a1

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 44aff0d commit 2c728a1

3 files changed

Lines changed: 204 additions & 235 deletions

File tree

OMPython/ModelicaSystem.py

Lines changed: 100 additions & 52 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(oval, numbers.Number):
153161
oval_str = str(oval)
154162
else:
155-
raise ModelicaSystemError(f"Invalid value for override key {okey}: {type(oval)}")
163+
raise ModelExecutionException(f"Invalid value for override key {okey}: {type(oval)}")
156164

157165
return f"{okey}={oval_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

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

546588
# check if the executable exists ...
547-
om_cmd = ModelicaSystemCmd(
548-
session=self._session,
589+
om_cmd = ModelExecutionCmd(
549590
runpath=self.getWorkDirectory(),
550-
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,
551595
)
552596
# ... by running it - output help for command help
553597
om_cmd.arg_set(key="help", val="help")
554598
cmd_definition = om_cmd.definition()
555-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
599+
returncode = cmd_definition.run()
556600
if returncode != 0:
557601
raise ModelicaSystemError("Model executable not working!")
558602

@@ -1069,7 +1113,7 @@ def simulate_cmd(
10691113
result_file: OMCPath,
10701114
simflags: Optional[str] = None,
10711115
simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None,
1072-
) -> ModelicaSystemCmd:
1116+
) -> ModelExecutionCmd:
10731117
"""
10741118
This method prepares the simulates model according to the simulation options. It returns an instance of
10751119
ModelicaSystemCmd which can be used to run the simulation.
@@ -1091,10 +1135,12 @@ def simulate_cmd(
10911135
An instance if ModelicaSystemCmd to run the requested simulation.
10921136
"""
10931137

1094-
om_cmd = ModelicaSystemCmd(
1095-
session=self._session,
1138+
om_cmd = ModelExecutionCmd(
10961139
runpath=self.getWorkDirectory(),
1097-
modelname=self._model_name,
1140+
cmd_local=self._session.model_execution_local,
1141+
cmd_windows=self._session.model_execution_windows,
1142+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
1143+
model_name=self._model_name,
10981144
)
10991145

11001146
# always define the result file to use
@@ -1183,7 +1229,7 @@ def simulate(
11831229
self._result_file.unlink()
11841230
# ... run simulation ...
11851231
cmd_definition = om_cmd.definition()
1186-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
1232+
returncode = cmd_definition.run()
11871233
# and check returncode *AND* resultfile
11881234
if returncode != 0 and self._result_file.is_file():
11891235
# check for an empty (=> 0B) result file which indicates a crash of the model executable
@@ -1786,10 +1832,12 @@ def linearize(
17861832
"use ModelicaSystem() to build the model first"
17871833
)
17881834

1789-
om_cmd = ModelicaSystemCmd(
1790-
session=self._session,
1835+
om_cmd = ModelExecutionCmd(
17911836
runpath=self.getWorkDirectory(),
1792-
modelname=self._model_name,
1837+
cmd_local=self._session.model_execution_local,
1838+
cmd_windows=self._session.model_execution_windows,
1839+
cmd_prefix=self._session.model_execution_prefix(cwd=self.getWorkDirectory()),
1840+
model_name=self._model_name,
17931841
)
17941842

17951843
self._process_override_data(
@@ -1829,7 +1877,7 @@ def linearize(
18291877
linear_file.unlink(missing_ok=True)
18301878

18311879
cmd_definition = om_cmd.definition()
1832-
returncode = self._session.run_model_executable(cmd_run_data=cmd_definition)
1880+
returncode = cmd_definition.run()
18331881
if returncode != 0:
18341882
raise ModelicaSystemError(f"Linearize failed with return code: {returncode}")
18351883
if not linear_file.is_file():
@@ -2015,7 +2063,7 @@ def __init__(
20152063
self._parameters = {}
20162064

20172065
self._doe_def: Optional[dict[str, dict[str, Any]]] = None
2018-
self._doe_cmd: Optional[dict[str, OMCSessionRunData]] = None
2066+
self._doe_cmd: Optional[dict[str, ModelExecutionData]] = None
20192067

20202068
def get_session(self) -> OMCSession:
20212069
"""
@@ -2134,7 +2182,7 @@ def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]:
21342182
"""
21352183
return self._doe_def
21362184

2137-
def get_doe_command(self) -> Optional[dict[str, OMCSessionRunData]]:
2185+
def get_doe_command(self) -> Optional[dict[str, ModelExecutionData]]:
21382186
"""
21392187
Get the definitions of simulations commands to run for this DoE.
21402188
"""
@@ -2180,13 +2228,13 @@ def worker(worker_id, task_queue):
21802228
if cmd_definition is None:
21812229
raise ModelicaSystemError("Missing simulation definition!")
21822230

2183-
resultfile = cmd_definition.cmd_result_path
2231+
resultfile = cmd_definition.cmd_result_file
21842232
resultpath = self.get_session().omcpath(resultfile)
21852233

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

21882236
try:
2189-
returncode = self.get_session().run_model_executable(cmd_run_data=cmd_definition)
2237+
returncode = cmd_definition.run()
21902238
logger.info(f"[Worker {worker_id}] Simulation {resultpath.name} "
21912239
f"finished with return code: {returncode}")
21922240
except ModelicaSystemError as ex:

0 commit comments

Comments
 (0)