From c75acc0145ea9d9d9d60729009d2b636e94d6306 Mon Sep 17 00:00:00 2001
From: Simon Wolf <swolf@mbp-von-simon.speedport.ip>
Date: Tue, 3 Dec 2024 16:59:34 +0100
Subject: [PATCH] Added loadability functionality to import OECL from Cortado
 and pass own data structure from backend to cortado. Furthermore adapted
 evaluation package of pm4py and integrated it into project to get rid of
 dependency mismatch between cortado and ocpa/ocsv.

---
 src/backend/.DS_Store                         | Bin 10244 -> 8196 bytes
 .../api/routes/input_output/importing.py      |  42 ++
 src/backend/api/websocket/main.py             |   3 +
 src/backend/mine_log.py                       | 128 ----
 src/evaluation/__init__.py                    |  26 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1232 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 3842 bytes
 .../earth_mover_distance/__init__.py          |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1028 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 2174 bytes
 .../earth_mover_distance/evaluator.py         |  59 ++
 .../earth_mover_distance/variants/__init__.py |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1023 bytes
 .../__pycache__/pyemd.cpython-310.pyc         | Bin 0 -> 5072 bytes
 .../earth_mover_distance/variants/pyemd.py    | 175 +++++
 src/evaluation/evaluator.py                   | 114 +++
 src/evaluation/generalization/__init__.py     |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 998 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 1914 bytes
 .../__pycache__/parameters.cpython-310.pyc    | Bin 0 -> 1230 bytes
 src/evaluation/generalization/evaluator.py    |  43 ++
 src/evaluation/generalization/parameters.py   |  22 +
 .../generalization/variants/__init__.py       |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 999 bytes
 .../__pycache__/token_based.cpython-310.pyc   | Bin 0 -> 4125 bytes
 .../generalization/variants/token_based.py    | 115 +++
 src/evaluation/precision/__init__.py          |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 988 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 2647 bytes
 .../__pycache__/parameters.cpython-310.pyc    | Bin 0 -> 1437 bytes
 .../__pycache__/utils.cpython-310.pyc         | Bin 0 -> 4170 bytes
 src/evaluation/precision/evaluator.py         |  82 +++
 src/evaluation/precision/parameters.py        |  26 +
 src/evaluation/precision/utils.py             | 148 ++++
 src/evaluation/precision/variants/__init__.py |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1027 bytes
 .../align_etconformance.cpython-310.pyc       | Bin 0 -> 7289 bytes
 .../etconformance_token.cpython-310.pyc       | Bin 0 -> 3513 bytes
 .../precision/variants/align_etconformance.py | 274 ++++++++
 .../precision/variants/etconformance_token.py | 113 +++
 src/evaluation/replay_fitness/__init__.py     |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 998 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 3643 bytes
 .../__pycache__/parameters.cpython-310.pyc    | Bin 0 -> 1455 bytes
 src/evaluation/replay_fitness/evaluator.py    | 122 ++++
 src/evaluation/replay_fitness/parameters.py   |  26 +
 .../replay_fitness/variants/__init__.py       |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1026 bytes
 .../alignment_based.cpython-310.pyc           | Bin 0 -> 4655 bytes
 .../__pycache__/token_replay.cpython-310.pyc  | Bin 0 -> 3963 bytes
 .../variants/alignment_based.py               | 126 ++++
 .../replay_fitness/variants/token_replay.py   | 100 +++
 src/evaluation/simplicity/__init__.py         |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 990 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 1728 bytes
 src/evaluation/simplicity/evaluator.py        |  39 ++
 .../simplicity/variants/__init__.py           |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 990 bytes
 .../__pycache__/arc_degree.cpython-310.pyc    | Bin 0 -> 2389 bytes
 .../simplicity/variants/arc_degree.py         |  70 ++
 src/evaluation/soundness/__init__.py          |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 966 bytes
 src/evaluation/soundness/woflan/__init__.py   |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1060 bytes
 .../__pycache__/algorithm.cpython-310.pyc     | Bin 0 -> 21598 bytes
 src/evaluation/soundness/woflan/algorithm.py  | 654 ++++++++++++++++++
 .../soundness/woflan/graphs/__init__.py       |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1101 bytes
 .../__pycache__/utility.cpython-310.pyc       | Bin 0 -> 5420 bytes
 .../minimal_coverability_graph/__init__.py    |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1068 bytes
 ...minimal_coverability_graph.cpython-310.pyc | Bin 0 -> 5951 bytes
 .../minimal_coverability_graph.py             | 186 +++++
 .../graphs/reachability_graph/__init__.py     |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1044 bytes
 .../reachability_graph.cpython-310.pyc        | Bin 0 -> 2223 bytes
 .../reachability_graph/reachability_graph.py  |  56 ++
 .../restricted_coverability_graph/__init__.py |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1077 bytes
 ...tricted_coverability_graph.cpython-310.pyc | Bin 0 -> 2923 bytes
 .../restricted_coverability_graph.py          |  89 +++
 .../soundness/woflan/graphs/utility.py        | 139 ++++
 .../woflan/not_well_handled_pairs/__init__.py |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1042 bytes
 .../not_well_handled_pairs.cpython-310.pyc    | Bin 0 -> 2426 bytes
 .../not_well_handled_pairs.py                 |  64 ++
 .../woflan/place_invariants/__init__.py       |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 1092 bytes
 .../place_invariants.cpython-310.pyc          | Bin 0 -> 2672 bytes
 .../__pycache__/s_component.cpython-310.pyc   | Bin 0 -> 3315 bytes
 .../uniform_invariant.cpython-310.pyc         | Bin 0 -> 1340 bytes
 .../__pycache__/utility.cpython-310.pyc       | Bin 0 -> 4225 bytes
 .../place_invariants/place_invariants.py      |  66 ++
 .../woflan/place_invariants/s_component.py    |  90 +++
 .../place_invariants/uniform_invariant.py     |  24 +
 .../woflan/place_invariants/utility.py        | 118 ++++
 src/evaluation/wf_net/__init__.py             |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 982 bytes
 .../__pycache__/evaluator.cpython-310.pyc     | Bin 0 -> 1655 bytes
 src/evaluation/wf_net/evaluator.py            |  52 ++
 src/evaluation/wf_net/variants/__init__.py    |  17 +
 .../__pycache__/__init__.cpython-310.pyc      | Bin 0 -> 981 bytes
 .../__pycache__/petri_net.cpython-310.pyc     | Bin 0 -> 2709 bytes
 src/evaluation/wf_net/variants/petri_net.py   | 101 +++
 .../header-bar/header-bar.component.ts        |  22 +-
 .../backendService/backend.service.ts         |  19 +
 .../app/services/logService/log.service.ts    |   3 +
 107 files changed, 3744 insertions(+), 132 deletions(-)
 delete mode 100644 src/backend/mine_log.py
 create mode 100644 src/evaluation/__init__.py
 create mode 100644 src/evaluation/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/earth_mover_distance/__init__.py
 create mode 100644 src/evaluation/earth_mover_distance/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/earth_mover_distance/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/earth_mover_distance/evaluator.py
 create mode 100644 src/evaluation/earth_mover_distance/variants/__init__.py
 create mode 100644 src/evaluation/earth_mover_distance/variants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/earth_mover_distance/variants/__pycache__/pyemd.cpython-310.pyc
 create mode 100644 src/evaluation/earth_mover_distance/variants/pyemd.py
 create mode 100644 src/evaluation/evaluator.py
 create mode 100644 src/evaluation/generalization/__init__.py
 create mode 100644 src/evaluation/generalization/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/generalization/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/generalization/__pycache__/parameters.cpython-310.pyc
 create mode 100644 src/evaluation/generalization/evaluator.py
 create mode 100644 src/evaluation/generalization/parameters.py
 create mode 100644 src/evaluation/generalization/variants/__init__.py
 create mode 100644 src/evaluation/generalization/variants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/generalization/variants/__pycache__/token_based.cpython-310.pyc
 create mode 100644 src/evaluation/generalization/variants/token_based.py
 create mode 100644 src/evaluation/precision/__init__.py
 create mode 100644 src/evaluation/precision/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/precision/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/precision/__pycache__/parameters.cpython-310.pyc
 create mode 100644 src/evaluation/precision/__pycache__/utils.cpython-310.pyc
 create mode 100644 src/evaluation/precision/evaluator.py
 create mode 100644 src/evaluation/precision/parameters.py
 create mode 100644 src/evaluation/precision/utils.py
 create mode 100644 src/evaluation/precision/variants/__init__.py
 create mode 100644 src/evaluation/precision/variants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/precision/variants/__pycache__/align_etconformance.cpython-310.pyc
 create mode 100644 src/evaluation/precision/variants/__pycache__/etconformance_token.cpython-310.pyc
 create mode 100644 src/evaluation/precision/variants/align_etconformance.py
 create mode 100644 src/evaluation/precision/variants/etconformance_token.py
 create mode 100644 src/evaluation/replay_fitness/__init__.py
 create mode 100644 src/evaluation/replay_fitness/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/replay_fitness/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/replay_fitness/__pycache__/parameters.cpython-310.pyc
 create mode 100644 src/evaluation/replay_fitness/evaluator.py
 create mode 100644 src/evaluation/replay_fitness/parameters.py
 create mode 100644 src/evaluation/replay_fitness/variants/__init__.py
 create mode 100644 src/evaluation/replay_fitness/variants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/replay_fitness/variants/__pycache__/alignment_based.cpython-310.pyc
 create mode 100644 src/evaluation/replay_fitness/variants/__pycache__/token_replay.cpython-310.pyc
 create mode 100644 src/evaluation/replay_fitness/variants/alignment_based.py
 create mode 100644 src/evaluation/replay_fitness/variants/token_replay.py
 create mode 100644 src/evaluation/simplicity/__init__.py
 create mode 100644 src/evaluation/simplicity/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/simplicity/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/simplicity/evaluator.py
 create mode 100644 src/evaluation/simplicity/variants/__init__.py
 create mode 100644 src/evaluation/simplicity/variants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/simplicity/variants/__pycache__/arc_degree.cpython-310.pyc
 create mode 100644 src/evaluation/simplicity/variants/arc_degree.py
 create mode 100644 src/evaluation/soundness/__init__.py
 create mode 100644 src/evaluation/soundness/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/__pycache__/algorithm.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/algorithm.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/__pycache__/utility.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__pycache__/minimal_coverability_graph.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/minimal_coverability_graph.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/reachability_graph/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/reachability_graph/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/reachability_graph/__pycache__/reachability_graph.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/reachability_graph/reachability_graph.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__pycache__/restricted_coverability_graph.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/restricted_coverability_graph.py
 create mode 100644 src/evaluation/soundness/woflan/graphs/utility.py
 create mode 100644 src/evaluation/soundness/woflan/not_well_handled_pairs/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/not_well_handled_pairs/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/not_well_handled_pairs/__pycache__/not_well_handled_pairs.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/not_well_handled_pairs/not_well_handled_pairs.py
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/__init__.py
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/__pycache__/place_invariants.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/__pycache__/s_component.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/__pycache__/uniform_invariant.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/__pycache__/utility.cpython-310.pyc
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/place_invariants.py
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/s_component.py
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/uniform_invariant.py
 create mode 100644 src/evaluation/soundness/woflan/place_invariants/utility.py
 create mode 100644 src/evaluation/wf_net/__init__.py
 create mode 100644 src/evaluation/wf_net/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/wf_net/__pycache__/evaluator.cpython-310.pyc
 create mode 100644 src/evaluation/wf_net/evaluator.py
 create mode 100644 src/evaluation/wf_net/variants/__init__.py
 create mode 100644 src/evaluation/wf_net/variants/__pycache__/__init__.cpython-310.pyc
 create mode 100644 src/evaluation/wf_net/variants/__pycache__/petri_net.cpython-310.pyc
 create mode 100644 src/evaluation/wf_net/variants/petri_net.py

