Skip to content

Commit 0052e65

Browse files
authored
Merge branch 'master' into ModelicaSystem_small_changes
2 parents 1d8e992 + d2ebb57 commit 0052e65

2 files changed

Lines changed: 79 additions & 36 deletions

File tree

OMPython/ModelicaSystem.py

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"""
3434

3535
import ast
36-
import csv
3736
from dataclasses import dataclass
3837
import logging
3938
import numbers
@@ -382,7 +381,6 @@ def __init__(
382381
if not isinstance(lmodel, list):
383382
raise ModelicaSystemError(f"Invalid input type for lmodel: {type(lmodel)} - list expected!")
384383

385-
self._xml_file = None
386384
self._lmodel = lmodel # may be needed if model is derived from other model
387385
self._model_name = modelName # Model class name
388386
self._file_name = pathlib.Path(fileName).resolve() if fileName is not None else None # Model file/package name
@@ -479,8 +477,8 @@ def buildModel(self, variableFilter: Optional[str] = None):
479477
buildModelResult = self._requestApi("buildModel", self._model_name, properties=varFilter)
480478
logger.debug("OM model build result: %s", buildModelResult)
481479

482-
self._xml_file = pathlib.Path(buildModelResult[0]).parent / buildModelResult[1]
483-
self._xmlparse()
480+
xml_file = pathlib.Path(buildModelResult[0]).parent / buildModelResult[1]
481+
self._xmlparse(xml_file=xml_file)
484482

485483
def sendExpression(self, expr: str, parsed: bool = True):
486484
try:
@@ -506,30 +504,42 @@ def _requestApi(self, apiName, entity=None, properties=None): # 2
506504

507505
return self.sendExpression(exp)
508506

509-
def _xmlparse(self):
510-
if not self._xml_file.is_file():
511-
raise ModelicaSystemError(f"XML file not generated: {self._xml_file}")
507+
def _xmlparse(self, xml_file: pathlib.Path):
508+
if not xml_file.is_file():
509+
raise ModelicaSystemError(f"XML file not generated: {xml_file}")
512510

513-
tree = ET.parse(self._xml_file)
511+
xml_content = xml_file.read_text()
512+
tree = ET.ElementTree(ET.fromstring(xml_content))
514513
rootCQ = tree.getroot()
515514
for attr in rootCQ.iter('DefaultExperiment'):
516515
for key in ("startTime", "stopTime", "stepSize", "tolerance",
517516
"solver", "outputFormat"):
518-
self._simulate_options[key] = attr.get(key)
517+
self._simulate_options[key] = str(attr.get(key))
519518

520519
for sv in rootCQ.iter('ScalarVariable'):
521-
scalar = {}
522-
for key in ("name", "description", "variability", "causality", "alias"):
523-
scalar[key] = sv.get(key)
524-
scalar["changeable"] = sv.get('isValueChangeable')
525-
scalar["aliasvariable"] = sv.get('aliasVariable')
520+
translations = {
521+
"alias": "alias",
522+
"aliasvariable": "aliasVariable",
523+
"causality": "causality",
524+
"changeable": "isValueChangeable",
525+
"description": "description",
526+
"name": "name",
527+
"variability": "variability",
528+
}
529+
530+
scalar: dict[str, Any] = {}
531+
for key_dst, key_src in translations.items():
532+
val = sv.get(key_src)
533+
scalar[key_dst] = None if val is None else str(val)
534+
526535
ch = list(sv)
527536
for att in ch:
528537
scalar["start"] = att.get('start')
529538
scalar["min"] = att.get('min')
530539
scalar["max"] = att.get('max')
531540
scalar["unit"] = att.get('unit')
532541

542+
# save parameters in the corresponding class variables
533543
if scalar["variability"] == "parameter":
534544
if scalar["name"] in self._override_variables:
535545
self._params[scalar["name"]] = self._override_variables[scalar["name"]]
@@ -953,16 +963,17 @@ def simulate_cmd(
953963
if simargs:
954964
om_cmd.args_set(args=simargs)
955965

956-
overrideFile = self._tempdir / f"{self._model_name}_override.txt"
957966
if self._override_variables or self._simulate_options_override:
958-
tmpdict = self._override_variables.copy()
959-
tmpdict.update(self._simulate_options_override)
960-
# write to override file
961-
with open(file=overrideFile, mode="w", encoding="utf-8") as fh:
962-
for key, value in tmpdict.items():
963-
fh.write(f"{key}={value}\n")
967+
override_file = result_file.parent / f"{result_file.stem}_override.txt"
968+
969+
override_content = (
970+
"\n".join([f"{key}={value}" for key, value in self._override_variables.items()])
971+
+ "\n".join([f"{key}={value}" for key, value in self._simulate_options_override.items()])
972+
+ "\n"
973+
)
964974

965-
om_cmd.arg_set(key="overrideFile", val=overrideFile.as_posix())
975+
override_file.write_text(override_content)
976+
om_cmd.arg_set(key="overrideFile", val=override_file.as_posix())
966977

967978
if self._inputs: # if model has input quantities
968979
for key in self._inputs:
@@ -1420,9 +1431,10 @@ def _createCSVData(self, csvfile: Optional[pathlib.Path] = None) -> pathlib.Path
14201431
if csvfile is None:
14211432
csvfile = self._tempdir / f'{self._model_name}.csv'
14221433

1423-
with open(file=csvfile, mode="w", encoding="utf-8", newline="") as fh:
1424-
writer = csv.writer(fh)
1425-
writer.writerows(csv_rows)
1434+
# basic definition of a CSV file using csv_rows as input
1435+
csv_content = "\n".join([",".join(map(str, row)) for row in csv_rows]) + "\n"
1436+
1437+
csvfile.write_text(csv_content)
14261438

14271439
return csvfile
14281440

@@ -1534,7 +1546,8 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
15341546
compatibility, because linearize() used to return `[A, B, C, D]`.
15351547
"""
15361548

