Skip to content

Commit 4b7dda8

Browse files
committed
(B002) split ModelicaSystemDoE
[ModelicaSystem] split ModelicaSystemDoE into ModelicaDoEABC and ModelicaDoE [ModelicaSystem] rename ModelicaSystemDoE => ModelicaDoEOMC * add compatibility variable for ModelicaSystemDoE [test_ModelicaDoEOMC] rename from ModelicaSystemDoE and update [ModelicaSystem] update ModelicaDoEABC to use ModelicaSystemABC [ModelicaSystem] define doe_get_solutions() as separate method
1 parent d9f705a commit 4b7dda8

File tree

3 files changed

+176
-85
lines changed

3 files changed

+176
-85
lines changed

OMPython/ModelicaSystem.py

Lines changed: 159 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import re
1616
import textwrap
1717
import threading
18-
from typing import Any, cast, Optional
18+
from typing import Any, cast, Optional, Tuple
1919
import warnings
2020
import xml.etree.ElementTree as ET
2121

@@ -2112,9 +2112,9 @@ class ModelicaSystem(ModelicaSystemOMC):
21122112
"""
21132113

21142114

2115-
class ModelicaSystemDoE:
2115+
class ModelicaDoEABC(metaclass=abc.ABCMeta):
21162116
"""
2117-
Class to run DoEs based on a (Open)Modelica model using ModelicaSystem
2117+
Base class to run DoEs based on a (Open)Modelica model using ModelicaSystem
21182118
21192119
Example
21202120
-------
@@ -2187,7 +2187,7 @@ def run_doe():
21872187
def __init__(
21882188
self,
21892189
# ModelicaSystem definition to use
2190-
mod: ModelicaSystemOMC,
2190+
mod: ModelicaSystemABC,
21912191
# simulation specific input
21922192
# TODO: add more settings (simulation options, input options, ...)
21932193
simargs: Optional[dict[str, Optional[str | dict[str, str] | numbers.Number]]] = None,
@@ -2200,7 +2200,7 @@ def __init__(
22002200
ModelicaSystem.simulate(). Additionally, the path to store the result files is needed (= resultpath) as well as
22012201
a list of parameters to vary for the Doe (= parameters). All possible combinations are considered.
22022202
"""
2203-
if not isinstance(mod, ModelicaSystemOMC):
2203+
if not isinstance(mod, ModelicaSystemABC):
22042204
raise ModelicaSystemError("Missing definition of ModelicaSystem!")
22052205

22062206
self._mod = mod
@@ -2256,30 +2256,11 @@ def prepare(self) -> int:
22562256
param_non_structural_combinations = list(itertools.product(*param_non_structure.values()))
22572257

22582258
for idx_pc_structure, pc_structure in enumerate(param_structure_combinations):
2259-
2260-
build_dir = self._resultpath / f"DOE_{idx_pc_structure:09d}"
2261-
build_dir.mkdir()
2262-
self._mod.setWorkDirectory(work_directory=build_dir)
2263-
2264-
sim_param_structure = {}
2265-
for idx_structure, pk_structure in enumerate(param_structure.keys()):
2266-
sim_param_structure[pk_structure] = pc_structure[idx_structure]
2267-
2268-
pk_value = pc_structure[idx_structure]
2269-
if isinstance(pk_value, str):
2270-
pk_value_str = self.get_session().escape_str(pk_value)
2271-
expr = f"setParameterValue({self._model_name}, {pk_structure}, \"{pk_value_str}\")"
2272-
elif isinstance(pk_value, bool):
2273-
pk_value_bool_str = "true" if pk_value else "false"
2274-
expr = f"setParameterValue({self._model_name}, {pk_structure}, {pk_value_bool_str});"
2275-
else:
2276-
expr = f"setParameterValue({self._model_name}, {pk_structure}, {pk_value})"
2277-
res = self._mod.sendExpression(expr=expr)
2278-
if not res:
2279-
raise ModelicaSystemError(f"Cannot set structural parameter {self._model_name}.{pk_structure} "
2280-
f"to {pk_value} using {repr(expr)}")
2281-
2282-
self._mod.buildModel()
2259+
sim_param_structure = self._prepare_structure_parameters(
2260+
idx_pc_structure=idx_pc_structure,
2261+
pc_structure=pc_structure,
2262+
param_structure=param_structure,
2263+
)
22832264

22842265
for idx_non_structural, pk_non_structural in enumerate(param_non_structural_combinations):
22852266
sim_param_non_structural = {}
@@ -2324,6 +2305,17 @@ def prepare(self) -> int:
23242305

23252306
return len(doe_sim)
23262307

2308+
@abc.abstractmethod
2309+
def _prepare_structure_parameters(
2310+
self,
2311+
idx_pc_structure: int,
2312+
pc_structure: Tuple,
2313+
param_structure: dict[str, list[str] | list[int] | list[float]],
2314+
) -> dict[str, str | int | float]:
2315+
"""
2316+
Handle structural parameters. This should be implemented by the derived class
2317+
"""
2318+
23272319
def get_doe_definition(self) -> Optional[dict[str, dict[str, Any]]]:
23282320
"""
23292321
Get the defined DoE as a dict, where each key is the result filename and the value is a dict of simulation
@@ -2435,65 +2427,157 @@ def worker(worker_id, task_queue):
24352427

24362428
return doe_def_total == doe_def_done
24372429

2430+
2431+
class ModelicaDoEOMC(ModelicaDoEABC):
2432+
"""
2433+
Class to run DoEs based on a (Open)Modelica model using ModelicaSystemOMC
2434+
2435+
The example is the same as defined for ModelicaDoEABC
2436+
"""
2437+
2438+
def __init__(
2439+
self,
2440+
# ModelicaSystem definition to use
2441+
mod: ModelicaSystemOMC,
2442+
# simulation specific input
2443+
# TODO: add more settings (simulation options, input options, ...)
2444+
simargs: Optional[dict[str, Optional[str | dict[str, str] | numbers.Number]]] = None,
2445+
# DoE specific inputs
2446+
resultpath: Optional[str | os.PathLike] = None,
2447+
parameters: Optional[dict[str, list[str] | list[int] | list[float]]] = None,
2448+
) -> None:
2449+
2450+
if not isinstance(mod, ModelicaSystemOMC):
2451+
raise ModelicaSystemError(f"Invalid definition for mod: {type(mod)} - expect ModelicaSystemOMC!")
2452+
2453+
super().__init__(
2454+
mod=mod,
2455+
simargs=simargs,
2456+
resultpath=resultpath,
2457+
parameters=parameters,
2458+
)
2459+
2460+
def _prepare_structure_parameters(
2461+
self,
2462+
idx_pc_structure: int,
2463+
pc_structure: Tuple,
2464+
param_structure: dict[str, list[str] | list[int] | list[float]],
2465+
) -> dict[str, str | int | float]:
2466+
build_dir = self._resultpath / f"DOE_{idx_pc_structure:09d}"
2467+
build_dir.mkdir()
2468+
self._mod.setWorkDirectory(work_directory=build_dir)
2469+
2470+
# need to repeat this check to make the linters happy
2471+
if not isinstance(self._mod, ModelicaSystemOMC):
2472+
raise ModelicaSystemError(f"Invalid definition for mod: {type(self._mod)} - expect ModelicaSystemOMC!")
2473+
2474+
sim_param_structure = {}
2475+
for idx_structure, pk_structure in enumerate(param_structure.keys()):
2476+
sim_param_structure[pk_structure] = pc_structure[idx_structure]
2477+
2478+
pk_value = pc_structure[idx_structure]
2479+
if isinstance(pk_value, str):
2480+
pk_value_str = self.get_session().escape_str(pk_value)
2481+
expr = f"setParameterValue({self._model_name}, {pk_structure}, \"{pk_value_str}\")"
2482+
elif isinstance(pk_value, bool):
2483+
pk_value_bool_str = "true" if pk_value else "false"
2484+
expr = f"setParameterValue({self._model_name}, {pk_structure}, {pk_value_bool_str});"
2485+
else:
2486+
expr = f"setParameterValue({self._model_name}, {pk_structure}, {pk_value})"
2487+
res = self._mod.sendExpression(expr=expr)
2488+
if not res:
2489+
raise ModelicaSystemError(f"Cannot set structural parameter {self._model_name}.{pk_structure} "
2490+
f"to {pk_value} using {repr(expr)}")
2491+
2492+
self._mod.buildModel()
2493+
2494+
return sim_param_structure
2495+
24382496
def get_doe_solutions(
24392497
self,
24402498
var_list: Optional[list] = None,
24412499
) -> Optional[tuple[str] | dict[str, dict[str, np.ndarray]]]:
24422500
"""
2443-
Get all solutions of the DoE run. The following return values are possible:
2501+
Wrapper for doe_get_solutions()
2502+
"""
2503+
if not isinstance(self._mod, ModelicaSystemOMC):
2504+
raise ModelicaSystemError(f"Invalid definition for mod: {type(self._mod)} - expect ModelicaSystemOMC!")
24442505

2445-
* A list of variables if val_list == None
2506+
return doe_get_solutions(
2507+
msomc=self._mod,
2508+
resultpath=self._resultpath,
2509+
doe_def=self.get_doe_definition(),
2510+
var_list=var_list,
2511+
)
24462512

2447-
* The Solutions as dict[str, pd.DataFrame] if a value list (== val_list) is defined.
24482513

2449-
The following code snippet can be used to convert the solution data for each run to a pandas dataframe:
2514+
def doe_get_solutions(
2515+
msomc: ModelicaSystemOMC,
2516+
resultpath: OMCPath,
2517+
doe_def: Optional[dict] = None,
2518+
var_list: Optional[list] = None,
2519+
) -> Optional[tuple[str] | dict[str, dict[str, np.ndarray]]]:
2520+
"""
2521+
Get all solutions of the DoE run. The following return values are possible:
24502522
2451-
```
2452-
import pandas as pd
2523+
* A list of variables if val_list == None
24532524
2454-
doe_sol = doe_mod.get_doe_solutions()
2455-
for key in doe_sol:
2456-
data = doe_sol[key]['data']
2457-
if data:
2458-
doe_sol[key]['df'] = pd.DataFrame.from_dict(data=data)
2459-
else:
2460-
doe_sol[key]['df'] = None
2461-
```
2525+
* The Solutions as dict[str, pd.DataFrame] if a value list (== val_list) is defined.
24622526
2463-
"""
2464-
if not isinstance(self._doe_def, dict):
2465-
return None
2527+
The following code snippet can be used to convert the solution data for each run to a pandas dataframe:
24662528
2467-
if len(self._doe_def) == 0:
2468-
raise ModelicaSystemError("No result files available - all simulations did fail?")
2529+
```
2530+
import pandas as pd
24692531
2470-
sol_dict: dict[str, dict[str, Any]] = {}
2471-
for resultfilename in self._doe_def:
2472-
resultfile = self._resultpath / resultfilename
2532+
doe_sol = doe_mod.get_doe_solutions()
2533+
for key in doe_sol:
2534+
data = doe_sol[key]['data']
2535+
if data:
2536+
doe_sol[key]['df'] = pd.DataFrame.from_dict(data=data)
2537+
else:
2538+
doe_sol[key]['df'] = None
2539+
```
24732540
2474-
sol_dict[resultfilename] = {}
2541+
"""
2542+
if not isinstance(doe_def, dict):
2543+
return None
24752544

2476-
if not self._doe_def[resultfilename][self.DICT_RESULT_AVAILABLE]:
2477-
msg = f"No result file available for {resultfilename}"
2478-
logger.warning(msg)
2479-
sol_dict[resultfilename]['msg'] = msg
2480-
sol_dict[resultfilename]['data'] = {}
2481-
continue
2545+
if len(doe_def) == 0:
2546+
raise ModelicaSystemError("No result files available - all simulations did fail?")
24822547

2483-
if var_list is None:
2484-
var_list_row = list(self._mod.getSolutions(resultfile=resultfile))
2485-
else:
2486-
var_list_row = var_list
2487-
2488-
try:
2489-
sol = self._mod.getSolutions(varList=var_list_row, resultfile=resultfile)
2490-
sol_data = {var: sol[idx] for idx, var in enumerate(var_list_row)}
2491-
sol_dict[resultfilename]['msg'] = 'Simulation available'
2492-
sol_dict[resultfilename]['data'] = sol_data
2493-
except ModelicaSystemError as ex:
2494-
msg = f"Error reading solution for {resultfilename}: {ex}"
2495-
logger.warning(msg)
2496-
sol_dict[resultfilename]['msg'] = msg
2497-
sol_dict[resultfilename]['data'] = {}
2498-
2499-
return sol_dict
2548+
sol_dict: dict[str, dict[str, Any]] = {}
2549+
for resultfilename in doe_def:
2550+
resultfile = resultpath / resultfilename
2551+
2552+
sol_dict[resultfilename] = {}
2553+
2554+
if not doe_def[resultfilename][ModelicaDoEABC.DICT_RESULT_AVAILABLE]:
2555+
msg = f"No result file available for {resultfilename}"
2556+
logger.warning(msg)
2557+
sol_dict[resultfilename]['msg'] = msg
2558+
sol_dict[resultfilename]['data'] = {}
2559+
continue
2560+
2561+
if var_list is None:
2562+
var_list_row = list(msomc.getSolutions(resultfile=resultfile))
2563+
else:
2564+
var_list_row = var_list
2565+
2566+
try:
2567+
sol = msomc.getSolutions(varList=var_list_row, resultfile=resultfile)
2568+
sol_data = {var: sol[idx] for idx, var in enumerate(var_list_row)}
2569+
sol_dict[resultfilename]['msg'] = 'Simulation available'
2570+
sol_dict[resultfilename]['data'] = sol_data
2571+
except ModelicaSystemError as ex:
2572+
msg = f"Error reading solution for {resultfilename}: {ex}"
2573+
logger.warning(msg)
2574+
sol_dict[resultfilename]['msg'] = msg
2575+
sol_dict[resultfilename]['data'] = {}
2576+
2577+
return sol_dict
2578+
2579+
2580+
class ModelicaSystemDoE(ModelicaDoEOMC):
2581+
"""
2582+
Compatibility class.
2583+
"""

OMPython/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
ModelicaSystemOMC,
1818
ModelExecutionCmd,
1919
ModelicaSystemDoE,
20+
ModelicaDoEOMC,
2021
ModelicaSystemError,
22+
23+
doe_get_solutions,
2124
)
2225
from OMPython.OMCSession import (
2326
OMCPath,
@@ -47,11 +50,15 @@
4750
'ModelicaSystemOMC',
4851
'ModelExecutionCmd',
4952
'ModelicaSystemDoE',
53+
'ModelicaDoEOMC',
5054
'ModelicaSystemError',
5155

5256
'OMCPath',
5357

5458
'OMCSession',
59+
60+
'doe_get_solutions',
61+
5562
'OMCSessionCmd',
5663
'OMCSessionDocker',
5764
'OMCSessionDockerContainer',
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def param_doe() -> dict[str, list]:
5151
return param
5252

5353

54-
def test_ModelicaSystemDoE_local(tmp_path, model_doe, param_doe):
54+
def test_ModelicaDoEOMC_local(tmp_path, model_doe, param_doe):
5555
tmpdir = tmp_path / 'DoE'
5656
tmpdir.mkdir(exist_ok=True)
5757

@@ -61,19 +61,19 @@ def test_ModelicaSystemDoE_local(tmp_path, model_doe, param_doe):
6161
model_name="M",
6262
)
6363

64-
doe_mod = OMPython.ModelicaSystemDoE(
64+
doe_mod = OMPython.ModelicaDoEOMC(
6565
mod=mod,
6666
parameters=param_doe,
6767
resultpath=tmpdir,
6868
simargs={"override": {'stopTime': '1.0'}},
6969
)
7070

71-
_run_ModelicaSystemDoe(doe_mod=doe_mod)
71+
_run_ModelicaDoEOMC(doe_mod=doe_mod)
7272

7373

7474
@skip_on_windows
7575
@skip_python_older_312
76-
def test_ModelicaSystemDoE_docker(tmp_path, model_doe, param_doe):
76+
def test_ModelicaDoEOMC_docker(tmp_path, model_doe, param_doe):
7777
omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal")
7878
omversion = omcs.sendExpression("getVersion()")
7979
assert isinstance(omversion, str) and omversion.startswith("OpenModelica")
@@ -86,18 +86,18 @@ def test_ModelicaSystemDoE_docker(tmp_path, model_doe, param_doe):
8686
model_name="M",
8787
)
8888

89-
doe_mod = OMPython.ModelicaSystemDoE(
89+
doe_mod = OMPython.ModelicaDoEOMC(
9090
mod=mod,
9191
parameters=param_doe,
9292
simargs={"override": {'stopTime': '1.0'}},
9393
)
9494

95-
_run_ModelicaSystemDoe(doe_mod=doe_mod)
95+
_run_ModelicaDoEOMC(doe_mod=doe_mod)
9696

9797

9898
@pytest.mark.skip(reason="Not able to run WSL on github")
9999
@skip_python_older_312
100-
def test_ModelicaSystemDoE_WSL(tmp_path, model_doe, param_doe):
100+
def test_ModelicaDoEOMC_WSL(tmp_path, model_doe, param_doe):
101101
omcs = OMPython.OMCSessionWSL()
102102
omversion = omcs.sendExpression("getVersion()")
103103
assert isinstance(omversion, str) and omversion.startswith("OpenModelica")
@@ -110,16 +110,16 @@ def test_ModelicaSystemDoE_WSL(tmp_path, model_doe, param_doe):
110110
model_name="M",
111111
)
112112

113-
doe_mod = OMPython.ModelicaSystemDoE(
113+
doe_mod = OMPython.ModelicaDoEOMC(
114114
mod=mod,
115115
parameters=param_doe,
116116
simargs={"override": {'stopTime': '1.0'}},
117117
)
118118

119-
_run_ModelicaSystemDoe(doe_mod=doe_mod)
119+
_run_ModelicaDoEOMC(doe_mod=doe_mod)
120120

121121

122-
def _run_ModelicaSystemDoe(doe_mod):
122+
def _run_ModelicaDoEOMC(doe_mod):
123123
doe_count = doe_mod.prepare()
124124
assert doe_count == 16
125125

0 commit comments

Comments
 (0)