Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pm4py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
discover_eventually_follows_graph,
discover_directly_follows_graph,
discover_bpmn_inductive,
discover_bpmn_split_miner,
discover_performance_dfg,
discover_transition_system,
discover_prefix_tree,
Expand Down
3 changes: 2 additions & 1 deletion pm4py/algo/discovery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
ocel,
performance_spectrum,
powl,
split_miner,
temporal_profile,
transition_system
transition_system,
)
36 changes: 36 additions & 0 deletions pm4py/algo/discovery/split_miner/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
from pm4py.algo.discovery.split_miner import (
algorithm,
bpmn_export,
bpmn_init,
sese,
concurrency,
dfg_discovery,
dtypes,
filtering,
heuristics,
joins,
or_min,
splits,
variants,
)
77 changes: 77 additions & 0 deletions pm4py/algo/discovery/split_miner/algorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
"""Top-level dispatcher for Split Miner.

Two variants are exposed:

* :data:`CLASSIC` — the classic Split Miner pipeline.
* :data:`SM2` — Split Miner 2.0, with a lifecycle-aware refined DFG,
a lifecycle-overlap concurrency oracle, and two heuristics for
improper-completion repair and OR-split identification.

Both variants return a :class:`pm4py.objects.bpmn.obj.BPMN`.
"""
from enum import Enum
from typing import Any, Dict, Optional, Tuple, Union

import pandas as pd

from pm4py.algo.discovery.split_miner.variants import classic, sm2
from pm4py.objects.bpmn.obj import BPMN
from pm4py.objects.log.obj import EventLog, EventStream
from pm4py.util import exec_utils


class Variants(Enum):
CLASSIC = classic
SM2 = sm2


CLASSIC = Variants.CLASSIC
SM2 = Variants.SM2
DEFAULT_VARIANT = CLASSIC

VERSIONS = {CLASSIC, SM2}


def apply(
log: Union[
EventLog, EventStream, pd.DataFrame, Dict[Tuple[str, str], int]
],
parameters: Optional[Dict[Any, Any]] = None,
variant: Variants = DEFAULT_VARIANT,
) -> BPMN:
"""Discover a BPMN model from a log using Split Miner.

Parameters
----------
log
Event log (``EventLog`` / ``EventStream`` / ``pandas.DataFrame``)
or a precomputed DFG (only accepted by the classic variant).
parameters
Variant-specific parameters; see ``classic.Parameters`` and
``sm2.Parameters`` for the supported keys (``EPSILON``, ``ETA``,
``OR_MINIMISE``, ``ACTIVITY_KEY``, …).
variant
Either :data:`CLASSIC` (default) or :data:`SM2`.
"""
return exec_utils.get_variant(variant).apply(log, parameters=parameters)
22 changes: 22 additions & 0 deletions pm4py/algo/discovery/split_miner/bpmn_export/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
from pm4py.algo.discovery.split_miner.bpmn_export import abc, classic
40 changes: 40 additions & 0 deletions pm4py/algo/discovery/split_miner/bpmn_export/abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
"""Abstract base class for the BPMN-export phase."""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional

from pm4py.algo.discovery.split_miner.dtypes.working_graph import WorkingGraph
from pm4py.objects.bpmn.obj import BPMN


class BPMNExporter(ABC):
"""Convert the internal :class:`WorkingGraph` into a pm4py BPMN object."""

@classmethod
@abstractmethod
def apply(
cls,
wg: WorkingGraph,
parameters: Optional[Dict[str, Any]] = None,
) -> BPMN:
...
118 changes: 118 additions & 0 deletions pm4py/algo/discovery/split_miner/bpmn_export/classic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
"""Convert :class:`WorkingGraph` into a pm4py :class:`BPMN`.

Self-loops detected during the loops phase are reattached here by
wrapping the looped task with an XOR-join (predecessor side) and an
XOR-split (successor side) that connects back to the join.
"""
from typing import Any, Dict, Optional

from pm4py.algo.discovery.split_miner.bpmn_export.abc import BPMNExporter
from pm4py.algo.discovery.split_miner.dtypes.log import END_LABEL, START_LABEL
from pm4py.algo.discovery.split_miner.dtypes.working_graph import WorkingGraph
from pm4py.objects.bpmn.obj import BPMN


