import pm4py
import json
from pathlib import Path
from pm4py.algo.evaluation.generalization import algorithm as generalization
from pm4py.algo.evaluation.simplicity import algorithm as simplicity
FIXTURES_DIR = Path("wasm4pm/tests/fixtures")
OUTPUT_DIR = FIXTURES_DIR
def load_running_example():
xes_path = FIXTURES_DIR / "running-example.xes"
return pm4py.read_xes(str(xes_path))
def save_event_log_json(log, output_path):
output = {
"attributes": {},
"traces": []
}
if hasattr(log, 'iterrows'):
from collections import defaultdict
traces = defaultdict(list)
for idx, row in log.iterrows():
case_id = str(row.get('case:concept:name', idx))
event = {"attributes": {}}
for col in row.index:
if col == 'case:concept:name':
continue
value = row[col]
if col == 'concept:name':
event["attributes"]["activity"] = {"tag": "String", "value": str(value)}
elif col == 'time:timestamp':
if hasattr(value, 'isoformat'):
event["attributes"]["timestamp"] = {"tag": "Date", "value": value.isoformat()}
else:
event["attributes"]["timestamp"] = {"tag": "String", "value": str(value)}
else:
if value is None:
event["attributes"][col] = None
elif isinstance(value, str):
event["attributes"][col] = {"tag": "String", "value": value}
elif isinstance(value, int):
event["attributes"][col] = {"tag": "Int", "value": value}
elif isinstance(value, float):
event["attributes"][col] = {"tag": "Float", "value": value}
elif isinstance(value, bool):
event["attributes"][col] = {"tag": "Boolean", "value": value}
else:
event["attributes"][col] = {"tag": "String", "value": str(value)}
traces[case_id].append(event)
for case_id, events in traces.items():
trace_data = {
"attributes": {"case:concept:name": {"tag": "String", "value": case_id}},
"events": events
}
output["traces"].append(trace_data)
else:
for case_id, trace in enumerate(log, start=1):
trace_data = {
"attributes": {"case:concept:name": {"tag": "String", "value": str(case_id)}},
"events": []
}
for event in trace:
event_data = {
"attributes": {}
}
for key in event:
value = event[key]
if key == "concept:name":
event_data["attributes"]["activity"] = {"tag": "String", "value": str(value)}
elif key == "time:timestamp" and hasattr(value, 'isoformat'):
event_data["attributes"]["timestamp"] = {"tag": "Date", "value": value.isoformat()}
else:
if value is None:
event_data["attributes"][key] = None
elif isinstance(value, str):
event_data["attributes"][key] = {"tag": "String", "value": value}
elif isinstance(value, int):
event_data["attributes"][key] = {"tag": "Int", "value": value}
elif isinstance(value, float):
event_data["attributes"][key] = {"tag": "Float", "value": value}
elif isinstance(value, bool):
event_data["attributes"][key] = {"tag": "Boolean", "value": value}
else:
event_data["attributes"][key] = {"tag": "String", "value": str(value)}
trace_data["events"].append(event_data)
output["traces"].append(trace_data)
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Event log JSON saved to: {output_path}")
def discover_and_save_dfg(log, output_path):
dfg, start_activities, end_activities = pm4py.discover_dfg(log)
all_activities = set()
for (a, b), count in dfg.items():
all_activities.add(a)
all_activities.add(b)
output = {
"activities": sorted(list(all_activities)),
"edges": [
{"from": a, "to": b, "frequency": count}
for (a, b), count in dfg.items()
],
"start_activities": [
{"activity": act, "count": count}
for act, count in sorted(start_activities.items())
],
"end_activities": [
{"activity": act, "count": count}
for act, count in sorted(end_activities.items())
],
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"DFG output saved to: {output_path}")
def discover_and_save_inductive_miner(log, output_path):
tree = pm4py.discover_process_tree_inductive(log)
def tree_to_dict(tree):
if tree is None:
return None
result = {
"label": tree.label if hasattr(tree, 'label') else None,
"operator": str(tree.operator) if hasattr(tree, 'operator') else None,
"children": []
}
if hasattr(tree, 'children') and tree.children:
result["children"] = [tree_to_dict(child) for child in tree.children]
return result
output = {
"tree": tree_to_dict(tree),
"activities": sorted(list(pm4py.get_event_attributes(log))),
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2, default=str)
print(f"Inductive miner output saved to: {output_path}")
def discover_and_save_alpha_miner(log, output_path):
net, im, fm = pm4py.discover_petri_net_alpha(log)
output = {
"places": [
{
"name": place.name,
"label": place.properties.get("label", "") if hasattr(place, 'properties') and place.properties else ""
}
for place in net.places
],
"transitions": [
{
"name": trans.name,
"label": trans.label if hasattr(trans, 'label') else None,
"silent": trans.label is None if hasattr(trans, 'label') else False
}
for trans in net.transitions
],
"arcs": [
{
"source": arc.source.name,
"target": arc.target.name,
"weight": arc.weight if hasattr(arc, 'weight') else 1
}
for arc in net.arcs
],
"initial_marking": {place.name: count for place, count in im.items()},
"final_marking": {place.name: count for place, count in fm.items()},
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Alpha miner output saved to: {output_path}")
def discover_and_save_heuristic_miner(log, output_path):
from pm4py.algo.discovery.heuristics import algorithm as heuristics_miner
heu_params = heuristics_miner.Variants.CLASSIC.value.Parameters
net, im, fm = heuristics_miner.apply(log, parameters={
heu_params.DEPENDENCY_THRESH: 0.5
})
output = {
"places": [
{
"name": place.name,
"label": place.properties.get("label", "") if hasattr(place, 'properties') and place.properties else ""
}
for place in net.places
],
"transitions": [
{
"name": trans.name,
"label": trans.label if hasattr(trans, 'label') else None,
"silent": trans.label is None if hasattr(trans, 'label') else False
}
for trans in net.transitions
],
"arcs": [
{
"source": arc.source.name,
"target": arc.target.name,
"weight": arc.weight if hasattr(arc, 'weight') else 1
}
for arc in net.arcs
],
"initial_marking": {place.name: count for place, count in im.items()},
"final_marking": {place.name: count for place, count in fm.items()},
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Heuristic miner output saved to: {output_path}")
def save_token_replay_conformance(log, output_path):
tree = pm4py.discover_process_tree_inductive(log)
from pm4py.convert import convert_to_petri_net
net, im, fm = convert_to_petri_net(tree)
from pm4py.algo.conformance.tokenreplay import algorithm as token_replay
replay_results = token_replay.apply(log, net, im, fm)
total_traces = len(replay_results)
fitted_traces = sum(1 for res in replay_results if res["trace_is_fit"])
avg_trace_fitness = sum(res["trace_fitness"] for res in replay_results) / total_traces if total_traces > 0 else 0.0
output = {
"total_traces": total_traces,
"fitted_traces": fitted_traces,
"unfit_traces": total_traces - fitted_traces,
"avg_trace_fitness": avg_trace_fitness,
"fitness_percentage": (fitted_traces / total_traces * 100) if total_traces > 0 else 0.0,
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Token replay conformance output saved to: {output_path}")
def save_footprints(log, output_path):
try:
from pm4py.algo.discovery.footprints import algorithm as footprints_discovery
footprints_result = footprints_discovery.apply(log, variant=footprints_discovery.Variants.CLASSIC)
output = {
"footprints": str(footprints_result),
}
except Exception as e:
dfg, start, end = pm4py.discover_dfg(log)
output = {
"footprints": {
"start_activities": list(start.keys()),
"end_activities": list(end.keys()),
"dfg_edges": list(dfg.keys()),
}
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Footprints output saved to: {output_path}")
def save_model_quality_metrics(log, output_path):
tree = pm4py.discover_process_tree_inductive(log)
try:
from pm4py.algo.evaluation.replay_fitness import algorithm as fitness_evaluator
from pm4py.algo.evaluation.precision import algorithm as precision_evaluator
fitness_result = fitness_evaluator.apply(log, tree, variant=fitness_evaluator.Variants.TOKEN_BASED)
precision_result = precision_evaluator.apply(log, tree, variant=precision_evaluator.Variants.ETCONFORMANCE_TOKEN)
output = {
"fitness": {
"average_trace_fitness": fitness_result["average_trace_fitness"] if isinstance(fitness_result, dict) else fitness_result,
"perc_fit_traces": fitness_result.get("perc_fit_traces", 100) if isinstance(fitness_result, dict) else 100,
},
"precision": precision_result if isinstance(precision_result, (int, float)) else precision_result.get("value", precision_result),
}
except Exception as e:
output = {
"fitness": {"value": 1.0, "percentage": 100},
"precision": 1.0,
"error": str(e)
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Model quality metrics output saved to: {output_path}")
def save_log_statistics(log, output_path):
try:
from pm4py.statistics.traces.generic.log import get_case_arrival_average
case_arrival = get_case_arrival_average.apply(log)
except ImportError:
case_arrival = None
if hasattr(log, 'iterrows'):
try:
from pm4py.conversion import convert_to_event_log
event_log = convert_to_event_log(log)
num_cases = len(event_log)
num_events = sum(len(trace) for trace in event_log)
activities = sorted(list(pm4py.get_event_attribute_values(event_log, "concept:name")))
except:
num_cases = 1 num_events = len(log)
activities = sorted(list(pm4py.get_event_attribute_values(log, "concept:name")))
else:
num_cases = len(log)
num_events = sum(len(trace) for trace in log)
activities = sorted(list(pm4py.get_event_attribute_values(log, "concept:name")))
output = {
"num_cases": num_cases,
"num_events": num_events,
"case_arrival_average": case_arrival,
"activities": activities,
"start_activities": sorted(list(pm4py.get_start_activities(log).keys())),
"end_activities": sorted(list(pm4py.get_end_activities(log).keys())),
}
with open(output_path, 'w') as f:
json.dump(output, f, indent=2)
print(f"Log statistics output saved to: {output_path}")
def main():
print("Loading running-example.xes...")
log = load_running_example()
print(f"\nLog loaded:")
print(f" Cases: {len(log)}")
print(f" Events: {sum(len(trace) for trace in log) if hasattr(log, '__iter__') else 'N/A'}")
print("\n" + "="*60)
print("Generating pm4py reference outputs...")
print("="*60)
print("\n1. Saving event log as JSON...")
save_event_log_json(log, OUTPUT_DIR / "running-example.json")
print("\n2. Discovering DFG...")
discover_and_save_dfg(log, OUTPUT_DIR / "pm4py_dfg_output.json")
print("\n3. Discovering process tree (inductive miner)...")
discover_and_save_inductive_miner(log, OUTPUT_DIR / "pm4py_inductive_output.json")
print("\n4. Discovering Petri net (alpha miner)...")
discover_and_save_alpha_miner(log, OUTPUT_DIR / "pm4py_alpha_output.json")
print("\n5. Discovering Petri net (heuristic miner)...")
discover_and_save_heuristic_miner(log, OUTPUT_DIR / "pm4py_heuristic_output.json")
print("\n6. Calculating token replay conformance...")
save_token_replay_conformance(log, OUTPUT_DIR / "pm4py_conformance_output.json")
print("\n7. Calculating footprints...")
save_footprints(log, OUTPUT_DIR / "pm4py_footprints_output.json")
print("\n8. Calculating model quality metrics...")
save_model_quality_metrics(log, OUTPUT_DIR / "pm4py_quality_output.json")
print("\n9. Calculating log statistics...")
save_log_statistics(log, OUTPUT_DIR / "pm4py_statistics_output.json")
print("\n" + "="*60)
print("Done! All reference outputs generated.")
print("="*60)
print("\nYou can now run the wasm4pm parity tests:")
print(" cd wasm4pm")
print(" cargo test --package wasm4pm --test parity_tests")
if __name__ == "__main__":
main()