From 3920eb6d847d56e77c0697871f6891d0bdb58fbc Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 14 Feb 2026 11:37:37 +0100 Subject: [PATCH 01/14] (F001) cleanup after restructure [README.md] small updates [__init__] small updates --- OMPython/__init__.py | 7 ++++--- README.md | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 282923a7..78c8959e 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -6,7 +6,7 @@ ``` import OMPython omc = OMPython.OMCSessionLocal() -omc.sendExpression("command") +omc.sendExpression("getVersion()") ``` """ @@ -58,15 +58,16 @@ ModelicaDoERunner, ) +# the imports below are compatibility functionality (OMPython v4.0.0) from OMPython.ModelicaSystem import ( ModelicaSystem, - ModelicaSystemDoE, ModelicaSystemCmd, + ModelicaSystemDoE, ) from OMPython.OMCSession import ( OMCSessionCmd, - OMCSessionZMQ, OMCSessionException, + OMCSessionZMQ, OMCProcessLocal, OMCProcessPort, diff --git a/README.md b/README.md index a9cf3bdc..56730349 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ OMPython is a Python interface that uses ZeroMQ to communicate with OpenModelica ## Dependencies -- Python 3.x supported -- PyZMQ is required + - Python >= 3.10 supported with complete functionality for Python >= 3.12 + - Additional packages: numpy, psutil, pyparsing and pyzmq ## Installation @@ -49,8 +49,8 @@ help(OMPython) ``` ```python -from OMPython import OMCSessionLocal -omc = OMCSessionLocal() +import OMPython +omc = OMPython.OMCSessionLocal() omc.sendExpression("getVersion()") ``` From 5184474b07409293c5db8148100fe0e8cfd35f9b Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 27 Jun 2026 19:02:33 +0200 Subject: [PATCH 02/14] rename classes * ModelExecutionData => ModelExecutionRun * ModelExecutionCmd => ModelExecutionConfig --- OMPython/ModelicaSystem.py | 4 ++-- OMPython/__init__.py | 8 ++++---- OMPython/model_execution.py | 8 ++++---- OMPython/modelica_doe_abc.py | 6 +++--- OMPython/modelica_system_abc.py | 12 ++++++------ tests/test_ModelExecutionCmd.py | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 17678bb0..96fbfaf6 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -12,7 +12,7 @@ import numpy as np from OMPython.model_execution import ( - ModelExecutionCmd, + ModelExecutionConfig, ModelExecutionException, ) from OMPython.om_session_omc import ( @@ -176,7 +176,7 @@ class ModelicaSystemDoE(ModelicaDoEOMC): """ -class ModelicaSystemCmd(ModelExecutionCmd): +class ModelicaSystemCmd(ModelExecutionConfig): """ Compatibility class; in the new version it is renamed as ModelExecutionCmd. """ diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 78c8959e..1ea0ed8a 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -12,8 +12,8 @@ """ from OMPython.model_execution import ( - ModelExecutionCmd, - ModelExecutionData, + ModelExecutionConfig, + ModelExecutionRun, ModelExecutionException, ) from OMPython.om_session_abc import ( @@ -81,8 +81,8 @@ 'LinearizationResult', - 'ModelExecutionCmd', - 'ModelExecutionData', + 'ModelExecutionConfig', + 'ModelExecutionRun', 'ModelExecutionException', 'ModelicaDoEABC', diff --git a/OMPython/model_execution.py b/OMPython/model_execution.py index ebd4c011..ae1664ae 100644 --- a/OMPython/model_execution.py +++ b/OMPython/model_execution.py @@ -27,7 +27,7 @@ class ModelExecutionException(Exception): @dataclasses.dataclass -class ModelExecutionData: +class ModelExecutionRun: """ Data class to store the command line data for running a model executable in the OMC environment. @@ -105,7 +105,7 @@ def run(self) -> int: return returncode -class ModelExecutionCmd: +class ModelExecutionConfig: """ All information about a compiled model executable. This should include data about all structured parameters, i.e. parameters which need a recompilation of the model. All non-structured parameters can be easily changed without @@ -261,7 +261,7 @@ def get_cmd_args(self) -> list[str]: return cmdl - def definition(self) -> ModelExecutionData: + def definition(self) -> ModelExecutionRun: """ Define all needed data to run the model executable. The data is stored in an OMCSessionRunData object. """ @@ -301,7 +301,7 @@ def definition(self) -> ModelExecutionData: if self._cmd_local: cmd_cwd_local = cmd_path.as_posix() - omc_run_data = ModelExecutionData( + omc_run_data = ModelExecutionRun( cmd_path=cmd_path.as_posix(), cmd_model_name=self._model_name, cmd_args=self.get_cmd_args(), diff --git a/OMPython/modelica_doe_abc.py b/OMPython/modelica_doe_abc.py index e3ab8403..0ab3add9 100644 --- a/OMPython/modelica_doe_abc.py +++ b/OMPython/modelica_doe_abc.py @@ -13,7 +13,7 @@ from typing import Any, cast, Optional, Tuple from OMPython.model_execution import ( - ModelExecutionData, + ModelExecutionRun, ) from OMPython.om_session_abc import ( OMPathABC, @@ -138,7 +138,7 @@ def __init__( self._parameters = {} self._doe_def: Optional[dict[str, dict[str, Any]]] = None - self._doe_cmd: Optional[dict[str, ModelExecutionData]] = None + self._doe_cmd: Optional[dict[str, ModelExecutionRun]] = None def get_session(self) -> OMSessionABC: """ @@ -255,7 +255,7 @@ def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]: """ return self._doe_def - def get_doe_command(self) -> Optional[dict[str, ModelExecutionData]]: + def get_doe_command(self) -> Optional[dict[str, ModelExecutionRun]]: """ Get the definitions of simulations commands to run for this DoE. """ diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index fcc31deb..d37b0f44 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -17,7 +17,7 @@ import numpy as np from OMPython.model_execution import ( - ModelExecutionCmd, + ModelExecutionConfig, ) from OMPython.om_session_abc import ( OMPathABC, @@ -189,7 +189,7 @@ def check_model_executable(self): Check if the model executable is working """ # check if the executable exists ... - om_cmd = ModelExecutionCmd( + om_cmd = ModelExecutionConfig( runpath=self.getWorkDirectory(), cmd_local=self._session.model_execution_local, cmd_windows=self._session.model_execution_windows, @@ -579,7 +579,7 @@ def _parse_om_version(version: str) -> tuple[int, int, int]: def _process_override_data( self, - om_cmd: ModelExecutionCmd, + om_cmd: ModelExecutionConfig, override_file: OMPathABC, override_var: dict[str, str], override_sim: dict[str, str], @@ -619,7 +619,7 @@ def simulate_cmd( result_file: OMPathABC, simflags: Optional[str] = None, simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, - ) -> ModelExecutionCmd: + ) -> ModelExecutionConfig: """ This method prepares the simulates model according to the simulation options. It returns an instance of ModelicaSystemCmd which can be used to run the simulation. @@ -641,7 +641,7 @@ def simulate_cmd( An instance if ModelicaSystemCmd to run the requested simulation. """ - om_cmd = ModelExecutionCmd( + om_cmd = ModelExecutionConfig( runpath=self.getWorkDirectory(), cmd_local=self._session.model_execution_local, cmd_windows=self._session.model_execution_windows, @@ -1134,7 +1134,7 @@ def linearize( "use ModelicaSystemOMC() to build the model first" ) - om_cmd = ModelExecutionCmd( + om_cmd = ModelExecutionConfig( runpath=self.getWorkDirectory(), cmd_local=self._session.model_execution_local, cmd_windows=self._session.model_execution_windows, diff --git a/tests/test_ModelExecutionCmd.py b/tests/test_ModelExecutionCmd.py index db5aadeb..71e96fc1 100644 --- a/tests/test_ModelExecutionCmd.py +++ b/tests/test_ModelExecutionCmd.py @@ -24,7 +24,7 @@ def mscmd_firstorder(model_firstorder): model_name="M", ) - mscmd = OMPython.ModelExecutionCmd( + mscmd = OMPython.ModelExecutionConfig( runpath=mod.getWorkDirectory(), cmd_local=mod.get_session().model_execution_local, cmd_windows=mod.get_session().model_execution_windows, From aa38cb72fe78f7764aeb6de0c151d572d7562949 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 27 Jun 2026 19:03:00 +0200 Subject: [PATCH 03/14] update of docstrings for ModelExecutionRun and ModelExecutionConfig --- OMPython/model_execution.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OMPython/model_execution.py b/OMPython/model_execution.py index ae1664ae..87fc6bdf 100644 --- a/OMPython/model_execution.py +++ b/OMPython/model_execution.py @@ -29,12 +29,11 @@ class ModelExecutionException(Exception): @dataclasses.dataclass class ModelExecutionRun: """ - Data class to store the command line data for running a model executable in the OMC environment. + Data class to store the command line data for running a model executable. This definition is independent of the OMC + environment as only the executable is needed. - All data should be defined for the environment, where OMC is running (local, docker or WSL) - - To use this as a definition of an OMC simulation run, it has to be processed within - OMCProcess*.self_update(). This defines the attribute cmd_model_executable. + All data should be defined for the environment, where the executable was defined / is located. This is especially + important if OMPython and the executable are defined in different environments (docker or WSL). """ # cmd_path is the expected working directory cmd_path: str @@ -107,9 +106,10 @@ def run(self) -> int: class ModelExecutionConfig: """ - All information about a compiled model executable. This should include data about all structured parameters, i.e. - parameters which need a recompilation of the model. All non-structured parameters can be easily changed without - the need for recompilation. + This class collects all information about a compiled model executable. This includes data about all structured + parameters, i.e. parameters which need a recompilation of the model. All non-structured parameters can be easily + changed without the need for recompilation. The final result is an instance of class ModelExecutionRun - a + definition to run one simulation based on the compiled model executable. """ def __init__( From 6f9317dbf0eedfb3ad0d5295e3713f6f10216d7b Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 15 Feb 2026 14:25:52 +0100 Subject: [PATCH 04/14] G001-pylint [pylint] fix 'R1729: Use a generator instead 'all(isinstance(item, tuple) for item in val_evaluated)' (use-a-generator)' [pylint] fix 'W0237: Parameter 'expr' has been renamed to 'command' in overriding 'OMCSessionZMQ.sendExpression' method (arguments-renamed)' [pylint] [OM*Path*] fix pylint messags about incompatible definitions --- OMPython/OMCSession.py | 8 ++++--- OMPython/modelica_system_abc.py | 2 +- OMPython/om_session_abc.py | 12 +++++----- OMPython/om_session_omc.py | 22 +++++++++++++----- OMPython/om_session_runner.py | 41 +++++++++++++++++++++++---------- 5 files changed, 57 insertions(+), 28 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index c5511923..ecc033f1 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -282,12 +282,14 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC def execute(self, command: str): return self.omc_process.execute(command=command) - def sendExpression(self, command: str, parsed: bool = True) -> Any: + def sendExpression(self, command: str, parsed: bool = True) -> Any: # pylint: disable=W0237 """ Send an expression to the OMC server and return the result. - The complete error handling of the OMC result is done within this method using '"getMessagesStringInternal()'. - Caller should only check for OMSessionException. + The complete error handling of the OMC result is done within this method using 'getMessagesStringInternal()'. + Caller should only check for OMCSessionException. + + Compatibility: 'command' was renamed to 'expr' """ return self.omc_process.sendExpression(expr=command, parsed=parsed) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index d37b0f44..0f04e4df 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -1026,7 +1026,7 @@ def setInputs( self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), (float(self._simulate_options["stopTime"]), float(val))] elif isinstance(val_evaluated, list): - if not all([isinstance(item, tuple) for item in val_evaluated]): + if not all(isinstance(item, tuple) for item in val_evaluated): raise ModelicaSystemError("Value for setInput() must be in tuple format; " f"got {repr(val_evaluated)}") if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]): diff --git a/OMPython/om_session_abc.py b/OMPython/om_session_abc.py index 70e897d7..fdfa5491 100644 --- a/OMPython/om_session_abc.py +++ b/OMPython/om_session_abc.py @@ -97,13 +97,13 @@ def with_segments(self, *pathsegments) -> OMPathABC: return type(self)(*pathsegments, session=self._session) @abc.abstractmethod - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ @abc.abstractmethod - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ @@ -115,19 +115,19 @@ def is_absolute(self) -> bool: """ @abc.abstractmethod - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ @abc.abstractmethod - def write_text(self, data: str) -> int: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int: """ Write text data to the file represented by this path. """ @abc.abstractmethod - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -137,7 +137,7 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: """ @abc.abstractmethod - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ diff --git a/OMPython/om_session_omc.py b/OMPython/om_session_omc.py index 6626cd17..4fafc3a1 100644 --- a/OMPython/om_session_omc.py +++ b/OMPython/om_session_omc.py @@ -52,19 +52,23 @@ class _OMCPath(OMPathABC): OMCSession* classes. """ - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ + del follow_symlinks + retval = self.get_session().sendExpression(expr=f'regularFileExists("{self.as_posix()}")') if not isinstance(retval, bool): raise OMSessionException(f"Invalid return value for is_file(): {retval} - expect bool") return retval - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ + del follow_symlinks + retval = self.get_session().sendExpression(expr=f'directoryExists("{self.as_posix()}")') if not isinstance(retval, bool): raise OMSessionException(f"Invalid return value for is_dir(): {retval} - expect bool") @@ -78,19 +82,23 @@ def is_absolute(self) -> bool: return pathlib.PureWindowsPath(self.as_posix()).is_absolute() return pathlib.PurePosixPath(self.as_posix()).is_absolute() - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ + del encoding, errors, newline + retval = self.get_session().sendExpression(expr=f'readFile("{self.as_posix()}")') if not isinstance(retval, str): raise OMSessionException(f"Invalid return value for read_text(): {retval} - expect str") return retval - def write_text(self, data: str) -> int: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int: """ Write text data to the file represented by this path. """ + del encoding, errors, newline + if not isinstance(data, str): raise TypeError(f"data must be str, not {data.__class__.__name__}") @@ -99,7 +107,7 @@ def write_text(self, data: str) -> int: return len(data) - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -107,13 +115,15 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent directories are also created. """ + del mode + if self.is_dir() and not exist_ok: raise FileExistsError(f"Directory {self.as_posix()} already exists!") if not self._session.sendExpression(expr=f'mkdir("{self.as_posix()}")'): raise OMSessionException(f"Error on directory creation for {self.as_posix()}!") - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ diff --git a/OMPython/om_session_runner.py b/OMPython/om_session_runner.py index fc8e5ac8..b81c3ae1 100644 --- a/OMPython/om_session_runner.py +++ b/OMPython/om_session_runner.py @@ -49,16 +49,20 @@ class _OMPathRunnerLocal(OMPathRunnerABC): conversion via pathlib.Path(.as_posix()). """ - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ + del follow_symlinks + return self._path().is_file() - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ + del follow_symlinks + return self._path().is_dir() def is_absolute(self) -> bool: @@ -67,22 +71,26 @@ def is_absolute(self) -> bool: """ return self._path().is_absolute() - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ + del encoding, errors, newline + return self._path().read_text(encoding='utf-8') - def write_text(self, data: str): + def write_text(self, data: str, encoding=None, errors=None, newline=None): """ Write text data to the file represented by this path. """ + del encoding, errors, newline + if not isinstance(data, str): raise TypeError(f"data must be str, not {data.__class__.__name__}") return self._path().write_text(data=data, encoding='utf-8') - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -90,9 +98,11 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent directories are also created. """ + del mode + self._path().mkdir(parents=parents, exist_ok=exist_ok) - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ @@ -132,10 +142,12 @@ class _OMPathRunnerBash(OMPathRunnerABC): conversion via pathlib.Path(.as_posix()). """ - def is_file(self) -> bool: + def is_file(self, *, follow_symlinks=True) -> bool: """ Check if the path is a regular file. """ + del follow_symlinks + cmdl = self.get_session().get_cmd_prefix() cmdl += ['bash', '-c', f'test -f "{self.as_posix()}"'] @@ -145,7 +157,7 @@ def is_file(self) -> bool: except subprocess.CalledProcessError: return False - def is_dir(self) -> bool: + def is_dir(self, *, follow_symlinks: bool = True) -> bool: """ Check if the path is a directory. """ @@ -172,10 +184,12 @@ def is_absolute(self) -> bool: except subprocess.CalledProcessError: return False - def read_text(self) -> str: + def read_text(self, encoding=None, errors=None, newline=None) -> str: """ Read the content of the file represented by this path as text. """ + del encoding, errors, newline + cmdl = self.get_session().get_cmd_prefix() cmdl += ['bash', '-c', f'cat "{self.as_posix()}"'] @@ -184,10 +198,12 @@ def read_text(self) -> str: return result.stdout.decode('utf-8') raise FileNotFoundError(f"Cannot read file: {self.as_posix()}") - def write_text(self, data: str) -> int: + def write_text(self, data: str, encoding=None, errors=None, newline=None) -> int: """ Write text data to the file represented by this path. """ + del encoding, errors, newline + if not isinstance(data, str): raise TypeError(f"data must be str, not {data.__class__.__name__}") @@ -202,7 +218,7 @@ def write_text(self, data: str) -> int: except subprocess.CalledProcessError as exc: raise IOError(f"Error writing data to file {self.as_posix()}!") from exc - def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: + def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None: """ Create a directory at the path represented by this class. @@ -210,6 +226,7 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent directories are also created. """ + del mode if self.is_file(): raise OSError(f"The given path {self.as_posix()} exists and is a file!") @@ -226,7 +243,7 @@ def mkdir(self, parents: bool = True, exist_ok: bool = False) -> None: except subprocess.CalledProcessError as exc: raise OMSessionException(f"Error on directory creation for {self.as_posix()}!") from exc - def cwd(self) -> OMPathABC: + def cwd(self) -> OMPathABC: # pylint: disable=W0221 # is @classmethod in the original; see pathlib.PathBase """ Returns the current working directory as an OMPathABC object. """ From 93447c921d7acf1268505eb83c126f8265e6a45e Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 15 Feb 2026 12:54:43 +0100 Subject: [PATCH 05/14] G002-bugfix [ModelExecutionException] catch exception if ModelExecutionCmd.run() is used [bugfix] [ModelicaSystem] fix exception; use ModelicaSystemError (instead of wrong ModelExecutionException) [bugfix] [ModelicaSystemABC] fix _prepare_input_data() - ensure returned data is dict[str, str] --- OMPython/ModelicaSystem.py | 15 ++++++++++----- OMPython/modelica_doe_abc.py | 5 +++-- OMPython/modelica_system_abc.py | 27 ++++++++++++++++++++++----- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 96fbfaf6..12028fb1 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -140,7 +140,7 @@ def getContinuous( retval3.append(str(val)) return retval3 - raise ModelExecutionException("Invalid data!") + raise ModelicaSystemError("Invalid data!") def getOutputs( self, @@ -167,7 +167,7 @@ def getOutputs( retval3.append(str(val)) return retval3 - raise ModelExecutionException("Invalid data!") + raise ModelicaSystemError("Invalid data!") class ModelicaSystemDoE(ModelicaDoEOMC): @@ -209,7 +209,8 @@ def get_exe(self) -> pathlib.Path: return path_exe def get_cmd(self) -> list: - """Get a list with the path to the executable and all command line args. + """ + Get a list with the path to the executable and all command line args. This can later be used as an argument for subprocess.run(). """ @@ -218,6 +219,10 @@ def get_cmd(self) -> list: return cmdl - def run(self): + def run(self) -> int: cmd_definition = self.definition() - return cmd_definition.run() + try: + returncode = cmd_definition.run() + except ModelExecutionException as exc: + raise ModelicaSystemError(f"Cannot execute model: {exc}") from exc + return returncode diff --git a/OMPython/modelica_doe_abc.py b/OMPython/modelica_doe_abc.py index 0ab3add9..392253f0 100644 --- a/OMPython/modelica_doe_abc.py +++ b/OMPython/modelica_doe_abc.py @@ -14,6 +14,7 @@ from OMPython.model_execution import ( ModelExecutionRun, + ModelExecutionException, ) from OMPython.om_session_abc import ( OMPathABC, @@ -310,8 +311,8 @@ def worker(worker_id, task_queue): returncode = cmd_definition.run() logger.info(f"[Worker {worker_id}] Simulation {resultpath.name} " f"finished with return code: {returncode}") - except ModelicaSystemError as ex: - logger.warning(f"Simulation error for {resultpath.name}: {ex}") + except ModelExecutionException as exc: + logger.warning(f"Simulation error for {resultpath.name}: {exc}") # Mark the task as done task_queue.task_done() diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 0f04e4df..44bac274 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -18,6 +18,7 @@ from OMPython.model_execution import ( ModelExecutionConfig, + ModelExecutionException, ) from OMPython.om_session_abc import ( OMPathABC, @@ -200,7 +201,10 @@ def check_model_executable(self): # ... by running it - output help for command help om_cmd.arg_set(key="help", val="help") cmd_definition = om_cmd.definition() - returncode = cmd_definition.run() + try: + returncode = cmd_definition.run() + except ModelExecutionException as exc: + raise ModelicaSystemError(f"Cannot execute model: {exc}") from exc if returncode != 0: raise ModelicaSystemError("Model executable not working!") @@ -736,7 +740,10 @@ def simulate( self._result_file.unlink() # ... run simulation ... cmd_definition = om_cmd.definition() - returncode = cmd_definition.run() + try: + returncode = cmd_definition.run() + except ModelExecutionException as exc: + raise ModelicaSystemError(f"Cannot execute model: {exc}") from exc # and check returncode *AND* resultfile if returncode != 0 and self._result_file.is_file(): # check for an empty (=> 0B) result file which indicates a crash of the model executable @@ -764,8 +771,10 @@ def prepare_str(str_in: str) -> dict[str, str]: key_val_list: list[str] = str_in.split("=") if len(key_val_list) != 2: raise ModelicaSystemError(f"Invalid 'key=value' pair: {str_in}") + if len(key_val_list[0]) == 0: + raise ModelicaSystemError(f"Empty key: {str_in}") - input_data_from_str: dict[str, str] = {key_val_list[0]: key_val_list[1]} + input_data_from_str: dict[str, str] = {str(key_val_list[0]): str(key_val_list[1])} return input_data_from_str @@ -791,7 +800,12 @@ def prepare_str(str_in: str) -> dict[str, str]: raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(item)}!") input_data = input_data | prepare_str(item) elif isinstance(input_arg, dict): - input_data = input_data | input_arg + input_arg_str: dict[str, str] = {} + for key, val in input_arg.items(): + if not isinstance(key, str) or len(key) == 0: + raise ModelicaSystemError(f"Invalid key for set*() functions: {repr(key)}") + input_arg_str[key] = str(val) + input_data = input_data | input_arg_str else: raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(input_arg)}!") @@ -1180,7 +1194,10 @@ def linearize( linear_file.unlink(missing_ok=True) cmd_definition = om_cmd.definition() - returncode = cmd_definition.run() + try: + returncode = cmd_definition.run() + except ModelExecutionException as exc: + raise ModelicaSystemError(f"Cannot execute model: {exc}") from exc if returncode != 0: raise ModelicaSystemError(f"Linearize failed with return code: {returncode}") if not linear_file.is_file(): From 40538ec8ca08a0bd542b9caae1eaf763754de6e3 Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 15 Feb 2026 14:02:16 +0100 Subject: [PATCH 06/14] G003-compatibility [compatibility] add class wrapper to provide the depreciation message [ModelicaSystem] fix / improve wrapper functions for v4.0.0 compatibility [ModelicaSystemABC] additional checks for setInputs() [test_ModelicaSystemOMC] add tests for setInputs() [__init__] define ModelicaSystemDoE at the right point (=> compatibility layer) [__init__] remove duplicate 'OMCSessionABC' in __all__ --- OMPython/ModelicaSystem.py | 183 ++++++++++++++++++++++++++------ OMPython/OMCSession.py | 59 ++++++---- OMPython/__init__.py | 3 +- OMPython/compatibility_v400.py | 39 +++++++ OMPython/modelica_system_abc.py | 26 +++-- tests/test_ModelicaSystemOMC.py | 8 ++ 6 files changed, 257 insertions(+), 61 deletions(-) create mode 100644 OMPython/compatibility_v400.py diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 12028fb1..846f75ce 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -28,10 +28,15 @@ ModelicaDoEOMC, ) +from OMPython.compatibility_v400 import ( + depreciated_class, +) + # define logger using the current module name as ID logger = logging.getLogger(__name__) +@depreciated_class(msg="Please use class ModelicaSystemOMC instead!") class ModelicaSystem(ModelicaSystemOMC): """ Compatibility class. @@ -67,58 +72,167 @@ def __init__( def setCommandLineOptions(self, commandLineOptions: str): super().set_command_line_options(command_line_option=commandLineOptions) - def setContinuous( # type: ignore[override] + def _set_compatibility_helper( + self, + pkey: str, + args: Any, + kwargs: dict[str, Any], + ) -> Any: + param = None + if len(args) == 1: + param = args[0] + if param is None and pkey in kwargs: + param = kwargs[pkey] + + return param + + def setContinuous( self, - cvals: str | list[str] | dict[str, Any], + *args: Any, + **kwargs: dict[str, Any], ) -> bool: - if isinstance(cvals, dict): - return super().setContinuous(**cvals) - raise ModelicaSystemError("Only dict input supported for setContinuous()") + """ + Compatibility wrapper for setContinuous() from OMPython v4.0.0 + + Original definition: - def setParameters( # type: ignore[override] + ``` + def setContinuous( + self, + cvals: str | list[str] | dict[str, Any], + ) -> bool: + ``` + """ + param = self._set_compatibility_helper(pkey='cvals', args=args, kwargs=kwargs) + if param is None: + raise ModelicaSystemError("Invalid input for setContinuous() (v4.0.0 compatibility mode).") + + return super().setContinuous(param) + + def setParameters( self, - pvals: str | list[str] | dict[str, Any], + *args: Any, + **kwargs: dict[str, Any], ) -> bool: - if isinstance(pvals, dict): - return super().setParameters(**pvals) - raise ModelicaSystemError("Only dict input supported for setParameters()") + """ + Compatibility wrapper for setParameters() from OMPython v4.0.0 + + Original definition: - def setOptimizationOptions( # type: ignore[override] + ``` + def setParameters( + self, + pvals: str | list[str] | dict[str, Any], + ) -> bool: + ``` + """ + param = self._set_compatibility_helper(pkey='pvals', args=args, kwargs=kwargs) + if param is None: + raise ModelicaSystemError("Invalid input for setParameters() (v4.0.0 compatibility mode).") + + return super().setParameters(param) + + def setOptimizationOptions( self, - optimizationOptions: str | list[str] | dict[str, Any], + *args: Any, + **kwargs: dict[str, Any], ) -> bool: - if isinstance(optimizationOptions, dict): - return super().setOptimizationOptions(**optimizationOptions) - raise ModelicaSystemError("Only dict input supported for setOptimizationOptions()") + """ + Compatibility wrapper for setOptimizationOptions() from OMPython v4.0.0 + + Original definition: - def setInputs( # type: ignore[override] + ``` + def setOptimizationOptions( + self, + optimizationOptions: str | list[str] | dict[str, Any], + ) -> bool: + ``` + """ + param = self._set_compatibility_helper(pkey='optimizationOptions', args=args, kwargs=kwargs) + if param is None: + raise ModelicaSystemError("Invalid input for setOptimizationOptions() (v4.0.0 compatibility mode).") + + return super().setOptimizationOptions(param) + + def setInputs( self, - name: str | list[str] | dict[str, Any], + *args: Any, + **kwargs: dict[str, Any], ) -> bool: - if isinstance(name, dict): - return super().setInputs(**name) - raise ModelicaSystemError("Only dict input supported for setInputs()") + """ + Compatibility wrapper for setInputs() from OMPython v4.0.0 + + Original definition: - def setSimulationOptions( # type: ignore[override] + ``` + def setInputs( + self, + name: str | list[str] | dict[str, Any], + ) -> bool: + ``` + """ + param = self._set_compatibility_helper(pkey='name', args=args, kwargs=kwargs) + if param is None: + raise ModelicaSystemError("Invalid input for setInputs() (v4.0.0 compatibility mode).") + + return super().setInputs(param) + + def setSimulationOptions( self, - simOptions: str | list[str] | dict[str, Any], + *args: Any, + **kwargs: dict[str, Any], ) -> bool: - if isinstance(simOptions, dict): - return super().setSimulationOptions(**simOptions) - raise ModelicaSystemError("Only dict input supported for setSimulationOptions()") + """ + Compatibility wrapper for setSimulationOptions() from OMPython v4.0.0 + + Original definition: - def setLinearizationOptions( # type: ignore[override] + ``` + def setSimulationOptions( + self, + simOptions: str | list[str] | dict[str, Any], + ) -> bool: + ``` + """ + param = self._set_compatibility_helper(pkey='simOptions', args=args, kwargs=kwargs) + if param is None: + raise ModelicaSystemError("Invalid input for setSimulationOptions() (v4.0.0 compatibility mode).") + + return super().setSimulationOptions(param) + + def setLinearizationOptions( self, - linearizationOptions: str | list[str] | dict[str, Any], + *args: Any, + **kwargs: dict[str, Any], ) -> bool: - if isinstance(linearizationOptions, dict): - return super().setLinearizationOptions(**linearizationOptions) - raise ModelicaSystemError("Only dict input supported for setLinearizationOptions()") + """ + Compatibility wrapper for setLinearizationOptions() from OMPython v4.0.0 + + Original definition: + + ``` + def setLinearizationOptions( + self, + linearizationOptions: str | list[str] | dict[str, Any], + ) -> bool: + ``` + """ + param = self._set_compatibility_helper(pkey='linearizationOptions', args=args, kwargs=kwargs) + if param is None: + raise ModelicaSystemError("Invalid input for setLinearizationOptions() (v4.0.0 compatibility mode).") + + return super().setLinearizationOptions(param) def getContinuous( self, names: Optional[str | list[str]] = None, ): + """ + Compatibility wrapper for getContinuous() from OMPython v4.0.0 + + If no model simulation was run (self._simulated == False), the return value should be converted to str. + """ retval = super().getContinuous(names=names) if self._simulated: return retval @@ -146,6 +260,11 @@ def getOutputs( self, names: Optional[str | list[str]] = None, ): + """ + Compatibility wrapper for getOutputs() from OMPython v4.0.0 + + If no model simulation was run (self._simulated == False), the return value should be converted to str. + """ retval = super().getOutputs(names=names) if self._simulated: return retval @@ -170,15 +289,17 @@ def getOutputs( raise ModelicaSystemError("Invalid data!") +@depreciated_class(msg="Please use class ModelicaDoEOMC instead!") class ModelicaSystemDoE(ModelicaDoEOMC): """ Compatibility class. """ +@depreciated_class(msg="Please use class ModelExecutionConfig instead!") class ModelicaSystemCmd(ModelExecutionConfig): """ - Compatibility class; in the new version it is renamed as ModelExecutionCmd. + Compatibility class; in the new version it is renamed as ModelExecutionConfig. """ def __init__( diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index ecc033f1..b7a4c1dd 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -7,7 +7,6 @@ import logging from typing import Any, Optional -import warnings import pyparsing @@ -17,7 +16,6 @@ OMSessionException, ) from OMPython.om_session_omc import ( - DockerPopen, OMCSessionABC, OMCSessionDocker, OMCSessionDockerContainer, @@ -26,30 +24,28 @@ OMCSessionWSL, ) +from OMPython.compatibility_v400 import ( + depreciated_class, +) # define logger using the current module name as ID logger = logging.getLogger(__name__) +@depreciated_class(msg="Please use class OMSessionException instead!") class OMCSessionException(OMSessionException): """ Just a compatibility layer ... """ +@depreciated_class(msg="Please use OMCSession*.sendExpression(...) instead!") class OMCSessionCmd: """ Implementation of Open Modelica Compiler API functions. Depreciated! """ def __init__(self, session: OMSessionABC, readonly: bool = False): - warnings.warn( - message="The class OMCSessionCMD is depreciated and will be removed in future versions; " - "please use OMCSession*.sendExpression(...) instead!", - category=DeprecationWarning, - stacklevel=2, - ) - if not isinstance(session, OMSessionABC): raise OMCSessionException("Invalid OMC process definition!") self._session = session @@ -228,6 +224,7 @@ def getClassNames(self, className=None, recursive=False, qualified=False, sort=F return self._ask(question='getClassNames', opt=opt) +@depreciated_class(msg="Please use OMCSession* classes instead!") class OMCSessionZMQ(OMSessionABC): """ This class is a compatibility layer for the new schema using OMCSession* classes. @@ -242,11 +239,6 @@ def __init__( """ Initialisation for OMCSessionZMQ """ - warnings.warn(message="The class OMCSessionZMQ is depreciated and will be removed in future versions; " - "please use OMCProcess* classes instead!", - category=DeprecationWarning, - stacklevel=2) - if omc_process is None: omc_process = OMCSessionLocal(omhome=omhome, timeout=timeout) elif not isinstance(omc_process, OMCSessionABC): @@ -303,9 +295,36 @@ def set_workdir(self, workdir: OMPathABC) -> None: return self.omc_process.set_workdir(workdir=workdir) -DummyPopen = DockerPopen -OMCProcessLocal = OMCSessionLocal -OMCProcessPort = OMCSessionPort -OMCProcessDocker = OMCSessionDocker -OMCProcessDockerContainer = OMCSessionDockerContainer -OMCProcessWSL = OMCSessionWSL +@depreciated_class(msg="Please use class OMCSessionLocal instead!") +class OMCProcessLocal(OMCSessionLocal): + """ + Just a wrapper class; OMCProcessLocal => OMCSessionLocal + """ + + +@depreciated_class(msg="Please use class OMCSessionPort instead!") +class OMCProcessPort(OMCSessionPort): + """ + Just a wrapper class; OMCProcessPort => OMCSessionPort + """ + + +@depreciated_class(msg="Please use class OMCSessionDocker instead!") +class OMCProcessDocker(OMCSessionDocker): + """ + Just a wrapper class; OMCProcessDocker => OMCSessionDocker + """ + + +@depreciated_class(msg="Please use class OMCSessionDockerContainer instead!") +class OMCProcessDockerContainer(OMCSessionDockerContainer): + """ + Just a wrapper class; OMCProcessDockerContainer => OMCSessionDockerContainer + """ + + +@depreciated_class(msg="Please use class OMCSessionWSL instead!") +class OMCProcessWSL(OMCSessionWSL): + """ + Just a wrapper class; OMCProcessWSL => OMCSessionWSL + """ diff --git a/OMPython/__init__.py b/OMPython/__init__.py index 1ea0ed8a..f3526da9 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -89,7 +89,6 @@ 'ModelicaDoEOMC', 'ModelicaDoERunner', 'ModelicaSystemABC', - 'ModelicaSystemDoE', 'ModelicaSystemError', 'ModelicaSystemOMC', 'ModelicaSystemRunner', @@ -112,8 +111,8 @@ 'ModelicaSystemCmd', 'ModelicaSystem', + 'ModelicaSystemDoE', - 'OMCSessionABC', 'OMCSessionCmd', 'OMCSessionException', diff --git a/OMPython/compatibility_v400.py b/OMPython/compatibility_v400.py new file mode 100644 index 00000000..61fa27a8 --- /dev/null +++ b/OMPython/compatibility_v400.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Helper functions for compatibility with OMPython v4.0.0 +""" +import warnings +from typing import Optional + + +def depreciated_class(msg: Optional[str] = None): + """ + Decorator for depreciated / compatibility classes. + """ + + def depreciated(cls): + """ + Helper functions to do the decoration part. + """ + + class Wrapper(cls): + """ + Wrapper to define the depreciation message. + """ + + def __init__(self, *args, **kwargs): + message = f"The class {cls.__name__} is depreciated and will be removed in future versions!" + if msg is not None: + message += f" {msg}" + + warnings.warn( + message=message, + category=DeprecationWarning, + stacklevel=3, + ) + + super().__init__(*args, **kwargs) + + return Wrapper + + return depreciated diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 44bac274..4bfbb0b6 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -1035,7 +1035,6 @@ def setInputs( raise ModelicaSystemError(f"Invalid data in input for {repr(key)}: {repr(val)}") val_evaluated = ast.literal_eval(val) - if isinstance(val_evaluated, (int, float)): self._inputs[key] = [(float(self._simulate_options["startTime"]), float(val)), (float(self._simulate_options["stopTime"]), float(val))] @@ -1043,19 +1042,30 @@ def setInputs( if not all(isinstance(item, tuple) for item in val_evaluated): raise ModelicaSystemError("Value for setInput() must be in tuple format; " f"got {repr(val_evaluated)}") - if val_evaluated != sorted(val_evaluated, key=lambda x: x[0]): - raise ModelicaSystemError("Time value should be in increasing order; " - f"got {repr(val_evaluated)}") + val_evaluated_checked: list[tuple[float, float]] = [] for item in val_evaluated: - if item[0] < float(self._simulate_options["startTime"]): - raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less " - "than the simulation start time") if len(item) != 2: raise ModelicaSystemError(f"Value {repr(item)} of {repr(val_evaluated)} " "is in incorrect format!") - self._inputs[key] = val_evaluated + try: + val_evaluated_checked.append((float(item[0]), float(item[1]))) + except (ValueError, TypeError) as exc: + raise ModelicaSystemError("All elements of the input for setInput() should be convertible to " + "type Tuple[float, float] - " + f"found [{repr(item[0])}, {repr(item[1])}] with types " + f"[{type(item[0])}, {type(item[1])}]!") from exc + + if item[0] < float(self._simulate_options["startTime"]): + raise ModelicaSystemError(f"Time value in {repr(item)} of {repr(val_evaluated)} is less " + "than the simulation start time") + + if val_evaluated_checked != sorted(val_evaluated_checked, key=lambda x: x[0]): + raise ModelicaSystemError("Time value should be in increasing order; " + f"got {repr(val_evaluated_checked)}") + + self._inputs[key] = val_evaluated_checked else: raise ModelicaSystemError(f"Data cannot be evaluated for {repr(key)}: {repr(val)}") diff --git a/tests/test_ModelicaSystemOMC.py b/tests/test_ModelicaSystemOMC.py index c63b92e1..0b642089 100644 --- a/tests/test_ModelicaSystemOMC.py +++ b/tests/test_ModelicaSystemOMC.py @@ -439,6 +439,14 @@ def test_simulate_inputs(tmp_path): simOptions = {"stopTime": 1.0} mod.setSimulationOptions(**simOptions) + # check invalid inputs + # * 'None' cannot be converted to float + with pytest.raises(OMPython.ModelicaSystemError): + mod.setInputs(u1=[(0.0, None), (0.5, 1)]) + # * 'abc' cannot be converted to float + with pytest.raises(OMPython.ModelicaSystemError): + mod.setInputs(u1=[(0.0, 0.0), ("abc", 1)]) + # integrate zero (no setInputs call) - it should default to None -> 0 assert mod.getInputs() == { "u1": None, From ae15b5307851486db438cec22570a7c798b9ac83 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 18 Feb 2026 20:54:13 +0100 Subject: [PATCH 07/14] G004-remove_deprecated-ModelicaSystem_rewrite_set_functions2 [ModelicaSystemABC] remove code for (depreciated) arguments in set*() methods * define code in the compatibility layer in class ModelicaSystem [test_ModelicaSystem(OMC)] update tests * for new version: remove usage of old definition * for compatibility version: test old definition --- OMPython/ModelicaSystem.py | 92 ++++++++++++++++--------- OMPython/modelica_doe_abc.py | 2 +- OMPython/modelica_system_abc.py | 109 ++++++------------------------ tests/test_ModelicaSystemOMC.py | 6 +- tests_v400/test_ModelicaSystem.py | 2 +- 5 files changed, 85 insertions(+), 126 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 846f75ce..ce4f76ba 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -77,14 +77,62 @@ def _set_compatibility_helper( pkey: str, args: Any, kwargs: dict[str, Any], - ) -> Any: - param = None + ) -> dict[str, Any]: + input_args = [] if len(args) == 1: - param = args[0] - if param is None and pkey in kwargs: - param = kwargs[pkey] - - return param + input_args.append(args[0]) + elif pkey in kwargs: + input_args.append(kwargs[pkey]) + + # the code below is based on _prepare_input_data2() + + def prepare_str(str_in: str) -> dict[str, str]: + str_in = str_in.replace(" ", "") + key_val_list: list[str] = str_in.split("=") + if len(key_val_list) != 2: + raise ModelicaSystemError(f"Invalid 'key=value' pair: {str_in}") + if len(key_val_list[0]) == 0: + raise ModelicaSystemError(f"Empty key: {str_in}") + + input_data_from_str: dict[str, str] = {str(key_val_list[0]): str(key_val_list[1])} + + return input_data_from_str + + input_data: dict[str, str] = {} + + if input_args is None: + return input_data + + for input_arg in input_args: + if isinstance(input_arg, str): + warnings.warn(message="The definition of values to set should use a dictionary, " + "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " + "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", + category=DeprecationWarning, + stacklevel=3) + input_data = input_data | prepare_str(input_arg) + elif isinstance(input_arg, list): + warnings.warn(message="The definition of values to set should use a dictionary, " + "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " + "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", + category=DeprecationWarning, + stacklevel=3) + + for item in input_arg: + if not isinstance(item, str): + raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(item)}!") + input_data = input_data | prepare_str(item) + elif isinstance(input_arg, dict): + input_arg_str: dict[str, str] = {} + for key, val in input_arg.items(): + if not isinstance(key, str) or len(key) == 0: + raise ModelicaSystemError(f"Invalid key for set*() functions: {repr(key)}") + input_arg_str[key] = str(val).replace(' ', '') + input_data = input_data | input_arg_str + else: + raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(input_arg)}!") + + return input_data def setContinuous( self, @@ -104,10 +152,7 @@ def setContinuous( ``` """ param = self._set_compatibility_helper(pkey='cvals', args=args, kwargs=kwargs) - if param is None: - raise ModelicaSystemError("Invalid input for setContinuous() (v4.0.0 compatibility mode).") - - return super().setContinuous(param) + return super().setContinuous(**param) def setParameters( self, @@ -127,10 +172,7 @@ def setParameters( ``` """ param = self._set_compatibility_helper(pkey='pvals', args=args, kwargs=kwargs) - if param is None: - raise ModelicaSystemError("Invalid input for setParameters() (v4.0.0 compatibility mode).") - - return super().setParameters(param) + return super().setParameters(**param) def setOptimizationOptions( self, @@ -150,10 +192,7 @@ def setOptimizationOptions( ``` """ param = self._set_compatibility_helper(pkey='optimizationOptions', args=args, kwargs=kwargs) - if param is None: - raise ModelicaSystemError("Invalid input for setOptimizationOptions() (v4.0.0 compatibility mode).") - - return super().setOptimizationOptions(param) + return super().setOptimizationOptions(**param) def setInputs( self, @@ -173,10 +212,7 @@ def setInputs( ``` """ param = self._set_compatibility_helper(pkey='name', args=args, kwargs=kwargs) - if param is None: - raise ModelicaSystemError("Invalid input for setInputs() (v4.0.0 compatibility mode).") - - return super().setInputs(param) + return super().setInputs(**param) def setSimulationOptions( self, @@ -196,10 +232,7 @@ def setSimulationOptions( ``` """ param = self._set_compatibility_helper(pkey='simOptions', args=args, kwargs=kwargs) - if param is None: - raise ModelicaSystemError("Invalid input for setSimulationOptions() (v4.0.0 compatibility mode).") - - return super().setSimulationOptions(param) + return super().setSimulationOptions(**param) def setLinearizationOptions( self, @@ -219,10 +252,7 @@ def setLinearizationOptions( ``` """ param = self._set_compatibility_helper(pkey='linearizationOptions', args=args, kwargs=kwargs) - if param is None: - raise ModelicaSystemError("Invalid input for setLinearizationOptions() (v4.0.0 compatibility mode).") - - return super().setLinearizationOptions(param) + return super().setLinearizationOptions(**param) def getContinuous( self, diff --git a/OMPython/modelica_doe_abc.py b/OMPython/modelica_doe_abc.py index 392253f0..062f8833 100644 --- a/OMPython/modelica_doe_abc.py +++ b/OMPython/modelica_doe_abc.py @@ -210,7 +210,7 @@ def prepare(self) -> int: } ) - self._mod.setParameters(sim_param_non_structural) + self._mod.setParameters(**sim_param_non_structural) mscmd = self._mod.simulate_cmd( result_file=resultfile, ) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 4bfbb0b6..f3e02ddf 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -11,7 +11,6 @@ import os import re from typing import Any, Optional -import warnings import xml.etree.ElementTree as ET import numpy as np @@ -759,56 +758,13 @@ def simulate( @staticmethod def _prepare_input_data( - input_args: Any, input_kwargs: dict[str, Any], ) -> dict[str, str]: """ Convert raw input to a structured dictionary {'key1': 'value1', 'key2': 'value2'}. """ - - def prepare_str(str_in: str) -> dict[str, str]: - str_in = str_in.replace(" ", "") - key_val_list: list[str] = str_in.split("=") - if len(key_val_list) != 2: - raise ModelicaSystemError(f"Invalid 'key=value' pair: {str_in}") - if len(key_val_list[0]) == 0: - raise ModelicaSystemError(f"Empty key: {str_in}") - - input_data_from_str: dict[str, str] = {str(key_val_list[0]): str(key_val_list[1])} - - return input_data_from_str - input_data: dict[str, str] = {} - for input_arg in input_args: - if isinstance(input_arg, str): - warnings.warn(message="The definition of values to set should use a dictionary, " - "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " - "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", - category=DeprecationWarning, - stacklevel=3) - input_data = input_data | prepare_str(input_arg) - elif isinstance(input_arg, list): - warnings.warn(message="The definition of values to set should use a dictionary, " - "i.e. {'key1': 'val1', 'key2': 'val2', ...}. Please convert all cases which " - "use a string ('key=val') or list ['key1=val1', 'key2=val2', ...]", - category=DeprecationWarning, - stacklevel=3) - - for item in input_arg: - if not isinstance(item, str): - raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(item)}!") - input_data = input_data | prepare_str(item) - elif isinstance(input_arg, dict): - input_arg_str: dict[str, str] = {} - for key, val in input_arg.items(): - if not isinstance(key, str) or len(key) == 0: - raise ModelicaSystemError(f"Invalid key for set*() functions: {repr(key)}") - input_arg_str[key] = str(val) - input_data = input_data | input_arg_str - else: - raise ModelicaSystemError(f"Invalid input data type for set*() function: {type(input_arg)}!") - if len(input_kwargs): for key, val in input_kwargs.items(): # ensure all values are strings to align it on one type: dict[str, str] @@ -886,21 +842,17 @@ def isParameterChangeable( def setContinuous( self, - *args: Any, **kwargs: dict[str, Any], ) -> bool: """ - This method is used to set continuous values. It can be called: - with a sequence of continuous name and assigning corresponding values as arguments as show in the example below: - usage - >>> setContinuous("Name=value") # depreciated - >>> setContinuous(["Name1=value1","Name2=value2"]) # depreciated + This method is used to set continuous values. + usage: >>> setContinuous(Name1="value1", Name2="value2") >>> param = {"Name1": "value1", "Name2": "value2"} >>> setContinuous(**param) """ - inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs) + inputdata = self._prepare_input_data(input_kwargs=kwargs) return self._set_method_helper( inputdata=inputdata, @@ -910,21 +862,17 @@ def setContinuous( def setParameters( self, - *args: Any, **kwargs: dict[str, Any], ) -> bool: """ - This method is used to set parameter values. It can be called: - with a sequence of parameter name and assigning corresponding value as arguments as show in the example below: - usage - >>> setParameters("Name=value") # depreciated - >>> setParameters(["Name1=value1","Name2=value2"]) # depreciated + This method is used to set parameter values + usage: >>> setParameters(Name1="value1", Name2="value2") >>> param = {"Name1": "value1", "Name2": "value2"} >>> setParameters(**param) """ - inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs) + inputdata = self._prepare_input_data(input_kwargs=kwargs) return self._set_method_helper( inputdata=inputdata, @@ -934,22 +882,17 @@ def setParameters( def setSimulationOptions( self, - *args: Any, **kwargs: dict[str, Any], ) -> bool: """ - This method is used to set simulation options. It can be called: - with a sequence of simulation options name and assigning corresponding values as arguments as show in the - example below: - usage - >>> setSimulationOptions("Name=value") # depreciated - >>> setSimulationOptions(["Name1=value1","Name2=value2"]) # depreciated + This method is used to set simulation options. + usage: >>> setSimulationOptions(Name1="value1", Name2="value2") >>> param = {"Name1": "value1", "Name2": "value2"} >>> setSimulationOptions(**param) """ - inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs) + inputdata = self._prepare_input_data(input_kwargs=kwargs) return self._set_method_helper( inputdata=inputdata, @@ -959,22 +902,17 @@ def setSimulationOptions( def setLinearizationOptions( self, - *args: Any, **kwargs: dict[str, Any], ) -> bool: """ - This method is used to set linearization options. It can be called: - with a sequence of linearization options name and assigning corresponding value as arguments as show in the - example below - usage - >>> setLinearizationOptions("Name=value") # depreciated - >>> setLinearizationOptions(["Name1=value1","Name2=value2"]) # depreciated + This method is used to set linearization options. + usage: >>> setLinearizationOptions(Name1="value1", Name2="value2") >>> param = {"Name1": "value1", "Name2": "value2"} >>> setLinearizationOptions(**param) """ - inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs) + inputdata = self._prepare_input_data(input_kwargs=kwargs) return self._set_method_helper( inputdata=inputdata, @@ -984,22 +922,17 @@ def setLinearizationOptions( def setOptimizationOptions( self, - *args: Any, **kwargs: dict[str, Any], ) -> bool: """ - This method is used to set optimization options. It can be called: - with a sequence of optimization options name and assigning corresponding values as arguments as show in the - example below: - usage - >>> setOptimizationOptions("Name=value") # depreciated - >>> setOptimizationOptions(["Name1=value1","Name2=value2"]) # depreciated + This method is used to set optimization options. + usage: >>> setOptimizationOptions(Name1="value1", Name2="value2") >>> param = {"Name1": "value1", "Name2": "value2"} >>> setOptimizationOptions(**param) """ - inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs) + inputdata = self._prepare_input_data(input_kwargs=kwargs) return self._set_method_helper( inputdata=inputdata, @@ -1013,19 +946,17 @@ def setInputs( **kwargs: dict[str, Any], ) -> bool: """ - This method is used to set input values. It can be called with a sequence of input name and assigning - corresponding values as arguments as show in the example below. Compared to other set*() methods this is a - special case as value could be a list of tuples - these are converted to a string in _prepare_input_data() - and restored here via ast.literal_eval(). + This method is used to set input values. - >>> setInputs("Name=value") # depreciated - >>> setInputs(["Name1=value1","Name2=value2"]) # depreciated + Compared to other set*() methods this is a special case as value could be a list of tuples - these are + converted to a string in _prepare_input_data() and restored here via ast.literal_eval(). + usage: >>> setInputs(Name1="value1", Name2="value2") >>> param = {"Name1": "value1", "Name2": "value2"} >>> setInputs(**param) """ - inputdata = self._prepare_input_data(input_args=args, input_kwargs=kwargs) + inputdata = self._prepare_input_data(input_kwargs=kwargs) for key, val in inputdata.items(): if key not in self._inputs: diff --git a/tests/test_ModelicaSystemOMC.py b/tests/test_ModelicaSystemOMC.py index 0b642089..bff63315 100644 --- a/tests/test_ModelicaSystemOMC.py +++ b/tests/test_ModelicaSystemOMC.py @@ -64,9 +64,8 @@ def test_setParameters(): model_name="BouncingBall", ) - # method 1 (test depreciated variants) - mod.setParameters("e=1.234") - mod.setParameters(["g=321.0"]) + mod.setParameters(e=1.234) + mod.setParameters(g=321.0) assert mod.getParameters("e") == ["1.234"] assert mod.getParameters("g") == ["321.0"] assert mod.getParameters() == { @@ -76,7 +75,6 @@ def test_setParameters(): with pytest.raises(KeyError): mod.getParameters("thisParameterDoesNotExist") - # method 2 (new style) pvals = {"e": 21.3, "g": 0.12} mod.setParameters(**pvals) assert mod.getParameters() == { diff --git a/tests_v400/test_ModelicaSystem.py b/tests_v400/test_ModelicaSystem.py index c55e95fc..aa713af0 100644 --- a/tests_v400/test_ModelicaSystem.py +++ b/tests_v400/test_ModelicaSystem.py @@ -35,7 +35,7 @@ def test_setParameters(): mod = OMPython.ModelicaSystem(model_path + "BouncingBall.mo", "BouncingBall") # method 1 - mod.setParameters(pvals={"e": 1.234}) + mod.setParameters(pvals="e=1.234") mod.setParameters(pvals={"g": 321.0}) assert mod.getParameters("e") == ["1.234"] assert mod.getParameters("g") == ["321.0"] From 2b5c2dbf45302e2ca7a0ddb3819446b32421c522 Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 11 May 2026 21:11:55 +0200 Subject: [PATCH 08/14] add missing import for warnings --- OMPython/ModelicaSystem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index ce4f76ba..067fed24 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -8,6 +8,7 @@ import pathlib import platform from typing import Any, Optional +import warnings import numpy as np From 7d37c85129d846944739f1091cf71ad6dd117f0f Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 16 Feb 2026 19:22:40 +0100 Subject: [PATCH 09/14] G005-remove_depreciated_functionality2 [OMCSessionABC] remove execute(); still available in compatibility v4.0.0 [ModelicaSystem] define _set_compatibility_helper() as static [ModelExecutionCmd] remove depreciated simflags [test_ModelSystemCmd/ModelExecutionCmd] fix test due to changes [ModelicaSystemCmd] cleanup - do not define (unused / not useable) class --- OMPython/ModelicaSystem.py | 149 +++++++++++++++++++++------ OMPython/OMCSession.py | 9 +- OMPython/__init__.py | 4 +- OMPython/model_execution.py | 43 -------- OMPython/modelica_system_abc.py | 20 ---- OMPython/om_session_omc.py | 11 -- tests/test_ModelExecutionCmd.py | 14 +-- tests/test_ZMQ.py | 8 +- tests_v400/test_ModelicaSystemCmd.py | 14 +-- 9 files changed, 145 insertions(+), 127 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 067fed24..514043d7 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -4,9 +4,9 @@ """ import logging +import numbers import os import pathlib -import platform from typing import Any, Optional import warnings @@ -16,10 +16,14 @@ ModelExecutionConfig, ModelExecutionException, ) +from OMPython.om_session_abc import ( + OMPathABC, +) from OMPython.om_session_omc import ( OMCSessionLocal, ) from OMPython.modelica_system_abc import ( + LinearizationResult, ModelicaSystemError, ) from OMPython.modelica_system_omc import ( @@ -73,8 +77,73 @@ def __init__( def setCommandLineOptions(self, commandLineOptions: str): super().set_command_line_options(command_line_option=commandLineOptions) - def _set_compatibility_helper( + def simulate_cmd( # type: ignore[override] + self, + result_file: OMPathABC, + simflags: Optional[str] = None, + simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, + ) -> ModelExecutionCmd: + """ + Compatibility layer for OMPython v4.0.0 - keep simflags available and use ModelicaSystemCmd! + """ + + if simargs is None: + simargs = {} + + if simflags is not None: + simargs_extra = parse_simflags(simflags=simflags) + simargs = simargs | simargs_extra + + return super().simulate_cmd( + result_file=result_file, + simargs=simargs, + ) + + def simulate( # type: ignore[override] + self, + resultfile: Optional[str | os.PathLike] = None, + simflags: Optional[str] = None, + simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, + ) -> None: + """ + Compatibility layer for OMPython v4.0.0 - keep simflags available and use ModelicaSystemCmd! + """ + + if simargs is None: + simargs = {} + + if simflags is not None: + simargs_extra = parse_simflags(simflags=simflags) + simargs = simargs | simargs_extra + + return super().simulate( + resultfile=resultfile, + simargs=simargs, + ) + + def linearize( # type: ignore[override] self, + lintime: Optional[float] = None, + simflags: Optional[str] = None, + simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, + ) -> LinearizationResult: + """ + Compatibility layer for OMPython v4.0.0 - keep simflags available and use ModelicaSystemCmd! + """ + if simargs is None: + simargs = {} + + if simflags is not None: + simargs_extra = parse_simflags(simflags=simflags) + simargs = simargs | simargs_extra + + return super().linearize( + lintime=lintime, + simargs=simargs, + ) + + @staticmethod + def _set_compatibility_helper( pkey: str, args: Any, kwargs: dict[str, Any], @@ -330,7 +399,12 @@ class ModelicaSystemDoE(ModelicaDoEOMC): @depreciated_class(msg="Please use class ModelExecutionConfig instead!") class ModelicaSystemCmd(ModelExecutionConfig): """ - Compatibility class; in the new version it is renamed as ModelExecutionConfig. + Compatibility class; not much content. + + Missing definitions: + * get_exe() - see self.definition.cmd_model_executable + * get_cmd() - use self.get_cmd_args() or self.definition().get_cmd() + * run() - use self.definition().run() """ def __init__( @@ -346,35 +420,44 @@ def __init__( model_name=modelname, ) - def get_exe(self) -> pathlib.Path: - """Get the path to the compiled model executable.""" - - path_run = pathlib.Path(self._runpath) - if platform.system() == "Windows": - path_exe = path_run / f"{self._model_name}.exe" - else: - path_exe = path_run / self._model_name - if not path_exe.exists(): - raise ModelicaSystemError(f"Application file path not found: {path_exe}") - - return path_exe - - def get_cmd(self) -> list: - """ - Get a list with the path to the executable and all command line args. - - This can later be used as an argument for subprocess.run(). - """ - - cmdl = [self.get_exe().as_posix()] + self.get_cmd_args() - - return cmdl +def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | numbers.Number]]: + """ + Parse a simflag definition; this is deprecated! - def run(self) -> int: - cmd_definition = self.definition() - try: - returncode = cmd_definition.run() - except ModelExecutionException as exc: - raise ModelicaSystemError(f"Cannot execute model: {exc}") from exc - return returncode + The return data can be used as input for self.args_set(). + """ + warnings.warn( + message="The argument 'simflags' is depreciated and will be removed in future versions; " + "please use 'simargs' instead", + category=DeprecationWarning, + stacklevel=2, + ) + + simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {} + + args = [s for s in simflags.split(' ') if s] + for arg in args: + if arg[0] != '-': + raise ModelExecutionException(f"Invalid simulation flag: {arg}") + arg = arg[1:] + parts = arg.split('=') + if len(parts) == 1: + simargs[parts[0]] = None + elif parts[0] == 'override': + override = '='.join(parts[1:]) + + override_dict = {} + for item in override.split(','): + kv = item.split('=') + if not 0 < len(kv) < 3: + raise ModelExecutionException(f"Invalid value for '-override': {override}") + if kv[0]: + try: + override_dict[kv[0]] = kv[1] + except (KeyError, IndexError) as ex: + raise ModelExecutionException(f"Invalid value for '-override': {override}") from ex + + simargs[parts[0]] = override_dict + + return simargs diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index b7a4c1dd..35e1271a 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -7,6 +7,7 @@ import logging from typing import Any, Optional +import warnings import pyparsing @@ -272,7 +273,13 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC return self.omc_process.omcpath_tempdir(tempdir_base=tempdir_base) def execute(self, command: str): - return self.omc_process.execute(command=command) + warnings.warn( + message="This function is depreciated and will be removed in future versions; " + "please use sendExpression() instead", + category=DeprecationWarning, + stacklevel=2, + ) + return self.omc_process.sendExpression(expr=command, parsed=False) def sendExpression(self, command: str, parsed: bool = True) -> Any: # pylint: disable=W0237 """ diff --git a/OMPython/__init__.py b/OMPython/__init__.py index f3526da9..848421c5 100644 --- a/OMPython/__init__.py +++ b/OMPython/__init__.py @@ -61,8 +61,8 @@ # the imports below are compatibility functionality (OMPython v4.0.0) from OMPython.ModelicaSystem import ( ModelicaSystem, - ModelicaSystemCmd, ModelicaSystemDoE, + parse_simflags, ) from OMPython.OMCSession import ( OMCSessionCmd, @@ -109,9 +109,9 @@ 'OMPathRunnerLocal', 'OMSessionRunner', - 'ModelicaSystemCmd', 'ModelicaSystem', 'ModelicaSystemDoE', + 'parse_simflags', 'OMCSessionCmd', diff --git a/OMPython/model_execution.py b/OMPython/model_execution.py index 87fc6bdf..d3b344d1 100644 --- a/OMPython/model_execution.py +++ b/OMPython/model_execution.py @@ -12,7 +12,6 @@ import re import subprocess from typing import Any, Optional -import warnings # define logger using the current module name as ID logger = logging.getLogger(__name__) @@ -314,45 +313,3 @@ def definition(self) -> ModelExecutionRun: ) return omc_run_data - - @staticmethod - def parse_simflags(simflags: str) -> dict[str, Optional[str | dict[str, Any] | numbers.Number]]: - """ - Parse a simflag definition; this is deprecated! - - The return data can be used as input for self.args_set(). - """ - warnings.warn( - message="The argument 'simflags' is depreciated and will be removed in future versions; " - "please use 'simargs' instead", - category=DeprecationWarning, - stacklevel=2, - ) - - simargs: dict[str, Optional[str | dict[str, Any] | numbers.Number]] = {} - - args = [s for s in simflags.split(' ') if s] - for arg in args: - if arg[0] != '-': - raise ModelExecutionException(f"Invalid simulation flag: {arg}") - arg = arg[1:] - parts = arg.split('=') - if len(parts) == 1: - simargs[parts[0]] = None - elif parts[0] == 'override': - override = '='.join(parts[1:]) - - override_dict = {} - for item in override.split(','): - kv = item.split('=') - if not 0 < len(kv) < 3: - raise ModelExecutionException(f"Invalid value for '-override': {override}") - if kv[0]: - try: - override_dict[kv[0]] = kv[1] - except (KeyError, IndexError) as ex: - raise ModelExecutionException(f"Invalid value for '-override': {override}") from ex - - simargs[parts[0]] = override_dict - - return simargs diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index f3e02ddf..6ddb8f5d 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -620,7 +620,6 @@ def _process_override_data( def simulate_cmd( self, result_file: OMPathABC, - simflags: Optional[str] = None, simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, ) -> ModelExecutionConfig: """ @@ -636,7 +635,6 @@ def simulate_cmd( Parameters ---------- result_file - simflags simargs Returns @@ -656,10 +654,6 @@ def simulate_cmd( # always define the result file to use om_cmd.arg_set(key="r", val=result_file.as_posix()) - # allow runtime simulation flags from user input - if simflags is not None: - om_cmd.args_set(args=om_cmd.parse_simflags(simflags=simflags)) - if simargs: om_cmd.args_set(args=simargs) @@ -693,7 +687,6 @@ def simulate_cmd( def simulate( self, resultfile: Optional[str | os.PathLike] = None, - simflags: Optional[str] = None, simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, ) -> None: """Simulate the model according to simulation options. @@ -702,16 +695,11 @@ def simulate( Args: resultfile: Path to a custom result file - simflags: String of extra command line flags for the model binary. - This argument is deprecated, use simargs instead. simargs: Dict with simulation runtime flags. Examples: mod.simulate() mod.simulate(resultfile="a.mat") - # set runtime simulation flags, deprecated - mod.simulate(simflags="-noEventEmit -noRestart -override=e=0.3,g=10") - # using simargs mod.simulate(simargs={"noEventEmit": None, "noRestart": None, "override": "override": {"e": 0.3, "g": 10}}) """ @@ -730,7 +718,6 @@ def simulate( om_cmd = self.simulate_cmd( result_file=self._result_file, - simflags=simflags, simargs=simargs, ) @@ -1060,7 +1047,6 @@ def _createCSVData(self, csvfile: Optional[OMPathABC] = None) -> OMPathABC: def linearize( self, lintime: Optional[float] = None, - simflags: Optional[str] = None, simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, ) -> LinearizationResult: """Linearize the model according to linearization options. @@ -1069,8 +1055,6 @@ def linearize( Args: lintime: Override "stopTime" value. - simflags: String of extra command line flags for the model binary. - This argument is deprecated, use simargs instead. simargs: A dict with command line flags and possible options; example: "simargs={'csvInput': 'a.csv'}" Returns: @@ -1123,10 +1107,6 @@ def linearize( f"<= lintime <= {self._linearization_options['stopTime']}") om_cmd.arg_set(key="l", val=str(lintime)) - # allow runtime simulation flags from user input - if simflags is not None: - om_cmd.args_set(args=om_cmd.parse_simflags(simflags=simflags)) - if simargs: om_cmd.args_set(args=simargs) diff --git a/OMPython/om_session_omc.py b/OMPython/om_session_omc.py index 4fafc3a1..52d63b55 100644 --- a/OMPython/om_session_omc.py +++ b/OMPython/om_session_omc.py @@ -21,7 +21,6 @@ import time from typing import Any, Optional, Tuple import uuid -import warnings import psutil import pyparsing @@ -387,16 +386,6 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC return self._tempdir(tempdir_base=tempdir_base) - def execute(self, command: str): - warnings.warn( - message="This function is depreciated and will be removed in future versions; " - "please use sendExpression() instead", - category=DeprecationWarning, - stacklevel=2, - ) - - return self.sendExpression(command, parsed=False) - def sendExpression(self, expr: str, parsed: bool = True) -> Any: """ Send an expression to the OMC server and return the result. diff --git a/tests/test_ModelExecutionCmd.py b/tests/test_ModelExecutionCmd.py index 71e96fc1..19111070 100644 --- a/tests/test_ModelExecutionCmd.py +++ b/tests/test_ModelExecutionCmd.py @@ -38,17 +38,19 @@ def mscmd_firstorder(model_firstorder): def test_simflags(mscmd_firstorder): mscmd = mscmd_firstorder - mscmd.args_set({ + mscmd.args_set(args={ + "override": { + 'b': 2, + 'a': 4, + }, + "noRestart": None, "noEventEmit": None, - "override": {'b': 2} }) - with pytest.deprecated_call(): - mscmd.args_set(args=mscmd.parse_simflags(simflags="-noEventEmit -noRestart -override=a=1,x=3")) assert mscmd.get_cmd_args() == [ '-noEventEmit', '-noRestart', - '-override=a=1,b=2,x=3', + '-override=a=4,b=2', ] mscmd.args_set({ @@ -58,5 +60,5 @@ def test_simflags(mscmd_firstorder): assert mscmd.get_cmd_args() == [ '-noEventEmit', '-noRestart', - '-override=a=1,x=3', + '-override=a=4', ] diff --git a/tests/test_ZMQ.py b/tests/test_ZMQ.py index 89a8387b..1ba62cb9 100644 --- a/tests/test_ZMQ.py +++ b/tests/test_ZMQ.py @@ -38,14 +38,12 @@ def test_Simulate(omcs, model_time_str): assert omcs.sendExpression('res.resultFile') -def test_execute(omcs): - with pytest.deprecated_call(): - assert omcs.execute('"HelloWorld!"') == '"HelloWorld!"\n' +def test_sendExpression(omcs): assert omcs.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' assert omcs.sendExpression('"HelloWorld!"', parsed=True) == 'HelloWorld!' -def test_omcprocessport_execute(omcs): +def test_sendExpression_port(omcs): port = omcs.get_port() omcs2 = OMPython.OMCSessionPort(omc_port=port) @@ -58,7 +56,7 @@ def test_omcprocessport_execute(omcs): del omcs2 -def test_omcprocessport_simulate(omcs, model_time_str): +def test_Simulate_port(omcs, model_time_str): port = omcs.get_port() omcs2 = OMPython.OMCSessionPort(omc_port=port) diff --git a/tests_v400/test_ModelicaSystemCmd.py b/tests_v400/test_ModelicaSystemCmd.py index 3544a1bd..75116894 100644 --- a/tests_v400/test_ModelicaSystemCmd.py +++ b/tests_v400/test_ModelicaSystemCmd.py @@ -18,7 +18,11 @@ def model_firstorder(tmp_path): @pytest.fixture def mscmd_firstorder(model_firstorder): mod = OMPython.ModelicaSystem(fileName=model_firstorder.as_posix(), modelName="M") - mscmd = OMPython.ModelicaSystemCmd(runpath=mod.getWorkDirectory(), modelname=mod._model_name) + mscmd = OMPython.ModelExecutionCmd( + runpath=mod.getWorkDirectory(), + model_name=mod._model_name, + cmd_prefix=[], + ) return mscmd @@ -30,10 +34,9 @@ def test_simflags(mscmd_firstorder): "override": {'b': 2} }) with pytest.deprecated_call(): - mscmd.args_set(args=mscmd.parse_simflags(simflags="-noEventEmit -noRestart -override=a=1,x=3")) + mscmd.args_set(args=OMPython.parse_simflags(simflags="-noEventEmit -noRestart -override=a=1,x=3")) - assert mscmd.get_cmd() == [ - mscmd.get_exe().as_posix(), + assert mscmd.get_cmd_args() == [ '-noEventEmit', '-noRestart', '-override=a=1,b=2,x=3', @@ -43,8 +46,7 @@ def test_simflags(mscmd_firstorder): "override": {'b': None}, }) - assert mscmd.get_cmd() == [ - mscmd.get_exe().as_posix(), + assert mscmd.get_cmd_args() == [ '-noEventEmit', '-noRestart', '-override=a=1,x=3', From 8733d1eb57fe34841d2c2868bc71f0d5952986ab Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 27 Jun 2026 19:03:00 +0200 Subject: [PATCH 10/14] fix missing class rename (ModelExecutionCmd => ModelExecutionConfig) --- OMPython/ModelicaSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 514043d7..3f15c372 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -82,7 +82,7 @@ def simulate_cmd( # type: ignore[override] result_file: OMPathABC, simflags: Optional[str] = None, simargs: Optional[dict[str, Optional[str | dict[str, Any] | numbers.Number]]] = None, - ) -> ModelExecutionCmd: + ) -> ModelExecutionConfig: """ Compatibility layer for OMPython v4.0.0 - keep simflags available and use ModelicaSystemCmd! """ From aa69241f003558b60801556112d714e1db8e7104 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 27 Jun 2026 20:28:41 +0200 Subject: [PATCH 11/14] another fix - missing class rename (ModelExecutionCmd => ModelExecutionConfig) --- tests_v400/test_ModelicaSystemCmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_v400/test_ModelicaSystemCmd.py b/tests_v400/test_ModelicaSystemCmd.py index 75116894..0f4535fd 100644 --- a/tests_v400/test_ModelicaSystemCmd.py +++ b/tests_v400/test_ModelicaSystemCmd.py @@ -18,7 +18,7 @@ def model_firstorder(tmp_path): @pytest.fixture def mscmd_firstorder(model_firstorder): mod = OMPython.ModelicaSystem(fileName=model_firstorder.as_posix(), modelName="M") - mscmd = OMPython.ModelExecutionCmd( + mscmd = OMPython.ModelExecutionConfig( runpath=mod.getWorkDirectory(), model_name=mod._model_name, cmd_prefix=[], From 6d674473f67042ae71774074f7b133dabb31a822 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 6 Mar 2026 19:27:38 +0100 Subject: [PATCH 12/14] [ModelicaSystemABC] check OM version - force the version used by the model executable --- OMPython/modelica_system_abc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 6ddb8f5d..ea0a90a8 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -216,6 +216,15 @@ def _xmlparse(self, xml_file: OMPathABC): root = tree.getroot() if root is None: raise ModelicaSystemError(f"Cannot read XML file: {xml_file}") + # check OM version - force the version used by the model executable + if 'generationTool' in root.attrib: + generation_tool_version = self._parse_om_version(version=root.attrib['generationTool']) + if self._version != generation_tool_version: + logger.warning(f"Mismatch in OpenModelica version: {self._version!r} (OMSession) " + f"vs. {generation_tool_version!r} (model executable) " + f"- using {generation_tool_version!r}!") + self._version = generation_tool_version + for attr in root.iter('DefaultExperiment'): for key in ("startTime", "stopTime", "stepSize", "tolerance", "solver", "outputFormat"): From 4d34aaf993246644c4d1eac1d3ff7291af9e8e98 Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 6 Mar 2026 20:13:57 +0100 Subject: [PATCH 13/14] [ModelicaSystemABC] define setInputCSV() - function to define input based on the content of a CSV file --- OMPython/modelica_system_abc.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index ea0a90a8..72665fd4 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -5,6 +5,7 @@ import abc import ast +import csv from dataclasses import dataclass import logging import numbers @@ -998,6 +999,44 @@ def setInputs( return True + def setInputsCSV( + self, + csvfile: os.PathLike, + ) -> None: + """ + Read content from a CSV file and use it to define the time based input data. + """ + + # real type is 'dict[str, list[tuple[float, float]]]' - 'dict[str, Any]' is used to make setInputs() happy + inputs: dict[str, Any] = {} + try: + with open(csvfile, newline='') as csvfh: + dialect = csv.Sniffer().sniff(csvfh.read(1024)) + csvfh.seek(0) + reader = csv.DictReader(csvfh, dialect=dialect) + + keys: list[str] = [] + for idx, line in enumerate(reader): + if not keys: + keys = list(line.keys()) + for var in keys[1:]: + if var in inputs: + raise ModelicaSystemError(f"Error reading {csvfile}: duplicated column {var}!") + inputs[var] = [] + try: + # use key[0] as time; all other columns use the header as name + for var in keys[1:]: + inputs[var].append((float(line[keys[0]]), float(line[var]))) + except (ValueError, TypeError) as exc2: + raise ModelicaSystemError(f"Invalid value reading {csvfile} line {idx}/{var}: " + f"{line}!") from exc2 + + except IOError as exc1: + raise ModelicaSystemError(f"Error reading {csvfile}: {exc1}") from exc1 + + if inputs: + self.setInputs(**inputs) + def _createCSVData(self, csvfile: Optional[OMPathABC] = None) -> OMPathABC: """ Create a csv file with inputs for the simulation/optimization of the model. If csvfile is provided as argument, From 5354c7de3fa3962965104b005b496432bb2f7fd7 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 1 Apr 2026 22:55:31 +0200 Subject: [PATCH 14/14] add toInputs() - convert pandas DataFrame.to_dict(orient='list') output to OMPython input based on code written by joewa (see https://github.com/OpenModelica/OMPython/pull/447#issuecomment-4101449288) --- OMPython/modelica_system_abc.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 72665fd4..15261141 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -937,6 +937,29 @@ def setOptimizationOptions( datatype="optimization-option", overridedata=None) + @staticmethod + def toInputs(data: dict[str, list[float]]) -> dict[str, list[tuple[float, float]]]: + """ + Converts a dictionary of lists (from pandas DataFrame.to_dict(orient='list')) + into the OMPython setInputs input format. + + Example: mod.setInputs(**toInputs(pdf.to_dict(orient='list'))) + + Assumes the dictionary contains a key named 'time'. + """ + if "time" not in data: + raise ValueError("The provided data must contain a 'time' key.") + + time_series = data["time"] + + inputs = { + var_name: list(zip(time_series, values)) + for var_name, values in data.items() + if var_name != "time" + } + + return inputs + def setInputs( self, *args: Any,