1537-
if self._xml_file is None:
1549+
if len(self._quantities) == 0:
1550+
# if self._quantities has no content, the xml file was not parsed; see self._xmlparse()
15381551
raise ModelicaSystemError(
15391552
"Linearization cannot be performed as the model is not build, "
15401553
"use ModelicaSystem() to build the model first"
@@ -1545,10 +1558,10 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N
15451558
overrideLinearFile = self._tempdir / f'{self._model_name}_override_linear.txt'
15461559

15471560
with open(file=overrideLinearFile, mode="w", encoding="utf-8") as fh:
1548-
for key, value in self._override_variables.items():
1549-
fh.write(f"{key}={value}\n")
1550-
for key, value in self._linearization_options.items():
1551-
fh.write(f"{key}={value}\n")
1561+
for key1, value1 in self._override_variables.items():
1562+
fh.write(f"{key1}={value1}\n")
1563+
for key2, value2 in self._linearization_options.items():
1564+
fh.write(f"{key2}={value2}\n")
15521565

15531566
om_cmd.arg_set(key="overrideFile", val=overrideLinearFile.as_posix())
15541567

OMPython/OMCSession.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,11 +483,17 @@ def __del__(self):
483483
self._omc_process = None
484484

485485
def get_port(self) -> Optional[str]:
486+
"""
487+
Get the port to connect to the OMC process.
488+
"""
486489
if not isinstance(self._omc_port, str):
487490
raise OMCSessionException(f"Invalid port to connect to OMC process: {self._omc_port}")
488491
return self._omc_port
489492

490493
def get_log(self) -> str:
494+
"""
495+
Get the log file content of the OMC session.
496+
"""
491497
if self._omc_loghandle is None:
492498
raise OMCSessionException("Log file not available!")
493499

@@ -509,6 +515,9 @@ def _get_portfile_path(self) -> Optional[pathlib.Path]:
509515

510516

511517
class OMCProcessPort(OMCProcess):
518+
"""
519+
OMCProcess implementation which uses a port to connect to an already running OMC server.
520+
"""
512521

513522
def __init__(
514523
self,
@@ -519,6 +528,9 @@ def __init__(
519528

520529

521530
class OMCProcessLocal(OMCProcess):
531+
"""
532+
OMCProcess implementation which runs the OMC server locally on the machine (Linux / Windows).
533+
"""
522534

523535
def __init__(
524536
self,
@@ -600,6 +612,9 @@ def _omc_port_get(self) -> str:
600612

601613

602614
class OMCProcessDockerHelper(OMCProcess):
615+
"""
616+
Base class for OMCProcess implementations which run the OMC server in a Docker container.
617+
"""
603618

604619
def __init__(
605620
self,
@@ -692,20 +707,29 @@ def _omc_port_get(self) -> str:
692707
return port
693708

694709
def get_server_address(self) -> Optional[str]:
710+
"""
711+
Get the server address of the OMC server running in a Docker container.
712+
"""
695713
if self._dockerNetwork == "separate" and isinstance(self._dockerCid, str):
696714
output = subprocess.check_output(["docker", "inspect", self._dockerCid]).decode().strip()
697715
return json.loads(output)[0]["NetworkSettings"]["IPAddress"]
698716

699717
return None
700718

701719
def get_docker_container_id(self) -> str:
720+
"""
721+
Get the Docker container ID of the Docker container with the OMC server.
722+
"""
702723
if not isinstance(self._dockerCid, str):
703724
raise OMCSessionException(f"Invalid docker container ID: {self._dockerCid}!")
704725

705726
return self._dockerCid
706727

707728

708729
class OMCProcessDocker(OMCProcessDockerHelper):
730+
"""
731+
OMC process running in a Docker container.
732+
"""
709733

710734
def __init__(
711735
self,
@@ -764,9 +788,8 @@ def _docker_omc_cmd(
764788
if sys.platform == "win32":
765789
extraFlags = ["-d=zmqDangerousAcceptConnectionsFromAnywhere"]
766790
if not self._interactivePort:
767-
raise OMCSessionException("docker on Windows requires knowing which port to connect to. For "
768-
"dockerContainer=..., the container needs to have already manually exposed "
769-
"this port when it was started (-p 127.0.0.1:n:n) or you get an error later.")
791+
raise OMCSessionException("docker on Windows requires knowing which port to connect to - "
792+
"please set the interactivePort argument")
770793

771794
if sys.platform == "win32":
772795
if isinstance(self._interactivePort, str):
@@ -847,6 +870,9 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DummyPopen, str]:
847870

848871

849872
class OMCProcessDockerContainer(OMCProcessDockerHelper):
873+
"""
874+
OMC process running in a Docker container (by container ID).
875+
"""
850876

851877
def __init__(
852878
self,
@@ -892,9 +918,10 @@ def _docker_omc_cmd(self, omc_path_and_args_list) -> list:
892918
if sys.platform == "win32":
893919
extraFlags = ["-d=zmqDangerousAcceptConnectionsFromAnywhere"]
894920
if not self._interactivePort:
895-
raise OMCSessionException("docker on Windows requires knowing which port to connect to. For "
896-
"dockerContainer=..., the container needs to have already manually exposed "
897-
"this port when it was started (-p 127.0.0.1:n:n) or you get an error later.")
921+
raise OMCSessionException("Docker on Windows requires knowing which port to connect to - "
922+
"Please set the interactivePort argument. Furthermore, the container needs "
923+
"to have already manually exposed this port when it was started "
924+
"(-p 127.0.0.1:n:n) or you get an error later.")
898925

899926
if isinstance(self._interactivePort, int):
900927
extraFlags = extraFlags + [f"--interactivePort={int(self._interactivePort)}"]
@@ -936,6 +963,9 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DummyPopen]:
936963

937964

938965
class OMCProcessWSL(OMCProcess):
966+
"""
967+
OMC process running in Windows Subsystem for Linux (WSL).
968+
"""
939969

940970
def __init__(
941971
self,

0 commit comments

Comments
 (0)