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