Skip to content

Commit 1cc91f2

Browse files
Merge remote-tracking branch 'upstream/hotfixes' into release
2 parents 4b4a307 + 261413d commit 1cc91f2

23 files changed

Lines changed: 1238 additions & 981 deletions

File tree

docs/build.sh

100644100755
File mode changed.

examples/execute_everything.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
import sys
44
import traceback
55
import importlib.util
6+
import argparse
67

78

89

910
EXECUTE_EXAMPLES = True
11+
PRINT_FAILURE_TRACES = True
1012

1113
class OutcomeMeasurement:
1214
SUCCESS = 0
1315
FAILED = 0
16+
FAILURE_TRACES = []
1417

1518

1619
def polars_process_cubes():
@@ -954,17 +957,23 @@ def execute_script(f):
954957
OutcomeMeasurement.SUCCESS += 1
955958
except ImportError:
956959
import time
957-
traceback.print_exc()
960+
trace = traceback.format_exc()
961+
print(trace)
958962
OutcomeMeasurement.FAILED += 1
963+
OutcomeMeasurement.FAILURE_TRACES.append(trace)
959964
time.sleep(3)
960965
except KeyError:
961966
import time
962-
traceback.print_exc()
967+
trace = traceback.format_exc()
968+
print(trace)
963969
OutcomeMeasurement.FAILED += 1
970+
OutcomeMeasurement.FAILURE_TRACES.append(trace)
964971
time.sleep(3)
965972
except:
966-
traceback.print_exc()
973+
trace = traceback.format_exc()
974+
print(trace)
967975
OutcomeMeasurement.FAILED += 1
976+
OutcomeMeasurement.FAILURE_TRACES.append(trace)
968977
input("\npress INPUT if you want to continue")
969978

970979

@@ -1000,9 +1009,23 @@ def print_versions():
10001009
print(pm4py.util.pandas_utils.DATAFRAME)
10011010

10021011

1012+
def parse_args():
1013+
parser = argparse.ArgumentParser(
1014+
prog='Example Tests',
1015+
description='Runs example tests')
1016+
1017+
# some libraries can not be installed in the pipeline, so some tests are disabled in pipelines
1018+
parser.add_argument('-p', '--pipeline',
1019+
action='store_true')
1020+
1021+
args = parser.parse_args()
1022+
return args.pipeline
1023+
10031024
def main():
10041025
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))))
10051026

1027+
in_pipeline = parse_args()
1028+
10061029
import pm4py
10071030
import numpy
10081031
import pandas
@@ -1015,6 +1038,8 @@ def main():
10151038

10161039
print_versions()
10171040

1041+
skipped = 0
1042+
10181043
if EXECUTE_EXAMPLES:
10191044
if importlib.util.find_spec("polars"):
10201045
execute_script(polars_process_cubes)
@@ -1033,7 +1058,10 @@ def main():
10331058
execute_script(simplified_interface)
10341059
execute_script(bpmn_js_visualization)
10351060
execute_script(read_write_ocel)
1036-
execute_script(discovery_data_petri_net)
1061+
if not in_pipeline:
1062+
execute_script(discovery_data_petri_net)
1063+
else:
1064+
skipped += 1
10371065
execute_script(data_petri_nets)
10381066
execute_script(inhibitor_reset_arcs)
10391067
execute_script(logs_petri_visual_comparison)
@@ -1064,10 +1092,16 @@ def main():
10641092
execute_script(ocel20_import_export)
10651093
execute_script(revised_playout)
10661094
execute_script(timestamp_case_grouping_filter)
1067-
execute_script(trace_clustering)
1095+
if not in_pipeline:
1096+
execute_script(trace_clustering)
1097+
else:
1098+
skipped += 1
10681099
execute_script(validation_ocel20_relational)
10691100
execute_script(interval_events_overlap)
1070-
execute_script(kneighb_regression)
1101+
if not in_pipeline:
1102+
execute_script(kneighb_regression)
1103+
else:
1104+
skipped += 1
10711105
execute_script(log_to_int_tree_open_paths)
10721106
execute_script(concept_drift)
10731107
execute_script(dfg_align_metrics)
@@ -1087,7 +1121,10 @@ def main():
10871121
execute_script(ocel_validation)
10881122
execute_script(process_tree_frequency_annotation)
10891123
execute_script(tree_manual_generation)
1090-
execute_script(workalendar_example)
1124+
if not in_pipeline:
1125+
execute_script(workalendar_example)
1126+
else:
1127+
skipped += 1
10911128
execute_script(merging_case_relations)
10921129
execute_script(activity_position)
10931130
execute_script(link_analysis_vbfa)
@@ -1159,17 +1196,24 @@ def main():
11591196

