diff --git a/src/froidure-pin.cpp b/src/froidure-pin.cpp
index 0df0cf0b5..15fe87638 100644
--- a/src/froidure-pin.cpp
+++ b/src/froidure-pin.cpp
@@ -16,6 +16,8 @@
// along with this program. If not, see .
//
+#include
+
// libsemigroups headers
#include
#include
@@ -28,18 +30,371 @@
// pybind11....
#include
#include
+#include
// libsemigroups_pybind11....
+#include "kbe.hpp"
#include "main.hpp" // for init_froidure_pin
namespace libsemigroups {
namespace py = pybind11;
namespace {
+
+ // Functionality that doesn't depend on the Element type is bound by this
+ // function
+ template
+ void
+ bind_froidure_pin_core(py::module& m,
+ py::class_& thing) {
+ thing.def("__repr__", [](FroidurePin_ const& x) {
+ return to_human_readable_repr(x);
+ });
+
+ thing.def("__copy__",
+ [](FroidurePin_ const& self) { return FroidurePin_(self); });
+
+ thing.def(
+ "init",
+ [](FroidurePin_& self) -> FroidurePin_& { return self.init(); },
+ R"pbdoc(
+:sig=(self: FroidurePin) -> FroidurePin:
+
+Reinitialize a :any:`FroidurePin` object.
+
+This function re-initializes a :any:`FroidurePin` object so that it is in
+the same state as if it had just been default constructed.
+
+:returns: *self*.
+:rtype: FroidurePin
+)pbdoc");
+
+ thing.def(
+ "copy",
+ [](FroidurePin_ const& self) { return FroidurePin_(self); },
+ R"pbdoc(
+:sig=(self: FroidurePin) -> FroidurePin:
+
+Copy a :any:`FroidurePin` object.
+
+:returns: A copy.
+:rtype: FroidurePin
+)pbdoc");
+
+ thing.def(
+ "position_of_generator",
+ [](FroidurePinBase const& self, size_t i) {
+ return self.position_of_generator(i);
+ },
+ py::arg("i"),
+ R"pbdoc(
+:sig=(self: FroidurePin, i: int) -> int:
+
+Returns the position in of the generator with specified index.
+
+In many cases :any:`FroidurePin.current_position` called with argument *i* will
+return *i*, examples of when this will not be the case are:
+
+* there are duplicate generators;
+* :any:`FroidurePin.add_generators` was called after the semigroup was
+ already partially enumerated.
+
+:param i: the index of the generator.
+:type i: int
+
+:returns: The position of the generator with index *i*.
+:rtype: int
+
+:raises LibsemigroupsError:
+ if *i* is greater than or equal to :any:`FroidurePin.number_of_generators`.
+
+:complexity: Constant.
+)pbdoc");
+
+ thing.def("fast_product",
+ &FroidurePin_::fast_product,
+ py::arg("i"),
+ py::arg("j"),
+ R"pbdoc(
+:sig=(self: FroidurePin, i: int, j: int) -> int:
+
+Multiply elements via their indices.
+
+This function returns the position of the product of the element with
+index *i* and the element with index *j*.
+
+This function either:
+
+* follows the path in the right or left Cayley graph from *i* to *j*,
+ whichever is shorter using :any:`froidure_pin.product_by_reduction`; or
+
+* multiplies the elements in positions *i* and *j* together;
+
+whichever is better.
+
+For example, if the complexity of the multiplication is linear and *self* is
+a semigroup of transformations of degree 20, and the shortest paths in the left
+and right Cayley graphs from *i* to *j* are of length 100 and 1131, then it is
+better to just multiply the transformations together.
+
+:param i: the index of the first element to multiply.
+:type i: int
+
+:param j: the index of the second element to multiply.
+:type j: int
+
+:returns: The index of the product.
+:rtype: int
+
+:raises LibsemigroupsError:
+ if the values *i* and *j* are greater than or equal to
+ :any:`FroidurePin.current_size`.
+)pbdoc");
+
+ thing.def("is_finite",
+ &FroidurePin_::is_finite,
+ R"pbdoc(
+:sig=(self: FroidurePin) -> tril:
+
+Check finiteness.
+
+This function returns :any:`tril.true` if the semigroup represented by *self*
+is finite, :any:`tril.false` if it is infinite, and :any:`tril.unknown` if it
+is not known. For some types of elements, such as matrices over the integers,
+for example, it is undecidable, in general, if the semigroup generated by these
+elements is finite or infinite. On the other hand, for other types, such as
+transformation, the semigroup is always finite.
+
+:returns:
+ If the :any:`FroidurePin` object is finite, or not finite, or it isn't possible to
+ answer this question without performing a full enumeration.
+:rtype:
+ tril
+)pbdoc");
+
+ thing.def("is_idempotent",
+ &FroidurePin_::is_idempotent,
+ py::arg("i"),
+ R"pbdoc(
+:sig=(self: FroidurePin, i: int) -> bool:
+
+Check if an element is an idempotent via its index.
+
+This function returns ``True`` if the element in position *i* is an
+idempotent and ``False`` if it is not.
+
+:param i: the index of the element.
+:type i: int
+
+:returns: A value of type ``bool``.
+:rtype: bool
+
+:raises LibsemigroupsError:
+ if *i* is greater than or equal to the size of the :any:`FroidurePin`
+ instance.
+)pbdoc");
+
+ thing.def("number_of_generators",
+ &FroidurePin_::number_of_generators,
+ R"pbdoc(
+:sig=(self: FroidurePin) -> int:
+
+Returns the number of generators.
+
+This function returns the number of generators of a :any:`FroidurePin` instance.
+
+:returns:
+ The number of generators.
+:rtype:
+ int
+)pbdoc");
+
+ thing.def("number_of_idempotents",
+ &FroidurePin_::number_of_idempotents,
+ R"pbdoc(
+:sig=(self: FroidurePin) -> int:
+
+Returns the number of idempotents.
+
+This function returns the number of idempotents in the semigroup represented by
+a :any:`FroidurePin` instance. Calling this function triggers a full enumeration.
+
+:returns:
+ The number of idempotents.
+:rtype:
+ int
+)pbdoc");
+
+ thing.def("reserve",
+ &FroidurePin_::reserve,
+ py::arg("val"),
+ R"pbdoc(
+:sig=(self: FroidurePin, x: Element) -> FroidurePin:
+
+Requests the given capacity for elements.
+
+The parameter *val* is also used to initialise certain data members of a
+:any:`FroidurePin` instance. If you know a good upper bound for the size of
+your semigroup, then it might be a good idea to call this function with that
+upper bound as an argument; this can significantly improve the performance of
+the :any:`Runner.run` function, and consequently every other function too.
+
+:param val: the number of elements to reserve space for.
+:type val: int
+
+:returns: *self*.
+:rtype: FroidurePin
+)pbdoc");
+
+ thing.def("to_sorted_position",
+ &FroidurePin_::to_sorted_position,
+ py::arg("i"),
+ R"pbdoc(
+:sig=(self: FroidurePin, i: int) -> int | Undefined:
+
+Returns the sorted index of an element via its index.
+
+This function returns the position of the element with index *i* when the
+elements are sorted, or :any:`UNDEFINED` if *i* is greater than
+:any:`FroidurePin.size`.
+
+:param i: the index of the element.
+:type i: int
+
+:returns: The sorted position of the element with position *i*.
+:rtype: int | Undefined
+ )pbdoc");
+
+ ////////////////////////////////////////////////////////////////////////
+ // Helper functions
+ ////////////////////////////////////////////////////////////////////////
+
+ m.def(
+ "froidure_pin_current_position",
+ [](FroidurePinBase const& fpb, word_type const& w) {
+ return from_int(froidure_pin::current_position(fpb, w));
+ },
+ py::arg("fpb"),
+ py::arg("w"),
+ R"pbdoc(
+:sig=(self: FroidurePin, w: list[int]) -> int | Undefined:
+:only-document-once:
+
+Returns the position corresponding to a word.
+
+This function returns the position in *fp* corresponding to the the word
+*w* (in the generators). No enumeration is performed, and :any:`UNDEFINED`
+is returned if the position of the element corresponding to *w* cannot be
+determined.
+
+:param fp: the :any:`FroidurePin` instance.
+:type fp: FroidurePin
+
+:param w: a word in the generators.
+:type w: list[int]
+
+:returns: The current position of the element represented by a word.
+:rtype: int | Undefined
+
+:raises LibsemigroupsError:
+ if *w* contains a value that is not strictly less than
+ :any:`FroidurePin.number_of_generators`.
+
+:complexity: :math:`O(n)` where :math:`n` is the length of the word *w*.
+ )pbdoc");
+
+ m.def(
+ "froidure_pin_equal_to",
+ [](FroidurePin_& fp, word_type const& x, word_type const& y) {
+ return froidure_pin::equal_to(fp, x, y);
+ },
+ py::arg("fp"),
+ py::arg("x"),
+ py::arg("y"),
+ R"pbdoc(
+:sig=(fp: FroidurePin, x: list[int], y: list[int]) -> bool:
+:only-document-once:
+
+Check equality of words in the generators.
+
+This function returns ``True`` if the parameters *x* and *y* represent the same
+element of *fp* and ``False`` otherwise.
+
+:param fp: the :any:`FroidurePin` instance.
+:type fp: FroidurePin
+
+:param x: the first word for comparison.
+:type x: list[int]
+
+:param y: the second word for comparison.
+:type y: list[int]
+
+:returns: Whether or not the words *x* and *y* represent the same element.
+:rtype: bool
+
+:raises LibsemigroupsError:
+ if *x* or *y* contains any value that is not strictly less than
+ :any:`FroidurePin.number_of_generators`.
+
+.. note::
+ No enumeration of *fp* is triggered by calls to this function.)pbdoc");
+
+ // Documented in the Element overload.
+ m.def(
+ "froidure_pin_factorisation",
+ [](FroidurePinBase& fpb, FroidurePinBase::element_index_type pos) {
+ return froidure_pin::factorisation(fpb, pos);
+ },
+ py::arg("fpb"),
+ py::arg("pos"));
+
+ // Documented in the Element overload.
+ m.def(
+ "froidure_pin_minimal_factorisation",
+ [](FroidurePin_& fp, size_t i) {
+ return froidure_pin::minimal_factorisation(fp, i);
+ },
+ py::arg("fp"),
+ py::arg("i"));
+
+ m.def(
+ "froidure_pin_position",
+ [](FroidurePin_& fp, word_type const& w) {
+ return froidure_pin::position(fp, w);
+ },
+ py::arg("fp"),
+ py::arg("w"),
+ R"pbdoc(
+:sig=(fp: FroidurePin, w: list[int]) -> int:
+:only-document-once:
+
+Returns the position corresponding to a word.
+
+This function returns the position in *fp* corresponding to the word *w*. A
+full enumeration is triggered by calls to this function.
+
+:param fp: the :any:`FroidurePin` instance.
+:type fp: FroidurePin
+
+:param w: a word in the generators.
+:type w: list[int]
+
+:returns: The position of the element represented by a word.
+:rtype: int
+
+:raises LibsemigroupsError:
+ if *w* contains any values that are not strictly less than
+ :any:`FroidurePin.number_of_generators`.
+
+:complexity: :math:`O(n)` where :math:`n` is the length of the word *w*.)pbdoc");
+ } // bind_froidure_pin_core
+
template
- void bind_froidure_pin(py::module& m, std::string const& name) {
+ void bind_froidure_pin_stateless(py::module& m, std::string const& name) {
using FroidurePin_ = FroidurePin;
+ static_assert(std::is_void_v);
+
std::string pyclass_name = std::string("FroidurePin") + name;
py::class_ thing(
m,
@@ -47,9 +402,9 @@ namespace libsemigroups {
// To change the top-level signature of a class, :sig=...: should be
// specified here in the class docstring. This is most likely the
// desired behaiour if a constructor is not overloaded.
- // If the constructor is overloaded and the signature of an individual
- // overload is to be changed, :sig=...: should be specified in the
- // docstring of that py::init function.
+ // If the constructor is overloaded and the signature of an
+ // individual overload is to be changed, :sig=...: should be
+ // specified in the docstring of that py::init function.
R"pbdoc(
:sig=(self: FroidurePin, gens: list[Element]) -> None:
@@ -62,27 +417,27 @@ matrices.
In the following documentation the type of the elements of the semigroup
represented by a :any:`FroidurePin` instance is denoted by ``Element``.
-The class :any:`FroidurePin` implements the Froidure-Pin algorithm as
-described in the article :cite:`Froidure1997aa`. A :any:`FroidurePin`
-instance is defined by a generating set, and the main function is :any:`Runner.run`,
-which implements the Froidure-Pin Algorithm. If :any:`Runner.run` is invoked and
+The class :any:`FroidurePin` implements the Froidure-Pin algorithm as described
+in the article :cite:`Froidure1997aa`. A :any:`FroidurePin` instance is defined
+by a generating set, and the main function is :any:`Runner.run`, which
+implements the Froidure-Pin Algorithm. If :any:`Runner.run` is invoked and
:any:`Runner.finished` returns ``True``, then the size :any:`FroidurePin.size`,
the left and right Cayley graphs :any:`FroidurePin.left_cayley_graph` and
:any:`FroidurePin.right_cayley_graph` are determined, and a confluent
-terminating presentation :any:`froidure_pin.rules` for the semigroup is
-known.
+terminating presentation :any:`froidure_pin.rules` for the semigroup is known.
.. seealso:: :any:`Runner`.
)pbdoc");
+
+ bind_froidure_pin_core(m, thing);
+
// thing.attr("Element") = py::class_(m);
- thing.def("__repr__", [](FroidurePin_ const& x) {
- return to_human_readable_repr(x);
- });
thing.def("__getitem__", &FroidurePin_::at, py::is_operator());
thing.def("__iter__", [](FroidurePin_& self) {
self.run();
return py::make_iterator(self.begin(), self.end());
});
+
thing.def(
"current_elements",
[](FroidurePin_ const& self) {
@@ -119,27 +474,13 @@ in the list *gens*.
:type gens: list[Element]
:raises LibsemigroupsError: if the generators do not all have the same degree.
-)pbdoc");
-
- thing.def("__copy__",
- [](FroidurePin_ const& self) { return FroidurePin_(self); });
-
- thing.def(
- "copy",
- [](FroidurePin_ const& self) { return FroidurePin_(self); },
- R"pbdoc(
-:sig=(self: FroidurePin) -> FroidurePin:
-
-Copy a :any:`FroidurePin` object.
-
-:returns: A copy.
-:rtype: FroidurePin
)pbdoc");
// This function should really throw a ValueError if the degree of x is
- // incompatible with the existing degree, but this doesn't get detected at
- // the Python level, so a LibsemigroupsError is thrown instead. It would
- // be possible to intercept this, but it probably isn't worth the effort.
+ // incompatible with the existing degree, but this doesn't get detected
+ // at the Python level, so a LibsemigroupsError is thrown instead. It
+ // would be possible to intercept this, but it probably isn't worth the
+ // effort.
thing.def("add_generator",
&FroidurePin_::add_generator,
@@ -263,36 +604,6 @@ the next idempotent.
Iterator[Element]
)pbdoc");
- thing.def(
- "position_of_generator",
- [](FroidurePinBase const& self, size_t i) {
- return self.position_of_generator(i);
- },
- py::arg("i"),
- R"pbdoc(
-:sig=(self: FroidurePin, i: int) -> int:
-
-Returns the position in of the generator with specified index.
-
-In many cases :any:`FroidurePin.current_position` called with argument *i* will
-return *i*, examples of when this will not be the case are:
-
-* there are duplicate generators;
-* :any:`FroidurePin.add_generators` was called after the semigroup was
- already partially enumerated.
-
-:param i: the index of the generator.
-:type i: int
-
-:returns: The position of the generator with index *i*.
-:rtype: int
-
-:raises LibsemigroupsError:
- if *i* is greater than or equal to :any:`FroidurePin.number_of_generators`.
-
-:complexity: Constant.
-)pbdoc");
-
thing.def(
"sorted_elements",
[](FroidurePin_& self) {
@@ -414,64 +725,24 @@ Copy and add non-redundant generators.
This function is equivalent to copying a :any:`FroidurePin`
instance and then calling :any:`closure` on the copy. But this function
-avoids copying the parts of the initial :any:`FroidurePin` instance that are
-immediately discarded by :any:`closure`.
-
-:param gens: the list of generators.
-:type gens: list[Element]
-
-:returns:
- A new :any:`FroidurePin` instance by value generated by the generators of
- *self* and the non-redundant generators in *gens*.
-:rtype:
- FroidurePin
-
-:raises LibsemigroupsError:
- if any of the elements in *gens* do not have degree compatible with any
- existing elements of the :any:`FroidurePin` instance.
-
-:raises LibsemigroupsError:
- if the elements in *gens* do not all have the same degree.
-)pbdoc");
-
- thing.def("fast_product",
- &FroidurePin_::fast_product,
- py::arg("i"),
- py::arg("j"),
- R"pbdoc(
-:sig=(self: FroidurePin, i: int, j: int) -> int:
-
-Multiply elements via their indices.
-
-This function returns the position of the product of the element with
-index *i* and the element with index *j*.
-
-This function either:
-
-* follows the path in the right or left Cayley graph from *i* to *j*,
- whichever is shorter using :any:`froidure_pin.product_by_reduction`; or
-
-* multiplies the elements in positions *i* and *j* together;
-
-whichever is better.
-
-For example, if the complexity of the multiplication is linear and *self* is
-a semigroup of transformations of degree 20, and the shortest paths in the left
-and right Cayley graphs from *i* to *j* are of length 100 and 1131, then it is
-better to just multiply the transformations together.
+avoids copying the parts of the initial :any:`FroidurePin` instance that are
+immediately discarded by :any:`closure`.
-:param i: the index of the first element to multiply.
-:type i: int
+:param gens: the list of generators.
+:type gens: list[Element]
-:param j: the index of the second element to multiply.
-:type j: int
+:returns:
+ A new :any:`FroidurePin` instance by value generated by the generators of
+ *self* and the non-redundant generators in *gens*.
+:rtype:
+ FroidurePin
-:returns: The index of the product.
-:rtype: int
+:raises LibsemigroupsError:
+ if any of the elements in *gens* do not have degree compatible with any
+ existing elements of the :any:`FroidurePin` instance.
:raises LibsemigroupsError:
- if the values *i* and *j* are greater than or equal to
- :any:`FroidurePin.current_size`.
+ if the elements in *gens* do not all have the same degree.
)pbdoc");
thing.def(
@@ -501,21 +772,6 @@ is that in which the generators were added at construction, or via
if *i* is greater than or equal to :any:`number_of_generators()`.
)pbdoc");
- thing.def(
- "init",
- [](FroidurePin_& self) -> FroidurePin_& { return self.init(); },
- R"pbdoc(
-:sig=(self: FroidurePin) -> FroidurePin:
-
-Reinitialize a :any:`FroidurePin` object.
-
-This function re-initializes a :any:`FroidurePin` object so that it is in
-the same state as if it had just been default constructed.
-
-:returns: *self*.
-:rtype: FroidurePin
-)pbdoc");
-
thing.def(
"init",
[](FroidurePin_& self,
@@ -543,80 +799,6 @@ in the same state as if it had just been constructed from *gens*.
if the elements in *gens* do not all have the same degree.
)pbdoc");
- thing.def("is_finite",
- &FroidurePin_::is_finite,
- R"pbdoc(
-:sig=(self: FroidurePin) -> tril:
-
-Check finiteness.
-
-This function returns :any:`tril.true` if the semigroup represented by *self*
-is finite, :any:`tril.false` if it is infinite, and :any:`tril.unknown` if it
-is not known. For some types of elements, such as matrices over the integers,
-for example, it is undecidable, in general, if the semigroup generated by these
-elements is finite or infinite. On the other hand, for other types, such as
-transformation, the semigroup is always finite.
-
-:returns:
- If the :any:`FroidurePin` object is finite, or not finite, or it isn't possible to
- answer this question without performing a full enumeration.
-:rtype:
- tril
-)pbdoc");
-
- thing.def("is_idempotent",
- &FroidurePin_::is_idempotent,
- py::arg("i"),
- R"pbdoc(
-:sig=(self: FroidurePin, i: int) -> bool:
-
-Check if an element is an idempotent via its index.
-
-This function returns ``True`` if the element in position *i* is an
-idempotent and ``False`` if it is not.
-
-:param i: the index of the element.
-:type i: int
-
-:returns: A value of type ``bool``.
-:rtype: bool
-
-:raises LibsemigroupsError:
- if *i* is greater than or equal to the size of the :any:`FroidurePin`
- instance.
-)pbdoc");
-
- thing.def("number_of_generators",
- &FroidurePin_::number_of_generators,
- R"pbdoc(
-:sig=(self: FroidurePin) -> int:
-
-Returns the number of generators.
-
-This function returns the number of generators of a :any:`FroidurePin` instance.
-
-:returns:
- The number of generators.
-:rtype:
- int
-)pbdoc");
-
- thing.def("number_of_idempotents",
- &FroidurePin_::number_of_idempotents,
- R"pbdoc(
-:sig=(self: FroidurePin) -> int:
-
-Returns the number of idempotents.
-
-This function returns the number of idempotents in the semigroup represented by
-a :any:`FroidurePin` instance. Calling this function triggers a full enumeration.
-
-:returns:
- The number of idempotents.
-:rtype:
- int
-)pbdoc");
-
thing.def(
"position",
[](FroidurePin_& self, Element const& x) { return self.position(x); },
@@ -636,27 +818,6 @@ This function the position of *x* in a :any:`FroidurePin` instance, or
:rtype: int | Undefined
.. seealso:: :any:`current_position` and :any:`sorted_position`.
-)pbdoc");
-
- thing.def("reserve",
- &FroidurePin_::reserve,
- py::arg("val"),
- R"pbdoc(
-:sig=(self: FroidurePin, x: Element) -> FroidurePin:
-
-Requests the given capacity for elements.
-
-The parameter *val* is also used to initialise certain data members of a
-:any:`FroidurePin` instance. If you know a good upper bound for the size of
-your semigroup, then it might be a good idea to call this function with that
-upper bound as an argument; this can significantly improve the performance of
-the :any:`Runner.run` function, and consequently every other function too.
-
-:param val: the number of elements to reserve space for.
-:type val: int
-
-:returns: *self*.
-:rtype: FroidurePin
)pbdoc");
thing.def(
@@ -706,111 +867,18 @@ if *x* is not an element.
.. seealso:: :any:`current_position` and :any:`position`.
)pbdoc");
- thing.def("to_sorted_position",
- &FroidurePin_::to_sorted_position,
- py::arg("i"),
- R"pbdoc(
-:sig=(self: FroidurePin, i: int) -> int | Undefined:
-
-Returns the sorted index of an element via its index.
-
-This function returns the position of the element with index *i* when the
-elements are sorted, or :any:`UNDEFINED` if *i* is greater than
-:any:`FroidurePin.size`.
-
-:param i: the index of the element.
-:type i: int
-
-:returns: The sorted position of the element with position *i*.
-:rtype: int | Undefined
- )pbdoc");
-
////////////////////////////////////////////////////////////////////////
// Helper functions
////////////////////////////////////////////////////////////////////////
- {
- py::options options;
- options.disable_function_signatures();
-
- m.def(
- "froidure_pin_current_position",
- [](FroidurePinBase const& fpb, word_type const& w) {
- return from_int(froidure_pin::current_position(fpb, w));
- },
- py::arg("fpb"),
- py::arg("w"),
- R"pbdoc(
-:sig=(self: FroidurePin, w: list[int]) -> int | Undefined:
-:only-document-once:
-
-Returns the position corresponding to a word.
-
-This function returns the position in *fp* corresponding to the the word
-*w* (in the generators). No enumeration is performed, and :any:`UNDEFINED`
-is returned if the position of the element corresponding to *w* cannot be
-determined.
-
-:param fp: the :any:`FroidurePin` instance.
-:type fp: FroidurePin
-
-:param w: a word in the generators.
-:type w: list[int]
-
-:returns: The current position of the element represented by a word.
-:rtype: int | Undefined
-
-:raises LibsemigroupsError:
- if *w* contains a value that is not strictly less than
- :any:`FroidurePin.number_of_generators`.
-
-:complexity: :math:`O(n)` where :math:`n` is the length of the word *w*.
- )pbdoc");
-
- m.def(
- "froidure_pin_equal_to",
- [](FroidurePin_& fp, word_type const& x, word_type const& y) {
- return froidure_pin::equal_to(fp, x, y);
- },
- py::arg("fp"),
- py::arg("x"),
- py::arg("y"),
- R"pbdoc(
-:sig=(fp: FroidurePin, x: list[int], y: list[int]) -> bool:
-:only-document-once:
-
-Check equality of words in the generators.
-
-This function returns ``True`` if the parameters *x* and *y* represent the same
-element of *fp* and ``False`` otherwise.
-
-:param fp: the :any:`FroidurePin` instance.
-:type fp: FroidurePin
-
-:param x: the first word for comparison.
-:type x: list[int]
-
-:param y: the second word for comparison.
-:type y: list[int]
-
-:returns: Whether or not the words *x* and *y* represent the same element.
-:rtype: bool
-
-:raises LibsemigroupsError:
- if *x* or *y* contains any value that is not strictly less than
- :any:`FroidurePin.number_of_generators`.
-
-.. note::
- No enumeration of *fp* is triggered by calls to this function.)pbdoc");
-
- m.def(
- "froidure_pin_factorisation",
- [](FroidurePin_& fp, Element const& x) {
- return froidure_pin::factorisation(fp, x);
- },
- py::arg("fp"),
- py::arg("x"),
- R"pbdoc(
+ m.def(
+ "froidure_pin_factorisation",
+ [](FroidurePin_& fp, Element const& x) {
+ return froidure_pin::factorisation(fp, x);
+ },
+ py::arg("fp"),
+ py::arg("x"),
+ R"pbdoc(
:sig=(fp: FroidurePin, x: Element | int) -> list[int]:
:only-document-once:
@@ -837,23 +905,14 @@ is that the resulting factorisation may not be minimal.
if *x* is an :any:`int` and *x* is greater than or equal to :any:`FroidurePin.size`.
)pbdoc");
- // Documented above.
- m.def(
- "froidure_pin_factorisation",
- [](FroidurePinBase& fpb, FroidurePinBase::element_index_type pos) {
- return froidure_pin::factorisation(fpb, pos);
- },
- py::arg("fpb"),
- py::arg("pos"));
-
- m.def(
- "froidure_pin_minimal_factorisation",
- [](FroidurePin_& fp, Element const& x) {
- return froidure_pin::minimal_factorisation(fp, x);
- },
- py::arg("fp"),
- py::arg("x"),
- R"pbdoc(
+ m.def(
+ "froidure_pin_minimal_factorisation",
+ [](FroidurePin_& fp, Element const& x) {
+ return froidure_pin::minimal_factorisation(fp, x);
+ },
+ py::arg("fp"),
+ py::arg("x"),
+ R"pbdoc(
:sig=(fp: FroidurePin, x: Element | int) -> list[int]:
:only-document-once:
@@ -879,55 +938,15 @@ that evaluates to *x*.
if *x* is an :any:`int` and *x* is greater than or equal to :any:`FroidurePin.size`.
)pbdoc");
- // Documented above.
- m.def(
- "froidure_pin_minimal_factorisation",
- [](FroidurePin_& fp, size_t i) {
- return froidure_pin::minimal_factorisation(fp, i);
- },
- py::arg("fp"),
- py::arg("i"));
-
- m.def(
- "froidure_pin_position",
- [](FroidurePin_& fp, word_type const& w) {
- return froidure_pin::position(fp, w);
- },
- py::arg("fp"),
- py::arg("w"),
- R"pbdoc(
-:sig=(fp: FroidurePin, w: list[int]) -> int:
-:only-document-once:
-
-Returns the position corresponding to a word.
-
-This function returns the position in *fp* corresponding to the word *w*. A
-full enumeration is triggered by calls to this function.
-
-:param fp: the :any:`FroidurePin` instance.
-:type fp: FroidurePin
-
-:param w: a word in the generators.
-:type w: list[int]
-
-:returns: The position of the element represented by a word.
-:rtype: int
-
-:raises LibsemigroupsError:
- if *w* contains any values that are not strictly less than
- :any:`FroidurePin.number_of_generators`.
-
-:complexity: :math:`O(n)` where :math:`n` is the length of the word *w*.)pbdoc");
-
- m.def(
- "froidure_pin_to_element",
- [](FroidurePin_& fp, word_type const& w) -> Element const& {
- return froidure_pin::to_element(fp, w);
- },
- py::return_value_policy::reference_internal,
- py::arg("fp"),
- py::arg("w").noconvert(),
- R"pbdoc(
+ m.def(
+ "froidure_pin_to_element",
+ [](FroidurePin_& fp, word_type const& w) -> Element const& {
+ return froidure_pin::to_element(fp, w);
+ },
+ py::return_value_policy::reference_internal,
+ py::arg("fp"),
+ py::arg("w").noconvert(),
+ R"pbdoc(
:sig=(fp: FroidurePin, w: list[int]) -> Element:
:only-document-once:
@@ -952,46 +971,330 @@ This function returns the element of *fp* obtained by evaluating *w*.
.. note::
No enumeration of *fp* is triggered by calls to this function.)pbdoc");
+ } // bind_froidure_pin_stateless
+
+ template
+ auto from_element(FroidurePin_ const& fp,
+ typename FroidurePin_::const_reference x) {
+ return ElementStateful(x, fp.state().get());
+ }
+
+ template ::value>>
+ auto from_element(FroidurePin_ const& fp, Range r) {
+ auto rr
+ = r | rx::transform([&fp](typename FroidurePin_::const_reference x) {
+ return from_element(fp, x);
+ });
+ return py::make_iterator(rx::begin(rr), rx::end(rr));
+ }
+
+ template
+ auto from_element(FroidurePin_ const& fp, Iterator first, Iterator last) {
+ return from_element(fp, rx::iterator_range(first, last));
+ }
+
+ template
+ typename FroidurePin_::const_reference
+ to_element(ElementStateful const& x) {
+ return x.element;
+ }
+
+ template
+ auto to_element(std::vector> const& vec) {
+ std::vector copy;
+ for (auto const& x : vec) {
+ copy.push_back(x.element);
+ }
+ return copy;
+ }
+
+ template
+ || std::is_same_v>>
+ typename FroidurePin_::element_type to_element(FroidurePin_ const& fp,
+ Word const& x) {
+ return typename FroidurePin_::element_type(*fp.state().get(), x);
+ }
+
+ template >>
+ std::vector
+ to_element(FroidurePin_ const& fp, std::vector const& vec) {
+ std::vector copy;
+ for (auto const& x : vec) {
+ copy.push_back(to_element(fp, x));
+ }
+ return copy;
+ }
+
+ template
+ void bind_froidure_pin_stateful(py::module& m, std::string const& name) {
+ using FroidurePin_ = FroidurePin;
+
+ static_assert(!std::is_void_v);
+
+ std::string pyclass_name = std::string("FroidurePin") + name;
+ py::class_ thing(m, pyclass_name.c_str());
+
+ bind_froidure_pin_core(m, thing);
+
+ if constexpr (!std::is_same_v) {
+ // TODO implement for TCE also
+ using Word = typename Element::native_word_type;
+ thing.def(py::init(
+ [](std::vector> const& gens) {
+ using state_type = typename FroidurePin_::state_type;
+ auto real_gens = to_element(gens);
+ FroidurePin_ result(
+ std::make_shared(*gens[0].state_ptr));
+ result.add_generators(real_gens.begin(), real_gens.end());
+ return result;
+ }));
+
+ thing.def(
+ "__getitem__",
+ [](FroidurePin_& self, size_t i) {
+ return from_element(self, self.at(i));
+ },
+ py::is_operator());
+
+ thing.def("__iter__", [](FroidurePin_& self) {
+ self.run();
+ return from_element(self, self.begin(), self.end());
+ });
+
+ thing.def("add_generator",
+ [](FroidurePin_& self,
+ ElementStateful const& x) -> FroidurePin_& {
+ return self.add_generator(to_element(x));
+ });
+
+ thing.def("add_generator",
+ [](FroidurePin_& self, Word const& x) -> FroidurePin_& {
+ return self.add_generator(to_element(self, x));
+ });
+
+ thing.def("add_generators",
+ [](FroidurePin_& self,
+ std::vector> const& gens)
+ -> FroidurePin_& {
+ froidure_pin::add_generators(self, to_element(gens));
+ return self;
+ });
+
+ thing.def("add_generators",
+ [](FroidurePin_& self,
+ std::vector const& gens) -> FroidurePin_& {
+ froidure_pin::add_generators(self, to_element(self, gens));
+ return self;
+ });
+
+ thing.def("closure",
+ [](FroidurePin_& self,
+ std::vector> const& gens)
+ -> FroidurePin_& {
+ froidure_pin::closure(self, to_element(gens));
+ return self;
+ });
+
+ thing.def("closure",
+ [](FroidurePin_& self,
+ std::vector const& gens) -> FroidurePin_& {
+ froidure_pin::closure(self, to_element(self, gens));
+ return self;
+ });
+
+ thing.def(
+ "contains",
+ [](FroidurePin_& self, ElementStateful const& x) {
+ return self.contains(to_element(x));
+ });
+
+ thing.def("contains", [](FroidurePin_& self, Word const& x) {
+ return self.contains(to_element(self, x));
+ });
+
+ thing.def("copy_add_generators",
+ [](FroidurePin_& self,
+ std::vector> const& gens)
+ -> FroidurePin_ {
+ return froidure_pin::copy_add_generators(self,
+ to_element(gens));
+ });
+
+ thing.def("copy_add_generators",
+ [](FroidurePin_& self,
+ std::vector const& gens) -> FroidurePin_ {
+ return froidure_pin::copy_add_generators(
+ self, to_element(self, gens));
+ });
+
+ thing.def("copy_closure",
+ [](FroidurePin_& self,
+ std::vector> const& gens)
+ -> FroidurePin_ {
+ return froidure_pin::copy_closure(self, to_element(gens));
+ });
+
+ thing.def("copy_closure",
+ [](FroidurePin_& self,
+ std::vector const& gens) -> FroidurePin_ {
+ return froidure_pin::copy_closure(self,
+ to_element(self, gens));
+ });
+
+ thing.def("current_elements", [](FroidurePin_ const& self) {
+ return from_element(self, self.begin(), self.end());
+ });
+
+ thing.def("current_position",
+ [](FroidurePin_ const& self,
+ ElementStateful const& x) {
+ return from_int(self.current_position(to_element(x)));
+ });
+
+ thing.def("current_position", [](FroidurePin_& self, Word const& x) {
+ return from_int(self.current_position(to_element(self, x)));
+ });
+
+ thing.def("generator", [](FroidurePin_ const& self, size_t i) {
+ return from_element(self, self.generator(i));
+ });
+
+ thing.def("idempotents", [](FroidurePin_& self) {
+ return from_element(
+ self, self.cbegin_idempotents(), self.cend_idempotents());
+ });
+
+ thing.def("init",
+ [](FroidurePin_& self,
+ std::vector> const& gens)
+ -> FroidurePin_& {
+ return froidure_pin::init(self, to_element(gens));
+ });
+
+ thing.def("init",
+ [](FroidurePin_& self,
+ std::vector const& gens) -> FroidurePin_& {
+ return froidure_pin::init(self, to_element(self, gens));
+ });
+
+ thing.def(
+ "position",
+ [](FroidurePin_& self, ElementStateful const& x) {
+ return self.position(to_element(x));
+ });
+
+ thing.def("position", [](FroidurePin_& self, Word const& x) {
+ return self.position(to_element(self, x));
+ });
+
+ thing.def("sorted_at", [](FroidurePin_& self, size_t i) {
+ return from_element(self, self.sorted_at(i));
+ });
+
+ thing.def(
+ "sorted_position",
+ [](FroidurePin_& self, ElementStateful const& x) {
+ return self.sorted_position(to_element(x));
+ });
+
+ thing.def("sorted_position", [](FroidurePin_& self, Word const& x) {
+ return self.sorted_position(to_element(self, x));
+ });
+
+ thing.def("sorted_elements", [](FroidurePin_& self) {
+ return from_element(self, self.cbegin_sorted(), self.cend_sorted());
+ });
+
+ ////////////////////////////////////////////////////////////////////////
+ // Helpers
+ ////////////////////////////////////////////////////////////////////////
+
+ m.def("froidure_pin_factorisation",
+ [](FroidurePin_& fp, ElementStateful const& x) {
+ return froidure_pin::factorisation(fp, to_element(x));
+ });
+
+ m.def("froidure_pin_factorisation",
+ [](FroidurePin_& fp, Word const& x) {
+ return froidure_pin::factorisation(fp, to_element(fp, x));
+ });
+
+ m.def("froidure_pin_minimal_factorisation",
+ [](FroidurePin_& fp, ElementStateful const& x) {
+ return froidure_pin::minimal_factorisation(fp, to_element(x));
+ });
+
+ m.def("froidure_pin_minimal_factorisation",
+ [](FroidurePin_& fp, Word const& x) {
+ return froidure_pin::minimal_factorisation(fp,
+ to_element(fp, x));
+ });
+
+ m.def("froidure_pin_to_element",
+ [](FroidurePin_& fp, word_type const& w) {
+ return from_element(fp, froidure_pin::to_element(fp, w));
+ });
}
- } // bind_froidure_pin
+ } // bind_froidure_pin_stateful
} // namespace
void init_froidure_pin(py::module& m) {
// TODO(0) uncomment bind_froidure_pin>(m, "Transf16");
- bind_froidure_pin>(m, "Transf1");
- bind_froidure_pin>(m, "Transf2");
- bind_froidure_pin>(m, "Transf4");
- // TODO(0) uncomment bind_froidure_pin>(m, "PPerm16");
- bind_froidure_pin>(m, "PPerm1");
- bind_froidure_pin>(m, "PPerm2");
- bind_froidure_pin>(m, "PPerm4");
- // TODO(0) uncomment bind_froidure_pin>(m, "Perm16");
- bind_froidure_pin>(m, "Perm1");
- bind_froidure_pin>(m, "Perm2");
- bind_froidure_pin>(m, "Perm4");
- bind_froidure_pin<
- detail::KBE>>(
- m, "KBERewriteFromLeft");
- bind_froidure_pin<
- detail::KBE>>(
- m, "KBERewriteTrie");
- bind_froidure_pin(m, "TCE");
- bind_froidure_pin>(m, "KEString");
- bind_froidure_pin>(m,
- "KEMultiStringView");
- bind_froidure_pin>(
+ bind_froidure_pin_stateless>(m, "Transf1");
+ bind_froidure_pin_stateless>(m, "Transf2");
+ bind_froidure_pin_stateless>(m, "Transf4");
+ // TODO(0) uncomment bind_froidure_pin_stateless>(m,
+ // "PPerm16");
+ bind_froidure_pin_stateless>(m, "PPerm1");
+ bind_froidure_pin_stateless>(m, "PPerm2");
+ bind_froidure_pin_stateless>(m, "PPerm4");
+ // TODO(0) uncomment bind_froidure_pin_stateless>(m,
+ // "Perm16");
+ bind_froidure_pin_stateless>(m, "Perm1");
+ bind_froidure_pin_stateless>(m, "Perm2");
+ bind_froidure_pin_stateless>(m, "Perm4");
+
+ bind_froidure_pin_stateless(m, "Bipartition");
+ bind_froidure_pin_stateless(m, "PBR");
+
+ bind_froidure_pin_stateless(m, "BMat8");
+ bind_froidure_pin_stateless>(m, "BMat");
+ bind_froidure_pin_stateless>(m, "IntMat");
+ bind_froidure_pin_stateless>(m, "MaxPlusMat");
+ bind_froidure_pin_stateless>(m, "MinPlusMat");
+ bind_froidure_pin_stateless>(
+ m, "ProjMaxPlusMat");
+ bind_froidure_pin_stateless>(
+ m, "MaxPlusTruncMat");
+ bind_froidure_pin_stateless>(
+ m, "MinPlusTruncMat");
+ bind_froidure_pin_stateless>(m, "NTPMat");
+
+ bind_froidure_pin_stateful<
+ detail::KBE>>(
+ m, "KBEStringRewriteFromLeft");
+ bind_froidure_pin_stateful<
+ detail::KBE>>(
+ m, "KBEStringRewriteTrie");
+ bind_froidure_pin_stateful<
+ detail::KBE>>(
+ m, "KBEWordRewriteFromLeft");
+ bind_froidure_pin_stateful<
+ detail::KBE>>(
+ m, "KBEWordRewriteTrie");
+
+ bind_froidure_pin_stateful>(m, "KEString");
+ bind_froidure_pin_stateful>(
+ m, "KEMultiStringView");
+ bind_froidure_pin_stateful>(
m, "KEWord"); // codespell:ignore keword
- bind_froidure_pin(m, "Bipartition");
- bind_froidure_pin(m, "PBR");
-
- bind_froidure_pin(m, "BMat8");
- bind_froidure_pin>(m, "BMat");
- bind_froidure_pin>(m, "IntMat");
- bind_froidure_pin>(m, "MaxPlusMat");
- bind_froidure_pin>(m, "MinPlusMat");
- bind_froidure_pin>(m, "ProjMaxPlusMat");
- bind_froidure_pin>(m, "MaxPlusTruncMat");
- bind_froidure_pin>(m, "MinPlusTruncMat");
- bind_froidure_pin>(m, "NTPMat");
+
+ bind_froidure_pin_stateful(m, "TCE");
}
} // namespace libsemigroups
diff --git a/src/kbe.cpp b/src/kbe.cpp
new file mode 100644
index 000000000..4e4d02c86
--- /dev/null
+++ b/src/kbe.cpp
@@ -0,0 +1,90 @@
+//
+// libsemigroups_pybind11
+// Copyright (C) 2025 James D. Mitchell
+//
+// 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 .
+//
+
+// libsemigroups headers
+#include
+#include
+
+#include
+
+// pybind11....
+#include
+#include
+#include
+
+// libsemigroups_pybind11....
+#include "kbe.hpp" // for ElementStateful
+#include "main.hpp" // for init_kbe
+
+namespace libsemigroups {
+ namespace py = pybind11;
+
+ template
+ void bind_kbe(py::module& m, std::string_view thing_name) {
+ using Result = ElementStateful>;
+ using Word = typename FroidurePin::element_type::native_word_type;
+
+ py::class_ thing(m, thing_name.data());
+
+ thing.def("__repr__", [](Result const& self) {
+ return fmt::format("\"{}\"", self.element.word());
+ });
+ thing.def("__str__",
+ [](Result const& self) { return self.element.word(); });
+
+ thing.def("word", [](Result const& self) { return self.element.word(); });
+
+ thing.def("__mul__", [](Result const& self, Result const& other) {
+ Result result;
+ result.state_ptr = self.state_ptr;
+ Product()(
+ result.element, self.element, other.element, result.state_ptr, 0);
+ return result;
+ });
+
+ thing.def("__eq__", [](Result const& self, Result const& other) {
+ return self.element == other.element && self.state_ptr == other.state_ptr;
+ });
+
+ thing.def("__eq__", [](Result const& self, Word const& other) {
+ return self.element.word() == other;
+ });
+ thing.def("__eq__", [](Word const& other, Result const& self) {
+ return self.element.word() == other;
+ });
+
+ // This class does not have very many methods, maybe that's a good thing,
+ // better to convert to a "word" and use that instead.
+ }
+
+ void init_kbe(py::module& m) {
+ using KBEStringTrie
+ = detail::KBE>;
+ using KBEWordTrie
+ = detail::KBE>;
+ using KBEStringFromLeft
+ = detail::KBE>;
+ using KBEWordFromLeft
+ = detail::KBE>;
+
+ bind_kbe(m, "KBEStringTrie");
+ bind_kbe(m, "KBEWordTrie");
+ bind_kbe(m, "KBEStringFromLeft");
+ bind_kbe(m, "KBEWordFromLeft");
+ }
+} // namespace libsemigroups
diff --git a/src/kbe.hpp b/src/kbe.hpp
new file mode 100644
index 000000000..c830f3b5c
--- /dev/null
+++ b/src/kbe.hpp
@@ -0,0 +1,42 @@
+//
+// libsemigroups_pybind11
+// Copyright (C) 2025 James D. Mitchell
+//
+// 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 .
+//
+
+#ifndef SRC_KBE_HPP_
+#define SRC_KBE_HPP_
+
+namespace libsemigroups {
+ template
+ struct ElementStateful {
+ using element_type = typename FroidurePinType::element_type;
+ using state_type = typename FroidurePinType::state_type;
+
+ ElementStateful() = default;
+ ElementStateful(ElementStateful const&) = default;
+ ElementStateful(ElementStateful&&) = default;
+ ElementStateful& operator=(ElementStateful const&) = default;
+ ElementStateful& operator=(ElementStateful&&) = default;
+
+ ElementStateful(element_type const& lmnt, state_type* sttptr)
+ : element(lmnt), state_ptr(sttptr) {}
+
+ element_type element;
+ state_type* state_ptr;
+ };
+
+} // namespace libsemigroups
+#endif // SRC_KBE_HPP_
diff --git a/src/libsemigroups_pybind11/froidure_pin.py b/src/libsemigroups_pybind11/froidure_pin.py
index 97d26862c..5ad2eac7f 100644
--- a/src/libsemigroups_pybind11/froidure_pin.py
+++ b/src/libsemigroups_pybind11/froidure_pin.py
@@ -39,13 +39,19 @@
FroidurePinTransf1 as _FroidurePinTransf1,
FroidurePinTransf2 as _FroidurePinTransf2,
FroidurePinTransf4 as _FroidurePinTransf4,
- FroidurePinKBERewriteFromLeft as _FroidurePinKBERewriteFromLeft,
- FroidurePinKBERewriteTrie as _FroidurePinKBERewriteTrie,
+ FroidurePinKBEStringRewriteFromLeft as _FroidurePinKBEStringRewriteFromLeft,
+ FroidurePinKBEStringRewriteTrie as _FroidurePinKBEStringRewriteTrie,
+ FroidurePinKBEWordRewriteFromLeft as _FroidurePinKBEWordRewriteFromLeft,
+ FroidurePinKBEWordRewriteTrie as _FroidurePinKBEWordRewriteTrie,
FroidurePinKEMultiStringView as _FroidurePinKEMultiStringView,
FroidurePinKEString as _FroidurePinKEString,
FroidurePinKEWord as _FroidurePinKEWord,
FroidurePinTCE as _FroidurePinTCE,
IntMat as _IntMat,
+ KBEStringTrie as _KBEStringTrie,
+ KBEStringFromLeft as _KBEStringFromLeft,
+ KBEWordTrie as _KBEWordTrie,
+ KBEWordFromLeft as _KBEWordFromLeft,
MaxPlusMat as _MaxPlusMat,
MaxPlusTruncMat as _MaxPlusTruncMat,
MinPlusMat as _MinPlusMat,
@@ -120,6 +126,10 @@ class FroidurePin(_CxxWrapper): # pylint: disable=missing-class-docstring
(_Transf1,): _FroidurePinTransf1,
(_Transf2,): _FroidurePinTransf2,
(_Transf4,): _FroidurePinTransf4,
+ (_KBEStringTrie,): _FroidurePinKBEStringRewriteTrie,
+ (_KBEStringFromLeft,): _FroidurePinKBEStringRewriteFromLeft,
+ (_KBEWordTrie,): _FroidurePinKBEWordRewriteTrie,
+ (_KBEWordFromLeft,): _FroidurePinKBEWordRewriteFromLeft,
}
_cxx_type_to_py_template_params = dict(
@@ -130,8 +140,6 @@ class FroidurePin(_CxxWrapper): # pylint: disable=missing-class-docstring
)
_all_wrapped_cxx_types = {*_py_template_params_to_cxx_type.values()} | {
- _FroidurePinKBERewriteFromLeft,
- _FroidurePinKBERewriteTrie,
_FroidurePinKEMultiStringView,
_FroidurePinKEString,
_FroidurePinKEWord,
@@ -227,8 +235,6 @@ def sorted_elements( # pylint: disable=missing-function-docstring
_register_cxx_wrapped_type(_fp_type, FroidurePin)
-_register_cxx_wrapped_type(_FroidurePinKBERewriteFromLeft, FroidurePin)
-_register_cxx_wrapped_type(_FroidurePinKBERewriteTrie, FroidurePin)
_register_cxx_wrapped_type(_FroidurePinKEMultiStringView, FroidurePin)
_register_cxx_wrapped_type(_FroidurePinKEString, FroidurePin)
_register_cxx_wrapped_type(_FroidurePinKEWord, FroidurePin)
diff --git a/src/main.cpp b/src/main.cpp
index d8bf75244..088549eb6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -85,6 +85,7 @@ namespace libsemigroups {
init_gabow(m);
init_imagerightaction(m);
init_kambites(m);
+ init_kbe(m);
init_knuth_bendix(m);
init_konieczny(m);
init_matrix(m);
diff --git a/src/main.hpp b/src/main.hpp
index c452a4dee..ec49df8e5 100644
--- a/src/main.hpp
+++ b/src/main.hpp
@@ -52,6 +52,7 @@ namespace libsemigroups {
void init_imagerightaction(py::module&);
void init_inverse_present(py::module&);
void init_kambites(py::module&);
+ void init_kbe(py::module&);
void init_knuth_bendix(py::module&);
void init_konieczny(py::module&);
void init_matrix(py::module&);
diff --git a/tests/test_froidure_pin.py b/tests/test_froidure_pin.py
index 85085e0df..1feed66aa 100644
--- a/tests/test_froidure_pin.py
+++ b/tests/test_froidure_pin.py
@@ -16,19 +16,24 @@
import pytest
from libsemigroups_pybind11 import (
- FroidurePin,
- ReportGuard,
- PBR,
+ BMat8,
Bipartition,
+ FroidurePin,
+ KnuthBendix,
+ LibsemigroupsError,
Matrix,
MatrixKind,
- Transf,
+ PBR,
PPerm,
Perm,
- BMat8,
- froidure_pin,
- LibsemigroupsError,
+ Presentation,
+ ReportGuard,
+ Transf,
UNDEFINED,
+ congruence_kind,
+ froidure_pin,
+ presentation,
+ to,
)
from .runner import check_runner
@@ -581,8 +586,6 @@ def test_froidure_pin_method_wrap():
S.add_generators([Perm([1, 0, 2])])
assert S.degree() == 3
- # TODO more
-
def test_froidure_pin_return_undefined_1():
S = FroidurePin(Perm([1, 0, 2, 3, 4, 5, 6]))
@@ -623,29 +626,264 @@ def test_froidure_pin_return_policy():
)
-# def test_froidure_pin_tce(checks_for_froidure_pin):
-# ReportGuard(False)
-# tc = ToddCoxeter(congruence_kind.twosided)
-# tc.set_number_of_generators(2)
-# tc.add_pair([0, 0, 0, 0], [0])
-# tc.add_pair([1, 1, 1, 1], [1])
-# tc.add_pair([0, 1], [1, 0])
-#
-# assert tc.number_of_classes() == 15
-#
-# for check in checks_for_froidure_pin:
-# check(FroidurePin(tc.quotient_froidure_pin()))
-#
-#
-# def test_froidure_pin_kbe(checks_for_froidure_pin):
-# ReportGuard(False)
-# kb = KnuthBendix()
-# kb.set_alphabet(2)
-# kb.add_rule([0, 0, 0, 0], [0])
-# kb.add_rule([1, 1, 1, 1], [1])
-# kb.add_rule([0, 1], [1, 0])
-#
-# assert kb.size() == 15
-#
-# for check in checks_for_froidure_pin:
-# check(FroidurePin(kb.froidure_pin()))
+def test_froidure_pin_kbe_string(): # pylint: disable=too-many-statements
+ p = Presentation("ab")
+ presentation.add_rule(p, "aaaaaa", "aaa")
+ presentation.add_rule(p, "bbbbbbbb", "bb")
+ presentation.add_rule(p, "ab", "ba")
+ kb = KnuthBendix(congruence_kind.twosided, p)
+ S = to(kb, Return=(FroidurePin,))
+
+ assert list(S.current_elements()) == ["a", "b"]
+ assert S.size() == kb.number_of_classes()
+
+ assert S.generator(0) == "a"
+ assert S.generator(1) == "b"
+
+ assert S[42] == "aaaabbbbbb"
+
+ assert list(S) == [
+ "a",
+ "b",
+ "aa",
+ "ab",
+ "bb",
+ "aaa",
+ "aab",
+ "abb",
+ "bbb",
+ "aaaa",
+ "aaab",
+ "aabb",
+ "abbb",
+ "bbbb",
+ "aaaaa",
+ "aaaab",
+ "aaabb",
+ "aabbb",
+ "abbbb",
+ "bbbbb",
+ "aaaaab",
+ "aaaabb",
+ "aaabbb",
+ "aabbbb",
+ "abbbbb",
+ "bbbbbb",
+ "aaaaabb",
+ "aaaabbb",
+ "aaabbbb",
+ "aabbbbb",
+ "abbbbbb",
+ "bbbbbbb",
+ "aaaaabbb",
+ "aaaabbbb",
+ "aaabbbbb",
+ "aabbbbbb",
+ "abbbbbbb",
+ "aaaaabbbb",
+ "aaaabbbbb",
+ "aaabbbbbb",
+ "aabbbbbbb",
+ "aaaaabbbbb",
+ "aaaabbbbbb",
+ "aaabbbbbbb",
+ "aaaaabbbbbb",
+ "aaaabbbbbbb",
+ "aaaaabbbbbbb",
+ ]
+
+ for i, x in enumerate(S.current_elements()):
+ assert S.current_position(x) == i
+
+ S.add_generator(S.generator(0) * S.generator(1))
+
+ assert S.number_of_generators() == 3
+ assert S.generator(2) == "ab"
+ S.add_generator("a" * 5 + "b" * 3)
+ assert S.number_of_generators() == 4
+ assert S.generator(3) == "aaaaabbb"
+
+ S.add_generators([S.generator(0), S.generator(1)])
+ assert S.number_of_generators() == 6
+ assert S.current_position("aababababababba") == 46
+ assert S.current_position("aa") == 2
+
+ S.add_generators(["a" * 5, "b" * 3])
+ assert S.number_of_generators() == 8
+ assert S.generator(6) == "aaaaa"
+ assert S.generator(7) == "bbb"
+
+ assert list(S.idempotents()) == ["aaa", "bbbbbb", "aaabbbbbb"]
+
+ assert all(
+ a == b for a, b in zip(S.sorted_elements(), S.current_elements())
+ )
+
+ S.closure([S.generator(0)])
+ assert S.number_of_generators() == 8
+
+ S.closure(["a"])
+ assert S.number_of_generators() == 8
+
+ assert S.contains(S.generator(0))
+ with pytest.raises(LibsemigroupsError):
+ assert not S.contains("cd")
+ assert S.contains("a")
+
+ T = S.copy_add_generators([S.generator(0)])
+ assert T is not S
+ assert T.number_of_generators() == 9
+
+ T = S.copy_add_generators(["a"])
+ assert T is not S
+ assert T.number_of_generators() == 9
+
+ assert T.init([S.generator(0), S.generator(1)]) is T
+ assert T.number_of_generators() == 2
+
+ assert T.init(["a", "b"]) is T
+ assert T.number_of_generators() == 2
+ assert T.size() == 47
+
+ S = to(kb, Return=(FroidurePin,))
+ assert S.sorted_position(S.generator(0)) == 0
+ assert S.sorted_position(S.generator(1)) == 1
+ assert S.sorted_position("a") == 0
+ assert S.sorted_position("b") == 1
+
+ for i, x in enumerate(S):
+ assert S.sorted_position(x) == i
+ assert S.sorted_at(i) == x
+
+ assert froidure_pin.factorisation(S, S.generator(0) * S.generator(0)) == [
+ 0,
+ 0,
+ ]
+ assert froidure_pin.factorisation(S, "aa") == [0, 0]
+
+ assert froidure_pin.minimal_factorisation(
+ S, S.generator(0) * S.generator(0)
+ ) == [0, 0]
+ assert froidure_pin.minimal_factorisation(S, "aa") == [0, 0]
+
+ assert froidure_pin.to_element(S, [0, 0]) == "aa"
+
+
+def test_froidure_pin_kbe_word(): # pylint: disable=too-many-statements
+ p = Presentation([0, 1])
+ presentation.add_rule(p, [0] * 6, [0] * 3)
+ presentation.add_rule(p, [1] * 8, [1] * 2)
+ presentation.add_rule(p, [0, 1], [1, 0])
+ kb = KnuthBendix(congruence_kind.twosided, p)
+ S = to(kb, Return=(FroidurePin,))
+
+ assert list(S.current_elements()) == [[0], [1]]
+
+ assert S.size() == kb.number_of_classes()
+
+ assert S.generator(0) == [0]
+ assert S.generator(1) == [1]
+
+ assert S[42] == [0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
+
+ assert list(S)[:10] == [
+ [0],
+ [1],
+ [0, 0],
+ [0, 1],
+ [1, 1],
+ [0, 0, 0],
+ [0, 0, 1],
+ [0, 1, 1],
+ [1, 1, 1],
+ [0, 0, 0, 0],
+ ]
+
+ for i, x in enumerate(S.current_elements()):
+ assert S.current_position(x) == i
+
+ S.add_generator(S.generator(0) * S.generator(1))
+
+ assert S.number_of_generators() == 3
+ assert S.generator(2) == [0, 1]
+ S.add_generator([0] * 5 + [1] * 3)
+
+ assert S.number_of_generators() == 4
+ assert S.generator(3) == [0, 0, 0, 0, 0, 1, 1, 1]
+
+ S.add_generators([S.generator(0), S.generator(1)])
+ assert S.number_of_generators() == 6
+ assert S.current_position([0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0]) == 34
+ assert S.current_position([0, 0]) == 2
+
+ S.add_generators([[0], [1]])
+ assert S.number_of_generators() == 8
+
+ assert list(S.idempotents()) == [
+ [0, 0, 0],
+ [0, 0, 0, 1, 1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1],
+ ]
+
+ assert all(
+ a == b for a, b in zip(S.sorted_elements(), S.current_elements())
+ )
+
+ S.closure([S.generator(0)])
+ assert S.number_of_generators() == 8
+ S.closure([[0], [1], [0, 1, 0, 1]])
+ assert S.number_of_generators() == 8
+
+ assert S.contains(S.generator(0))
+ with pytest.raises(LibsemigroupsError):
+ assert not S.contains([2, 3])
+ assert S.contains([0, 1])
+
+ T = S.copy_add_generators([S.generator(0)])
+ assert T is not S
+ assert T.number_of_generators() == 9
+
+ T = S.copy_add_generators([[1]])
+ assert T is not S
+ assert T.number_of_generators() == 9
+
+ T = S.copy_closure([S.generator(0)])
+ assert T is not S
+ assert T.number_of_generators() == 8
+
+ T = S.copy_closure([[1]])
+ assert T is not S
+ assert T.number_of_generators() == 8
+
+ assert T.init([S.generator(0), S.generator(1)]) is T
+ assert T.number_of_generators() == 2
+
+ assert T.init([[0], [1]]) is T
+ assert T.number_of_generators() == 2
+ assert T.size() == 47
+
+ S = to(kb, Return=(FroidurePin,))
+ assert S.sorted_position(S.generator(0)) == 0
+ assert S.sorted_position(S.generator(1)) == 1
+
+ assert S.sorted_position([0]) == 0
+ assert S.sorted_position([1]) == 1
+
+ for i, x in enumerate(S):
+ assert S.sorted_position(x) == i
+ assert S.sorted_at(i) == x
+
+ assert froidure_pin.factorisation(S, S.generator(0) * S.generator(0)) == [
+ 0,
+ 0,
+ ]
+
+ assert froidure_pin.factorisation(S, [0, 0]) == [0, 0]
+
+ assert froidure_pin.minimal_factorisation(
+ S, S.generator(0) * S.generator(0)
+ ) == [0, 0]
+
+ assert froidure_pin.minimal_factorisation(S, [0] * 2) == [0, 0]
+
+ assert froidure_pin.to_element(S, [0, 0]) == [0, 0]
diff --git a/tests/test_to.py b/tests/test_to.py
index 8d331ee12..8dd07f252 100644
--- a/tests/test_to.py
+++ b/tests/test_to.py
@@ -13,8 +13,10 @@
import pytest
from _libsemigroups_pybind11 import (
- FroidurePinKBERewriteFromLeft,
- FroidurePinKBERewriteTrie,
+ FroidurePinKBEStringRewriteFromLeft,
+ FroidurePinKBEStringRewriteTrie,
+ FroidurePinKBEWordRewriteFromLeft,
+ FroidurePinKBEWordRewriteTrie,
FroidurePinKEMultiStringView,
FroidurePinKEString,
FroidurePinKEWord,
@@ -186,24 +188,24 @@ def test_to_FroidurePin_000():
fp = check_cong_to_froidure_pin(
KnuthBendix, str, Rewriter="RewriteFromLeft"
)
- assert isinstance(to_cxx(fp), FroidurePinKBERewriteFromLeft)
+ assert isinstance(to_cxx(fp), FroidurePinKBEStringRewriteFromLeft)
def test_to_FroidurePin_001():
fp = check_cong_to_froidure_pin(KnuthBendix, str, Rewriter="RewriteTrie")
- assert isinstance(to_cxx(fp), FroidurePinKBERewriteTrie)
+ assert isinstance(to_cxx(fp), FroidurePinKBEStringRewriteTrie)
def test_to_FroidurePin_002():
fp = check_cong_to_froidure_pin(
KnuthBendix, int, Rewriter="RewriteFromLeft"
)
- assert isinstance(to_cxx(fp), FroidurePinKBERewriteFromLeft)
+ assert isinstance(to_cxx(fp), FroidurePinKBEWordRewriteFromLeft)
def test_to_FroidurePin_003():
fp = check_cong_to_froidure_pin(KnuthBendix, int, Rewriter="RewriteTrie")
- assert isinstance(to_cxx(fp), FroidurePinKBERewriteTrie)
+ assert isinstance(to_cxx(fp), FroidurePinKBEWordRewriteTrie)
# From ToddCoxeter