Skip to content

Commit 16e2248

Browse files
order: add support for wr_cmp
Co-authored-by: James Mitchell <jdm3@st-andrews.ac.uk>
1 parent 96d3399 commit 16e2248

8 files changed

Lines changed: 137 additions & 62 deletions

File tree

docs/source/data-structures/order/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Contents
3030
rev_rpo_cmp
3131
rpo_cmp
3232
shortlex_compare
33+
wr_cmp
3334

3435
Full API
3536
--------
@@ -47,3 +48,5 @@ Full API
4748
.. autofunction:: rpo_cmp
4849

4950
.. autofunction:: shortlex_compare
51+
52+
.. autofunction:: wr_cmp

src/libsemigroups_pybind11/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
knuth_bendix,
2323
konieczny,
2424
matrix,
25+
order,
2526
paths,
2627
pbr,
2728
presentation,
@@ -52,6 +53,7 @@
5253
from .knuth_bendix import KnuthBendix
5354
from .konieczny import Konieczny
5455
from .matrix import Matrix, MatrixKind
56+
from .order import wr_cmp
5557
from .presentation import InversePresentation, Presentation
5658
from .schreier_sims import SchreierSims
5759
from .sims import MinimalRepOrc, RepOrc, Sims1, Sims2, SimsRefinerFaithful, SimsRefinerIdeals
@@ -183,6 +185,7 @@
183185
"shortlex_compare",
184186
"side",
185187
"tril",
188+
"wr_cmp",
186189
# Submodules
187190
"action",
188191
"adapters",
@@ -199,6 +202,7 @@
199202
"knuth_bendix",
200203
"konieczny",
201204
"matrix",
205+
"order",
202206
"paths",
203207
"pbr",
204208
"presentation",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2026 J. D. Mitchell
2+
#
3+
# Distributed under the terms of the GPL license version 3.
4+
#
5+
# The full license is in the file LICENSE, distributed with this software.
6+
7+
"""Word-ordering comparison functions."""
8+
9+
from _libsemigroups_pybind11 import wr_cmp as _wr_cmp
10+
11+
from .detail.cxx_wrapper import wrap_cxx_free_fn as _wrap_cxx_free_fn
12+
13+
wr_cmp = _wrap_cxx_free_fn(_wr_cmp)
14+
15+
__all__ = ["wr_cmp"]

src/order.cpp

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
// C++ stl headers....
2020
#include <string> // for string
21+
#include <vector> // for vector
2122

