Skip to content

Commit 27f6b22

Browse files
presentation: attempt to resolve #391
1 parent 77662b8 commit 27f6b22

2 files changed

Lines changed: 129 additions & 7 deletions

File tree

src/libsemigroups_pybind11/presentation/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
from _libsemigroups_pybind11 import (
1212
InversePresentationString as _InversePresentationString,
1313
InversePresentationWord as _InversePresentationWord,
14+
RulesWord as _RulesWord,
1415
PresentationString as _PresentationString,
1516
PresentationWord as _PresentationWord,
17+
RulesString as _RulesString,
1618
presentation_add_cyclic_conjugates as _add_cyclic_conjugates,
1719
presentation_add_identity_rules as _add_identity_rules,
1820
presentation_add_inverse_rules as _add_inverse_rules,
@@ -160,14 +162,23 @@ def __init__(self: _Self, *args, **kwargs) -> None:
160162

161163
@_copydoc(_PresentationWord.rules)
162164
@property
163-
def rules(self: _Self) -> list[list[int] | str]:
165+
def rules(self: _Self) -> list[list[int]] | list[str]:
164166
# pylint: disable=missing-function-docstring
165167
return _to_cxx(self).rules
166168

167169
@rules.setter
168-
def rules(self: _Self, val: list[list[int] | str]) -> None:
169-
_to_cxx(self).rules = val
170-
170+
def rules(self: _Self, val: list[list[int]] | list[str]) -> None:
171+
if self.py_template_params == (list[int],):
172+
_to_cxx(self).rules = _RulesWord(val)
173+
else:
174+
_to_cxx(self).rules = _RulesString(val)
175+
176+
177+
# HACK: The next line is a hack to make _RulesString objects to look like
178+
# lists, for some reason _RulesWord doesn't have this problem. It looks like
179+
# _RulesString has a __repr__ function bound in pybind11::bind_vector and
180+
# defining it again has no effect.
181+
_RulesString.__repr__ = lambda self: repr(list(self))
171182

172183
_copy_cxx_mem_fns(_PresentationWord, Presentation)
173184
_register_cxx_wrapped_type(_PresentationWord, Presentation)

src/present.cpp

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,21 @@
2929
#include <libsemigroups/presentation.hpp> // for Presentation
3030
#include <libsemigroups/ranges.hpp> // for is_sorted
3131
#include <libsemigroups/types.hpp> // for word_type
32+
#include <libsemigroups/word-range.hpp> // for operator+
3233

3334
// pybind11....
34-
#include <pybind11/cast.h> // for arg
35+
#include <pybind11/cast.h> // for arg
36+
37+
PYBIND11_MAKE_OPAQUE(std::vector<std::string>);
38+
PYBIND11_MAKE_OPAQUE(std::vector<libsemigroups::word_type>);
39+
3540
#include <pybind11/detail/common.h> // for const_, overload_cast, ove...
3641
#include <pybind11/detail/descr.h> // for operator+
3742
#include <pybind11/functional.h> // for std::function conversion
3843
#include <pybind11/pybind11.h> // for class_, init, module
3944
#include <pybind11/pytypes.h> // for sequence, str_attr_accessor
4045
#include <pybind11/stl.h> // for std::vector conversion
46+
#include <pybind11/stl_bind.h> // for bind_vector
4147

4248
// libsemigroups_pybind11....
4349
#include "main.hpp" // for init_present
@@ -46,6 +52,104 @@ namespace libsemigroups {
4652
namespace py = pybind11;
4753

4854
namespace {
55+
// TODO there are probably more functions like the vector_* functions
56+
// below that could be implemented.
57+
58+
[[nodiscard]] bool
59+
vector_equals_sequence(std::vector<word_type> const& self,
60+
py::object other) {
61+
if (py::isinstance<py::sequence>(other)) {
62+
py::sequence other_seq = other.cast<py::sequence>();
63+
64+
if (other_seq.size() != self.size()) {
65+
return false;
66+
}
67+
68+
for (size_t i = 0; i < self.size(); ++i) {
69+
py::sequence other_item_seq = other_seq[i].cast<py::sequence>();
70+
word_type const& self_item = self[i];
71+
if (other_item_seq.size() != self_item.size()) {
72+
return false;
73+
}
74+
for (size_t j = 0; j < self_item.size(); ++j) {
75+
if (self_item[j] != other_item_seq[j].cast<letter_type>()) {
76+
return false;
77+
}
78+
}
79+
}
80+
return true;
81+
}
82+
return false;
83+
}
84+
85+
[[nodiscard]] bool
86+
vector_equals_sequence(std::vector<std::string> const& self,
87+
py::object other) {
88+
if (py::isinstance<py::sequence>(other)) {
89+
py::sequence other_seq = other.cast<py::sequence>();
90+
91+
if (other_seq.size() != self.size()) {
92+
return false;
93+
}
94+
95+
for (size_t i = 0; i < self.size(); ++i) {
96+
if (self[i] != other_seq[i].cast<std::string>()) {
97+
return false;
98+
}
99+
}
100+
return true;
101+
}
102+
return false;
103+
}
104+
105+
template <typename Word>
106+
[[nodiscard]] std::vector<Word>
107+
vector_add_sequence(std::vector<Word> const& self, py::object other) {
108+
if (!py::isinstance<py::sequence>(other)) {
109+
throw py::type_error("unsupported operand type(s) for +");
110+
}
111+
py::sequence other_seq = other.cast<py::sequence>();
112+
113+
std::vector<Word> result(self);
114+
result.reserve(self.size() + other_seq.size());
115+
116+
for (auto const& item : other_seq) {
117+
result.push_back(item.cast<Word>());
118+
}
119+
120+
return result;
121+
}
122+
123+
template <typename Word>
124+
[[nodiscard]] std::vector<Word>
125+
vector_add_vector(std::vector<Word> const& self,
126+
std::vector<Word> const& other) {
127+
std::vector<Word> result(self);
128+
result.insert(result.end(), other.begin(), other.end());
129+
return result;
130+
}
131+
132+
template <typename Word>
133+
void bind_vector(py::module& m, std::string const& name) {
134+
py::bind_vector<std::vector<Word>>(
135+
m, name.c_str(), py::module_local(false))
136+
.def("__repr__",
137+
[](std::vector<Word> const& self) {
138+
return fmt::format("[{}]",
139+
fmt::join(self.begin(), self.end(), ", "));
140+
})
141+
.def("__eq__",
142+
[](std::vector<Word> const& self, py::object other) {
143+
return vector_equals_sequence(self, other);
144+
})
145+
.def("__ne__",
146+
[](std::vector<Word> const& self, py::object other) {
147+
return !vector_equals_sequence(self, other);
148+
})
149+
.def("__add__", &vector_add_sequence<Word>)
150+
.def("__add__", &vector_add_vector<Word>);
151+
}
152+
49153
template <typename Word>
50154
void bind_present(py::module& m, std::string const& name) {
51155
using Presentation_ = Presentation<Word>;
@@ -71,18 +175,22 @@ available in the module :any:`libsemigroups_pybind11.presentation`.)pbdoc");
71175
[](Presentation_ const& lhop, Presentation_ rhop) -> bool {
72176
return lhop == rhop;
73177
});
178+
74179
thing.def_readwrite("rules",
75180
&Presentation_::rules,
76181
R"pbdoc(
77182
Data member holding the rules of the presentation.
78183
79184
The rules can be altered using the member functions of ``list``, and the
80-
presentation can be checked for validity using :any:`throw_if_bad_alphabet_or_rules`.)pbdoc");
185+
presentation can be checked for validity using
186+
:any:`throw_if_bad_alphabet_or_rules`.)pbdoc");
187+
81188
thing.def(py::init<>(), R"pbdoc(
82189
:sig=(self: Presentation) -> None:
83190
Default constructor.
84191
85192
Constructs an empty presentation with no rules and no alphabet.)pbdoc");
193+
86194
thing.def(
87195
"copy",
88196
[](Presentation_ const& self) { return Presentation_(self); },
@@ -94,6 +202,7 @@ Copy a :any:`Presentation` object.
94202
:returns: A copy.
95203
:rtype: Presentation
96204
)pbdoc");
205+
97206
thing.def("__copy__",
98207
[](Presentation_ const& that) { return Presentation_(that); });
99208
thing.def(
@@ -1655,9 +1764,11 @@ defined in the alphabet, and that the inverses act as semigroup inverses.
16551764
* :any:`presentation.throw_if_bad_inverses`
16561765
)pbdoc");
16571766
} // bind_inverse_present
1658-
} // namespace
1767+
} // namespace
16591768

16601769
void init_present(py::module& m) {
1770+
bind_vector<word_type>(m, "RulesWord");
1771+
bind_vector<std::string>(m, "RulesString");
16611772
bind_present<word_type>(m, "PresentationWord");
16621773
bind_present<std::string>(m, "PresentationString");
16631774
}

0 commit comments

Comments
 (0)