From 233ab338375d45d78ddaa0a7200e76c3e8e02380 Mon Sep 17 00:00:00 2001 From: Maximilian Hoerstrup Date: Fri, 12 Apr 2024 11:39:19 +0200 Subject: [PATCH] Correct the clustering of nodes The way that the nodes (places and transitions) in the petri net were clustered with the help of SNAKES was wrong. Now, the cluster is not overwritten anymore, but correctly added in the intended way. This resolves a bug where the deletion of specific nodes resulted in an KeyError in the snakes library. --- pfdl_scheduler/petri_net/generator.py | 260 +++++++++++++------------- 1 file changed, 128 insertions(+), 132 deletions(-) diff --git a/pfdl_scheduler/petri_net/generator.py b/pfdl_scheduler/petri_net/generator.py index e986f9c..c43accc 100644 --- a/pfdl_scheduler/petri_net/generator.py +++ b/pfdl_scheduler/petri_net/generator.py @@ -7,6 +7,7 @@ """Contains the PetriNetGenerator class.""" # standard libraries +import copy from typing import Any, Callable, Dict, List, OrderedDict, Tuple import uuid import functools @@ -37,18 +38,28 @@ class Node(object): + """Represents a type of component relative to its position in the call graph + + Attributes: + group_uuid: The id of the group of petri net components this node represents + """ + def __init__(self, group_uuid: str, name="", parent: "Node" = None): self.group_uuid: str = group_uuid self.name: str = name self.children: List[Node] = [] - self.cluster = None - + self.path = [] if parent: parent.add_child(self) + self.path = copy.deepcopy(parent.path) + self.path.append(len(parent.children)) def add_child(self, node: "Node"): self.children.append(node) + def get_path(self) -> List[str]: + return self.path + def toJSON(self): children_list = [] for child in self.children: @@ -110,7 +121,6 @@ def __init__( self.used_in_extension: bool = used_in_extension self.tree = None self.file_name = file_name - self.cluster = None def add_callback(self, transition_uuid: str, callback_function: Callable, *args: Any) -> None: """Registers the given callback function in the transition_dict. @@ -141,52 +151,46 @@ def generate_petri_net(self, process: Process) -> PetriNet: A PetriNet instance representing the generated net. """ self.tasks = process.tasks - for task in process.tasks.values(): - if task.name == "productionTask": - group_uuid = str(uuid.uuid4()) - self.tree = Node(group_uuid, task.name) - - task_context = TaskAPI(task, None) - if self.generate_test_ids: - task_context.uuid = "0" - - self.task_started_uuid = create_place( - task.name + "_started", self.net, group_uuid, [0, 0] - ) - connection_uuid = create_transition("", "", self.net, group_uuid) - - self.add_callback(connection_uuid, self.callbacks.task_started, task_context) - - self.net.add_input(self.task_started_uuid, connection_uuid, Value(1)) - self.task_finished_uuid = create_place( - task.name + "_finished", self.net, group_uuid - ) - - second_connection_uuid = create_transition("", "", self.net, group_uuid) - - self.tree.cluster = Cluster( - [ - self.task_started_uuid, - connection_uuid, - second_connection_uuid, - self.task_finished_uuid, - ] - ) - self.generate_statements( - task_context, - task.statements, - connection_uuid, - second_connection_uuid, - self.tree, - ) - self.net.add_output(self.task_finished_uuid, second_connection_uuid, Value(1)) - - self.add_callback( - second_connection_uuid, self.callbacks.task_finished, task_context - ) - - # assign new clusters before drawing - self.net.clusters = self.tree.cluster + production_task = process.tasks["productionTask"] + group_uuid = str(uuid.uuid4()) + self.tree = Node(group_uuid, production_task.name) + + task_context = TaskAPI(production_task, None) + if self.generate_test_ids: + task_context.uuid = "0" + + self.task_started_uuid = create_place( + production_task.name + "_started", self.net, self.tree + ) + connection_uuid = create_transition("", "", self.net, self.tree) + + self.add_callback(connection_uuid, self.callbacks.task_started, task_context) + + self.net.add_input(self.task_started_uuid, connection_uuid, Value(1)) + self.task_finished_uuid = create_place( + production_task.name + "_finished", self.net, self.tree + ) + + second_connection_uuid = create_transition("", "", self.net, self.tree) + + self.tree.cluster = Cluster( + [ + self.task_started_uuid, + connection_uuid, + second_connection_uuid, + self.task_finished_uuid, + ] + ) + self.generate_statements( + task_context, + production_task.statements, + connection_uuid, + second_connection_uuid, + self.tree, + ) + self.net.add_output(self.task_finished_uuid, second_connection_uuid, Value(1)) + + self.add_callback(second_connection_uuid, self.callbacks.task_finished, task_context) if self.draw_net: json_string = json.dumps(self.tree.toJSON(), indent=4) @@ -227,10 +231,8 @@ def generate_statements( # and we are not in the last iteration if multiple_statements: if i < len(statements) - 1: - current_connection_uuid = create_transition( - "connection", "", self.net, node.group_uuid - ) - node.cluster.add_node(current_connection_uuid) + current_connection_uuid = create_transition("connection", "", self.net, node) + # node.cluster.add_child(Cluster(current_connection_uuid)) else: current_connection_uuid = last_connection_uuid else: @@ -265,25 +267,6 @@ def generate_statements( return connection_uuids - def handle_other_statements( - self, - statement: Any, - task_context: TaskAPI, - first_transition_uuid: str, - second_transition_uuid: str, - node: Node, - in_loop: bool = False, - ) -> List[str]: - """Generates Petri Net components for newly added PFDL components. - - This function can be used by plugin developers to generate Petri Net components - if they add new components through their plugin. - - Returns: - A list of uuids of the last transition in the respective component. - """ - return None - def generate_service( self, service: Service, @@ -299,17 +282,17 @@ def generate_service( The uuid of the last transition of the Service petri net component. """ group_uuid = str(uuid.uuid4()) - Node(group_uuid, service.name, node) + service_node = Node(group_uuid, service.name, node) service_api = ServiceAPI(service, task_context, in_loop=in_loop) - service_started_uuid = create_place(service.name + " started", self.net, group_uuid) - service_finished_uuid = create_place(service.name + " finished", self.net, group_uuid) + service_started_uuid = create_place(service.name + " started", self.net, service_node) + service_finished_uuid = create_place(service.name + " finished", self.net, service_node) self.place_dict[service_api.uuid] = service_finished_uuid - service_done_uuid = create_place(service.name + " done", self.net, group_uuid) - service_done_transition_uuid = create_transition("service_done", "", self.net, group_uuid) + service_done_uuid = create_place(service.name + " done", self.net, service_node) + service_done_transition_uuid = create_transition("service_done", "", self.net, service_node) self.add_callback(first_transition_uuid, self.callbacks.service_started, service_api) self.add_callback( @@ -323,17 +306,13 @@ def generate_service( self.net.add_output(service_started_uuid, first_transition_uuid, Value(1)) self.net.add_input(service_done_uuid, second_transition_uuid, Value(1)) - node.cluster.add_child( - ( - Cluster( - [ - service_started_uuid, - service_finished_uuid, - service_done_transition_uuid, - service_done_uuid, - ] - ) - ) + service_node.cluster = Cluster( + [ + service_started_uuid, + service_finished_uuid, + service_done_transition_uuid, + service_done_uuid, + ] ) return service_done_transition_uuid @@ -357,10 +336,6 @@ def generate_task_call( group_uuid = str(uuid.uuid4()) task_node = Node(group_uuid, task_call.name, node) - task_cluster = Cluster([]) - node.cluster.add_child(task_cluster) - task_node.cluster = task_cluster - # Order for callbacks important: Task starts before statement and finishes after self.add_callback(first_transition_uuid, self.callbacks.task_started, new_task_context) last_connection_uuids = self.generate_statements( @@ -396,7 +371,7 @@ def generate_parallel( parallel_node = Node(group_uuid, "Parallel", node) sync_uuid = create_transition("", "", self.net, group_uuid) - parallel_finished_uuid = create_place("Parallel finished", self.net, group_uuid) + parallel_finished_uuid = create_place("Parallel finished", self.net, parallel_node) cluster = Cluster([], Cluster([sync_uuid, parallel_finished_uuid])) node.cluster.add_child(cluster) @@ -431,17 +406,17 @@ def generate_condition( group_uuid = str(uuid.uuid4()) condition_node = Node(group_uuid, "Condition", node) - passed_uuid = create_place("Passed", self.net, group_uuid) - failed_uuid = create_place("Failed", self.net, group_uuid) + passed_uuid = create_place("Passed", self.net, condition_node) + failed_uuid = create_place("Failed", self.net, condition_node) expression_uuid = create_place( "If " + self.parse_expression(condition.expression), self.net, - group_uuid, + condition_node, ) - first_passed_transition_uuid = create_transition("", "", self.net, group_uuid) - first_failed_transition_uuid = create_transition("", "", self.net, group_uuid) + first_passed_transition_uuid = create_transition("", "", self.net, condition_node) + first_failed_transition_uuid = create_transition("", "", self.net, condition_node) self.net.add_input(expression_uuid, first_passed_transition_uuid, Value(1)) self.net.add_input(expression_uuid, first_failed_transition_uuid, Value(1)) @@ -449,9 +424,9 @@ def generate_condition( self.net.add_input(passed_uuid, first_passed_transition_uuid, Value(1)) self.net.add_input(failed_uuid, first_failed_transition_uuid, Value(1)) - finished_uuid = create_place("Condition_Finished", self.net, group_uuid) + finished_uuid = create_place("Condition_Finished", self.net, condition_node) - second_passed_transition_uuid = create_transition("", "", self.net, group_uuid) + second_passed_transition_uuid = create_transition("", "", self.net, condition_node) self.net.add_output(finished_uuid, second_passed_transition_uuid, Value(1)) cluster = Cluster([passed_uuid, failed_uuid, expression_uuid, finished_uuid]) @@ -480,7 +455,7 @@ def generate_condition( if condition.failed_stmts: condition_node.cluster = cluster_failed - second_failed_transition_uuid = create_transition("", "", self.net, group_uuid) + second_failed_transition_uuid = create_transition("", "", self.net, condition_node) cluster_failed.add_node(second_failed_transition_uuid) self.generate_statements( task_context, @@ -522,12 +497,14 @@ def generate_counting_loop( loop_text = "Loop" - loop_statements_uuid = create_place(loop_text, self.net, group_uuid) - loop_finished_uuid = create_place("Number of Steps Done", self.net, group_uuid) + loop_statements_uuid = create_place(loop_text, self.net, counting_loop_node) + loop_finished_uuid = create_place("Number of Steps Done", self.net, counting_loop_node) - condition_passed_transition_uuid = create_transition("", "", self.net, group_uuid) - condition_failed_transition_uuid = create_transition("", "", self.net, group_uuid) - iteration_step_done_transition_uuid = create_transition("", "", self.net, group_uuid) + condition_passed_transition_uuid = create_transition("", "", self.net, counting_loop_node) + condition_failed_transition_uuid = create_transition("", "", self.net, counting_loop_node) + iteration_step_done_transition_uuid = create_transition( + "", "", self.net, counting_loop_node + ) self.net.add_input(loop_uuid, condition_passed_transition_uuid, Value(1)) self.net.add_input(loop_statements_uuid, condition_passed_transition_uuid, Value(1)) @@ -535,7 +512,7 @@ def generate_counting_loop( self.net.add_input(loop_finished_uuid, condition_failed_transition_uuid, Value(1)) self.net.add_output(loop_uuid, iteration_step_done_transition_uuid, Value(1)) - loop_done_uuid = create_place("Loop Done", self.net, group_uuid) + loop_done_uuid = create_place("Loop Done", self.net, counting_loop_node) cluster = Cluster( [ @@ -596,10 +573,10 @@ def generate_parallel_loop( parallel_loop_started = create_place( "Start " + loop.statements[0].name + " in parallel", self.net, - group_uuid, + parallel_loop_node, ) cluster = Cluster([parallel_loop_started]) - node.cluster.add_child(cluster) + # node.cluster.add_child(cluster) parallel_loop_node.cluster = cluster self.net.add_output(parallel_loop_started, first_transition_uuid, Value(1)) self.net.add_input(parallel_loop_started, second_transition_uuid, Value(1)) @@ -616,24 +593,45 @@ def generate_parallel_loop( self.add_callback(first_transition_uuid, self.callbacks.parallel_loop_started, *args) return second_transition_uuid + def handle_other_statements( + self, + statement: Any, + task_context: TaskAPI, + first_transition_uuid: str, + second_transition_uuid: str, + node: Node, + in_loop: bool = False, + ) -> List[str]: + """Generates Petri Net components for newly added PFDL components. + + This function can be used by plugin developers to generate Petri Net components + if they add new components through their plugin. + + Returns: + A list of uuids of the last transition in the respective component. + """ + return None + def remove_place_on_runtime(self, place_uuid: str) -> None: """Removes a place from the petri net at runtime. + This method tries to remove the place with the given id from the petri net. + Due to the clustering plugin in the petri net library, the `remove` place method + + Args: place_uuid: The uuid as string of the task which should be removed from the net. """ if self.net.has_place(place_uuid): - # temporary fix - # self.net.clusters.remove_node(self.task_started_uuid) - # self.net.remove_place(self.task_started_uuid) - + # self.net.clusters._nodes.add(place_uuid) + self.net.remove_place(place_uuid) if self.draw_net: draw_petri_net(self.net, self.path_for_image) def generate_empty_parallel_loop( - self, first_transition_uuid: str, second_transition_uuid: str + self, first_transition_uuid: str, second_transition_uuid: str, node: Node ) -> None: - empty_loop_place = create_place("Execute 0 tasks", self.net) + empty_loop_place = create_place("Execute 0 tasks", self.net, node) self.net.add_output(empty_loop_place, first_transition_uuid, Value(1)) self.net.add_input(empty_loop_place, second_transition_uuid, Value(1)) @@ -654,16 +652,16 @@ def generate_while_loop( group_uuid = str(uuid.uuid4()) while_loop_node = Node(group_uuid, "While Loop", node) - loop_uuid = create_place("Loop", self.net, group_uuid) + loop_uuid = create_place("Loop", self.net, while_loop_node) loop_text = "Loop" - loop_statements_uuid = create_place(loop_text, self.net, group_uuid) - loop_finished_uuid = create_place("Number of Steps Done", self.net, group_uuid) + loop_statements_uuid = create_place(loop_text, self.net, while_loop_node) + loop_finished_uuid = create_place("Number of Steps Done", self.net, while_loop_node) - condition_passed_transition_uuid = create_transition("", "", self.net, group_uuid) - condition_failed_transition_uuid = create_transition("", "", self.net, group_uuid) - iteration_step_done_transition_uuid = create_transition("", "", self.net, group_uuid) + condition_passed_transition_uuid = create_transition("", "", self.net, while_loop_node) + condition_failed_transition_uuid = create_transition("", "", self.net, while_loop_node) + iteration_step_done_transition_uuid = create_transition("", "", self.net, while_loop_node) self.net.add_input(loop_uuid, condition_passed_transition_uuid, Value(1)) self.net.add_input(loop_statements_uuid, condition_passed_transition_uuid, Value(1)) @@ -671,7 +669,7 @@ def generate_while_loop( self.net.add_input(loop_finished_uuid, condition_failed_transition_uuid, Value(1)) self.net.add_output(loop_uuid, iteration_step_done_transition_uuid, Value(1)) - loop_done_uuid = create_place("Loop Done", self.net, group_uuid) + loop_done_uuid = create_place("Loop Done", self.net, while_loop_node) cluster = Cluster( [ @@ -737,7 +735,7 @@ def parse_expression(self, expression: Dict) -> str: ) -def create_place(name: str, net: PetriNet, group_uuid: str, cluster: List = []) -> str: +def create_place(name: str, net: PetriNet, node: Node) -> str: """Utility function for creating a place with the snakes module. This function is used to add a place with the given name and to add labels for @@ -746,20 +744,18 @@ def create_place(name: str, net: PetriNet, group_uuid: str, cluster: List = []) Args: name: A string representing the displayed name of the place. net: The petri net instance this place should be added to. - group_uuid: + group_uuid: The id of a group of petri net components this place belongs to Returns: A UUID as string for the added place. """ place_uuid = str(uuid.uuid4()) - net.add_place(Place(place_uuid, []), cluster=cluster) - net.place(place_uuid).label(name=name, group_uuid=group_uuid) + net.add_place(Place(place_uuid, []), cluster=node.get_path()) + net.place(place_uuid).label(name=name, group_uuid=node.group_uuid) return place_uuid -def create_transition( - transition_name: str, transition_type: str, net: PetriNet, group_uuid: str -) -> str: +def create_transition(transition_name: str, transition_type: str, net: PetriNet, node: Node) -> str: """Utility function for creating a transition with the snakes module. This function is used to add a transition with the given name and to add labels for @@ -768,16 +764,16 @@ def create_transition( Args: transition_name: A string representing the displayed name of the transition. net: The petri net instance this transition should be added to. - group_uuid: + group_uuid: The id of a group of petri net components this transition belongs to Returns: A UUID as string for the added transition. """ transition_uuid = str(uuid.uuid4()) - net.add_transition(Transition(transition_uuid)) + net.add_transition(Transition(transition_uuid), cluster=node.get_path()) net.transition(transition_uuid).label( name=transition_name, transitionType=transition_type, - group_uuid=group_uuid, + group_uuid=node.group_uuid, ) return transition_uuid