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