diff --git a/docs/source/conf.py b/docs/source/conf.py index ac68796a..d35f9f91 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -161,7 +161,19 @@ def no_doc_run(self): r"libsemigroups::DynamicMatrix, " r"libsemigroups::IntegerProd, libsemigroups::IntegerZero" r", libsemigroups::IntegerOne, long long>" - ): "Matrix", + ): r"Matrix", + r"libsemigroups::SimsStats": r"SimsStats", + r"libsemigroups::Sims1": r"Sims1", + r"SimsSettingsSims1": r"Sims1", + r"libsemigroups::Sims2": r"Sims2", + r"SimsSettingsSims2": r"Sims2", + r"libsemigroups::RepOrc": r"RepOrc", + r"SimsSettingsRepOrc": r"RepOrc", + ( + r"libsemigroups::DynamicMatrix" + ): r"Matrix", } # This dictionary should be of the form class_name -> (pattern, repl), where @@ -216,6 +228,10 @@ def no_doc_run(self): ("PTransfBase16", "StaticPerm16"), ("Transf", "StaticPerm16"), ], + "Sims1": [("SubclassType", "Sims1")], + "Sims2": [("SubclassType", "Sims2")], + "RepOrc": [("SubclassType", "RepOrc")], + "MinimalRepOrc": [("SubclassType", "MinimalRepOrc")], } # This is what sphinx considers to be a signature diff --git a/docs/source/index.rst b/docs/source/index.rst index ed7f27cf..7d024e50 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -71,6 +71,7 @@ See the installation instructions: main-algorithms/knuth-bendix/index main-algorithms/konieczny/index main-algorithms/radoszewski-rytter/index + main-algorithms/sims/index main-algorithms/stephen/index main-algorithms/todd-coxeter/index main-algorithms/ukkonen/index diff --git a/docs/source/main-algorithms/sims/index.rst b/docs/source/main-algorithms/sims/index.rst new file mode 100644 index 00000000..fa801a5e --- /dev/null +++ b/docs/source/main-algorithms/sims/index.rst @@ -0,0 +1,20 @@ +.. Copyright (c) 2024 R. Cirpons + + Distributed under the terms of the GPL license version 3. + + The full license is in the file LICENSE, distributed with this software. + +Sims +======= + +This page contains links to the functionality in ``libsemigroups_pybind11`` +related to Sims's low-index congruence algorithm +:cite:`Anagnostopoulou-Merkouri2023aa` for finitely presented semigroups. + +.. toctree:: + :maxdepth: 1 + + sims + sims-helpers + + diff --git a/docs/source/main-algorithms/sims/sims-helpers.rst b/docs/source/main-algorithms/sims/sims-helpers.rst new file mode 100644 index 00000000..d954f757 --- /dev/null +++ b/docs/source/main-algorithms/sims/sims-helpers.rst @@ -0,0 +1,38 @@ +.. Copyright (c) 2024 R. Cirpons + + Distributed under the terms of the GPL license version 3. + + The full license is in the file LICENSE, distributed with this software. + +.. currentmodule:: _libsemigroups_pybind11 + +Sims helpers +============ + +This page contains the documentation for various helper functions for +manipulating ``Sims1``, ``Sims2``, ``RepOrc`` or ``MinimalRepOrc`` objects. +All such functions are contained in the submodule +``libsemigroups_pybind11.sims``. + + +Contents +-------- + +.. currentmodule:: libsemigroups_pybind11.sims + +.. autosummary:: + :nosignatures: + + is_maximal_right_congruence + is_right_congruence + is_right_congruence_of_dual + is_two_sided_congruence + poset + right_generating_pairs + two_sided_generating_pairs + +Full API +-------- +.. automodule:: libsemigroups_pybind11.sims + :members: + :imported-members: diff --git a/docs/source/main-algorithms/sims/sims.rst b/docs/source/main-algorithms/sims/sims.rst new file mode 100644 index 00000000..b5419629 --- /dev/null +++ b/docs/source/main-algorithms/sims/sims.rst @@ -0,0 +1,76 @@ +.. Copyright (c) 2024 R. Cirpons + + Distributed under the terms of the GPL license version 3. + + The full license is in the file LICENSE, distributed with this software. + +.. currentmodule:: _libsemigroups_pybind11 + +Sims +==== + +On this page we describe the functionality relating to the Sims's +low-index congruence algorithm for semigroups and monoids that is available in +``libsemigroups_pybind11``. This page contains a details of the methods +of the classes: + +* :py:class:`Sims1` for computing finite index right congruences, +* :py:class:`Sims2` for computing finite index two-sided congruences, +* :py:class:`RepOrc` for computing small degree transformation representations of a finite semigroup or monoid, +* :py:class:`MinimalRepOrc` for computing minimal degree transformation representations of a finite semigroup or monoid. + +.. doctest:: + + >>> from libsemigroups_pybind11 import Sims1, Presentation, presentation + >>> p = Presentation([0, 1]) + >>> presentation.add_rule(p, [0, 1], [1, 0]) + >>> sims = Sims1(p) + >>> sims.number_of_congruences(4) + 117 + +Contents +-------- + +.. autosummary:: + :nosignatures: + + ~Sims1 + ~Sims2 + ~RepOrc + ~MinimalRepOrc + ~SimsStats + ~SimsRefinerFaithful + ~SimsRefinerIdeals + +Full API +-------- + +.. autoclass:: Sims1 + :members: + :inherited-members: + +.. autoclass:: Sims2 + :members: + :inherited-members: + +.. autoclass:: RepOrc + :members: + :inherited-members: + +.. autoclass:: MinimalRepOrc + :members: + :inherited-members: + +.. autoclass:: SimsStats + :members: + :inherited-members: + +.. autoclass:: SimsRefinerFaithful + :members: + :inherited-members: + :special-members: __call__ + +.. autoclass:: SimsRefinerIdeals + :members: + :inherited-members: + :special-members: __call__ diff --git a/libsemigroups_pybind11/__init__.py b/libsemigroups_pybind11/__init__.py index 3d194526..78e316a9 100644 --- a/libsemigroups_pybind11/__init__.py +++ b/libsemigroups_pybind11/__init__.py @@ -64,6 +64,12 @@ Dot, PBR, Ukkonen, + Sims1, + Sims2, + RepOrc, + MinimalRepOrc, + SimsRefinerFaithful, + SimsRefinerIdeals, ) except ModuleNotFoundError as e: raise ModuleNotFoundError( diff --git a/libsemigroups_pybind11/sims.py b/libsemigroups_pybind11/sims.py new file mode 100644 index 00000000..71d4a8ba --- /dev/null +++ b/libsemigroups_pybind11/sims.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 R. Cirpons +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +# pylint: disable=no-name-in-module, invalid-name, unused-import, fixme, +# pylint: disable=unidiomatic-typecheck + +""" +This package provides the user-facing python part of libsemigroups_pybind11 for +the Sims class from libsemigroups. +""" + +from _libsemigroups_pybind11 import ( + right_generating_pairs, + two_sided_generating_pairs, + is_right_congruence, + is_right_congruence_of_dual, + is_two_sided_congruence, + is_maximal_right_congruence, + poset, +) diff --git a/pyproject.toml b/pyproject.toml index 2a3d25ce..c6f14f94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,3 +10,11 @@ requires = [ ] build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +markers = [ + "quick", + "standard", + "extreme", + "fail", +] diff --git a/src/main.cpp b/src/main.cpp index c9453f98..396c94b8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -154,6 +154,7 @@ namespace libsemigroups { init_present(m); init_inverse_present(m); init_to_present(m); + init_sims(m); init_transf(m); init_ranges(m); init_words(m); diff --git a/src/main.hpp b/src/main.hpp index d410a103..9f47ddf9 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -42,6 +42,7 @@ namespace libsemigroups { void init_present(py::module&); void init_inverse_present(py::module&); void init_to_present(py::module&); + void init_sims(py::module&); void init_transf(py::module&); void init_ranges(py::module&); void init_words(py::module&); diff --git a/src/sims.cpp b/src/sims.cpp new file mode 100644 index 00000000..02c90d91 --- /dev/null +++ b/src/sims.cpp @@ -0,0 +1,2147 @@ +// +// libsemigroups_pybind11 +// Copyright (C) 2024 Reinis Cirpons +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// C std headers.... +// TODO complete or delete + +// C++ stl headers.... +// TODO complete or delete +#include // for string +#include // for vector + +// libsemigroups.... + +#include // for Presentation +#include // for rx::begin, rx::end, rx::transform +#include // for Sims1, Sims2, .... +#include // for word_type +#include // for WordGraph + +// pybind11.... +// #include +#include +#include +#include +// TODO uncomment/delete + +// libsemigroups_pybind11.... +#include "main.hpp" // for init_sims + +namespace py = pybind11; + +namespace libsemigroups { + using node_type = uint32_t; + using word_graph_type = WordGraph; + using size_type = typename word_graph_type::size_type; + + template + void bind_sims(py::module& m, std::string const& name) { + using SimsSettings_ = SimsSettings; + + py::class_ ss(m, + name.c_str(), + R"pbdoc( +For setting the presentation and various runtime parameters of the Sims low +index algorithm. + +On this page we describe the :any:`SimsSettings` class. The purpose of this +class is to allow us to use the same interface for settings for :any:`Sims1` , +:any:`Sims2`, :any:`RepOrc` and :any:`MinimalRepOrc`. + +.. seealso:: :any:`Sims1`, :any:`Sims2`, :any:`RepOrc`, :any:`MinimalRepOrc` +)pbdoc"); + ss.def(py::init<>(), R"pbdoc( +Default constructor. + +This function constructs a :any:`SimsSettings` object. Defaults to a single +thread and 64 idle thread restarts, no other settings set. Use + +* :py:meth:`~Sims1.presentation` to set the presentation; +* :py:meth:`~Sims1.number_of_threads` to set the number of threads; +* :py:meth:`~Sims1.include` to set the pairs to be included; +* :py:meth:`~Sims1.exclude` to set the pairs to be excluded; +* :py:meth:`~Sims1.add_pruner` to add a pruner; +* :py:meth:`~Sims1.long_rule_length` to set the length of long rules; +* :py:meth:`~Sims1.idle_thread_restarts` to set the number of idle thread restarts. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def(py::init(), R"pbdoc( +Copy constructor. + +This function returns a :any:`SimsSettings` object that is a copy of ``that`` . +The state of the new :any:`SimsSettings` object is the same as ``that``. + +:param that: the SimsSettings to copy. +:type that: SimsSettings + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def( + "init", + [](SimsSettings_& self) -> Subclass& { return self.init(); }, + R"pbdoc( +Reinitialize an existing SimsSettings object. + +This function puts a :any:`SimsSettings` object back into the same state as if +it had been newly default constructed. + +:returns: A reference to *self*. + +:raises: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + // TODO(0) should we remove this function? + ss.def("settings", + &SimsSettings_::settings, + R"pbdoc( +Returns the settings object of *self*. + +The settings object contains all the settings that are common to :any:`Sims1`, +:any:`Sims2`, :any:`RepOrc` and :any:`MinimalRepOrc` , which are currently: + +* :py:meth:`~Sims1.presentation` +* :py:meth:`~Sims1.long_rules` +* :py:meth:`~Sims1.number_of_threads` + +The return value of this function can be used to initialise another +:any:`Sims1` , :any:`Sims2` , :any:`RepOrc` , or :any:`MinimalRepOrc` with +these settings. + +:returns: A :any:`SimsSettings` object. +:rtype: SimsSettings + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "number_of_threads", + // TODO(0): add -> Subclass& here and everywhere else where we return a + // subclass + [](SimsSettings_& self, size_t val) -> Subclass& { + return self.number_of_threads(val); + }, + py::arg("val"), + R"pbdoc( +Set the number of threads. + +This function sets the number of threads to be used by :any:`Sims1` or +:any:`Sims2`. The default value is ``1``. + +:param val: the maximum number of threads to use. +:type val: int + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if the argument *val* is 0. +)pbdoc"); + ss.def( + "number_of_threads", + [](SimsSettings_ const& self) { return self.number_of_threads(); }, + R"pbdoc( +Get the number of threads. + +:returns: A ``int``. +:rtype: int + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "presentation", + [](SimsSettings_& self, Presentation const& p) -> Subclass& { + return self.presentation(p); + }, + py::arg("p"), + R"pbdoc( +:sig=(self: SubclassType, p: Presentation) -> SubclassType: +:only-document-once: +Set the presentation over which the congruences produced by an instance are +defined. + +This function sets the presentation over which the congruences produced by an +instance are defined. These are the rules used at every node in the depth first +search conducted by objects of this type. The parameter *p* is always first +converted to a :any:`Presentation` of :ref:`Word` and +it is this converted value that is used. + +:param p: the presentation. +:type p: Presentation + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`to_presentation(p)` throws. + +:raises LibsemigroupsError: + if the alphabet of *p* is non-empty and not equal to that of + :py:meth:`~Sims1.long_rules` or :py:meth:`~Sims1.presentation`. + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. +)pbdoc"); + ss.def( + "presentation", + [](SimsSettings_& self, Presentation const& p) + -> Subclass& { return self.presentation(p); }, + py::arg("p"), + R"pbdoc( +:sig=(self: SubclassType, p: Presentation) -> SubclassType: +:only-document-once: +Set the presentation over which the congruences produced by an instance are +defined. + +This function sets the presentation over which the congruences produced by an +instance are defined. These are the rules used at every node in the depth first +search conducted by objects of this type. The parameter *p* is always first +converted to a :any:`Presentation` of :ref:`Word` and +it is this converted value that is used. + +:param p: the presentation. +:type p: Presentation + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`to_presentation(p)` throws. + +:raises LibsemigroupsError: + if the alphabet of *p* is non-empty and not equal to that of + :py:meth:`~Sims1.long_rules` or :py:meth:`~Sims1.presentation`. + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. +)pbdoc"); + ss.def( + "presentation", + [](SimsSettings_ const& self) { return self.presentation(); }, + R"pbdoc( +:sig=(self: SubclassType) -> Presentation: +:only-document-once: +Get the presentation over which the congruences produced by an instance are +defined. + +This function returns the defining presentation of a :any:`Sims1` or +:any:`Sims2` instance. The congruences computed by `~Sims1.iterator` of the +appropriate subclass are defined over the semigroup or monoid defined by this +presentation. + +:returns: A const reference to ``Presentation``. +:rtype: Presentation + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "first_long_rule_position", + [](SimsSettings_& self, size_t pos) -> Subclass& { + return self.cbegin_long_rules(pos); + }, + py::arg("pos"), + R"pbdoc( +Set the beginning of the long rules (position). + +This function sets the beginning of the long rules using a position in +``self.presentation().rules``. The "long rules" are the rules used after a +complete deterministic word graph has been found in the search. If such a word +graph is compatible with the long rules specified by this function, then this +word graph is accepted, and if not it is rejected. + +The purpose of this is to improve the backtrack search by reducing the time +spent processing "long" rules in each node of the search tree, and to only +check them at the leaves. + +:param pos: position of the the left hand side of the first long rule. +:type pos: int + +:returns: A reference to *self*. + +:raises LibsemigroupsError: + if *pos* is not a valid position in ``self.presentation().rules``. + +:raises LibsemigroupsError: + if the rule at position *pos* is not the left hand side of a rule (i.e. if + *pos* is odd). +)pbdoc"); + ss.def( + "long_rules", + [](SimsSettings_ const& self) { + return py::make_iterator(self.cbegin_long_rules(), + self.presentation().rules.cend()); + }, + R"pbdoc( +:sig=(self: SubclassType) -> Iterator[Tuple[Word, Word]]: +Get the long rules. + +Returns an iterator of long rules. + +:returns: A const reference to :any:`Presentation`. +:rtype: Iterator[Tuple[Word, Word]] + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "clear_long_rules", + [](SimsSettings_& self) -> Subclass& { + return self.clear_long_rules(); + }, + R"pbdoc( +Clear the set of long rules. + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def("number_of_long_rules", + &SimsSettings_::number_of_long_rules, + R"pbdoc( +Returns the number of rules marked as long rules. +)pbdoc"); + ss.def( + "long_rule_length", + [](SimsSettings_& self, size_t val) -> Subclass& { + return self.long_rule_length(val); + }, + py::arg("val"), + R"pbdoc( +Set the length of a long rule. + +Define the length of a "long" rule. This function modifies +:py:meth:`~Sims1.presentation()` so that the rules whose length (sum of the +lengths of both sides) is at least *val* (if any) occur at the end of +``presentation().rules`` and so that :py:meth:`~Sims1.long_rules` returns all +such rules. The relative orders of the rules within +:py:meth:`~Sims1.presentation` may not be preserved. + +:param val: the long rule length. +:type val: int + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def("pruners", + &SimsSettings_::pruners, + R"pbdoc( +Get all active pruners of the search tree. + +This function returns the list of pruners. A pruner is any function that takes +as input a word graph and returns a boolean. We require that if a pruner +returns ``False`` for a word graph ``wg``, then it returns ``False`` for all +word graphs that are descended from ``wg`` in the Sims word graph search tree. + +The pruners are used to refine the congruence search tree during the execution +of the Sims algorithm. As such, the congruences computed by this instance are +only taken among those whose word graphs are accepted by all pruners returned +by :py:meth:`~Sims1.pruners`. + +:returns: A list of boolean functions on word graphs, the set of all pruners. +:rtype: List[Callable[[WordGraph], bool]] + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "add_pruner", + [](SimsSettings_& self, SimsRefinerIdeals const& func) -> Subclass& { + return self.add_pruner(func); + }, + py::arg("pruner"), + R"pbdoc( +:sig=(self: SubclassType, pruner: Callable[[WordGraph], bool]) -> SubclassType: +:only-document-once: +Add a pruner to the search tree. + +:param pruner: a pruner function. +:type pruner: Callable[[WordGraph], bool] + +:returns: A reference to *self*. + +.. warning:: + When running the Sims low-index backtrack with multiple threads, each added + pruner must be guaranteed thread safe. Failing to do so could cause bad + things to happen. +)pbdoc"); + ss.def( + "add_pruner", + [](SimsSettings_& self, SimsRefinerFaithful const& func) -> Subclass& { + return self.add_pruner(func); + }, + py::arg("pruner"), + R"pbdoc( +:sig=(self: SubclassType, pruner: Callable[[WordGraph], bool]) -> SubclassType: +:only-document-once: +Add a pruner to the search tree. + +:param pruner: a pruner function. +:type pruner: Callable[[WordGraph], bool] + +:returns: A reference to *self*. + +.. warning:: + When running the Sims low-index backtrack with multiple threads, each added + pruner must be guaranteed thread safe. Failing to do so could cause bad + things to happen. +)pbdoc"); + ss.def( + "add_pruner", + [](SimsSettings_& self, + std::function func) -> Subclass& { + return self.add_pruner(func); + }, + py::arg("pruner"), + R"pbdoc( +:sig=(self: SubclassType, pruner: Callable[[WordGraph], bool]) -> SubclassType: + Callable[[WordGraph], bool]) -> Sims1: +:only-document-once: +Add a pruner to the search tree. + +:param pruner: a pruner function. +:type pruner: Callable[[WordGraph], bool] + +:returns: A reference to *self*. + +.. warning:: + When running the Sims low-index backtrack with multiple threads, each added + pruner must be guaranteed thread safe. Failing to do so could cause bad + things to happen. +)pbdoc"); + ss.def( + "clear_pruners", + [](SimsSettings_& self) -> Subclass& { return self.clear_pruners(); }, + R"pbdoc( +Clear the set of pruners. + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def( + "include", + [](SimsSettings_ const& self) { return self.include(); }, + R"pbdoc( +:sig=(self: SubclassType) -> List[Word]: +Returns the set of pairs that must be excluded from every congruence. + +This function returns the list of included pairs. The congruences computed by a +:any:`Sims1` or :any:`Sims2` instance always contain the relations of this +list. In other words, the congruences computed by this instance are only taken +among those that contains the pairs of elements of the underlying semigroup +(defined by the presentation returned by :py:meth:`~Sims1.presentation()` and +:py:meth:`~Sims1.long_rules()`) represented by the relations of the list of +words returned by :py:meth:`~Sims1.include()`. + +:returns: + A list of words ``result`` such that ``(result[2*i], result[2*i+1])`` is + the ``i``-th included pair. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "include", + [](SimsSettings_& self, word_type const& lhs, word_type const& rhs) + -> Subclass& { return self.include(lhs, rhs); }, + py::arg("lhs"), + py::arg("rhs"), + R"pbdoc( +:sig=(self: SubclassType, lhs: Word, rhs: Word) -> SubclassType: +Add a pair that should be included in every congruence. + +:param lhs: the left hand side of the rule being added. +:type lhs: :ref:`Word` + +:param rhs: the right hand side of the rule being added. +:type rhs: :ref:`Word` + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`validate_word` throws on *lhs* or *rhs*. +)pbdoc"); + ss.def( + "include", + [](SimsSettings_& self, std::vector const& c) -> Subclass& { + return self.include(c); + }, + py::arg("included_pairs"), + R"pbdoc( +:sig=(self: SubclassType, included_pairs: list[Word]) -> SubclassType: +Define the set of pairs that must be included in every congruence. + +Define the set of pairs that must be included in every congruence. The +congruences computed by an instance of this type will always contain the +relations input here. In other words, the congruences computed are only taken +among those that contains the pairs of elements of the underlying semigroup +(defined by the presentation returned by :py:meth:`~Sims1.presentation` ) +represented by the relations returned by :py:meth:`~Sims1.include`. + +:param included_pairs: A list of pairs to be included. +:type included_pairs: List[:ref:`Word`] + +:returns: A reference to *self*. + +:raises LibsemigroupsError: + if *included_pairs* does not define a set of pairs, i.e. there are an odd + number of words in *included_pairs*. + +:raises LibsemigroupsError: + if :py:meth:`validate_word` throws for any word ``w`` in *included_pairs*. +)pbdoc"); + ss.def( + "clear_include", + [](SimsSettings_& self) -> Subclass& { return self.clear_include(); }, + R"pbdoc( +Clear the set of included words. + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def( + "exclude", + [](SimsSettings_ const& self) { return self.exclude(); }, + R"pbdoc( +:sig=(self: SubclassType) -> List[Word]: +Returns the set of pairs that must be excluded from every congruence. + +This function returns the list of excluded pairs. The congruences computed by a +:any:`Sims1` or :any:`Sims2` instance will never contain the relations of this +presentation. In other words, the congruences computed by this instance are +only taken among those that do not contain any of the pairs of elements of the +underlying semigroup (defined by the presentation returned by +:py:meth:`~Sims1.presentation()` and :py:meth:`~Sims1.long_rules()`) +represented by the relations of the presentation returned by +:py:meth:`~Sims1.exclude()`. + +:returns: + A list of words ``result`` such that ``(result[2*i], result[2*i+1])`` is + the ``i``-th excluded pair. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "exclude", + [](SimsSettings_& self, + word_type const& lhs, + word_type const& rhs) -> Subclass { return self.exclude(lhs, rhs); }, + py::arg("lhs"), + py::arg("rhs"), + R"pbdoc( +:sig=(self: SubclassType, lhs: Word, rhs: Word) -> SubclassType: +Add a pair that must be excluded from every congruence. + +:param lhs: the left hand side of the rule being added. +:type lhs: :ref:`Word` + +:param rhs: the right hand side of the rule being added. +:type rhs: :ref:`Word` + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`validate_word` throws on *lhs* or *rhs*. +)pbdoc"); + ss.def( + "exclude", + [](SimsSettings_& self, std::vector const& c) -> Subclass& { + return self.exclude(c); + }, + py::arg("excluded_pairs"), + R"pbdoc( +:sig=(self: SubclassType, exluded_pairs: list[Word]) -> SubclassType: +Define the set of pairs that must be excluded from every congruence. + +This function defines the set of pairs that must be excluded from every +congruence. The congruences computed by an instance of this type will never +contain the relations input here. In other words, the congruences computed are +only taken among those that do not contain the pairs of elements of the +underlying semigroup (defined by the presentation returned by +:py:meth:`~Sims1.presentation` ) represented by the relations returned by +:py:meth:`~Sims1.exclude`. + +:param excluded_pairs: A list of pairs to be excluded. +:type excluded_pairs: List[:ref:`Word`] + +:returns: A reference to *self*. + +:raises LibsemigroupsError: + if *excluded_pairs* does not define a set of pairs, i.e. there are an odd + number of words in *excluded_pairs*. + +:raises LibsemigroupsError: + if :py:meth:`validate_word` throws for any word ``w`` in *excluded_pairs*. +)pbdoc"); + ss.def( + "clear_exclude", + [](SimsSettings_& self) -> Subclass& { return self.clear_exclude(); }, + R"pbdoc( +Clear the set of excluded words. + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ss.def("stats", + &SimsSettings_::stats, + R"pbdoc( +Get the current stats object. + +This function returns the current stats object. The value returned by this +function is a :any:`SimsStats` object which contains some statistics related to +the current :any:`Sims1` or :any:`Sims2` instance and any part of the depth +first search already conducted. + +:returns: A const reference to :any:`SimsStats`. +:rtype: SimsStats + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "idle_thread_restarts", + [](SimsSettings_ const& self) { return self.idle_thread_restarts(); }, + R"pbdoc( +Get the idle thread restart attempt count. + +This function returns the number of times an idle thread will attempt to +restart before yielding during execution. + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ss.def( + "idle_thread_restarts", + [](SimsSettings_& self, size_t val) -> Subclass& { + return self.idle_thread_restarts(val); + }, + py::arg("val"), + R"pbdoc( +Set the idle thread restart attempt count. + +This function sets the idle thread restart attempt count. The default value is +``64``. + +:param val: + the maximum number of times an idle thread will attempt to restart before + yielding. +:type val: int + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if the argument *val* is 0. +)pbdoc"); + ss.def(py::init(), R"pbdoc( +Construct from Subclass object. +)pbdoc"); + ss.def(py::init(), R"pbdoc( +Construct from Sims1 object. +)pbdoc"); + ss.def(py::init(), R"pbdoc( +Construct from Sims2 object. +)pbdoc"); + ss.def(py::init(), R"pbdoc( +Construct from RepOrc object. +)pbdoc"); + ss.def(py::init(), R"pbdoc( +Construct from MinimalRepOrc object. +)pbdoc"); + // TODO(0) check if these need a -> Subclass& + // It fails to compile if you add it, but then its unclear what these do + // without it + ss.def( + "init", + [](SimsSettings_& self, Subclass const& that) { + return self.init(that); + }, + py::arg("that"), + R"pbdoc( +Initialize from Subclass object. +)pbdoc"); + ss.def( + "init", + [](SimsSettings_& self, Sims1 const& that) { return self.init(that); }, + py::arg("that"), + R"pbdoc( +Initialize from Sims1 object. +)pbdoc"); + ss.def( + "init", + [](SimsSettings_& self, Sims2 const& that) { return self.init(that); }, + py::arg("that"), + R"pbdoc( +Initialize from Sims2 object. +)pbdoc"); + ss.def( + "init", + [](SimsSettings_& self, RepOrc const& that) { return self.init(that); }, + py::arg("that"), + R"pbdoc( +Initialize from RepOrc object. +)pbdoc"); + ss.def( + "init", + [](SimsSettings_& self, MinimalRepOrc const& that) { + return self.init(that); + }, + py::arg("that"), + R"pbdoc( +Initialize from MinimalRepOrc object. +)pbdoc"); + } + + void init_sims(py::module& m) { + bind_sims(m, "SimsSettingsSims1"); + bind_sims(m, "SimsSettingsSims2"); + bind_sims(m, "SimsSettingsRepOrc"); + bind_sims(m, "SimsSettingsMinimalRepOrc"); + + py::class_ st(m, + "SimsStats", + R"pbdoc( +For keeping track of various statistics arising during the runtime of the low +index algorithm. + +The purpose of this class is to collect some statistics related to :any:`Sims1` +or :any:`Sims2`. + +.. seealso:: :any:`Sims1`, :any:`Sims2` +)pbdoc"); + st.def("__repr__", + [](SimsStats const& st) { return to_human_readable_repr(st); }); + st.def( + "count_last", + [](SimsStats const& d) { return d.count_last.load(); }, + R"pbdoc( +Get the number of congruences found at time of last report. + +This function returns member holds the number of congruences found by the +:any:`Sims1` or :any:`Sims2` algorithm at the time of the last call to +:py:meth:`~SimsStats.stats_check_point`. + +.. seealso:: + :py:meth:`~SimsStats.stats_check_point`, :py:meth:`~SimsStats.count_now` +)pbdoc"); + st.def( + "count_now", + [](SimsStats const& d) { return d.count_now.load(); }, + R"pbdoc( +Get number of congruences found up to this point. + +This function returns the total number of congruences found during the running +of the :any:`Sims1` or :any:`Sims2` algorithm. + +.. seealso:: :py:meth:`~SimsStats.count_last` +)pbdoc"); + st.def( + "max_pending", + [](SimsStats const& d) { return d.max_pending.load(); }, + R"pbdoc( +Get the maximum number of pending definitions. + +A "pending definition" is just an edge that will be defined at some point in +the future in the :any:`WordGraph` represented by a :any:`Sims1` or +:any:`Sims2` instance at any given moment. This function returns the maximum +number of such pending definitions that occur during the running of the +algorithms in :any:`Sims1` or :any:`Sims2`. +)pbdoc"); + st.def( + "total_pending_last", + [](SimsStats const& d) { return d.total_pending_last.load(); }, + R"pbdoc( +Get the total number of pending definitions at time of last report. + +A "pending definition" is just an edge that will be defined at some point in +the future in the :any:`WordGraph` represented by a :any:`Sims1` or +:any:`Sims2` instance at any given moment. This function returns the total +number of pending definitions that occur at the time of the last call to +:py:meth:`~SimsStats.stats_check_point`. This is the same as the number of +nodes in the search tree encounter during the running of :any:`Sims1` or +:any:`Sims2`. + +.. seealso:: + :py:meth:`~SimsStats.stats_check_point`, + :py:meth:`~SimsStats.total_pending_now` +)pbdoc"); + st.def( + "total_pending_now", + [](SimsStats const& d) { return d.total_pending_now.load(); }, + R"pbdoc( +Get the total number of pending definitions. + +A "pending definition" is just an edge that will be defined at some point in +the future in the :any:`WordGraph` represented by a :any:`Sims1` or +:any:`Sims2` instance at any given moment. This function returns the total +number of pending definitions that occur during the running of the algorithms +in :any:`Sims1` or :any:`Sims2`. This is the same as the number of nodes in the +search tree encounter during the running of :any:`Sims1` or :any:`Sims2`. + +.. seealso:: :py:meth:`~SimsStats.total_pending_last` +)pbdoc"); + st.def(py::init<>(), R"pbdoc( +Default constructor. + +Constructs a :any:`SimsStats` object with all statistics set to zero. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + st.def(py::init(), + py::arg("that"), + R"pbdoc( +Copy constructor. + +This function returns a :any:`SimsStats` object that is a copy of *that*. The +state of the new :any:`SimsStats` object is the same as *that*. This triggers +an atomic load on the member variables of *that*. + +:param that: the :any:`SimsStats` to copy. +:type that: SimsStats + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + st.def( + "init", + [](SimsStats& self) -> SimsStats& { return self.init(); }, + R"pbdoc( +Reinitialize an existing SimsStats object. + +This function puts a :any:`SimsStats` object back into the same state as if it +had been newly default constructed. + +:returns: A reference to *self*. +:rtype: SimsStats + +:raises This: function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + st.def( + "init", + [](SimsStats& self, SimsStats const& that) -> SimsStats& { + return self.init(that); + }, + py::arg("that"), + R"pbdoc( +This function reinitializes the :any:`SimsStats` object from *that*. The state +of the new :any:`SimsStats` object is the same as *that*. This triggers an +atomic load on the member variables of *that*. + +:param that: the :any:`SimsStats` to reinitialize from. +:type that: SimsStats + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + st.def("stats_check_point", + &SimsStats::stats_check_point, + R"pbdoc( +Store the current statistic values. + +This function overwrites the values of :py:meth:`~SimsStats.count_last` and +:py:meth:`~SimsStats.total_pending_last` with :py:meth:`~SimsStats.count_now` +and :py:meth:`~SimsStats.total_pending_now` respectively. Triggers an atomic +load on :py:meth:`~SimsStats.count_now` and +:py:meth:`~SimsStats.total_pending_now`. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + st.def("stats_zero", + &SimsStats::stats_zero, + R"pbdoc( +Set all statistics to zero. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + + py::class_> s1(m, + "Sims1", + R"pbdoc( +For computing finite index right congruences of a finitely presented semigroup +or monoid. + +The algorithm implemented by this class is essentially the low index subgroup +algorithm for finitely presented groups described in Section 5.6 of +:cite:`Sims1994aa`. The low index subgroups algorithm was adapted for +semigroups and monoids by R. Cirpons, J. D. Mitchell, and M. Tsalakou; see +:cite:`Anagnostopoulou-Merkouri2023aa`. + +The purpose of this class is to provide the functions +:py:meth:`~Sims1.iterator`, :py:meth:`~Sims1.for_each` and +:py:meth:`~Sims1.find_if`, which permit iterating through the one-sided +congruences of a semigroup or monoid defined by a presentation containing (a +possibly empty) set of pairs and with at most a given number of classes. An +iterator returned by :py:meth:`~Sims1.iterator` yields :any:`WordGraph` +instances representing the action of the semigroup or monoid on the classes of +a congruence. + +.. seealso:: :any:`Sims2` for equivalent functionality for 2-sided congruences. +)pbdoc"); + + s1.def("__repr__", + [](Sims1 const& s1) { return to_human_readable_repr(s1); }); + s1.def(py::init<>(), R"pbdoc( +:sig=(self: Sims1) -> None: +:only-document-once: +Default constructor. +)pbdoc"); + s1.def(py::init const&>(), + py::arg("p"), + R"pbdoc( +:sig=(self: Sims1, p: Presentation) -> None: +:only-document-once: +Construct from a presentation. + +The rules of the presentation *p* are used at every node in the depth first +search conducted by an object of this type. + +:raises LibsemigroupsError: if :any:`to_presentation` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. + +.. seealso:: :py:meth:`~Sims1.presentation`, :py:meth:`~Sims1.init` +)pbdoc"); + s1.def(py::init const&>(), + py::arg("p"), + R"pbdoc( +:sig=(self: Sims1, p: Presentation) -> None: +:only-document-once: +Construct from a presentation. + +The rules of the presentation *p* are used at every node in the depth first +search conducted by an object of this type. + +:raises LibsemigroupsError: if :any:`to_presentation` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. + +.. seealso:: :py:meth:`~Sims1.presentation`, :py:meth:`~Sims1.init` +)pbdoc"); + s1.def(py::init(), + py::arg("other"), + R"pbdoc( +:sig=(self: Sims1, other: Sims1) -> None: +:only-document-once: +Default copy constructor. +)pbdoc"); + s1.def( + "init", + [](Sims1& self, Presentation const& p) -> Sims1& { + return self.init(p); + }, + py::arg("p"), + R"pbdoc( +:sig=(self: Sims1, p: Presentation) -> Sims1: +:only-document-once: +Reinitialize an existing :any:`Sims1` object from a presentation. + +This function puts an object back into the same state as if it had been newly +constructed from the presentation *p*. + +:param p: the presentation. +:type p: Presentation + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`to_presentation(p)` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. +)pbdoc"); + s1.def( + "init", + [](Sims1& self, Presentation const& p) -> Sims1& { + return self.init(p); + }, + py::arg("p"), + R"pbdoc( +:sig=(self: Sims1, p: Presentation) -> Sims1: +:only-document-once: +Reinitialize an existing :any:`Sims1` object from a presentation. + +This function puts an object back into the same state as if it had been newly +constructed from the presentation *p*. + +:param p: the presentation. +:type p: Presentation + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`to_presentation(p)` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. +)pbdoc"); + s1.def("number_of_congruences", + &Sims1::number_of_congruences, + py::arg("n"), + R"pbdoc( +Returns the number of one-sided congruences with up to a given number of +classes. + +This function exists to: + +* provide some feedback on the progress of the computation if it runs for more + than 1 second. +* allow for the computation of the number of congruence to be performed using + :py:meth:`~Sims1.number_of_threads` in parallel. + +:param n: the maximum number of congruence classes. +:type n: int + +:returns: + the number of one sided congruences with at most *n* congruence classes. +:rtype: int + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims1.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). +)pbdoc"); + s1.def("for_each", + &Sims1::for_each, + py::arg("n"), + py::arg("pred"), + R"pbdoc( +Apply a unary predicate to every one-sided congruence with at most a given +number of classes. + +This function applies the function *pred* to every one-sided congruence with at +most *n* classes. This function exists to: + +* provide some feedback on the progress of the computation if it runs for more + than 1 second. +* allow for a function to be applied to all found word graphs using + :py:meth:`~Sims1.number_of_threads` in parallel. + +:param n: the maximum number of congruence classes. +:type n: int + +:param pred: the predicate applied to every congruence found. +:type pred: Callable[[WordGraph], None] + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims1.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). + +.. seealso:: :py:meth:`~Sims1.iterator`, :py:meth:`~Sims1.find_if` +)pbdoc"); + s1.def("find_if", + &Sims1::find_if, + py::arg("n"), + py::arg("pred"), + R"pbdoc( +Apply a unary predicate to one-sided congruences with at most a given number of +classes, until it returns ``True``. + +This function applies the predicate *pred* to every congruence with at most *n* +classes, until a congruence satisfying the predicate is found. This function +exists to: + +* provide some feedback on the progress of the computation if it runs for more + than 1 second. +* allow for searching for a congruence satisfying certain conditions using + :py:meth:`~Sims1.number_of_threads` in parallel. + +:param n: the maximum number of congruence classes. +:type n: int + +:param pred: the predicate applied to every congruence found. +:type pred: Callable[[WordGraph], bool] + +:returns: + The first :any:`WordGraph` for which *pred* returns ``True``, or the empty + word graph if no such word graph exists. +:rtype: WordGraph + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims1.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). + +.. seealso:: :py:meth:`~Sims1.iterator`, :py:meth:`~Sims1.for_each` +)pbdoc"); + s1.def( + "iterator", + [](Sims1 const& self, size_type n) { + return py::make_iterator(self.cbegin(n), self.cend(n)); + }, + py::arg("n"), + R"pbdoc( +Returns an iterator yielding all congruences of index at most *n*. + +This function returns an iterator yielding instances of :any:`WordGraph` that +represent the congruences with at most *n* classes. The order in which the +congruences are yielded by the iterator is implementation specific. The meaning +of the :any:`WordGraph` objects yielded by the iterator depends on whether the +input is a monoid presentation (i.e. +:py:meth:`~Presentation::contains_empty_word()` returns ``True`` ) or a +semigroup presentation. + +If the input is a monoid presentation for a monoid :math:`M` , then the +:any:`WordGraph` pointed to by an iterator of this type has at most *n* nodes, +and the right action of :math:`M` on the nodes of the word graph is isomorphic +to the action of :math:`M` on the classes of a right congruence. + +If the input is a semigroup presentation for a semigroup :math:`S`, then the +:any:`WordGraph` has at most *n* + 1 nodes, and the right action of :math:`S` +on the nodes :math:`\{1, \ldots, n\}` of the :any:`WordGraph` is isomorphic to +the action of :math:`S` on the classes of a right congruence. It'd probably be +better in this case if node :math:`0` was not included in the output +:any:`WordGraph`, but it is required in the implementation of the low-index +congruence algorithm, and to avoid unnecessary copies, we've left it in for the +time being. + +:param n: the maximum number of classes in a congruence. +:type n: int + +:returns: + An iterator ``it`` yielding :any:`WordGraph` objects with at most *n* or + *n* + 1 nodes depending on the presentation, see above. +:rtype: Iterator[WordGraph] + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims1.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). +)pbdoc"); + + py::class_> s2(m, + "Sims2", + R"pbdoc( +For computing finite index two-sided congruences of a finitely presented +semigroup or monoid. + +The algorithm implemented by this class is described in +:cite:`Anagnostopoulou-Merkouri2023aa`. The purpose of this class is to provide +the functions :py:meth:`~Sims2.iterator`, :py:meth:`~Sims2.for_each` and +:py:meth:`~Sims2.find_if`, which permit iterating through the two-sided +congruences of a semigroup or monoid defined by a presentation containing, or +not containing, (possibly empty) sets of pairs and with at most a given number +of classes. An iterator returned by :py:meth:`~Sims2.iterator` yields +:any:`WordGraph` instances representing the action of the semigroup or monoid +on the classes of a congruence. + +.. seealso:: + :any:`Sims1` for equivalent functionality for 1-sided congruences. +)pbdoc"); + s2.def("__repr__", + [](Sims2 const& s2) { return to_human_readable_repr(s2); }); + s2.def(py::init<>(), R"pbdoc( +Default constructor. +)pbdoc"); + s2.def(py::init(), R"pbdoc( +Default copy constructor. +)pbdoc"); + s2.def(py::init const&>(), R"pbdoc( +:sig=(self: Sims2, p: Presentation) -> None: +:only-document-once: +Construct from a presentation. + +The rules of the presentation *p* are used at every node in the depth first +search conducted by an object of this type. + +:raises LibsemigroupsError: if :any:`to_presentation` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. + +.. seealso:: :py:meth:`~Sims2.presentation`, :py:meth:`~Sims2.init` +)pbdoc"); + s2.def(py::init const&>(), R"pbdoc( +:sig=(self: Sims2, p: Presentation) -> None: +:only-document-once: +Construct from a presentation. + +The rules of the presentation *p* are used at every node in the depth first +search conducted by an object of this type. + +:raises LibsemigroupsError: if :any:`to_presentation` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. + +.. seealso:: :py:meth:`~Sims2.presentation`, :py:meth:`~Sims2.init` +)pbdoc"); + s2.def( + "init", + [](Sims2& self, Presentation const& p) -> Sims2& { + return self.init(p); + }, + py::arg("p"), + R"pbdoc( +:sig=(self: Sims2, p: Presentation) -> Sims2: +:only-document-once: +Reinitialize an existing :any:`Sims2` object from a presentation. + +This function puts an object back into the same state as if it had been newly +constructed from the presentation *p*. + +:param p: the presentation. +:type p: Presentation + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`to_presentation(p)` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. +)pbdoc"); + s2.def( + "init", + [](Sims2& self, Presentation const& p) -> Sims2& { + return self.init(p); + }, + py::arg("p"), + R"pbdoc( +:sig=(self: Sims2, p: Presentation) -> Sims2: +:only-document-once: +Reinitialize an existing :any:`Sims2` object from a presentation. + +This function puts an object back into the same state as if it had been newly +constructed from the presentation *p*. + +:param p: the presentation. +:type p: Presentation + +:returns: A reference to *self*. + +:raises LibsemigroupsError: if :any:`to_presentation(p)` throws + +:raises LibsemigroupsError: if *p* has 0-generators and 0-relations. +)pbdoc"); + s2.def("number_of_congruences", + &Sims2::number_of_congruences, + py::arg("n"), + R"pbdoc( +Returns the number of two-sided congruences with up to a given number of +classes. + +This function exists to: + +* provide some feedback on the progress of the computation if it runs for more + than 1 second. +* allow for the computation of the number of congruence to be performed using + :py:meth:`~Sims2.number_of_threads` in parallel. + +:param n: the maximum number of congruence classes. +:type n: int + +:returns: + the number of two-sided congruences with at most *n* congruence classes. +:rtype: int + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims2.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). +)pbdoc"); + s2.def("for_each", + &Sims2::for_each, + py::arg("n"), + py::arg("pred"), + R"pbdoc( +Apply a unary predicate to every two-sided congruence with at most a given +number of classes. + +This function applies the function *pred* to every two-sided congruence with at +most *n* classes. This function exists to: + +* provide some feedback on the progress of the computation if it runs for more + than 1 second. +* allow for a function to be applied to all found word graphs using + :py:meth:`~Sims2.number_of_threads` in parallel. + +:param n: the maximum number of congruence classes. +:type n: int + +:param pred: the predicate applied to every congruence found. +:type pred: Callable[[WordGraph], None] + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims2.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). + +.. seealso:: :py:meth:`~Sims2.iterator`, :py:meth:`~Sims2.find_if` +)pbdoc"); + s2.def("find_if", + &Sims2::find_if, + py::arg("n"), + py::arg("pred"), + R"pbdoc( +Apply a unary predicate to two-sided congruences with at most a given number of +classes, until it returns ``True``. + +This function applies the predicate *pred* to every congruence with at most *n* +classes, until a congruence satisfying the predicate is found. This function +exists to: + +* provide some feedback on the progress of the computation if it runs for more + than 1 second. +* allow for searching for a congruence satisfying certain conditions using + :py:meth:`~Sims2.number_of_threads` in parallel. + +:param n: the maximum number of congruence classes. +:type n: int + +:param pred: the predicate applied to every congruence found. +:type pred: Callable[[WordGraph], bool] + +:returns: + The first :any:`WordGraph` for which *pred* returns ``True``, or the empty + word graph if no such word graph exists. +:rtype: WordGraph + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims2.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). + +.. seealso:: :py:meth:`~Sims2.iterator`, :py:meth:`~Sims2.for_each` +)pbdoc"); + s2.def( + "iterator", + [](Sims2 const& self, size_type n) { + return py::make_iterator(self.cbegin(n), self.cend(n)); + }, + py::arg("n"), + R"pbdoc( +Returns an iterator yielding all congruences of index at most *n*. + +This function returns an iterator yielding instances of :any:`WordGraph` that +represent the congruences with at most *n* classes. The order in which the +congruences are yielded by the iterator is implementation specific. The meaning +of the :any:`WordGraph` objects yielded by the iterator depends on whether the +input is a monoid presentation (i.e. +:py:meth:`~Presentation::contains_empty_word()` returns ``True`` ) or a +semigroup presentation. + +If the input is a monoid presentation for a monoid :math:`M` , then the +:any:`WordGraph` pointed to by an iterator of this type has at most *n* nodes, +and the right action of :math:`M` on the nodes of the word graph is isomorphic +to the action of :math:`M` on the classes of a right congruence. + +If the input is a semigroup presentation for a semigroup :math:`S`, then the +:any:`WordGraph` has at most *n* + 1 nodes, and the right action of :math:`S` +on the nodes :math:`\{1, \ldots, n\}` of the :any:`WordGraph` is isomorphic to +the action of :math:`S` on the classes of a right congruence. It'd probably be +better in this case if node :math:`0` was not included in the output +:any:`WordGraph`, but it is required in the implementation of the low-index +congruence algorithm, and to avoid unnecessary copies, we've left it in for the +time being. + +:param n: the maximum number of classes in a congruence. +:type n: int + +:returns: + An iterator ``it`` yielding :any:`WordGraph` objects with at most *n* or + *n* + 1 nodes depending on the presentation, see above. +:rtype: Iterator[WordGraph] + +:raises LibsemigroupsError: if *n* is ``0``. + +:raises LibsemigroupsError: + if :py:meth:`~Sims1.presentation()` has 0-generators and 0-relations (i.e. + it has not been initialised). +)pbdoc"); + + py::class_> ro(m, + "RepOrc", + R"pbdoc( +For computing small degree transformation representations of a finite semigroup +or monoid. + +This class is a helper for :any:`Sims1` calling the :any:`word_graph` member +function attempts to find a right congruence, represented as an +:any:`WordGraph` , of the semigroup or monoid defined by the presentation +consisting of its :py:meth:`~RepOrc.presentation` and +py:meth:`~RepOrc.long_rules` with the following properties: + +* the transformation semigroup defined by the :any:`WordGraph` has size + :py:meth:`~RepOrc.target_size` ; +* the number of nodes in the :any:`WordGraph` is at least + :py:meth:`~RepOrc.min_nodes` and at most :py:meth:`~RepOrc.max_nodes`. + +If no such :any:`WordGraph` can be found, then an empty :any:`WordGraph` is +returned (with ``0`` nodes and ``0`` edges). +)pbdoc"); + ro.def("__repr__", + [](RepOrc const& ro) { return to_human_readable_repr(ro); }); + ro.def(py::init<>(), R"pbdoc( +Default constructor. +)pbdoc"); + ro.def( + "init", + [](RepOrc& self) -> RepOrc& { return self.init(); }, + R"pbdoc( +Reinitialize an existing :any:`RepOrc` object. + +This function puts a :any:`RepOrc` object back into the same state as if it had +been newly default constructed. + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + ro.def( + "max_nodes", + [](RepOrc const& self) { return self.max_nodes(); }, + R"pbdoc( +Get the current maximum number of nodes. + +This function returns the current value for the maximum number of nodes in the +:any:`WordGraph` that we are seeking. + +:returns: A value of type ``int``. +:rtype: int + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ro.def( + "max_nodes", + [](RepOrc& self, size_t val) -> RepOrc& { return self.max_nodes(val); }, + py::arg("val"), + R"pbdoc( +Set the maximum number of nodes. + +This function sets the maximum number of nodes in the :any:`WordGraph` that we +are seeking. + +:param val: the maximum number of nodes +:type val: int + +:returns: A reference to *self*. +:rtype: RepOrc + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ro.def( + "min_nodes", + [](RepOrc const& self) { return self.min_nodes(); }, + R"pbdoc( +Get the current minimum number of nodes. + +This function returns the current value for the minimum number of nodes in the +:any:`WordGraph` that we are seeking. + +:returns: A value of type ``int``. +:rtype: int + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ro.def( + "min_nodes", + [](RepOrc& self, size_t val) -> RepOrc& { return self.min_nodes(val); }, + py::arg("val"), + R"pbdoc( +Set the minimum number of nodes. + +This function sets the minimal number of nodes in the :any:`WordGraph` that we +are seeking. + +:param val: the minimum number of nodes +:type val: int + +:returns: A reference to *self*. +:rtype: RepOrc + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ro.def( + "target_size", + [](RepOrc const& self) { return self.target_size(); }, + R"pbdoc( +Get the current target size. + +This function returns the current value for the target size, i.e. the desired +size of the transformation semigroup corresponding to the :any:`WordGraph` +returned by the function :py:meth:`~RepOrc.word_graph`. + +:returns: A value of type ``int``. +:rtype: int + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ro.def( + "target_size", + [](RepOrc& self, size_t val) -> RepOrc& { + return self.target_size(val); + }, + py::arg("val"), + R"pbdoc( +Set the target size. + +This function sets the target size, i.e. the desired size of the transformation +semigroup corresponding to the :any:`WordGraph` returned by the function +:py:meth:`~Sims1.word_graph`. + +:param val: the target size. +:type val: int + +:returns: A reference to *self*. +:rtype: RepOrc + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + ro.def("word_graph", + &RepOrc::word_graph, + R"pbdoc( +Get the word graph. + +This function attempts to find a right congruence, represented as an +:any:`WordGraph`, of the semigroup or monoid defined by the presentation +consisting of its :py:meth:`~RepOrc.presentation` and +:py:meth`~RepOrc.long_rules` with the following properties: + +* the transformation semigroup defined by the :any:`WordGraph` has size + :py:meth:`~RepOrc.target_size` ; +* the number of nodes in the :any:`WordGraph` is at least + :py:meth:`~RepOrc.min_nodes` and at most :py:meth:`~RepOrc.max_nodes`. + +If no such :any:`WordGraph` can be found, then an empty :any:`WordGraph` is +returned (with ``0`` nodes and ``0`` edges). + +:returns: A value of type :any:`WordGraph`. +:rtype: WordGraph + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + + py::class_> mro(m, + "MinimalRepOrc", + R"pbdoc( +For computing the minimal degree of a transformation representation arising +from a right congruences of a finite semigroup or monoid. + +This class is a helper for :any:`Sims1` , calling the :any:`word_graph` member +function attempts to find a right congruence, represented as an +:any:`WordGraph`, with the minimum possible number of nodes such that the +action of the semigroup or monoid defined by the presentation consisting of its +:py:meth:`~MinimalRepOrc.presentation` on the nodes of the :any:`WordGraph` +corresponds to a semigroup of size :py:meth:`~MinimalRepOrc.target_size`. + +If no such :py:meth:`~MinimalRepOrc.WordGraph` can be found, then an empty +:py:meth:`~MinimalRepOrc.WordGraph` is returned (with ``0`` nodes and ``0`` +edges). +)pbdoc"); + mro.def("__repr__", [](MinimalRepOrc const& mro) { + return to_human_readable_repr(mro); + }); + mro.def(py::init<>(), R"pbdoc( +Default constructor. +)pbdoc"); + mro.def( + "init", + [](MinimalRepOrc& self) -> MinimalRepOrc& { return self.init(); }, + R"pbdoc( +Reinitialize an existing :any:`MinimalRepOrc` object. + +This function puts a :any:`MinimalRepOrc` object back into the same state as if +it had been newly default constructed. + +:returns: A reference to *self*. + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + mro.def( + "target_size", + [](MinimalRepOrc const& self) { return self.target_size(); }, + R"pbdoc( +Get the current target size. + +This function returns the current value for the target size, i.e. the desired +size of the transformation semigroup corresponding to the :any:`WordGraph` +returned by the function :py:meth:`~MinimalRepOrc.word_graph`. + +:returns: A value of type ``int``. +:rtype: int + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + mro.def( + "target_size", + [](MinimalRepOrc& self, size_t val) -> MinimalRepOrc& { + return self.target_size(val); + }, + py::arg("val"), + R"pbdoc( +Set the target size. + +This function sets the target size, i.e. the desired size of the transformation +semigroup corresponding to the :any:`WordGraph` returned by the function +:py:meth:`~MinimalRepOrc.word_graph`. + +:param val: the target size. +:type val: int + +:returns: A reference to *self*. +:rtype: MinimalRepOrc + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + mro.def("word_graph", + &MinimalRepOrc::word_graph, + R"pbdoc( +Get the word graph. + +This function attempts to find a right congruence, represented as an +:any:`WordGraph`, with the minimum possible number of nodes such that the +action of the semigroup or monoid defined by the presentation consisting of its +:py:meth:`~MinimalRepOrc.presentation` and :py:meth:`~MinimalRepOrc.long_rules` +on the nodes of the :any:`WordGraph` corresponds to a semigroup of size +:py:meth:`~MinimalRepOrc.target_size`. If no such :any:`WordGraph` can be +found, then an empty :any:`WordGraph` is returned (with ``0`` nodes and ``0`` +edges). + +The algorithm implemented by this function repeatedly runs: + +.. code-block:: python3 + + RepOrc(self) + .min_nodes(1) + .max_nodes(best) + .target_size(self.target_size()) + .word_graph(); + +where ``best`` is initially :py:meth:`~MinimalRepOrc.target_size`, until the +returned :any:`WordGraph` is empty, and then the penultimate :any:`WordGraph` +is returned (if any). + +:returns: A value of type :any:`WordGraph`. +:rtype: WordGraph + +:exceptions: This function guarantees not to throw a :any:`LibsemigroupsError`. +)pbdoc"); + + py::class_ srf(m, + "SimsRefinerFaithful", + R"pbdoc( +For pruning the search tree when looking for congruences arising from right or +two-sided congruences representing faithful actions. + +This class provides a pruner for pruning the search tree when looking for right +congruences representing faithful actions. A right congruence represents a +faithful action if and only if it does not contain any non-trivial two-sided +congruence. Equivalently, a word graph of a right congruence represents a +faithful action if and only if there is no nontrivial pair of words :math:`(u, +v)` such that every vertex of the word graph is compatible with :math:`(u, v)`. + +.. seealso:: :py:meth:`~Sims1.pruners`, :py:meth:`~Sims1.add_pruner` +)pbdoc"); + srf.def("__repr__", [](SimsRefinerFaithful const& srf) { + return to_human_readable_repr(srf); + }); + srf.def(py::init<>(), R"pbdoc( +Default constructor. +)pbdoc"); + srf.def(py::init const&>(), + py::arg("forbid"), + R"pbdoc( +:sig=(self: SimsRefinerFaithful, forbid: List[Word]) -> None: +Construct from set of forbidden pairs. + +This function constructs a :any:`SimsRefinerFaithful` pruner with respect to the set of +forbidden relations in *forbid*. If *forbid* contains no trivial pairs (i.e. +pairs of words that are equal in the underlying semigroup or monoid), then all +word graphs rejected by :any:`SimsRefinerFaithful` are guaranteed to not be +extendable to a word graph representing a faithful congruence. Otherwise, the +pruner will incorrectly reject all word graphs. + +If in addition *forbid* is a set of relations containing all minimal congruence +generating pairs of a given semigroup or monoid, then +:any:`SimsRefinerFaithful` will also correctly determine if a complete word +graph represents a faithful congruence. Otherwise, the complete word graphs +accepted by :any:`SimsRefinerFaithful` are not guaranteed to be faithful and +must be checked by some other means. + +:param forbid: + A list of words such that ``(forbid[2*i], forbid[2*i+1])`` is the ``i``-th + forbidden pair. +:type forbid: List[Word] +)pbdoc"); + srf.def("forbid", + &SimsRefinerFaithful::forbid, + R"pbdoc( +:sig=(self: SimsRefinerFaithful) -> List[Word]: +Get the forbidden pairs defining the refiner. + +This function returns the defining forbidden pairs of a +:any:`SimsRefinerFaithful` instance. + +:returns: + A list of words ``result`` such that ``(result[2*i], result[2*i+1])`` is + the ``i``-th forbidden pair. +:rtype: List[Word] + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + srf.def( + "init", + [](SimsRefinerFaithful& self) -> SimsRefinerFaithful& { + return self.init(); + }, + R"pbdoc( +Reinitialize an existing SimsRefinerFaithful object. + +This function puts an object back into the same state as if it had been newly +default constructed. + +:returns: A reference to *self*. +:rtype: SimsRefinerFaithful +)pbdoc"); + srf.def( + "init", + [](SimsRefinerFaithful& self, std::vector const& forbid) + -> SimsRefinerFaithful& { return self.init(forbid); }, + py::arg("forbid"), + R"pbdoc( +:sig=(self: SimsRefinerFaithful, forbid: List[Word]) -> SimsRefinerFaithful: +Reinitialize an existing :any:`SimsRefinerFaithful` object from a set of +forbidden pairs. + +This function puts an object back into the same state as if it had been newly +constructed from set of forbidden pairs *forbid*. + +:param forbid: + A list of words such that ``(forbid[2*i], forbid[2*i+1])`` is the ``i``-th + forbidden pair. +:type forbid: List[Word] + +:returns: A reference to *self*. +:rtype: SimsRefinerFaithful +)pbdoc"); + srf.def( + "__call__", + [](SimsRefinerFaithful& d, word_graph_type const& wg) { return d(wg); }, + py::arg("wg"), + R"pbdoc( +Check if a word graph can be extended to one defining a faithful congruence. + +Returns ``False`` if there is no way of adding edges and nodes to +*wg* which will result in a word graph defining a faithful congruence. +Otherwise returns ``True``. + +:param wg: A word graph. +:type wg: WordGraph + +:returns: A boolean. +:rtype: bool +)pbdoc"); + + py::class_ sri(m, + "SimsRefinerIdeals", + R"pbdoc( +For pruning the search tree when looking for congruences arising from right or +two-sided ideals. + +This class provides a pruner for pruning the search tree when looking for right +or two-sided congruences arising from ideals (Rees congruences). + +.. seealso:: :py:meth:`~Sims1.pruners`, :py:meth:`~Sims1.add_pruner` +)pbdoc"); + sri.def("__repr__", [](SimsRefinerIdeals const& sri) { + return to_human_readable_repr(sri); + }); + sri.def(py::init<>(), R"pbdoc( +Default constructor. +)pbdoc"); + sri.def(py::init const&>(), R"pbdoc( +:sig=(self: SimsRefinerIdeals, p: Presentation) -> None: +:only-document-once: +Construct from presentation. + +This function constructs a :any:`SimsRefinerIdeals` pruner for the semigroup or +monoid defined by *p*. + +:param p: A presentation. +:type p: Presentation +)pbdoc"); + sri.def(py::init const&>(), R"pbdoc( +:sig=(self: SimsRefinerIdeals, p: Presentation) -> None: +:only-document-once: +Construct from presentation. + +This function constructs a :any:`SimsRefinerIdeals` pruner for the semigroup or +monoid defined by *p*. + +:param p: A presentation. +:type p: Presentation +)pbdoc"); + sri.def( + "init", + [](SimsRefinerIdeals& self) -> SimsRefinerIdeals { + return self.init(); + }, + R"pbdoc( +Reinitialize an existing :any:`SimsRefinerIdeals` object. + +This function puts an object back into the same state as if it had been newly +default constructed. + +:returns: A reference to *self*. +:rtype: SimsRefinerIdeals +)pbdoc"); + sri.def( + "init", + [](SimsRefinerIdeals& self, Presentation const& p) + -> SimsRefinerIdeals& { return self.init(p); }, + py::arg("p"), + R"pbdoc( +:sig=(self: SimsRefinerIdeals, p: Presentation) -> SimsRefinerIdeals: +:only-document-once: +Reinitialize an existing SimsRefinerIdeals object from a presentation. + +This function puts an object back into the same state as if it had been newly +constructed from the presentation *p*. + +:param p: A presentation. +:type p: Presentation + +:returns: A reference to *self*. +:rtype: SimsRefinerIdeals + +:raises LibsemigroupsError: if ``p`` is not valid +:raises LibsemigroupsError: if ``p`` has 0-generators and 0-relations. + +.. seealso:: :py:meth:`~Sims1.presentation` +)pbdoc"); + sri.def( + "init", + [](SimsRefinerIdeals& self, Presentation const& p) + -> SimsRefinerIdeals& { return self.init(p); }, + py::arg("p"), + R"pbdoc( +:sig=(self: SimsRefinerIdeals, p: Presentation) -> SimsRefinerIdeals: +:only-document-once: +Reinitialize an existing SimsRefinerIdeals object from a presentation. + +This function puts an object back into the same state as if it had been newly +constructed from the presentation *p*. + +:param p: A presentation. +:type p: Presentation + +:returns: A reference to *self*. +:rtype: SimsRefinerIdeals + +:raises LibsemigroupsError: if ``p`` is not valid +:raises LibsemigroupsError: if ``p`` has 0-generators and 0-relations. + +.. seealso:: :py:meth:`~Sims1.presentation` +)pbdoc"); + sri.def("presentation", + &SimsRefinerIdeals::presentation, + R"pbdoc( +Get the presentation over which the refiner is defined. + +This function returns the defining presentation of a :any:`SimsRefinerIdeals` +instance. + +:returns: A const reference to :any:`Presentation`. +:rtype: Presentation + +:exceptions: This function is ``noexcept`` and is guaranteed never to throw. +)pbdoc"); + sri.def( + "__call__", + [](SimsRefinerIdeals& d, word_graph_type const& wg) { return d(wg); }, + py::arg("wg"), + R"pbdoc( +Check if a word graph can be extended to one defining a Rees congruence. + +Returns ``False`` if there is no way of adding edges and nodes to *wg* which +will result in a word graph defining a Rees congruence. Otherwise returns +``True``. + +:param wg: A word graph. +:type wg: WordGraph + +:returns: A boolean. +:rtype: bool +)pbdoc"); + + m.def( + "right_generating_pairs", + [](Presentation const& p, word_graph_type const& wg) { + auto gen_pairs + = sims::right_generating_pairs(p, wg) + | rx::transform([](auto x) -> relation_type { return x; }); + return py::make_iterator(rx::begin(gen_pairs), rx::end(gen_pairs)); + }, + py::arg("p"), + py::arg("wg"), + R"pbdoc( +:sig=(p: Presentation, wg: WordGraph) -> Iterator[Tuple[Word, Word]]: +Compute the right congruence generating pairs of a word graph on +an f.p. semigroup or monoid. + +This function returns the right congruence generating pairs of the right +congruence defined by the word graph *wg* on the semigroup or monoid +defined by *p*. + +:param p: A presentation. +:type p: Presentation + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An iterator of generating pairs. +:rtype: Iterator[Tuple[Word, Word]] + +:raises LibsemigroupsError: + if the argument *wg* does not define a right congruence on the + semigroup or monoid defined by *p*. +)pbdoc"); + m.def( + "right_generating_pairs", + [](word_graph_type const& wg) { + auto gen_pairs + = sims::right_generating_pairs(wg) + | rx::transform([](auto x) -> relation_type { return x; }); + return py::make_iterator(rx::begin(gen_pairs), rx::end(gen_pairs)); + }, + py::arg("wg"), + R"pbdoc( +:sig=(wg: WordGraph) -> Iterator[Tuple[Word, Word]]: +Compute the right congruence generating pairs of a word graph on +the free monoid. + +This function returns the right congruence generating pairs of the right +congruence defined by the word graph *wg* on the free monoid. + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An iterator of generating pairs. +:rtype: Iterator[Tuple[Word, Word]] + +:raises LibsemigroupsError: + if the argument *wg* does not define a right congruence on the free monoid. +)pbdoc"); + m.def( + "two_sided_generating_pairs", + [](Presentation const& p, word_graph_type const& wg) { + auto gen_pairs + = sims::two_sided_generating_pairs(p, wg) + | rx::transform([](auto x) -> relation_type { return x; }); + return py::make_iterator(rx::begin(gen_pairs), rx::end(gen_pairs)); + }, + py::arg("p"), + py::arg("wg"), + R"pbdoc( +:sig=(p: Presentation, wg: WordGraph) -> Iterator[Tuple[Word, Word]]: +Compute the two-sided congruence generating pairs of a word graph on +an f.p. semigroup or monoid. + +This function returns the two-sided congruence generating pairs of the two-sided +congruence defined by the word graph *wg* on the semigroup or monoid +defined by *p*. + +:param p: A presentation. +:type p: Presentation + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An iterator of generating pairs. +:rtype: Iterator[Tuple[Word, Word]] + +:raises LibsemigroupsError: + if the argument *wg* does not define a two-sided congruence on the + semigroup or monoid defined by *p*. + +.. note:: + The generating pairs of a two-sided congruence :math:`\rho` as a two-sided + congruence differ from the generating pairs of :math:`\rho` as a right + congruence. This function returns the two-sided congruence generating + pairs, not the right congruence generating pairs of a two-sided congruence. +)pbdoc"); + m.def( + "two_sided_generating_pairs", + [](word_graph_type const& wg) { + auto gen_pairs + = sims::two_sided_generating_pairs(wg) + | rx::transform([](auto x) -> relation_type { return x; }); + return py::make_iterator(rx::begin(gen_pairs), rx::end(gen_pairs)); + }, + py::arg("wg"), + R"pbdoc( +:sig=(wg: WordGraph) -> Iterator[Tuple[Word, Word]]: +Compute the two-sided congruence generating pairs of a word graph on the free +monoid. + +This function returns the two-sided congruence generating pairs of the two-sided +congruence defined by the word graph *wg* on the free monoid. + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An iterator of generating pairs. +:rtype: Iterator[Tuple[Word, Word]] + +:raises LibsemigroupsError: + if the argument *wg* does not define a two-sided congruence on the free + monoid. + +.. note:: + The generating pairs of a two-sided congruence :math:`\rho` as a two-sided + congruence differ from the generating pairs of :math:`\rho` as a right + congruence. This function returns the two-sided congruence generating + pairs, not the right congruence generating pairs of a two-sided congruence. +)pbdoc"); + m.def( + "is_right_congruence", + [](Presentation const& p, word_graph_type const& wg) { + return sims::is_right_congruence(p, wg); + }, + py::arg("p"), + py::arg("wg"), + R"pbdoc( +Check if a word graph defines a right congruence on an f.p. semigroup or +monoid. + +Returns ``True`` if the word graph *wg* defines a right congruence on the +semigroup or monoid defined by *p* and ``False`` otherwise. + +:param p: A presentation. +:type p: Presentation + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An boolean. +:rtype: bool +)pbdoc"); + m.def( + "is_right_congruence_of_dual", + [](Presentation const& p, word_graph_type const& wg) { + return sims::is_right_congruence_of_dual(p, wg); + }, + py::arg("p"), + py::arg("wg"), + R"pbdoc( +Check if a word graph defines a right congruence on the dual of an f.p. +semigroup or monoid. + +Returns ``True`` if the word graph *wg* defines a right congruence on the dual +of the semigroup or monoid defined by *p* and ``False`` otherwise. This is +equivalent to checking if the word graph defines a left congruence in the +semigroup or monoid defined by *p*. + +:param p: A presentation. +:type p: Presentation + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An boolean. +:rtype: bool +)pbdoc"); + m.def( + "is_two_sided_congruence", + [](Presentation const& p, word_graph_type const& wg) { + return sims::is_two_sided_congruence(p, wg); + }, + py::arg("p"), + py::arg("wg"), + R"pbdoc( +Check if a word graph defines a two sided congruence on an f.p. semigroup or +monoid. + +Returns ``True`` if the word graph *wg* defines a two-sided congruence on the +semigroup or monoid defined by *p* and ``False`` otherwise. + +:param p: A presentation. +:type p: Presentation + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An boolean. +:rtype: bool +)pbdoc"); + m.def( + "is_maximal_right_congruence", + [](Presentation const& p, word_graph_type const& wg) { + return sims::is_maximal_right_congruence(p, wg); + }, + py::arg("p"), + py::arg("wg"), + R"pbdoc( +Check if a word graph defines a maximal right congruence on an f.p. semigroup +or monoid. + +Returns ``True`` if the word graph *wg* defines a maximal right congruence on +the semigroup or monoid defined by *p* and ``False`` otherwise. + +:param p: A presentation. +:type p: Presentation + +:param wg: A word graph. +:type wg: WordGraph + +:returns: An boolean. +:rtype: bool +)pbdoc"); + m.def( + "poset", + [](Sims1& sims, size_t n) { + return sims::poset(sims.cbegin(n), sims.cend(n)); + }, + py::arg("sims"), + py::arg("n"), + R"pbdoc( +Compute the inclusion poset of a collection of congruences returned by a +:any:`Sims1` object with at most *n* classes. + +This function returns a boolean matrix whose :math:`(i, j)`-th entry is 1 if +and only if the congruence defined by the :math:`i`-th word graph returned by +:any:`Sims1.iterator` with input *n* is a subrelation of the congruence +defined by the :math:`j`-th word graph. + +When *n* is large enough, so that :any:`Sims1` computes all right congruences +of a given semigroup or monoid, then this is equivalent to computing the right +congruence lattice of the semigroup or monoid. + +:param sims: A :any:`Sims1` object. +:type sims: Sims1 + +:param n: Maximum number of congruence classes. +:type n: int + +:returns: A boolean matrix defining the congruence poset. +:rtype: Matrix +)pbdoc"); + m.def( + "poset", + [](Sims2& sims, size_t n) { + return sims::poset(sims.cbegin(n), sims.cend(n)); + }, + py::arg("sims"), + py::arg("n"), + R"pbdoc( +Compute the inclusion poset of a collection of congruences returned by a +:any:`Sims2` object with at most *n* classes. + +This function returns a boolean matrix whose :math:`(i, j)`-th entry is 1 if +and only if the congruence defined by the :math:`i`-th word graph returned by +:any:`Sims2.iterator` with input *n* is a subrelation of the congruence +defined by the :math:`j`-th word graph. + +When *n* is large enough, so that :any:`Sims2` computes all two-sided +congruences of a given semigroup or monoid, then this is equivalent to +computing the two-sided congruence lattice of the semigroup or monoid. + +:param sims: A :any:`Sims2` object. +:type sims: Sims2 + +:param n: Maximum number of congruence classes. +:type n: int + +:returns: A boolean matrix defining the congruence poset. +:rtype: Matrix +)pbdoc"); + } // init_sims + +} // namespace libsemigroups diff --git a/tests/test_sims.py b/tests/test_sims.py new file mode 100644 index 00000000..3a14e63d --- /dev/null +++ b/tests/test_sims.py @@ -0,0 +1,462 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, R. Cirpons +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +""" +This module contains some tests for the libsemigroups_pybind11 functionality +arising from sims.*pp in libsemigroups. +""" + +# pylint: disable=no-name-in-module, missing-function-docstring, invalid-name + +import os +import pytest + +from libsemigroups_pybind11 import ( + Matrix, + MatrixKind, + MinimalRepOrc, + Presentation, + presentation, + ReportGuard, + sims, + Sims1, + Sims2, + SimsRefinerIdeals, + SimsRefinerFaithful, + word_graph, + WordGraph, + to_word_graph, + LibsemigroupsError, +) + + +def check_right_generating_pairs(s, wg): + # TODO(1): Implement correct function once ToddCoxeter is ported. + assert True + + +def check_meets_and_joins(it): + # TODO(1): Implement correct function once issue in libsemigroups is resolved. + assert True + + +@pytest.mark.quick +def test_sims1_000(): + """fp example 1""" + ReportGuard(False) + + p = Presentation([0, 1]) + p.contains_empty_word(True) + presentation.add_rule(p, [0, 0, 0], [0]) + presentation.add_rule(p, [1, 1], [1]) + presentation.add_rule(p, [0, 1, 0, 1], [0]) + + S = Sims1() + assert S.presentation(p).number_of_threads(2).number_of_congruences(5) == 6 + with pytest.raises(LibsemigroupsError): + S.number_of_congruences(0) + with pytest.raises(LibsemigroupsError): + S.find_if(0, lambda: False) + with pytest.raises(LibsemigroupsError): + S.for_each(0, lambda: False) + with pytest.raises(LibsemigroupsError): + S.iterator(0) + assert S.number_of_congruences(1) == 1 + + it = S.iterator(1) + assert next(it) == to_word_graph(1, [[0, 0]]) + + it = S.iterator(5) + assert next(it) == to_word_graph(5, [[0, 0]]) + assert next(it) == to_word_graph(5, [[1, 0], [1, 1]]) + assert next(it) == to_word_graph(5, [[1, 1], [1, 1]]) + assert next(it) == to_word_graph(5, [[1, 2], [1, 1], [1, 2]]) + assert next(it) == to_word_graph(5, [[1, 2], [1, 1], [2, 2]]) + assert next(it) == to_word_graph(5, [[1, 2], [1, 1], [3, 2], [3, 3]]) + with pytest.raises(StopIteration): + next(it) + with pytest.raises(StopIteration): + next(it) + + it = S.iterator(3) + assert next(it) == to_word_graph(3, [[0, 0]]) + S.number_of_threads(1).for_each( + 5, lambda wg: check_right_generating_pairs(S, wg) + ) + presentation.reverse(p) + S = Sims1() + assert S.presentation(p).number_of_congruences(5) == 9 + for wg in S.iterator(5): + assert word_graph.follow_path( + wg, 0, [1, 0, 1, 0] + ) == word_graph.follow_path(wg, 0, [0]) + S.for_each(5, lambda wg: check_right_generating_pairs(S, wg)) + mat = sims.poset(S, 5) + assert mat == Matrix( + MatrixKind.Boolean, + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 1, 0, 0, 0], + [0, 1, 1, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 1, 1, 0], + ], + ) + check_meets_and_joins(S.iterator(5)) + + +@pytest.mark.quick +def test_sims1_001(): + """fp example 2""" + ReportGuard(False) + p = Presentation([0, 1, 2]) + p.contains_empty_word(True) + + presentation.add_rule(p, [0, 1, 0], [0, 0]) + presentation.add_rule(p, [2, 2], [0, 0]) + presentation.add_rule(p, [0, 0, 0], [0, 0]) + presentation.add_rule(p, [2, 1], [1, 2]) + presentation.add_rule(p, [2, 0], [0, 0]) + presentation.add_rule(p, [1, 1], [1]) + presentation.add_rule(p, [0, 2], [0, 0]) + + S = Sims1() + S.presentation(p) + assert S.number_of_congruences(1) == 1 + assert S.number_of_congruences(2) == 3 + assert S.number_of_congruences(3) == 13 + assert S.number_of_congruences(4) == 36 + assert S.number_of_congruences(5) == 82 + assert S.number_of_congruences(6) == 135 + assert S.number_of_congruences(7) == 166 + assert S.number_of_congruences(8) == 175 + assert S.number_of_congruences(9) == 176 + assert S.number_of_congruences(10) == 176 + + it = S.iterator(2) + assert next(it) == to_word_graph(2, [[0, 0, 0]]) + assert next(it) == to_word_graph(2, [[1, 0, 1], [1, 1, 1]]) + assert next(it) == to_word_graph(2, [[1, 1, 1], [1, 1, 1]]) + with pytest.raises(StopIteration): + next(it) + with pytest.raises(StopIteration): + next(it) + + presentation.reverse(p) + S.init(p) + assert S.number_of_congruences(11) == 176 + + +@pytest.mark.quick +def test_sims1_002(): + """ToddCoxeter failing example (word_type)""" + ReportGuard(False) + p = Presentation([0, 1, 2, 3, 4, 5, 6]) + p.contains_empty_word(False) + + presentation.add_identity_rules(p, 6) + presentation.add_inverse_rules(p, [1, 0, 3, 2, 5, 4, 6], 6) + presentation.add_rule(p, [0, 0, 5, 0, 4], [6]) + presentation.add_rule(p, [0, 4, 2, 2, 1, 5, 2], [6]) + presentation.add_rule(p, [1, 3, 0, 2, 4, 4, 4], [6]) + S = Sims1() + S.presentation(p) + + S.for_each(3, lambda wg: check_right_generating_pairs(S, wg)) + + it = S.iterator(3) + wg = next(it) + assert list(sims.right_generating_pairs(wg)) == [ + ([1], [0]), + ([2], [0]), + ([3], [0]), + ([4], [0]), + ([5], [0]), + ([6], [0]), + ([0, 0], [0]), + ([0, 1], [0]), + ([0, 2], [0]), + ([0, 3], [0]), + ([0, 4], [0]), + ([0, 5], [0]), + ([0, 6], [0]), + ] + assert list(sims.right_generating_pairs(p, wg)) == [ + ([1], [0]), + ([2], [0]), + ([3], [0]), + ([4], [0]), + ([5], [0]), + ([6], [0]), + ] + + check_right_generating_pairs(S, wg) + + wg = next(it) + check_right_generating_pairs(S, wg) + assert list(sims.right_generating_pairs(wg)) == [ + ([1], [0]), + ([2], [0]), + ([3], [0]), + ([6], [0]), + ([0, 0], [0]), + ([0, 1], [0]), + ([0, 2], [0]), + ([0, 3], [0]), + ([0, 4], [4]), + ([0, 5], [5]), + ([0, 6], [0]), + ([4, 0], [4]), + ([4, 1], [4]), + ([4, 2], [4]), + ([4, 3], [4]), + ([4, 4], [5]), + ([4, 5], [0]), + ([4, 6], [4]), + ([5, 0], [5]), + ([5, 1], [5]), + ([5, 2], [5]), + ([5, 3], [5]), + ([5, 4], [0]), + ([5, 5], [4]), + ([5, 6], [5]), + ] + assert list(sims.right_generating_pairs(p, wg)) == [ + ([1], [0]), + ([2], [0]), + ([3], [0]), + ([6], [0]), + ([4, 0], [4]), + ([4, 2], [4]), + ([4, 4], [5]), + ([5, 0], [5]), + ] + + +@pytest.mark.quick +def test_sims1_003(): + """ToddCoxeter failing example (std::string)""" + ReportGuard(False) + p = Presentation("aAbBcCe") + p.contains_empty_word(False) + + presentation.add_identity_rules(p, "e") + + presentation.add_inverse_rules(p, "AaBbCce", "e") + presentation.add_rule(p, "aaCac", "e") + presentation.add_rule(p, "acbbACb", "e") + presentation.add_rule(p, "ABabccc", "e") + S = Sims1() + S.presentation(p) + assert S.number_of_congruences(3) == 14 + + +@pytest.mark.quick +def test_sims1_004(): + """partition_monoid(2) right""" + ReportGuard(False) + p = Presentation([0, 1, 2, 3]) + p.contains_empty_word(False) + + presentation.add_identity_rules(p, 0) + presentation.add_rule(p, [1, 1], [0]) + presentation.add_rule(p, [1, 3], [3]) + presentation.add_rule(p, [2, 2], [2]) + presentation.add_rule(p, [3, 1], [3]) + presentation.add_rule(p, [3, 3], [3]) + presentation.add_rule(p, [2, 3, 2], [2]) + presentation.add_rule(p, [3, 2, 3], [3]) + presentation.add_rule(p, [1, 2, 1, 2], [2, 1, 2]) + presentation.add_rule(p, [2, 1, 2, 1], [2, 1, 2]) + + S = Sims1(p) + assert S.number_of_congruences(2) == 4 + assert S.number_of_congruences(3) == 7 + assert S.number_of_congruences(4) == 14 + assert S.number_of_congruences(5) == 23 + assert S.number_of_congruences(6) == 36 + assert S.number_of_congruences(7) == 51 + assert S.number_of_congruences(8) == 62 + assert S.number_of_congruences(9) == 74 + assert S.number_of_congruences(10) == 86 + assert S.number_of_congruences(11) == 95 + assert S.number_of_congruences(12) == 100 + assert S.number_of_congruences(13) == 102 + assert S.number_of_congruences(14) == 104 + assert S.number_of_congruences(15) == 105 + assert S.number_of_congruences(16) == 105 + assert S.number_of_congruences(17) == 105 + + T = Sims2() + T.presentation(p) + T.number_of_threads(2) + assert T.number_of_congruences(16) == 13 + + orc = MinimalRepOrc() + d = ( + orc.presentation(p) + .target_size(15) + .number_of_threads(os.cpu_count()) + .word_graph() + ) + + assert d.number_of_nodes() == 7 + + +def test_sims1_901(): + ReportGuard(False) + p = Presentation(list(range(4))) + presentation.add_rule(p, [1, 2, 1], [1, 1]) + presentation.add_rule(p, [3, 3], [1, 1]) + presentation.add_rule(p, [1, 1, 1], [1, 1]) + presentation.add_rule(p, [3, 2], [2, 3]) + presentation.add_rule(p, [3, 1], [1, 1]) + presentation.add_rule(p, [2, 2], [2]) + presentation.add_rule(p, [1, 3], [1, 1]) + sims = Sims1(p) + assert sims.number_of_congruences(2) == 67 + assert sims.number_of_threads(2).number_of_congruences(2) == 67 + assert sims.number_of_threads(4).number_of_congruences(2) == 67 + assert sims.number_of_threads(8).number_of_congruences(2) == 67 + + +def test_sims1_902(): + ReportGuard(False) + p = Presentation("abcd") + presentation.add_rule(p, "bcb", "bb") + presentation.add_rule(p, "dd", "bb") + presentation.add_rule(p, "bbb", "bb") + presentation.add_rule(p, "dc", "cd") + presentation.add_rule(p, "db", "bb") + presentation.add_rule(p, "cc", "c") + presentation.add_rule(p, "bd", "bb") + sims = Sims1(p) + assert sims.number_of_congruences(2) == 67 + assert sims.number_of_threads(2).number_of_congruences(2) == 67 + assert sims.number_of_threads(4).number_of_congruences(2) == 67 + assert sims.number_of_threads(8).number_of_congruences(2) == 67 + + +def test_sims2_901(): + ReportGuard(False) + p = Presentation([0, 1]) + presentation.add_rule(p, [0, 1], [1, 0]) + + sims = Sims2(p) + assert sims.number_of_congruences(1) == 1 + assert sims.number_of_congruences(2) == 9 + assert sims.number_of_congruences(3) == 37 + assert sims.number_of_congruences(4) == 117 + assert sims.number_of_congruences(5) == 301 + assert sims.number_of_congruences(6) == 699 + assert sims.number_of_congruences(7) == 1484 + assert sims.number_of_congruences(8) == 2947 + assert sims.number_of_congruences(9) == 5607 + assert sims.number_of_congruences(10) == 10168 + assert sims.number_of_congruences(11) == 17942 + assert sims.number_of_congruences(12) == 30650 + assert sims.number_of_congruences(13) == 51292 + assert sims.number_of_threads(2).number_of_congruences(4) == 117 + assert sims.number_of_threads(4).number_of_congruences(4) == 117 + assert sims.number_of_threads(8).number_of_congruences(4) == 117 + + +def test_sims2_902(): + ReportGuard(False) + p = Presentation("ab") + presentation.add_rule(p, "ab", "ba") + + sims = Sims2(p) + assert sims.number_of_congruences(1) == 1 + assert sims.number_of_congruences(2) == 9 + assert sims.number_of_congruences(3) == 37 + assert sims.number_of_congruences(4) == 117 + assert sims.number_of_congruences(5) == 301 + assert sims.number_of_congruences(6) == 699 + assert sims.number_of_congruences(7) == 1484 + assert sims.number_of_congruences(8) == 2947 + assert sims.number_of_congruences(9) == 5607 + assert sims.number_of_congruences(10) == 10168 + assert sims.number_of_congruences(11) == 17942 + assert sims.number_of_congruences(12) == 30650 + assert sims.number_of_congruences(13) == 51292 + assert sims.number_of_threads(2).number_of_congruences(4) == 117 + assert sims.number_of_threads(4).number_of_congruences(4) == 117 + assert sims.number_of_threads(8).number_of_congruences(4) == 117 + + +def test_sims_refiner_ideals_901(): + p = Presentation([0, 1, 2]) + p.contains_empty_word(True) + presentation.add_rule(p, [0, 0], []) + presentation.add_rule(p, [0, 2], [2]) + presentation.add_rule(p, [1, 1], [1]) + presentation.add_rule(p, [2, 0], [2]) + presentation.add_rule(p, [2, 2], [2]) + presentation.add_rule(p, [1, 2, 1], [1]) + presentation.add_rule(p, [2, 1, 2], [2]) + presentation.add_rule(p, [0, 1, 0, 1], [1, 0, 1]) + presentation.add_rule(p, [1, 0, 1, 0], [1, 0, 1]) + + ip = SimsRefinerIdeals(p) + + s = Sims1(p) + s.add_pruner(ip) + assert s.number_of_congruences(15) == 15 + assert s.number_of_threads(2).number_of_congruences(15) == 15 + assert s.number_of_threads(4).number_of_congruences(15) == 15 + assert s.number_of_threads(8).number_of_congruences(15) == 15 + + +def test_sims_refiner_ideals_902(): + p = Presentation([0, 1]) + presentation.add_rule(p, [0, 0, 0], [1, 1]) + presentation.add_rule(p, [0, 0, 1], [1, 0]) + + sims = Sims2(p) + pruner = SimsRefinerIdeals(sims.presentation()) + sims.add_pruner(pruner) + assert sims.number_of_congruences(1) == 1 + assert sims.number_of_congruences(2) == 3 + assert sims.number_of_congruences(3) == 5 + assert sims.number_of_congruences(4) == 7 + assert sims.number_of_congruences(5) == 9 + assert sims.number_of_congruences(6) == 11 + assert sims.number_of_congruences(7) == 12 + for n in range(8, 20): + assert sims.number_of_congruences(n) == 12 + assert sims.number_of_threads(2).number_of_congruences(7) == 12 + assert sims.number_of_threads(4).number_of_congruences(7) == 12 + assert sims.number_of_threads(8).number_of_congruences(7) == 12 + + +# def test_sims_refiner_faithful_002(): +# p = Presentation([0, 1]) +# presentation.add_rule(p, [0, 0, 0], [1, 1]) +# presentation.add_rule(p, [0, 0, 1], [1, 0]) +# +# sims = Sims2(p) +# pruner = SimsRefinerFaithful(sims.presentation()) +# sims.add_pruner(pruner) +# assert sims.number_of_congruences(1) == 1 +# assert sims.number_of_congruences(2) == 3 +# assert sims.number_of_congruences(3) == 5 +# assert sims.number_of_congruences(4) == 7 +# assert sims.number_of_congruences(5) == 9 +# assert sims.number_of_congruences(6) == 11 +# assert sims.number_of_congruences(7) == 12 +# for n in range(8, 20): +# assert sims.number_of_congruences(n) == 12 +# # FIXME: Fix issue with deadlocks when using pruners +# # assert sims.number_of_threads(2).number_of_congruences(7) == 12 +# # assert sims.number_of_threads(4).number_of_congruences(7) == 12 +# # assert sims.number_of_threads(8).number_of_congruences(7) == 12