From e81e136ac0d658ae97cd83ef7c52d73357b616cb Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Wed, 17 Jun 2026 15:24:25 +0100 Subject: [PATCH 1/2] knuth-bendix: register rpo cxx types --- src/libsemigroups_pybind11/knuth_bendix.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libsemigroups_pybind11/knuth_bendix.py b/src/libsemigroups_pybind11/knuth_bendix.py index 856835dc..d6ac52d7 100644 --- a/src/libsemigroups_pybind11/knuth_bendix.py +++ b/src/libsemigroups_pybind11/knuth_bendix.py @@ -127,6 +127,11 @@ def __init__(self, *args, rewriting_system="Trie", order=_Order.shortlex, **kwar _register_cxx_wrapped_type(_KnuthBendixWordLenLexTrie, KnuthBendix) _register_cxx_wrapped_type(_KnuthBendixStringLenLexSet, KnuthBendix) _register_cxx_wrapped_type(_KnuthBendixWordLenLexSet, KnuthBendix) +_register_cxx_wrapped_type(_KnuthBendixStringRPOTrie, KnuthBendix) +_register_cxx_wrapped_type(_KnuthBendixWordRPOTrie, KnuthBendix) +_register_cxx_wrapped_type(_KnuthBendixStringRPOSet, KnuthBendix) +_register_cxx_wrapped_type(_KnuthBendixWordRPOSet, KnuthBendix) + ######################################################################## # Helpers From fcd61f2a97d0d608c7eb9905913f2352764f771e Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Wed, 17 Jun 2026 15:24:54 +0100 Subject: [PATCH 2/2] Add support for TietzeExplorer co-authored-by: Codex OpenAI --- .../main-algorithms/knuth-bendix/helpers.rst | 9 +- src/knuth-bendix.cpp | 510 +++++++++++++++++- src/libsemigroups_pybind11/knuth_bendix.py | 63 +++ tests/test_knuth_bendix.py | 52 +- 4 files changed, 605 insertions(+), 29 deletions(-) diff --git a/docs/source/main-algorithms/knuth-bendix/helpers.rst b/docs/source/main-algorithms/knuth-bendix/helpers.rst index a27b1fc6..481b57c2 100644 --- a/docs/source/main-algorithms/knuth-bendix/helpers.rst +++ b/docs/source/main-algorithms/knuth-bendix/helpers.rst @@ -1,5 +1,5 @@ .. - Copyright (c) 2021-2024 J. D. Mitchell + Copyright (c) 2021-2026 J. D. Mitchell Distributed under the terms of the GPL license version 3. @@ -8,9 +8,9 @@ KnuthBendix helpers =================== -This page contains the documentation for various helper functions for -manipulating :any:`KnuthBendix` objects. All such functions are contained in the -submodule ``libsemigroups_pybind11.knuth_bendix``. +This page contains the documentation for various helper functions and classes +for manipulating :any:`KnuthBendix` objects. All these functions and classes +are contained in the submodule ``libsemigroups_pybind11.knuth_bendix``. .. seealso:: @@ -30,6 +30,7 @@ Contents normal_forms partition redundant_rule + TietzeExplorer Full API -------- diff --git a/src/knuth-bendix.cpp b/src/knuth-bendix.cpp index b4921330..e93c5b26 100644 --- a/src/knuth-bendix.cpp +++ b/src/knuth-bendix.cpp @@ -390,21 +390,20 @@ Copy a :any:`NormalFormRange` object. }); thing.def("next", [](NormalFormRange& nfr) { nfr.next(); }); } // bind_normal_form_range - } // namespace - template - void bind_redundant_rule(py::module& m) { - m.def( - "knuth_bendix_redundant_rule", - [](Presentation const& p, std::chrono::milliseconds t) - -> std::optional> { - auto it = knuth_bendix::redundant_rule(p, t); - if (it != p.rules.cend()) { - return std::make_pair(*it, *(it + 1)); - } - return {}; - }, - R"pbdoc( + template + void bind_redundant_rule(py::module& m) { + m.def( + "knuth_bendix_redundant_rule", + [](Presentation const& p, std::chrono::milliseconds t) + -> std::optional> { + auto it = knuth_bendix::redundant_rule(p, t); + if (it != p.rules.cend()) { + return std::make_pair(*it, *(it + 1)); + } + return {}; + }, + R"pbdoc( :sig=(p: Presentation, t: datetime.timedelta) -> tuple[list[int], list[int]] | tuple[str, str] | None: :only-document-once: @@ -448,13 +447,471 @@ redundant in this way, then ``None`` is returned. >>> knuth_bendix.redundant_rule(p, t) ('bab', 'abb') )pbdoc"); - } + } + + template + void bind_tietze_explorer(py::module& m, std::string const& name) { + using TietzeExplorer_ + = knuth_bendix::TietzeExplorer; + + py::class_ thing(m, + name.c_str(), + R"pbdoc( +Search for a finite complete rewriting system using Tietze transformations. + +This class searches for a presentation for which the Knuth-Bendix algorithm +terminates, by introducing new generators for subwords of the rules and trying +all different orders on the resulting alphabet. + +More precisely, an instance of this class starts with the presentation of the +:any:`KnuthBendix` instance used to construct it. It then forms presentations +obtained by repeatedly replacing a non-empty subword of length at least 2 by a +new generator. The number of such replacements is controlled by +:any:`TietzeExplorer.depth_min` and :any:`TietzeExplorer.depth_max`. For every +presentation in this search, the alphabet is permuted in every possible way, +and the Knuth-Bendix algorithm is run for :any:`TietzeExplorer.run_each_for`. +The search succeeds if one of these runs produces a confluent rewriting system. + +Since this class derives from :any:`Runner`, the search can be run to +completion using :any:`Runner.run`, for a bounded amount of time using +:any:`Runner.run_for`, or until a predicate holds using :any:`Runner.run_until`. +The member function :any:`TietzeExplorer.result` is the usual way to run the +search and obtain the successful :any:`KnuthBendix` instance, if one was found. + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb) + >>> explorer.depth_min(), explorer.depth_max() + (0, 3) +)pbdoc"); + // thing.def("__repr__", [](TietzeExplorer_ const& thing) { + // return to_human_readable_repr(thing); + // }); + thing.def(py::init&>(), R"pbdoc( +Construct from a KnuthBendix instance. + +Constructs a :any:`TietzeExplorer` using the kind and presentation of ``kb``. +The default settings are: + +* :any:`TietzeExplorer.depth_min` is ``0``; +* :any:`TietzeExplorer.depth_max` is ``3``; +* :any:`TietzeExplorer.run_each_for` is ``datetime.timedelta``; +* :any:`TietzeExplorer.number_of_threads` is ``1``. + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb) + >>> explorer.number_of_threads() + 1 + +:param kb: the KnuthBendix instance whose presentation is to be explored. +:type kb: KnuthBendix +)pbdoc"); + // thing.def(py::init(), R"pbdoc( + // Copy constructor. + // )pbdoc"); + + thing.def( + "depth_max", + [](TietzeExplorer_ const& self) { return self.depth_max(); }, + R"pbdoc( +:sig=(self: TietzeExplorer) -> int: +Get the maximum search depth. + +Returns the maximum number of subword replacements by new generators to perform +when constructing presentations for the search. The default value is ``3``. + +:returns: The maximum number of new generators introduced. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).depth_max() + 3 +)pbdoc"); + + thing.def( + "depth_max", + [](TietzeExplorer_& self, size_t val) -> TietzeExplorer_& { + return self.depth_max(val); + }, + py::arg("val"), + R"pbdoc( +:sig=(self: TietzeExplorer, val: SupportsInt | SupportsIndex) -> TietzeExplorer: +Set the maximum search depth. + +This function sets the maximum number of subword replacements by new generators +to perform when constructing presentations for the search. The default value is +``3``. + +:param val: the maximum search depth. +:type val: SupportsInt | SupportsIndex + +:returns: ``self``. +:rtype: TietzeExplorer + +:raises LibsemigroupsError: if the internal search queue has already been populated. + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).depth_max(1).depth_max() + 1 +)pbdoc"); + + thing.def( + "depth_min", + [](TietzeExplorer_ const& self) { return self.depth_min(); }, + R"pbdoc( +:sig=(self: TietzeExplorer) -> int: +Get the minimum search depth. + +Returns the minimum number of subword replacements by new generators required +for a presentation to be tried. The default value is ``0``. + +:returns: The minimum number of new generators to introduce. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).depth_min() + 0 +)pbdoc"); + + thing.def( + "depth_min", + [](TietzeExplorer_& self, size_t val) -> TietzeExplorer_& { + return self.depth_min(val); + }, + py::arg("val"), + R"pbdoc( +:sig=(self: TietzeExplorer, val: SupportsInt | SupportsIndex) -> TietzeExplorer: + +Set the minimum search depth. + +This function sets the minimum number of subword replacements by new generators +required for a presentation to be tried. The default value is ``0``. + +:param val: the minimum search depth. +:type val: SupportsInt | SupportsIndex + +:returns: ``self``. +:rtype: TietzeExplorer + +:raises LibsemigroupsError: if the internal search queue has already been populated. + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).depth_min(1).depth_min() + 1 +)pbdoc"); + + thing.def("estimated_run_time", + &TietzeExplorer_::estimated_run_time, + R"pbdoc( +:sig=(self: TietzeExplorer) -> datetime.timedelta: + +Estimate the running time of the search. + +Returns :any:`TietzeExplorer.number_of_runs` multiplied by +:any:`TietzeExplorer.run_each_for` and divided by +:any:`TietzeExplorer.number_of_threads`. + +:returns: The estimate total run time. +:rtype: datetime.timedelta + +.. doctest:: + + >>> from datetime import timedelta + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb) + >>> isinstance(explorer.estimated_run_time(), timedelta) + True +)pbdoc"); + + thing.def("init", + &TietzeExplorer_::init, + py::arg("kb"), + R"pbdoc( +:sig=(self: TietzeExplorer, kb: KnuthBendix) -> TietzeExplorer: + +Reinitialize an existing TietzeExplorer. + +This function puts a :any:`TietzeExplorer` object back into the same state as if +it had been newly constructed from ``kb``. + +:param kb: the KnuthBendix instance whose presentation is to be explored. +:type kb: KnuthBendix + +:returns: ``self``. +:rtype: TietzeExplorer + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb).depth_max(1) + >>> explorer.init(kb) is explorer + True + >>> explorer.depth_max() + 3 +)pbdoc"); + + thing.def("knuth_bendix", + &TietzeExplorer_::knuth_bendix, + R"pbdoc( +:sig=(self: TietzeExplorer) -> KnuthBendix: + +Return the initial :any:`KnuthBendix` instance. + +Returns the :any:`KnuthBendix` instance supplied at construction or most recent +initialization. + +:returns: A :any:`KnuthBendix` instance. +:rtype: KnuthBendix + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb) + >>> isinstance(explorer.knuth_bendix(), KnuthBendix) + True +)pbdoc"); + + thing.def("number_of_runs", + &TietzeExplorer_::number_of_runs, + R"pbdoc( +:sig=(self: TietzeExplorer) -> int: + +Return the number of Knuth-Bendix runs to try. + +Returns the number of presentations and alphabet orders that will be tried by +the search, subject to the current values of :any:`TietzeExplorer.depth_min` +and :any:`TietzeExplorer.depth_max`. + +:returns: An ``int``. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb).depth_max(0) + >>> explorer.number_of_runs() + 2 +)pbdoc"); + + thing.def( + "number_of_threads", + [](TietzeExplorer_ const& self) { return self.number_of_threads(); }, + R"pbdoc( +:sig=(self: TietzeExplorer) -> int: + +Get the number of threads. + +Returns the number of threads used to run the search. The default value is +``1``. + +:returns: An ``int``. +:rtype: int + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).number_of_threads() + 1 +)pbdoc"); + + thing.def( + "number_of_threads", + [](TietzeExplorer_& self, size_t val) -> TietzeExplorer_& { + return self.number_of_threads(val); + }, + py::arg("val"), + R"pbdoc( +:sig=(self: TietzeExplorer, val: SupportsInt | SupportsIndex) -> TietzeExplorer: + +Set the number of threads. + +This function sets the number of threads used to run the search. The default +value is ``1``. + +:param val: the number of threads to use. +:type val: SupportsInt | SupportsIndex + +:returns: ``self``. +:rtype: TietzeExplorer + +:raises LibsemigroupsError: if ``val`` is ``0``. + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).number_of_threads(2).number_of_threads() + 2 +)pbdoc"); + + thing.def("result", + &TietzeExplorer_::result, + R"pbdoc( +:sig=(self: TietzeExplorer) -> KnuthBendix | None: + +Run the search and return a successful Knuth-Bendix instance. + +This function runs the search, if it has not already finished, and returns the +first :any:`KnuthBendix` instance found whose rewriting system is confluent. + +:returns: A :any:`KnuthBendix` instance if the search succeeds, and ``None`` otherwise. +:rtype: KnuthBendix | None + +:raises LibsemigroupsError: if the initial alphabet size plus + :any:`TietzeExplorer.depth_max` is greater than 20. + +.. seealso:: :any:`Runner.run` + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> result = knuth_bendix.TietzeExplorer(kb).depth_max(0).result() + >>> isinstance(result, KnuthBendix) + True +)pbdoc"); + + thing.def( + "run_each_for", + [](TietzeExplorer_ const& self) { return self.run_each_for(); }, + R"pbdoc( +:sig=(self: TietzeExplorer) -> datetime.timedelta: + +Get the time allowed for each Knuth-Bendix run. + +Returns the amount of time for which Knuth-Bendix is run for each presentation +and alphabet order tried by the search. The default value is +``datetime.timedelta``. + +:returns: A value of type ``datetime.timedelta``. +:rtype: datetime.timedelta + +.. doctest:: + + >>> from datetime import timedelta + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb) + >>> isinstance(explorer.run_each_for(), timedelta) + True +)pbdoc"); + + thing.def( + "run_each_for", + [](TietzeExplorer_& self, std::chrono::nanoseconds val) + -> TietzeExplorer_& { return self.run_each_for(val); }, + py::arg("val"), + R"pbdoc( +:sig=(self: TietzeExplorer, val: datetime.timedelta) -> TietzeExplorer: + +Set the time allowed for each Knuth-Bendix run. + +This function sets the amount of time for which Knuth-Bendix is run for each +presentation and alphabet order tried by the search. The default value is +``datetime.timedelta``. + +:param val: the amount of time to run each Knuth-Bendix instance for. +:type val: datetime.timedelta + +:returns: ``self``. +:rtype: TietzeExplorer + +.. doctest:: + + >>> from datetime import timedelta + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> explorer = knuth_bendix.TietzeExplorer(kb) + >>> explorer.run_each_for(timedelta(milliseconds=1)) is explorer + True + >>> explorer.run_each_for() == timedelta(milliseconds=1) + True +)pbdoc"); + + thing.def("success", + &TietzeExplorer_::success, + R"pbdoc( +:sig=(self: TietzeExplorer) -> bool: + +Check whether the search finished successfully. + +Returns ``True`` if the search has finished and found a successful +:any:`KnuthBendix` instance, and ``False`` otherwise. + +:returns: Whether or not the search was successful. +:rtype: bool + +.. doctest:: + + >>> from libsemigroups_pybind11 import (KnuthBendix, Presentation, + ... congruence_kind, knuth_bendix) + >>> p = Presentation("ab") + >>> kb = KnuthBendix(congruence_kind.twosided, p) + >>> knuth_bendix.TietzeExplorer(kb).success() + False + +)pbdoc"); + } // bind_tietze_explorer + } // namespace void init_knuth_bendix(py::module& m) { - using LenLexTrie = detail::RewritingSystemTrie; - using LenLexSet = detail::RewritingSystemSet; - using RPOTrie = detail::RewritingSystemTrie; - using RPOSet = detail::RewritingSystemSet; + using LenLexTrie = detail::RewritingSystemTrie; + using LenLexSet = detail::RewritingSystemSet; + // TODO rename RevRPOTrie etc + using RPOTrie = detail::RewritingSystemTrie; + using RPOSet = detail::RewritingSystemSet; bind_knuth_bendix(m, "KnuthBendixWordLenLexTrie"); bind_knuth_bendix(m, "KnuthBendixWordLenLexSet"); @@ -487,6 +944,21 @@ redundant in this way, then ``None`` is returned. bind_redundant_rule(m); bind_redundant_rule(m); + + bind_tietze_explorer(m, + "TietzeExplorerWordLenLexSet"); + bind_tietze_explorer(m, + "TietzeExplorerWordLenLexTrie"); + bind_tietze_explorer(m, "TietzeExplorerWordRPOSet"); + bind_tietze_explorer(m, "TietzeExplorerWordRPOTrie"); + + bind_tietze_explorer( + m, "TietzeExplorerStringLenLexSet"); + bind_tietze_explorer( + m, "TietzeExplorerStringLenLexTrie"); + bind_tietze_explorer(m, "TietzeExplorerStringRPOSet"); + bind_tietze_explorer(m, + "TietzeExplorerStringRPOTrie"); } } // namespace libsemigroups diff --git a/src/libsemigroups_pybind11/knuth_bendix.py b/src/libsemigroups_pybind11/knuth_bendix.py index d6ac52d7..cb92ee74 100644 --- a/src/libsemigroups_pybind11/knuth_bendix.py +++ b/src/libsemigroups_pybind11/knuth_bendix.py @@ -21,6 +21,14 @@ KnuthBendixWordRPOSet as _KnuthBendixWordRPOSet, KnuthBendixWordRPOTrie as _KnuthBendixWordRPOTrie, Order as _Order, + TietzeExplorerStringLenLexSet as _TietzeExplorerStringLenLexSet, + TietzeExplorerStringLenLexTrie as _TietzeExplorerStringLenLexTrie, + TietzeExplorerStringRPOSet as _TietzeExplorerStringRPOSet, + TietzeExplorerStringRPOTrie as _TietzeExplorerStringRPOTrie, + TietzeExplorerWordLenLexSet as _TietzeExplorerWordLenLexSet, + TietzeExplorerWordLenLexTrie as _TietzeExplorerWordLenLexTrie, + TietzeExplorerWordRPOSet as _TietzeExplorerWordRPOSet, + TietzeExplorerWordRPOTrie as _TietzeExplorerWordRPOTrie, knuth_bendix_by_overlap_length as _knuth_bendix_by_overlap_length, knuth_bendix_is_reduced as _knuth_bendix_is_reduced, knuth_bendix_non_trivial_classes as _knuth_bendix_non_trivial_classes, @@ -31,6 +39,7 @@ from .detail.congruence_common import CongruenceCommon as _CongruenceCommon from .detail.cxx_wrapper import ( + CxxWrapper as _CxxWrapper, copy_cxx_mem_fns as _copy_cxx_mem_fns, register_cxx_wrapped_type as _register_cxx_wrapped_type, to_cxx as _to_cxx, @@ -133,6 +142,59 @@ def __init__(self, *args, rewriting_system="Trie", order=_Order.shortlex, **kwar _register_cxx_wrapped_type(_KnuthBendixWordRPOSet, KnuthBendix) +######################################################################## +# TietzeExplorer +######################################################################## + + +class TietzeExplorer(_CxxWrapper): + __doc__ = _TietzeExplorerStringLenLexTrie.__doc__ + + _py_template_params_to_cxx_type = { + (list[int], "Trie", _Order.shortlex): _TietzeExplorerWordLenLexTrie, + (str, "Trie", _Order.shortlex): _TietzeExplorerStringLenLexTrie, + (list[int], "Set", _Order.shortlex): _TietzeExplorerWordLenLexSet, + (str, "Set", _Order.shortlex): _TietzeExplorerStringLenLexSet, + (list[int], "Trie", _Order.recursive): _TietzeExplorerWordRPOTrie, + (str, "Trie", _Order.recursive): _TietzeExplorerStringRPOTrie, + (list[int], "Set", _Order.recursive): _TietzeExplorerWordRPOSet, + (str, "Set", _Order.recursive): _TietzeExplorerStringRPOSet, + } + + _cxx_type_to_py_template_params = dict( + zip( + _py_template_params_to_cxx_type.values(), + _py_template_params_to_cxx_type.keys(), + strict=True, + ) + ) + + _all_wrapped_cxx_types = {*_py_template_params_to_cxx_type.values()} + + @_copydoc(_TietzeExplorerStringLenLexTrie.__init__) + def __init__(self, kb: KnuthBendix) -> None: + super().__init__(kb) + if _to_cxx(self) is not None: + return + self.py_template_params = kb.py_template_params + self.init_cxx_obj(kb) + + +######################################################################## +# Copy mem fns from sample C++ type and register types +######################################################################## + +_copy_cxx_mem_fns(_TietzeExplorerStringLenLexTrie, TietzeExplorer) + +_register_cxx_wrapped_type(_TietzeExplorerStringLenLexTrie, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerWordLenLexTrie, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerStringLenLexSet, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerWordLenLexSet, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerStringRPOTrie, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerWordRPOTrie, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerStringRPOSet, TietzeExplorer) +_register_cxx_wrapped_type(_TietzeExplorerWordRPOSet, TietzeExplorer) + ######################################################################## # Helpers ######################################################################## @@ -146,6 +208,7 @@ def __init__(self, *args, rewriting_system="Trie", order=_Order.shortlex, **kwar __all__ = [ "KnuthBendix", + "TietzeExplorer", "by_overlap_length", "is_reduced", "non_trivial_classes", diff --git a/tests/test_knuth_bendix.py b/tests/test_knuth_bendix.py index 505dc499..9f41c1ce 100644 --- a/tests/test_knuth_bendix.py +++ b/tests/test_knuth_bendix.py @@ -20,7 +20,6 @@ LibsemigroupsError, Order, Presentation, - ReportGuard, StringRange, congruence_kind, is_obviously_infinite, @@ -39,7 +38,6 @@ def check_initialisation(*args): def test_initialisation(): - ReportGuard(False) kinds = [congruence_kind.twosided, congruence_kind.onesided] p = Presentation("ba") @@ -68,7 +66,6 @@ def test_initialisation(): def test_attributes(): - ReportGuard(False) p = Presentation("abBe") presentation.add_identity_rules(p, "e") presentation.add_inverse_rules(p, "aBbe", "e") @@ -104,7 +101,6 @@ def test_attributes(): def test_operators(): - ReportGuard(False) p = Presentation("abBe") presentation.add_identity_rules(p, "e") presentation.add_inverse_rules(p, "aBbe", "e") @@ -133,8 +129,6 @@ def test_operators(): def test_running_state(): - ReportGuard(False) - p = Presentation("abce") presentation.add_identity_rules(p, "e") presentation.add_rule(p, "aa", "e") @@ -353,6 +347,52 @@ def test_rpo(): assert list(kb.active_rules()) == [("bbb", ""), ("aa", ""), ("abb", "baba"), ("abab", "bba")] +def test_tietze_explorer(): + p = Presentation("bABa") + p.contains_empty_word(True) + presentation.add_inverse_rules(p, "BabA") + presentation.add_rule(p, "Abba", "BB") + presentation.add_rule(p, "Baab", "AA") + + kb = KnuthBendix(congruence_kind.twosided, p, order=Order.recursive) + dora = knuth_bendix.TietzeExplorer(kb) + result = dora.depth_max(2).result() + + assert result.finished() + assert result.presentation().alphabet() == "bBcAa" + assert result.presentation().rules == [ + "bB", + "", + "Aa", + "", + "Bb", + "", + "aA", + "", + "Abba", + "BB", + "cb", + "AA", + "c", + "Baa", + ] + + assert sorted(result.active_rules()) == [ + ("AA", "cb"), + ("BA", "bAbb"), + ("Bb", ""), + ("Bc", "bcBB"), + ("a", "Abc"), + ("bB", ""), + ("bbA", "ABB"), + ("bbc", "cbb"), + ("cA", "bAbcbb"), + ("cbA", "Acb"), + ("cc", "BB"), + ] + assert result.confluent() + + # TODO(0) Does the alphabet bug persist? YES: the test fails # def test_alphabet_bug(): # p = Presentation("".join(chr(i) for i in range(-126, 128)))