|
21 | 21 |
|
22 | 22 | import numpy as np |
23 | 23 |
|
24 | | -from OMPython.OMCSession import ( |
| 24 | +from OMPython.model_execution import ( |
| 25 | + ModelExecutionCmd, |
25 | 26 | ModelExecutionData, |
26 | 27 | ModelExecutionException, |
27 | | - |
| 28 | +) |
| 29 | +from OMPython.OMCSession import ( |
28 | 30 | OMSessionException, |
29 | 31 | OMCSessionLocal, |
30 | 32 |
|
@@ -95,253 +97,6 @@ def __getitem__(self, index: int): |
95 | 97 | return {0: self.A, 1: self.B, 2: self.C, 3: self.D}[index] |
96 | 98 |
|
97 | 99 |
|
98 | | -class ModelExecutionCmd: |
99 | | - """ |
100 | | - All information about a compiled model executable. This should include data about all structured parameters, i.e. |
101 | | - parameters which need a recompilation of the model. All non-structured parameters can be easily changed without |
102 | | - the need for recompilation. |
103 | | - """ |
104 | | - |
105 | | - def __init__( |
106 | | - self, |
107 | | - runpath: os.PathLike, |
108 | | - cmd_prefix: list[str], |
109 | | - cmd_local: bool = False, |
110 | | - cmd_windows: bool = False, |
111 | | - timeout: float = 10.0, |
112 | | - model_name: Optional[str] = None, |
113 | | - ) -> None: |
114 | | - if model_name is None: |
115 | | - raise ModelExecutionException("Missing model name!") |
116 | | - |
117 | | - self._cmd_local = cmd_local |
118 | | - self._cmd_windows = cmd_windows |
119 | | - self._cmd_prefix = cmd_prefix |
120 | | - self._runpath = pathlib.PurePosixPath(runpath) |
121 | | - self._model_name = model_name |
122 | | - self._timeout = timeout |
123 | | - |
124 | | - # dictionaries of command line arguments for the model executable |
125 | | - self._args: dict[str, str | None] = {} |
126 | | - # 'override' argument needs special handling, as it is a dict on its own saved as dict elements following the |
127 | | - # structure: 'key' => 'key=value' |
128 | | - self._arg_override: dict[str, str] = {} |
129 | | - |
130 | | - def arg_set( |
131 | | - self, |
132 | | - key: str, |
133 | | - val: Optional[str | dict[str, Any] | numbers.Number] = None, |
134 | | - ) -> None: |
135 | | - """ |
136 | | - Set one argument for the executable model. |
137 | | -
|
138 | | - Args: |
139 | | - key: identifier / argument name to be used for the call of the model executable. |
140 | | - val: value for the given key; None for no value and for key == 'override' a dictionary can be used which |
141 | | - indicates variables to override |
142 | | - """ |
143 | | - |
144 | | - def override2str( |
145 | | - orkey: str, |
146 | | - orval: str | bool | numbers.Number, |
147 | | - ) -> str: |
148 | | - """ |
149 | | - Convert a value for 'override' to a string taking into account differences between Modelica and Python. |
150 | | - """ |
151 | | - # check oval for any string representations of numbers (or bool) and convert these to Python representations |
152 | | - if isinstance(orval, str): |
153 | | - try: |
154 | | - val_evaluated = ast.literal_eval(orval) |
155 | | - if isinstance(val_evaluated, (numbers.Number, bool)): |
156 | | - orval = val_evaluated |
157 | | - except (ValueError, SyntaxError): |
158 | | - pass |
159 | | - |
160 | | - if isinstance(orval, str): |
161 | | - val_str = orval.strip() |
162 | | - elif isinstance(orval, bool): |
163 | | - val_str = 'true' if orval else 'false' |
164 | | - elif isinstance(orval, numbers.Number): |
165 | | - val_str = str(orval) |
166 | | - else: |
167 | | - raise ModelExecutionException(f"Invalid value for override key {orkey}: {type(orval)}") |
168 | | - |
169 | | - return f"{orkey}={val_str}" |
170 | | - |
171 | | - if not isinstance(key, str): |
172 | | - raise ModelExecutionException(f"Invalid argument key: {repr(key)} (type: {type(key)})") |
173 | | - key = key.strip() |
174 | | - |
175 | | - if isinstance(val, dict): |
176 | | - if key != 'override': |
177 | | - raise ModelExecutionException("Dictionary input only possible for key 'override'!") |
178 | | - |
179 | | - for okey, oval in val.items(): |
180 | | - if not isinstance(okey, str): |
181 | | - raise ModelExecutionException("Invalid key for argument 'override': " |
182 | | - f"{repr(okey)} (type: {type(okey)})") |
183 | | - |
184 | | - if not isinstance(oval, (str, bool, numbers.Number, type(None))): |
185 | | - raise ModelExecutionException(f"Invalid input for 'override'.{repr(okey)}: " |
186 | | - f"{repr(oval)} (type: {type(oval)})") |
187 | | - |
188 | | - if okey in self._arg_override: |
189 | | - if oval is None: |
190 | | - logger.info(f"Remove model executable override argument: {repr(self._arg_override[okey])}") |
191 | | - del self._arg_override[okey] |
192 | | - continue |
193 | | - |
194 | | - logger.info(f"Update model executable override argument: {repr(okey)} = {repr(oval)} " |
195 | | - f"(was: {repr(self._arg_override[okey])})") |
196 | | - |
197 | | - if oval is not None: |
198 | | - self._arg_override[okey] = override2str(orkey=okey, orval=oval) |
199 | | - |
200 | | - argval = ','.join(sorted(self._arg_override.values())) |
201 | | - elif val is None: |
202 | | - argval = None |
203 | | - elif isinstance(val, str): |
204 | | - argval = val.strip() |
205 | | - elif isinstance(val, numbers.Number): |
206 | | - argval = str(val) |
207 | | - else: |
208 | | - raise ModelExecutionException(f"Invalid argument value for {repr(key)}: {repr(val)} (type: {type(val)})") |
209 | | - |
210 | | - if key in self._args: |
211 | | - logger.warning(f"Override model executable argument: {repr(key)} = {repr(argval)} " |
212 | | - f"(was: {repr(self._args[key])})") |
213 | | - self._args[key] = argval |
214 | | - |
215 | | - def arg_get(self, key: str) -> Optional[str | dict[str, str | bool | numbers.Number]]: |
216 | | - """ |
217 | | - Return the value for the given key |
218 | | - """ |
219 | | - if key in self._args: |
220 | | - return self._args[key] |
221 | | - |
222 | | - return None |
223 | | - |
224 | | - def args_set( |
225 | | - self, |
226 | | - args: dict[str, Optional[str | dict[str, Any] | numbers.Number]], |
227 | | - ) -> None: |
228 | | - """ |
229 | | - Define arguments for the model executable. |
230 | | - """ |
231 | | - for arg in args: |
232 | | - self.arg_set(key=arg, val=args[arg]) |
233 | | - |
234 | | - def get_cmd_args(self) -> list[str]: |
235 | | - """ |
236 | | - Get a list with the command arguments for the model executable. |
237 | | - """ |
238 | | - |
239 | | - cmdl = [] |
240 | | - for key in sorted(self._args): |
241 | | - if self._args[key] is None: |
242 | | - cmdl.append(f"-{key}") |
243 | | - else: |
244 | | - cmdl.append(f"-{key}={self._args[key]}") |
245 | | - |
246 | | - return cmdl |
247 | | - |
248 | | - def definition(self) -> ModelExecutionData: |
249 | | - """ |
250 | | - Define all needed data to run the model executable. The data is stored in an OMCSessionRunData object. |
251 | | - """ |
252 | | - # ensure that a result filename is provided |
253 | | - result_file = self.arg_get('r') |
254 | | - if not isinstance(result_file, str): |
255 | | - result_file = (self._runpath / f"{self._model_name}.mat").as_posix() |
256 | | - |
257 | | - # as this is the local implementation, pathlib.Path can be used |
258 | | - cmd_path = self._runpath |
259 | | - |
260 | | - cmd_library_path = None |
261 | | - if self._cmd_local and self._cmd_windows: |
262 | | - cmd_library_path = "" |
263 | | - |
264 | | - # set the process environment from the generated .bat file in windows which should have all the dependencies |
265 | | - # for this pathlib.PurePosixPath() must be converted to a pathlib.Path() object, i.e. WindowsPath |
266 | | - path_bat = pathlib.Path(cmd_path) / f"{self._model_name}.bat" |
267 | | - if not path_bat.is_file(): |
268 | | - raise ModelExecutionException("Batch file (*.bat) does not exist " + str(path_bat)) |
269 | | - |
270 | | - content = path_bat.read_text(encoding='utf-8') |
271 | | - for line in content.splitlines(): |
272 | | - match = re.match(pattern=r"^SET PATH=([^%]*)", string=line, flags=re.IGNORECASE) |
273 | | - if match: |
274 | | - cmd_library_path = match.group(1).strip(';') # Remove any trailing semicolons |
275 | | - my_env = os.environ.copy() |
276 | | - my_env["PATH"] = cmd_library_path + os.pathsep + my_env["PATH"] |
277 | | - |
278 | | - cmd_model_executable = cmd_path / f"{self._model_name}.exe" |
279 | | - else: |
280 | | - # for Linux the paths to the needed libraries should be included in the executable (using rpath) |
281 | | - cmd_model_executable = cmd_path / self._model_name |
282 | | - |
283 | | - # define local(!) working directory |
284 | | - cmd_cwd_local = None |
285 | | - if self._cmd_local: |
286 | | - cmd_cwd_local = cmd_path.as_posix() |
287 | | - |
288 | | - omc_run_data = ModelExecutionData( |
289 | | - cmd_path=cmd_path.as_posix(), |
290 | | - cmd_model_name=self._model_name, |
291 | | - cmd_args=self.get_cmd_args(), |
292 | | - cmd_result_file=result_file, |
293 | | - cmd_prefix=self._cmd_prefix, |
294 | | - cmd_library_path=cmd_library_path, |
295 | | - cmd_model_executable=cmd_model_executable.as_posix(), |
296 | | - cmd_cwd_local=cmd_cwd_local, |
297 | | - cmd_timeout=self._timeout, |
298 | | - ) |
299 | | - |
300 | | - return omc_run_data |
301 | | - |
302 | | - @staticmethod |
303 | | - def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | numbers.Number]]: |
304 | | - """ |
305 | | - Parse a simflag definition; this is deprecated! |
306 | | -
|
307 | | - The return data can be used as input for self.args_set(). |
308 | | - """ |
309 | | - warnings.warn( |
310 | | - message="The argument 'simflags' is depreciated and will be removed in future versions; " |
311 | | - "please use 'simargs' instead", |
312 | | - category=DeprecationWarning, |
313 | | - stacklevel=2, |
314 | | - ) |
315 | | - |
316 | | - simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {} |
317 | | - |
318 | | - args = [s for s in simflags.split(' ') if s] |
319 | | - for arg in args: |
320 | | - if arg[0] != '-': |
321 | | - raise ModelExecutionException(f"Invalid simulation flag: {arg}") |
322 | | - arg = arg[1:] |
323 | | - parts = arg.split('=') |
324 | | - if len(parts) == 1: |
325 | | - simargs[parts[0]] = None |
326 | | - elif parts[0] == 'override': |
327 | | - override = '='.join(parts[1:]) |
328 | | - |
329 | | - override_dict = {} |
330 | | - for item in override.split(','): |
331 | | - kv = item.split('=') |
332 | | - if not 0 < len(kv) < 3: |
333 | | - raise ModelExecutionException(f"Invalid value for '-override': {override}") |
334 | | - if kv[0]: |
335 | | - try: |
336 | | - override_dict[kv[0]] = kv[1] |
337 | | - except (KeyError, IndexError) as ex: |
338 | | - raise ModelExecutionException(f"Invalid value for '-override': {override}") from ex |
339 | | - |
340 | | - simargs[parts[0]] = override_dict |
341 | | - |
342 | | - return simargs |
343 | | - |
344 | | - |
345 | 100 | class ModelicaSystemABC(metaclass=abc.ABCMeta): |
346 | 101 | """ |
347 | 102 | Base class to simulate a Modelica models. |
|
0 commit comments