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
@@ -2108,9 +2108,9 @@ class ModelicaSystem(ModelicaSystemOMC):
21082108 """
21092109
21102110
2111- class ModelicaSystemDoE :
2111+ class ModelicaDoEABC ( metaclass = abc . ABCMeta ) :
21122112 """
2113- Class to run DoEs based on a (Open)Modelica model using ModelicaSystem
2113+ Base class to run DoEs based on a (Open)Modelica model using ModelicaSystem
21142114
21152115 Example
21162116 -------
@@ -2183,7 +2183,7 @@ def run_doe():
21832183 def __init__ (
21842184 self ,
21852185 # ModelicaSystem definition to use
2186- mod : ModelicaSystemOMC ,
2186+ mod : ModelicaSystemABC ,
21872187 # simulation specific input
21882188 # TODO: add more settings (simulation options, input options, ...)
21892189 simargs : Optional [dict [str , Optional [str | dict [str , str ] | numbers .Number ]]] = None ,
@@ -2196,7 +2196,7 @@ def __init__(
21962196 ModelicaSystem.simulate(). Additionally, the path to store the result files is needed (= resultpath) as well as
21972197 a list of parameters to vary for the Doe (= parameters). All possible combinations are considered.
21982198 """
2199- if not isinstance (mod , ModelicaSystemOMC ):
2199+ if not isinstance (mod , ModelicaSystemABC ):
22002200 raise ModelicaSystemError ("Missing definition of ModelicaSystem!" )
22012201
22022202 self ._mod = mod
@@ -2252,30 +2252,11 @@ def prepare(self) -> int:
22522252 param_non_structural_combinations = list (itertools .product (* param_non_structure .values ()))
22532253
22542254 for idx_pc_structure , pc_structure in enumerate (param_structure_combinations ):
2255-
2256- build_dir = self ._resultpath / f"DOE_{ idx_pc_structure :09d} "
2257- build_dir .mkdir ()
2258- self ._mod .setWorkDirectory (work_directory = build_dir )
2259-
2260- sim_param_structure = {}
2261- for idx_structure , pk_structure in enumerate (param_structure .keys ()):
2262- sim_param_structure [pk_structure ] = pc_structure [idx_structure ]
2263-
2264- pk_value = pc_structure [idx_structure ]
2265- if isinstance (pk_value , str ):
2266- pk_value_str = self .get_session ().escape_str (pk_value )
2267- expr = f"setParameterValue({ self ._model_name } , { pk_structure } , \" { pk_value_str } \" )"
2268- elif isinstance (pk_value , bool ):
2269- pk_value_bool_str = "true" if pk_value else "false"
2270- expr = f"setParameterValue({ self ._model_name } , { pk_structure } , { pk_value_bool_str } );"
2271- else :
2272- expr = f"setParameterValue({ self ._model_name } , { pk_structure } , { pk_value } )"
2273- res = self ._mod .sendExpression (expr = expr )
2274- if not res :
2275- raise ModelicaSystemError (f"Cannot set structural parameter { self ._model_name } .{ pk_structure } "
2276- f"to { pk_value } using { repr (expr )} " )
2277-
2278- self ._mod .buildModel ()
2255+ sim_param_structure = self ._prepare_structure_parameters (
2256+ idx_pc_structure = idx_pc_structure ,
2257+ pc_structure = pc_structure ,
2258+ param_structure = param_structure ,
2259+ )
22792260
22802261 for idx_non_structural , pk_non_structural in enumerate (param_non_structural_combinations ):
22812262 sim_param_non_structural = {}
@@ -2320,6 +2301,17 @@ def prepare(self) -> int:
23202301
23212302 return len (doe_sim )
23222303
2304+ @abc .abstractmethod
2305+ def _prepare_structure_parameters (
2306+ self ,
2307+ idx_pc_structure : int ,
2308+ pc_structure : Tuple ,
2309+ param_structure : dict [str , list [str ] | list [int ] | list [float ]],
2310+ ) -> dict [str , str | int | float ]:
2311+ """
2312+ Handle structural parameters. This should be implemented by the derived class
2313+ """
2314+
23232315 def get_doe_definition (self ) -> Optional [dict [str , dict [str , Any ]]]:
23242316 """
23252317 Get the defined DoE as a dict, where each key is the result filename and the value is a dict of simulation
@@ -2431,65 +2423,157 @@ def worker(worker_id, task_queue):
24312423
24322424 return doe_def_total == doe_def_done
24332425
2426+
2427+ class ModelicaDoEOMC (ModelicaDoEABC ):
2428+ """
2429+ Class to run DoEs based on a (Open)Modelica model using ModelicaSystemOMC
2430+
2431+ The example is the same as defined for ModelicaDoEABC
2432+ """
2433+
2434+ def __init__ (
2435+ self ,
2436+ # ModelicaSystem definition to use
2437+ mod : ModelicaSystemOMC ,
2438+ # simulation specific input
2439+ # TODO: add more settings (simulation options, input options, ...)
2440+ simargs : Optional [dict [str , Optional [str | dict [str , str ] | numbers .Number ]]] = None ,
2441+ # DoE specific inputs
2442+ resultpath : Optional [str | os .PathLike ] = None ,
2443+ parameters : Optional [dict [str , list [str ] | list [int ] | list [float ]]] = None ,
2444+ ) -> None :
2445+
2446+ if not isinstance (mod , ModelicaSystemOMC ):
2447+ raise ModelicaSystemError (f"Invalid definition for mod: { type (mod )} - expect ModelicaSystemOMC!" )
2448+
2449+ super ().__init__ (
2450+ mod = mod ,
2451+ simargs = simargs ,
2452+ resultpath = resultpath ,
2453+ parameters = parameters ,
2454+ )
2455+
2456+ def _prepare_structure_parameters (
2457+ self ,
2458+ idx_pc_structure : int ,
2459+ pc_structure : Tuple ,
2460+ param_structure : dict [str , list [str ] | list [int ] | list [float ]],
2461+ ) -> dict [str , str | int | float ]:
2462+ build_dir = self ._resultpath / f"DOE_{ idx_pc_structure :09d} "
2463+ build_dir .mkdir ()
2464+ self ._mod .setWorkDirectory (work_directory = build_dir )
2465+
2466+ # need to repeat this check to make the linters happy
2467+ if not isinstance (self ._mod , ModelicaSystemOMC ):
2468+ raise ModelicaSystemError (f"Invalid definition for mod: { type (self ._mod )} - expect ModelicaSystemOMC!" )
2469+
2470+ sim_param_structure = {}
2471+ for idx_structure , pk_structure in enumerate (param_structure .keys ()):
2472+ sim_param_structure [pk_structure ] = pc_structure [idx_structure ]
2473+
2474+ pk_value = pc_structure [idx_structure ]
2475+ if isinstance (pk_value , str ):
2476+ pk_value_str = self .get_session ().escape_str (pk_value )
2477+ expr = f"setParameterValue({ self ._model_name } , { pk_structure } , \" { pk_value_str } \" )"
2478+ elif isinstance (pk_value , bool ):
2479+ pk_value_bool_str = "true" if pk_value else "false"
2480+ expr = f"setParameterValue({ self ._model_name } , { pk_structure } , { pk_value_bool_str } );"
2481+ else :
2482+ expr = f"setParameterValue({ self ._model_name } , { pk_structure } , { pk_value } )"
2483+ res = self ._mod .sendExpression (expr = expr )
2484+ if not res :
2485+ raise ModelicaSystemError (f"Cannot set structural parameter { self ._model_name } .{ pk_structure } "
2486+ f"to { pk_value } using { repr (expr )} " )
2487+
2488+ self ._mod .buildModel ()
2489+
2490+ return sim_param_structure
2491+
24342492 def get_doe_solutions (
24352493 self ,
24362494 var_list : Optional [list ] = None ,
24372495 ) -> Optional [tuple [str ] | dict [str , dict [str , np .ndarray ]]]:
24382496 """
2439- Get all solutions of the DoE run. The following return values are possible:
2497+ Wrapper for doe_get_solutions()
2498+ """
2499+ if not isinstance (self ._mod , ModelicaSystemOMC ):
2500+ raise ModelicaSystemError (f"Invalid definition for mod: { type (self ._mod )} - expect ModelicaSystemOMC!" )
24402501
2441- * A list of variables if val_list == None
2502+ return doe_get_solutions (
2503+ msomc = self ._mod ,
2504+ resultpath = self ._resultpath ,
2505+ doe_def = self .get_doe_definition (),
2506+ var_list = var_list ,
2507+ )
24422508
2443- * The Solutions as dict[str, pd.DataFrame] if a value list (== val_list) is defined.
24442509
2445- The following code snippet can be used to convert the solution data for each run to a pandas dataframe:
2510+ def doe_get_solutions (
2511+ msomc : ModelicaSystemOMC ,
2512+ resultpath : OMCPath ,
2513+ doe_def : Optional [dict ] = None ,
2514+ var_list : Optional [list ] = None ,
2515+ ) -> Optional [tuple [str ] | dict [str , dict [str , np .ndarray ]]]:
2516+ """
2517+ Get all solutions of the DoE run. The following return values are possible:
24462518
2447- ```
2448- import pandas as pd
2519+ * A list of variables if val_list == None
24492520
2450- doe_sol = doe_mod.get_doe_solutions()
2451- for key in doe_sol:
2452- data = doe_sol[key]['data']
2453- if data:
2454- doe_sol[key]['df'] = pd.DataFrame.from_dict(data=data)
2455- else:
2456- doe_sol[key]['df'] = None
2457- ```
2521+ * The Solutions as dict[str, pd.DataFrame] if a value list (== val_list) is defined.
24582522
2459- """
2460- if not isinstance (self ._doe_def , dict ):
2461- return None
2523+ The following code snippet can be used to convert the solution data for each run to a pandas dataframe:
24622524
2463- if len ( self . _doe_def ) == 0 :
2464- raise ModelicaSystemError ( "No result files available - all simulations did fail?" )
2525+ ```
2526+ import pandas as pd
24652527
2466- sol_dict : dict [str , dict [str , Any ]] = {}
2467- for resultfilename in self ._doe_def :
2468- resultfile = self ._resultpath / resultfilename
2528+ doe_sol = doe_mod.get_doe_solutions()
2529+ for key in doe_sol:
2530+ data = doe_sol[key]['data']
2531+ if data:
2532+ doe_sol[key]['df'] = pd.DataFrame.from_dict(data=data)
2533+ else:
2534+ doe_sol[key]['df'] = None
2535+ ```
24692536
2470- sol_dict [resultfilename ] = {}
2537+ """
2538+ if not isinstance (doe_def , dict ):
2539+ return None
24712540
2472- if not self ._doe_def [resultfilename ][self .DICT_RESULT_AVAILABLE ]:
2473- msg = f"No result file available for { resultfilename } "
2474- logger .warning (msg )
2475- sol_dict [resultfilename ]['msg' ] = msg
2476- sol_dict [resultfilename ]['data' ] = {}
2477- continue
2541+ if len (doe_def ) == 0 :
2542+ raise ModelicaSystemError ("No result files available - all simulations did fail?" )
24782543
2479- if var_list is None :
2480- var_list_row = list (self ._mod .getSolutions (resultfile = resultfile ))
2481- else :
2482- var_list_row = var_list
2483-
2484- try :
2485- sol = self ._mod .getSolutions (varList = var_list_row , resultfile = resultfile )
2486- sol_data = {var : sol [idx ] for idx , var in enumerate (var_list_row )}
2487- sol_dict [resultfilename ]['msg' ] = 'Simulation available'
2488- sol_dict [resultfilename ]['data' ] = sol_data
2489- except ModelicaSystemError as ex :
2490- msg = f"Error reading solution for { resultfilename } : { ex } "
2491- logger .warning (msg )
2492- sol_dict [resultfilename ]['msg' ] = msg
2493- sol_dict [resultfilename ]['data' ] = {}
2494-
2495- return sol_dict
2544+ sol_dict : dict [str , dict [str , Any ]] = {}
2545+ for resultfilename in doe_def :
2546+ resultfile = resultpath / resultfilename
2547+
2548+ sol_dict [resultfilename ] = {}
2549+
2550+ if not doe_def [resultfilename ][ModelicaDoEABC .DICT_RESULT_AVAILABLE ]:
2551+ msg = f"No result file available for { resultfilename } "
2552+ logger .warning (msg )
2553+ sol_dict [resultfilename ]['msg' ] = msg
2554+ sol_dict [resultfilename ]['data' ] = {}
2555+ continue
2556+
2557+ if var_list is None :
2558+ var_list_row = list (msomc .getSolutions (resultfile = resultfile ))
2559+ else :
2560+ var_list_row = var_list
2561+
2562+ try :
2563+ sol = msomc .getSolutions (varList = var_list_row , resultfile = resultfile )
2564+ sol_data = {var : sol [idx ] for idx , var in enumerate (var_list_row )}
2565+ sol_dict [resultfilename ]['msg' ] = 'Simulation available'
2566+ sol_dict [resultfilename ]['data' ] = sol_data
2567+ except ModelicaSystemError as ex :
2568+ msg = f"Error reading solution for { resultfilename } : { ex } "
2569+ logger .warning (msg )
2570+ sol_dict [resultfilename ]['msg' ] = msg
2571+ sol_dict [resultfilename ]['data' ] = {}
2572+
2573+ return sol_dict
2574+
2575+
2576+ class ModelicaSystemDoE (ModelicaDoEOMC ):
2577+ """
2578+ Compatibility class.
2579+ """
0 commit comments