Skip to content
Draft
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
12 changes: 6 additions & 6 deletions .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Test-Publish

on:
push:
branches: ['master']
branches: [ 'master' ]
tags:
- 'v*' # only publish when pushing version tags (e.g., v1.0.0)
pull_request:
Expand All @@ -22,13 +22,13 @@ jobs:
# test for:
# * oldest supported version
# * latest available Python version
python-version: ['3.10', '3.14']
python-version: [ '3.12', '3.14' ]
# * Linux using ubuntu-latest
# * Windows using windows-latest
os: ['ubuntu-latest', 'windows-latest']
os: [ 'ubuntu-latest', 'windows-latest' ]
# * OM stable - latest stable version
# * OM nightly - latest nightly build
omc-version: ['stable', 'nightly']
omc-version: [ 'stable', 'nightly' ]

steps:
- uses: actions/checkout@v7
Expand Down Expand Up @@ -98,8 +98,8 @@ jobs:
needs: test
strategy:
matrix:
python-version: ['3.10']
os: ['ubuntu-latest']
python-version: [ '3.12' ]
os: [ 'ubuntu-latest' ]
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v7
Expand Down
104 changes: 104 additions & 0 deletions OMPython/modelica_system_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import abc
import ast
import csv
from dataclasses import dataclass
import logging
import numbers
Expand Down Expand Up @@ -216,6 +217,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"):
Expand Down Expand Up @@ -586,12 +596,20 @@ def _process_override_data(
override_file: OMPathABC,
override_var: dict[str, str],
override_sim: dict[str, str],
variable_filter: Optional[str] = None,
) -> None:
"""
Define the override parameters. As the definition of simulation specific override parameter changes with OM
1.26.0, version specific code is needed. Please keep in mind, that this will fail if OMC is not used to run the
model executable.

Including also override of variable filter settings.
"""

# define variable filter if defined (override any original setting)
if variable_filter is not None:
om_cmd.arg_set(key="variableFilter", val=variable_filter)

if len(override_var) == 0 and len(override_sim) == 0:
return

Expand Down Expand Up @@ -662,6 +680,7 @@ def simulate_cmd(
override_file=result_file.parent / f"{result_file.stem}_override.txt",
override_var=self._override_variables,
override_sim=self._simulate_options_override,
variable_filter=self._variable_filter,
)

if self._inputs: # if model has input quantities
Expand Down Expand Up @@ -927,6 +946,52 @@ def setOptimizationOptions(
datatype="optimization-option",
overridedata=None)

def set_variable_filter(
self,
variable_filter: Optional[str] = None,
escape: bool = False,
) -> None:
"""
This method is used to set variable filters. If escape is True, all regex special characters are escaped.
"""
if variable_filter is None:
self._variable_filter = None
return

if escape:
variable_filter = re.escape(variable_filter)

# Validate filter_val as a regular expression
try:
re.compile(variable_filter)
except re.error as exc:
raise ModelicaSystemError(f"Invalid variable_filter regular expression: {variable_filter!r} ({exc})")

self._variable_filter = variable_filter

@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,
Expand Down Expand Up @@ -989,6 +1054,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,
Expand Down Expand Up @@ -1087,6 +1190,7 @@ def linearize(
override_file=self.getWorkDirectory() / f'{self._model_name}_override_linear.txt',
override_var=self._override_variables,
override_sim=self._linearization_options,
variable_filter=self._variable_filter,
)

if self._inputs:
Expand Down
2 changes: 1 addition & 1 deletion OMPython/modelica_system_omc.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def model(
# set variables
self._model_name = model_name # Model class name
self._libraries = libraries # may be needed if model is derived from other model
self._variable_filter = variable_filter
self.set_variable_filter(variable_filter=variable_filter, escape=True)

if self._libraries:
self._loadLibrary(libraries=self._libraries)
Expand Down
Loading
Loading