From 4c9f0ca4ce3a319325e62b2c38b8f1ec7a586841 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 9 Jul 2025 21:42:19 +0200 Subject: [PATCH 1/4] [ModelicaSystem.linearize] do not execute python file but use ast to get the data --- OMPython/ModelicaSystem.py | 78 +++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index d0178cf2..4ac70753 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -32,9 +32,9 @@ CONDITIONS OF OSMC-PL. """ +import ast import csv from dataclasses import dataclass -import importlib import logging import numbers import numpy as np @@ -1438,14 +1438,6 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N compatibility, because linearize() used to return `[A, B, C, D]`. """ - # replacement for depreciated importlib.load_module() - def load_module_from_path(module_name, file_path): - spec = importlib.util.spec_from_file_location(module_name, file_path) - module_def = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module_def) - - return module_def - if self._xml_file is None: raise ModelicaSystemError( "Linearization cannot be performed as the model is not build, " @@ -1484,38 +1476,62 @@ def load_module_from_path(module_name, file_path): if simargs: om_cmd.args_set(args=simargs) + # the file create by the model executable which contains the matrix and linear inputs, outputs and states + linear_file = self._tempdir / "linearized_model.py" + + linear_file.unlink(missing_ok=True) + returncode = om_cmd.run() if returncode != 0: raise ModelicaSystemError(f"Linearize failed with return code: {returncode}") self._simulated = True - # code to get the matrix and linear inputs, outputs and states - linearFile = self._tempdir / "linearized_model.py" + if not linear_file.exists(): + raise ModelicaSystemError(f"Linearization failed: {linear_file} not found!") # support older openmodelica versions before OpenModelica v1.16.2 where linearize() generates "linear_model_name.mo" file - if not linearFile.exists(): - linearFile = pathlib.Path(f'linear_{self._model_name}.py') - - if not linearFile.exists(): - raise ModelicaSystemError(f"Linearization failed: {linearFile} not found!") + if not linear_file.exists(): + linear_file = pathlib.Path(f'linear_{self._model_name}.py') - # this function is called from the generated python code linearized_model.py at runtime, - # to improve the performance by directly reading the matrices A, B, C and D from the julia code and avoid building the linearized modelica model + # extract data from the python file with the linearized model using the ast module - this allows to get the + # needed information without executing the created code + linear_data = {} + linear_file_content = linear_file.read_text() try: - # do not add the linearfile directory to path, as multiple execution of linearization will always use the first added path, instead execute the file - # https://github.com/OpenModelica/OMPython/issues/196 - module = load_module_from_path(module_name="linearized_model", file_path=linearFile.as_posix()) - - result = module.linearized_model() - (n, m, p, x0, u0, A, B, C, D, stateVars, inputVars, outputVars) = result - self._linearized_inputs = inputVars - self._linearized_outputs = outputVars - self._linearized_states = stateVars - return LinearizationResult(n, m, p, A, B, C, D, x0, u0, stateVars, - inputVars, outputVars) - except ModuleNotFoundError as ex: - raise ModelicaSystemError("No module named 'linearized_model'") from ex + linear_file_ast = ast.parse(linear_file_content) + for body_part in linear_file_ast.body[0].body: + if not isinstance(body_part, ast.Assign): + continue + + target = body_part.targets[0].id + value = ast.literal_eval(body_part.value) + + linear_data[target] = value + except (AttributeError, IndexError, ValueError, SyntaxError, TypeError) as ex: + raise ModelicaSystemError(f"Error parsing linearization file {linear_file}!") from ex + + # remove the file + linear_file.unlink() + + self._linearized_inputs = linear_data["inputVars"] + self._linearized_outputs = linear_data["outputVars"] + self._linearized_states = linear_data["stateVars"] + + return LinearizationResult( + n=linear_data["n"], + m=linear_data["m"], + p=linear_data["p"], + x0=linear_data["x0"], + u0=linear_data["u0"], + A=linear_data["A"], + B=linear_data["B"], + C=linear_data["C"], + D=linear_data["D"], + stateVars=linear_data["stateVars"], + inputVars=linear_data["inputVars"], + outputVars=linear_data["outputVars"], + ) def getLinearInputs(self) -> list[str]: """Get names of input variables of the linearized model.""" From 191cd59a0cade1a19924cc3d563e38136c3d8110 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 9 Jul 2025 21:44:17 +0200 Subject: [PATCH 2/4] [ModelicaSystem.linearize] remove old check / use of file in current dir --- OMPython/ModelicaSystem.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 4ac70753..934ae77e 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1490,10 +1490,6 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N if not linear_file.exists(): raise ModelicaSystemError(f"Linearization failed: {linear_file} not found!") - # support older openmodelica versions before OpenModelica v1.16.2 where linearize() generates "linear_model_name.mo" file - if not linear_file.exists(): - linear_file = pathlib.Path(f'linear_{self._model_name}.py') - # extract data from the python file with the linearized model using the ast module - this allows to get the # needed information without executing the created code linear_data = {} From 2802d9f988efdf6e4cd852f12fc2284f180702ab Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 10 Jul 2025 20:31:17 +0200 Subject: [PATCH 3/4] [ModelicaSystem.linearize] fix mypy --- OMPython/ModelicaSystem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 934ae77e..c87f18be 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1495,12 +1495,13 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N linear_data = {} linear_file_content = linear_file.read_text() try: + # ignore possible typing errors below (mypy) - these are catched by the try .. except .. block linear_file_ast = ast.parse(linear_file_content) - for body_part in linear_file_ast.body[0].body: + for body_part in linear_file_ast.body[0].body: # type: ignore if not isinstance(body_part, ast.Assign): continue - target = body_part.targets[0].id + target = body_part.targets[0].id # type: ignore value = ast.literal_eval(body_part.value) linear_data[target] = value From ab0beae5d917024913115e991a3015977948958b Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 11 Jul 2025 19:04:20 +0200 Subject: [PATCH 4/4] [ModelicaSystem] add spelling fix (fox codespell) --- OMPython/ModelicaSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index c87f18be..ed387f51 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1495,7 +1495,7 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N linear_data = {} linear_file_content = linear_file.read_text() try: - # ignore possible typing errors below (mypy) - these are catched by the try .. except .. block + # ignore possible typing errors below (mypy) - these are caught by the try .. except .. block linear_file_ast = ast.parse(linear_file_content) for body_part in linear_file_ast.body[0].body: # type: ignore if not isinstance(body_part, ast.Assign):