def _make_node(kind: str, label: str, node_id: str) -> BPMN.BPMNNode:
if kind == "start":
return BPMN.StartEvent(id=node_id, name="")
if kind == "end":
return BPMN.EndEvent(id=node_id, name="")
if kind == "task":
return BPMN.Task(id=node_id, name=label)
if kind == "xor":
return BPMN.ExclusiveGateway(id=node_id, name="")
if kind == "and":
return BPMN.ParallelGateway(id=node_id, name="")
if kind == "or":
return BPMN.InclusiveGateway(id=node_id, name="")
raise ValueError(f"Unknown node kind: {kind}")


class ClassicBPMNExporter(BPMNExporter):
"""Materialise the pm4py :class:`BPMN` from the working graph."""

@classmethod
def apply(
cls,
wg: WorkingGraph,
parameters: Optional[Dict[str, Any]] = None,
) -> BPMN:
bpmn = BPMN()
node_map: Dict[str, BPMN.BPMNNode] = {}
for nid, n in wg.nodes.items():
bnode = _make_node(n.kind, n.label, nid)
bpmn.add_node(bnode)
node_map[nid] = bnode

for src, tgt in wg.edges():
bpmn.add_flow(
BPMN.SequenceFlow(node_map[src], node_map[tgt])
)

# Sort to keep self-loop attachment order independent of
# hash randomization; semantically the model is the same, but
# node/flow ids and rendering order are then reproducible.
for task_id in sorted(wg.self_loops, reverse=True):
if task_id not in node_map:
continue
if task_id in {START_LABEL, END_LABEL}:
continue
cls._attach_self_loop(bpmn, node_map, task_id)
return bpmn

# ------------------------------------------------------------------
# helpers
# ------------------------------------------------------------------

@staticmethod
def _attach_self_loop(
bpmn: BPMN,
node_map: Dict[str, BPMN.BPMNNode],
task_id: str,
) -> None:
task_node = node_map[task_id]
in_flows = [
f for f in bpmn.get_flows() if f.get_target() is task_node
]
out_flows = [
f for f in bpmn.get_flows() if f.get_source() is task_node
]

loop_join = BPMN.ExclusiveGateway(id=f"{task_id}__loop_join", name="")
loop_split = BPMN.ExclusiveGateway(id=f"{task_id}__loop_split", name="")
bpmn.add_node(loop_join)
bpmn.add_node(loop_split)

for f in in_flows:
src = f.get_source()
bpmn.remove_flow(f)
bpmn.add_flow(BPMN.SequenceFlow(src, loop_join))
for f in out_flows:
tgt = f.get_target()
bpmn.remove_flow(f)
bpmn.add_flow(BPMN.SequenceFlow(loop_split, tgt))

bpmn.add_flow(BPMN.SequenceFlow(loop_join, task_node))
bpmn.add_flow(BPMN.SequenceFlow(task_node, loop_split))
bpmn.add_flow(BPMN.SequenceFlow(loop_split, loop_join))
22 changes: 22 additions & 0 deletions pm4py/algo/discovery/split_miner/bpmn_init/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
from pm4py.algo.discovery.split_miner.bpmn_init import abc, classic
46 changes: 46 additions & 0 deletions pm4py/algo/discovery/split_miner/bpmn_init/abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2026 Process Intelligence Solutions GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.

Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
"""Abstract base class for the BPMN-initialisation phase."""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional

from pm4py.algo.discovery.split_miner.dtypes.concurrency import (
ConcurrencyResult,
)
from pm4py.algo.discovery.split_miner.dtypes.filtering import FilterResult
from pm4py.algo.discovery.split_miner.dtypes.loops import LoopInfo
from pm4py.algo.discovery.split_miner.dtypes.working_graph import WorkingGraph


class BPMNInitializer(ABC):
"""Materialise a :class:`WorkingGraph` from the filtered PDFG."""

@classmethod
@abstractmethod
def apply(
cls,
filtered: FilterResult,
concurrency: ConcurrencyResult,
loops: LoopInfo,
parameters: Optional[Dict[str, Any]] = None,
) -> WorkingGraph:
"""Return a fresh working graph ready for the splits phase."""
Loading
Loading