diff --git a/src/backend/.DS_Store b/src/backend/.DS_Store
index 10b404014fd30fd8b8d4dc68d6ab2da8d4b403a1..74b6af4ecf10a5602fdf50e7768df49a5e8fcedb 100644
GIT binary patch
delta 129
zcmZn(XmOBWU|?W$DortDU;r^W7$CF&_ryZ6iF%5nq9Az&1_1_!OokGMc!qceJ%)mf
zh11w4Ht=p{=V0Mr6rG$RaC367(3{QvBKMd!n@dD63o<i+fdn@L1A{9Ag8=u&!tczJ
U`Befr7#SEqb}(#?=b6I{0N3^yEdT%j

delta 537
zcmZp1XbF&DU|?W$DortDU{GLSVBlbY&;mRY3&ketDGGxG85tM^7#I>gbMljua`KZl
z7EWWI*ucM;or8rV5+u*cpvREOkiw9PP{RP$!YIqYz@YaZ3>ZLwok5SGjG>qznIRvf
z9%dp`bvLr=$vgr#jguHk88R7i7*bGGGB`3YFw6rR`yXU111keu<>V&e1`cB*6CDL(
z6NAZIBKG1)l4Zd~c{%xc=^#%}ejxDCkcA<IA(Nq)p#;?eCXfnb3*hRY#+&LW7#mxH
zj2DGVAsfE=oZv$yU1o-2h9ZV!RI?eo85kJrkQ@(HHMvRnxG<EBV$>RuXy%O#6)b|x
zV81dba5FG4xH2#Z@N6vn&ODi4CQyV45_1|L1&j;~1|WKJflTk_W+5HYov{i4t<i`j

diff --git a/src/backend/api/routes/input_output/importing.py b/src/backend/api/routes/input_output/importing.py
index 178a958..f21641e 100644
--- a/src/backend/api/routes/input_output/importing.py
+++ b/src/backend/api/routes/input_output/importing.py
@@ -12,6 +12,10 @@ from endpoints.load_event_log import calculate_event_log_properties
 from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
 from pydantic import BaseModel
 
+from backend_utilities import mine_log as ml
+import tempfile
+import os
+
 router = APIRouter(tags=["importing"], prefix="/importing")
 
 
@@ -39,6 +43,44 @@ class FilePathInput(BaseModel):
     file_path: str
 
 
+@router.post("/loadOCELFromFile")
+async def load_ocel_from_file(
+    file: UploadFile = File(...),
+    config_repo: ConfigurationRepository = Depends(get_config_repo),
+):
+    cache.pcache = {}
+
+    # Save the uploaded file to a temporary location
+    with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
+        tmp_file.write(await file.read())
+        tmp_file_path = tmp_file.name
+
+    content = "".join([line.decode("UTF-8") for line in file.file])
+
+    try:
+        print(f"FILE: {tmp_file_path}")
+        ocel = await ml.process_ocel(filename=tmp_file_path)
+    except FileNotFoundError as e:
+        raise HTTPException(
+            status_code=404, detail=f"Event log not found ({tmp_file_path})"
+        )
+    finally:
+        # Clean up the temporary file
+        os.remove(tmp_file_path)
+
+    #event_log = xes_importer.deserialize(content)
+    #use_mp = (
+    #    len(event_log) > config_repo.get_configuration().min_traces_variant_detection_mp
+    #)
+    #info = calculate_event_log_properties(event_log, use_mp=use_mp)
+    print(f"OCEL: {ocel}")
+    print("\n\n\n")
+    return ocel
+
+
+class FilePathInput(BaseModel):
+    file_path: str
+
 @router.post("/loadEventLogFromFilePath")
 async def load_event_log_from_file_path(
     d: FilePathInput, config_repo: ConfigurationRepository = Depends(get_config_repo)
diff --git a/src/backend/api/websocket/main.py b/src/backend/api/websocket/main.py
index 394356f..e8a4728 100644
--- a/src/backend/api/websocket/main.py
+++ b/src/backend/api/websocket/main.py
@@ -2,15 +2,18 @@ from fastapi.routing import APIRouter
 from typing import Callable
 from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
 
+
 from api.routes.conformance.variantConformance import (
     calculate_alignment_intern_with_timeout,
     get_alignment_callback,
 )
+
 from api.routes.variants.subvariantMining import (
     mine_repetition_patterns_with_timeout,
     RepetitionsMiningConfig,
     get_repetition_mining_callback,
 )
+
 from backend_utilities.configuration.repository import ConfigurationRepositoryFactory
 from backend_utilities.multiprocessing.pool_factory import PoolFactory
 from cache import cache
diff --git a/src/backend/mine_log.py b/src/backend/mine_log.py
deleted file mode 100644
index d7ff26c..0000000
--- a/src/backend/mine_log.py
+++ /dev/null
@@ -1,128 +0,0 @@
-from fastapi import FastAPI, UploadFile, File
-from pydantic import BaseModel
-from typing import List, Dict, Any, Optional
-import uvicorn
-
-from ocpa.objects.log.importer.ocel import factory as ocel_import_factory
-from ocpa.visualization.log.variants import factory as variants_visualization_factory
-from ocpa.algo.util.filtering.log import case_filtering
-from ocpa.objects.log.exporter.ocel import factory as ocel_export_factory
-
-import ocsv.Input_Extraction_Definition as IED  # Seems like a helper class to define data structures and querying lanes
-import ocsv.Super_Variant_Definition as SVD     # Super Variant Definition, super lane definition
-import ocsv.Super_Variant_Visualization as SVV  # Visualization of super variants
-import ocsv.Intra_Variant_Summarization as IAVS # 
-import ocsv.Summarization_Selection as SS
-import ocsv.Intra_Variant_Generation as IAVG
-import ocsv.Inter_Variant_Summarization as IEVS
-import ocsv.Inter_Variant_Generation as IEVG
-import ocsv.Super_Variant_Hierarchy as SVH
-import time 
-import numpy as np
-
-app = FastAPI()
-
-class Parameters(BaseModel):
-    execution_extraction: Optional[str] = "leading_type"
-    leading_type: Optional[str] = "application"
-    max_levels: Optional[int] = 4
-    frequency_distribution_type: Optional[str] = "NORMAL"
-
-
-@app.post("/process_ocel/")
-#async def process_ocel(file: UploadFile = File(...), parameters: Parameters = None):
-async def process_ocel(parameters: Parameters = Parameters()):
-    '''
-    # Save the uploaded file
-    file_location = f"/tmp/{file.filename}"
-    with open(file_location, "wb+") as file_object:
-        file_object.write(file.file.read())
-    '''
-    # Predefined filename
-    filename = "../ocsv/ocsv/EventLogs/BPI2017-Top10.jsonocel"
-    #parameters = {"execution_extraction": "leading_type",
-              #"leading_type": "application"}
-    # Load the OCEL file
-    ocel = ocel_import_factory.apply(file_path=filename, parameters=parameters)
-
-    # Step 1: Summarization Generation
-    all_summarizations, per_variant_dict, per_encoding_dict = IAVG.complete_intra_variant_summarization(ocel)
-
-    # Step 2: Summarization Matching
-    summarizations = SS.intra_variant_summarization_selection(all_summarizations, per_variant_dict, per_encoding_dict)
-
-    # Step 3: Inter-Variant Summarization
-    IEVG.NESTED_STRUCTURES = True
-    initial_set = [summarizations[i] for i in range(len(summarizations))]
-    hierarchies, final_super_variants = IEVG.generate_super_variant_hierarchy(
-        initial_set, parameters.max_levels, frequency_distribution_type=getattr(IEVG.Distribution, parameters.frequency_distribution_type)
-    )
-
-    # Extract super variants
-    values = []
-    for super_variant in final_super_variants[0]:
-        values.append("NEW SUPER VARIANT")
-        values.append(super_variant)
-
-    def extract_super_variants(nested_structure):
-        """Recursively extract SuperVariant objects from a nested structure."""
-        super_variants = []
-        if isinstance(nested_structure, tuple):
-            for item in nested_structure:
-                super_variants.extend(extract_super_variants(item))
-        elif isinstance(nested_structure, list):
-            for item in nested_structure:
-                super_variants.extend(extract_super_variants(item))
-        elif isinstance(nested_structure, SVD.SuperVariant):
-            super_variants.append(nested_structure)
-        return super_variants
-
-    # Create a useful data structure out of the nested list of super variants and tuples containing super variants.
-    super_variants_dict = {}
-    current_super_variant = None
-
-    for line in values:
-        if line == "NEW SUPER VARIANT":
-            current_super_variant = None
-        else:
-            if current_super_variant is None:
-                current_super_variant = []
-                super_variants_dict[len(super_variants_dict)] = current_super_variant
-            current_super_variant.extend(extract_super_variants(line))
-
-    def extract_hierarchy_info(hierarchy, level=0, info=None):
-        """Recursively extract information about the hierarchical structure."""
-        if info is None:
-            info = {"levels": {}, "super_variants": []}
-        
-        if isinstance(hierarchy, dict):
-            for key, value in hierarchy.items():
-                if key not in info["levels"]:
-                    info["levels"][key] = []
-                info["levels"][key].append(value)
-                extract_hierarchy_info(value, level + 1, info)
-        elif isinstance(hierarchy, list):
-            for item in hierarchy:
-                extract_hierarchy_info(item, level, info)
-        elif isinstance(hierarchy, tuple):
-            for item in hierarchy:
-                extract_hierarchy_info(item, level, info)
-        elif isinstance(hierarchy, SVD.SuperVariant):
-            info["super_variants"].append(hierarchy)
-        
-        return info
-
-    # Extract hierarchy information
-    hierarchy_info_list = []
-    for hierarchy in hierarchies:
-        hierarchy_info = extract_hierarchy_info(hierarchy)
-        hierarchy_info_list.append(hierarchy_info)
-
-    return {
-        "super_variants_dict": super_variants_dict,
-        "hierarchy_info_list": hierarchy_info_list
-    }
-
-if __name__ == "__main__":
-    uvicorn.run(app, host="0.0.0.0", port=8000)
-
diff --git a/src/evaluation/__init__.py b/src/evaluation/__init__.py
new file mode 100644
index 0000000..789b957
--- /dev/null
+++ b/src/evaluation/__init__.py
@@ -0,0 +1,26 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation import generalization, precision, replay_fitness, simplicity, evaluator, wf_net
+import pkgutil
+
+if pkgutil.find_loader("pyemd"):
+    # import the EMD only if the pyemd package is installed
+    from evaluation import earth_mover_distance
+
+if pkgutil.find_loader("networkx") and pkgutil.find_loader("sympy"):
+    # import the Woflan package only if NetworkX and sympy are installed
+    from evaluation import soundness
diff --git a/src/evaluation/__pycache__/__init__.cpython-310.pyc b/src/evaluation/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..55963c030775480230afbc23cea3e705eb17672b
GIT binary patch
literal 1232
zcmd1j<>g{vU|=x4>6<>2g@NHQh=Yuo85kHG7#J9e^B5QyQW&BbQW#U1au{=&qL^}-
zqnLA9qF8cSqgZp<qS$iTqu3ci^2|9LQ5>lp3pi6)7BUtorZA?k<}l`RMR74QfJNCB
zG8P$wS?miLi%e4(Q#f)MbGf6q85vTTgW*_{Gx0bR0|S?Wf`URwMrN@>T4qkFLT0f-
zL1IyfLVlV;fUikFrGkcUeo?A|XI@&ql|n{INkOrdzJ5WjNkOGvT4sq}T2W$YUPgXe
zYLQ+_s-_+n*ffYnkcma9sS3sUX(i=}MX6Q_mHDL#$%%OiMX4#7#U(|VNu?#J3YjGe
ziFqmd`9%u3`6-!cm0%MxOB71;Qc{Z)N-|OvN>Yn*i$M+oaoqhv72H$vQi~FE6aq?<
zax#+@d@_?$^NLdy5{nfIKqAE%sVNFc5Q9Kk+(51fhPpz*Ex$A`C9xzkKTk&?HM1lm
zwMd~XwWv5VKTp9JW(m|p9fkZNuyZsLOF)h<Qphg=Y1C9m%&Sz$Ni0F=MfW+vgDDD`
zd0=m3<QJqWlw>59fV^CunUkZCl&Vl#oSIgeqXV`jskB5P+%v?(KQu(a(JxXV+%YJ~
z(Jv&@TA@6%BqP7HL?N{-6=GIqZb43FYKlU6Vo_0IUP+}wej3;aU)LaK4@bWcM<-7o
z&yYxk{2~Q6&k#S?;9vze{~!fNg#gE(5Kre&AIBhtfY6`-|6o@=1%=>Lki$~JCg2I*
zG;kahr7EPPmLz886hr+SnO~|<oRMFelcJE3SeB|#l$xBHS(cijkf@NHUjT9%*!#E*
z2HTsMlb@Fk_9!IX=_x39rYYp*m*^-Ir=}{{z>;2hdAVMCUa4MwQM!H(B;t$p?ew@_
zf^wB6+bzCyNaDz>0>|+!&Vr)U<V;ZX-r_4tEyzi%j8Drf$xAIRzQt7x4#wonlFD10
zsbz^drHLi^MYq_>)8g||OZ=i(3o27{Q#2WGiKHeLm1M-{=9i@w#e-rtF)umw7Drxc
zNqK%zcEv5$;>z5DN=?RFoW-ET1u|Td=N5Z>d`f<DeEcmgsM(qMc|}YN3=Bn}OkczT
zVzGh<HW0ziz`$^ey&yZiv?Md<7I#`^UP^pUequ^$Q4t47o|}PzVI@P60Ei7Deg*1>
z7N-^!>lc^j=cMVoq!wqF<QM4YX69w)rN^gdmgpCk7Ni#GmL(QtCgzpsrdAZ><QJtD
z=@%Cz>mwYbA0MBYmst`YuUAlci^C>2KczG$)s7KVB7tmSVNej@VB})rVG>~yVB+`(
E03<McqW}N^

literal 0
HcmV?d00001

diff --git a/src/evaluation/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..144fed0f31b32085fd8f16ba37e162e693c48748
GIT binary patch
literal 3842
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

literal 0
HcmV?d00001

diff --git a/src/evaluation/earth_mover_distance/__init__.py b/src/evaluation/earth_mover_distance/__init__.py
new file mode 100644
index 0000000..b686dd1
--- /dev/null
+++ b/src/evaluation/earth_mover_distance/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.evaluation.earth_mover_distance import evaluator, variants
diff --git a/src/evaluation/earth_mover_distance/__pycache__/__init__.cpython-310.pyc b/src/evaluation/earth_mover_distance/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..069ee8fa5be8d80d2b9bd05e376ea18811c2acbc
GIT binary patch
literal 1028
zcmd1j<>g{vU|{e!5KC8OW?*;>;vi!t1_lNP1_p*=2?hp+6ox2<6vh;$9L8LxD5hNI
zC}u{66y{(CO_s#tObiTM3JMAeAsLy)3Tc@+sS26J3I&NpB?|dz3IV<*0hJ0GzWGI|
z3Z8js`Bn-UB_##LR{Ht{xh4gbdTE&@dTB+8rFj|oX{kkeDXE%zTwv268bKx&rKTzr
z=ckpFCl;kzDOBc{DkLZ7DHNrqWEPhcWhRxDq$*^VC?w{k=;s$H<mRVjrd5JX$ShGP
z%}YrwQYgttRVYa<$}I*t2*h#s3srDW%}Xsx%uxs^P0GnkR`AJ8PR%P$RY)vWC;*8R
zXQZYmBtZ-UX>kL&A{gol1-Jatyp+U}%=|nZh1ATFjMO59vecsD%=|nBW0)mS6Ll2w
zi@?s&NGt(4zDOaz0Hje<Au+E~At$i}p%>lf2oI(xWafdrk&$1Js!)=VSOW5Ld1g+I
zLQ<+iX>n>=X^sxqmZZ`Wg>cUh5C6~*1xLR~g>c89AV<HDNNa`i%#w`!(h`N#vQ&s!
znYjfynW-rX<%vZ_iFqZJ3i)YZBYa(hoIM=<LL8kueLO=V74nM|+&n}4T!VuZ-28(S
z92Ei_gF-x=Lwy{B6aqqn0{nwr^%N9>Q$Y?(1)G2;eAB>jRFtZal3J3OnNtk)b7X$0
zLUBfZX-<klMq*j2LQ!gRYGzq#ibA47a()5GX<+Z;HW+MgVorWuI@qI-bf>4F;F+e7
zmtUfzP@I~oU;|5f<>lpi>3OAk`9<maIgp4i*0<B+dI?JZnoPGiQ_B)_N)t=+i*9k0
zB^G5S=9LutX|mj6kB?8uPmYg|QiUX5sItubJiXM!qLPgG-2AfCqIgieCFUik7J;%*
z5i<h=!%BuCRt5$L@vBHbv^ce>SiiVDKPOE;H#09YEx#x|)mT3@udEo%&jUFtNxz`7
zBqKl1SkKTvzc{lbRkt89IXf{uwHT6h^bxk}W3yd9K0Y%qvm`!Vub}c4hfQvNN@-52
R9U~}5voJ6)urP=S002mJLCydG

literal 0
HcmV?d00001

diff --git a/src/evaluation/earth_mover_distance/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/earth_mover_distance/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a6c586d2d84d1a08ec1a29c60f92e349e47b10e5
GIT binary patch
literal 2174
zcmd1j<>g{vU|{e!5KGTzXJB{?;vi!-1_lNP1_p-W1_lO(6ox2<6vh;$9L8LxC?-Y_
zn>mL$mnDh?%x1}9&1H*X1G8Ck*rV7}7*p7CIC43oIH6)(QC#i}DeNg6Eet6fsVvRR
zQQWEASv*m^sq9&NDV!->shla?DLlO_Akh@oG^P~Z6uuV5X2vM~6xLt{P5#8=ObiTM
z3JMAeAsLy)3Tc@+sS26J3I&NpB?|dz3IV<*0hJ0GzWGI|3Z8js`Bn-UB_##LR{Ht{
zxh4gbdTE&@dTB+8rFj|oX{kkeDXE%zTwv268bKx&rKTzr=ckpFCl;kzDOBc{DkLZ7
zDHNrqWEPhcWhRxDq$*^VC?w{k=;s$H<mRVjrd5JX$ShGP%}YrwQYgttRVYa<$}I*t
z2*h#s3srDW%}Xsx%uxs^P0GnkR`AJ8PR%P$RY)vWC;*8RXQZYmBtZ-UX>kL&A{gol
z1-Jatyp+U}%=|nZh1ATFjMO59vecsD%=|nBW0)mS6Ll2wi@?s&NGt(4zDOaz0Hje<
zAu+E~At$i}p%>lf2oI(xWafdrk&$1Js!)=VSOW5Ld1g+ILQ<+iX>n>=X^sxqmZZ`W
zg>cUh5C6~*1xLR~g>c89AV<HDNNa`i%#w`!(h`N#vQ&s!nYjfynW-rX<%vZ_iFqZJ
z3i)YZBYa(hoIM=<LL8kueLO=V74nM|+&n}4T!VuZ-28(S92Ei_gF-x=Lwy{B6aqqn
z0{nwr^%N9>Q$Y?(1)G2;eAB>jRFtZal3J3OnNtk)b7X$0LUBfZX-<klMq*j2LQ!gR
zYGzq#ibA47a()5GX<+Z;HW+MgVorWuI@qI-bf>4F;F+e7mtUfzP@I~oU;|5f<>lpi
z>3OAk`9<maIgp4i*0<B+dI`#BnvAzt3o27{Q#2WGvAE`y=4vwD;!3SZO^z=u$;>JC
z(`3BG9_AVp?CI~93{sDb8DV)-fPsM_l_82Tg&~S5l{tzfg)x{xlj#;mSYlCTVqQtH
zpC;=qj`;Yz#N5>Q_*-1@@wxdar8yurPkek~X<`mUrih7wf#DWwK%}d$%SwhKZUzPj
z@vB%rv^ce>SiiVDKPOE;H#09YEx#x|)mT3@udEo%&jWcaNxz`7BqKl1SkKTvzc{lb
zRkt89IXf{uwHT89^i#_cb4tMpSwA(gs3apkH@_^kC?1sZ67!N%Ve<J!dIgn5ppr)t
zRPwMeFfed1a4>>Js#uNmjPy*an2q%eep!VUgOh;*BzGs~r044)?9jtzhXT||nk-Sg
zDX9fTsmX~YsVVW9c~M+NsUYWo*thtTON)w9^Gf2O`Ro=uGyx@p90Q935Sx{Ofx#K%
zN(BZ6h7yJaj46x@85tQ$m=-YCFfC-vVyR)MVXR?JVd`ZLX3%6#oXH3+zuZ$xiovPO
z)z?KKCowO*G%-C@At|+_JT(=PV@k^N;S$BrVk)sHF*mg&wWt`J0(FrASOjE{A(#a!
zBi%BKic8>TfQ3N{jKD0Ag5cET{Ja!Y6=je_16Kn}M6i+u6v{=JB^kM36$Pj+0vQ^B
zDg-qT;$o0IP6KrmGV_viN>eiP(yhRTgH(gCt^!Pt9yoDYDS)$#LQ-OJYKj6Z`9Ojw
zD7B=tC=X&Aa`5PaO$4b0GeE|ILLMXlN_$|zB3=dt22ch>_)3%Y7DstvQC?<Vdhsn5
z5UU6j?YFqoQ%mBZUc1GbSWu8tsmT(>3JP9B2xWAO9V%Z0%AbhrRKx?ySRxDz44^bq
z3^I;ON{q1xB%&#Bi#<L*B|kYnK1vUgstF`$J*Xwcklf3Xnpc`z#LU3J5XA-24=P8X
zZiXfJ%=|o<Y;I~vVi7127YTw~1<G_qpcX+Czl*DzW2jF^e3)a9r=wrUEiRbu)D*B&
zkvs%0DL}ru#bE;}TI@h&Q?U#K0|N^Kj{pxN4<iQ{3ox^>a4<75GBL9J6JrJd;q7cW

literal 0
HcmV?d00001

diff --git a/src/evaluation/earth_mover_distance/evaluator.py b/src/evaluation/earth_mover_distance/evaluator.py
new file mode 100644
index 0000000..528aeea
--- /dev/null
+++ b/src/evaluation/earth_mover_distance/evaluator.py
@@ -0,0 +1,59 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.evaluation.earth_mover_distance.variants import pyemd
+from enum import Enum
+from pm4py.util import exec_utils
+import deprecation
+from pm4py.meta import VERSION
+import warnings
+
+
+class Variants(Enum):
+    PYEMD = pyemd
+
+
+DEFAULT_VARIANT = Variants.PYEMD
+
+
+@deprecation.deprecated(deprecated_in="2.2.5", removed_in="3.0",
+                        current_version=VERSION,
+                        details="Use the pm4py.algo.evaluation.earth_mover_distance package")
+def apply(lang1, lang2, variant=Variants.PYEMD, parameters=None):
+    """
+    Gets the EMD language between the two languages
+
+    Parameters
+    -------------
+    lang1
+        First language
+    lang2
+        Second language
+    variant
+        Variants of the algorithm
+    parameters
+        Parameters
+    variants
+        Variants of the algorithm, including:
+            - Variants.PYEMD: pyemd based distance
+
+    Returns
+    -------------
+    dist
+        EMD distance
+    """
+    warnings.warn("Use the pm4py.algo.evaluation.earth_mover_distance package")
+    return exec_utils.get_variant(variant).apply(lang1, lang2, parameters=parameters)
diff --git a/src/evaluation/earth_mover_distance/variants/__init__.py b/src/evaluation/earth_mover_distance/variants/__init__.py
new file mode 100644
index 0000000..18335b3
--- /dev/null
+++ b/src/evaluation/earth_mover_distance/variants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.evaluation.earth_mover_distance.variants import pyemd
diff --git a/src/evaluation/earth_mover_distance/variants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/earth_mover_distance/variants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fcc433767db38236cdcbadcc1fa3e8966a71da9a
GIT binary patch
literal 1023
zcmd1j<>g{vU|{e!5KC8JW?*;>;vi!t1_lNP1_p*=5e5c^6ox2<6vh;$9L8LxC?-aR
z6y{(CO_s#tObiTM3JMAeAsLy)3Tc@+sS26J3I&NpB?|dz3IV<*0hJ0GzWGI|3Z8js
z`Bn-UB_##LR{Ht{xh4gbdTE&@dTB+8rFj|oX{kkeDXE%zTwv268bKx&rKTzr=ckpF
zCl;kzDOBc{DkLZ7DHNrqWEPhcWhRxDq$*^VC?w{k=;s$H<mRVjrd5JX$ShGP%}Yrw
zQYgttRVYa<$}I*t2*h#s3srDW%}Xsx%uxs^P0GnkR`AJ8PR%P$RY)vWC;*8RXQZYm
zBtZ-UX>kL&A{gol1-Jatyp+U}%=|nZh1ATFjMO59vecsD%=|nBW0)mS6Ll2wi@?s&
zNGt(4zDOaz0Hje<Au+E~At$i}p%>lf2oI(xWafdrk&$1Js!)=VSOW5Ld1g+ILQ<+i
zX>n>=X^sxqmZZ`Wg>cUh5C6~*1xLR~g>c89AV<HDNNa`i%#w`!(h`N#vQ&s!nYjfy
znW-rX<%vZ_iFqZJ3i)YZBYa(hoIM=<LL8kueLO=V74nM|+&n}4T!VuZ-28(S92Ei_
zgF-x=Lwy{B6aqqn0{nwr^%N9>Q$Y?(1)G2;eAB>jRFtZal3J3OnNtk)b7X$0LUBfZ
zX-<klMq*j2LQ!gRYGzq#ibA47a()5GX<+Z;HW+MgVorWuI@qI-bf>4F;F+e7mtUfz
zP@I~oU;|5f<>lpi>3OAk`9<maIgp4i*0<B+dI?JVnvAzt3o27{Q~WfUZ?VV6r{pKc
z$4BWwl51*NVooVI;`LG!i%K%$bMwnmi{e2sm6(^Ds#lg+l$n@UQd|VeIV%~8SQr=}
z#II`o(BjmhV*TRs{G2rX+|0bpwEUv<RAc?rys~02KM&-XB>jTQl8pR3V?9Fy{o>4$
zRNaEa<m|-s)M7}6(MPyIADavGVJ^^*kI&4@EQycTE2zB1VUwGmQks)$#|X;IAV;w<
H2nYZG5j#P+

literal 0
HcmV?d00001

diff --git a/src/evaluation/earth_mover_distance/variants/__pycache__/pyemd.cpython-310.pyc b/src/evaluation/earth_mover_distance/variants/__pycache__/pyemd.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..70fc6d67e009093eb97479dc5ddd1ddb6dec2b70
GIT binary patch
literal 5072
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

literal 0
HcmV?d00001

diff --git a/src/evaluation/earth_mover_distance/variants/pyemd.py b/src/evaluation/earth_mover_distance/variants/pyemd.py
new file mode 100644
index 0000000..e9b3386
--- /dev/null
+++ b/src/evaluation/earth_mover_distance/variants/pyemd.py
@@ -0,0 +1,175 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.util.regex import SharedObj, get_new_char
+from pm4py.util import string_distance
+import numpy as np
+from pyemd import emd
+from pm4py.util import exec_utils
+
+
+class Parameters:
+    STRING_DISTANCE = "string_distance"
+
+
+def normalized_levensthein(s1, s2):
+    """
+    Normalized Levensthein distance
+
+    Parameters
+    -------------
+    s1
+        First string
+    s2
+        Second string
+
+    Returns
+    --------------
+    dist
+        Distance
+    """
+    return float(string_distance.levenshtein(s1, s2)) / float(max(len(s1), len(s2)))
+
+
+def get_act_correspondence(activities, parameters=None):
+    """
+    Gets an encoding for each activity
+
+    Parameters
+    --------------
+    activities
+        Activities of the two languages
+    parameters
+        Parameters
+
+    Returns
+    -------------
+    encoding
+        Encoding into hex characters
+    """
+    if parameters is None:
+        parameters = {}
+
+    shared_obj = SharedObj()
+    ret = {}
+    for act in activities:
+        get_new_char(act, shared_obj)
+        ret[act] = shared_obj.mapping_dictio[act]
+
+    return ret
+
+
+def encode_two_languages(lang1, lang2, parameters=None):
+    """
+    Encode the two languages into hexadecimal strings
+
+    Parameters
+    --------------
+    lang1
+        Language 1
+    lang2
+        Language 2
+    parameters
+        Parameters of the algorithm
+
+    Returns
+    --------------
+    enc1
+        Encoding of the first language
+    enc2
+        Encoding of the second language
+    """
+    if parameters is None:
+        parameters = {}
+
+    all_activities = sorted(list(set(y for x in lang1 for y in x).union(set(y for x in lang2 for y in x))))
+    acts_corresp = get_act_correspondence(all_activities, parameters=parameters)
+
+    enc1 = {}
+    enc2 = {}
+
+    for k in lang1:
+        new_key = "".join(acts_corresp[i] for i in k)
+        enc1[new_key] = lang1[k]
+
+    for k in lang2:
+        new_key = "".join(acts_corresp[i] for i in k)
+        enc2[new_key] = lang2[k]
+
+    # each language should have the same keys, even if not present
+    for x in enc1:
+        if x not in enc2:
+            enc2[x] = 0.0
+
+    for x in enc2:
+        if x not in enc1:
+            enc1[x] = 0.0
+
+    enc1 = [(x, y) for x, y in enc1.items()]
+    enc2 = [(x, y) for x, y in enc2.items()]
+
+    # sort the keys in a decreasing way
+    enc1 = sorted(enc1, reverse=True, key=lambda x: x[0])
+    enc2 = sorted(enc2, reverse=True, key=lambda x: x[0])
+
+    return enc1, enc2
+
+
+def apply(lang1, lang2, parameters=None):
+    """
+    Calculates the EMD distance between the two stochastic languages
+
+    Parameters
+    -------------
+    lang1
+        First language
+    lang2
+        Second language
+    parameters
+        Parameters of the algorithm, including:
+            - Parameters.STRING_DISTANCE: function that accepts two strings and returns a distance
+
+    Returns
+    ---------------
+    emd_dist
+        EMD distance
+    """
+    if parameters is None:
+        parameters = {}
+
+    distance_function = exec_utils.get_param_value(Parameters.STRING_DISTANCE, parameters, normalized_levensthein)
+
+    enc1, enc2 = encode_two_languages(lang1, lang2, parameters=parameters)
+
+    # transform everything into a numpy array
+    first_histogram = np.array([x[1] for x in enc1])
+    second_histogram = np.array([x[1] for x in enc2])
+
+    # including a distance matrix that includes the distance between
+    # the traces
+    distance_matrix = []
+    for x in enc1:
+        distance_matrix.append([])
+        for y in enc2:
+            # calculates the (normalized) distance between the strings
+            dist = distance_function(x[0], y[0])
+            distance_matrix[-1].append(float(dist))
+
+    distance_matrix = np.array(distance_matrix)
+
+    ret = emd(first_histogram, second_histogram, distance_matrix)
+
+    return ret
diff --git a/src/evaluation/evaluator.py b/src/evaluation/evaluator.py
new file mode 100644
index 0000000..b75264b
--- /dev/null
+++ b/src/evaluation/evaluator.py
@@ -0,0 +1,114 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py import util as pmutil
+from pm4py.algo.conformance.tokenreplay.variants import token_replay
+from evaluation.generalization.variants import token_based as generalization_token_based
+from evaluation.precision.variants import etconformance_token as precision_token_based
+from evaluation.replay_fitness.variants import token_replay as fitness_token_based
+from evaluation.simplicity.variants import arc_degree as simplicity_arc_degree
+from pm4py.objects import log as log_lib
+from pm4py.objects.conversion.log import converter as log_conversion
+from pm4py.util import xes_constants as xes_util
+from pm4py.util import constants
+from enum import Enum
+from pm4py.util import exec_utils
+import deprecation
+from pm4py.meta import VERSION
+import warnings
+
+
+class Parameters(Enum):
+    ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+    PARAM_FITNESS_WEIGHT = 'fitness_weight'
+    PARAM_PRECISION_WEIGHT = 'precision_weight'
+    PARAM_SIMPLICITY_WEIGHT = 'simplicity_weight'
+    PARAM_GENERALIZATION_WEIGHT = 'generalization_weight'
+
+
+@deprecation.deprecated(deprecated_in="2.2.5", removed_in="3.0",
+                        current_version=VERSION,
+                        details="Use the pm4py.algo.evaluation.evaluator class")
+def apply(log, net, initial_marking, final_marking, parameters=None):
+    """
+    Calculates all metrics based on token-based replay and returns a unified dictionary
+
+    Parameters
+    -----------
+    log
+        Log
+    net
+        Petri net
+    initial_marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Parameters
+
+    Returns
+    -----------
+    dictionary
+        Dictionary containing fitness, precision, generalization and simplicity; along with the average weight of
+        these metrics
+    """
+    warnings.warn("Use the pm4py.algo.evaluation.evaluator class")
+    if parameters is None:
+        parameters = {}
+    log = log_conversion.apply(log, parameters, log_conversion.TO_EVENT_LOG)
+
+    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, log_lib.util.xes.DEFAULT_NAME_KEY)
+    fitness_weight = exec_utils.get_param_value(Parameters.PARAM_FITNESS_WEIGHT, parameters, 0.25)
+    precision_weight = exec_utils.get_param_value(Parameters.PARAM_PRECISION_WEIGHT, parameters, 0.25)
+    simplicity_weight = exec_utils.get_param_value(Parameters.PARAM_SIMPLICITY_WEIGHT, parameters, 0.25)
+    generalization_weight = exec_utils.get_param_value(Parameters.PARAM_GENERALIZATION_WEIGHT, parameters, 0.25)
+
+    sum_of_weights = (fitness_weight + precision_weight + simplicity_weight + generalization_weight)
+    fitness_weight = fitness_weight / sum_of_weights
+    precision_weight = precision_weight / sum_of_weights
+    simplicity_weight = simplicity_weight / sum_of_weights
+    generalization_weight = generalization_weight / sum_of_weights
+
+    parameters_tr = {token_replay.Parameters.ACTIVITY_KEY: activity_key}
+
+    aligned_traces = token_replay.apply(log, net, initial_marking, final_marking, parameters=parameters_tr)
+
+    parameters = {
+        token_replay.Parameters.ACTIVITY_KEY: activity_key
+    }
+
+    fitness = fitness_token_based.evaluate(aligned_traces)
+    precision = precision_token_based.apply(log, net, initial_marking, final_marking, parameters=parameters)
+    generalization = generalization_token_based.get_generalization(net, aligned_traces)
+    simplicity = simplicity_arc_degree.apply(net)
+
+    metrics_average_weight = fitness_weight * fitness["log_fitness"] + precision_weight * precision \
+                             + generalization_weight * generalization + simplicity_weight * simplicity
+
+    fscore = 0.0
+    if (fitness['log_fitness'] + precision) > 0:
+        fscore = (2*fitness['log_fitness']*precision)/(fitness['log_fitness']+precision)
+    dictionary = {
+        "fitness": fitness,
+        "precision": precision,
+        "generalization": generalization,
+        "simplicity": simplicity,
+        "metricsAverageWeight": metrics_average_weight,
+        "fscore": fscore
+    }
+
+    return dictionary
+
diff --git a/src/evaluation/generalization/__init__.py b/src/evaluation/generalization/__init__.py
new file mode 100644
index 0000000..9972d59
--- /dev/null
+++ b/src/evaluation/generalization/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.generalization import evaluator, variants
diff --git a/src/evaluation/generalization/__pycache__/__init__.cpython-310.pyc b/src/evaluation/generalization/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ed7ee7c7324415daeac154af07fa7e1f6c8b6949
GIT binary patch
literal 998
zcmd1j<>g{vU|{gL?wc;j%)sy%#6iYP3=9ko3=9m#5)2FsDGX5zDU2yhIgGhXQB1ka
zQOt}CDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^
z6+H9O@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL
z&QB{TPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQo
znwOGVq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs
z&PYvBNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U
z7lEClkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZ
zg``x4(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLe
zGII-ZGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVu
zI4T4<28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0
zh2o6-(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)E
zFTX@bp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i+HJNU4rj{k<lqQzs7v170
zODxJv%quDO(`32D9v`2QpBx__B?(oKnV+YZ4oOIvRp9hc1j;r=%nS?+D;bJd85khM
zuLS+j;?$yI{o?ZcoHTux)Z*-t`~v;l%)HFJ^!W7568+-Rg47~isAamT6$Lr@MX5#l
v#YM^b2)p!A?b45r&&<m#iI3MSsJz8tlbfGXnv-hB2+Ff83=9k`3?c#m;UhRf

literal 0
HcmV?d00001

diff --git a/src/evaluation/generalization/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/generalization/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bb0b604a58bc366ccb62df039bb9564813e4a89c
GIT binary patch
literal 1914
zcmd1j<>g{vU|?vx?VBFX&cN^(#6iYv3=9ko3=9m#6Brm6QW&BbQW#U1au{=&qL>&#
zY~~#1T$U(SFq<WZEtfrt9n5CU;mGBT;smqVa=4<nQW#U%bGUPPqIjTUyivUF3@IEb
zoGlC~oT=>1%u#%)d|CWa{HgpIj8OuqTv>uCTq)eCJSjXWyuB<bY^nTdOeuUR{4I>l
zj8Q@<Y{3kg0*S|&7#O$|6ciLfGBS%5(lT>W6*7wz3KEM-6!Oy)0(?yZDit(*^NUgy
zJoD1>trRj!N(zdt^z{pJO$sXY(lSf*(uxvG^D^?&Qj7FbQZ@Csz@|Yof=nz*O;sq)
zPb(=;EK0RfsLU@_NKVXCC`wJqEG{X^Oe!r&Rmdz+NX$#o&o5HQ%}>cps|1^nS)x#y
zmy%kfP?C|VP?B1dTMTj#h~w@Ts^FfQms*sVqYzM<l#`jP;FFo0npd2vkXWox01_$A
zNKH{lf*1tS;s$a>Fw_+aZuzBoDTyVS`FT1DshK4isYME9sYS(^`FRS)FiW5&>L}zF
zft{m~SORi<kwSg}NTa4gVqT>}PGSi{FS^eW9!ycl%maHPBflV3p(G=*1mxxN%$yvB
zq*R5{;?%U#938MNNu?zU;hrHL{-GfXj((8};f_H;j(#DL)(YjBB^mjpB?_r!sSvX=
za|?1ZQ&SYm6N`!x^GYfe^3%XZ___u;dpP=qI68Uyc!oqO<QFNpd4~A81_vv+`3EUD
zDg-zNg?KuL`ZxwD1cU|!_y@b{DJTS|f*h6#HUUrgrh((AC{-aPwIne!rx@zz$ox`;
z;*9*#oD_wO#IjU{qSWNn%(B!Jg+ztq`~r~Ez~0AgFxcM2ocz3Wuty>3PESF>Gfg2c
zzeGo&I5kzl2A1^7%ggoB^Gfydi_-OTAQ4}zZ>Pug5|r;W8E<iy<Y%Ym#U~{er>1B!
z-r`Ko&nrtUDoHKUWW2@Vnpc{u$#{z^wIVe+zO*DWr`S)E@fLfSYf!MKzh5%Q2xQC%
z%f12(3=F9ZQH&`JQB0}KQ7kEp!3>&Aw>ZKQi!u}QN{an7S#NQ~$LA&HrpCwL;);*Y
z%}*)K0kL`F;|og@b09KBObiSRw?y1s{ak|_eLSNaLp=Tc;zRtsUHw)v6!9=HK!{(7
z`k}?CMaBBX<@q^j`Yx%(*(Lb}`nj2TnR)5)>6s<^#ia$QMY?4W+jUbb3Ucy`Qj7G9
zi<0$I%Mx=+!O2@c9g<Bls~}vcOn#AGL1hs;0|SF3sAOVcU|`^2;9vxcRIwWA8R?l;
zF&pa{{4xnG1}6&zNN!KeNzd1Vnvt2Gr-y2~LP273c4B&}Cd)0}l+=Qv)a1mH)Rg$l
zyjxsFsk!-O5H^2uX;D#XUP(MO$K7IwCZ%LhNeK&j5F6w^XOR1B7#J8z7#1+5FfL?d
zWT;_DVJKxN;?88KWhr4=z+A(+kTHv;gmnQ+4MPn}32PSHLgpIA8m1cN8kQ8MUe;g+
zO=iC$J_ZH`P_!2$7A5AUmZTOHYqH<sC{HW`1!(at77(k56%;qz>8U00&~UxQnpjYf
zQ+bOoCqF$Nl8iv5#4Vl>|9IChSHF;WAAfgEwp+|O`RTVf3sOspGUM}7OK$OJ=4F;-
zCg#NFCKhF9=B3}_P0P$faEpXN7O_JuLBx6yFDU-S85kH8LH-80nTv&kk%du$iHVVo
zk?mg*NKRAu7JGbrN`7*De3TN-*wBNTUJOZbGLRU|Ps&P7E-BVS_(d-#KfQ<<WUmCs
zQ7ox>rMX3*6mg3Sq6L&kpdkZ`)y(`nm~3upNn#Nwwu(eRW`N?Xh#O=O2Q)_o-{OL4
zOHBd0AIZPq^bYd$Ee;z<ez5~(xMBqc1_l-e9swRk5aeLwVdP*GU}9roW8`3FVq{`u
I`6t2*0Jd{1-v9sr

literal 0
HcmV?d00001

diff --git a/src/evaluation/generalization/__pycache__/parameters.cpython-310.pyc b/src/evaluation/generalization/__pycache__/parameters.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c70a616dcfe5f26650d3e56dc5030e06ccd18456
GIT binary patch
literal 1230
zcmd1j<>g{vU|{e!5KAv&W?*;>;vi!d1_lNP1_p*=0|o|$6ox2<6vh;$9L8LxC?-Y_
zn>mL$mnDjYk-?oIg(Zcxg&~DCm8qFIiZz8Tm_d_0@i-F$1DAq=f<j0}X0bwAW=^U?
zX0bv+Vo`}gewspnuSr0qf`)H?QL2JxURu7DLPkkRL9vy-enGBDL8V?=W{F-}QDSLc
zMt)jqkzPuwrXCmAG>Ar!iAAZY3dQ+pCFO}lsa6V=`K1cUiFpb|sVSMoB}JJ@r6s8f
znI#H|c`5q&MGCq3DVb@NU=uP+6iV|_Qi~KyGEx;vQj2nnK@I|O-2Flo+*9*XixP7b
z0!ou|GLsd2GLuvDic=L5ixmn$BE=c0DGEstgFsr`K&}Xex<bJ%zceo;u_QA;Pe&m&
zvm_(6NTDpXs5mn}Pr(>w3DiU#h5RD0b2Ji5K#ng`$S(kC)Ko~!t5nEIEJ5f+_c_9Y
zDGHf+U~gpP7o;kbWF(e=yj-4{lcSK7s!&>-npT>l1GXipv_v7?GsMF`G(^GCFH#}g
zF(}B<FC@}hp**uBBfqpnA+;<OVpe8uK~83Bib8o}QBh)ENu@%58rTS5*C1yPN52q9
zCr=;GkVu96A_X_k5I@)8U<Eh-AO%N-0LP#ZPv=k{#~_7((4YYSU{^f_h2T_>!&1Q}
z;0fO}a2yq-Dx{>ABxdFmL;W0?U#d`?kzbmVqL7hTma0&cnw*+hmYSlFsF0jr0CF1G
z`?w7T+nbn^pO+5yC?wtKDJXcRDdgps=qMDYrYhLLl3savxn6o+sa}3jx_%BM;*0g|
z^tfJv@`WbjEf&|j(p*i(Tb#-HdBr7(c_qckAQ@!L2+K7>3=9mZ3{i|J3{gy}%vmf^
ztSOAa44O>0xB?Q35_3~aQj3cHG}&%(#K-3)=BCES-{Ojo&&^LM%>l7_;^PZT6LTOk
zpzO&IB_H4z<ml@f;u;k1?C%#G;^-F=@8}%j8Ri)h8Sm{H8O4LlTggzw!N33^e&y+h
z7N-^!>lc^j=cMW9X69w4<rk%=8tbR#l@){ec_0rb=@(R%WaQ@=>lqs87iX5F>J}s>
zXD6no7DF<nerj1_PANEf>!(AqNoEy<TLAZyUO{CMsOS&{6&)Z32Ll%)SOnx-_W1ae
z{N(ufTP&%0rMX2+3=9lWT#y`JT9TOq_9<8~!u1dqCj$e+Ee;z<0NH^;xftX>76u*x
I9!3#n0QS9RumAu6

literal 0
HcmV?d00001

diff --git a/src/evaluation/generalization/evaluator.py b/src/evaluation/generalization/evaluator.py
new file mode 100644
index 0000000..d183420
--- /dev/null
+++ b/src/evaluation/generalization/evaluator.py
@@ -0,0 +1,43 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.generalization.variants import token_based
+from pm4py.objects.conversion.log import converter as log_conversion
+from enum import Enum
+from pm4py.util import exec_utils
+import deprecation
+from pm4py.meta import VERSION
+import warnings
+
+class Variants(Enum):
+    GENERALIZATION_TOKEN = token_based
+
+
+GENERALIZATION_TOKEN = Variants.GENERALIZATION_TOKEN
+VERSIONS = {GENERALIZATION_TOKEN}
+
+
+@deprecation.deprecated(deprecated_in="2.2.5", removed_in="3.0",
+                        current_version=VERSION,
+                        details="Use the pm4py.algo.evaluation.generalization package")
+def apply(log, petri_net, initial_marking, final_marking, parameters=None, variant=GENERALIZATION_TOKEN):
+    warnings.warn("Use the pm4py.algo.evaluation.generalization package")
+    if parameters is None:
+        parameters = {}
+
+    return exec_utils.get_variant(variant).apply(log_conversion.apply(log, parameters, log_conversion.TO_EVENT_LOG),
+                                                 petri_net,
+                                                 initial_marking, final_marking, parameters=parameters)
diff --git a/src/evaluation/generalization/parameters.py b/src/evaluation/generalization/parameters.py
new file mode 100644
index 0000000..6475c53
--- /dev/null
+++ b/src/evaluation/generalization/parameters.py
@@ -0,0 +1,22 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from enum import Enum
+from pm4py.util import constants
+
+
+class Parameters(Enum):
+    ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
diff --git a/src/evaluation/generalization/variants/__init__.py b/src/evaluation/generalization/variants/__init__.py
new file mode 100644
index 0000000..0e206b4
--- /dev/null
+++ b/src/evaluation/generalization/variants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.generalization.variants import token_based
diff --git a/src/evaluation/generalization/variants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/generalization/variants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..435bab56c97036f7ddcb37d1dc3d184ee6f81bc5
GIT binary patch
literal 999
zcmd1j<>g{vU|{IE?VB#e%)sy%#6iYP3=9ko3=9m#A`A=+DGX5zDU2yhIgGhXQA~^s
zDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^6+H9O
z@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL&QB{T
zPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQonwOGV
zq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs&PYvB
zNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U7lECl
zkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZg``x4
z(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLeGII-Z
zGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVuI4T4<
z28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0h2o6-
z(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)EFTX@b
zp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i!H5qSlm*i)s=EWx^7N@58X)@np
zkB?8uPmYhjrIcEhm{SUlc)fH;0?Mp{aLW>lG86Mkii<#bW+g)rD+2?B_?4+2TAW%`
ztY2K7pOdEVl3JWyl3$>oo0*rHmmZ&<S)yNDT98_#3pGGDwW1&=zbLgxzqlw_A7P(9
ss(t!U<;D8(@tJv<CGqik1(mlrY;yBcN^?@}7(tnqg@J*Ag+V|70PV>;VE_OC

literal 0
HcmV?d00001

diff --git a/src/evaluation/generalization/variants/__pycache__/token_based.cpython-310.pyc b/src/evaluation/generalization/variants/__pycache__/token_based.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..033d3985221fb278a3ef089c3d878b816d9b370e
GIT binary patch
literal 4125
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
I4rV@m0Gjc_vH$=8

literal 0
HcmV?d00001

diff --git a/src/evaluation/generalization/variants/token_based.py b/src/evaluation/generalization/variants/token_based.py
new file mode 100644
index 0000000..83f098c
--- /dev/null
+++ b/src/evaluation/generalization/variants/token_based.py
@@ -0,0 +1,115 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from collections import Counter
+from math import sqrt
+
+from pm4py import util as pmutil
+from pm4py.algo.conformance.tokenreplay import algorithm as token_replay
+from evaluation.generalization.parameters import Parameters
+from pm4py.util import exec_utils
+
+
+def get_generalization(petri_net, aligned_traces):
+    """
+    Gets the generalization from the Petri net and the list of activated transitions
+    during the replay
+
+    The approach has been suggested by the paper
+    Buijs, Joos CAM, Boudewijn F. van Dongen, and Wil MP van der Aalst. "Quality dimensions in process discovery:
+    The importance of fitness, precision, generalization and simplicity."
+    International Journal of Cooperative Information Systems 23.01 (2014): 1440001.
+
+    A token replay is applied and, for each transition, we can measure the number of occurrences
+    in the replay. The following formula is applied for generalization
+
+           \sum_{t \in transitions} (math.sqrt(1.0/(n_occ_replay(t)))
+    1 -    ----------------------------------------------------------
+                             # transitions
+
+    Parameters
+    -----------
+    petri_net
+        Petri net
+    aligned_traces
+        Result of the token-replay
+
+    Returns
+    -----------
+    generalization
+        Generalization measure
+    """
+
+    trans_occ_map = Counter()
+    for trace in aligned_traces:
+        for trans in trace["activated_transitions"]:
+            trans_occ_map[trans] += 1
+    inv_sq_occ_sum = 0.0
+    for trans in trans_occ_map:
+        this_term = 1.0 / sqrt(trans_occ_map[trans])
+        inv_sq_occ_sum = inv_sq_occ_sum + this_term
+    for trans in petri_net.transitions:
+        if trans not in trans_occ_map:
+            inv_sq_occ_sum = inv_sq_occ_sum + 1
+    generalization = 1.0
+    if len(petri_net.transitions) > 0:
+        generalization = 1.0 - inv_sq_occ_sum / float(len(petri_net.transitions))
+    return generalization
+
+
+def apply(log, petri_net, initial_marking, final_marking, parameters=None):
+    """
+    Calculates generalization on the provided log and Petri net.
+
+    The approach has been suggested by the paper
+    Buijs, Joos CAM, Boudewijn F. van Dongen, and Wil MP van der Aalst. "Quality dimensions in process discovery:
+    The importance of fitness, precision, generalization and simplicity."
+    International Journal of Cooperative Information Systems 23.01 (2014): 1440001.
+
+    A token replay is applied and, for each transition, we can measure the number of occurrences
+    in the replay. The following formula is applied for generalization
+
+           \sum_{t \in transitions} (math.sqrt(1.0/(n_occ_replay(t)))
+    1 -    ----------------------------------------------------------
+                             # transitions
+
+    Parameters
+    -----------
+    log
+        Trace log
+    petri_net
+        Petri net
+    initial_marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Algorithm parameters
+
+    Returns
+    -----------
+    generalization
+        Generalization measure
+    """
+    if parameters is None:
+        parameters = {}
+    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, pmutil.xes_constants.DEFAULT_NAME_KEY)
+
+    parameters_tr = {Parameters.ACTIVITY_KEY: activity_key}
+
+    aligned_traces = token_replay.apply(log, petri_net, initial_marking, final_marking, parameters=parameters_tr)
+
+    return get_generalization(petri_net, aligned_traces)
diff --git a/src/evaluation/precision/__init__.py b/src/evaluation/precision/__init__.py
new file mode 100644
index 0000000..57ae427
--- /dev/null
+++ b/src/evaluation/precision/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.precision import evaluator, variants
diff --git a/src/evaluation/precision/__pycache__/__init__.cpython-310.pyc b/src/evaluation/precision/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8b3121b84a71b90a3a92e6be05b94631703bc53f
GIT binary patch
literal 988
zcmd1j<>g{vU|{gS?wc;c%)sy%#6iYP3=9ko3=9m#5)2FsDGX5zDU2yhIgGhXQB1ka
zQOt}CDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^
z6+H9O@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL
z&QB{TPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQo
znwOGVq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs
z&PYvBNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U
z7lEClkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZ
zg``x4(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLe
zGII-ZGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVu
zI4T4<28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0
zh2o6-(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)E
zFTX@bp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i+HJNU4rj{k<lqQzs7v170
zODxJv%quDO(`32D9v`2QpBx__B?48DnV+XuP?VaS35xn6P>v~LW?*1g$xy_~zyKkB
zMeBzarxq3K7nkSfr0Kh)7H5~_7wG3^=4Ixk$ERnO=ogn3q!#Hy?a@uGD9Fh#N-fea
vE=tx%SfdZOMn672GcU6wK3=b&@)n0pZhlH>PO2RvD5tV8Ffgz%hzI}xXOJ`*

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/precision/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..325520fd35997d4eb08db90fcce60568eeeacea2
GIT binary patch
literal 2647
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

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/__pycache__/parameters.cpython-310.pyc b/src/evaluation/precision/__pycache__/parameters.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a26c6ebefedde9cfb6659519a1ae09993a423893
GIT binary patch
literal 1437
zcmd1j<>g{vU|{e!5KFgbVPJR+;vi!d1_lNP1_p*=8wLi36ox2<6vh;$9L8LxC?-Y_
zn>mL$mnDh?%x1}9&1H*XV`Ok=NMTK3Yhg%XOJ!<ij$%(?4`$HhNIcHOz`&)Tpr8<v
zky)&emYI{PkXfuykXTfrke{Xy;A;|4si5JTUzDognU|JtrI1llQc!HAuV0XBQc$Uv
zmRX{gR+Lzpmyw^ATBMhfs;S2XHVvW?WMWZjszPyoT1k0gQL2?fWqzqba$=rBQEEzN
zaY<2TQfW!5LS~6VVqS`Vevv|MeoAIqCD??_5{1&dl++@Hl8jV^lGLKyVvvJC9CyD^
z1^3jv)S|>3g@DqeoXlhepUmXcyy8@a#A1a4kVtVxYKlS<#2}CsH;^lWp{`JH%P-AK
zNi50C&(l#z%`C}CEm9~;Eh^5;&r>jlSpqdtM<Krm>>Q265|HDI6!Hr|8Z{LX^C}f`
z5=#(z(S45aV2VO!9@rZh`30#8B^ik&ATO6^=Hw_Or7Dybr>2$W=zwiWDlJh6_YCpy
z4-HXp^ovvocMJ-0^b3i!Rw&Ob$;dA)QAjOIg_xC@Tac5PnxasiSX7jlS5m2vp9VI<
z*EPu5!_hCq(aF=tGbB<WzevH&GsMp|I9S2WKS;q*A;2*x#M3#{$1zADAT%hzKiE}I
zK_NI5<giq*33$Rc4ID>BsR}8nC5f3i#ZW&-=9elIXXKaWq$p%0mZd5br6#9lmZhdB
zBq}857l51w_C9Wd!S*KR<maV>Jqk&8dI}1jX$pDyB{~Ylsi_J!u%uUBUaps(SE`p^
zl&+rxiTGlDJ3X$Kpgf|<c#Fj~uQXSa@fK%teqM1&VqQrxh?AI;o?nz%l98JXQihBf
zVcAKJfq@~FA&N1DA&M!LIg2HVHH9&XErltHJ%u@nBZVcHL6h|sS3qJ>Vs2_lYEf~N
zNJ)NnYF>O%YC%q7Wqes;QD$OZNt8%(PHJLaW?p(cL^>@eKR+c(us9>XJieePKfNfm
zxHvv3vB*!8^A<;Zd|qO1YJB`HuK4)e{FKrh5Su4HzOXbg2O?9%%)r19B_H4z<ml@f
z;u;k1?C%#G;^-F=@8}%j8Ri)h8Sm{H8O4Lb6AAJ6cJ+%7at-itjEoO+4DxjJ3yBhO
z_HlLe^Yn9%he*5m`1`v=2?l%khsOs5`MU?X1_#GGIR>p{DB@&bfDpej^+StOi;DG&
z%ky*6^m8-wGSl*l(o>D~Q}fD-!TdZ>JS6ECRF-7q=Nao68t4~imZa(yBqnDkrl%G|
z$_o9|vc#NHaIVuYC`wJv1m!pV0(fBR6;u|nGB7ZRfeJQI`NzS)#>m3R!pOl07WdQS
zxWyhHpOT*(AAgG_HLo<chzS%hT#%Bev?MbJ5>iSK0Z@GFCFkd*<rn29<|U`<fg=MF
nRYjl@6l^NOpAZ(v;#(XxkeILorHo>b5DNp301qP%qX;tq{<Ej}

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/__pycache__/utils.cpython-310.pyc b/src/evaluation/precision/__pycache__/utils.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0becb8d68747d016c028d4a88181f84341f3af3e
GIT binary patch
literal 4170
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

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/evaluator.py b/src/evaluation/precision/evaluator.py
new file mode 100644
index 0000000..b3b6409
--- /dev/null
+++ b/src/evaluation/precision/evaluator.py
@@ -0,0 +1,82 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.precision.variants import etconformance_token, align_etconformance
+from pm4py.objects.conversion.log import converter as log_conversion
+from pm4py.objects.petri_net.utils.check_soundness import check_easy_soundness_net_in_fin_marking
+from enum import Enum
+from pm4py.util import exec_utils
+import deprecation
+from pm4py.meta import VERSION
+import warnings
+
+
+class Variants(Enum):
+    ETCONFORMANCE_TOKEN = etconformance_token
+    ALIGN_ETCONFORMANCE = align_etconformance
+
+
+ETCONFORMANCE_TOKEN = Variants.ETCONFORMANCE_TOKEN
+ALIGN_ETCONFORMANCE = Variants.ALIGN_ETCONFORMANCE
+
+VERSIONS = {ETCONFORMANCE_TOKEN, ALIGN_ETCONFORMANCE}
+
+@deprecation.deprecated(deprecated_in="2.2.5", removed_in="3.0",
+                        current_version=VERSION,
+                        details="Use the pm4py.algo.evaluation.precision package")
+def apply(log, net, marking, final_marking, parameters=None, variant=None):
+    """
+    Method to apply ET Conformance
+
+    Parameters
+    -----------
+    log
+        Trace log
+    net
+        Petri net
+    marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Parameters of the algorithm, including:
+            pm4py.util.constants.PARAMETER_CONSTANT_ACTIVITY_KEY -> Activity key
+    variant
+        Variant of the algorithm that should be applied:
+            - Variants.ETCONFORMANCE_TOKEN
+            - Variants.ALIGN_ETCONFORMANCE
+    """
+    warnings.warn("Use the pm4py.algo.evaluation.precision package")
+
+    if parameters is None:
+        parameters = {}
+
+    log = log_conversion.apply(log, parameters, log_conversion.TO_EVENT_LOG)
+
+    # execute the following part of code when the variant is not specified by the user
+    if variant is None:
+        if not (check_easy_soundness_net_in_fin_marking(
+                net,
+                marking,
+                final_marking)):
+            # in the case the net is not a easy sound workflow net, we must apply token-based replay
+            variant = ETCONFORMANCE_TOKEN
+        else:
+            # otherwise, use the align-etconformance approach (safer, in the case the model contains duplicates)
+            variant = ALIGN_ETCONFORMANCE
+
+    return exec_utils.get_variant(variant).apply(log, net, marking,
+                             final_marking, parameters=parameters)
diff --git a/src/evaluation/precision/parameters.py b/src/evaluation/precision/parameters.py
new file mode 100644
index 0000000..fcdbd4c
--- /dev/null
+++ b/src/evaluation/precision/parameters.py
@@ -0,0 +1,26 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from enum import Enum
+from pm4py.util import constants
+from pm4py.algo.conformance.tokenreplay import algorithm
+
+
+class Parameters(Enum):
+    ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+    TOKEN_REPLAY_VARIANT = "token_replay_variant"
+    CLEANING_TOKEN_FLOOD = "cleaning_token_flood"
+    SHOW_PROGRESS_BAR = "show_progress_bar"
diff --git a/src/evaluation/precision/utils.py b/src/evaluation/precision/utils.py
new file mode 100644
index 0000000..c08cc20
--- /dev/null
+++ b/src/evaluation/precision/utils.py
@@ -0,0 +1,148 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from collections import Counter
+from pm4py.objects.log.obj import EventLog, Event, Trace
+from pm4py.util import xes_constants as xes_util, constants
+import heapq
+from pm4py.objects.petri_net.utils.petri_utils import decorate_places_preset_trans, decorate_transitions_prepostset
+from pm4py.objects.petri_net.utils import align_utils as utils
+from pm4py.objects.petri_net.utils.incidence_matrix import construct
+
+def __search(sync_net, ini, fin, stop, cost_function, skip):
+    decorate_transitions_prepostset(sync_net)
+    decorate_places_preset_trans(sync_net)
+
+    incidence_matrix = construct(sync_net)
+    ini_vec, fin_vec, cost_vec = utils.__vectorize_initial_final_cost(incidence_matrix, ini, fin, cost_function)
+
+    closed = set()
+
+    ini_state = utils.SearchTuple(0, 0, 0, ini, None, None, None, True)
+    open_set = [ini_state]
+    heapq.heapify(open_set)
+    visited = 0
+    queued = 0
+    traversed = 0
+
+    # return all the prefix markings of the optimal alignments as set
+    ret_markings = None
+    # keep track of the optimal cost of an alignment (to trim search when needed)
+    optimal_cost = None
+
+    while not len(open_set) == 0:
+        curr = heapq.heappop(open_set)
+
+        current_marking = curr.m
+
+        # trim alignments when we already reached an optimal alignment and the
+        # current cost is greater than the optimal cost
+        if optimal_cost is not None and curr.f > optimal_cost:
+            break
+
+        already_closed = current_marking in closed
+        if already_closed:
+            continue
+
+        if stop <= current_marking:
+            # add the current marking to the set
+            # of returned markings
+            if ret_markings is None:
+                ret_markings = set()
+            ret_markings.add(current_marking)
+            # close the marking
+            closed.add(current_marking)
+            # set the optimal cost
+            optimal_cost = curr.f
+
+            continue
+
+        closed.add(current_marking)
+        visited += 1
+
+        enabled_trans = set()
+        for p in current_marking:
+            for t in p.ass_trans:
+                if t.sub_marking <= current_marking:
+                    enabled_trans.add(t)
+
+        trans_to_visit_with_cost = [(t, cost_function[t]) for t in enabled_trans if
+                                    not (t is None or utils.__is_log_move(t, skip) or (
+                                            utils.__is_model_move(t, skip) and not t.label[1] is None))]
+
+        for t, cost in trans_to_visit_with_cost:
+            traversed += 1
+            new_marking = utils.add_markings(current_marking, t.add_marking)
+
+            if new_marking in closed:
+                continue
+            g = curr.g + cost
+
+            queued += 1
+            new_f = g
+
+            tp = utils.SearchTuple(new_f, g, 0, new_marking, curr, t, None, True)
+            heapq.heappush(open_set, tp)
+
+    return ret_markings
+
+
+def get_log_prefixes(log, activity_key=xes_util.DEFAULT_NAME_KEY):
+    """
+    Get log prefixes
+
+    Parameters
+    ----------
+    log
+        Trace log
+    activity_key
+        Activity key (must be provided if different from concept:name)
+    """
+    prefixes = {}
+    prefix_count = Counter()
+    for trace in log:
+        for i in range(1, len(trace)):
+            red_trace = trace[0:i]
+            prefix = constants.DEFAULT_VARIANT_SEP.join([x[activity_key] for x in red_trace])
+            next_activity = trace[i][activity_key]
+            if prefix not in prefixes:
+                prefixes[prefix] = set()
+            prefixes[prefix].add(next_activity)
+            prefix_count[prefix] += 1
+    return prefixes, prefix_count
+
+
+def form_fake_log(prefixes_keys, activity_key=xes_util.DEFAULT_NAME_KEY):
+    """
+    Form fake log for replay (putting each prefix as separate trace to align)
+
+    Parameters
+    ----------
+    prefixes_keys
+        Keys of the prefixes (to form a log with a given order)
+    activity_key
+        Activity key (must be provided if different from concept:name)
+    """
+    fake_log = EventLog()
+    for prefix in prefixes_keys:
+        trace = Trace()
+        prefix_activities = prefix.split(constants.DEFAULT_VARIANT_SEP)
+        for activity in prefix_activities:
+            event = Event()
+            event[activity_key] = activity
+            trace.append(event)
+        fake_log.append(trace)
+    return fake_log
diff --git a/src/evaluation/precision/variants/__init__.py b/src/evaluation/precision/variants/__init__.py
new file mode 100644
index 0000000..b89f02b
--- /dev/null
+++ b/src/evaluation/precision/variants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.precision.variants import etconformance_token, align_etconformance
diff --git a/src/evaluation/precision/variants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/precision/variants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0f9b915c37691a80a420d566ad6dac8b6a5082f1
GIT binary patch
literal 1027
zcmd1j<>g{vU|`sG+c({anStRkh=Yuo7#J8F7#J9eB^VeOQW&BbQW#U1au{=&qL^}-
zqnH^PQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM1XL<$_~sX-
zDtP9l<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;PXat#9l$xqg
zoS#-wo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9nN|rlA+toG
zG%qE!NTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01RUxrhp#UUO
zoRONMkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHljA52QP1I4y
zF9JJ9Be4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOdVhPC0<(WA-
z3Q4I7rNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^OG^||%Tgg`
zW#$&-WTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiDa}5qwaPtpR
za8w9z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`BW==8G&yo41
z3dI@ur8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS(w&}yf@hjS
zUVe#=LUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?}tYckyuPAy5!&r8cM%1z8m
zPK__g&rZ#|C7hU(nVuJqBIT#aa*I7aJ|#anKK_<$YFT1VDL7T=6%?f=XM)m$URh#M
zW@27RaS<p#6)`g~Fsx)KVq;){5WiCNLyJ?3iuH@j^K;VlT~dp)OY#f!b2IZY^U~wf
zGfVV~OAAtqbfKo`rdAZ><QJtD=@%Cz>m%&bhufzQRavYbAD@|*SrQ+wS5SG2!zMRB
Ur8FnijuDihSr`}?SQtbE0L2AF?*IS*

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/variants/__pycache__/align_etconformance.cpython-310.pyc b/src/evaluation/precision/variants/__pycache__/align_etconformance.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4c0eb16b9bd78d4a434e6edbffd14de1d9db639c
GIT binary patch
literal 7289
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
ClouTU

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/variants/__pycache__/etconformance_token.cpython-310.pyc b/src/evaluation/precision/variants/__pycache__/etconformance_token.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..57bf8524fdc6a84f3d0311b3c316e8a7550c7016
GIT binary patch
literal 3513
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
WpkQWU;1S?q<YD4rgh3`IK4AdmK@2+p

literal 0
HcmV?d00001

diff --git a/src/evaluation/precision/variants/align_etconformance.py b/src/evaluation/precision/variants/align_etconformance.py
new file mode 100644
index 0000000..6412d37
--- /dev/null
+++ b/src/evaluation/precision/variants/align_etconformance.py
@@ -0,0 +1,274 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.objects import log as log_lib
+from evaluation.precision import utils as precision_utils
+from pm4py.objects.petri_net.utils import align_utils as utils, check_soundness
+from pm4py.objects.petri_net.obj import Marking
+from pm4py.objects.petri_net.utils.petri_utils import construct_trace_net
+from pm4py.objects.petri_net.utils.synchronous_product import construct
+from pm4py.statistics.start_activities.log.get import get_start_activities
+from pm4py.objects.petri_net.utils.align_utils import get_visible_transitions_eventually_enabled_by_marking
+from evaluation.precision.parameters import Parameters
+from pm4py.util import exec_utils
+from pm4py.util import xes_constants
+import pkgutil
+
+
+def apply(log, net, marking, final_marking, parameters=None):
+    """
+    Get Align-ET Conformance precision
+
+    Parameters
+    ----------
+    log
+        Trace log
+    net
+        Petri net
+    marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Parameters of the algorithm, including:
+            Parameters.ACTIVITY_KEY -> Activity key
+    """
+
+    if parameters is None:
+        parameters = {}
+
+    debug_level = parameters["debug_level"] if "debug_level" in parameters else 0
+
+    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, log_lib.util.xes.DEFAULT_NAME_KEY)
+
+    # default value for precision, when no activated transitions (not even by looking at the initial marking) are found
+    precision = 1.0
+    sum_ee = 0
+    sum_at = 0
+    unfit = 0
+
+    if not check_soundness.check_easy_soundness_net_in_fin_marking(net, marking, final_marking):
+        raise Exception("trying to apply Align-ETConformance on a Petri net that is not a easy sound net!!")
+
+    prefixes, prefix_count = precision_utils.get_log_prefixes(log, activity_key=activity_key)
+    prefixes_keys = list(prefixes.keys())
+    fake_log = precision_utils.form_fake_log(prefixes_keys, activity_key=activity_key)
+
+    align_stop_marking = align_fake_log_stop_marking(fake_log, net, marking, final_marking, parameters=parameters)
+    all_markings = transform_markings_from_sync_to_original_net(align_stop_marking, net, parameters=parameters)
+
+    for i in range(len(prefixes)):
+        markings = all_markings[i]
+
+        if markings is not None:
+            log_transitions = set(prefixes[prefixes_keys[i]])
+            activated_transitions_labels = set()
+            for m in markings:
+                # add to the set of activated transitions in the model the activated transitions
+                # for each prefix
+                activated_transitions_labels = activated_transitions_labels.union(
+                    x.label for x in utils.get_visible_transitions_eventually_enabled_by_marking(net, m) if
+                    x.label is not None)
+            escaping_edges = activated_transitions_labels.difference(log_transitions)
+
+            sum_at += len(activated_transitions_labels) * prefix_count[prefixes_keys[i]]
+            sum_ee += len(escaping_edges) * prefix_count[prefixes_keys[i]]
+
+            if debug_level > 1:
+                print("")
+                print("prefix=", prefixes_keys[i])
+                print("log_transitions=", log_transitions)
+                print("activated_transitions=", activated_transitions_labels)
+                print("escaping_edges=", escaping_edges)
+        else:
+            unfit += prefix_count[prefixes_keys[i]]
+
+    if debug_level > 0:
+        print("\n")
+        print("overall unfit", unfit)
+        print("overall activated transitions", sum_at)
+        print("overall escaping edges", sum_ee)
+
+    # fix: also the empty prefix should be counted!
+    start_activities = set(get_start_activities(log, parameters=parameters))
+    trans_en_ini_marking = set([x.label for x in get_visible_transitions_eventually_enabled_by_marking(net, marking)])
+    diff = trans_en_ini_marking.difference(start_activities)
+    sum_at += len(log) * len(trans_en_ini_marking)
+    sum_ee += len(log) * len(diff)
+    # end fix
+
+    if sum_at > 0:
+        precision = 1 - float(sum_ee) / float(sum_at)
+
+    return precision
+
+
+def transform_markings_from_sync_to_original_net(markings0, net, parameters=None):
+    """
+    Transform the markings of the sync net (in which alignment stops) into markings of the original net
+    (in order to measure the precision)
+
+    Parameters
+    -------------
+    markings0
+        Markings on the sync net (expressed as place name with count)
+    net
+        Petri net
+    parameters
+        Parameters of the algorithm
+
+    Returns
+    -------------
+    markings
+        Markings of the original model (expressed as place with count)
+    """
+    if parameters is None:
+        parameters = {}
+
+    places_corr = {p.name: p for p in net.places}
+
+    markings = []
+
+    for i in range(len(markings0)):
+        res_list = markings0[i]
+
+        # res_list shall be a list of markings.
+        # If it is None, then there is no correspondence markings
+        # in the original Petri net
+        if res_list is not None:
+            # saves all the markings reached by the optimal alignment
+            # as markings of the original net
+            markings.append([])
+
+            for j in range(len(res_list)):
+                res = res_list[j]
+
+                atm = Marking()
+                for pl, count in res.items():
+                    if pl[0] == utils.SKIP:
+                        atm[places_corr[pl[1]]] = count
+                markings[-1].append(atm)
+        else:
+            markings.append(None)
+
+    return markings
+
+
+def align_fake_log_stop_marking(fake_log, net, marking, final_marking, parameters=None):
+    """
+    Align the 'fake' log with all the prefixes in order to get the markings in which
+    the alignment stops
+
+    Parameters
+    -------------
+    fake_log
+        Fake log
+    net
+        Petri net
+    marking
+        Marking
+    final_marking
+        Final marking
+    parameters
+        Parameters of the algorithm
+
+    Returns
+    -------------
+    alignment
+        For each trace in the log, return the marking in which the alignment stops (expressed as place name with count)
+    """
+    if parameters is None:
+        parameters = {}
+
+    show_progress_bar = exec_utils.get_param_value(Parameters.SHOW_PROGRESS_BAR, parameters, True)
+
+    align_result = []
+
+    progress = None
+    if pkgutil.find_loader("tqdm") and show_progress_bar and len(fake_log) > 1:
+        from tqdm.auto import tqdm
+        progress = tqdm(total=len(fake_log), desc="computing precision with alignments, completed variants :: ")
+
+    for i in range(len(fake_log)):
+        trace = fake_log[i]
+        sync_net, sync_initial_marking, sync_final_marking = build_sync_net(trace, net, marking, final_marking,
+                                                                            parameters=parameters)
+        stop_marking = Marking()
+        for pl, count in sync_final_marking.items():
+            if pl.name[1] == utils.SKIP:
+                stop_marking[pl] = count
+        cost_function = utils.construct_standard_cost_function(sync_net, utils.SKIP)
+
+        # perform the alignment of the prefix
+        res = precision_utils.__search(sync_net, sync_initial_marking, sync_final_marking, stop_marking, cost_function,
+                                       utils.SKIP)
+
+        if res is not None:
+            align_result.append([])
+            for mark in res:
+                res2 = {}
+                for pl in mark:
+                    # transforms the markings for easier correspondence at the end
+                    # (distributed engine friendly!)
+                    res2[(pl.name[0], pl.name[1])] = mark[pl]
+
+                align_result[-1].append(res2)
+        else:
+            # if there is no path from the initial marking
+            # replaying the given prefix, then add None
+            align_result.append(None)
+        if progress is not None:
+            progress.update()
+
+    # gracefully close progress bar
+    if progress is not None:
+        progress.close()
+    del progress
+
+    return align_result
+
+
+def build_sync_net(trace, petri_net, initial_marking, final_marking, parameters=None):
+    """
+    Build the sync product net between the Petri net and the trace prefix
+
+    Parameters
+    ---------------
+    trace
+        Trace prefix
+    petri_net
+        Petri net
+    initial_marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Possible parameters of the algorithm
+    """
+    if parameters is None:
+        parameters = {}
+
+    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, xes_constants.DEFAULT_NAME_KEY)
+
+    trace_net, trace_im, trace_fm = construct_trace_net(trace, activity_key=activity_key)
+
+    sync_prod, sync_initial_marking, sync_final_marking = construct(trace_net, trace_im,
+                                                                                              trace_fm, petri_net,
+                                                                                              initial_marking,
+                                                                                              final_marking,
+                                                                                              utils.SKIP)
+
+    return sync_prod, sync_initial_marking, sync_final_marking
diff --git a/src/evaluation/precision/variants/etconformance_token.py b/src/evaluation/precision/variants/etconformance_token.py
new file mode 100644
index 0000000..35ed6a4
--- /dev/null
+++ b/src/evaluation/precision/variants/etconformance_token.py
@@ -0,0 +1,113 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.algo.conformance.tokenreplay.variants import token_replay
+from pm4py.algo.conformance.tokenreplay import algorithm as executor
+
+from pm4py.objects import log as log_lib
+from evaluation.precision import utils as precision_utils
+from pm4py.statistics.start_activities.log.get import get_start_activities
+from pm4py.objects.petri_net.utils.align_utils import get_visible_transitions_eventually_enabled_by_marking
+from evaluation.precision.parameters import Parameters
+from pm4py.util import exec_utils
+
+"""
+Implementation of the approach described in paper
+
+Muñoz-Gama, Jorge, and Josep Carmona. "A fresh look at precision in process conformance." International Conference
+on Business Process Management. Springer, Berlin, Heidelberg, 2010.
+
+for measuring precision.
+
+For each prefix in the log, the reflected tasks are calculated (outgoing attributes from the prefix)
+Then, a token replay is done on the prefix in order to get activated transitions
+Escaping edges is the set difference between activated transitions and reflected tasks
+
+Then, precision is calculated by the formula used in the paper
+
+At the moment, the precision value is different from the one provided by the ProM plug-in,
+although the implementation seems to follow the paper concept
+"""
+
+
+def apply(log, net, marking, final_marking, parameters=None):
+    """
+    Get ET Conformance precision
+
+    Parameters
+    ----------
+    log
+        Trace log
+    net
+        Petri net
+    marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Parameters of the algorithm, including:
+            Parameters.ACTIVITY_KEY -> Activity key
+    """
+
+    if parameters is None:
+        parameters = {}
+
+    cleaning_token_flood = exec_utils.get_param_value(Parameters.CLEANING_TOKEN_FLOOD, parameters, False)
+    token_replay_variant = exec_utils.get_param_value(Parameters.TOKEN_REPLAY_VARIANT, parameters,
+                                                      executor.Variants.TOKEN_REPLAY)
+    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, log_lib.util.xes.DEFAULT_NAME_KEY)
+    # default value for precision, when no activated transitions (not even by looking at the initial marking) are found
+    precision = 1.0
+    sum_ee = 0
+    sum_at = 0
+
+    parameters_tr = {
+        token_replay.Parameters.CONSIDER_REMAINING_IN_FITNESS: False,
+        token_replay.Parameters.TRY_TO_REACH_FINAL_MARKING_THROUGH_HIDDEN: False,
+        token_replay.Parameters.STOP_IMMEDIATELY_UNFIT: True,
+        token_replay.Parameters.WALK_THROUGH_HIDDEN_TRANS: True,
+        token_replay.Parameters.CLEANING_TOKEN_FLOOD: cleaning_token_flood,
+        token_replay.Parameters.ACTIVITY_KEY: activity_key
+    }
+
+    prefixes, prefix_count = precision_utils.get_log_prefixes(log, activity_key=activity_key)
+    prefixes_keys = list(prefixes.keys())
+    fake_log = precision_utils.form_fake_log(prefixes_keys, activity_key=activity_key)
+
+    aligned_traces = executor.apply(fake_log, net, marking, final_marking, variant=token_replay_variant,
+                                        parameters=parameters_tr)
+
+    # fix: also the empty prefix should be counted!
+    start_activities = set(get_start_activities(log, parameters=parameters))
+    trans_en_ini_marking = set([x.label for x in get_visible_transitions_eventually_enabled_by_marking(net, marking)])
+    diff = trans_en_ini_marking.difference(start_activities)
+    sum_at += len(log) * len(trans_en_ini_marking)
+    sum_ee += len(log) * len(diff)
+    # end fix
+
+    for i in range(len(aligned_traces)):
+        if aligned_traces[i]["trace_is_fit"]:
+            log_transitions = set(prefixes[prefixes_keys[i]])
+            activated_transitions_labels = set(
+                [x.label for x in aligned_traces[i]["enabled_transitions_in_marking"] if x.label is not None])
+            sum_at += len(activated_transitions_labels) * prefix_count[prefixes_keys[i]]
+            escaping_edges = activated_transitions_labels.difference(log_transitions)
+            sum_ee += len(escaping_edges) * prefix_count[prefixes_keys[i]]
+
+    if sum_at > 0:
+        precision = 1 - float(sum_ee) / float(sum_at)
+
+    return precision
diff --git a/src/evaluation/replay_fitness/__init__.py b/src/evaluation/replay_fitness/__init__.py
new file mode 100644
index 0000000..5e16070
--- /dev/null
+++ b/src/evaluation/replay_fitness/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.replay_fitness import evaluator, variants
diff --git a/src/evaluation/replay_fitness/__pycache__/__init__.cpython-310.pyc b/src/evaluation/replay_fitness/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d1813c392983bd8d398f50e70da01728aeeb752b
GIT binary patch
literal 998
zcmd1j<>g{vU|@*6?wc;j%)sy%#6iYP3=9ko3=9m#5)2FsDGX5zDU2yhIgGhXQB1ka
zQOt}CDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^
z6+H9O@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL
z&QB{TPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQo
znwOGVq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs
z&PYvBNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U
z7lEClkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZ
zg``x4(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLe
zGII-ZGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVu
zI4T4<28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0
zh2o6-(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)E
zFTX@bp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i+HJNU4rj{k<lqQzs7v170
zODxJv%quDO(`32D9v`2QpBx__B?(oKnV+Xulv<FJSQ(#|S(2ApTwDaoHbu+~3=At7
zidY#KAjGc({m|mnqGJ8x^8B1MeV5eY?2`Nf{oKsF%)IpY^vn|d;?jcDB3-Cux~UZf
zIr&AYMf$}>$@&Pp^il27kB`sH%PfhH*DI*J#bJ}1pHiBWYR3r5vn&h@3@i*H0st&*
BIBEa@

literal 0
HcmV?d00001

diff --git a/src/evaluation/replay_fitness/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/replay_fitness/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6dc8ce5267bfefeb7ed21a3157a8b9563ac7dbf6
GIT binary patch
literal 3643
zcmd1j<>g{vU|_g++c!N%h=JiTh=YvT7#J8F7#J9e&oMACq%cG=q%fv1<uK+lMKR?v
zM=>*k_{=#hxvWvFU^Yt*TP}MP2bj&8!<ow!#RX=w<#6ZnMDc*x>^Z!-d{KO0Hb)MB
z6n_e13TKW$u3(fPR7@yJ$ekgDD}}p-A%#1YubDYY*qtGTCxy3#A%z#r7fBV)5{(i~
z70wci5=#|L70Y0X5>MsNl1Sl8;ZGGz5l9j2Wl0fB5lRtmVQglMl1$-96-#4E5lInk
zVT_VW;Rt5X6i+<P#K6F%prD`-l95@gkd~Q~s*qW%P>@(uqL81a5a4SPP^qBdn_rZw
z;F*_}Z>5k?Qc_TCrLSL*Yf@0DmzG(gmsXTmnwOEEmRh8jlB%i41vU+$5oBUfYN|qU
zep*R+Vo|D<LS=rbLULlBLQ!f;W^qYTW>RTMszPRoLSkNuetwZcZhlH;S|!+o%o2st
zyp+@;g_4X^g_6{w++vV}Kpc0!PzCqYywsw^9EE_=q@2uT1)t31)V$(Ug~Vco0+2{?
zMrw*e62u^o7B`S9f}yTZaLX^vOGzxr%+J$NNX;zCNG(z*OD!tS%+FIWhFJnNQAZ)a
z2<#k<#1fF>ixlz;KpHg_67wn*auQ1rdeME3@L-BUW**oZ8TkdN3MCndB_J=CXXfN6
zB&8~p7N@3_=IDTJNh&Q-2=@%}@DB}9aP*5*2zLw$a`X#{v{oq3EXl|(Em24<ONE$~
znOl&PnVO<do>)|rm{(G%ke>!N!q+v(*~8H<#L>yq$1@~SA-_n$%`?Q$H8@zo%|A%N
zQ6a!FD8$n_)W<PMAs{p;z(3ekPeCC#738o~unBm=Hw_#|MX3rYsU?Y-ImJ*vN9LC*
z6ldg@=A<ZOB$lNr6s0DoW|pO<C?qN*=NEvS2KGK~gTeMD=H%z4gFOmKcX|p6o@oks
z`6W6E#i^+ZHn5~uUS6)3o>!`uUzDz&1Bv)zeLFp_m!Mp$$#jc9F()%UFE=%>Bt9vz
zI5p)KPf31uYF>O%YC%q7r6%JoF1UhXO~zZC$@zI@sYNBJMIfoviqz!z(vr*^kd%6I
zMrv|)d}?BGWqdIx(d4BT7suzNmc(b~#iwQF#pfm#WoPE4Yck$qam_2u_0wd$#UADw
z6zu8mmke?ZGG>I8BO(k845<uJj42FJOsULKEUBzfY$=Sv44O>0IKmQ(G86Mkiv2X%
zZ*j!O=OyN*#>d~{ijU9DPbtj-v3cU-3riDoATmWv3=9mn_#J&b-Ti!B{X*iM9D`k5
zikLy7+#&wnu6_`al?+7!3=9zBSE7DsacWVqesOtzPMW?;YH@Z+et~{&W?p7qdVG3j
ziGFcuL28k18N?~NsTBn|`9-Nk`o%@b`l)4!Ii=vNqYnwr__WLtP^iFU@{9BeDvLlR
zstl;i1Qnhf3@nTsj9@X`fdX<KEKpJyqnJ~ef*CZKZ*c`A7A5AUmZTOH-{J*FTRhZV
zewr*r+#o;kfCyfYk9oo25Fh3k<mu=ag77hfB?wX{#K6Fy2yz$;0|NsG0|#RfNTiC@
zNY6;mw2IkS&)}CyXfZf1C_sw1#GLeeJ%qRQP`#~CkeHmEn4YT1a*H=5wV)_9Ik6-)
zB|bCn7FSVfZhjer&7WLaR0K+o&|>BmJ2d|!gTe=vNZ7$nVqkCvxo;i=149YJ0>%`^
zg^Y|0H7qGir3^*fnGCfoHLPH^R0&fJV+~Ucb2D?XL<w^(YaUYxOD$^+YYB5UQ;}f_
z>jJhK)`g5&>?Irv*lQSSSV}muI2STQ^whAVF!!<sGnB9{;40zH;;CUN;mzX9;$O&I
z%SJ#aTMbJJOD|h6gC=XD9jx+jEGWpSRDdQ01w?2m6qh6xm1O3nE2I_W=PD%Tfy=KF
zg`E6!P?4{Yr~t}AsVNEpsU=033VEp|&~mU+p|lvJEk7?6Rw6?JUZEs4IU_H#ur#$;
zp`a+gEHedM(nCrNXx#%(GT>rG7XiThocwe!4X*Tx5|dNGuE|fzN=+^SOB8_I0?OrJ
z9!MG7bzqUqyv&l!#2i@W2aADJc|yd&UdqgaSdx~Rhef3uNEAg~0jirp#vxL^LQyKH
zrb$sK$p@FC(2xM7%%aSaj9jn_pxFY<25EQB$S+RKQ-BIv!Noyxx(cvzMh~225Jd+r
zWk_WSB#?trOG=CKioxp90vMVM;z7v(EDy5M4N^=Xr46uDkthQLgWpQVTU>|`&}1qS
z2IW<01_lOAfm<BqiAA8IsrVKPh*bp2CPn-p1$-a^l!I<@r>B;{3Wi&(iJ;VYi!UcX
zJswiFgR1shJR$z^u3?};(#PNZ7C%S_*v@!Rk(LTB2Z}&Na}lU$xWx$%-CO)Fu5ON@
zJ|VDzMU(v&b54HxElzksxy6r`CT{VfM0^p*E4SF8aRKoisK7%inB*B47_>ndUyp%-
zflH2qiG`7anT?5wk?B9ve>N5tW)3C^CMHHUMz()ktZa-d{~<gkMz+62Aic?;G9Olw
zfY=~kfJ+jPpFkx^4bwu#ET$TU8pagHUQp@El$gs1ZTz`{!XU8(RC9ysNQI)*;?kUw
zVukVyXlnyK$th$c7Aqv?6s0DnR4OE;rsgSt0zES|1(G@tX%`&Jhy<bw=0lAKvq6a?
z2x?t^8t(KBjw^6Q0M{0fUtFA-l#>c-u0ksbg@VN5;#7D}0VP35bp_T0jQ}tkWUpsl
zN+zhl26;3yIRlnB^NT=j;N--d9GHKwr*?>ENJ{Qfpft@~1WL3;a-cek1M0+7P3Bwd
z&~O1Kmm*D2>PAZ?S_}*f8K6`Gs`o{Nia>l#CO<z->09ja@hSPq@$t8mpeAPK=b=?r
zdQkrt7lE{cTM1EONVQyYeqLIBQEp;haw;Sf$Ur0^Wn-}(A~Wgb<fj+0f*hy-@&p$|
z9jMs^_F9xKL;$7{o>BBbN{aO$%_?|{3Y?{Hv83je<`!{)%;SbtrJ4D8x1fgSrj{fY
zae}M_H7vlb2}qs=mja;j;1&n8=@op73uZuS3OILw0}7JcP($<<hYh4<Wd~}W6@$W)
og+V}ohY<!j7<m{u7zLOGSlL+E7&(|Yn3))v7+L=DFbi1$03J&H00000

literal 0
HcmV?d00001

diff --git a/src/evaluation/replay_fitness/__pycache__/parameters.cpython-310.pyc b/src/evaluation/replay_fitness/__pycache__/parameters.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..72bdd18a01e2f6aa7b4afa08548a80f2feeb2408
GIT binary patch
literal 1455
zcmd1j<>g{vU|{e!5K9kYVPJR+;vi!d1_lNP1_p*=8wLi36ox2<6vh;$9L8LxC?-Y_
zn>mL$mnDh?%x1}9&1H*XV`Ok=NMTK3Yhg%XOJ!<ij$%(?4`$HhNIcHOz`&)Tpr8<v
zky)&emYI{PkXfuykXTfrke{Xy;A;|4si5JTUzDognU|JtrI1llQc!HAuV0XBQc$Uv
zmRX{gR+Lzpmyw^ATBMhfs;S2XHVvW?WMWZjszPyoT1k0gQL2?fWqzqba$=rBQEEzN
zaY<2TQfW!5LS~6VVqS`Vevv|MeoAIqCD??_5{1&dl++@Hl8jV^lGLKyVvvJC9CyD^
z1^3jv)S|>3g@DqeoXlhepUmXcyy8@a#A1a4kVtVxYKlS<#2}CsH;^lWp{`JH%P-AK
zNi50C&(l#z%`C}CEm9~;Eh^5;&r>jlSpqdtM<Krm>>Q265|HDI6!Hr|8Z{LX^C}f`
z5=#(z(S45aV2VO!9@rZh`30#8B^ik&ATO6^=Hw_Or7Dybr>2$W=zwiWDlJh6_YCpy
z4-HXp^ovvocMJ-0^b3i!Rw&Ob$;dA)QAjOIg_xC@Tac5PnxasiSX7jlS5m2vp9VI<
z*EPu5!_hCq(aF=tGbB<WzevH&GsMp|I9S2WKS;q*A;2*x#M3#{$1zADAT%hzKiE}I
zK_NI5<giq*33$Rc4ID>BsR}8nC5f3i#ZW&-=9elIXXKaWq$p%0mZd5br6#9lmZhdB
zBq}857l51w_C9Wd!S*KR<maV>Jqk&8dI}1jX$pDyB{~Ylsi_J!u%uUBUaps(SE`p^
zl&+rxiTGlDJ3X$Kpgf|<c#Fj~uQXSa@fK%teqM1&VqQrxh?AI;o?nz%l98JXQihBf
zVcAK6fq@~FA&N1DA&M!LIg2HVHI+GwEs8ybF^VIFDT*_NIha9{<rY^!Vo_plYDsEQ
zag<0&es*eJd{JsaPGV(zSz=LUVqQs<NODeUVqRumdOSorEhj%e#ZQy#7Ds%1USe))
zeEco0`1suXl+qj!n<qZLurx6TB2&c7z`zhCAK)0|=<6Ed8WivB?-v~6=ob?2=p5o1
z<{1(h@9i2H#e>2FMJk5!kdPoxr_c~ruzFrc6s|~!zqhMje2{B^k7Hzfm}8KqqhCmr
zh_jEYqo1drdptzi&Bx#0WhFxqCj$e7_?4$0TAW%`tY2K7pOdDao0*rHmS2>fYOJ4{
zS5^$>=Yb+6Nxz`7BqKl1SkKTvzc{lbRkt89IXf{uwHQ)x=%<z?=9Gf7pguJ0(lSf(
zQj3fA3*dpPS5R5R%D})N1}f@6B_am{8zUPd3nK?3SlmyO;}&~-d`f<DeEcnz)V$K%
zA|_DSaY4$a(vr*^NMI^K1VAZ3FF8LiEx#x?F)uk)4;(oV>x)2TD%eznXCW+*#kV+Y
XAdzJUN-M=6Ar=N60UkykMiFKJ+mWmN

literal 0
HcmV?d00001

diff --git a/src/evaluation/replay_fitness/evaluator.py b/src/evaluation/replay_fitness/evaluator.py
new file mode 100644
index 0000000..160f610
--- /dev/null
+++ b/src/evaluation/replay_fitness/evaluator.py
@@ -0,0 +1,122 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.replay_fitness.variants import alignment_based, token_replay
+from pm4py.algo.conformance import alignments
+from pm4py.objects.conversion.log import converter as log_conversion
+from pm4py.util import exec_utils
+from pm4py.objects.petri_net.utils.check_soundness import check_easy_soundness_net_in_fin_marking
+from enum import Enum
+import deprecation
+from pm4py.meta import VERSION
+import warnings
+
+
+class Variants(Enum):
+    ALIGNMENT_BASED = alignment_based
+    TOKEN_BASED = token_replay
+
+
+class Parameters(Enum):
+    ALIGN_VARIANT = "align_variant"
+
+
+ALIGNMENT_BASED = Variants.ALIGNMENT_BASED
+TOKEN_BASED = Variants.TOKEN_BASED
+
+VERSIONS = {ALIGNMENT_BASED, TOKEN_BASED}
+
+
+@deprecation.deprecated(deprecated_in="2.2.5", removed_in="3.0",
+                        current_version=VERSION,
+                        details="Use the pm4py.algo.evaluation.replay_fitness package")
+def apply(log, petri_net, initial_marking, final_marking, parameters=None, variant=None):
+    """
+    Apply fitness evaluation starting from an event log and a marked Petri net,
+    by using one of the replay techniques provided by PM4Py
+
+    Parameters
+    -----------
+    log
+        Trace log object
+    petri_net
+        Petri net
+    initial_marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Parameters related to the replay algorithm
+    variant
+        Chosen variant:
+            - Variants.ALIGNMENT_BASED
+            - Variants.TOKEN_BASED
+
+    Returns
+    ----------
+    fitness_eval
+        Fitness evaluation
+    """
+    warnings.warn("Use the pm4py.algo.evaluation.replay_fitness package")
+
+    if parameters is None:
+        parameters = {}
+
+    # execute the following part of code when the variant is not specified by the user
+    if variant is None:
+        if not (
+                check_easy_soundness_net_in_fin_marking(petri_net, initial_marking,
+                                                                              final_marking)):
+            # in the case the net is not a easy sound workflow net, we must apply token-based replay
+            variant = TOKEN_BASED
+        else:
+            # otherwise, use the align-etconformance approach (safer, in the case the model contains duplicates)
+            variant = ALIGNMENT_BASED
+
+    if variant == TOKEN_BASED:
+        # execute the token-based replay variant
+        return exec_utils.get_variant(variant).apply(log_conversion.apply(log, parameters, log_conversion.TO_EVENT_LOG),
+                                                     petri_net,
+                                                     initial_marking, final_marking, parameters=parameters)
+    else:
+        # execute the alignments based variant, with the specification of the alignments variant
+        align_variant = exec_utils.get_param_value(Parameters.ALIGN_VARIANT, parameters,
+                                                   alignments.algorithm.DEFAULT_VARIANT)
+        return exec_utils.get_variant(variant).apply(log_conversion.apply(log, parameters, log_conversion.TO_EVENT_LOG),
+                                                     petri_net,
+                                                     initial_marking, final_marking, align_variant=align_variant,
+                                                     parameters=parameters)
+
+
+def evaluate(results, parameters=None, variant=TOKEN_BASED):
+    """
+    Evaluate replay results when the replay algorithm has already been applied
+
+    Parameters
+    -----------
+    results
+        Results of the replay algorithm
+    parameters
+        Possible parameters passed to the evaluation
+    variant
+        Indicates which evaluator is called
+
+    Returns
+    -----------
+    fitness_eval
+        Fitness evaluation
+    """
+    return exec_utils.get_variant(variant).evaluate(results, parameters=parameters)
diff --git a/src/evaluation/replay_fitness/parameters.py b/src/evaluation/replay_fitness/parameters.py
new file mode 100644
index 0000000..2f07722
--- /dev/null
+++ b/src/evaluation/replay_fitness/parameters.py
@@ -0,0 +1,26 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from enum import Enum
+from pm4py.util import constants
+from pm4py.algo.conformance.tokenreplay import algorithm
+
+
+class Parameters(Enum):
+    ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+    ATTRIBUTE_KEY = constants.PARAMETER_CONSTANT_ATTRIBUTE_KEY
+    TOKEN_REPLAY_VARIANT = "token_replay_variant"
+    CLEANING_TOKEN_FLOOD = "cleaning_token_flood"
diff --git a/src/evaluation/replay_fitness/variants/__init__.py b/src/evaluation/replay_fitness/variants/__init__.py
new file mode 100644
index 0000000..ab66e85
--- /dev/null
+++ b/src/evaluation/replay_fitness/variants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.replay_fitness.variants import alignment_based, token_replay
diff --git a/src/evaluation/replay_fitness/variants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/replay_fitness/variants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4d7342cb4e61bd9e1d41984297e91ab3f8dbebf2
GIT binary patch
literal 1026
zcmd1j<>g{vU|{%m+c#Z@nStRkh=Yuo7#J8F7#J9eB^VeOQW&BbQW#U1au{=&qL^}-
zqnH^PQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM1XL<$_~sX-
zDtP9l<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;PXat#9l$xqg
zoS#-wo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9nN|rlA+toG
zG%qE!NTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01RUxrhp#UUO
zoRONMkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHljA52QP1I4y
zF9JJ9Be4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOdVhPC0<(WA-
z3Q4I7rNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^OG^||%Tgg`
zW#$&-WTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiDa}5qwaPtpR
za8w9z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`BW==8G&yo41
z3dI@ur8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS(w&}yf@hjS
zUVe#=LUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?}tYck#9Pt3_o&&y5CD~V4^
zEKW_i#Z!`>othV4lv<FJSm~$9a*I7aJ|#anKK_<cYFT1VDL5(UL8Rl;GE4GOi;MNj
z5{ohu^Gb?~Ksl+1nSp^}B|{M#0|SKkm8l<EoLW?@UtFG_lcw*ITAW>yU!b3xnU|TD
z9-p3BqF-EEkXob*HAOeIq97-~D78qxxF}g4VW~c<rTS3i#rpB_nR%Hd@$q^EmA5!-
Xa`RJ4b5iXXLHU`5fq{XAK|}xmFNQ?e

literal 0
HcmV?d00001

diff --git a/src/evaluation/replay_fitness/variants/__pycache__/alignment_based.cpython-310.pyc b/src/evaluation/replay_fitness/variants/__pycache__/alignment_based.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3b95eec483b619446e06ca18fcdd770390cd13eb
GIT binary patch
literal 4655
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
P1CId6^IXgVT0DvXlR9ej

literal 0
HcmV?d00001

diff --git a/src/evaluation/replay_fitness/variants/__pycache__/token_replay.cpython-310.pyc b/src/evaluation/replay_fitness/variants/__pycache__/token_replay.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8f885b14e679afa5940eb4685676df516b7a943b
GIT binary patch
literal 3963
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
RHe_Mo5#V8jKrUuJH2`yEJ|X}B

literal 0
HcmV?d00001

diff --git a/src/evaluation/replay_fitness/variants/alignment_based.py b/src/evaluation/replay_fitness/variants/alignment_based.py
new file mode 100644
index 0000000..b8db21f
--- /dev/null
+++ b/src/evaluation/replay_fitness/variants/alignment_based.py
@@ -0,0 +1,126 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.algo.conformance.alignments.petri_net import algorithm as alignments
+from pm4py.algo.conformance.alignments.decomposed import algorithm as decomp_alignments
+from evaluation.replay_fitness.parameters import Parameters
+
+
+def evaluate(aligned_traces, parameters=None):
+    """
+    Transforms the alignment result to a simple dictionary
+    including the percentage of fit traces and the average fitness
+
+    Parameters
+    ----------
+    aligned_traces
+        Alignments calculated for the traces in the log
+    parameters
+        Possible parameters of the evaluation
+
+    Returns
+    ----------
+    dictionary
+        Containing two keys (percFitTraces and averageFitness)
+    """
+    if parameters is None:
+        parameters = {}
+    str(parameters)
+    no_traces = len([x for x in aligned_traces if x is not None])
+    no_fit_traces = 0
+    sum_fitness = 0.0
+
+    for tr in aligned_traces:
+        if tr is not None:
+            if tr["fitness"] == 1.0:
+                no_fit_traces = no_fit_traces + 1
+            sum_fitness = sum_fitness + tr["fitness"]
+
+    perc_fit_traces = 0.0
+    average_fitness = 0.0
+
+    if no_traces > 0:
+        perc_fit_traces = (100.0 * float(no_fit_traces)) / (float(no_traces))
+        average_fitness = float(sum_fitness) / float(no_traces)
+
+    return {"percFitTraces": perc_fit_traces, "averageFitness": average_fitness,
+            "percentage_of_fitting_traces": perc_fit_traces,
+            "average_trace_fitness": average_fitness}
+
+
+def apply(log, petri_net, initial_marking, final_marking, align_variant=alignments.DEFAULT_VARIANT, parameters=None):
+    """
+    Evaluate fitness based on alignments
+
+    Parameters
+    ----------------
+    log
+        Event log
+    petri_net
+        Petri net
+    initial_marking
+        Initial marking
+    final_marking
+        Final marking
+    align_variant
+        Variants of the alignments to apply
+    parameters
+        Parameters of the algorithm
+
+    Returns
+    ---------------
+    dictionary
+        Containing two keys (percFitTraces and averageFitness)
+    """
+    if align_variant == decomp_alignments.Variants.RECOMPOS_MAXIMAL.value:
+        alignment_result = decomp_alignments.apply(log, petri_net, initial_marking, final_marking,
+                                                   variant=align_variant, parameters=parameters)
+    else:
+        alignment_result = alignments.apply(log, petri_net, initial_marking, final_marking, variant=align_variant,
+                                            parameters=parameters)
+    return evaluate(alignment_result)
+
+
+def apply_trace(trace, petri_net, initial_marking, final_marking, best_worst, activity_key):
+    """
+    Performs the basic alignment search, given a trace, a net and the costs of the \"best of the worst\".
+    The costs of the best of the worst allows us to deduce the fitness of the trace.
+    We compute the fitness by means of 1 - alignment costs / best of worst costs (i.e. costs of 0 => fitness 1)
+
+    Parameters
+    ----------
+    trace: :class:`list` input trace, assumed to be a list of events (i.e. the code will use the activity key to
+    get the attributes)
+    petri_net: :class:`pm4py.objects.petri.net.PetriNet` the Petri net to use in the alignment
+    initial_marking: :class:`pm4py.objects.petri.net.Marking` initial marking in the Petri net
+    final_marking: :class:`pm4py.objects.petri.net.Marking` final marking in the Petri net
+    best_worst: cost of the best worst alignment of a trace (empty trace aligned to the model)
+    activity_key: :class:`str` (optional) key to use to identify the activity described by the events
+
+    Returns
+    -------
+    dictionary: `dict` with keys **alignment**, **cost**, **visited_states**, **queued_states** and **traversed_arcs**
+    """
+    alignment = alignments.apply_trace(trace, petri_net, initial_marking, final_marking,
+                                       {Parameters.ACTIVITY_KEY: activity_key})
+    fixed_costs = alignment['cost'] // alignments.utils.STD_MODEL_LOG_MOVE_COST
+    if best_worst > 0:
+        fitness = 1 - (fixed_costs / best_worst)
+    else:
+        fitness = 1
+    return {'trace': trace, 'alignment': alignment['alignment'], 'cost': fixed_costs, 'fitness': fitness,
+            'visited_states': alignment['visited_states'], 'queued_states': alignment['queued_states'],
+            'traversed_arcs': alignment['traversed_arcs']}
diff --git a/src/evaluation/replay_fitness/variants/token_replay.py b/src/evaluation/replay_fitness/variants/token_replay.py
new file mode 100644
index 0000000..84c5692
--- /dev/null
+++ b/src/evaluation/replay_fitness/variants/token_replay.py
@@ -0,0 +1,100 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from pm4py.algo.conformance.tokenreplay import algorithm as executor
+from pm4py.algo.conformance.tokenreplay.variants import token_replay
+from evaluation.replay_fitness.parameters import Parameters
+from pm4py.util import exec_utils
+from pm4py.util.xes_constants import DEFAULT_NAME_KEY
+
+
+def evaluate(aligned_traces, parameters=None):
+    """
+    Gets a dictionary expressing fitness in a synthetic way from the list of boolean values
+    saying if a trace in the log is fit, and the float values of fitness associated to each trace
+
+    Parameters
+    ------------
+    aligned_traces
+        Result of the token-based replayer
+    parameters
+        Possible parameters of the evaluation
+
+    Returns
+    -----------
+    dictionary
+        Containing two keys (percFitTraces and averageFitness)
+    """
+    if parameters is None:
+        parameters = {}
+    no_traces = len(aligned_traces)
+    fit_traces = len([x for x in aligned_traces if x["trace_is_fit"]])
+    sum_of_fitness = sum([x["trace_fitness"] for x in aligned_traces])
+    perc_fit_traces = 0.0
+    average_fitness = 0.0
+    log_fitness = 0
+    total_m = sum([x["missing_tokens"] for x in aligned_traces])
+    total_c = sum([x["consumed_tokens"] for x in aligned_traces])
+    total_r = sum([x["remaining_tokens"] for x in aligned_traces])
+    total_p = sum([x["produced_tokens"] for x in aligned_traces])
+    if no_traces > 0:
+        perc_fit_traces = float(100.0 * fit_traces) / float(no_traces)
+        average_fitness = float(sum_of_fitness) / float(no_traces)
+        if total_c > 0 and total_p > 0:
+            log_fitness = 0.5 * (1 - total_m / total_c) + 0.5 * (1 - total_r / total_p)
+    return {"perc_fit_traces": perc_fit_traces, "average_trace_fitness": average_fitness, "log_fitness": log_fitness,
+            "percentage_of_fitting_traces": perc_fit_traces }
+
+
+def apply(log, petri_net, initial_marking, final_marking, parameters=None):
+    """
+    Apply token replay fitness evaluation
+
+    Parameters
+    -----------
+    log
+        Trace log
+    petri_net
+        Petri net
+    initial_marking
+        Initial marking
+    final_marking
+        Final marking
+    parameters
+        Parameters
+
+    Returns
+    -----------
+    dictionary
+        Containing two keys (percFitTraces and averageFitness)
+    """
+
+    if parameters is None:
+        parameters = {}
+    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, DEFAULT_NAME_KEY)
+    token_replay_variant = exec_utils.get_param_value(Parameters.TOKEN_REPLAY_VARIANT, parameters,
+                                                      executor.Variants.TOKEN_REPLAY)
+    cleaning_token_flood = exec_utils.get_param_value(Parameters.CLEANING_TOKEN_FLOOD, parameters, False)
+    remaining_in_fitness = exec_utils.get_param_value(token_replay.Parameters.CONSIDER_REMAINING_IN_FITNESS, parameters, True)
+
+    parameters_tr = {token_replay.Parameters.ACTIVITY_KEY: activity_key,
+                     token_replay.Parameters.CONSIDER_REMAINING_IN_FITNESS: remaining_in_fitness,
+                     token_replay.Parameters.CLEANING_TOKEN_FLOOD: cleaning_token_flood}
+
+    aligned_traces = executor.apply(log, petri_net, initial_marking, final_marking, variant=token_replay_variant,
+                                    parameters=parameters_tr)
+
+    return evaluate(aligned_traces)
diff --git a/src/evaluation/simplicity/__init__.py b/src/evaluation/simplicity/__init__.py
new file mode 100644
index 0000000..ef0d4ef
--- /dev/null
+++ b/src/evaluation/simplicity/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.simplicity import evaluator, variants
diff --git a/src/evaluation/simplicity/__pycache__/__init__.cpython-310.pyc b/src/evaluation/simplicity/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..35c404eb3a5ab3c97869cb2417a7a7ce4f61964a
GIT binary patch
literal 990
zcmd1j<>g{vU|>kR?wc;k%)sy%#6iYP3=9ko3=9m#5)2FsDGX5zDU2yhIgGhXQB1ka
zQOt}CDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^
z6+H9O@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL
z&QB{TPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQo
znwOGVq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs
z&PYvBNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U
z7lEClkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZ
zg``x4(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLe
zGII-ZGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVu
zI4T4<28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0
zh2o6-(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)E
zFTX@bp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i+HJNU4rj{k<lqQzs7v170
zODxJv%quDO(`32D9v`2QpBx__B??uLnV+Xu3=YTS%#z9?P?jlTW?*1g$xy_~zyKkB
z#ps6?rxq3K7nkSfr0Kh)7H5~_7wG3^=4Ixk$ERnO=ogn3q!#HyEz(V`D9Fh#N-fea
vE=tx%*rSiIM?XG3GcU6wK3=b&@)n0pZhlH>PO2RvD6g_GFfgz%hzI}xIq)?k

literal 0
HcmV?d00001

diff --git a/src/evaluation/simplicity/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/simplicity/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..19b414499b921880733c921dc9134ad1b6649a99
GIT binary patch
literal 1728
zcmd1j<>g{vU|`_A<D341m4V?gh=YvT7#J8F7#J9eTNoG^QW&BbQW#U1au{=&qL>&#
zY~~#1T$U&nFq<WZHJ2@l4a{cEVUJ=@VN7Am;mGBT;)IHEMRB<^q_C%Ov@oP_q_Q+K
zM{%ceXYoYwr1E4iM)9VyXYr+Qrf{Wlrf{e5^s=O|rt+jQrSPWkwJ<g_M)9Yx1~X{#
zCmv^FVBk_vP*4cT$ShV!%gjkt$ShVUNGvK*$WK!U@HGjjRM7CvFG^ML%uCC+QphMN
zDJZtm*DuI5DX7#-%Pi4LD@rWQ%g9elEz(O#)zsqxn+DMcGO;K%RiQXPt)x7$DAh`#
zGQU(IIWbS6C^aRsxTGjEsk9_jA+tmwF)u|wzepiBKP5A*5^O?di9%^!N@|fpNk*zd
zNorAUF~~t6j=Nu|f_rLSYEfd2LO^L!PG+)#PiAszUU8~IVzEL2NTfI;HANu_Vh~7+
z8^{&GP**6p<(KBAB$j06=jkY<W|m~67Acga78Pga=P4M&EP<M+qmW+&c8*433CQt9
z3i$;fjhYIHd6fz|i6scV=srhyFhwCV5A2PM{DM@4l8nR>keACdb8-}tQWZ*zQ`1Uw
zbilSGm6j-kdxm)UhlVIP`b8>)I|c<g`h`SVE0kxJWaO8YD5RF9Ld?p{Ey&4CO;IRM
zEGkOOE2&h-PXim_>l)<j;pi9Q=;Z0+84{_GU!>sX8RF*}9IW8xAEe-@5a1XT;^`df
z;~1n65E>NVAMC29pb(r2a#$+Z1U%uJ29BemRE3n(lEloMVyK@Z^Gg+qGxAGwQWP>0
z%Tg7JQj=3N%TiMm5*3p33qVc-dmp#KV0#mD^7GQc9)+YkJp~2NG=;qU5*>x&)Kmo<
zSkfymFV{=YE7i*{O4rYUM0~NnogUXqP+rqyyv3DRlpLRunqHKes>yhZ#Wk-q7bKcm
zk(wM|T9TPl?5D|ii#^OWDA?2AFBzl>88gB%sQ?25Ln=cQV+unQQz~;5OA2E!gC^50
zj<Cd{%*4EsVn0pRTO9H6d5O8H@$t8~;^TAkQ%Z9{Y@Yb|!qUVXh)fX^0|Ub?(O^&C
z03T0h&ydJ?#~|l;7gzTnSJ#ycMcfPw5aL&?erR!OQL%n;d45iszDsIxc1eDLer{%7
zW?p)HdS;1!acMznk!~5pdfn8Df}H%K)FS=jqGbKlvc#NHaEjJ11}BW<%#uo&Xnv7i
zL1huB@R0-+J}e9j3>*v`j9`%}RwF$lJ<}>?V?Bdk2BF2^RG<LK+=)5q`FaSG^$;d2
z6eK2RC#I)rvfScLNi8T!O-?LHO^MIUyTw(Mnwwt+Ve==K78RxDmBd3c*DZEvs!3)>
z3UCk`<RoX1+vFG+7)lrxFs3jrWMpJ0VOqdk!?chwi=~F4hB1YymnoP*li9C`mw|x+
z6r%-+MTxnoC8<Tlpr|NMECK~p@huh*s|aM~E$;Nxl6Yu{-C|8FD9EYQWWL2&kXlld
z8K0M0QX~Y59CoM*L}V24fTBf=fq_8@<SdX`Tr47tMIaGPfm`hH@hSPq@$pe|Sc69o
zYF;rU?padvN^^@q{=CHn30Y7oggOuwe3|)qFxlMHlEflbkOPE41SsN)Ko!d^4rrPU
zzQqO8mYM=~6Ozlp2@2%=TO2l!6ln)a(#4>lWnthE;9=xp<N#v<W;PZ!CJtsMMkYp<
He<I8PrH=2@

literal 0
HcmV?d00001

diff --git a/src/evaluation/simplicity/evaluator.py b/src/evaluation/simplicity/evaluator.py
new file mode 100644
index 0000000..439848c
--- /dev/null
+++ b/src/evaluation/simplicity/evaluator.py
@@ -0,0 +1,39 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.simplicity.variants import arc_degree
+from enum import Enum
+from pm4py.util import exec_utils
+import deprecation
+from pm4py.meta import VERSION
+import warnings
+
+
+class Variants(Enum):
+    SIMPLICITY_ARC_DEGREE = arc_degree
+
+
+SIMPLICITY_ARC_DEGREE = Variants.SIMPLICITY_ARC_DEGREE
+
+VERSIONS = {SIMPLICITY_ARC_DEGREE}
+
+
+@deprecation.deprecated(deprecated_in="2.2.5", removed_in="3.0",
+                        current_version=VERSION,
+                        details="Use the pm4py.algo.evaluation.simplicity package")
+def apply(petri_net, parameters=None, variant=SIMPLICITY_ARC_DEGREE):
+    warnings.warn("Use the pm4py.algo.evaluation.simplicity package")
+    return exec_utils.get_variant(variant).apply(petri_net, parameters=parameters)
diff --git a/src/evaluation/simplicity/variants/__init__.py b/src/evaluation/simplicity/variants/__init__.py
new file mode 100644
index 0000000..0ca2017
--- /dev/null
+++ b/src/evaluation/simplicity/variants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.simplicity.variants import arc_degree
diff --git a/src/evaluation/simplicity/variants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/simplicity/variants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..79bce5a0bf4c6ab34b32a6eb4c80d85591765cd5
GIT binary patch
literal 990
zcmd1j<>g{vU|^8F<C`wZ%)sy%#6iYP3=9ko3=9m#A`A=+DGX5zDU2yhIgGhXQA~^s
zDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^6+H9O
z@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL&QB{T
zPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQonwOGV
zq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs&PYvB
zNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U7lECl
zkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZg``x4
z(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLeGII-Z
zGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVuI4T4<
z28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0h2o6-
z(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)EFTX@b
zp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i!H5qSlB^D*er=+GArKb96GT&m4
zk59=@j*q`3ms*yXQwolEy<%|iC1;jY>XjuHWhUm86c>TA%SwhKRt5$L@heq7v^ce>
zSiiVDKPOG!CAB!aB)>pEH#09YFFigzvqZnRv>>%e7ixfRYDGa#eo<<XesNK<KEf`2
sgkAbjrN#R3@tJv<CGqik1(mlrY;yBcN^?@}7(scJg@J*Ag+V|70MvdrJpcdz

literal 0
HcmV?d00001

diff --git a/src/evaluation/simplicity/variants/__pycache__/arc_degree.cpython-310.pyc b/src/evaluation/simplicity/variants/__pycache__/arc_degree.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e926c1d78c5f68701dcc52538bc816ede860d607
GIT binary patch
literal 2389
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
ZDTD1m;aUt5VqxGB;9=xp6kz6K0RVn@ur2@q

literal 0
HcmV?d00001

diff --git a/src/evaluation/simplicity/variants/arc_degree.py b/src/evaluation/simplicity/variants/arc_degree.py
new file mode 100644
index 0000000..33819ec
--- /dev/null
+++ b/src/evaluation/simplicity/variants/arc_degree.py
@@ -0,0 +1,70 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from statistics import mean
+from enum import Enum
+from pm4py.util import exec_utils
+
+
+class Parameters(Enum):
+    K = "k"
+
+
+def apply(petri_net, parameters=None):
+    """
+    Gets simplicity from a Petri net
+
+    Vázquez-Barreiros, Borja, Manuel Mucientes, and Manuel Lama. "ProDiGen: Mining complete, precise and minimal
+    structure process models with a genetic algorithm." Information Sciences 294 (2015): 315-333.
+
+    Parameters
+    -----------
+    petri_net
+        Petri net
+    parameters
+        Possible parameters of the algorithm:
+            - K: defines the value to be substracted in the formula: the lower is the value,
+            the lower is the simplicity value. k is the baseline arc degree (that is subtracted from the others)
+
+    Returns
+    -----------
+    simplicity
+        Simplicity measure associated to the Petri net
+    """
+    if parameters is None:
+        parameters = {}
+
+    # original model: we have plenty of choices there.
+    # one choice is about taking a model containing the most frequent variant,
+    # along with a short circuit between the final and the initial marking.
+    # in that case, the average arc degree of the "original model" is 2
+
+    # keep the default to 2
+    k = exec_utils.get_param_value(Parameters.K, parameters, 2)
+
+    # TODO: verify the real provenence of the approach before!
+
+    all_arc_degrees = []
+    for place in petri_net.places:
+        all_arc_degrees.append(len(place.in_arcs) + len(place.out_arcs))
+    for trans in petri_net.transitions:
+        all_arc_degrees.append(len(trans.in_arcs) + len(trans.out_arcs))
+
+    mean_degree = mean(all_arc_degrees) if all_arc_degrees else 0.0
+
+    simplicity = 1.0 / (1.0 + max(mean_degree - k, 0))
+
+    return simplicity
diff --git a/src/evaluation/soundness/__init__.py b/src/evaluation/soundness/__init__.py
new file mode 100644
index 0000000..6afd88a
--- /dev/null
+++ b/src/evaluation/soundness/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness import woflan
diff --git a/src/evaluation/soundness/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d11df19de565faefadc6ec9de3a49a014ff76b9b
GIT binary patch
literal 966
zcmd1j<>g{vU|?{%<D1UT%)sy%#6iYP3=9ko3=9m#A`A=+DGX5zDU2yhIgGhXQA~^s
zDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^6+H9O
z@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL&QB{T
zPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQonwOGV
zq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs&PYvB
zNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U7lECl
zkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZg``x4
z(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLeGII-Z
zGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVuI4T4<
z28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0h2o6-
z(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)EFTX@b
zp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i!H5qTQmFK7BB<A^PGT&m4k59=@
zj*pKLNi9pvDFw&5UNI>8^HPh8i$ED<B|{Ml0|SKk6|EmyoLW?@UtFG_lcw*ITAW>y
zU!b3xnU|TD9-p3BqF-EEkXoc$mROXTm{+2kT2YXbUzA#;UtE-|kFY@>Zi9Y&d}dx|
eNqoFsLFFwDo80`A(wtN~Mo^vvS<1p7AOHYuVl7Dk

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/__init__.py b/src/evaluation/soundness/woflan/__init__.py
new file mode 100644
index 0000000..a77c871
--- /dev/null
+++ b/src/evaluation/soundness/woflan/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan import algorithm, graphs, not_well_handled_pairs, place_invariants
diff --git a/src/evaluation/soundness/woflan/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f72bf4fca8b2e01d810ec574bcec811ac06597db
GIT binary patch
literal 1060
zcmd1j<>g{vU|{gQ<D2fx%)sy%#6iYP3=9ko3=9m#3JeSkDGX5zDU2yhIgGhXQB1ka
zQOvn4Q7pNvQLKy%Da^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur
z0xA_WeDjM^6+H9O@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}C
zG=fYlN=;QL&QB{TPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5
zOsfQ&kXfQonwOGVq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjh
zs*qT$PyiAs&PYvBNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN
z#xP5uCh92U7lEClkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!
zu>|Dh^30qZg``x4(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$d
zr6mffWvLLeGII-ZGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7
zxdsO-xcLVuI4T4<28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0m
zGp88p=g9n0h2o6-(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu
z=}u2U!81)EFTX@bp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9g1G+AzOCg!B)
z7iE@Y<lbUSFG?)PD840@mtPWJo|=;rpOKiCl9QSeUyzttRD4UIASW?7H9j-1EU_pv
zF|VZ9Pm}EydwhIKesX+#lyquYVooVIq39KZ(o9}zaj{-`ep*gqUJ)px6)`g~Fch(X
z2v!CLhLsFO>>xIX_?4s|TAW%`tY2K7pOdEVl3JWyl3$>oo0*rHmmZ&<S)yNDT98_#
z3$<7`wW1&=zbLgxzqlw_A7QUP++KZ%z54O-nR%Hd@$q^EmA5!-a`RJ4b5iXXK{+1e
J4i*L(0RX_<O)>xg

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/__pycache__/algorithm.cpython-310.pyc b/src/evaluation/soundness/woflan/__pycache__/algorithm.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3ed770e1faef9fdd363c56cf7cf76ea405d925a3
GIT binary patch
literal 21598
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
WHZD;vUM?;Uc2F6^!^9!UqXz&KN&oNw

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/algorithm.py b/src/evaluation/soundness/woflan/algorithm.py
new file mode 100644
index 0000000..88097d4
--- /dev/null
+++ b/src/evaluation/soundness/woflan/algorithm.py
@@ -0,0 +1,654 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import copy
+import warnings
+from enum import Enum
+
+import deprecation
+import networkx as nx
+import numpy as np
+
+# Minimal Coverability Graph
+from evaluation.soundness.woflan.graphs.minimal_coverability_graph.minimal_coverability_graph import \
+    apply as minimal_coverability_graph
+# reachability Graph Creation
+from evaluation.soundness.woflan.graphs.reachability_graph.reachability_graph import apply as reachability_graph
+# Restricted coverability graph
+from evaluation.soundness.woflan.graphs.restricted_coverability_graph.restricted_coverability_graph import \
+    construct_tree as restricted_coverability_tree
+from evaluation.soundness.woflan.graphs.utility import check_for_dead_tasks
+from evaluation.soundness.woflan.graphs.utility import check_for_improper_conditions
+from evaluation.soundness.woflan.graphs.utility import check_for_substates
+from evaluation.soundness.woflan.graphs.utility import convert_marking
+# Importing to discover not-well handled pairs
+from evaluation.soundness.woflan.not_well_handled_pairs.not_well_handled_pairs import \
+    apply as compute_not_well_handled_pairs
+# Importing for place invariants related stuff (s-components, uniform and weighted place invariants)
+from evaluation.soundness.woflan.place_invariants.place_invariants import compute_place_invariants
+from evaluation.soundness.woflan.place_invariants.s_component import compute_s_components
+from evaluation.soundness.woflan.place_invariants.s_component import compute_uncovered_places_in_component
+from evaluation.soundness.woflan.place_invariants.utility import \
+    compute_uncovered_places as compute_uncovered_place_in_invariants
+from evaluation.soundness.woflan.place_invariants.utility import transform_basis
+from pm4py.objects.petri_net.utils import petri_utils
+from pm4py.objects.petri_net.obj import PetriNet
+from pm4py.util import exec_utils
+
+
+class Parameters(Enum):
+    RETURN_ASAP_WHEN_NOT_SOUND = "return_asap_when_not_sound"
+    PRINT_DIAGNOSTICS = "print_diagnostics"
+    RETURN_DIAGNOSTICS = "return_diagnostics"
+
+
+class Outputs(Enum):
+    S_C_NET = "s_c_net"
+    PLACE_INVARIANTS = "place_invariants"
+    UNIFORM_PLACE_INVARIANTS = "uniform_place_invariants"
+    S_COMPONENTS = "s_components"
+    UNCOVERED_PLACES_S_COMPONENT = "uncovered_places_s_component"
+    NOT_WELL_HANDLED_PAIRS = "not_well_handled_pairs"
+    LEFT = "left"
+    UNCOVERED_PLACES_UNIFORM = "uncovered_places_uniform"
+    WEIGHTED_PLACE_INVARIANTS = "weighted_place_invariants"
+    UNCOVERED_PLACES_WEIGHTED = "uncovered_places_weighted"
+    MCG = "mcg"
+    DEAD_TASKS = "dead_tasks"
+    R_G_S_C = "r_g_s_c"
+    R_G = "r_g"
+    LOCKING_SCENARIOS = "locking_scenarios"
+    RESTRICTED_COVERABILITY_TREE = "restricted_coverability_tree"
+
+
+class woflan:
+    def __init__(self, net, initial_marking, final_marking, print_diagnostics=False):
+        self.net = net
+        self.initial_marking = initial_marking
+        self.final_marking = final_marking
+        self.print_diagnostics = print_diagnostics
+        self.s_c_net = None
+        self.place_invariants = None
+        self.uniform_place_invariants = None
+        self.s_components = None
+        self.uncovered_places_s_component = None
+        self.not_well_handled_pairs = None
+        self.left = None
+        self.uncovered_places_uniform = None
+        self.weighted_place_invariants = None
+        self.uncovered_places_weighted = None
+        self.mcg = None
+        self.dead_tasks = None
+        self.r_g_s_c = None
+        self.r_g = None
+        self.locking_scenarios = None
+        self.restricted_coverability_tree = None
+
+    def set_s_c_net(self, s_c_net):
+        self.s_c_net = s_c_net
+
+    def set_place_invariants(self, invariants):
+        self.place_invariants = invariants
+
+    def set_uniform_place_invariants(self, invariants):
+        self.uniform_place_invariants = invariants
+
+    def set_s_components(self, s_components):
+        self.s_components = s_components
+
+    def set_uncovered_places_s_component(self, uncovered_places):
+        self.uncovered_places_s_component = uncovered_places
+
+    def set_not_well_handled_pairs(self, not_well_handled_pairs):
+        self.not_well_handled_pairs = not_well_handled_pairs
+
+    def set_left(self, left):
+        self.left = left
+
+    def set_uncovered_places_uniform(self, places):
+        self.uncovered_places_uniform = places
+
+    def set_weighted_place_invariants(self, invariants):
+        self.weighted_place_invariants = invariants
+
+    def set_uncovered_places_weighted(self, places):
+        self.uncovered_places_weighted = places
+
+    def set_mcg(self, mcg):
+        self.mcg = mcg
+
+    def set_dead_tasks(self, dead_tasks):
+        self.dead_tasks = dead_tasks
+
+    def set_r_g_s_c(self, r_g):
+        self.r_g_s_c = r_g
+
+    def set_r_g(self, r_g):
+        self.r_g = r_g
+
+    def set_locking_scenarios(self, scenarios):
+        self.locking_scenarios = scenarios
+
+    def set_restricted_coverability_tree(self, graph):
+        self.restricted_coverability_tree = graph
+
+    def get_net(self):
+        return self.net
+
+    def get_initial_marking(self):
+        return self.initial_marking
+
+    def get_final_marking(self):
+        return self.final_marking
+
+    def get_s_c_net(self):
+        return self.s_c_net
+
+    def get_place_invariants(self):
+        return self.place_invariants
+
+    def get_uniform_place_invariants(self):
+        return self.uniform_place_invariants
+
+    def get_s_components(self):
+        return self.s_components
+
+    def get_uncovered_places_s_component(self):
+        return self.uncovered_places_s_component
+
+    def get_not_well_handled_pairs(self):
+        return self.not_well_handled_pairs
+
+    def get_left(self):
+        return self.left
+
+    def get_uncovered_places_uniform(self):
+        return self.uncovered_places_uniform
+
+    def get_weighted_place_invariants(self):
+        return self.weighted_place_invariants
+
+    def get_uncovered_places_weighted(self):
+        return self.uncovered_places_weighted
+
+    def get_mcg(self):
+        return self.mcg
+
+    def get_dead_tasks(self):
+        return self.dead_tasks
+
+    def get_r_g_s_c(self):
+        return self.r_g_s_c
+
+    def get_r_g(self):
+        return self.r_g
+
+    def get_locking_scenarios(self):
+        return self.locking_scenarios
+
+    def get_restricted_coverability_tree(self):
+        return self.restricted_coverability_tree
+
+    def get_output(self):
+        """
+        Returns a dictionary representation of the
+        entities that are calculated during WOFLAN
+        """
+        ret = {}
+        if self.s_c_net is not None:
+            ret[Outputs.S_C_NET.value] = self.s_c_net
+        if self.place_invariants is not None:
+            ret[Outputs.PLACE_INVARIANTS.value] = self.place_invariants
+        if self.uniform_place_invariants is not None:
+            ret[Outputs.UNIFORM_PLACE_INVARIANTS.value] = self.uniform_place_invariants
+        if self.s_components is not None:
+            ret[Outputs.S_COMPONENTS.value] = self.s_components
+        if self.uncovered_places_s_component is not None:
+            ret[Outputs.UNCOVERED_PLACES_S_COMPONENT.value] = self.uncovered_places_s_component
+        if self.not_well_handled_pairs is not None:
+            ret[Outputs.NOT_WELL_HANDLED_PAIRS.value] = self.not_well_handled_pairs
+        if self.left is not None:
+            ret[Outputs.LEFT.value] = self.left
+        if self.uncovered_places_uniform is not None:
+            ret[Outputs.UNCOVERED_PLACES_UNIFORM.value] = self.uncovered_places_uniform
+        if self.weighted_place_invariants is not None:
+            ret[Outputs.WEIGHTED_PLACE_INVARIANTS.value] = self.weighted_place_invariants
+        if self.uncovered_places_weighted is not None:
+            ret[Outputs.UNCOVERED_PLACES_WEIGHTED.value] = self.uncovered_places_weighted
+        if self.mcg is not None:
+            ret[Outputs.MCG.value] = self.mcg
+        if self.dead_tasks is not None:
+            ret[Outputs.DEAD_TASKS.value] = self.dead_tasks
+        if self.r_g_s_c is not None:
+            ret[Outputs.R_G_S_C.value] = self.r_g_s_c
+        if self.r_g is not None:
+            ret[Outputs.R_G] = self.r_g
+        if self.locking_scenarios is not None:
+            ret[Outputs.LOCKING_SCENARIOS] = self.locking_scenarios
+        if self.restricted_coverability_tree is not None:
+            ret[Outputs.RESTRICTED_COVERABILITY_TREE] = self.restricted_coverability_tree
+        return ret
+
+
+def short_circuit_petri_net(net, print_diagnostics=False):
+    """
+    Fist, sink and source place are identified. Then, a transition from source to sink is added to short-circuited
+    the given petri net. If there is no unique source and sink place, an error gets returned
+    :param net: Petri net that is going to be short circuited
+    :return:
+    """
+    s_c_net = copy.deepcopy(net)
+    no_source_places = 0
+    no_sink_places = 0
+    sink = None
+    source = None
+    for place in s_c_net.places:
+        if len(place.in_arcs) == 0:
+            source = place
+            no_source_places += 1
+        if len(place.out_arcs) == 0:
+            sink = place
+            no_sink_places += 1
+    if (sink is not None) and (source is not None) and no_source_places == 1 and no_sink_places == 1:
+        # If there is one unique source and sink place, short circuit Petri Net is constructed
+        t_1 = PetriNet.Transition("short_circuited_transition", "short_circuited_transition")
+        s_c_net.transitions.add(t_1)
+        # add arcs in short-circuited net
+        petri_utils.add_arc_from_to(sink, t_1, s_c_net)
+        petri_utils.add_arc_from_to(t_1, source, s_c_net)
+        return s_c_net
+    else:
+        if sink is None:
+            if print_diagnostics:
+                print("There is no sink place.")
+            return None
+        elif source is None:
+            if print_diagnostics:
+                print("There is no source place.")
+            return None
+        elif no_source_places > 1:
+            if print_diagnostics:
+                print("There is more than one source place.")
+            return None
+        elif no_sink_places > 1:
+            if print_diagnostics:
+                print("There is more than one sink place.")
+            return None
+
+
+def step_1(woflan_object, return_asap_when_unsound=False):
+    """
+    In the first step, we check if the input is given correct. We check if net is an PM4Py Petri Net representation
+    and if the exist a correct entry for the initial and final marking.
+    :param woflan_object: Object that contains all necessary information
+    :return: Proceed with step 2 if ok; else False
+    """
+
+    def check_if_marking_in_net(marking, net):
+        """
+        Checks if the marked place exists in the Petri Net and if there is only one i_m and f_m
+        :param marking: Marking of Petri Net
+        :param net: PM4Py representation of Petri Net
+        :return: Boolean. True if marking can exists; False if not.
+        """
+        for place in marking:
+            if place in net.places:
+                return True
+        return False
+
+    if isinstance(woflan_object.get_net(), PetriNet):
+        if len(woflan_object.get_initial_marking()) != 1 or len(woflan_object.get_final_marking()) != 1:
+            if woflan_object.print_diagnostics:
+                print('There is more than one initial or final marking.')
+            return False
+        if check_if_marking_in_net(woflan_object.get_initial_marking(), woflan_object.get_net()):
+            if check_if_marking_in_net(woflan_object.get_final_marking(), woflan_object.get_net()):
+                if woflan_object.print_diagnostics:
+                    print("Input is ok.")
+                return step_2(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    if woflan_object.print_diagnostics:
+        print('The Petri Net is not PM4Py Petri Net represenatation.')
+    return False
+
+
+def step_2(woflan_object, return_asap_when_unsound=False):
+    """
+    This method checks if a given Petri net is a workflow net. First, the Petri Net gets short-circuited
+    (connect start and end place with a tau-transition. Second, the Petri Net gets converted into a networkx graph.
+    Finally, it is tested if the resulting graph is a strongly connected component.
+    :param woflan_object: Woflan objet containing all information
+    :return: Bool=True if net is a WF-Net
+    """
+
+    def transform_petri_net_into_regular_graph(still_need_to_discover):
+        """
+        Ths method transforms a list of places and transitions into a networkx graph
+        :param still_need_to_discover: set of places and transition that are not fully added to graph
+        :return:
+        """
+        G = nx.DiGraph()
+        while len(still_need_to_discover) > 0:
+            element = still_need_to_discover.pop()
+            G.add_node(element.name)
+            for in_arc in element.in_arcs:
+                G.add_node(in_arc.source.name)
+                G.add_edge(in_arc.source.name, element.name)
+            for out_arc in element.out_arcs:
+                G.add_node(out_arc.target.name)
+                G.add_edge(element.name, out_arc.target.name)
+        return G
+
+    woflan_object.set_s_c_net(short_circuit_petri_net(woflan_object.get_net(),
+                                                      print_diagnostics=woflan_object.print_diagnostics))
+    if woflan_object.get_s_c_net() == None:
+        return False
+    to_discover = woflan_object.get_s_c_net().places | woflan_object.get_s_c_net().transitions
+    graph = transform_petri_net_into_regular_graph(to_discover)
+    if not nx.algorithms.components.is_strongly_connected(graph):
+        if woflan_object.print_diagnostics:
+            print('Petri Net is a not a worflow net.')
+        return False
+    else:
+        if woflan_object.print_diagnostics:
+            print("Petri Net is a workflow net.")
+        return step_3(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_3(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_place_invariants(compute_place_invariants(woflan_object.get_s_c_net()))
+    woflan_object.set_uniform_place_invariants(transform_basis(woflan_object.get_place_invariants(), style='uniform'))
+    woflan_object.set_s_components(
+        compute_s_components(woflan_object.get_s_c_net(), woflan_object.get_uniform_place_invariants()))
+    woflan_object.set_uncovered_places_s_component(
+        compute_uncovered_places_in_component(woflan_object.get_s_components(), woflan_object.get_s_c_net()))
+    if len(woflan_object.get_uncovered_places_s_component()) == 0:
+        woflan_object.set_left(True)
+        if woflan_object.print_diagnostics:
+            print('Every place is covered by s-components.')
+        return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        if woflan_object.print_diagnostics:
+            print('The following places are not covered by an s-component: {}.'.format(
+                woflan_object.get_uncovered_places_s_component()))
+        if return_asap_when_unsound:
+            return False
+        return step_4(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_4(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_not_well_handled_pairs(compute_not_well_handled_pairs(woflan_object.get_s_c_net()))
+    if len(woflan_object.get_not_well_handled_pairs()) == 0:
+        if woflan_object.print_diagnostics:
+            print('Petri Net is unsound')
+        woflan_object.set_left(False)
+        if return_asap_when_unsound:
+            return False
+        return step_5(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        if woflan_object.print_diagnostics:
+            print('Not well-handled pairs are: {}.'.format(woflan_object.get_not_well_handled_pairs()))
+        woflan_object.set_left(True)
+        return step_5(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_5(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_uncovered_places_uniform(
+        compute_uncovered_place_in_invariants(woflan_object.get_uniform_place_invariants(),
+                                              woflan_object.get_s_c_net()))
+    if len(woflan_object.get_uncovered_places_uniform()) == 0:
+        if woflan_object.print_diagnostics:
+            print('There are no uncovered places in uniform invariants.')
+        return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        if woflan_object.print_diagnostics:
+            print('The following places are uncovered in uniform invariants: {}'.format(
+                woflan_object.get_uncovered_places_uniform()))
+        return step_6(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_6(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_weighted_place_invariants(transform_basis(woflan_object.get_place_invariants(), style='weighted'))
+    woflan_object.set_uncovered_places_weighted(
+        compute_uncovered_place_in_invariants(woflan_object.get_weighted_place_invariants(),
+                                              woflan_object.get_s_c_net()))
+    if len(woflan_object.get_uncovered_places_weighted()) == 0:
+        if woflan_object.print_diagnostics:
+            print('There are no uncovered places in weighted invariants.')
+        return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        if woflan_object.print_diagnostics:
+            print('The following places are uncovered in weighted invariants: {}'.format(
+                woflan_object.get_uncovered_places_weighted()))
+        return step_7(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_7(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_mcg(minimal_coverability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(),
+                                                     woflan_object.get_net()))
+    if len(check_for_improper_conditions(woflan_object.get_mcg())) == 0:
+        if woflan_object.print_diagnostics:
+            print('No improper coditions.')
+        if woflan_object.get_left == True:
+            return step_8(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+        else:
+            return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        if woflan_object.print_diagnostics:
+            print('Improper WPD. The following are the improper conditions: {}.'.format(
+                check_for_improper_conditions(woflan_object.get_mcg())))
+        if return_asap_when_unsound:
+            return False
+        return step_9(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_8(woflan_object, return_asap_when_unsound=False):
+    if check_for_substates(woflan_object.get_mcg()):
+        return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_9(woflan_object, return_asap_when_unsound=False):
+    if woflan_object.print_diagnostics:
+        print('The following sequences are unbounded: {}'.format(compute_unbounded_sequences(woflan_object)))
+    return False
+
+
+def step_10(woflan_object, return_asap_when_unsound=False):
+    if woflan_object.get_mcg() == None:
+        woflan_object.set_mcg(
+            minimal_coverability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(),
+                                       woflan_object.get_net()))
+    woflan_object.set_dead_tasks(check_for_dead_tasks(woflan_object.get_s_c_net(), woflan_object.get_mcg()))
+    if len(woflan_object.get_dead_tasks()) == 0:
+        if woflan_object.print_diagnostics:
+            print('There are no dead tasks.')
+        if woflan_object.get_left() == True:
+            return step_11(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+        else:
+            if return_asap_when_unsound:
+                return False
+            return step_12(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+    else:
+        if woflan_object.print_diagnostics:
+            print('The following tasks are dead: {}'.format(woflan_object.get_dead_tasks()))
+        return False
+
+
+def step_11(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_r_g_s_c(
+        reachability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(), woflan_object.get_net()))
+    if nx.is_strongly_connected(woflan_object.get_r_g_s_c()):
+        if woflan_object.print_diagnostics:
+            print('All tasks are live.')
+        return True
+    else:
+        if return_asap_when_unsound:
+            return False
+        return step_13(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_12(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_r_g_s_c(
+        reachability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(), woflan_object.get_net()))
+    if woflan_object.print_diagnostics:
+        print('There are non-live tasks.')
+    if return_asap_when_unsound:
+        return False
+    return step_13(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+
+def step_13(woflan_object, return_asap_when_unsound=False):
+    woflan_object.set_locking_scenarios(compute_non_live_sequences(woflan_object))
+    if woflan_object.print_diagnostics:
+        print('The following sequences lead to deadlocks: {}.'.format(woflan_object.get_locking_scenarios()))
+    return False
+
+
+@deprecation.deprecated('2.2.2', removed_in='3.0.0',
+                        details='deprecated version of WOFLAN; use pm4py.algo.analysis.woflan')
+def apply(net, i_m, f_m, parameters=None):
+    """
+    Apply the Woflan Soundness check. Trough this process, different steps are executed.
+    :param net: Petri Net representation of PM4Py
+    :param i_m: initial marking of given Net. Marking object of PM4Py
+    :param f_m: final marking of given Net. Marking object of PM4Py
+    :return: True, if net is sound; False otherwise.
+    """
+    warnings.warn('deprecated version of WOFLAN; use pm4py.algo.analysis.woflan',
+                  DeprecationWarning)
+    if parameters is None:
+        parameters = {}
+    return_asap_when_unsound = exec_utils.get_param_value(Parameters.RETURN_ASAP_WHEN_NOT_SOUND, parameters, False)
+    print_diagnostics = exec_utils.get_param_value(Parameters.PRINT_DIAGNOSTICS, parameters, True)
+    return_diagnostics = exec_utils.get_param_value(Parameters.RETURN_DIAGNOSTICS, parameters, False)
+
+    woflan_object = woflan(net, i_m, f_m, print_diagnostics=print_diagnostics)
+    step_1_res = step_1(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
+
+    if return_diagnostics:
+        return step_1_res, woflan_object.get_output()
+
+    return step_1_res
+
+
+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 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
diff --git a/src/evaluation/soundness/woflan/graphs/__init__.py b/src/evaluation/soundness/woflan/graphs/__init__.py
new file mode 100644
index 0000000..4855257
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.graphs import utility, minimal_coverability_graph, reachability_graph, restricted_coverability_graph
\ No newline at end of file
diff --git a/src/evaluation/soundness/woflan/graphs/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..74a70e1f9f35c8fe049f475dc789ce98a56de058
GIT binary patch
literal 1101
zcmd1j<>g{vU|^Vf$2UEhnStRkh=Yuo7#J8F7#J9e6&M&8QW&BbQW#U1au{=&qL^}-
zqnLA9qF8cSqgWXkQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM
z1XL<$_~sX-DtP9l<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;P
zXat#9l$xqgoS#-wo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9
znN|rlA+toGG%qE!NTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01
zRUxrhp#UUOoRONMkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHl
zjA52QP1I4yF9JJ9Be4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOd
zVhPC0<(WA-3Q4I7rNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^
zOG^||%Tgg`W#$&-WTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiD
za}5qwaPtpRa8w9z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`B
zW==8G&yo413dI@ur8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS
z(w&}yf@hjSUVe#=LUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?{3XtLa5FD=Q;
z$t<b7C6$|*mzkTG6Q7)4mRgjU1Qv}?FG?)PxFuASnwXq{DkfW$3QCj7C8;S`wD@VV
z-C~cAPsvY?kB?GHElbQP1*ajsVo-9*OD!(eE6-2MNzBs&+fZBt%8EtI3=9lKEFglF
zfq`KqLlHZO4I+MJ>W3Dm78UCkm*?lC>AR#BXP4v`=!4vxmmZ&<S)yNDT98_#Tb5Xq
znV46in_5wjlV6ltq+eW=tdFo>A8x%q#Cm;*_4@JgnR%Hd@$q^EmA5!-a`RJ4b5iXX
NK}7?|IV=n^0s!CTUU~ok

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/__pycache__/utility.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/__pycache__/utility.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b1530b9a7d2962d50aef93f7f617d52411e6e553
GIT binary patch
literal 5420
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
Y5a3|sVB(PE;OF4t;^q+K5aQth01uYM$N&HU

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__init__.py b/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__init__.py
new file mode 100644
index 0000000..c7dc477
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.graphs.minimal_coverability_graph import minimal_coverability_graph
diff --git a/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..610b0d1035c2be275c910f7099fa5bae47ffc542
GIT binary patch
literal 1068
zcmd1j<>g{vU|^Vk$2Z-DnStRkh=Yuo7#J8F7#J9eMHm<uQW&BbQW#U1au{=&qL>&N
zQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM1XL<$_~sX-DtP9l
z<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;PXat#9l$xqgoS#-w
zo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9nN|rlA+toGG%qE!
zNTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01RUxrhp#UUOoRONM
zkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHljA52QP1I4yF9JJ9
zBe4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOdVhPC0<(WA-3Q4I7
zrNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^OG^||%Tgg`W#$&-
zWTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiDa}5qwaPtpRa8w9z
z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`BW==8G&yo413dI@u
zr8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS(w&}yf@hjSUVe#=
zLUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?}dYck%F%FWEn%uURRPtGq(ElNzv
z%*iaNj8890EXeTFWWL27AD@z+93LNLn_8BbQwmN8dc~k5k(XLrtXH0&mXnyL2UcFJ
zhuf$kQ0`jEP{hK(03m+0=!X`k78UCkm*?lC>AR#BXP4v`=z~niOOH>_EYUA6El4fW
zElVuQOw23MO|2-%$uCMR(l0Jb)<-x+AMO-=h*R_-P5~Q)-B<eY@tJv<CGqik1(mlr
WY;yBcN^?@}7(qE7<T@4x0RaHoo>x5p

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__pycache__/minimal_coverability_graph.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/__pycache__/minimal_coverability_graph.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cadd5961672256893ba7c48e3ba32a774f20bf32
GIT binary patch
literal 5951
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
b3K||@C<fKdEDTHn9IT+KgolxfnXecC@Jvgg

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/minimal_coverability_graph.py b/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/minimal_coverability_graph.py
new file mode 100644
index 0000000..e149db5
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/minimal_coverability_graph/minimal_coverability_graph.py
@@ -0,0 +1,186 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+"""
+This module is based on:
+The minimal coverability graph for Petri nets
+from Alain Finkel
+"""
+import numpy as np
+import networkx as nx
+from evaluation.soundness.woflan.graphs import utility as helper
+from copy import copy
+
+
+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 apply(net, initial_marking, original_net=None):
+    """
+    Apply method from the "outside".
+    :param net: Petri Net object
+    :param initial_marking: Initial marking of the Petri Net object
+    :param original_net: Petri Net object without short-circuited transition. For better usability, initial set to None
+    :return: MultiDiGraph networkx object
+    """
+
+    def detect_same_labelled_nodes(G):
+        same_labels = {}
+        for node in G.nodes:
+            if np.array2string(G.nodes[node]['marking']) not in same_labels:
+                same_labels[np.array2string(G.nodes[node]['marking'])] = [node]
+            else:
+                same_labels[np.array2string(G.nodes[node]['marking'])].append(node)
+        return same_labels
+
+    def merge_nodes_of_same_label(G, same_labels):
+        for marking in same_labels:
+            if len(same_labels[marking]) > 1:
+                origin = same_labels[marking][0]
+                i = 1
+                while i < len(same_labels[marking]):
+                    G = nx.contracted_nodes(G, origin, same_labels[marking][i])
+                    i += 1
+        return G
+
+    mct, firing_dict, req_dict = minimal_coverability_tree(net, initial_marking, original_net)
+    mcg = merge_nodes_of_same_label(mct, detect_same_labelled_nodes(mct))
+
+    to_remove_edges = []
+    for edge in mcg.edges:
+        reachable_markings = helper.enabled_markings(firing_dict, req_dict, mcg.nodes[edge[0]]['marking'])
+        not_reachable = True
+        for el in reachable_markings:
+            if np.array_equal(el[0], mcg.nodes[edge[1]]['marking']):
+                not_reachable = False
+                break
+        if not_reachable:
+            to_remove_edges.append(edge)
+    for edge in to_remove_edges:
+        mcg.remove_edge(edge[0], edge[1])
+    return mcg
diff --git a/src/evaluation/soundness/woflan/graphs/reachability_graph/__init__.py b/src/evaluation/soundness/woflan/graphs/reachability_graph/__init__.py
new file mode 100644
index 0000000..cb23bcc
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/reachability_graph/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.graphs.reachability_graph import reachability_graph
diff --git a/src/evaluation/soundness/woflan/graphs/reachability_graph/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/reachability_graph/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..356ec3c9aaa77f275ac426ee0e6f1038c5c79b5c
GIT binary patch
literal 1044
zcmd1j<>g{vU|`sA$2Z-CnStRkh=Yuo7#J8F7#J9eMHm<uQW&BbQW#U1au{=&qL>&N
zQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM1XL<$_~sX-DtP9l
z<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;PXat#9l$xqgoS#-w
zo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9nN|rlA+toGG%qE!
zNTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01RUxrhp#UUOoRONM
zkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHljA52QP1I4yF9JJ9
zBe4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOdVhPC0<(WA-3Q4I7
zrNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^OG^||%Tgg`W#$&-
zWTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiDa}5qwaPtpRa8w9z
z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`BW==8G&yo413dI@u
zr8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS(w&}yf@hjSUVe#=
zLUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?}dYck#vDoRaE&PYtk%*iaNj8890
zEXeTFWWL27AD@z+93LNLnp&2aQwolMy<$*O$V)9Q)+^6X%Sp`B11m4q!!)7@l$%yE
z6tOTcK!{&8`k}?CMaBBX<@q^j`Yx%(*(Lb}`nj2TnR)5)>6s<^#ia$QMY?5)MVX0t
zCAz5<1v&XesYUw5MalXIC+NeSpbv3^KEw(7m`>1-kI&4@EQycTE2zB1VUwGmQks)$
P#|X;dAXl+42nYZG@V`v}

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/reachability_graph/__pycache__/reachability_graph.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/reachability_graph/__pycache__/reachability_graph.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..029dc4aa5044bd9ea3d7c1b19c816dd79f30cc94
GIT binary patch
literal 2223
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
K9E?1Se9QnYTdiFH

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/reachability_graph/reachability_graph.py b/src/evaluation/soundness/woflan/graphs/reachability_graph/reachability_graph.py
new file mode 100644
index 0000000..6700e6b
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/reachability_graph/reachability_graph.py
@@ -0,0 +1,56 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import networkx as nx
+import numpy as np
+from evaluation.soundness.woflan.graphs import utility as helper
+
+
+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
diff --git a/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__init__.py b/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__init__.py
new file mode 100644
index 0000000..9b54423
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.graphs.restricted_coverability_graph import restricted_coverability_graph
diff --git a/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e0540b59c9cbccd0fdf1a420b4c396ce2521a4e7
GIT binary patch
literal 1077
zcmd1j<>g{vU|=|U$2Z-JnStRkh=Yuo7#J8F7#J9eMHm<uQW&BbQW#U1au{=&qL>&N
zQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM1XL<$_~sX-DtP9l
z<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;PXat#9l$xqgoS#-w
zo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9nN|rlA+toGG%qE!
zNTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01RUxrhp#UUOoRONM
zkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHljA52QP1I4yF9JJ9
zBe4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOdVhPC0<(WA-3Q4I7
zrNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^OG^||%Tgg`W#$&-
zWTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiDa}5qwaPtpRa8w9z
z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`BW==8G&yo413dI@u
zr8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS(w&}yf@hjSUVe#=
zLUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?}dYck%FElLF?t>lu_l=$TQvecr)
zq|BVmlFIn>qQrs>KTYOa?D6p_`N{F|Q4XnPi8-a<q@Y&}N)>sj#l?E%`Dr<cd3s>w
z#d>&6D+1-Ql?+8J3=9zBSG#^_acWVqesOtzPMW?;YH@Z+et~{&W?p7qdVG3jiGFcu
zL28k1Sz=LUVqS@EYDGa#eo<<XesNK<KEgHnaM$QVT%!+hjXqx2=*P!r=4F<|$LkeT
Z-r}&y%}*)KNws4H6#*azvM>k;002mUTWA0P

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__pycache__/restricted_coverability_graph.cpython-310.pyc b/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/__pycache__/restricted_coverability_graph.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..357971bbf8304707ad42eafbf85db13656fa6522
GIT binary patch
literal 2923
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

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/restricted_coverability_graph.py b/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/restricted_coverability_graph.py
new file mode 100644
index 0000000..e0019e1
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/restricted_coverability_graph/restricted_coverability_graph.py
@@ -0,0 +1,89 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import numpy as np
+import networkx as nx
+from evaluation.soundness.woflan.graphs import utility as helper
+
+
+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 check_if_transition_unique(marking, graph, transition):
+    for edge in graph.out_edges(marking):
+        if graph[edge[0]][edge[1]]['transition'] == transition:
+            return False
+    return True
+
+
+def check_for_smaller_marking(marking, coverability_graph, index, current_node, look_up_indices):
+    for node in coverability_graph.nodes:
+        if all(np.less_equal(coverability_graph.nodes[node]['marking'], marking[0])):
+            if coverability_graph.nodes[node]['marking'][index] < marking[0][index]:
+                if nx.has_path(coverability_graph,
+                               look_up_indices[np.array2string(coverability_graph.nodes[node]['marking'])],
+                               current_node):
+                    return True
+    return False
diff --git a/src/evaluation/soundness/woflan/graphs/utility.py b/src/evaluation/soundness/woflan/graphs/utility.py
new file mode 100644
index 0000000..ef646a4
--- /dev/null
+++ b/src/evaluation/soundness/woflan/graphs/utility.py
@@ -0,0 +1,139 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import numpy as np
+import networkx as nx
+
+def compute_incidence_matrix(net):
+    """
+    Given a Petri Net, the incidence matrix is computed. An incidence matrix has n rows (places) and m columns
+    (transitions).
+    :param net: Petri Net object
+    :return: Incidence matrix
+    """
+    n = len(net.transitions)
+    m = len(net.places)
+    C = np.zeros((m, n))
+    i = 0
+    transition_list = list(net.transitions)
+    place_list = list(net.places)
+    while i < n:
+        t = transition_list[i]
+        for in_arc in t.in_arcs:
+            # arcs that go to transition
+            C[place_list.index(in_arc.source), i] -= (1*in_arc.weight)
+        for out_arc in t.out_arcs:
+            # arcs that lead away from transition
+            C[place_list.index(out_arc.target), i] += (1*out_arc.weight)
+        i += 1
+    return C
+
+
+def split_incidence_matrix(matrix, net):
+    """
+    We split the incidence matrix columnwise to get the firing information for each transition
+    :param matrix: incidence matrix
+    :param net: Petri Net
+    :return: Dictionary, whereby the key is an np array that contains the firing information and the value is the name
+    of the transition
+    """
+    transition_dict = {}
+    i = 0
+    while i < len(net.transitions):
+        transition_dict[list(net.transitions)[i]] = np.hsplit(np.transpose(matrix), 1)[0][i]
+        i += 1
+    return transition_dict
+
+def compute_firing_requirement(net):
+    place_list=list(net.places)
+    transition_dict={}
+    for transition in net.transitions:
+        temp_array=np.zeros(len(place_list))
+        for arc in transition.in_arcs:
+            temp_array[place_list.index(arc.source)] -=1*arc.weight
+        transition_dict[transition]=temp_array
+    return transition_dict
+
+def enabled_markings(firing_dict, req_dict,marking):
+    enabled_transitions = []
+    for transition, requirment in req_dict.items():
+        if all(np.greater_equal(marking, requirment.copy()*-1)):
+            enabled_transitions.append(transition)
+    new_markings = []
+    for transition in enabled_transitions:
+        new_marking = marking + firing_dict[transition]
+        new_markings.append((new_marking, transition))
+    return new_markings
+
+def convert_marking(net, marking, original_net=None):
+    """
+    Takes an marking as input and converts it into an Numpy Array
+    :param net: PM4Py Petri Net object
+    :param marking: Marking that should be converted
+    :param original_net: PM4Py Petri Net object without short-circuited transition
+    :return: Numpy array representation
+    """
+    marking_list=list(el.name for el in marking.keys())
+    place_list = list(el.name for el in net.places)
+    mark = np.zeros(len(place_list))
+    for index, value in enumerate(mark):
+        if place_list[index] in marking_list:
+            #TODO: Is setting the value to 1 ok in this case?
+            mark[index]=1
+    return mark
+
+def check_for_dead_tasks(net, graph):
+    """
+    We compute a list of dead tasks. A dead task is a task which does not appear in the Minimal Coverability Graph
+    :param net: Petri Net representation of PM4Py
+    :param graph: Minimal coverability graph. NetworkX MultiDiGraph object.
+    :return: list of dead tasks
+    """
+    tasks=[]
+    for transition in list(net.transitions):
+        if transition.label != None:
+            tasks.append(transition)
+    for node,targets in graph.edges()._adjdict.items():
+        for target_node,activties in targets.items():
+            for option,activity in activties.items():
+                if activity['transition'] in tasks:
+                    tasks.remove(activity['transition'])
+    return tasks
+
+def check_for_improper_conditions(mcg):
+    """
+    An improper condition is a state in the minimum-coverability graph with an possible infinite amount of tokens
+    :param mcg: networkx object (minimal coverability graph)
+    :return: True, if there are no improper conditions; false otherwise
+    """
+    improper_states=[]
+    for node in mcg.nodes:
+        if np.inf in mcg.nodes[node]['marking']:
+            improper_states.append(node)
+    return improper_states
+
+def check_for_substates(mcg):
+    """
+    Checks if a substate exists in a given mcg
+    :param mcg: Minimal coverability graph (networkx object)
+    :return: True, if there exist no substate; False otherwise
+    """
+    for node in mcg.nodes:
+        reachable_states = nx.descendants(mcg, node)
+        for state in reachable_states:
+            if all(np.less(mcg.nodes[node]['marking'],mcg.nodes[state]['marking'])):
+                return False
+    return True
diff --git a/src/evaluation/soundness/woflan/not_well_handled_pairs/__init__.py b/src/evaluation/soundness/woflan/not_well_handled_pairs/__init__.py
new file mode 100644
index 0000000..05f5f47
--- /dev/null
+++ b/src/evaluation/soundness/woflan/not_well_handled_pairs/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.not_well_handled_pairs import not_well_handled_pairs
diff --git a/src/evaluation/soundness/woflan/not_well_handled_pairs/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/not_well_handled_pairs/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c4473ff44e33d890a9527d36972ce51162493e36
GIT binary patch
literal 1042
zcmd1j<>g{vU|@K9$2Z-SnStRkh=Yuo7#J8F7#J9eMHm<uQW&BbQW#U1au{=&qL>&N
zQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM1XL<$_~sX-DtP9l
z<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;PXat#9l$xqgoS#-w
zo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9nN|rlA+toGG%qE!
zNTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01RUxrhp#UUOoRONM
zkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHljA52QP1I4yF9JJ9
zBe4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOdVhPC0<(WA-3Q4I7
zrNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^OG^||%Tgg`W#$&-
zWTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiDa}5qwaPtpRa8w9z
z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`BW==8G&yo413dI@u
zr8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS(w&}yf@hjSUVe#=
zLUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?}dYck#v%gZl`FHg<MiO)#POUX%1
zi7!aZEGqWXWWL27AD@z+93LNLlv<XUQwmN4dc~mRke6CqtXH0&mXnyLhh2XWC_AlW
zC}Lq?fDpeb^+StOi;DG&%ky*6^j%VmvrF;|^m8-wGV{{o(=$u-i%Sbqi*(Bpi!u}Q
zN_0~z3Ucy`Qj7G9i<0#b_Ups#*N51zkKKO#`1s7c%#!$cy@JYH95%W6DWy57c8s9>
L4RR6-gMa`4aG^}v

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/not_well_handled_pairs/__pycache__/not_well_handled_pairs.cpython-310.pyc b/src/evaluation/soundness/woflan/not_well_handled_pairs/__pycache__/not_well_handled_pairs.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..622581dfc87812eca74c98dd856d4429946ff1cb
GIT binary patch
literal 2426
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

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/not_well_handled_pairs/not_well_handled_pairs.py b/src/evaluation/soundness/woflan/not_well_handled_pairs/not_well_handled_pairs.py
new file mode 100644
index 0000000..e025631
--- /dev/null
+++ b/src/evaluation/soundness/woflan/not_well_handled_pairs/not_well_handled_pairs.py
@@ -0,0 +1,64 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import networkx as nx
+
+def create_network_graph(net):
+    """
+    Transform a given Petri Net in a network graph. Each place and transition is node and gets duplicated.
+    The even numbers handle the inputs of a node, the odds the output.
+    :param net: PM4Py Petri Net representation
+    :return: networkx.DiGraph(), bookkeeping dictionary
+    """
+    graph = nx.DiGraph()
+    places = list(net.places)
+    transitions = list(net.transitions)
+    nodes=set(places) | set(transitions)
+    bookkeeping={}
+    for index,el in enumerate(nodes):
+        bookkeeping[el]=index*2
+    for node in nodes:
+        graph.add_node(bookkeeping[node])
+        graph.add_node(bookkeeping[node]+1)
+        graph.add_edge(bookkeeping[node], bookkeeping[node]+1, capacity=1)
+    #add edges for outgoing arcs in former Petri Net
+    for element in nodes:
+        for arc in element.out_arcs:
+            graph.add_edge(bookkeeping[element]+1, bookkeeping[arc.target], capacity=1)
+    #add edges for ingoing arcs in former Petri Net
+    for element in nodes:
+        for arc in element.in_arcs:
+            graph.add_edge(bookkeeping[arc.source]+1, bookkeeping[element], capacity=1)
+    return graph,bookkeeping
+
+def apply(net):
+    """
+    Using the max-flow min-cut theorem, we compute a list of nett well handled TP and PT pairs
+    (T=transition, P=place)
+    :param net: Petri Net
+    :return: List
+    """
+    graph,booking=create_network_graph(net)
+    pairs=[]
+    for place in net.places:
+        for transition in net.transitions:
+            p=booking[place]
+            t=booking[transition]
+            if nx.maximum_flow_value(graph, p+1, t)>1:
+                pairs.append((p+1,t))
+            if nx.maximum_flow_value(graph, t+1, p)>1:
+                pairs.append((t+1,p))
+    return pairs
diff --git a/src/evaluation/soundness/woflan/place_invariants/__init__.py b/src/evaluation/soundness/woflan/place_invariants/__init__.py
new file mode 100644
index 0000000..2c856d1
--- /dev/null
+++ b/src/evaluation/soundness/woflan/place_invariants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.place_invariants import place_invariants, s_component, uniform_invariant, utility
diff --git a/src/evaluation/soundness/woflan/place_invariants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/soundness/woflan/place_invariants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..842536a4fe3905874110b6b9b4ad256f5c2bf694
GIT binary patch
literal 1092
zcmd1j<>g{vU|{%l$2UEQnStRkh=Yuo7#J8F7#J9e6&M&8QW&BbQW#U1au{=&qL^}-
zqnLA9qF8cSqgWXkQka7oG+7dlGcho5DJUo?gk)qEE2L%Sq$*?<D-<Lal_=z=DFpbM
z1XL<$_~sX-DtP9l<y$Fal#~<{Tj}c;<eC&z>ZN6t=%p1UmgZ&Tr==F@rKD==ae+;P
zXat#9l$xqgoS#-wo>-J>rBIn)s*s$Rr%;rdl383*l$lgolB$qdqL7%EqMu)+kei>9
znN|rlA+toGG%qE!NTDPnRiPxcD7P5oAP~phFI2%jH7~U&F-IYwG$|)DS-~eWIW@01
zRUxrhp#UUOoRONMkOVOZq{R*7ieRWK6x{Ml^HLH^GV}9v6jC!wGE$2a%2JDpGxPHl
zjA52QP1I4yF9JJ9Be4YJ_#%b;0+2>cg~Yr{g`C6^gkE%?BRrU*keLVeMn--?szOOd
zVhPC0<(WA-3Q4I7rNyafr8zoaTarpk6v912Jp4mL6de5`6~Y~Zf*k!qBCQq5GfOh^
zOG^||%Tgg`W#$&-WTvJllqVJyCFYe>D&(hujqr61a`tfa3vqPv^zjUdRLCz<aPtiD
za}5qwaPtpRa8w9z3<~jd4)t*iQV0kQ3h)nh)l*OiP6atE6>I{Y@J$29QBkTwN@_`B
zW==8G&yo413dI@ur8y}I8Hr`73Pq{OshMS|DGG@S$@v8!r-8kX+hDN0i8=Xs>0pmS
z(w&}yf@hjSUVe#=LUC%Uf(<O`m6w<6rRSCE<rk&v=RhL9Sl>>M>m?{3XtLZAD9A}n
zPL0pZD@!cOOw21OzQtV}pPZjtke`>DS8_|RG%qtPzbF@>;1+vnNoG!FNu{4A+b#C^
z_>}zQ`1mND)Uw2!QgAZSD+Z;Qywu`iz4H9DoWwjmbSsKL`L2kWfq|ij1w^niFfgoS
zC}Ib(LBy|O{m|mnqGJ8x^8B1MeV5eY?2`Nf{oKsF%)IpY^vn|d;?jcDB3-DHbyF(}
za`KB(i}Z_&lJyY|(T6)kAL0;wbcg82$7kkcmc+;F6;$5hu*uC&Da}c>V+555AcwIq
H$Or%c<*{2h

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/place_invariants/__pycache__/place_invariants.cpython-310.pyc b/src/evaluation/soundness/woflan/place_invariants/__pycache__/place_invariants.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b3c4ef8ebc11e41f3bf6cfb1a7a8fb015dea5484
GIT binary patch
literal 2672
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
L1BU<yBM%b*<3t%;

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/place_invariants/__pycache__/s_component.cpython-310.pyc b/src/evaluation/soundness/woflan/place_invariants/__pycache__/s_component.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b1b3d43cb875ab94d4cf042390f6da3addce3a7
GIT binary patch
literal 3315
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
Y=A_zz%Ccfm%4K2T5#V6r;Nmd^08F@^D*ylh

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/place_invariants/__pycache__/uniform_invariant.cpython-310.pyc b/src/evaluation/soundness/woflan/place_invariants/__pycache__/uniform_invariant.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4e4eece20dcf20ef62a30b39f141e42c92a12f5b
GIT binary patch
literal 1340
zcmd1j<>g{vU|<ls>znSy!octt#6iYP3=9ko3=9m#8Vn2!DGX5zDU2yhIgGhXQA~^=
zHggVhE=v>(BSQ*H3Tq2P6l)4wFoPz0;&CPh1}+5!1%;4|%wmPK%$!t(%wmOt#G(>~
z{4|9CUz31J1r6W)qErRXytI5Pg^ZGtf?_Lu{eoPRf=a!#%o4q{qQug?jQq6JBE6JU
zO+7BKX%LMd6N^$)6^irIO3D+9Qmqs!^Gg+y6Y~^`Qd2UEONug+N=s4|GD{Q^^HTKl
zixhJ6Q!>*k!6syuD3s=<q!uZZWTYyTq!#5CgB%3nxch}FxTof&7A58=1e7M_WF{;4
zWG1KP6{jjB7Aq8hM2a&~QxuXQ27$D=fm{&`b%lakeraAxVo7Fxo{mClW=TeBkwRH&
zQE_H|o`NyV5~ztf3i(A~=V&CBfE-_>kY51OsHu>cSE-PbSc1@t?sJ3(Qxr1uz~0En
zFGy7=$w({#dAU3@Cr2SERiU&vHLWy92W(4HX^BF(XNZS?Xo!NNU!+2~V^ENzUr402
zLV0FMMt*6DLTXtm#H`HRf}G6M6ovA{qN2pSl1hdAG_VoAu0hToj(#DIPM$uVA(0CC
zMG9`7A%3pG!3u8vK?;rv0ggc-p3b2@jzJ0mp+N!u!LE7=3c;x$hoyo|z!Sb{;5aHu
zRY*xKNzBYChWa@&zf_?(Bfm5!MIj@xELEW>H90l2EHy<TQ6V|M0OT~V_i-Bxwl^^+
zKQA5ZQAoPeQ&8|sQ^?CN(NQQ)O;xagCB5?Ua=rAtQoa15bp0Gi#24$^>2bXT<p)j1
zTN26nxdo*qsqqCliOH$)nR#W2MVX0tCB>SIxA;qn67!1F@{4lglM;(Fi<22anxL4O
zfq{XAfq}spl#e7B7#K<zY8aXsYZ*%zYZyWKsh6pisfH<-L6h0<7JF%4CdeF3##^k#
zC6zg;noLEYOjramMU(j!b6#r6Edg{VL<xhFcxGw}lDk$i6mc*xF#M{~4=qkDD%LM9
z&(BHIcS$YIF3B&@&&|xs%uA0?&n(d|E-gqc(uI0QH?^W5C%-7QNWZu!SwFQbF{c!q
zruB<K$vZE#xLChDKP@LQPaoY8`cS7MoS;`wd5bl%pdhDG1mt%%1_lNWMjl2M#v)Lu
z<EP1Xi#<L*B|kYn{+2DmCOx=KdJvoR&~3sH00+@66Ozm*Ey>KuEU5$sEZ9BZ&_W_O
f85kIDaoFVMr<CTT+JR!U7!;l?3_Jonj66&LE%2Qy

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/place_invariants/__pycache__/utility.cpython-310.pyc b/src/evaluation/soundness/woflan/place_invariants/__pycache__/utility.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c67d4152c917af1c7b3f51e3af9046d5769ed240
GIT binary patch
literal 4225
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
XPO2TK@Gb^PvM_K6a4>TT@HhhipQ@FW

literal 0
HcmV?d00001

diff --git a/src/evaluation/soundness/woflan/place_invariants/place_invariants.py b/src/evaluation/soundness/woflan/place_invariants/place_invariants.py
new file mode 100644
index 0000000..4dcd43c
--- /dev/null
+++ b/src/evaluation/soundness/woflan/place_invariants/place_invariants.py
@@ -0,0 +1,66 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import numpy as np
+import sympy
+
+def compute_place_invariants(net):
+    """
+    We compute the NUllspace of the incidence matrix and obtain the place-invariants.
+    :param net: Petri Net of which we want to know the place invariants.
+    :return: Set of place invariants of the given Petri Net.
+    """
+
+    def compute_incidence_matrix(net):
+        """
+        Given a Petri Net, the incidence matrix is computed. An incidence matrix has n rows (places) and m columns
+        (transitions).
+        :param net: Petri Net object
+        :return: Incidence matrix
+        """
+        n = len(net.transitions)
+        m = len(net.places)
+        C = np.zeros((m, n))
+        i = 0
+        transition_list = list(net.transitions)
+        place_list = list(net.places)
+        while i < n:
+            t = transition_list[i]
+            for in_arc in t.in_arcs:
+                # arcs that go to transition
+                C[place_list.index(in_arc.source), i] -= 1
+            for out_arc in t.out_arcs:
+                # arcs that lead away from transition
+                C[place_list.index(out_arc.target), i] += 1
+            i += 1
+        return C
+
+    def extract_basis_vectors(incidence_matrix):
+        """
+        The name of the method describes what we want t achieve. We calculate the nullspace of the transposed identity matrix.
+        :param incidence_matrix: Numpy Array
+        :return: a collection of numpy arrays that form a base of transposed A
+        """
+        # To have the same dimension as described as in https://www7.in.tum.de/~esparza/fcbook-middle.pdf and to get the correct nullspace, we have to transpose
+        A = np.transpose(incidence_matrix)
+        # exp from book https://www7.in.tum.de/~esparza/fcbook-middle.pdf
+        x = sympy.Matrix(A).nullspace()
+        # TODO: Question here: Will x be always rational? Depends on sympy implementation. Normaly, yes, we we will have rational results
+        x = np.array(x).astype(np.float64)
+        return x
+
+    A = compute_incidence_matrix(net)
+    return extract_basis_vectors(A)
diff --git a/src/evaluation/soundness/woflan/place_invariants/s_component.py b/src/evaluation/soundness/woflan/place_invariants/s_component.py
new file mode 100644
index 0000000..73b7f1a
--- /dev/null
+++ b/src/evaluation/soundness/woflan/place_invariants/s_component.py
@@ -0,0 +1,90 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.place_invariants.uniform_invariant import apply as compute_uniform_invariants
+
+def apply(net):
+    """
+    General method to obtain a list of S-components
+    :param net: Petri Net for which S-components should be computed
+    :return: A list of S-components
+    """
+    uniform_invariants=compute_uniform_invariants(net)
+    return compute_s_components(net, uniform_invariants)
+
+
+def compute_s_components(net, p_invariants):
+    """
+    We perform the hint in 5.4.4 of https://pure.tue.nl/ws/portalfiles/portal/1596223/9715985.pdf
+    :param p_invariants: Semi-positive basis we calculate previously
+    :return: A list of S-Components. A s-component consists of a set which includes all related transitions a places
+    """
+
+    def compare_lists(list1, list2):
+        """
+        :param list1: a list
+        :param list2: a list
+        :return: a number how often a item from list1 appears in list2
+        """
+        counter = 0
+        for el in list1:
+            if el in list2:
+                counter += 1
+        return counter
+
+    s_components = []
+    place_list = list(net.places)
+    for invariant in p_invariants:
+        i = 0
+        s_component = []
+        for el in invariant:
+            if el > 0:
+                place = place_list[i]
+                s_component.append(place)
+                for in_arc in place.in_arcs:
+                    s_component.append(in_arc.source)
+                for out_arc in place.out_arcs:
+                    s_component.append(out_arc.target)
+            i += 1
+        if len(s_component) != 0:
+            is_s_component = True
+            for el in s_component:
+                if el in net.transitions:
+                    places_before = [arc.source for arc in el.in_arcs]
+                    if compare_lists(s_component, places_before) != 1:
+                        is_s_component = False
+                        break
+                    places_after = [arc.target for arc in el.out_arcs]
+                    if compare_lists(s_component, places_after) != 1:
+                        is_s_component = False
+                        break
+            if is_s_component:
+                s_components.append(set(s_component))
+    return s_components
+
+def compute_uncovered_places_in_component(s_components, net):
+    """
+    We check for uncovered places
+    :param s_components: List of s_components
+    :param net: Petri Net representation of PM4Py
+    :return: List of uncovered places
+    """
+    place_list=list(net.places)
+    for component in s_components:
+        for el in component:
+            if el in place_list:
+                place_list.remove(el)
+    return place_list
diff --git a/src/evaluation/soundness/woflan/place_invariants/uniform_invariant.py b/src/evaluation/soundness/woflan/place_invariants/uniform_invariant.py
new file mode 100644
index 0000000..d6c8500
--- /dev/null
+++ b/src/evaluation/soundness/woflan/place_invariants/uniform_invariant.py
@@ -0,0 +1,24 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.soundness.woflan.place_invariants.place_invariants import compute_place_invariants
+from evaluation.soundness.woflan.place_invariants.utility import transform_basis
+
+def apply(net):
+    place_invariants= compute_place_invariants(net)
+    modified_invariants=transform_basis(place_invariants, style='uniform')
+    return modified_invariants
+
diff --git a/src/evaluation/soundness/woflan/place_invariants/utility.py b/src/evaluation/soundness/woflan/place_invariants/utility.py
new file mode 100644
index 0000000..cf2ec9b
--- /dev/null
+++ b/src/evaluation/soundness/woflan/place_invariants/utility.py
@@ -0,0 +1,118 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import numpy as np
+
+
+def removearray(L, arr):
+    """
+    Remove an array from a given list and return the list with the removed element.
+    :param L: list object
+    :param arr: array that has to be removed
+    :return: list object without array
+    """
+    ind = 0
+    size = len(L)
+    while ind != size and not np.array_equal(L[ind], arr):
+        ind += 1
+    if ind != size:
+        L.pop(ind)
+    else:
+        raise ValueError('array not found in list.')
+
+def transform_basis(basis, style=None):
+    """
+    We construct a (I)LP to transform our basis into a set of vectors by using linear combination to fit certain styles/
+    properties
+    :param basis: list of p-invariants. Commonly computed by the method 'compute_place_invariants' in
+    place_invariants.py
+    :param style: String that is used to construct certain constraints
+    At the moment, 'uniform' (all weights have value 0 or 1), and 'weighted' (all weights are >=0) are supported
+    :return: List of p-invariants that fits the style
+    """
+    import pulp
+
+    if style==None:
+        style='weighted'
+
+    # First, we want to check if a vector of a basis only contains non-positve entries. If so, then we multiply the
+    # vector -1.
+    modified_base = []
+    for vector in basis:
+        all_non_positiv = True
+        for entry in vector:
+            if entry > 0:
+                all_non_positiv = False
+        if all_non_positiv:
+            modified_base.append(-1 * vector)
+        else:
+            modified_base.append(vector)
+    #For uniform variants, it is necessary that the weight for a place is either 0 or 1. We collect the variants for
+    #which this condition does not hold. We also collect the variants for the weighted invariants the entry is <0.
+    to_modify = []
+    for vector in modified_base:
+        for entry in vector:
+            if ((entry < 0 or entry > 1) and style=='uniform') or ( entry < 0 and style=='weighted'):
+                to_modify.append(vector)
+                break
+    # if we have nothing to modify, we are done
+    if len(to_modify) > 0:
+        for vector in to_modify:
+            removearray(modified_base, vector)
+            set_B = range(0, len(modified_base))
+            prob = pulp.LpProblem("linear_combination", pulp.LpMinimize)
+            X = pulp.LpVariable.dicts("x", set_B, cat='Integer')
+            y = pulp.LpVariable("y", cat='Integer', lowBound=1)
+            # add objective
+            prob += pulp.lpSum(X[i] for i in set_B)
+            if style=='uniform':
+                # variables for uniform. Therefore, the resulting weight can either be 0 or 1
+                z = pulp.LpVariable.dicts("z", range(0, len(vector)), lowBound=0, upBound=1, cat='Integer')
+                # add constraints
+                for i in range(len(vector)):
+                    prob += pulp.lpSum(X[j]*modified_base[j][i] for j in range(len(modified_base)))+y*vector[i]== z[i]
+            elif style=='weighted':
+                for i in range(len(vector)):
+                    prob += pulp.lpSum(X[j]*modified_base[j][i] for j in range(len(modified_base)))+y*vector[i] >= 0
+            prob.solve()
+            new_vector = np.zeros(len(vector))
+            if style=='weighted':
+                for i in range(len(new_vector)):
+                    new_vector[i] = y.varValue * vector[i]
+                    for j in range(len(modified_base)):
+                        new_vector[i] = new_vector[i] + modified_base[j][i] * X[j].varValue
+            elif style=='uniform':
+                for i in range(len(new_vector)):
+                    new_vector[i] = z[i].varValue
+            modified_base.append(new_vector)
+    return modified_base
+
+def compute_uncovered_places(invariants, net):
+    """
+    Compute a list of uncovered places for invariants of a given Petri Net. Note that there exists a separate algorithm
+    for s-components
+    :param invariants: list of invariants. Each invariants is a numpy-Array representation
+    :param net: Petri Net object of PM4Py
+    :return: List of uncovered place over all invariants
+    """
+    place_list=list(net.places)
+    unncovered_list=place_list.copy()
+    for invariant in invariants:
+        for index, value in enumerate(invariant):
+            if value != 0:
+                if place_list[index] in unncovered_list:
+                    unncovered_list.remove(place_list[index])
+    return unncovered_list
diff --git a/src/evaluation/wf_net/__init__.py b/src/evaluation/wf_net/__init__.py
new file mode 100644
index 0000000..b8ddfdc
--- /dev/null
+++ b/src/evaluation/wf_net/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.wf_net import evaluator, variants
diff --git a/src/evaluation/wf_net/__pycache__/__init__.cpython-310.pyc b/src/evaluation/wf_net/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6e65adaa65b90217a1def9be58b8827b53ddf6c1
GIT binary patch
literal 982
zcmd1j<>g{vU|?vt?wc;i%)sy%#6iYP3=9ko3=9m#5)2FsDGX5zDU2yhIgGhXQB1ka
zQOt}CDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^
z6+H9O@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL
z&QB{TPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQo
znwOGVq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs
z&PYvBNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U
z7lEClkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZ
zg``x4(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLe
zGII-ZGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVu
zI4T4<28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0
zh2o6-(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)E
zFTX@bp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i+HJNU4rj{k<lqQzs7v170
zODxJv%quDO(`32D9v`2QpBx__B?wiJnV+Xuo)({%T2chcE=9}?3=At7idY#KAjGc-
z{m|mnqGJ8x^8B1MeV5eY?2`Nf{oKsF%)IpY^vn|d;?jcDB3-B@x~UZfIr&AYMf$}>
t$@&O8^dWZW$H!;pWtPOp>lIYq;;_lhPbtkwwPOV3Q5FUU1{MYp0RW)*F|Pms

literal 0
HcmV?d00001

diff --git a/src/evaluation/wf_net/__pycache__/evaluator.cpython-310.pyc b/src/evaluation/wf_net/__pycache__/evaluator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2d3139da6d04c932419207fe0dc2780ce71c0706
GIT binary patch
literal 1655
zcmd1j<>g{vU|`U?<D0&Oje+4Yh=Yt-85kHG7#J9ea~K#HQW&BbQW#U1au}l+Qy5d2
zbC`0OqnH^XVk}WCDU2yBIjp&CQEXr_)*SX+jwlXB26u)OwiNajh7|Tx=4R$7&Qz8x
zt`v?G&J?Z`?q22;rc}->?lh(po)q2|#%9JSo)o5F22H-i<4g<;TnY*b3LzPp#R_Se
zIjIVn#R>(9MI{RPX$k?pCIOWS8ov2OsS2KXY57(P86_nJ#a8<I1-T{#m3nEJC3<N^
ziKTfN`Dv*|dMT-zdR$=BAR0j?7Nw>t6z8XvlqVLYS}9cKmntMD<|!1VreqeE6lErr
zmZU0VmMA3VrRe7uDdgs-WTsVuO~@=!D9uYrEmA1SNL46FEy^thIS9mY_X|~UPt8j$
zO3YCRC{4=AOjhv8Ois-!PE|-ORww|86lbKSC?r7)0%>srxgr?q3I(_P(!7+!lFa-(
z9fj1)l8n?Mg|gJ5;>`R!1!I^cP!n|&@{7RE(MT)-Ilf3CzW}6BQz0?0QXwa?1fdt*
z=Lip`C}ie=y^)b$kg8CUkyrxqa(QM>jzUtZLTPboT4|0B*p{Ty5`}Qj5D)**5CuoS
zNQH36pdd%TkVtEV^30Nq{L&JI)Us5FS(&*7Ihm;`3gwAKMTvPOl?wT3U?Y58gPc7a
z{X!g_JbgSvA{FwB6x=*R{9J>B72N!T6dV--9D_nUokM*bgA@Wng97}6UG)?cf>S{b
zO9h*NCw$Yuaa5G5kdj)Gn3+=y^>bu?sX}o^erZmMLPla)szOm}a%yH-YKlUlLUMiq
z$Z25j<2D#<Z(>e<UOL#LkaVY~px~LNke6Sgqfnfhs$c_4dgbNidg*zkdih1^`Z<t@
zFV?ry<9Z3oQ+}F^w^&^BN^><CZ*dl+mK0^i=cSftGT!1!tw>FdFD=Q;DNY6{M8=G;
z3@E_Bz>vxi#hAhn#gxhn%51?5noPGi!V-%z6Z1-n{WMu`am2^xCFZ8a$KT?LkI&6d
zDa`?~dE(;>OA~V-GDXY`3=Fq816)IbJmdXbLsl{rfl2@{@heh4v^ce>SiiVDKPOG!
zCAB!aB)>pEH#09YFFigzvqZnRv>>%ew+v#XZfZqAPJU5pk$!PevVLk=VooVI<?5HG
zfjkBi$uH6?s4M~%BvPP)goS~DfrEjA5iC;0YNThRXH>;%tY@HS@XNLYl+(-8bn{Y6
z6p}Mile0lNJ~tl}UnTkAh=OG9#GLeey~Mo4oXTQwk&v97n4YT1e2X_FwV)_9Ik6-)
zB|bCn7FSU!STl&t4ow%ypa6h{Fo+FukTb|-N(>APB@7D~Qy5E_7BVq1a59uIFJP%*
zTF98iTEkGon8MTxDwvr4ig*|p7(nq>kXV$Mn_7}uRIJH%i=#ZT2ozAow^%@|TS6{S
zw`At$g+ru@Kz7~YPERd~hlb%T*2IE>oJvjRTg-W>B}Je}zQqoe1IG!3;AUW8C=z90
zV2}ejl#PLbfrpuiQA7wVrpbGYJw84qKRG_WNDvgeEU9^=xkXGMCO0fVGV}AIq!4kW
z2Z<v+s8z*~WWfaqT~JB{JGBTDYqz*yW~8PRal#w|aTh%46oFiLi^B$z=<GnLwpfgT
cfq{jALx6{ogNcWUhf#o;gN2Ea<-Zsc0Ci;A=Kufz

literal 0
HcmV?d00001

diff --git a/src/evaluation/wf_net/evaluator.py b/src/evaluation/wf_net/evaluator.py
new file mode 100644
index 0000000..c270f08
--- /dev/null
+++ b/src/evaluation/wf_net/evaluator.py
@@ -0,0 +1,52 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import warnings
+from enum import Enum
+
+import deprecation
+
+from evaluation.wf_net.variants import petri_net
+from pm4py.util import exec_utils
+
+
+class Variants(Enum):
+    PETRI_NET = petri_net
+
+
+@deprecation.deprecated(deprecated_in='2.2.2', removed_in='3.0.0',
+                        details='this wf-net check is moved to the pm4py.algo.analysis package')
+def apply(net, parameters=None, variant=Variants.PETRI_NET):
+    warnings.warn('this wf-net check is moved to the pm4py.algo.analysis package', DeprecationWarning)
+    """
+    Checks if a Petri net is a workflow net
+
+    Parameters
+    ---------------
+    net
+        Petri net
+    parameters
+        Parameters of the algorithm
+    variant
+        Variant of the algorithm, possibe values:
+        - Variants.PETRI_NET
+
+    Returns
+    ---------------
+    boolean
+        Boolean value
+    """
+    return exec_utils.get_variant(variant).apply(net, parameters=parameters)
diff --git a/src/evaluation/wf_net/variants/__init__.py b/src/evaluation/wf_net/variants/__init__.py
new file mode 100644
index 0000000..00ebe89
--- /dev/null
+++ b/src/evaluation/wf_net/variants/__init__.py
@@ -0,0 +1,17 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+from evaluation.wf_net.variants import petri_net
diff --git a/src/evaluation/wf_net/variants/__pycache__/__init__.cpython-310.pyc b/src/evaluation/wf_net/variants/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c60fae0298498dca73b0eee057fbcb1a322423b8
GIT binary patch
literal 981
zcmd1j<>g{vU|=x6<C`wP%)sy%#6iYP3=9ko3=9m#A`A=+DGX5zDU2yhIgGhXQA~^s
zDa^qPnk<ROnHU(j6ciK`LNYRo71A<uQWY|b6$%oIN)+<b6asur0xA_WeDjM^6+H9O
z@~spyN=gcft@QN^a!m><_0lp+^wNqFOY<`F(^8A{Qc^YbxWJ}CG=fYlN=;QL&QB{T
zPb^BcQmD)?RY*?EQz%MJ$t*4@%1kOPNma-!QAo^7(a$eZ$jwj5OsfQ&kXfQonwOGV
zq)?KPs!)<zlv@mP5QyXM7pmZ%nwMIXn4=I-nv|27tl*QGoSIjhs*qT$PyiAs&PYvB
zNP-vy(&7ekMKIJA3U2wOc`1n{nfZA-3aObT8L34IWvNBQnfZAN#xP5uCh92U7lECl
zkyrw9e33$a0Z5~!LSkN}LQY}{LNB_{5gtrY$jk$KBO|{cRiPv!u>|Dh^30qZg``x4
z(&E&#(i|PIElH&%3gMn19{!;r3XXn}3gM1HL5_YQk=6?3nI#$dr6mffWvLLeGII-Z
zGE-9&$`gx<67xzb74p--M)<l0IeR$zg*ZBS`gn#!D&!X_xOs;7xdsO-xcLVuI4T4<
z28DP!hx#}MDFlQD1^5TM>M1A$r-B@o3N`^x_@;s5s3=t-CAB0mGp88p=g9n0h2o6-
z(wr29jKs23g`(8t)XcKf6oo{E<op7V)4<-xZ7|s0#GL%Rbg)Mu=}u2U!81)EFTX@b
zp*S^F!3LJ}%FE03((_97@{7{-b0864tZ%2s^%9i!H5qSl7NnLGWya^FmiTEh-(ruC
zPsvY?kH00AT9%ko3XXQY@-&dNURh#MW@27RaS<q|tYj!+Wnh31zY_IBi&Kk=^^42%
zbJFx(Qj4=o@(c8HGxIX@(&N)JOZ1CN3sQ@8p$6!tRutsq7o`^I7Z)Y#Bka+K*rN|s
nR;(W%pP83g5+AQuP<e~PCO1E&G$+-L5tKz)7#J8>7z6|W`7|?%

literal 0
HcmV?d00001

diff --git a/src/evaluation/wf_net/variants/__pycache__/petri_net.cpython-310.pyc b/src/evaluation/wf_net/variants/__pycache__/petri_net.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..963b1854ad3fc02917178a81b5cdb3666ea9d2bd
GIT binary patch
literal 2709
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
M0Z>-uVCK^R03>VqE&u=k

literal 0
HcmV?d00001

diff --git a/src/evaluation/wf_net/variants/petri_net.py b/src/evaluation/wf_net/variants/petri_net.py
new file mode 100644
index 0000000..39dfcaf
--- /dev/null
+++ b/src/evaluation/wf_net/variants/petri_net.py
@@ -0,0 +1,101 @@
+'''
+    This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+    PM4Py is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    PM4Py is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with PM4Py.  If not, see <https://www.gnu.org/licenses/>.
+'''
+import copy
+
+from pm4py.objects.petri_net.utils import petri_utils as pn_utils
+from pm4py.objects.petri_net.obj import PetriNet
+
+
+def _short_circuit_petri_net(net):
+    """
+    Creates a short circuited Petri net,
+    whether an unique source place and sink place are there,
+    by connecting the sink with the source
+
+    Parameters
+    ---------------
+    net
+        Petri net
+
+    Returns
+    ---------------
+    boolean
+        Boolean value
+    """
+    s_c_net = copy.deepcopy(net)
+    no_source_places = 0
+    no_sink_places = 0
+    sink = None
+    source = None
+    for place in s_c_net.places:
+        if len(place.in_arcs) == 0:
+            source = place
+            no_source_places += 1
+        if len(place.out_arcs) == 0:
+            sink = place
+            no_sink_places += 1
+    if (sink is not None) and (source is not None) and no_source_places == 1 and no_sink_places == 1:
+        # If there is one unique source and sink place, short circuit Petri Net is constructed
+        t_1 = PetriNet.Transition("short_circuited_transition", "short_circuited_transition")
+        s_c_net.transitions.add(t_1)
+        # add arcs in short-circuited net
+        pn_utils.add_arc_from_to(sink, t_1, s_c_net)
+        pn_utils.add_arc_from_to(t_1, source, s_c_net)
+        return s_c_net
+    else:
+        return None
+
+
+def apply(net, parameters=None):
+    """
+    Checks if a Petri net is a workflow net
+
+    Parameters
+    ---------------
+    net
+        Petri net
+    parameters
+        Parameters of the algorithm
+
+    Returns
+    ---------------
+    boolean
+        Boolean value
+    """
+    if parameters is None:
+        parameters = {}
+
+    import networkx as nx
+
+    scnet = _short_circuit_petri_net(net)
+    if scnet is None:
+        return False
+    nodes = scnet.transitions | scnet.places
+    graph = nx.DiGraph()
+    while len(nodes) > 0:
+        element = nodes.pop()
+        graph.add_node(element.name)
+        for in_arc in element.in_arcs:
+            graph.add_node(in_arc.source.name)
+            graph.add_edge(in_arc.source.name, element.name)
+        for out_arc in element.out_arcs:
+            graph.add_node(out_arc.target.name)
+            graph.add_edge(element.name, out_arc.target.name)
+    if nx.algorithms.components.is_strongly_connected(graph):
+        return True
+    else:
+        return False
diff --git a/src/frontend/src/app/components/header-bar/header-bar.component.ts b/src/frontend/src/app/components/header-bar/header-bar.component.ts
index 3112d6c..6466660 100644
--- a/src/frontend/src/app/components/header-bar/header-bar.component.ts
+++ b/src/frontend/src/app/components/header-bar/header-bar.component.ts
@@ -77,7 +77,7 @@ export class HeaderBarComponent implements OnDestroy {
 
   //TODO: remove comments
   importOCEL(): void {
-    console.log("We try to upload an OCEL, but nothing is yet implemented")
+    //console.log("We try to upload an OCEL, but nothing is yet implemented")
     this.fileUploadOCEL.nativeElement.click();
   }
 
@@ -89,7 +89,8 @@ export class HeaderBarComponent implements OnDestroy {
       let backendCall;
       if (!environment.electron) {
         console.log('Debug: Electron is not the detected environment, using uploadEventLog.');
-        backendCall = this.backendService.uploadEventLog(fileList[0]);
+        //backendCall = this.backendService.uploadEventLog(fileList[0]);
+        backendCall = this.backendService.uploadOCEL(fileList[0]);
       } else {
         console.log('Debug: Electron environment detected, using loadEventLogFromFilePath.');
         backendCall = this.backendService.loadEventLogFromFilePath(fileList[0]['path']);
@@ -97,9 +98,22 @@ export class HeaderBarComponent implements OnDestroy {
       this.loadingOverlayService.showLoader(
         'Importing OCEL. For large logs this can take up to several minutes'
       );
-      backendCall.subscribe(() => {
-        this.loadingOverlayService.hideLoader();
+      backendCall.subscribe({
+        next: () => {
+          this.loadingOverlayService.hideLoader();
+        },
+        error: (err) => {
+          console.error('Error importing OCEL:', err);
+          this.loadingOverlayService.hideLoader();
+        },
+        complete: () => {
+          this.loadingOverlayService.hideLoader();
+        }
       });
+
+      //backendCall.subscribe(() => {
+        //this.loadingOverlayService.hideLoader();
+      //});
     }
 
     // reset form
diff --git a/src/frontend/src/app/services/backendService/backend.service.ts b/src/frontend/src/app/services/backendService/backend.service.ts
index 2c4c378..c65e150 100644
--- a/src/frontend/src/app/services/backendService/backend.service.ts
+++ b/src/frontend/src/app/services/backendService/backend.service.ts
@@ -92,6 +92,25 @@ export class BackendService {
       );
   }
 
+  uploadOCEL(file: File) {
+    let formData = new FormData();
+    formData.append('file', file);
+
+    return this.httpClient
+      .post(
+        ROUTES.HTTP_BASE_URL + ROUTES.IMPORT + 'loadOCELFromFile',
+        formData
+      )
+      .pipe(
+        mapVariants(),
+        tap((res) => {
+          this.logService.processOCEL(res, file['path']);
+        })
+      );
+  }
+
+
+
   loadProcessTreeFromFilePath(filePath: string): void {
     this.httpClient
       .post(
diff --git a/src/frontend/src/app/services/logService/log.service.ts b/src/frontend/src/app/services/logService/log.service.ts
index f3a8322..8cba08e 100644
--- a/src/frontend/src/app/services/logService/log.service.ts
+++ b/src/frontend/src/app/services/logService/log.service.ts
@@ -250,6 +250,9 @@ export class LogService {
     this.logGranularity = res['timeGranularity'];
     this.logModifications = [];
   }
+
+  public processOCEL(res, filePath = null) {
+  }
 }
 
 export class LogStats {
-- 
GitLab