From 27f6b2244a42ec78299309f0516cac8a3811f27c Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Tue, 24 Feb 2026 12:15:44 +0000 Subject: [PATCH 1/4] presentation: attempt to resolve #391 --- .../presentation/__init__.py | 19 ++- src/present.cpp | 117 +++++++++++++++++- 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/src/libsemigroups_pybind11/presentation/__init__.py b/src/libsemigroups_pybind11/presentation/__init__.py index a916b1d2..f8a7e083 100644 --- a/src/libsemigroups_pybind11/presentation/__init__.py +++ b/src/libsemigroups_pybind11/presentation/__init__.py @@ -11,8 +11,10 @@ from _libsemigroups_pybind11 import ( InversePresentationString as _InversePresentationString, InversePresentationWord as _InversePresentationWord, + RulesWord as _RulesWord, PresentationString as _PresentationString, PresentationWord as _PresentationWord, + RulesString as _RulesString, presentation_add_cyclic_conjugates as _add_cyclic_conjugates, presentation_add_identity_rules as _add_identity_rules, presentation_add_inverse_rules as _add_inverse_rules, @@ -160,14 +162,23 @@ def __init__(self: _Self, *args, **kwargs) -> None: @_copydoc(_PresentationWord.rules) @property - def rules(self: _Self) -> list[list[int] | str]: + def rules(self: _Self) -> list[list[int]] | list[str]: # pylint: disable=missing-function-docstring return _to_cxx(self).rules @rules.setter - def rules(self: _Self, val: list[list[int] | str]) -> None: - _to_cxx(self).rules = val - + def rules(self: _Self, val: list[list[int]] | list[str]) -> None: + if self.py_template_params == (list[int],): + _to_cxx(self).rules = _RulesWord(val) + else: + _to_cxx(self).rules = _RulesString(val) + + +# HACK: The next line is a hack to make _RulesString objects to look like +# lists, for some reason _RulesWord doesn't have this problem. It looks like +# _RulesString has a __repr__ function bound in pybind11::bind_vector and +# defining it again has no effect. +_RulesString.__repr__ = lambda self: repr(list(self)) _copy_cxx_mem_fns(_PresentationWord, Presentation) _register_cxx_wrapped_type(_PresentationWord, Presentation) diff --git a/src/present.cpp b/src/present.cpp index 66e09ae2..9614cb51 100644 --- a/src/present.cpp +++ b/src/present.cpp @@ -29,15 +29,21 @@ #include // for Presentation #include // for is_sorted #include // for word_type +#include // for operator+ // pybind11.... -#include // for arg +#include // for arg + +PYBIND11_MAKE_OPAQUE(std::vector); +PYBIND11_MAKE_OPAQUE(std::vector); + #include // for const_, overload_cast, ove... #include // for operator+ #include // for std::function conversion #include // for class_, init, module #include // for sequence, str_attr_accessor #include // for std::vector conversion +#include // for bind_vector // libsemigroups_pybind11.... #include "main.hpp" // for init_present @@ -46,6 +52,104 @@ namespace libsemigroups { namespace py = pybind11; namespace { + // TODO there are probably more functions like the vector_* functions + // below that could be implemented. + + [[nodiscard]] bool + vector_equals_sequence(std::vector const& self, + py::object other) { + if (py::isinstance(other)) { + py::sequence other_seq = other.cast(); + + if (other_seq.size() != self.size()) { + return false; + } + + for (size_t i = 0; i < self.size(); ++i) { + py::sequence other_item_seq = other_seq[i].cast(); + word_type const& self_item = self[i]; + if (other_item_seq.size() != self_item.size()) { + return false; + } + for (size_t j = 0; j < self_item.size(); ++j) { + if (self_item[j] != other_item_seq[j].cast()) { + return false; + } + } + } + return true; + } + return false; + } + + [[nodiscard]] bool + vector_equals_sequence(std::vector const& self, + py::object other) { + if (py::isinstance(other)) { + py::sequence other_seq = other.cast(); + + if (other_seq.size() != self.size()) { + return false; + } + + for (size_t i = 0; i < self.size(); ++i) { + if (self[i] != other_seq[i].cast()) { + return false; + } + } + return true; + } + return false; + } + + template + [[nodiscard]] std::vector + vector_add_sequence(std::vector const& self, py::object other) { + if (!py::isinstance(other)) { + throw py::type_error("unsupported operand type(s) for +"); + } + py::sequence other_seq = other.cast(); + + std::vector result(self); + result.reserve(self.size() + other_seq.size()); + + for (auto const& item : other_seq) { + result.push_back(item.cast()); + } + + return result; + } + + template + [[nodiscard]] std::vector + vector_add_vector(std::vector const& self, + std::vector const& other) { + std::vector result(self); + result.insert(result.end(), other.begin(), other.end()); + return result; + } + + template + void bind_vector(py::module& m, std::string const& name) { + py::bind_vector>( + m, name.c_str(), py::module_local(false)) + .def("__repr__", + [](std::vector const& self) { + return fmt::format("[{}]", + fmt::join(self.begin(), self.end(), ", ")); + }) + .def("__eq__", + [](std::vector const& self, py::object other) { + return vector_equals_sequence(self, other); + }) + .def("__ne__", + [](std::vector const& self, py::object other) { + return !vector_equals_sequence(self, other); + }) + .def("__add__", &vector_add_sequence) + .def("__add__", &vector_add_vector); + } + template void bind_present(py::module& m, std::string const& name) { using Presentation_ = Presentation; @@ -71,18 +175,22 @@ available in the module :any:`libsemigroups_pybind11.presentation`.)pbdoc"); [](Presentation_ const& lhop, Presentation_ rhop) -> bool { return lhop == rhop; }); + thing.def_readwrite("rules", &Presentation_::rules, R"pbdoc( Data member holding the rules of the presentation. The rules can be altered using the member functions of ``list``, and the -presentation can be checked for validity using :any:`throw_if_bad_alphabet_or_rules`.)pbdoc"); +presentation can be checked for validity using +:any:`throw_if_bad_alphabet_or_rules`.)pbdoc"); + thing.def(py::init<>(), R"pbdoc( :sig=(self: Presentation) -> None: Default constructor. Constructs an empty presentation with no rules and no alphabet.)pbdoc"); + thing.def( "copy", [](Presentation_ const& self) { return Presentation_(self); }, @@ -94,6 +202,7 @@ Copy a :any:`Presentation` object. :returns: A copy. :rtype: Presentation )pbdoc"); + thing.def("__copy__", [](Presentation_ const& that) { return Presentation_(that); }); thing.def( @@ -1655,9 +1764,11 @@ defined in the alphabet, and that the inverses act as semigroup inverses. * :any:`presentation.throw_if_bad_inverses` )pbdoc"); } // bind_inverse_present - } // namespace + } // namespace void init_present(py::module& m) { + bind_vector(m, "RulesWord"); + bind_vector(m, "RulesString"); bind_present(m, "PresentationWord"); bind_present(m, "PresentationString"); } From 3168a9e36def59243a5909a2c7df654d8030b55b Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Tue, 24 Feb 2026 14:24:14 +0000 Subject: [PATCH 2/4] Format --- src/libsemigroups_pybind11/presentation/__init__.py | 2 +- src/present.cpp | 2 +- src/transf.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsemigroups_pybind11/presentation/__init__.py b/src/libsemigroups_pybind11/presentation/__init__.py index f8a7e083..624a1239 100644 --- a/src/libsemigroups_pybind11/presentation/__init__.py +++ b/src/libsemigroups_pybind11/presentation/__init__.py @@ -11,10 +11,10 @@ from _libsemigroups_pybind11 import ( InversePresentationString as _InversePresentationString, InversePresentationWord as _InversePresentationWord, - RulesWord as _RulesWord, PresentationString as _PresentationString, PresentationWord as _PresentationWord, RulesString as _RulesString, + RulesWord as _RulesWord, presentation_add_cyclic_conjugates as _add_cyclic_conjugates, presentation_add_identity_rules as _add_identity_rules, presentation_add_inverse_rules as _add_inverse_rules, diff --git a/src/present.cpp b/src/present.cpp index 9614cb51..37f2a5fd 100644 --- a/src/present.cpp +++ b/src/present.cpp @@ -1764,7 +1764,7 @@ defined in the alphabet, and that the inverses act as semigroup inverses. * :any:`presentation.throw_if_bad_inverses` )pbdoc"); } // bind_inverse_present - } // namespace + } // namespace void init_present(py::module& m) { bind_vector(m, "RulesWord"); diff --git a/src/transf.cpp b/src/transf.cpp index 69920a0f..9e5126e0 100644 --- a/src/transf.cpp +++ b/src/transf.cpp @@ -733,7 +733,7 @@ fewer points requiring less space per point. m.def("transf_inverse", py::overload_cast(&inverse)); } // bind_perm - } // namespace + } // namespace void init_transf(py::module& m) { // Transformations From 57438f82a620ff7f31d811976657f2ab02111cbf Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Tue, 24 Feb 2026 14:31:25 +0000 Subject: [PATCH 3/4] Try to fix MacOS runners --- src/present.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/present.cpp b/src/present.cpp index 37f2a5fd..c79dde54 100644 --- a/src/present.cpp +++ b/src/present.cpp @@ -32,11 +32,7 @@ #include // for operator+ // pybind11.... -#include // for arg - -PYBIND11_MAKE_OPAQUE(std::vector); -PYBIND11_MAKE_OPAQUE(std::vector); - +#include // for arg #include // for const_, overload_cast, ove... #include // for operator+ #include // for std::function conversion @@ -48,6 +44,9 @@ PYBIND11_MAKE_OPAQUE(std::vector); // libsemigroups_pybind11.... #include "main.hpp" // for init_present +PYBIND11_MAKE_OPAQUE(std::vector); +PYBIND11_MAKE_OPAQUE(std::vector); + namespace libsemigroups { namespace py = pybind11; @@ -1764,7 +1763,7 @@ defined in the alphabet, and that the inverses act as semigroup inverses. * :any:`presentation.throw_if_bad_inverses` )pbdoc"); } // bind_inverse_present - } // namespace + } // namespace void init_present(py::module& m) { bind_vector(m, "RulesWord"); From 003d1a666e4308ae4f491da216c59992c72a14d0 Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Tue, 24 Feb 2026 14:35:55 +0000 Subject: [PATCH 4/4] Format --- src/present.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/present.cpp b/src/present.cpp index c79dde54..b791036f 100644 --- a/src/present.cpp +++ b/src/present.cpp @@ -1763,7 +1763,7 @@ defined in the alphabet, and that the inverses act as semigroup inverses. * :any:`presentation.throw_if_bad_inverses` )pbdoc"); } // bind_inverse_present - } // namespace + } // namespace void init_present(py::module& m) { bind_vector(m, "RulesWord");