Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions OMPython/ModelicaSystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
OMPathABC,

OMSessionABC,
OMSessionRunner,
)

# define logger using the current module name as ID
Expand Down Expand Up @@ -2232,6 +2233,12 @@ def get_session(self) -> OMSessionABC:
"""
return self._mod.get_session()

def get_resultpath(self) -> OMPathABC:
"""
Get the path there the result data is saved.
"""
return self._resultpath

def prepare(self) -> int:
"""
Prepare the DoE by evaluating the parameters. Each structural parameter requires a new instance of
Expand Down Expand Up @@ -2583,3 +2590,98 @@ class ModelicaSystemDoE(ModelicaDoEOMC):
"""
Compatibility class.
"""


class ModelicaSystemRunner(ModelicaSystemABC):
"""
Class to simulate a Modelica model using a pre-compiled model binary.
"""

def __init__(
self,
work_directory: Optional[str | os.PathLike] = None,
session: Optional[OMSessionABC] = None,
) -> None:
if session is None:
session = OMSessionRunner()

if not isinstance(session, OMSessionRunner):
raise ModelicaSystemError("Only working if OMCsessionDummy is used!")

super().__init__(
work_directory=work_directory,
session=session,
)

def setup(
self,
model_name: Optional[str] = None,
variable_filter: Optional[str] = None,
) -> None:
"""
Needed definitions to set up the runner class. This class expects the model (defined by model_name) to exists
within the working directory. At least two files are needed:

* model executable (as '<model_name>' or '<model_name>.exe'; in case of Windows additional '<model_name>.bat'
is expected to evaluate the path to needed dlls
* the model initialization file (as '<model_name>_init.xml')
"""

if self._model_name is not None:
raise ModelicaSystemError("Can not reuse this instance of ModelicaSystem "
f"defined for {repr(self._model_name)}!")

if model_name is None or not isinstance(model_name, str):
raise ModelicaSystemError("A model name must be provided!")

# set variables
self._model_name = model_name # Model class name
self._variable_filter = variable_filter

# test if the model can be executed
self.check_model_executable()

# read XML file
xml_file = self._session.omcpath(self.getWorkDirectory()) / f"{self._model_name}_init.xml"
self._xmlparse(xml_file=xml_file)


class ModelicaDoERunner(ModelicaDoEABC):
"""
Class to run DoEs based on a (Open)Modelica model using ModelicaSystemRunner

The example is the same as defined for ModelicaDoEABC
"""

def __init__(
self,
# ModelicaSystem definition to use
mod: ModelicaSystemABC,
# simulation specific input
# TODO: add more settings (simulation options, input options, ...)
simargs: Optional[dict[str, Optional[str | dict[str, str] | numbers.Number]]] = None,
# DoE specific inputs
resultpath: Optional[str | os.PathLike] = None,
parameters: Optional[dict[str, list[str] | list[int] | list[float]]] = None,
) -> None:
if not isinstance(mod, ModelicaSystemABC):
raise ModelicaSystemError(f"Invalid definition for ModelicaSystem*: {type(mod)}!")

super().__init__(
mod=mod,
simargs=simargs,
resultpath=resultpath,
parameters=parameters,
)

def _prepare_structure_parameters(
self,
idx_pc_structure: int,
pc_structure: Tuple,
param_structure: dict[str, list[str] | list[int] | list[float]],
) -> dict[str, str | int | float]:
if len(param_structure.keys()) > 0:
raise ModelicaSystemError(f"{self.__class__.__name__} can not handle structure parameters as it uses a "
"pre-compiled binary of model.")

return {}
162 changes: 159 additions & 3 deletions OMPython/OMCSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ class OMPathCompatibilityWindows(pathlib.WindowsPath, OMPathCompatibility):

OMPathABC = OMPathCompatibility
OMCPath = OMPathCompatibility
OMPathRunnerABC = OMPathCompatibility
OMPathRunnerLocal = OMPathCompatibility
else:
class OMPathABC(pathlib.PurePosixPath, metaclass=abc.ABCMeta):
"""
Expand Down Expand Up @@ -514,7 +516,95 @@ def size(self) -> int:

raise OMCSessionException(f"Error reading file size for path {self.as_posix()}!")

class OMPathRunnerABC(OMPathABC, metaclass=abc.ABCMeta):
"""
Base function for OMPath definitions *without* OMC server
"""

def _path(self) -> pathlib.Path:
return pathlib.Path(self.as_posix())

class _OMPathRunnerLocal(OMPathRunnerABC):
"""
Implementation of OMPathBase which does not use the session data at all. Thus, this implementation can run
locally without any usage of OMC.

This class is based on OMPathBase and, therefore, on pathlib.PurePosixPath. This is working well, but it is not
the correct implementation on Windows systems. To get a valid Windows representation of the path, use the
conversion via pathlib.Path(<OMCPathDummy>.as_posix()).
"""

def is_file(self) -> bool:
"""
Check if the path is a regular file.
"""
return self._path().is_file()

def is_dir(self) -> bool:
"""
Check if the path is a directory.
"""
return self._path().is_dir()

def is_absolute(self):
"""
Check if the path is an absolute path.
"""
return self._path().is_absolute()

def read_text(self) -> str:
"""
Read the content of the file represented by this path as text.
"""
return self._path().read_text(encoding='utf-8')

def write_text(self, data: str):
"""
Write text data to the file represented by this path.
"""
return self._path().write_text(data=data, encoding='utf-8')

def mkdir(self, parents: bool = True, exist_ok: bool = False):
"""
Create a directory at the path represented by this class.