11601197
print("\n\nExamples executed correctly: %d\tExamples failed: %d\t" % (OutcomeMeasurement.SUCCESS, OutcomeMeasurement.FAILED))
11611198

1199+
print(f"Skipped {skipped} examples.")
1200+
11621201
# Compute the total and pass ratio
11631202
total_runs = OutcomeMeasurement.SUCCESS + OutcomeMeasurement.FAILED
11641203
if total_runs > 0:
11651204
pass_ratio = OutcomeMeasurement.SUCCESS / total_runs
11661205
else:
11671206
pass_ratio = 0.0
11681207

1208+
if PRINT_FAILURE_TRACES:
1209+
for i, trace in enumerate(OutcomeMeasurement.FAILURE_TRACES):
1210+
print(f"Failure {i}:")
1211+
print(trace)
1212+
11691213
print(f"Overall pass ratio: {round(pass_ratio * 100, 2)}%")
11701214

11711215
# If pass ratio is above 90%, exit code is 0; otherwise 1
1172-
if pass_ratio > 0.9:
1216+
if pass_ratio == 1:
11731217
#print("exiting with code 0")
11741218
sys.exit(0)
11751219
else:

files/flake8_score.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ def lint_package_with_score(package_path, config_path=None, threshold=8):
5757
if __name__ == "__main__":
5858
package_path = "../pm4py"
5959
config_file = "flake8.ini"
60-
quality_threshold = 950
60+
quality_threshold = 350
6161

6262
lint_package_with_score(package_path, config_path=config_file, threshold=quality_threshold)

pm4py/algo/analysis/woflan/algorithm.py

Lines changed: 77 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pm4py.objects.petri_net.utils import petri_utils
2525
import copy
2626
import numpy as np
27+
from collections import deque
2728

