From 97ccf628e568bf9f4951c33ffae820ae3c78d30d Mon Sep 17 00:00:00 2001 From: syntron Date: Fri, 6 Mar 2026 20:13:57 +0100 Subject: [PATCH 1/5] [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 3531b587f8c35078ab67756ff08c94bfc81e40e2 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 1 Apr 2026 22:55:31 +0200 Subject: [PATCH 2/5] 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, From 944201ae3bbd13d6812fcda0ffdc6949c0767ad9 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 23 Apr 2026 22:18:23 +0200 Subject: [PATCH 3/5] update handling of variable_filter * use public function `ModelicaSystemABC.set_variable_filter()` to define * process it in `ModelicaSystemABC._process_override_data()` as command line argument --- OMPython/modelica_system_abc.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 15261141..23d40abc 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -596,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 @@ -937,6 +945,29 @@ 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]]]: """ From 06f8b7ce8d3cba612a0db3b3dbd03eacfb221b06 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 23 Apr 2026 22:19:26 +0200 Subject: [PATCH 4/5] use new processing for variable_filter --- OMPython/modelica_system_abc.py | 2 ++ OMPython/modelica_system_omc.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/OMPython/modelica_system_abc.py b/OMPython/modelica_system_abc.py index 23d40abc..41a67205 100644 --- a/OMPython/modelica_system_abc.py +++ b/OMPython/modelica_system_abc.py @@ -680,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 @@ -1189,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: diff --git a/OMPython/modelica_system_omc.py b/OMPython/modelica_system_omc.py index 2d2088c0..c4a441c6 100644 --- a/OMPython/modelica_system_omc.py +++ b/OMPython/modelica_system_omc.py @@ -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) From aa3609770cf36d0625cc786656dab7f2c401b2e0 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 23 Apr 2026 22:19:39 +0200 Subject: [PATCH 5/5] add unittest test_variable_filter() --- tests/test_ModelicaSystemOMC.py | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_ModelicaSystemOMC.py b/tests/test_ModelicaSystemOMC.py index bff63315..a5e996c2 100644 --- a/tests/test_ModelicaSystemOMC.py +++ b/tests/test_ModelicaSystemOMC.py @@ -206,6 +206,53 @@ def _run_getSolutions(mod): assert np.isclose(x, x_analytical, rtol=1e-4).all() +def test_variable_filter(model_firstorder): + mod = OMPython.ModelicaSystemOMC() + mod.model( + model_file=model_firstorder, + model_name="M", + ) + + a = -1 + tau = -1 / a + stopTime = 5 * tau + + simOptions = {"stopTime": stopTime, "stepSize": 0.1, "tolerance": 1e-8} + mod.setSimulationOptions(**simOptions) + mod.simulate() + sol_names1 = mod.getSolutions() + assert isinstance(sol_names1, tuple) + assert sol_names1 == ('a', 'der(x)', 'time', 'x') + + mod.set_variable_filter(variable_filter='x') + mod.setSimulationOptions(stopTime=2.0) + mod.simulate() + sol_names2 = mod.getSolutions() + assert isinstance(sol_names2, tuple) + assert sol_names2 == ('a', 'time', 'x') + + mod.set_variable_filter(variable_filter='der(x)') + mod.setSimulationOptions(stopTime=3.0) + mod.simulate() + sol_names3 = mod.getSolutions() + assert isinstance(sol_names3, tuple) + assert sol_names3 == ('a', 'time') + + mod.set_variable_filter(variable_filter='der(x)', escape=True) + mod.setSimulationOptions(stopTime=3.0) + mod.simulate() + sol_names4 = mod.getSolutions() + assert isinstance(sol_names4, tuple) + assert sol_names4 == ('a', 'der(x)', 'time') + + mod.set_variable_filter(variable_filter='a') + mod.setSimulationOptions(stopTime=2.0) + mod.simulate() + sol_names5 = mod.getSolutions() + assert isinstance(sol_names5, tuple) + assert sol_names5 == ('a', 'time') + + def test_getters(tmp_path): model_file = tmp_path / "M_getters.mo" model_file.write_text("""