diff --git a/.gitignore b/.gitignore index 838de26..aa1b7f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ bimp_test_examples/ *.egg-info .pymon .DS_Store -.vscode/ \ No newline at end of file +.vscode/ +performance_exp/*/*/results +performance_exp/events/generated_events \ No newline at end of file diff --git a/bpdfr_simulation_engine/control_flow_manager.py b/bpdfr_simulation_engine/control_flow_manager.py index 0ad8e47..2afbd46 100644 --- a/bpdfr_simulation_engine/control_flow_manager.py +++ b/bpdfr_simulation_engine/control_flow_manager.py @@ -95,6 +95,16 @@ def is_start_or_end_event(self): def is_event(self): return self.type in [BPMN.START_EVENT, BPMN.END_EVENT, BPMN.INTERMEDIATE_EVENT] + def delete_incoming_flow(self, flow_id): + "Delete incoming flow if it exists" + if flow_id in self.incoming_flows: + self.incoming_flows.remove(flow_id) + + def delete_outgoing_flow(self, flow_id): + "Delete outgoing flow if it exists" + if flow_id in self.outgoing_flows: + self.outgoing_flows.remove(flow_id) + class ProcessState: def __init__(self, bpmn_graph): @@ -171,6 +181,12 @@ def add_bpmn_element(self, element_id, element_info): self.from_name[element_info.name] = element_id self.nodes_bitset[element_id] = (1 << len(self.element_info)) + def remove_incoming_flow(self, element_id, flow_id): + self.element_info[element_id].delete_incoming_flow(flow_id) + + def remove_outgoing_flow(self, element_id, flow_id): + self.element_info[element_id].delete_outgoing_flow(flow_id) + def add_flow_arc(self, flow_id, source_id, target_id): for node_id in [source_id, target_id]: if node_id not in self.element_info: diff --git a/bpdfr_simulation_engine/events_performance_experiments.py b/bpdfr_simulation_engine/events_performance_experiments.py new file mode 100644 index 0000000..3891caf --- /dev/null +++ b/bpdfr_simulation_engine/events_performance_experiments.py @@ -0,0 +1,72 @@ +import os +import string + +from bpdfr_simulation_engine.control_flow_manager import ( + BPMN, + EVENT_TYPE, + BPMNGraph, + ElementInfo, +) + + +def _add_events( + bpmn_graph: BPMNGraph, + sequence_flow_list, + element_probability, + num_inserted_events: int, +): + if len(sequence_flow_list) < num_inserted_events: + raise ValueError( + "A number of inserted events should not be higher than a number of sequence flows present in the BPMN model." + ) + + # keep track of the added events to the model + inserted_events_logs: string = "" + + for i in range(num_inserted_events): + # the flow arc to be replaced + to_be_deleted_flow_arc = sequence_flow_list[i] + to_be_deleted_flow_arc_id = to_be_deleted_flow_arc.attrib["id"] + + source_id = to_be_deleted_flow_arc.attrib["sourceRef"] + target_id = to_be_deleted_flow_arc.attrib["targetRef"] + + new_event_id = f"event_{i}" + + # log info about the newly added event + inserted_events_logs += f"{new_event_id} between {source_id} and {target_id}.\n" + + # add intermediate event + bpmn_graph.add_bpmn_element( + new_event_id, + ElementInfo( + BPMN.INTERMEDIATE_EVENT, + new_event_id, + new_event_id, + EVENT_TYPE.TIMER, + ), + ) + + # remove previously referenced sequence flow in the source & target activity + bpmn_graph.remove_outgoing_flow(source_id, to_be_deleted_flow_arc_id) + bpmn_graph.remove_incoming_flow(target_id, to_be_deleted_flow_arc_id) + + # add sequence flow to and from the newly added event + seq_flow_to_event = f"{source_id}_{new_event_id}" + bpmn_graph.add_flow_arc(seq_flow_to_event, source_id, new_event_id) + seq_flow_from_event = f"{new_event_id}_{target_id}" + bpmn_graph.add_flow_arc(seq_flow_from_event, new_event_id, target_id) + + # duplicate the gateway probability if it existed before + if source_id in element_probability: + element_probability[source_id].update_candidate_key( + to_be_deleted_flow_arc_id, seq_flow_to_event + ) + + # save logs about events + logs_path = os.path.join( + os.path.dirname(__file__), + f"../performance_exp/events/generated_events/{num_inserted_events}_events_logs.txt", + ) + with open(logs_path, "w+") as logs_file: + logs_file.write(inserted_events_logs) diff --git a/bpdfr_simulation_engine/probability_distributions.py b/bpdfr_simulation_engine/probability_distributions.py index d7f536a..97a56a7 100644 --- a/bpdfr_simulation_engine/probability_distributions.py +++ b/bpdfr_simulation_engine/probability_distributions.py @@ -175,6 +175,20 @@ def get_multiple_flows(self): selected.append((self.candidates_list[i], None)) return selected if len(selected) > 0 else [(self.get_outgoing_flow(), None)] + def update_candidate_key(self, old_candidate_id, new_candidate_id): + """ + After inserting an event, we need to change the candidate id in the list of candidates + This needs to happen due to the fact that we removed one gateway and inserted a new one instead + """ + + try: + candidate_index = self.candidates_list.index(old_candidate_id) + except ValueError: + # in case candidate is not on the list, not updating anything + return + + self.candidates_list[candidate_index] = new_candidate_id + def random_uniform(start, end): return numpy.random.uniform(low=start, high=end) diff --git a/bpdfr_simulation_engine/simulation_engine.py b/bpdfr_simulation_engine/simulation_engine.py index 4364c0c..bdbd717 100644 --- a/bpdfr_simulation_engine/simulation_engine.py +++ b/bpdfr_simulation_engine/simulation_engine.py @@ -644,9 +644,10 @@ def run_simulation( log_out_path=None, starting_at=None, is_event_added_to_log=False, + num_generated_events=None, ): diffsim_info = SimDiffSetup( - bpmn_path, json_path, is_event_added_to_log, total_cases + bpmn_path, json_path, is_event_added_to_log, total_cases, num_generated_events ) if not diffsim_info: diff --git a/bpdfr_simulation_engine/simulation_properties_parser.py b/bpdfr_simulation_engine/simulation_properties_parser.py index e13a6eb..c842cdd 100644 --- a/bpdfr_simulation_engine/simulation_properties_parser.py +++ b/bpdfr_simulation_engine/simulation_properties_parser.py @@ -11,6 +11,7 @@ BPMNGraph, ElementInfo, ) +from bpdfr_simulation_engine.events_performance_experiments import _add_events from bpdfr_simulation_engine.prioritisation import AllPriorityRules from bpdfr_simulation_engine.prioritisation_parser import PrioritisationParser from bpdfr_simulation_engine.probability_distributions import * @@ -27,7 +28,7 @@ PRIORITISATION_RULES_SECTION = "prioritisation_rules" ARRIVAL_TIME_CALENDAR = "arrival_time_calendar" RESOURCE_CALENDARS = "resource_calendars" - +TASK_RESOURCE_DISTR_SECTON = "task_resource_distribution" def parse_json_sim_parameters(json_path): with open(json_path) as json_file: @@ -38,7 +39,7 @@ def parse_json_sim_parameters(json_path): ) calendars_map = parse_resource_calendars(json_data[RESOURCE_CALENDARS]) task_resource_distribution = parse_task_resource_distributions( - json_data["task_resource_distribution"], res_pool + json_data[TASK_RESOURCE_DISTR_SECTON], res_pool ) element_distribution = parse_arrival_branching_probabilities( @@ -234,7 +235,13 @@ def parse_arrival_branching_probabilities(arrival_json, gateway_json): return element_distribution -def parse_simulation_model(bpmn_path): +def parse_simulation_model( + bpmn_path, element_probability={}, num_generated_events=None +): + """ + element_probability parameter is only used for adding probability for newly inserted sequence flows + when inserting events + """ tree = ET.parse(bpmn_path) root = tree.getroot() @@ -276,7 +283,8 @@ def parse_simulation_model(bpmn_path): # Counting incoming/outgoing flow arcs to handle cases of multiple in/out arcs simultaneously pending_flow_arcs = list() - for flow_arc in process.findall("xmlns:sequenceFlow", bpmn_element_ns): + sequence_flow_list = process.findall("xmlns:sequenceFlow", bpmn_element_ns) + for flow_arc in sequence_flow_list: # Fixing the case in which a task may have multiple incoming/outgoing flow-arcs pending_flow_arcs.append(flow_arc) if flow_arc.attrib["sourceRef"] in elements_map: @@ -332,6 +340,14 @@ def parse_simulation_model(bpmn_path): target_id = join_gateways[target_id] bpmn_graph.add_flow_arc(flow_arc.attrib["id"], source_id, target_id) + if num_generated_events != None: + _add_events( + bpmn_graph, + sequence_flow_list, + element_probability, + num_generated_events, + ) + bpmn_graph.encode_or_join_predecesors() bpmn_graph.validate_model() return bpmn_graph diff --git a/bpdfr_simulation_engine/simulation_setup.py b/bpdfr_simulation_engine/simulation_setup.py index 04aaedf..501fcd5 100644 --- a/bpdfr_simulation_engine/simulation_setup.py +++ b/bpdfr_simulation_engine/simulation_setup.py @@ -8,11 +8,21 @@ from bpdfr_simulation_engine.control_flow_manager import ProcessState, ElementInfo, BPMN from bpdfr_simulation_engine.probability_distributions import generate_number_from from bpdfr_simulation_engine.resource_calendar import RCalendar -from bpdfr_simulation_engine.simulation_properties_parser import parse_simulation_model, parse_json_sim_parameters +from bpdfr_simulation_engine.simulation_properties_parser import ( + parse_simulation_model, + parse_json_sim_parameters, +) class SimDiffSetup: - def __init__(self, bpmn_path, json_path, is_event_added_to_log, total_cases): + def __init__( + self, + bpmn_path, + json_path, + is_event_added_to_log, + total_cases, + num_generated_events=None, + ): self.process_name = ntpath.basename(bpmn_path).split(".")[0] self.start_datetime = datetime.datetime.now(pytz.utc) @@ -20,9 +30,15 @@ def __init__(self, bpmn_path, json_path, is_event_added_to_log, total_cases): self.event_distibution, self.batch_processing, self.case_attributes, self.prioritisation_rules \ = parse_json_sim_parameters(json_path) - self.bpmn_graph = parse_simulation_model(bpmn_path) - self.bpmn_graph.set_additional_fields_from_json(self.element_probability, \ - self.task_resource, self.event_distibution, self.batch_processing) + self.bpmn_graph = parse_simulation_model( + bpmn_path, self.element_probability, num_generated_events + ) + self.bpmn_graph.set_additional_fields_from_json( + self.element_probability, + self.task_resource, + self.event_distibution, + self.batch_processing + ) if not self.arrival_calendar: self.arrival_calendar = self.find_arrival_calendar() diff --git a/performance_exp/batching/bpi2012/input.zip b/performance_exp/batching/bpi2012/input.zip new file mode 100644 index 0000000..b420c38 Binary files /dev/null and b/performance_exp/batching/bpi2012/input.zip differ diff --git a/performance_exp/batching/bpi2012/results/.gitkeep b/performance_exp/batching/bpi2012/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/performance_exp/batching/perform_batching_runner.py b/performance_exp/batching/perform_batching_runner.py new file mode 100644 index 0000000..8b645f9 --- /dev/null +++ b/performance_exp/batching/perform_batching_runner.py @@ -0,0 +1,284 @@ +import json +import os +import uuid + +import matplotlib.pyplot as plt +import numpy as np +from bpdfr_simulation_engine.simulation_properties_parser import ( + BATCH_PROCESSING_SECTION, + TASK_RESOURCE_DISTR_SECTON, +) +from mpl_toolkits import mplot3d +from performance_exp.batching.testing_files import process_files_setup +from testing_scripts.bimp_diff_sim_tests import run_diff_res_simulation + + +def main(): + model_name = "bpi2012" + # model_name = "simple_example" + model_info = process_files_setup[model_name] + max_num_tasks_with_batching = model_info["max_num_tasks_with_batching"] + max_iter_num = model_info["max_iter_num"] + max_complexity_level = model_info["max_complexity_level"] + + num_batched_tasks_range = range(0, 1 + max_num_tasks_with_batching) + rules_level_range = range(1, 1 + max_complexity_level) + + # unique identifier of the experiment run + unique_run_id = uuid.uuid4() + + # file for saving received results (number_inserted_events, simulation_time) + final_plot_results = _get_abs_path( + model_info["results_folder"], + f"{unique_run_id}_plot_data.csv", + ) + + # add name of the model used during this experiment + with open(final_plot_results, "a") as plot_file: + plot_file.write(f"{model_name}\n\n") + + sim_time_list = [] + all_rule_complex_level = [] + all_batched_task = [] + + for index in num_batched_tasks_range: + print("-------------------------------------------") + print( + f"Starting Simulation with {index} priority levels in the simulation scenario" + ) + print("-------------------------------------------") + + for rule_complexity_level in rules_level_range: + median_sim_time = get_avg_after_all_iters( + max_iter_num, index, model_info, rule_complexity_level + ) + + all_batched_task.append(index) + all_rule_complex_level.append(rule_complexity_level) + sim_time_list.append(median_sim_time) + + # collect data points used for plotting + with open(final_plot_results, "a") as plot_file: + plot_file.write(f"{index},{rule_complexity_level},{median_sim_time}\n") + + print(sim_time_list) + + # save plot of the relationship: number of batched tasks - simulation time + plt_path = _get_abs_path( + model_info["results_folder"], + f"{unique_run_id}_plot.png", + ) + + # show plot of the relationship: number of priority levels - simulation time + print(all_batched_task) + print(sim_time_list) + print(f"np {np.array(sim_time_list)}") + print(all_rule_complex_level) + print(f"np {np.array(all_rule_complex_level)}") + _save_plot_2( + # all_batched_task, + num_batched_tasks_range, + sim_time_list, + all_rule_complex_level, + max_complexity_level, + model_name, + model_info["total_cases"], + plt_path, + ) + + +def get_avg_after_all_iters( + max_iter_num: int, current_run_index: int, model_info, rule_complexity: int +): + same_index_sim_time_list = [] + for iter_num in range(0, max_iter_num): + sim_time = run_one_iteration(current_run_index, model_info, rule_complexity) + print(f"iter {iter_num}: {sim_time}") + same_index_sim_time_list.append(sim_time) + + median_sim_time = np.median(same_index_sim_time_list) + print(f"median: {median_sim_time}") + + return median_sim_time + + +def run_one_iteration(num_tasks_with_batching: int, model_info, rule_complexity: int): + results_folder = model_info["results_folder"] + initial_json_path = _get_abs_path(model_info["json"]) + bpmn_path = _get_abs_path(model_info["bpmn"]) + demo_stats = _get_abs_path(results_folder, f"{num_tasks_with_batching}_stats.csv") + sim_log = _get_abs_path(results_folder, f"{num_tasks_with_batching}_logs.csv") + + new_json_path = _setup_sim_scenario( + initial_json_path, num_tasks_with_batching, rule_complexity + ) + + simulation_time, _ = run_diff_res_simulation( + model_info["start_datetime"], + model_info["total_cases"], + bpmn_path, + new_json_path, + demo_stats, + sim_log, + False, # no events in the log + None, # no added events + ) + + return simulation_time + # diff_sim_result.print_simulation_results() + + +def _setup_sim_scenario( + initial_json_path, num_tasks_with_batching: int, rule_complexity: int +): + """ + Create case-based prioritisation rules based on the required number (num_prioritisation_rules) + Save the newly created json in new location to keep track of the setup for simulations + """ + + one_batching_rule = _get_rule_by_complexity_level(rule_complexity) + + with open(initial_json_path, "r") as f: + json_dict = json.load(f) + + # collect all ids of activities in the BPMN model + all_tasks_distr = json_dict[TASK_RESOURCE_DISTR_SECTON] + all_tasks_id = map(lambda item: item["task_id"], all_tasks_distr) + + # select only number of activities that should have an assigned batching rule + selected_tasks_id = list(all_tasks_id)[:num_tasks_with_batching] + + # create batching setup + new_batching_rules_section = [ + { + "task_id": task_id, + "type": "Sequential", + "batch_frequency": 1.0, + "size_distrib": [ + {"key": "2", "value": 1}, + ], + "duration_distrib": [{"key": "3", "value": 0.8}], + "firing_rules": one_batching_rule, + } + for task_id in selected_tasks_id + ] + + json_dict[BATCH_PROCESSING_SECTION] = new_batching_rules_section + + # save modified json as a new file specifying the number of experiment + # in order to keep track of run experiments + folder_loc = os.path.dirname(initial_json_path) + new_filename = f"{num_tasks_with_batching}_batching_exp.json" + new_json_path = os.path.join(folder_loc, new_filename) + + with open(new_json_path, "w+") as json_file: + json.dump(json_dict, json_file) + + return new_json_path + + +def _get_rule_by_complexity_level(rule_complexity: int): + all_rules = { + "1": [ + [ + {"attribute": "size", "comparison": ">=", "value": 4}, + ] + ], + "2": [ + [ + {"attribute": "daily_hour", "comparison": "<", "value": "12"}, + {"attribute": "week_day", "comparison": "=", "value": "Friday"}, + ] + ], + "3": [ + [ + {"attribute": "daily_hour", "comparison": "<", "value": "12"}, + {"attribute": "week_day", "comparison": "=", "value": "Friday"}, + ], + [ + {"attribute": "size", "comparison": ">=", "value": 4}, + ], + ], + "4": [ + [ + {"attribute": "daily_hour", "comparison": "<", "value": "12"}, + {"attribute": "week_day", "comparison": "=", "value": "Friday"}, + ], + [ + {"attribute": "size", "comparison": ">=", "value": 4}, + {"attribute": "large_wt", "comparison": "<", "value": 3600}, + ], + ], + } + + return all_rules[str(rule_complexity)] + + +def _save_plot(xpoints, ypoints, zpoints, model_name, num_of_instances, plt_path): + fig = plt.figure() + ax = fig.add_subplot(projection="3d") + + ax.scatter3D(xpoints, ypoints, zpoints, c=zpoints, cmap="Greens") + + # give a general title + ax.set_title(f"Model: {model_name}, instances: {num_of_instances}") + + # name axis + ax.set_xlabel("Number of batched tasks") + ax.set_ylabel("Simulation time, sec") + ax.set_zlabel("Complexity of the batching rule") + + plt.show() + # save as a file + # plt.savefig(plt_path, bbox_inches="tight") + + +def _save_plot_2( + num_batched_task_arr, + sim_time_arr, + rule_complexity_level_arr, + max_complexity_level, + model_name, + num_of_instances, + plt_path, +): + fig = plt.figure() + + # give a general title + # plt.title(f"Model: {model_name}, instances: {num_of_instances}") + + # # provide data points + # plt.plot(xpoints, ypoints) + ax = fig.add_subplot(projection="3d") + colors = ["b", "g", "r", "c", "m", "y"] + + for i, (current_num_batched_task, color) in enumerate( + zip(num_batched_task_arr, colors) + ): + gap = max_complexity_level + start = i * gap # 1 - current_num_batched_task + end = start + gap + xs = sim_time_arr[start:end] + ys = rule_complexity_level_arr[start:end] + print(f"zs = {current_num_batched_task}") + print(start, end, xs, ys) + ax.bar(ys, xs, zs=current_num_batched_task, zdir="y", color=color, alpha=0.8) + + # name axis + ax.set_xlabel("Complexity of the batching rule") + ax.set_ylabel("Number of batched tasks") + ax.set_zlabel("Simulation time, sec") + ax.set_title(f"Model: {model_name}, instances: {num_of_instances}") + + # plt.colorbar() # show color scale + plt.show() + # save as a file + # plt.savefig(plt_path, bbox_inches="tight") + + +def _get_abs_path(*args): + return os.path.join(os.path.dirname(__file__), *args) + + +if __name__ == "__main__": + main() diff --git a/performance_exp/batching/testing_files.py b/performance_exp/batching/testing_files.py new file mode 100644 index 0000000..c275785 --- /dev/null +++ b/performance_exp/batching/testing_files.py @@ -0,0 +1,35 @@ +import numpy as np + + +process_files_setup = { + "simple_example": { + "bpmn": "simple_example/batch-example-end-task.bpmn", + "json": "simple_example/batch-example-with-batch.json", + "results_folder": "simple_example/results", + "start_datetime": "2022-06-21 13:22:30.035185+03:00", + "total_cases": 1000, + "disc_params": [60, 0.1, 0.9, 0.6, True], + "max_num_tasks_with_batching": 5, # should be the max number of tasks in the model + "measure_central_tendency": np.median, + "max_iter_num": 2, + "max_complexity_level": 4, + }, + "bpi2012": { + "bpmn": "bpi2012/input/BPI_Challenge_2012_W_Two_TS.bpmn", + "json": "bpi2012/input/bpi_2012.json", + "results_folder": "bpi2012/results", + "start_datetime": "2011-10-01 11:08:36.700000+03:00", # used to be as close to the real log as possible + "total_cases": 8616, + "disc_params": [ + 60, + 0.5, + 0.5, + 0.1, + True, + ], # to know the input required for discovering the json from the log + "max_num_tasks_with_batching": 6, # should be the max number of tasks in the model + "measure_central_tendency": np.median, # median since this metric is not impacted by outliers, compared to the mean one + "max_iter_num": 5, + "max_complexity_level": 4, + }, +} diff --git a/performance_exp/events/bpi2012/Archive.zip b/performance_exp/events/bpi2012/Archive.zip new file mode 100644 index 0000000..b420c38 Binary files /dev/null and b/performance_exp/events/bpi2012/Archive.zip differ diff --git a/performance_exp/events/bpi2012/results/.gitkeep b/performance_exp/events/bpi2012/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/performance_exp/events/generated_events/.gitkeep b/performance_exp/events/generated_events/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/performance_exp/events/input/batch-example-end-task.bpmn b/performance_exp/events/input/batch-example-end-task.bpmn new file mode 100644 index 0000000..9a4b123 --- /dev/null +++ b/performance_exp/events/input/batch-example-end-task.bpmn @@ -0,0 +1,189 @@ + + + + + + + + + + + sid-E469684F-C09F-4A8B-A916-E9927BA15372 + + + + + + + + + + + sid-6FD4FFD3-5784-4D33-9509-234EAB886930 + sid-10E6C62E-2CBD-476A-976B-B862156F5DEC + + + + + + + + + + + sid-9E95A790-241E-4629-8D67-E9A2CE55E3DC + sid-FF95F9DA-C10F-455B-B2FC-FBC1C270C0B4 + + + + + + + + + + + sid-E469684F-C09F-4A8B-A916-E9927BA15372 + sid-FA2D48D3-A316-4C2F-90DB-C2390990D727 + + + + + + + + + + + Flow_0ah4nto + Flow_0plakw8 + + + + + + + + + + + + + + + + + + + + + + + + + + + sid-FA2D48D3-A316-4C2F-90DB-C2390990D727 + sid-6FD4FFD3-5784-4D33-9509-234EAB886930 + sid-9E95A790-241E-4629-8D67-E9A2CE55E3DC + + + + + + + + sid-FF95F9DA-C10F-455B-B2FC-FBC1C270C0B4 + sid-10E6C62E-2CBD-476A-976B-B862156F5DEC + Flow_0ah4nto + + + Flow_0plakw8 + Flow_1xezdfv + + + + Flow_1xezdfv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/performance_exp/events/input/batch-example-with-batch.json b/performance_exp/events/input/batch-example-with-batch.json new file mode 100644 index 0000000..1707041 --- /dev/null +++ b/performance_exp/events/input/batch-example-with-batch.json @@ -0,0 +1,627 @@ +{ + "resource_profiles": [ + { + "id": "sid-generic-resource", + "name": "Generic_Resource", + "resource_list": [ + { + "id": "Resource_1", + "name": "Resource_1", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_2", + "name": "Resource_2", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_3", + "name": "Resource_3", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_4", + "name": "Resource_4", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_5", + "name": "Resource_5", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + } + ] + } + ], + "arrival_time_distribution": { + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + "arrival_time_calendar": [ + { + "from": "MONDAY", + "to": "SUNDAY", + "beginTime": "00:00:00.000", + "endTime": "23:59:59.999" + } + ], + "gateway_branching_probabilities": [ + { + "gateway_id": "sid-6B518C80-2B96-4C95-B6DE-F9E4A75FF191", + "probabilities": [ + { + "path_id": "sid-6FD4FFD3-5784-4D33-9509-234EAB886930", + "value": 0.3 + }, + { + "path_id": "sid-9E95A790-241E-4629-8D67-E9A2CE55E3DC", + "value": 0.7 + } + ] + } + ], + "task_resource_distribution": [ + { + "task_id": "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "sid-503A048D-6344-446A-8D67-172B164CF8FA", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "Activity_0ngxjs9", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + } + ], + "resource_calendars": [ + { + "id": "247timetable", + "name": "247timetable", + "time_periods": [ + { + "from": "MONDAY", + "to": "SUNDAY", + "beginTime": "00:00:00.000", + "endTime": "23:59:59.999" + } + ] + } + ], + "batch_processing": [ + { + "task_id": "sid-503A048D-6344-446A-8D67-172B164CF8FA", + "type": "Sequential", + "batch_frequency": 1.0, + "size_distrib": [ + { + "key": "1", + "value": 6 + }, + { + "key": "3", + "value": 35 + }, + { + "key": "4", + "value": 3 + } + ], + "duration_distrib": [ + { + "key": "3", + "value": 0.8 + } + ], + "firing_rules": [ + [ + { + "attribute": "size", + "comparison": "=", + "value": 3 + } + ] + ] + } + ], + "case_attributes": [ + { + "name": "client_type", + "type": "discrete", + "values": [ + { + "key": "REGULAR", + "value": 0.8 + }, + { + "key": "BUSINESS", + "value": 0.2 + } + ] + }, + { + "name": "loan_amount", + "type": "continuous", + "values": { + "distribution_name": "fix", + "distribution_params": [ + { + "value": 240 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + } + ], + "event_distribution": [ + { + "event_id": "event_0", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 900.0 + } + ] + }, + { + "event_id": "event_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 900.0 + } + ] + }, + { + "event_id": "event_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 900.0 + } + ] + } + ] +} \ No newline at end of file diff --git a/performance_exp/events/input/results/.gitkeep b/performance_exp/events/input/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/performance_exp/events/perform_events_runner.py b/performance_exp/events/perform_events_runner.py new file mode 100644 index 0000000..14dfdea --- /dev/null +++ b/performance_exp/events/perform_events_runner.py @@ -0,0 +1,110 @@ +import json +import os + +import matplotlib.pyplot as plt +from bpdfr_simulation_engine.simulation_properties_parser import ( + EVENT_DISTRIBUTION_SECTION, +) +from performance_exp.events.testing_files import process_files_setup +from performance_exp.shared_func import ( + run_whole_experiment, +) +from testing_scripts.bimp_diff_sim_tests import run_diff_res_simulation + + +def main(): + all_models = [ + "events_exp", + ] + for model_name in all_models: + run_whole_experiment( + model_name, + process_files_setup[model_name], + "number_of_added_events", + _get_abs_path, + run_one_iteration, + _save_plot, + False, + ) + + +def run_one_iteration(num_inserted_events: int, model_info): + initial_json_path = _get_abs_path(model_info["json"]) + bpmn_path = _get_abs_path(model_info["bpmn"]) + demo_stats = _get_abs_path( + model_info["results_folder"], f"{num_inserted_events}_stats.csv" + ) + sim_log = _get_abs_path( + model_info["results_folder"], + f"{num_inserted_events}_logs.csv", + ) + new_json_path = _setup_event_distribution(initial_json_path, num_inserted_events) + + simulation_time, _ = run_diff_res_simulation( + model_info["start_datetime"], + model_info["total_cases"], + bpmn_path, + new_json_path, + demo_stats, + sim_log, + True, + num_inserted_events, + ) + + return simulation_time + # diff_sim_result.print_simulation_results() + + +def _setup_event_distribution(initial_json_path, num_events: int): + """ + Create event distribution for all events that will be later added to the model + Save the newly created json in new location to keep track of the setup for simulations + """ + + event_distr_list = [ + { + "event_id": f"event_{index}", + "distribution_name": "fix", + "distribution_params": [{"value": 900.0}], + } + for index in range(num_events) + ] + + with open(initial_json_path, "r") as f: + json_dict = json.load(f) + + json_dict[EVENT_DISTRIBUTION_SECTION] = event_distr_list + + # save modified json as a new file specifying the number of experiment + # in order to keep track of run experiments + folder_loc = os.path.dirname(initial_json_path) + new_filename = f"{num_events}_events_exp.json" + new_json_path = os.path.join(folder_loc, new_filename) + + with open(new_json_path, "w+") as json_file: + json.dump(json_dict, json_file) + + return new_json_path + + +def _save_plot(xpoints, ypoints, model_name, num_of_instances, plt_path): + # give a general title + plt.title(f"Model: {model_name}, instances: {num_of_instances}") + + # name axis + plt.xlabel("Number of added events") + plt.ylabel("Simulation time, sec") + + # provide data points + plt.plot(xpoints, ypoints) + + # save as a file + plt.savefig(plt_path, bbox_inches="tight") + + +def _get_abs_path(*args): + return os.path.join(os.path.dirname(__file__), *args) + + +if __name__ == "__main__": + main() diff --git a/performance_exp/events/testing_files.py b/performance_exp/events/testing_files.py new file mode 100644 index 0000000..e6a1076 --- /dev/null +++ b/performance_exp/events/testing_files.py @@ -0,0 +1,27 @@ +import numpy as np + + +process_files_setup = { + "events_exp": { + "bpmn": "input/batch-example-end-task.bpmn", + "json": "input/batch-example-with-batch.json", + "results_folder": "input/results", + "start_datetime": "2022-06-21 13:22:30.035185+03:00", + "total_cases": 1000, + "disc_params": [60, 0.1, 0.9, 0.6, True], + "number_of_added_events": 9, # should be equal to the number of sequence flows in the BPMN model + "measure_central_tendency": np.median, + "max_iter_num": 1, + }, + "bpi2012_median_5": { + "bpmn": "bpi2012/BPI_Challenge_2012_W_Two_TS.bpmn", + "json": "bpi2012/bpi_2012.json", + "results_folder": "bpi2012/results", + "start_datetime": "2011-10-01 11:08:36.700000+03:00", # used to be as close to the real log as possible + "total_cases": 8616, + "disc_params": [60, 0.5, 0.5, 0.1, True], # to know the input required for discovering the json from the log + "number_of_added_events": 36, # should be equal to the number of sequence flows in the BPMN model + "measure_central_tendency": np.median, # median since this metric is not impacted by outliers, compared to the mean one + "max_iter_num": 5, + }, +} diff --git a/performance_exp/prioritisation/bpi2012/input.zip b/performance_exp/prioritisation/bpi2012/input.zip new file mode 100644 index 0000000..43aae77 Binary files /dev/null and b/performance_exp/prioritisation/bpi2012/input.zip differ diff --git a/performance_exp/prioritisation/bpi2012/results/.gitkeep b/performance_exp/prioritisation/bpi2012/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/performance_exp/prioritisation/perform_prioritisation_runner.py b/performance_exp/prioritisation/perform_prioritisation_runner.py new file mode 100644 index 0000000..193928c --- /dev/null +++ b/performance_exp/prioritisation/perform_prioritisation_runner.py @@ -0,0 +1,166 @@ +import json +import os + +import matplotlib.pyplot as plt + +from bpdfr_simulation_engine.simulation_properties_parser import ( + PRIORITISATION_RULES_SECTION, +) +from performance_exp.prioritisation.testing_files import process_files_setup +from performance_exp.shared_func import ( + run_whole_experiment, +) +from testing_scripts.bimp_diff_sim_tests import run_diff_res_simulation + + +def main(): + model_name = "bpi2012" + + run_whole_experiment( + model_name, + process_files_setup[model_name], + "number_of_priority_levels", + _get_abs_path, + run_one_iteration, + _save_plot, + True, + ) + + +def run_one_iteration(num_prioritisation_rules: int, model_info): + initial_json_path = _get_abs_path(model_info["json"]) + bpmn_path = _get_abs_path(model_info["bpmn"]) + demo_stats = _get_abs_path( + model_info["results_folder"], f"{num_prioritisation_rules}_stats.csv" + ) + sim_log = _get_abs_path( + model_info["results_folder"], + f"{num_prioritisation_rules}_logs.csv", + ) + new_json_path = _setup_sim_scenario(initial_json_path, num_prioritisation_rules) + + simulation_time, _ = run_diff_res_simulation( + model_info["start_datetime"], + model_info["total_cases"], + bpmn_path, + new_json_path, + demo_stats, + sim_log, + False, # no events in the log + None, # no added events + ) + + return simulation_time + # diff_sim_result.print_simulation_results() + + +def _setup_sim_scenario(initial_json_path, num_prioritisation_rules: int): + """ + Create case-based prioritisation rules based on the required number (num_prioritisation_rules) + Save the newly created json in new location to keep track of the setup for simulations + """ + + prioritisation_rules = [ + { + "priority_level": 1, + "rules": [ + [ + { + "attribute": "loan_amount", + "comparison": "in", + "value": [2000, "inf"], + } + ], + ], + }, + { + "priority_level": 2, + "rules": [ + [ + { + "attribute": "loan_amount", + "comparison": "in", + "value": [1500, 2000], + } + ], + ], + }, + { + "priority_level": 3, + "rules": [ + [ + { + "attribute": "loan_amount", + "comparison": "in", + "value": [1000, 1500], + } + ], + ], + }, + { + "priority_level": 4, + "rules": [ + [ + { + "attribute": "loan_amount", + "comparison": "in", + "value": [800, 1000], + } + ], + ], + }, + { + "priority_level": 5, + "rules": [ + [{"attribute": "loan_amount", "comparison": "in", "value": [500, 800]}], + ], + }, + { + "priority_level": 6, + "rules": [ + [{"attribute": "loan_amount", "comparison": "in", "value": [0, 500]}], + ], + }, + ] + + with open(initial_json_path, "r") as f: + json_dict = json.load(f) + + json_dict[PRIORITISATION_RULES_SECTION] = prioritisation_rules[ + :num_prioritisation_rules + ] + + # save modified json as a new file specifying the number of experiment + # in order to keep track of run experiments + folder_loc = os.path.dirname(initial_json_path) + new_filename = f"{num_prioritisation_rules}_prioritisation_rules_exp.json" + new_json_path = os.path.join(folder_loc, new_filename) + + with open(new_json_path, "w+") as json_file: + json.dump(json_dict, json_file) + + return new_json_path + + +def _save_plot(xpoints, ypoints, model_name, num_of_instances, plt_path, is_ms=False): + # give a general title + plt.title(f"Model: {model_name}, instances: {num_of_instances}") + + # name axis + plt.xlabel("Number of priority levels") + time_measure = "ms" if is_ms else "sec" + plt.ylabel(f"Simulation time, {time_measure}") + + # provide data points + plt.plot(xpoints, ypoints) + + # save as a file + plt.savefig(plt_path, bbox_inches="tight") + + +def _get_abs_path(*args): + return os.path.join(os.path.dirname(__file__), *args) + + +if __name__ == "__main__": + main() diff --git a/performance_exp/prioritisation/simple_example/batch-example-end-task.bpmn b/performance_exp/prioritisation/simple_example/batch-example-end-task.bpmn new file mode 100644 index 0000000..9a4b123 --- /dev/null +++ b/performance_exp/prioritisation/simple_example/batch-example-end-task.bpmn @@ -0,0 +1,189 @@ + + + + + + + + + + + sid-E469684F-C09F-4A8B-A916-E9927BA15372 + + + + + + + + + + + sid-6FD4FFD3-5784-4D33-9509-234EAB886930 + sid-10E6C62E-2CBD-476A-976B-B862156F5DEC + + + + + + + + + + + sid-9E95A790-241E-4629-8D67-E9A2CE55E3DC + sid-FF95F9DA-C10F-455B-B2FC-FBC1C270C0B4 + + + + + + + + + + + sid-E469684F-C09F-4A8B-A916-E9927BA15372 + sid-FA2D48D3-A316-4C2F-90DB-C2390990D727 + + + + + + + + + + + Flow_0ah4nto + Flow_0plakw8 + + + + + + + + + + + + + + + + + + + + + + + + + + + sid-FA2D48D3-A316-4C2F-90DB-C2390990D727 + sid-6FD4FFD3-5784-4D33-9509-234EAB886930 + sid-9E95A790-241E-4629-8D67-E9A2CE55E3DC + + + + + + + + sid-FF95F9DA-C10F-455B-B2FC-FBC1C270C0B4 + sid-10E6C62E-2CBD-476A-976B-B862156F5DEC + Flow_0ah4nto + + + Flow_0plakw8 + Flow_1xezdfv + + + + Flow_1xezdfv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/performance_exp/prioritisation/simple_example/batch-example-with-batch.json b/performance_exp/prioritisation/simple_example/batch-example-with-batch.json new file mode 100644 index 0000000..ed52bf4 --- /dev/null +++ b/performance_exp/prioritisation/simple_example/batch-example-with-batch.json @@ -0,0 +1,595 @@ +{ + "resource_profiles": [ + { + "id": "sid-generic-resource", + "name": "Generic_Resource", + "resource_list": [ + { + "id": "Resource_1", + "name": "Resource_1", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_2", + "name": "Resource_2", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_3", + "name": "Resource_3", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_4", + "name": "Resource_4", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + }, + { + "id": "Resource_5", + "name": "Resource_5", + "cost_per_hour": 1, + "amount": 1, + "calendar": "247timetable", + "assigned_tasks": [ + "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "sid-503A048D-6344-446A-8D67-172B164CF8FA" + ] + } + ] + } + ], + "arrival_time_distribution": { + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + "arrival_time_calendar": [ + { + "from": "MONDAY", + "to": "SUNDAY", + "beginTime": "00:00:00.000", + "endTime": "23:59:59.999" + } + ], + "gateway_branching_probabilities": [ + { + "gateway_id": "sid-6B518C80-2B96-4C95-B6DE-F9E4A75FF191", + "probabilities": [ + { + "path_id": "sid-6FD4FFD3-5784-4D33-9509-234EAB886930", + "value": 0.3 + }, + { + "path_id": "sid-9E95A790-241E-4629-8D67-E9A2CE55E3DC", + "value": 0.7 + } + ] + } + ], + "task_resource_distribution": [ + { + "task_id": "sid-4B24111F-B305-4608-9E12-744B47C44D0D", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "sid-D048D99D-F549-43B8-8ACB-5AE153B12B0F", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "sid-02577CBF-ABA3-4EFD-9480-E1DFCF238B1C", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "sid-503A048D-6344-446A-8D67-172B164CF8FA", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + }, + { + "task_id": "Activity_0ngxjs9", + "resources": [ + { + "resource_id": "Resource_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_3", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_4", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + }, + { + "resource_id": "Resource_5", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 120 + }, + { + "value": 0 + }, + { + "value": 1 + } + ] + } + ] + } + ], + "resource_calendars": [ + { + "id": "247timetable", + "name": "247timetable", + "time_periods": [ + { + "from": "MONDAY", + "to": "SUNDAY", + "beginTime": "00:00:00.000", + "endTime": "23:59:59.999" + } + ] + } + ], + "batch_processing": [], + "case_attributes": [ + { + "name": "client_type", + "type": "discrete", + "values": [ + { + "key": "REGULAR", + "value": 0.8 + }, + { + "key": "BUSINESS", + "value": 0.2 + } + ] + }, + { + "name": "loan_amount", + "type": "continuous", + "values": { + "distribution_name": "expon", + "distribution_params": [ + { + "value": 1000 + }, + { + "value": 500.0 + }, + { + "value": 0 + }, + { + "value": 5400 + } + ] + } + } + ], + "event_distribution": [ + { + "event_id": "event_0", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 900.0 + } + ] + }, + { + "event_id": "event_1", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 900.0 + } + ] + }, + { + "event_id": "event_2", + "distribution_name": "fix", + "distribution_params": [ + { + "value": 900.0 + } + ] + } + ] +} \ No newline at end of file diff --git a/performance_exp/prioritisation/simple_example/results/.gitkeep b/performance_exp/prioritisation/simple_example/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/performance_exp/prioritisation/testing_files.py b/performance_exp/prioritisation/testing_files.py new file mode 100644 index 0000000..9d6bdbb --- /dev/null +++ b/performance_exp/prioritisation/testing_files.py @@ -0,0 +1,32 @@ +import numpy as np + + +process_files_setup = { + "simple_example": { + "bpmn": "simple_example/batch-example-end-task.bpmn", + "json": "simple_example/batch-example-with-batch.json", + "results_folder": "simple_example/results", + "start_datetime": "2022-06-21 13:22:30.035185+03:00", + "total_cases": 1000, + "disc_params": [60, 0.1, 0.9, 0.6, True], + "number_of_priority_levels": 6, + "max_iter_num": 5, + }, + "bpi2012": { + "bpmn": "bpi2012/input/BPI_Challenge_2012_W_Two_TS.bpmn", + "json": "bpi2012/input/bpi_2012.json", + "results_folder": "bpi2012/results", + "start_datetime": "2011-10-01 11:08:36.700000+03:00", # used to be as close to the real log as possible + "total_cases": 8616, + "disc_params": [ + 60, + 0.5, + 0.5, + 0.1, + True, + ], # to know the input required for discovering the json from the log + "number_of_priority_levels": 6, # should be enough to detect the main trend of the received results + "measure_central_tendency": np.median, # median since this metric is not impacted by outliers, compared to the mean one + "max_iter_num": 5, + }, +} diff --git a/performance_exp/shared_func.py b/performance_exp/shared_func.py new file mode 100644 index 0000000..3cc1686 --- /dev/null +++ b/performance_exp/shared_func.py @@ -0,0 +1,169 @@ +import os +import uuid + +import numpy as np +import pandas as pd + + +def get_central_tendency_over_all_iters( + max_iter_num, + run_one_iteration, + current_index, + model_info, + measure_central_tendency, + _get_abs_path, + is_normalised=False, +): + """ + Run one iteration n number of times and calculate the central tendency of simulation times + + :param int max_iter_num: Number of iteration to run + :param func run_one_iteration: Function that needs to be run per one iteration + :param int current_index: + :param obj model_info: Description of the input provided by the user + :param func measure_central_tendency: numpy function used for calculating the central tendency (e.g., np.mean, np.median) + :param bool is_normalised: whether the simulation time is divided by the total number of activities during simulation + """ + same_index_sim_time_list = [] + + results_folder_path = _get_abs_path(model_info["results_folder"]) + current_log_path = get_log_filepath(results_folder_path, current_index) + normalised_data_path = os.path.join(results_folder_path, "normalised_table.csv") + + for iter_num in range(0, max_iter_num): + sim_time = run_one_iteration(current_index, model_info) + + if is_normalised: + # in case of normalising, we divide the simulation time + # by the number of activities run during the simulation + sim_time, num_total_activities, norm_sim_time = get_normalised_sim_time( + current_log_path, + sim_time, + normalised_data_path, + current_index, + iter_num, + ) + + print(f"iter {iter_num}: {sim_time}") + same_index_sim_time_list.append(sim_time) + + median_sim_time = measure_central_tendency(same_index_sim_time_list) + print(f"central_tendency: {median_sim_time}") + + if is_normalised: + # append row with statistics for the median value + with open(normalised_data_path, "a") as plot_file: + plot_file.write( + f"{current_index},median,{sim_time},{num_total_activities},{norm_sim_time}\n" + ) + + return median_sim_time + + +def run_whole_experiment( + model_name, + model_info, + metric_under_performance_range_str, + _get_abs_path, + run_one_iteration, + _save_plot, + is_sim_time_normalised, +): + total_number_of_x_values = model_info[metric_under_performance_range_str] + measure_central_tendency = ( + model_info["measure_central_tendency"] + if "measure_central_tendency" in model_info + else np.median + ) + max_iter_num = model_info["max_iter_num"] + + print(f"Selected log: {model_name}") + print(f"Selected function for central tendency: {measure_central_tendency}") + + number_of_events_to_add_list = range(0, 1 + total_number_of_x_values) + + # array to save ordinate (y coordinate) of data points + sim_time_list = [] + + # unique identifier of the experiment run + unique_run_id = uuid.uuid4() + + # file for saving received results (number_inserted_events, simulation_time) + final_plot_results = _get_abs_path( + model_info["results_folder"], + f"{unique_run_id}_plot_data.csv", + ) + + # add name of the model used during this experiment + with open(final_plot_results, "a") as plot_file: + plot_file.write(f"{model_name}\n\n") + + # clear file with normalised data + results_folder_path = _get_abs_path(model_info["results_folder"]) + normalised_data = os.path.join(results_folder_path, "normalised_table.csv") + + with open(normalised_data, "a") as plot_file: + plot_file.write( + f"num_priority_levels,iter,sim_time_sec,num_total_activities,norm_sim_time_ms\n" + ) + + for index in number_of_events_to_add_list: + print("-------------------------------------------") + print(f"Starting Simulation with {index} inserted events") + print("-------------------------------------------") + + median_sim_time = get_central_tendency_over_all_iters( + max_iter_num, + run_one_iteration, + index, + model_info, + measure_central_tendency, + _get_abs_path, + is_sim_time_normalised, + ) + sim_time_list.append(median_sim_time) + + with open(final_plot_results, "a") as plot_file: + plot_file.write(f"{index},{median_sim_time}\n") + + print(sim_time_list) + + # save plot of the relationship: number of added events - simulation time + plt_path = _get_abs_path( + model_info["results_folder"], + f"{unique_run_id}_plot.png", + ) + _save_plot( + np.array(number_of_events_to_add_list), + sim_time_list, + model_name, + model_info["total_cases"], + plt_path, + is_sim_time_normalised, # use ms if True + ) + + +def get_log_filepath(results_folder_path, index): + return os.path.join(results_folder_path, f"{index}_logs.csv") + + +def get_total_num_activities(log_path): + df = pd.read_csv(log_path) + return df.shape[0] # number of rows + + +def get_normalised_sim_time( + current_log_path, sim_time, normalised_data_path, current_index, iter_num +): + num_total_activities = get_total_num_activities(current_log_path) + norm_sim_time = sim_time / num_total_activities # seconds + norm_sim_time = norm_sim_time * 1000 # miliseconds + + # append row with statistics for this current iteration + with open(normalised_data_path, "a") as plot_file: + plot_file.write( + f"{current_index},{iter_num},{sim_time},{num_total_activities},{norm_sim_time}\n" + ) + + # normalised sim_time used as a final one + return norm_sim_time, num_total_activities, norm_sim_time diff --git a/testing_scripts/bimp_diff_sim_tests.py b/testing_scripts/bimp_diff_sim_tests.py index 4b6366f..9291390 100644 --- a/testing_scripts/bimp_diff_sim_tests.py +++ b/testing_scripts/bimp_diff_sim_tests.py @@ -59,9 +59,9 @@ def run_bimp_simulation(model_file_path, results_file_path, simulation_log, # return load_bimp_simulation_results(results_file_path, simulation_log) -def run_diff_res_simulation(start_date, total_cases, bpmn_model, json_sim_params, out_stats_csv_path, out_log_csv_path, is_event_added_to_log = False): +def run_diff_res_simulation(start_date, total_cases, bpmn_model, json_sim_params, out_stats_csv_path, out_log_csv_path, is_event_added_to_log = False, num_generated_events=None): s_t = datetime.datetime.now() - run_simulation(bpmn_model, json_sim_params, total_cases, out_stats_csv_path, out_log_csv_path, start_date, is_event_added_to_log) + run_simulation(bpmn_model, json_sim_params, total_cases, out_stats_csv_path, out_log_csv_path, start_date, is_event_added_to_log, num_generated_events) sim_time = (datetime.datetime.now() - s_t).total_seconds() # print((datetime.datetime.now() - s_t).total_seconds()) # print("DiffSim Execution Times: %s" %