2829
# Importing for place invariants related stuff (s-components, uniform and
2930
# weighted place invariants)
@@ -594,11 +595,12 @@ def step_7(woflan_object, return_asap_when_unsound=False):
594595
woflan_object.get_net(),
595596
)
596597
)
597-
if len(check_for_improper_conditions(woflan_object.get_mcg())) == 0:
598+
improper = check_for_improper_conditions(woflan_object.get_mcg())
599+
if len(improper) == 0:
598600
woflan_object.diagnostic_messages.append("No improper coditions.")
599601
if woflan_object.print_diagnostics:
600602
print("No improper conditions.")
601-
if woflan_object.get_left:
603+
if woflan_object.get_left():
602604
return step_8(
603605
woflan_object,
604606
return_asap_when_unsound=return_asap_when_unsound,
@@ -609,16 +611,9 @@ def step_7(woflan_object, return_asap_when_unsound=False):
609611
return_asap_when_unsound=return_asap_when_unsound,
610612
)
611613
else:
612-
woflan_object.diagnostic_messages.append(
613-
"Improper WPD. The following are the improper conditions: {}.".format(
614-
check_for_improper_conditions(
615-
woflan_object.get_mcg())))
614+
woflan_object.diagnostic_messages.append("Improper WPD. The following are the improper conditions: {}.".format(improper))
616615
if woflan_object.print_diagnostics:
617-
print(
618-
"Improper WPD. The following are the improper conditions: {}.".format(
619-
check_for_improper_conditions(woflan_object.get_mcg())
620-
)
621-
)
616+
print("Improper WPD. The following are the improper conditions: {}.".format(improper))
622617
if return_asap_when_unsound:
623618
return False
624619
return step_9(
@@ -627,14 +622,9 @@ def step_7(woflan_object, return_asap_when_unsound=False):
627622

628623

629624
def step_8(woflan_object, return_asap_when_unsound=False):
630-
if check_for_substates(woflan_object.get_mcg()):
631-
return step_10(
632-
woflan_object, return_asap_when_unsound=return_asap_when_unsound
633-
)
634-
else:
635-
return step_10(
636-
woflan_object, return_asap_when_unsound=return_asap_when_unsound
637-
)
625+
return step_10(
626+
woflan_object, return_asap_when_unsound=return_asap_when_unsound
627+
)
638628

639629

640630
def step_9(woflan_object, return_asap_when_unsound=False):
@@ -813,39 +803,34 @@ def compute_non_live_sequences(woflan_object):
813803
if all(np.equal(woflan_object.get_r_g().nodes[node]["marking"], f_m)):
814804
sucessfull_terminate_state = node
815805
break
816-
# red nodes are those from which the final marking is not reachable
817-
red_nodes = []
818-
for node in woflan_object.get_r_g().nodes:
819-
if not nx_utils.has_path(
820-
woflan_object.get_r_g(), node, sucessfull_terminate_state
821-
):
822-
red_nodes.append(node)
823-
# Compute directed spanning tree
824-
spanning_tree = nx_utils.Edmonds(woflan_object.get_r_g()).find_optimum()
825-
queue = set()
826-
paths = {}
827-
# root node
828-
queue.add(0)
829-
paths[0] = []
830-
processed_nodes = set()
806+
if sucessfull_terminate_state is None:
807+
return []
808+
reversed_graph = woflan_object.get_r_g().reverse(copy=False)
809+
can_reach_final = nx_utils.descendants(
810+
reversed_graph, sucessfull_terminate_state
811+
)
812+
can_reach_final.add(sucessfull_terminate_state)
813+
red_nodes = set(woflan_object.get_r_g().nodes) - can_reach_final
814+
815+
queue = deque()
816+
queue.append((0, []))
817+
shortest_path_len = {0: 0}
831818
red_paths = []
832-
while len(queue) > 0:
833-
v = queue.pop()
834-
for node in spanning_tree.neighbors(v):
835-
if node not in paths and node not in processed_nodes:
836-
paths[node] = paths[v].copy()
837-
# we can use directly 0 here, since we are working on a
838-
# spanning tree and there should be no more edges to a node
839-
paths[node].append(
840-
woflan_object.get_r_g().get_edge_data(v, node)[0][
841-
"transition"
842-
]
843-
)
844-
if node not in red_nodes:
845-
queue.add(node)
846-
else:
847-
red_paths.append(paths[node])
848-
processed_nodes.add(v)
819+
while queue:
820+
node, path = queue.popleft()
821+
if node in red_nodes:
822+
red_paths.append(path)
823+
continue
824+
for successor in woflan_object.get_r_g().successors(node):
825+
edge_data = woflan_object.get_r_g().get_edge_data(node, successor)
826+
if not edge_data:
827+
continue
828+
transition = next(iter(edge_data.values()))["transition"]
829+
next_path = path + [transition]
830+
if successor in shortest_path_len and len(next_path) >= shortest_path_len[successor]:
831+
continue
832+
shortest_path_len[successor] = len(next_path)
833+
queue.append((successor, next_path))
849834
return red_paths
850835

851836

@@ -869,70 +854,53 @@ def check_for_markings_larger_than_final_marking(graph, f_m):
869854
woflan_object.get_net(), woflan_object.get_initial_marking()
870855
)
871856
)
857+
tree = woflan_object.get_restricted_coverability_tree()
872858
f_m = convert_marking(
873859
woflan_object.get_net(), woflan_object.get_final_marking()
874860
)
875-
infinite_markings = []
876-
for node in woflan_object.get_restricted_coverability_tree().nodes:
877-
if (
878-
np.inf
879-
in woflan_object.get_restricted_coverability_tree().nodes[node][
880-
"marking"
881-
]
882-
):
883-
infinite_markings.append(node)
884-
larger_markings = check_for_markings_larger_than_final_marking(
885-
woflan_object.get_restricted_coverability_tree(), f_m
861+
infinite_markings = {
862+
node
863+
for node, data in tree.nodes(data=True)
864+
if np.inf in data["marking"]
865+
}
866+
larger_markings = set(
867+
check_for_markings_larger_than_final_marking(tree, f_m)
886868
)
887-
green_markings = []
888-
for node in woflan_object.get_restricted_coverability_tree().nodes:
889-
add_to_green = True
890-
for marking in infinite_markings:
891-
if nx_utils.has_path(
892-
woflan_object.get_restricted_coverability_tree(), node, marking
893-
):
894-
add_to_green = False
895-
for marking in larger_markings:
896-
if nx_utils.has_path(
897-
woflan_object.get_restricted_coverability_tree(), node, marking
898-
):
899-
add_to_green = False
900-
if add_to_green:
901-
green_markings.append(node)
902-
red_markings = []
903-
for node in woflan_object.get_restricted_coverability_tree().nodes:
904-
add_to_red = True
905-
for node_green in green_markings:
906-
if nx_utils.has_path(
907-
woflan_object.get_restricted_coverability_tree(),
908-
node,
909-
node_green,
910-
):
911-
add_to_red = False
912-
break
913-
if add_to_red:
914-
red_markings.append(node)
869+
bad_nodes = infinite_markings | larger_markings
870+
871+
postorder_nodes = list(nx_utils.topological_sort(tree))[::-1]
872+
subtree_has_bad = {}
873+
subtree_has_green = {}
874+
green_markings = set()
875+
876+
for node in postorder_nodes:
877+
children = list(tree.successors(node))
878+
has_bad = node in bad_nodes or any(subtree_has_bad.get(child, False) for child in children)
879+
is_green = not has_bad
880+
if is_green:
881+
green_markings.add(node)
882+
subtree_has_bad[node] = has_bad
883+
subtree_has_green[node] = is_green or any(
884+
subtree_has_green.get(child, False) for child in children
885+
)
886+
887+
red_markings = {
888+
node for node in tree.nodes if not subtree_has_green.get(node, False)
889+
}
915890
# Make the path as short as possible. If we reach a red state, we stop and
916891
# do not go further in the "red zone".
917-
queue = set()
918-
queue.add(0)
919-
paths = {}
920-
paths[0] = []
892+
queue = deque()
893+
queue.append((0, []))
921894
paths_to_red = []
922-
while len(queue) > 0:
923-
v = queue.pop()
924-
successors = (
925-
woflan_object.get_restricted_coverability_tree().successors(v)
926-
)
895+
while queue:
896+
v, path = queue.popleft()
897+
if v in red_markings:
898+
paths_to_red.append(path)
899+
continue
900+
successors = tree.successors(v)
927901
for suc in successors:
928-
paths[suc] = paths[v].copy()
929-
paths[suc].append(
930-
woflan_object.get_restricted_coverability_tree().get_edge_data(
931-
v, suc
932-
)["transition"]
933-
)
934-
if suc in red_markings:
935-
paths_to_red.append(paths[suc])
936-
else:
937-
queue.add(suc)
902+
edge_data = tree.get_edge_data(v, suc)
903+
transition = edge_data["transition"] if edge_data else None
904+
next_path = path + [transition]
905+
queue.append((suc, next_path))
938906
return paths_to_red

0 commit comments

Comments
 (0)