1515import re
1616import textwrap
1717import threading
18- from typing import Any , cast , Optional
18+ from typing import Any , cast , Optional , Tuple
1919import warnings
2020import 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+ """
0 commit comments