def construct_tree(net, initial_marking): """ Construct a restricted coverability marking. For more information, see the thesis "Verification of WF-nets", 4.3. :param net: :param initial_marking: :return: """ initial_marking = helper.convert_marking(net, initial_marking) firing_dict = helper.split_incidence_matrix( helper.compute_incidence_matrix(net), net) req_dict = helper.compute_firing_requirement(net) look_up_indices = {} j = 0 coverability_graph = nx.DiGraph() coverability_graph.add_node(j, marking=initial_marking) look_up_indices[np.array2string(initial_marking)] = j j += 1 new_arc = True while new_arc: new_arc = False nodes = list(coverability_graph.nodes).copy() while len(nodes) > 0: m = nodes.pop() if not np.inf in coverability_graph.nodes[m]['marking']: possible_markings = helper.enabled_markings( firing_dict, req_dict, coverability_graph.nodes[m]['marking']) m2 = None if len(possible_markings) > 0: for marking in possible_markings: # check for m1 + since we want to construct a tree, we do not want that a marking is already in a graph since it is going to have an arc if np.array2string(marking[0]) not in look_up_indices: if check_if_transition_unique( m, coverability_graph, marking[1]): m2 = marking new_arc = True break if new_arc: break if new_arc: m3 = np.zeros(len(list(net.places))) for place in list(net.places): if check_for_smaller_marking(m2, coverability_graph, list(net.places).index(place), m, look_up_indices): m3[list(net.places).index(place)] = np.inf else: m3[list(net.places).index(place)] = m2[0][list( net.places).index(place)] coverability_graph.add_node(j, marking=m3) coverability_graph.add_edge(m, j, transition=m2[1]) look_up_indices[np.array2string(m3)] = j j += 1 return coverability_graph
def compute_non_live_sequences(woflan_object): """ We want to compute the sequences of transitions which lead to deadlocks. To do this, we first compute a reachbility graph (possible, since we know that the Petri Net is bounded) and then we convert it to a spanning tree. Afterwards, we compute the paths which lead to nodes from which the final marking cannot be reached. Note: We are searching for the shortest sequence. After the first red node, all successors are also red. Therefore, we do not have to consider them. :param woflan_object: Object that contains the necessary information :return: List of sequence of transitions, each sequence is a list """ woflan_object.set_r_g( reachability_graph(woflan_object.get_net(), woflan_object.get_initial_marking())) f_m = convert_marking(woflan_object.get_net(), woflan_object.get_final_marking()) sucessfull_terminate_state = None for node in woflan_object.get_r_g().nodes: if all(np.equal(woflan_object.get_r_g().nodes[node]['marking'], f_m)): sucessfull_terminate_state = node break # red nodes are those from which the final marking is not reachable red_nodes = [] for node in woflan_object.get_r_g().nodes: if not nx.has_path(woflan_object.get_r_g(), node, sucessfull_terminate_state): red_nodes.append(node) # Compute directed spanning tree spanning_tree = nx.algorithms.tree.Edmonds( woflan_object.get_r_g()).find_optimum() queue = set() paths = {} # root node queue.add(0) paths[0] = [] processed_nodes = set() red_paths = [] while len(queue) > 0: v = queue.pop() for node in spanning_tree.neighbors(v): if node not in paths and node not in processed_nodes: paths[node] = paths[v].copy() # we can use directly 0 here, since we are working on a spanning tree and there should be no more edges to a node paths[node].append(woflan_object.get_r_g().get_edge_data( v, node)[0]['transition']) if node not in red_nodes: queue.add(node) else: red_paths.append(paths[node]) processed_nodes.add(v) return red_paths
def apply(net, initial_marking, original_net=None): """ Method that computes a reachability graph as networkx object :param net: Petri Net :param initial_marking: Initial Marking of the Petri Net :param original_net: Petri Net without short-circuited transition :return: Networkx Graph that represents the reachability graph of the Petri Net """ initial_marking = helper.convert_marking(net, initial_marking, original_net) firing_dict = helper.split_incidence_matrix( helper.compute_incidence_matrix(net), net) req_dict = helper.compute_firing_requirement(net) look_up_indices = {} j = 0 reachability_graph = nx.MultiDiGraph() reachability_graph.add_node(j, marking=initial_marking) working_set = set() working_set.add(j) look_up_indices[np.array2string(initial_marking)] = j j += 1 while len(working_set) > 0: m = working_set.pop() possible_markings = helper.enabled_markings( firing_dict, req_dict, reachability_graph.nodes[m]['marking']) for marking in possible_markings: if np.array2string(marking[0]) not in look_up_indices: look_up_indices[np.array2string(marking[0])] = j reachability_graph.add_node(j, marking=marking[0]) working_set.add(j) reachability_graph.add_edge(m, j, transition=marking[1]) j += 1 else: reachability_graph.add_edge(m, look_up_indices[np.array2string( marking[0])], transition=marking[1]) return reachability_graph
def minimal_coverability_tree(net, initial_marking, original_net=None): """ This method computes the minimal coverability tree. It is part of a method to obtain a minial coverability graph :param net: Petri Net :param initial_marking: Initial Marking of the Petri Net :param original_net: Petri Net without short-circuited transition :return: Minimal coverability tree """ def check_if_marking_already_in_processed_nodes(n, processed_nodes): for node in processed_nodes: if np.array_equal(G.nodes[node]['marking'], G.nodes[n]['marking']): return True return False def is_m_smaller_than_other(m, processed_nodes): for node in processed_nodes: if all(np.less_equal(m, G.nodes[node]['marking'])): return True return False def is_m_greater_than_other(m, processed_nodes): for node in processed_nodes: if all(np.greater_equal(m, G.nodes[node]['marking'])): return True return False def get_first_smaller_marking_on_path(n, m2): path = nx.shortest_path(G, source=0, target=n) for node in path: if all(np.less_equal(G.nodes[node]['marking'], m2)): return node return None def remove_subtree(tree, n): bfs_tree = nx.bfs_tree(tree, n) for edge in bfs_tree.edges: tree.remove_edge(edge[0], edge[1]) for node in bfs_tree.nodes: if node != n: tree.remove_node(node) return tree G = nx.MultiDiGraph() incidence_matrix = helper.compute_incidence_matrix(net) firing_dict = helper.split_incidence_matrix(incidence_matrix, net) req_dict = helper.compute_firing_requirement(net) initial_mark = helper.convert_marking(net, initial_marking, original_net) j = 0 unprocessed_nodes = set() G.add_node(j, marking=initial_mark) unprocessed_nodes.add(j) j += 1 processed_nodes = set() while len(unprocessed_nodes) > 0: n = unprocessed_nodes.pop() if check_if_marking_already_in_processed_nodes(n, processed_nodes): processed_nodes.add(n) elif is_m_smaller_than_other(G.nodes[n]['marking'], processed_nodes): G.remove_edge(next(G.predecessors(n)), n) G.remove_node(n) elif is_m_greater_than_other(G.nodes[n]['marking'], processed_nodes): m2 = G.nodes[n]['marking'].copy() ancestor_bool = False for ancestor in nx.ancestors(G, n): if is_m_greater_than_other(G.nodes[n]['marking'], [ancestor]): i = 0 while i < len(G.nodes[n]['marking']): if G.nodes[ancestor]['marking'][i] < G.nodes[n][ 'marking'][i]: m2[i] = np.inf i += 1 n1 = None for ancestor in nx.ancestors(G, n): if all(np.less_equal(G.nodes[ancestor]['marking'], m2)): n1 = get_first_smaller_marking_on_path(n, m2) break if n1 != None: ancestor_bool = True G.nodes[n1]['marking'] = m2.copy() subtree = nx.bfs_tree(G, n1) for node in subtree: if node in processed_nodes: processed_nodes.remove(node) if node in unprocessed_nodes: unprocessed_nodes.remove(node) G = remove_subtree(G, n1) unprocessed_nodes.add(n1) processed_nodes_copy = copy(processed_nodes) for node in processed_nodes_copy: if node in G.nodes: if all(np.less_equal(G.nodes[node]['marking'], m2)): subtree = nx.bfs_tree(G, node) for node in subtree: if node in processed_nodes: processed_nodes.remove(node) if node in unprocessed_nodes: unprocessed_nodes.remove(node) remove_subtree(G, node) G.remove_node(node) if not ancestor_bool: unprocessed_nodes.add(n) else: for el in helper.enabled_markings(firing_dict, req_dict, G.nodes[n]['marking']): G.add_node(j, marking=el[0]) G.add_edge(n, j, transition=el[1]) unprocessed_nodes.add(j) j += 1 processed_nodes.add(n) return (G, firing_dict, req_dict)
def compute_unbounded_sequences(woflan_object): """ We compute the sequences which lead to an infinite amount of tokens. To do this, we compute a restricted coverability tree. The tree works similar to the graph, despite we consider tree characteristics during the construction. :param woflan_object: Woflan object that contains all needed information. :return: List of unbounded sequences, each sequence is a list of transitions """ def check_for_markings_larger_than_final_marking(graph, f_m): markings = [] for node in graph.nodes: if all(np.greater_equal(graph.nodes[node]['marking'], f_m)): markings.append(node) return markings woflan_object.set_restricted_coverability_tree( restricted_coverability_tree(woflan_object.get_net(), woflan_object.get_initial_marking())) f_m = convert_marking(woflan_object.get_net(), woflan_object.get_final_marking()) infinite_markings = [] for node in woflan_object.get_restricted_coverability_tree().nodes: if np.inf in woflan_object.get_restricted_coverability_tree( ).nodes[node]['marking']: infinite_markings.append(node) larger_markings = check_for_markings_larger_than_final_marking( woflan_object.get_restricted_coverability_tree(), f_m) green_markings = [] for node in woflan_object.get_restricted_coverability_tree().nodes: add_to_green = True for marking in infinite_markings: if nx.has_path(woflan_object.get_restricted_coverability_tree(), node, marking): add_to_green = False for marking in larger_markings: if nx.has_path(woflan_object.get_restricted_coverability_tree(), node, marking): add_to_green = False if add_to_green: green_markings.append(node) red_markings = [] for node in woflan_object.get_restricted_coverability_tree().nodes: add_to_red = True for node_green in green_markings: if nx.has_path(woflan_object.get_restricted_coverability_tree(), node, node_green): add_to_red = False break if add_to_red: red_markings.append(node) # Make the path as short as possible. If we reach a red state, we stop and do not go further in the "red zone". queue = set() queue.add(0) paths = {} paths[0] = [] paths_to_red = [] while len(queue) > 0: v = queue.pop() successors = woflan_object.get_restricted_coverability_tree( ).successors(v) for suc in successors: paths[suc] = paths[v].copy() paths[suc].append( woflan_object.get_restricted_coverability_tree().get_edge_data( v, suc)['transition']) if suc in red_markings: paths_to_red.append(paths[suc]) else: queue.add(suc) return paths_to_red