2223
// libsemigroups....
2324
#include <libsemigroups/order.hpp> // for *_cmp, Order
@@ -260,6 +261,85 @@ Compare two words using reversed recursive-path ordering.
260261
This will be removed from ``libsemigroups_pybind11`` in v2. Instead, use
261262
:any:`rev_rpo_cmp`.
262263
)pbdoc");
264+
265+
m.def(
266+
"wr_cmp",
267+
[](std::vector<size_t> const& levels, Word const& x, Word const& y) {
268+
return wr_cmp(levels, x, y);
269+
},
270+
py::arg("levels"),
271+
py::arg("x"),
272+
py::arg("y"),
273+
R"pbdoc(
274+
:sig=(levels: list[int], x: str | list[int], y: str | list[int]) -> bool:
275+
:only-document-once:
276+
Compare two words using wreath-product ordering.
277+
278+
The *i*-th entry of *levels* is the level assigned to generator *i*.
279+
Differences at higher levels dominate differences at lower levels, and
280+
differences within one level are compared using len-lex ordering.
281+
282+
:param levels: the level assigned to each generator.
283+
:type levels: list[int]
284+
:param x: the first word.
285+
:type x: str | list[int]
286+
:param y: the second word.
287+
:type y: str | list[int]
288+
:returns: Whether *x* is less than *y*.
289+
:rtype: bool
290+
291+
:raises LibsemigroupsError: if a letter is not a valid index into *levels*.
292+
293+
.. doctest:: python
294+
295+
>>> from libsemigroups_pybind11 import wr_cmp
296+
>>> levels = [1, 1, 0]
297+
>>> wr_cmp(levels, [2, 1, 2, 2], [2, 2, 1, 2])
298+
True
299+
)pbdoc");
300+
301+
m.def(
302+
"wr_cmp",
303+
[](Alphabet<Word> const& alphabet,
304+
std::vector<size_t> const& levels,
305+
Word const& x,
306+
Word const& y) { return wr_cmp(alphabet, levels, x, y); },
307+
py::arg("alphabet"),
308+
py::arg("levels"),
309+
py::arg("x"),
310+
py::arg("y"),
311+
R"pbdoc(
312+
:sig=(alphabet: Alphabet, levels: list[int], x: str | list[int], y: str | list[int]) -> bool:
313+
:only-document-once:
314+
Compare two words using alphabet-aware wreath-product ordering.
315+
316+
Letters are mapped to their positions in *alphabet*, and the *i*-th entry of
317+
*levels* is the level assigned to the *i*-th letter of *alphabet*.
318+
Differences at higher levels dominate differences at lower levels, and
319+
differences within one level are compared using len-lex ordering.
320+
321+
:param alphabet: the ordered alphabet containing the letters of both words.
322+
:type alphabet: Alphabet
323+
:param levels: the level assigned to each letter of *alphabet*.
324+
:type levels: list[int]
325+
:param x: the first word.
326+
:type x: str | list[int]
327+
:param y: the second word.
328+
:type y: str | list[int]
329+
:returns: Whether *x* is less than *y*.
330+
:rtype: bool
331+
332+
:raises LibsemigroupsError: if a letter does not belong to *alphabet*, or its
333+
position in *alphabet* is not a valid index into *levels*.
334+
335+
.. doctest:: python
336+
337+
>>> from libsemigroups_pybind11 import Alphabet, wr_cmp
338+
>>> alphabet = Alphabet("bac")
339+
>>> levels = [1, 1, 0]
340+
>>> wr_cmp(alphabet, levels, "cbcc", "ccbc")
341+
True
342+
)pbdoc");
263343
}
264344
} // namespace
265345

@@ -322,7 +402,7 @@ respectively, in new code.
322402
323403
The recursive-path ordering, as described in :cite:`Jantzen2012aa`
324404
(Definition 1.2.14, page 24).
325-
405+
326406
This is deprecated; use :any:`Order.rpo` instead.
327407
)pbdoc")
328408
.value("none", Order::none)

src/present.cpp

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,18 +1581,16 @@ modified version.
15811581
* :any:`is_strongly_compressible`
15821582
15831583
)pbdoc");
1584-
15851584
m.def(
15861585
"presentation_to_gap_string",
15871586
[](Presentation_ const& p, std::string const& var_name) {
15881587
return presentation::to_gap_string(p, var_name);
15891588
},
15901589
py::arg("p"),
1591-
py::arg("var_name") = "S",
1590+
py::arg("var_name"),
15921591
R"pbdoc(
15931592
:sig=(p: Presentation, var_name: str) -> str:
15941593
:only-document-once:
1595-
15961594
Return the code that would create *p* in GAP.
15971595
15981596
This function returns the string of GAP code that could be used to create an
@@ -1602,26 +1600,12 @@ are created by taking quotients of free semigroups or monoids.
16021600
:param p: the presentation.
16031601
:type p: Presentation
16041602
1605-
:param var_name: the name of the variable to be used in GAP (defaults to ``"S"``).
1603+
:param var_name: the name of the variable to be used in GAP.
16061604
:type var_name: str
16071605
16081606
:returns: The GAP string.
16091607
:rtype: str
1610-
1611-
.. doctest::
1612-
1613-
>>> from libsemigroups_pybind11 import Presentation, presentation
1614-
>>> p = Presentation("ab")
1615-
>>> presentation.add_rule(p, "ab", "ba")
1616-
>>> print(presentation.to_gap_string(p), end="")
1617-
F := FreeSemigroup("a", "b");
1618-
AssignGeneratorVariables(F);;
1619-
R := [
1620-
[a * b, b * a]
1621-
];
1622-
S := F / R;
16231608
)pbdoc");
1624-
16251609
m.def(
16261610
"presentation_to_ace_string",
16271611
[](Presentation_ const& p) { return presentation::to_ace_string(p); },

src/schreier-sims.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,11 @@ the list *gens*.
7878
for this class, or the number of generators exceeds the maximum capacity.
7979
)pbdoc");
8080

