create_benchmark

The create_benchmark.py script enables the automatic generation of datasets, based on the Cyber-Physical Process Plant setups saved in the benchmark_config.json file.

The script sequentially calls the following functionalities:
  • create_ds_dir(setup: str)

    creating a dataset directory, which is named after the dataset name defined in the benchmark_config.json and in which the results will be saved.

  • create_call_file(setup: str)

    creating a customized call.mos file for running the custom simulation and saving the results in a custom directory.

  • create_process_plant(config_ds: dict)

    creating a customized process_plant.mo file, based on the Cyber-Physical Process Plant defined in the benchmark_config.json.

  • create_supermodel(faulty_module: str, fault: str)

    creating a customized superModel of the faulty module, to induce time-dependent faults.

  • run_simulation(setup: str, fault: str)

    run individual simulations for all faults indicated in the benchmark_config.json.

  • clean_ds(setup: str, fault: str)

    clean the dataset from unnecessary values and only keep values of interest.

  • save_connection_model(config_dict: dict, setup: str)

    saving the Cyber-Physical Process Plant configuration as *.json in the custom dataset directory.

  • create_dataset(config_ds: dict, setup: str)

    running all functionality calls in a function.

Functions

  • def create_ds_dir(setup: str)

    creating a dataset directory, which is named after the dataset name defined in the benchmark_config.json and in which the results will be saved.

    1def create_ds_dir(setup: str):
    2    os.system("cd ../datasets && mkdir " + setup)
    3    return print("Directory ./datasets/" + setup + " was created ...\n")
    
  • def create_call_file(setup: str)

    creating a customized call.mos file for running the custom simulation and saving the results in a custom directory.

     1def create_call_file(config_dict: dict, setup: str):
     2    """
     3    creating a call.mos file with updated dataset directory
     4    """
     5    file = open("call.mos", "r")
     6    replacement = ""
     7    for line in file:
     8        if 'cd("ds' in line:
     9            changes = 'cd("' + setup + '"); \n'
    10        elif 'simulate(' in line:
    11            changes = 'simulate(process_plant, startTime =' + str(config_dict["startTime"]) + ' , stopTime=' + str(config_dict["stopTime"]) + ' , numberOfIntervals=' + str(config_dict["numberOfIntervals"]) + ' , outputFormat="csv", simflags="-maxIntegrationOrder=1");\n'
    12        else:
    13            changes = line
    14        replacement = replacement + changes
    15    file.close()
    16    # write mode
    17    fout = open("call.mos", "w")
    18    fout.write(replacement)
    19    fout.close()
    20    return
    
  • def create_process_plant(config_ds: dict)

    creating a customized process_plant.mo file, based on the Cyber-Physical Process Plant defined in the benchmark_config.json.

     1def create_process_plant(config_dict: dict):
     2    """
     3    Creates a process plant model in modelica as .mo file,
     4    based on the setup, that is encoded in the config_ds dict.
     5
     6    The file is saved as process_plant.mo
     7    """
     8
     9    intro = ''\
    10            'model process_plant\n' \
    11            'inner Modelica.Fluid.System system(energyDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, m_flow_start = 1, massDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, momentumDynamics = Modelica.Fluid.Types.Dynamics.FixedInitial, p_ambient(displayUnit = "Pa"));\n' \
    12            'replaceable package Medium = Modelica.Media.Water.StandardWater;\n\n'
    13    models = ''
    14    equations = '\nequation\n'
    15    outro = 'end process_plant;\n'
    16
    17    mod_list = config_dict["model"]["nodes"]
    18    equ_list = config_dict["model"]["edges"]
    19
    20    for i in mod_list:
    21        models = models + (i[:-1] + " " + i + ";\n") #"(replaceable package Medium = Medium);\n")
    22
    23    l, m = 1, 1
    24    for j in equ_list:
    25        if "mixer" in j[1]:
    26            equations = equations + ("connect(" + str(j[0]) + ".port_out, " + str(j[1]) + ".port_in" + str(l) + ");\n")
    27            l += 1
    28        if "still" in j[0]:
    29            equations = equations + ("connect(" + str(j[0]) + ".port_out" + str(m) + ", " + str(j[1]) + ".port_in);\n")
    30            m += 1
    31        if "mixer" not in j[1] and "still" not in j[0]:
    32            equations = equations + ("connect(" + str(j[0]) + ".port_out, " + str(j[1]) + ".port_in);\n")
    33
    34    with open("../simulation_models/process_plant.mo", "w") as f:
    35        f.write(intro)
    36        f.write(models)
    37        f.write(equations)
    38        f.write(outro)
    39    return
    
  • def create_supermodel(faulty_module: str, fault: str)

    creating a customized superModel of the faulty module, to induce time-dependent faults.

     1def create_supermodel(config_dict: dict, fault: str):
     2    """
     3    Within the supermodels fault induction can be turned off and on.
     4    Hence, for each fault setup a separate supermodel must be created.
     5    """
     6    str_superModel = "../simulation_models/" + str(config_dict["faulty_module"][0][:-9]) + "_superModel.mo"
     7    ok_str1 = "valve_leaking_simulator = if time >=" + str(config_dict["stopTime"]) + " then 0.0001 else 0; // leaking"
     8    nok_str1 = "valve_leaking_simulator = if time >=" + str(config_dict["errorInduction"]) + " then 0.0001 else 0; // leaking"
     9    ok_str2 = "valve_clogging_simulator = if time >=" + str(config_dict["stopTime"]) + " then 0.01 else 1; // clogging"
    10    nok_str2 = "valve_clogging_simulator = if time >=" + str(config_dict["errorInduction"]) + " then 0.01 else 1; // clogging"
    11
    12    # replace all strings with ok strings before introducing nok strings
    13    file = open(str_superModel, "r")
    14    replacement = ""
    15    for line in file:
    16        if "valve_leaking_simulator =" in line:
    17            changes = ok_str1
    18        elif "valve_clogging_simulator =" in line:
    19            changes = ok_str2
    20        else:
    21            changes = line
    22        replacement = replacement + changes
    23    file.close()
    24    fout = open(str_superModel, "w")
    25    fout.write(replacement)
    26    fout.close()
    27
    28    # read mode
    29    file = open(str_superModel, "r")
    30    replacement = ""
    31    for line in file:
    32        if fault == "l":
    33            if ok_str1 in line:
    34                changes = line.replace(ok_str1, nok_str1)
    35            elif nok_str2 in line:
    36                changes = line.replace(nok_str2, ok_str2)
    37            else:
    38                changes = line
    39        elif fault == "c":
    40            if nok_str1 in line:
    41                changes = line.replace(nok_str1, ok_str1)
    42            elif ok_str2 in line:
    43                changes = line.replace(ok_str2, nok_str2)
    44            else:
    45                changes = line
    46        elif fault == "lc":
    47            if ok_str1 in line:
    48                changes = line.replace(ok_str1, nok_str1)
    49            elif ok_str2 in line:
    50                changes = line.replace(ok_str2, nok_str2)
    51            else:
    52                changes = line
    53        replacement = replacement + changes
    54    file.close()
    55    # write mode
    56    fout = open(str_superModel, "w")
    57    fout.write(replacement)
    58    fout.close()
    59    return
    
  • def run_simulation(setup: str, fault: str)

    run individual simulations for all faults indicated in the benchmark_config.json.

     1def run_simulation(setup: str, fault: str):
     2    """
     3
     4    This file runs the simulation by calling the call.mos file from console
     5    saves the results .csv as setup + fault .csv
     6    removes other unnecessary simulation files
     7    """
     8    os.system("omc call.mos")
     9
    10    # delete unnecessarey simulation information and rename file
    11    os.system("cd ../datasets/" + setup + " && mv process_plant_res.csv " + setup + fault + ".csv")
    12    filelist = [f for f in os.listdir("../datasets/" + setup) if not f.endswith(".csv")]
    13    for f in filelist:
    14        os.remove(os.path.join("../datasets/" + setup, f))
    15    return
    
  • def clean_ds(setup: str, fault: str)

    clean the dataset from unnecessary values and only keep values of interest.

     1def clean_ds(setup: str, fault: str):
     2    """
     3    reading csv as df and cleaning everything unnecessary
     4    """
     5    csv_file = "../datasets/" + setup + "/" + setup + fault + ".csv"
     6    channels_of_interest = ["time", "v_flow", "level", "m_flow", "fluidVolume", "N_in", "opening", "heatTransfer.Ts", "medium.t", "port_a.p", "port_b.p"]
     7
     8    df = pd.read_csv(csv_file)
     9
    10    for column in df:
    11        flag = False
    12        for i in channels_of_interest:
    13            if i in column:
    14                flag = True
    15        if flag == False:
    16            df = df.drop(column, 1)
    17
    18    df.to_csv(csv_file)
    19    print("dataset " + csv_file + " was created ...\n")
    20    return
    
  • def save_connection_model(config_dict: dict, setup: str)

    saving the Cyber-Physical Process Plant configuration as *.json in the custom dataset directory.

     1def save_connection_model(config_dict: dict, setup: str):
     2    """
     3
     4    saves the simulation configuration as .txt file in dataset directory
     5    """
     6    file = "../datasets/" + setup + "/" + setup + "_config.json"
     7    with open(file, "w") as json_file:
     8        json.dump(config_dict, json_file)
     9
    10    print("Saved config-file ... \n")
    11    return
    
  • def create_dataset(config_ds: dict, setup: str)

    running all functionality calls in a function.

     1def create_dataset(config_ds: dict, setup: str):
     2    """
     3    This funcion creates a single .csv dataset by
     4        - building a modelica model and supermodel of the desired process plant setup (including the adapted parametrization)
     5        - running the simulation (regarding to the desired fault setup)
     6        - cleaning the dataset from unnecessary values
     7        - and saving it in a separate directory
     8
     9    in:
    10        - a dict of the current ds config
    11
    12    returns:
    13        - a csv in a directory named after the process plant build
    14    """
    15
    16    create_ds_dir(setup=setup)                                          # creating a directory for the dataset setup
    17    create_call_file(config_dict=config_ds, setup=setup)                # creating an adapted call.mos file to run Modelica simulations
    18    create_process_plant(config_dict=config_ds)                         # creating a process plant setup following the config file
    19
    20    for fault in config_ds["faults"]:
    21        create_supermodel(config_dict=config_ds, fault=fault)           # creating a supermodel inducing the fault "fault"
    22        run_simulation(setup=setup, fault=fault)                        # running the om simulation
    23        clean_ds(setup=setup, fault=fault)                              # cleaning resulting ds from unnecessary values
    24        print("simulation run " + str(setup) + str(fault) + " finished ... \n")
    25    save_connection_model(config_dict=config_ds, setup=setup)           # saving the connection model and simulation setup as file in ds-directory
    26    return