Skip to content

Commit 4ca25d1

Browse files
committed
Implemented Split Miner v. 1.0 and 2.0
1 parent fcf3d25 commit 4ca25d1

50 files changed

Lines changed: 3695 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pm4py/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
discover_eventually_follows_graph,
147147
discover_directly_follows_graph,
148148
discover_bpmn_inductive,
149+
discover_bpmn_split_miner,
149150
discover_performance_dfg,
150151
discover_transition_system,
151152
discover_prefix_tree,

pm4py/algo/discovery/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
ocel,
4040
performance_spectrum,
4141
powl,
42+
split_miner,
4243
temporal_profile,
43-
transition_system
44+
transition_system,
4445
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
from pm4py.algo.discovery.split_miner import (
23+
algorithm,
24+
bpmn_export,
25+
bpmn_init,
26+
sese,
27+
concurrency,
28+
dfg_discovery,
29+
dtypes,
30+
filtering,
31+
heuristics,
32+
joins,
33+
or_min,
34+
splits,
35+
variants,
36+
)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
"""Top-level dispatcher for Split Miner.
23+
24+
Two variants are exposed:
25+
26+
* :data:`CLASSIC` — the classic Split Miner pipeline.
27+
* :data:`SM2` — Split Miner 2.0, with a lifecycle-aware refined DFG,
28+
a lifecycle-overlap concurrency oracle, and two heuristics for
29+
improper-completion repair and OR-split identification.
30+
31+
Both variants return a :class:`pm4py.objects.bpmn.obj.BPMN`.
32+
"""
33+
from enum import Enum
34+
from typing import Any, Dict, Optional, Tuple, Union
35+
36+
import pandas as pd
37+
38+
from pm4py.algo.discovery.split_miner.variants import classic, sm2
39+
from pm4py.objects.bpmn.obj import BPMN
40+
from pm4py.objects.log.obj import EventLog, EventStream
41+
from pm4py.util import exec_utils
42+
43+
44+
class Variants(Enum):
45+
CLASSIC = classic
46+
SM2 = sm2
47+
48+
49+
CLASSIC = Variants.CLASSIC
50+
SM2 = Variants.SM2
51+
DEFAULT_VARIANT = CLASSIC
52+
53+
VERSIONS = {CLASSIC, SM2}
54+
55+
56+
def apply(
57+
log: Union[
58+
EventLog, EventStream, pd.DataFrame, Dict[Tuple[str, str], int]
59+
],
60+
parameters: Optional[Dict[Any, Any]] = None,
61+
variant: Variants = DEFAULT_VARIANT,
62+
) -> BPMN:
63+
"""Discover a BPMN model from a log using Split Miner.
64+
65+
Parameters
66+
----------
67+
log
68+
Event log (``EventLog`` / ``EventStream`` / ``pandas.DataFrame``)
69+
or a precomputed DFG (only accepted by the classic variant).
70+
parameters
71+
Variant-specific parameters; see ``classic.Parameters`` and
72+
``sm2.Parameters`` for the supported keys (``EPSILON``, ``ETA``,
73+
``OR_MINIMISE``, ``ACTIVITY_KEY``, …).
74+
variant
75+
Either :data:`CLASSIC` (default) or :data:`SM2`.
76+
"""
77+
return exec_utils.get_variant(variant).apply(log, parameters=parameters)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
from pm4py.algo.discovery.split_miner.bpmn_export import abc, classic
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
"""Abstract base class for the BPMN-export phase."""
23+
from abc import ABC, abstractmethod
24+
from typing import Any, Dict, Optional
25+
26+
from pm4py.algo.discovery.split_miner.dtypes.working_graph import WorkingGraph
27+
from pm4py.objects.bpmn.obj import BPMN
28+
29+
30+
class BPMNExporter(ABC):
31+
"""Convert the internal :class:`WorkingGraph` into a pm4py BPMN object."""
32+
33+
@classmethod
34+
@abstractmethod
35+
def apply(
36+
cls,
37+
wg: WorkingGraph,
38+
parameters: Optional[Dict[str, Any]] = None,
39+
) -> BPMN:
40+
...
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
"""Convert :class:`WorkingGraph` into a pm4py :class:`BPMN`.
23+
24+
Self-loops detected during the loops phase are reattached here by
25+
wrapping the looped task with an XOR-join (predecessor side) and an
26+
XOR-split (successor side) that connects back to the join.
27+
"""
28+
from typing import Any, Dict, Optional
29+
30+
from pm4py.algo.discovery.split_miner.bpmn_export.abc import BPMNExporter
31+
from pm4py.algo.discovery.split_miner.dtypes.log import END_LABEL, START_LABEL
32+
from pm4py.algo.discovery.split_miner.dtypes.working_graph import WorkingGraph
33+
from pm4py.objects.bpmn.obj import BPMN
34+
35+
36+
def _make_node(kind: str, label: str, node_id: str) -> BPMN.BPMNNode:
37+
if kind == "start":
38+
return BPMN.StartEvent(id=node_id, name="")
39+
if kind == "end":
40+
return BPMN.EndEvent(id=node_id, name="")
41+
if kind == "task":
42+
return BPMN.Task(id=node_id, name=label)
43+
if kind == "xor":
44+
return BPMN.ExclusiveGateway(id=node_id, name="")
45+
if kind == "and":
46+
return BPMN.ParallelGateway(id=node_id, name="")
47+
if kind == "or":
48+
return BPMN.InclusiveGateway(id=node_id, name="")
49+
raise ValueError(f"Unknown node kind: {kind}")
50+
51+
52+
class ClassicBPMNExporter(BPMNExporter):
53+
"""Materialise the pm4py :class:`BPMN` from the working graph."""
54+
55+
@classmethod
56+
def apply(
57+
cls,
58+
wg: WorkingGraph,
59+
parameters: Optional[Dict[str, Any]] = None,
60+
) -> BPMN:
61+
bpmn = BPMN()
62+
node_map: Dict[str, BPMN.BPMNNode] = {}
63+
for nid, n in wg.nodes.items():
64+
bnode = _make_node(n.kind, n.label, nid)
65+
bpmn.add_node(bnode)
66+
node_map[nid] = bnode
67+
68+
for src, tgt in wg.edges():
69+
bpmn.add_flow(
70+
BPMN.SequenceFlow(node_map[src], node_map[tgt])
71+
)
72+
73+
# Sort to keep self-loop attachment order independent of
74+
# hash randomization; semantically the model is the same, but
75+
# node/flow ids and rendering order are then reproducible.
76+
for task_id in sorted(wg.self_loops, reverse=True):
77+
if task_id not in node_map:
78+
continue
79+
if task_id in {START_LABEL, END_LABEL}:
80+
continue
81+
cls._attach_self_loop(bpmn, node_map, task_id)
82+
return bpmn
83+
84+
# ------------------------------------------------------------------
85+
# helpers
86+
# ------------------------------------------------------------------
87+
88+
@staticmethod
89+
def _attach_self_loop(
90+
bpmn: BPMN,
91+
node_map: Dict[str, BPMN.BPMNNode],
92+
task_id: str,
93+
) -> None:
94+
task_node = node_map[task_id]
95+
in_flows = [
96+
f for f in bpmn.get_flows() if f.get_target() is task_node
97+
]
98+
out_flows = [
99+
f for f in bpmn.get_flows() if f.get_source() is task_node
100+
]
101+
102+
loop_join = BPMN.ExclusiveGateway(id=f"{task_id}__loop_join", name="")
103+
loop_split = BPMN.ExclusiveGateway(id=f"{task_id}__loop_split", name="")
104+
bpmn.add_node(loop_join)
105+
bpmn.add_node(loop_split)
106+
107+
for f in in_flows:
108+
src = f.get_source()
109+
bpmn.remove_flow(f)
110+
bpmn.add_flow(BPMN.SequenceFlow(src, loop_join))
111+
for f in out_flows:
112+
tgt = f.get_target()
113+
bpmn.remove_flow(f)
114+
bpmn.add_flow(BPMN.SequenceFlow(loop_split, tgt))
115+
116+
bpmn.add_flow(BPMN.SequenceFlow(loop_join, task_node))
117+
bpmn.add_flow(BPMN.SequenceFlow(task_node, loop_split))
118+
bpmn.add_flow(BPMN.SequenceFlow(loop_split, loop_join))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
from pm4py.algo.discovery.split_miner.bpmn_init import abc, classic
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'''
2+
PM4Py – A Process Mining Library for Python
3+
Copyright (C) 2026 Process Intelligence Solutions GmbH
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Affero General Public License as
7+
published by the Free Software Foundation, either version 3 of the
8+
License, or any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU Affero General Public License for more details.
14+
15+
You should have received a copy of the GNU Affero General Public License
16+
along with this program. If not, see this software project's root or
17+
visit <https://www.gnu.org/licenses/>.
18+
19+
Website: https://processintelligence.solutions
20+
Contact: info@processintelligence.solutions
21+
'''
22+
"""Abstract base class for the BPMN-initialisation phase."""
23+
from abc import ABC, abstractmethod
24+
from typing import Any, Dict, Optional
25+
26+
from pm4py.algo.discovery.split_miner.dtypes.concurrency import (
27+
ConcurrencyResult,
28+
)
29+
from pm4py.algo.discovery.split_miner.dtypes.filtering import FilterResult
30+
from pm4py.algo.discovery.split_miner.dtypes.loops import LoopInfo
31+
from pm4py.algo.discovery.split_miner.dtypes.working_graph import WorkingGraph
32+
33+
34+
class BPMNInitializer(ABC):
35+
"""Materialise a :class:`WorkingGraph` from the filtered PDFG."""
36+
37+
@classmethod
38+
@abstractmethod
39+
def apply(
40+
cls,
41+
filtered: FilterResult,
42+
concurrency: ConcurrencyResult,
43+
loops: LoopInfo,
44+
parameters: Optional[Dict[str, Any]] = None,
45+
) -> WorkingGraph:
46+
"""Return a fresh working graph ready for the splits phase."""

0 commit comments

Comments
 (0)