Skip to content

Commit 261413d

Browse files
Merge branch 'integration' into hotfixes
2 parents 0c7c680 + df25f76 commit 261413d

23 files changed

Lines changed: 1238 additions & 960 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
@@ -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

0 commit comments

Comments
 (0)