197 lines
6.4 KiB
Python
197 lines
6.4 KiB
Python
# Split Miner - BPMN process discovery from event logs.
|
|
# Authors:
|
|
# imacat@mail.imacat.idv.tw (imacat), 2026/3/11
|
|
# AI assistance: Claude Code (Anthropic)
|
|
|
|
# Copyright (c) 2026 imacat.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied. See the License for the specific language governing
|
|
# permissions and limitations under the License.
|
|
"""Tests for the epsilon parameter.
|
|
|
|
Ported from the bpmn project's test_epsilon.py.
|
|
High epsilon values may break the graph.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from split_miner import split_miner
|
|
|
|
TRACES_1: dict[tuple[str, ...], int] = {
|
|
("a", "b", "f", "g", "i", "j", "k"): 1150,
|
|
("b", "f", "g", "i", "j", "k", "a"): 684,
|
|
("a", "b", "k"): 432,
|
|
("a", "b", "k", "f", "g", "i", "j"): 252,
|
|
("b", "k", "a"): 194,
|
|
("a", "b", "f", "g", "i", "k", "j"): 192,
|
|
("a", "h", "f", "g", "i", "j", "k", "b"): 190,
|
|
("a", "h", "b", "f", "g", "i", "j", "k"): 188,
|
|
("a", "h", "k", "b"): 80,
|
|
("a", "h", "b", "k"): 79,
|
|
("a", "h", "b", "k", "f", "g", "i", "j"): 61,
|
|
("b", "f", "g", "i", "k", "j", "a"): 53,
|
|
("a", "h", "b", "f", "g", "i", "k", "j"): 41,
|
|
("a", "h", "k", "f", "g", "i", "j", "b"): 40,
|
|
("a", "b", "f", "g", "k", "i", "j"): 36,
|
|
("a", "h", "f", "g", "i", "k", "j", "b"): 28,
|
|
("b", "f", "g", "i", "k", "a"): 21,
|
|
("a", "b", "f", "k", "g", "i", "j"): 19,
|
|
("a", "f", "g", "i", "j", "k", "h", "b"): 19,
|
|
("a", "f", "g", "i", "j", "k", "b"): 16,
|
|
("a", "b", "f", "g", "i", "k"): 15,
|
|
("a", "h", "b", "f", "g", "k", "i", "j"): 9,
|
|
("a", "k", "h", "b"): 7,
|
|
("a", "k", "b"): 6,
|
|
("a", "b", "k", "f", "g", "i"): 5,
|
|
("a", "b", "k", "j"): 5,
|
|
("a", "c", "e", "b", "f", "g", "i", "j",
|
|
"k"): 5,
|
|
("a", "h", "f", "g", "i", "j", "k"): 5,
|
|
("a", "h", "k", "b", "f", "g", "i", "j"): 5,
|
|
("b", "k", "a", "f", "g", "i", "j"): 5,
|
|
("a", "b", "j", "k"): 4,
|
|
("a", "d", "h", "f", "g", "i", "j", "k",
|
|
"b"): 4,
|
|
("a", "f", "g", "i", "k", "j", "h", "b"): 4,
|
|
("a", "h", "f", "g", "k", "i", "j", "b"): 4,
|
|
("a", "k", "f", "g", "i", "j", "h", "b"): 4,
|
|
("a", "c", "e", "b", "f", "g", "i", "k",
|
|
"j"): 3,
|
|
("a", "c", "e", "b", "k", "f", "g", "i",
|
|
"j"): 3,
|
|
("a", "c", "e", "h", "b", "f", "g", "i",
|
|
"j", "k"): 3,
|
|
("a", "c", "h", "e", "b", "f", "g", "i",
|
|
"j", "k"): 3,
|
|
("a", "d", "h", "b", "k"): 3,
|
|
("a", "h", "k", "f", "g", "i", "b"): 3,
|
|
("a", "c", "e", "f", "g", "i", "j", "k",
|
|
"h", "b"): 2,
|
|
("a", "c", "e", "h", "f", "g", "i", "j",
|
|
"k", "b"): 2,
|
|
("a", "c", "h", "e", "b", "k", "f", "g",
|
|
"i", "j"): 2,
|
|
("a", "c", "h", "e", "f", "g", "i", "j",
|
|
"k", "b"): 2,
|
|
("a", "d", "b", "f", "g", "i", "j", "k"): 2,
|
|
("a", "h", "b", "f", "k", "g", "i", "j"): 2,
|
|
("a", "h", "k"): 2,
|
|
("b", "f", "g", "i", "k", "a", "j"): 2,
|
|
("a", "b", "f", "g", "k"): 1,
|
|
("a", "b", "i", "k"): 1,
|
|
("a", "c", "e", "b", "k"): 1,
|
|
("a", "c", "e", "b", "k", "j"): 1,
|
|
("a", "c", "e", "h", "b", "f", "g", "i",
|
|
"k"): 1,
|
|
("a", "c", "e", "h", "b", "k"): 1,
|
|
("a", "c", "e", "h", "b", "k", "f", "g",
|
|
"i", "j"): 1,
|
|
("a", "c", "e", "h", "k", "b"): 1,
|
|
("a", "c", "h", "e", "b", "k"): 1,
|
|
("a", "d", "h", "b", "f", "g", "i", "j",
|
|
"k"): 1,
|
|
("a", "f", "g", "h", "i", "j", "k", "b"): 1,
|
|
("a", "f", "g", "i", "b", "j", "k"): 1,
|
|
("a", "f", "g", "i", "j", "b", "k"): 1,
|
|
("a", "f", "g", "i", "k", "j", "b"): 1,
|
|
("a", "f", "g", "i", "k", "j", "h"): 1,
|
|
("a", "f", "g", "k", "i", "j", "h", "b"): 1,
|
|
("a", "h", "b", "f", "g", "i", "k"): 1,
|
|
("a", "h", "b", "f", "g", "k"): 1,
|
|
("a", "h", "b", "i", "k"): 1,
|
|
("a", "h", "b", "k", "f", "g", "i"): 1,
|
|
("a", "h", "b", "k", "j"): 1,
|
|
("a", "h", "f", "g", "i", "b", "j", "k"): 1,
|
|
("a", "h", "f", "g", "i", "k"): 1,
|
|
("a", "h", "f", "g", "i", "k", "b"): 1,
|
|
("a", "h", "f", "g", "i", "k", "j"): 1,
|
|
("a", "h", "f", "k", "g", "i", "j", "b"): 1,
|
|
("a", "h", "j", "k", "b"): 1,
|
|
("a", "h", "k", "b", "i", "j"): 1,
|
|
("a", "h", "k", "j", "b"): 1,
|
|
("a", "k", "b", "f", "g", "i", "j"): 1,
|
|
("a", "k", "h", "b", "f", "g", "i", "j"): 1,
|
|
("b", "f", "g", "k", "a"): 1,
|
|
("b", "i", "j", "k", "a"): 1,
|
|
("b", "j", "k", "a"): 1,
|
|
("b", "k", "a", "j"): 1,
|
|
}
|
|
"""Traces from the KMU log, filtered and anonymized."""
|
|
|
|
TRACES_2: dict[tuple[str, ...], int] = {
|
|
("a", "g", "e", "c", "d", "f", "h"): 252,
|
|
("a", "g", "c", "d", "f", "e", "h"): 192,
|
|
("a", "b", "c", "d", "f", "h", "e", "g"): 190,
|
|
("a", "g", "c", "d", "e", "f", "h"): 36,
|
|
("a", "g", "c", "e", "d", "f", "h"): 19,
|
|
}
|
|
"""Simplified traces from TRACES_1."""
|
|
|
|
TRACES_3: dict[tuple[str, ...], int] = {
|
|
("a", "b", "d", "b", "e", "d", "c", "e",
|
|
"d", "e"): 1,
|
|
("a", "b", "d", "e", "c", "d", "e", "a",
|
|
"b", "d", "e", "a", "b", "d", "e", "c",
|
|
"d", "a", "c", "e", "d", "e", "e"): 1,
|
|
}
|
|
"""Traces from the rent data."""
|
|
|
|
|
|
class TestEpsilon(unittest.TestCase):
|
|
"""Tests for the epsilon parameter edge cases."""
|
|
|
|
def test_traces_1(self) -> None:
|
|
"""Tests TRACES_1.
|
|
|
|
:return: None.
|
|
"""
|
|
self.__test_traces(TRACES_1)
|
|
|
|
def test_traces_2(self) -> None:
|
|
"""Tests TRACES_2.
|
|
|
|
:return: None.
|
|
"""
|
|
self.__test_traces(TRACES_2)
|
|
|
|
def test_traces_3(self) -> None:
|
|
"""Tests TRACES_3.
|
|
|
|
:return: None.
|
|
"""
|
|
self.__test_traces(TRACES_3)
|
|
|
|
def __test_traces(
|
|
self,
|
|
traces: dict[tuple[str, ...], int],
|
|
) -> None:
|
|
"""Tests a trace set.
|
|
|
|
A high epsilon (0.8) prunes many edges, which can
|
|
produce a graph with cut vertices. The SPQR-tree
|
|
construction raises ValueError for non-biconnected
|
|
graphs, but build_rpst() catches this and falls
|
|
back to a single fragment. All three epsilon
|
|
values should succeed without raising.
|
|
|
|
:param traces: The traces.
|
|
:return: None.
|
|
"""
|
|
split_miner(traces, epsilon=0.8, eta=0.8)
|
|
split_miner(traces, epsilon=0.33, eta=0.8)
|
|
split_miner(traces)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|