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