81-
thing.def("__copy__", [](SchreierSims_ const& self) {
82-
return std::make_unique<SchreierSims_>(self);
83-
});
81+
thing.def("__copy__",
82+
[](SchreierSims_ const& self) { return SchreierSims_(self); });
8483
thing.def(
8584
"copy",
86-
[](SchreierSims_ const& self) {
87-
return std::make_unique<SchreierSims_>(self);
88-
},
85+
[](SchreierSims_ const& self) { return SchreierSims_(self); },
8986
R"pbdoc(
9087
:sig=(self: SchreierSims) -> SchreierSims:
9188

tests/test_order.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
rev_rpo_cmp,
2020
rpo_cmp,
2121
shortlex_compare,
22+
wr_cmp,
2223
)
2324

2425

@@ -73,3 +74,31 @@ def test_deprecated_comparisons(old_compare, new_compare):
7374
with pytest.deprecated_call():
7475
result = old_compare("ab", "ba")
7576
assert result == new_compare("ab", "ba")
77+
78+
79+
def test_wr_cmp_for_integer_words():
80+
"""Check wreath-product comparison, validation, and its unchecked form."""
81+
levels = [0, 0, 1]
82+
x = [0, 2]
83+
y = [1, 2]
84+
85+
assert wr_cmp(levels, x, y)
86+
assert not wr_cmp(levels, y, x)
87+
88+
with pytest.raises(LibsemigroupsError):
89+
wr_cmp([0, 1], [0, 2], [0, 1])
90+
91+
92+
def test_wr_cmp_with_alphabet():
93+
"""Check wreath-product comparison over an explicitly ordered alphabet."""
94+
alphabet = Alphabet("bac")
95+
levels = [1, 1, 0]
96+
97+
assert wr_cmp(alphabet, levels, "cbcc", "ccbc")
98+
assert wr_cmp(alphabet, levels, "ac", "ca")
99+
100+
word_alphabet = Alphabet([1, 0])
101+
assert wr_cmp(word_alphabet, [0, 0], [1], [0])
102+
103+
with pytest.raises(LibsemigroupsError):
104+
wr_cmp(alphabet, levels, "d", "b")

tests/test_present.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -819,38 +819,6 @@ def check_to_ace_string(W):
819819
)
820820

821821

822-
def check_to_gap_string(W):
823-
p = Presentation(W([0, 1]))
824-
presentation.add_rule(p, W([0, 1]), W([1, 0]))
825-
826-
a, b = ("s0", "s1") if W is to_word else ("a", "b")
827-
assert (
828-
presentation.to_gap_string(p)
829-
== f"""F := FreeSemigroup("{a}", "{b}");
830-
AssignGeneratorVariables(F);;
831-
R := [
832-
[{a} * {b}, {b} * {a}]
833-
];
834-
S := F / R;
835-
"""
836-
)
837-
838-
a, b = ("m0", "m1") if W is to_word else ("a", "b")
839-
p.contains_empty_word(True)
840-
presentation.add_rule(p, W([0, 0]), W([]))
841-
assert (
842-
presentation.to_gap_string(p, "M")
843-
== f"""F := FreeMonoid("{a}", "{b}");
844-
AssignGeneratorVariables(F);;
845-
R := [
846-
[{a} * {b}, {b} * {a}],
847-
[{a} * {a}, One(F)]
848-
];
849-
M := F / R;
850-
"""
851-
)
852-
853-
854822
###############################################################################
855823
# Test functions begin
856824
###############################################################################
@@ -1812,8 +1780,3 @@ def test_add_idempotent_rules():
18121780
def test_to_ace_string():
18131781
check_to_ace_string(to_word)
18141782
check_to_ace_string(to_string)
1815-
1816-
1817-
def test_to_gap_string():
1818-
check_to_gap_string(to_word)
1819-
check_to_gap_string(to_string)

0 commit comments

Comments
 (0)