The argument parents with default value True exists to ensure compatibility with the fallback solution for
Python < 3.12. In this case, pathlib.Path is used directly and this option ensures, that missing parent
directories are also created.
"""
return self._path().mkdir(parents=parents, exist_ok=exist_ok)

def cwd(self):
"""
Returns the current working directory as an OMPathBase object.
"""
return self._path().cwd()

def unlink(self, missing_ok: bool = False) -> None:
"""
Unlink (delete) the file or directory represented by this path.
"""
return self._path().unlink(missing_ok=missing_ok)

def resolve(self, strict: bool = False):
"""
Resolve the path to an absolute path. This is done based on available OMC functions.
"""
path_resolved = self._path().resolve(strict=strict)
return type(self)(path_resolved, session=self._session)

def size(self) -> int:
"""
Get the size of the file in bytes - implementation baseon on pathlib.Path.
"""
if not self.is_file():
raise OMCSessionException(f"Path {self.as_posix()} is not a file!")

path = self._path()
return path.stat().st_size

OMCPath = _OMCPath
OMPathRunnerLocal = _OMPathRunnerLocal


class ModelExecutionException(Exception):
Expand Down Expand Up @@ -719,12 +809,20 @@ def __init__(

# store variables
self._timeout = timeout
# command prefix (to be used for docker or WSL)
self._cmd_prefix: list[str] = []

def __post_init__(self) -> None:
"""
Post initialisation method.
"""

def get_cmd_prefix(self) -> list[str]:
"""
Get session definition used for this instance of OMPath.
"""
return self._cmd_prefix.copy()

@staticmethod
def escape_str(value: str) -> str:
"""
Expand Down Expand Up @@ -753,7 +851,7 @@ def set_workdir(self, workdir: OMPathABC) -> None:
@abc.abstractmethod
def omcpath(self, *path) -> OMPathABC:
"""
Create an OMPathBase object based on the given path segments and the current class.
Create an OMPathABC object based on the given path segments and the current class.
"""

@abc.abstractmethod
Expand Down Expand Up @@ -817,13 +915,12 @@ def __init__(
"""
Initialisation for OMCSession
"""
super().__init__(timeout=timeout)

# some helper data
self.model_execution_windows = platform.system() == "Windows"
self.model_execution_local = False

# store variables
self._timeout = timeout
# generate a random string for this instance of OMC
self._random_string = uuid.uuid4().hex
# get a temporary directory
Expand Down Expand Up @@ -900,6 +997,7 @@ def __del__(self):
self._omc_process.kill()
self._omc_process.wait()
finally:

self._omc_process = None

def _timeout_loop(
Expand Down Expand Up @@ -1735,3 +1833,61 @@ def _omc_port_get(self) -> str:
f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}")

return port


class OMSessionRunner(OMSessionABC):
"""
Implementation based on OMSessionABC without any use of an OMC server.
"""

def __init__(
self,
timeout: float = 10.00,
version: str = "1.27.0"
) -> None:
super().__init__(timeout=timeout)
self.model_execution_local = True
self._version = version

def __post_init__(self) -> None:
"""
No connection to an OMC server is created by this class!
"""

def model_execution_prefix(self, cwd: Optional[OMPathABC] = None) -> list[str]:
"""
Helper function which returns a command prefix.
"""
return []

def get_version(self) -> str:
"""
We can not provide an OM version as we are not link to an OMC server. Thus, the provided version string is used
directly.
"""
return self._version

def set_workdir(self, workdir: OMPathABC) -> None:
"""
Set the workdir for this session.
"""
os.chdir(workdir.as_posix())

def omcpath(self, *path) -> OMPathABC:
"""
Create an OMCPath object based on the given path segments and the current OMCSession* class.
"""
return OMPathRunnerLocal(*path, session=self)

def omcpath_tempdir(self, tempdir_base: Optional[OMPathABC] = None) -> OMPathABC:
"""
Get a temporary directory without using OMC.
"""
if tempdir_base is None:
tempdir_str = tempfile.gettempdir()
tempdir_base = self.omcpath(tempdir_str)

return self._tempdir(tempdir_base=tempdir_base)

def sendExpression(self, expr: str, parsed: bool = True) -> Any:
raise OMCSessionException(f"{self.__class__.__name__} does not uses an OMC server!")
9 changes: 9 additions & 0 deletions OMPython/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
ModelicaSystemDoE,
ModelicaDoEOMC,
ModelicaSystemError,
ModelicaSystemRunner,
ModelicaDoERunner,

doe_get_solutions,
)
from OMPython.OMCSession import (
OMPathABC,
OMCPath,

OMSessionRunner,

OMCSessionABC,

ModelExecutionData,
Expand Down Expand Up @@ -55,9 +59,14 @@
'ModelicaDoEOMC',
'ModelicaSystemError',

'ModelicaSystemRunner',
'ModelicaDoERunner',

'OMPathABC',
'OMCPath',

'OMSessionRunner',

'OMCSessionABC',

'doe_get_solutions',
Expand Down
Loading
Loading