Skip to content

Commit 4293158

Browse files
Merge branch '391-performance-improvement-woflan' into 'integration'
Performance improvement WOFLAN Closes #391 See merge request process-intelligence-solutions/pm4py!1407
2 parents 6f11809 + e815b7a commit 4293158

9 files changed

Lines changed: 295 additions & 249 deletions

File tree

pm4py/algo/analysis/woflan/algorithm.py

Lines changed: 77 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pm4py.objects.petri_net.utils import petri_utils
44
import copy
55
import numpy as np
6+
from collections import deque
67

78
# Importing for place invariants related stuff (s-components, uniform and
89
# weighted place invariants)
@@ -573,11 +574,12 @@ def step_7(woflan_object, return_asap_when_unsound=False):
573574
woflan_object.get_net(),
574575
)
575576
)
576-
if len(check_for_improper_conditions(woflan_object.get_mcg())) == 0:
577+
improper = check_for_improper_conditions(woflan_object.get_mcg())
578+
if len(improper) == 0:
577579
woflan_object.diagnostic_messages.append("No improper coditions.")
578580
if woflan_object.print_diagnostics:
579581
print("No improper conditions.")
580-
if woflan_object.get_left:
582+
if woflan_object.get_left():
581583
return step_8(
582584
woflan_object,
583585
return_asap_when_unsound=return_asap_when_unsound,
@@ -588,16 +590,9 @@ def step_7(woflan_object, return_asap_when_unsound=False):
588590
return_asap_when_unsound=return_asap_when_unsound,
589591
)
590592
else:
591-
woflan_object.diagnostic_messages.append(
592-
"Improper WPD. The following are the improper conditions: {}.".format(
593-
check_for_improper_conditions(
594-
woflan_object.get_mcg())))
593+
woflan_object.diagnostic_messages.append("Improper WPD. The following are the improper conditions: {}.".format(improper))
595594
if woflan_object.print_diagnostics:
596-
print(
597-
"Improper WPD. The following are the improper conditions: {}.".format(
598-
check_for_improper_conditions(woflan_object.get_mcg())
599-
)
600-
)
595+
print("Improper WPD. The following are the improper conditions: {}.".format(improper))
601596
if return_asap_when_unsound:
602597
return False
603598
return step_9(
@@ -606,14 +601,9 @@ def step_7(woflan_object, return_asap_when_unsound=False):
606601

607602

608603
def step_8(woflan_object, return_asap_when_unsound=False):
609-
if check_for_substates(woflan_object.get_mcg()):
610-
return step_10(
611-
woflan_object, return_asap_when_unsound=return_asap_when_unsound
612-
)
613-
else:
614-
return step_10(
615-
woflan_object, return_asap_when_unsound=return_asap_when_unsound
616-
)
604+
return step_10(
605+
woflan_object, return_asap_when_unsound=return_asap_when_unsound
606+
)
617607

618608

619609
def step_9(woflan_object, return_asap_when_unsound=False):
@@ -792,39 +782,34 @@ def compute_non_live_sequences(woflan_object):
792782
if all(np.equal(woflan_object.get_r_g().nodes[node]["marking"], f_m)):
793783
sucessfull_terminate_state = node
794784
break
795-
# red nodes are those from which the final marking is not reachable
796-
red_nodes = []
797-
for node in woflan_object.get_r_g().nodes:
798-
if not nx_utils.has_path(
799-
woflan_object.get_r_g(), node, sucessfull_terminate_state
800-
):
801-
red_nodes.append(node)
802-
# Compute directed spanning tree
803-
spanning_tree = nx_utils.Edmonds(woflan_object.get_r_g()).find_optimum()
804-
queue = set()
805-
paths = {}
806-
# root node
807-
queue.add(0)
808-
paths[0] = []
809-
processed_nodes = set()
785+
if sucessfull_terminate_state is None:
786+
return []
787+
reversed_graph = woflan_object.get_r_g().reverse(copy=False)
788+
can_reach_final = nx_utils.descendants(
789+
reversed_graph, sucessfull_terminate_state
790+
)
791+
can_reach_final.add(sucessfull_terminate_state)
792+
red_nodes = set(woflan_object.get_r_g().nodes) - can_reach_final
793+
794+
queue = deque()
795+
queue.append((0, []))
796+
shortest_path_len = {0: 0}
810797
red_paths = []
811-
while len(queue) > 0:
812-
v = queue.pop()
813-
for node in spanning_tree.neighbors(v):
814-
if node not in paths and node not in processed_nodes:
815-
paths[node] = paths[v].copy()
816-
# we can use directly 0 here, since we are working on a
817-
# spanning tree and there should be no more edges to a node
818-
paths[node].append(
819-
woflan_object.get_r_g().get_edge_data(v, node)[0][
820-
"transition"
821-
]
822-
)
823-
if node not in red_nodes:
824-
queue.add(node)
825-
else:
826-
red_paths.append(paths[node])
827-
processed_nodes.add(v)
798+
while queue:
799+
node, path = queue.popleft()
800+
if node in red_nodes:
801+
red_paths.append(path)
802+
continue
803+
for successor in woflan_object.get_r_g().successors(node):
804+
edge_data = woflan_object.get_r_g().get_edge_data(node, successor)
805+
if not edge_data:
806+
continue
807+
transition = next(iter(edge_data.values()))["transition"]
808+
next_path = path + [transition]
809+
if successor in shortest_path_len and len(next_path) >= shortest_path_len[successor]:
810+
continue
811+
shortest_path_len[successor] = len(next_path)
812+
queue.append((successor, next_path))
828813
return red_paths
829814

830815

@@ -848,70 +833,53 @@ def check_for_markings_larger_than_final_marking(graph, f_m):
848833
woflan_object.get_net(), woflan_object.get_initial_marking()
849834
)
850835
)
836+
tree = woflan_object.get_restricted_coverability_tree()
851837
f_m = convert_marking(
852838
woflan_object.get_net(), woflan_object.get_final_marking()
853839
)
854-
infinite_markings = []
855-
for node in woflan_object.get_restricted_coverability_tree().nodes:
856-
if (
857-
np.inf
858-
in woflan_object.get_restricted_coverability_tree().nodes[node][
859-
"marking"
860-
]
861-
):
862-
infinite_markings.append(node)
863-
larger_markings = check_for_markings_larger_than_final_marking(
864-
woflan_object.get_restricted_coverability_tree(), f_m
840+
infinite_markings = {
841+
node
842+
for node, data in tree.nodes(data=True)
843+
if np.inf in data["marking"]
844+
}
845+
larger_markings = set(
846+
check_for_markings_larger_than_final_marking(tree, f_m)
865847
)
866-
green_markings = []
867-
for node in woflan_object.get_restricted_coverability_tree().nodes:
868-
add_to_green = True
869-
for marking in infinite_markings:
870-
if nx_utils.has_path(
871-
woflan_object.get_restricted_coverability_tree(), node, marking
872-
):
873-
add_to_green = False
874-
for marking in larger_markings:
875-
if nx_utils.has_path(
876-
woflan_object.get_restricted_coverability_tree(), node, marking
877-
):
878-
add_to_green = False
879-
if add_to_green:
880-
green_markings.append(node)
881-
red_markings = []
882-
for node in woflan_object.get_restricted_coverability_tree().nodes:
883-
add_to_red = True
884-
for node_green in green_markings:
885-
if nx_utils.has_path(
886-
woflan_object.get_restricted_coverability_tree(),
887-
node,
888-
node_green,
889-
):
890-
add_to_red = False
891-
break
892-
if add_to_red:
893-
red_markings.append(node)
848+
bad_nodes = infinite_markings | larger_markings
849+
850+
postorder_nodes = list(nx_utils.topological_sort(tree))[::-1]
851+
subtree_has_bad = {}
852+
subtree_has_green = {}
853+
green_markings = set()
854+
855+
for node in postorder_nodes:
856+
children = list(tree.successors(node))
857+
has_bad = node in bad_nodes or any(subtree_has_bad.get(child, False) for child in children)
858+
is_green = not has_bad
859+
if is_green:
860+
green_markings.add(node)
861+
subtree_has_bad[node] = has_bad
862+
subtree_has_green[node] = is_green or any(
863+
subtree_has_green.get(child, False) for child in children
864+
)
865+
866+
red_markings = {
867+
node for node in tree.nodes if not subtree_has_green.get(node, False)
868+
}
894869
# Make the path as short as possible. If we reach a red state, we stop and
895870
# do not go further in the "red zone".
896-
queue = set()
897-
queue.add(0)
898-
paths = {}
899-
paths[0] = []
871+
queue = deque()
872+
queue.append((0, []))
900873
paths_to_red = []
901-
while len(queue) > 0:
902-
v = queue.pop()
903-
successors = (
904-
woflan_object.get_restricted_coverability_tree().successors(v)
905-
)
874+
while queue:
875+
v, path = queue.popleft()
876+
if v in red_markings:
877+
paths_to_red.append(path)
878+
continue
879+
successors = tree.successors(v)
906880
for suc in successors:
907-
paths[suc] = paths[v].copy()
908-
paths[suc].append(
909-
woflan_object.get_restricted_coverability_tree().get_edge_data(
910-
v, suc
911-
)["transition"]
912-
)
913-
if suc in red_markings:
914-
paths_to_red.append(paths[suc])
915-
else:
916-
queue.add(suc)
881+
edge_data = tree.get_edge_data(v, suc)
882+
transition = edge_data["transition"] if edge_data else None
883+
next_path = path + [transition]
884+
queue.append((suc, next_path))
917885
return paths_to_red

pm4py/algo/analysis/woflan/graphs/minimal_coverability_graph/minimal_coverability_graph.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ def remove_subtree(tree, n):
5555

5656
G = nx_utils.MultiDiGraph()
5757

58-
incidence_matrix = helper.compute_incidence_matrix(net)
59-
firing_dict = helper.split_incidence_matrix(incidence_matrix, net)
60-
req_dict = helper.compute_firing_requirement(net)
58+
_, req_sparse, deltas, _, _ = helper.compute_firing_requirement(net)
6159

6260
initial_mark = helper.convert_marking(net, initial_marking, original_net)
6361
j = 0
@@ -126,7 +124,7 @@ def remove_subtree(tree, n):
126124
unprocessed_nodes.append(n)
127125
else:
128126
enabled_markings = helper.enabled_markings(
129-
firing_dict, req_dict, G.nodes[n]["marking"]
127+
req_sparse, deltas, G.nodes[n]["marking"]
130128
)
131129
for el in enabled_markings:
132130
G.add_node(j, marking=el[0])
@@ -136,7 +134,7 @@ def remove_subtree(tree, n):
136134
j += 1
137135
processed_nodes.add(n)
138136

139-
return (G, firing_dict, req_dict)
137+
return (G, req_sparse, deltas)
140138

141139

142140
def apply(net, initial_marking, original_net=None):
@@ -151,12 +149,11 @@ def apply(net, initial_marking, original_net=None):
151149
def detect_same_labelled_nodes(G):
152150
same_labels = {}
153151
for node in G.nodes:
154-
if np.array2string(G.nodes[node]["marking"]) not in same_labels:
155-
same_labels[np.array2string(G.nodes[node]["marking"])] = [node]
152+
marking_key = helper.marking_to_key(G.nodes[node]["marking"])
153+
if marking_key not in same_labels:
154+
same_labels[marking_key] = [node]
156155
else:
157-
same_labels[np.array2string(G.nodes[node]["marking"])].append(
158-
node
159-
)
156+
same_labels[marking_key].append(node)
160157
return same_labels
161158

162159
def merge_nodes_of_same_label(G, same_labels):
@@ -171,16 +168,20 @@ def merge_nodes_of_same_label(G, same_labels):
171168
i += 1
172169
return G
173170

174-
mct, firing_dict, req_dict = minimal_coverability_tree(
171+
mct, req_sparse, deltas = minimal_coverability_tree(
175172
net, initial_marking, original_net
176173
)
177174
mcg = merge_nodes_of_same_label(mct, detect_same_labelled_nodes(mct))
178175

179176
to_remove_edges = []
177+
enabled_cache = {}
180178
for edge in mcg.edges:
181-
reachable_markings = helper.enabled_markings(
182-
firing_dict, req_dict, mcg.nodes[edge[0]]["marking"]
183-
)
179+
source = edge[0]
180+
if source not in enabled_cache:
181+
enabled_cache[source] = helper.enabled_markings(
182+
req_sparse, deltas, mcg.nodes[source]["marking"]
183+
)
184+
reachable_markings = enabled_cache[source]
184185
not_reachable = True
185186
for el in reachable_markings:
186187
if np.array_equal(el[0], mcg.nodes[edge[1]]["marking"]):

pm4py/algo/analysis/woflan/graphs/reachability_graph/reachability_graph.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ def apply(net, initial_marking, original_net=None):
1414
initial_marking = helper.convert_marking(
1515
net, initial_marking, original_net
1616
)
17-
firing_dict = helper.split_incidence_matrix(
18-
helper.compute_incidence_matrix(net), net
19-
)
20-
req_dict = helper.compute_firing_requirement(net)
17+
_, req_sparse, deltas, _, _ = helper.compute_firing_requirement(net)
2118
look_up_indices = {}
2219
j = 0
2320
reachability_graph = nx_utils.MultiDiGraph()
@@ -26,25 +23,26 @@ def apply(net, initial_marking, original_net=None):
2623
working_set = set()
2724
working_set.add(j)
2825

29-
look_up_indices[np.array2string(initial_marking)] = j
26+
look_up_indices[helper.marking_to_key(initial_marking)] = j
3027

3128
j += 1
3229
while len(working_set) > 0:
3330
m = working_set.pop()
3431
possible_markings = helper.enabled_markings(
35-
firing_dict, req_dict, reachability_graph.nodes[m]["marking"]
32+
req_sparse, deltas, reachability_graph.nodes[m]["marking"]
3633
)
3734
for marking in possible_markings:
38-
if np.array2string(marking[0]) not in look_up_indices:
39-
look_up_indices[np.array2string(marking[0])] = j
35+
marking_key = helper.marking_to_key(marking[0])
36+
if marking_key not in look_up_indices:
37+
look_up_indices[marking_key] = j
4038
reachability_graph.add_node(j, marking=marking[0])
4139
working_set.add(j)
4240
reachability_graph.add_edge(m, j, transition=marking[1])
4341
j += 1
4442
else:
4543
reachability_graph.add_edge(
4644
m,
47-
look_up_indices[np.array2string(marking[0])],
45+
look_up_indices[marking_key],
4846
transition=marking[1],
4947
)
5048
return reachability_graph

0 commit comments

Comments
 (0)