Files
2026-03-12 07:03:52 +08:00

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()