From b3f403e61b7268f19a80834289098c68407f5871 Mon Sep 17 00:00:00 2001 From: Codex OpenAI Date: Fri, 14 Aug 2026 13:57:22 +0100 Subject: [PATCH] order: add support for wr_cmp Co-authored-by: James Mitchell --- docs/source/data-structures/order/index.rst | 3 + src/libsemigroups_pybind11/__init__.py | 4 + src/libsemigroups_pybind11/order.py | 15 ++++ src/order.cpp | 82 ++++++++++++++++++++- tests/test_order.py | 29 ++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/libsemigroups_pybind11/order.py diff --git a/docs/source/data-structures/order/index.rst b/docs/source/data-structures/order/index.rst index 6f371ce9..76bf6951 100644 --- a/docs/source/data-structures/order/index.rst +++ b/docs/source/data-structures/order/index.rst @@ -30,6 +30,7 @@ Contents rev_rpo_cmp rpo_cmp shortlex_compare + wr_cmp Full API -------- @@ -47,3 +48,5 @@ Full API .. autofunction:: rpo_cmp .. autofunction:: shortlex_compare + +.. autofunction:: wr_cmp diff --git a/src/libsemigroups_pybind11/__init__.py b/src/libsemigroups_pybind11/__init__.py index 4d15bbd4..321a1ed8 100644 --- a/src/libsemigroups_pybind11/__init__.py +++ b/src/libsemigroups_pybind11/__init__.py @@ -22,6 +22,7 @@ knuth_bendix, konieczny, matrix, + order, paths, pbr, presentation, @@ -51,6 +52,7 @@ from .knuth_bendix import KnuthBendix from .konieczny import Konieczny from .matrix import Matrix, MatrixKind +from .order import wr_cmp from .presentation import InversePresentation, Presentation from .schreier_sims import SchreierSims from .sims import MinimalRepOrc, RepOrc, Sims1, Sims2, SimsRefinerFaithful, SimsRefinerIdeals @@ -182,6 +184,7 @@ "shortlex_compare", "side", "tril", + "wr_cmp", # Submodules "action", "adapters", @@ -198,6 +201,7 @@ "knuth_bendix", "konieczny", "matrix", + "order", "paths", "pbr", "presentation", diff --git a/src/libsemigroups_pybind11/order.py b/src/libsemigroups_pybind11/order.py new file mode 100644 index 00000000..3fc4074f --- /dev/null +++ b/src/libsemigroups_pybind11/order.py @@ -0,0 +1,15 @@ +# Copyright (c) 2026 J. D. Mitchell +# +# Distributed under the terms of the GPL license version 3. +# +# The full license is in the file LICENSE, distributed with this software. + +"""Word-ordering comparison functions.""" + +from _libsemigroups_pybind11 import wr_cmp as _wr_cmp + +from .detail.cxx_wrapper import wrap_cxx_free_fn as _wrap_cxx_free_fn + +wr_cmp = _wrap_cxx_free_fn(_wr_cmp) + +__all__ = ["wr_cmp"] diff --git a/src/order.cpp b/src/order.cpp index 8e05b195..15368f8a 100644 --- a/src/order.cpp +++ b/src/order.cpp @@ -18,6 +18,7 @@ // C++ stl headers.... #include // for string +#include // for vector // libsemigroups.... #include // for *_cmp, Order @@ -260,6 +261,85 @@ Compare two words using reversed recursive-path ordering. This will be removed from ``libsemigroups_pybind11`` in v2. Instead, use :any:`rev_rpo_cmp`. )pbdoc"); + + m.def( + "wr_cmp", + [](std::vector const& levels, Word const& x, Word const& y) { + return wr_cmp(levels, x, y); + }, + py::arg("levels"), + py::arg("x"), + py::arg("y"), + R"pbdoc( +:sig=(levels: list[int], x: str | list[int], y: str | list[int]) -> bool: +:only-document-once: +Compare two words using wreath-product ordering. + +The *i*-th entry of *levels* is the level assigned to generator *i*. +Differences at higher levels dominate differences at lower levels, and +differences within one level are compared using len-lex ordering. + +:param levels: the level assigned to each generator. +:type levels: list[int] +:param x: the first word. +:type x: str | list[int] +:param y: the second word. +:type y: str | list[int] +:returns: Whether *x* is less than *y*. +:rtype: bool + +:raises LibsemigroupsError: if a letter is not a valid index into *levels*. + +.. doctest:: python + + >>> from libsemigroups_pybind11 import wr_cmp + >>> levels = [1, 1, 0] + >>> wr_cmp(levels, [2, 1, 2, 2], [2, 2, 1, 2]) + True +)pbdoc"); + + m.def( + "wr_cmp", + [](Alphabet const& alphabet, + std::vector const& levels, + Word const& x, + Word const& y) { return wr_cmp(alphabet, levels, x, y); }, + py::arg("alphabet"), + py::arg("levels"), + py::arg("x"), + py::arg("y"), + R"pbdoc( +:sig=(alphabet: Alphabet, levels: list[int], x: str | list[int], y: str | list[int]) -> bool: +:only-document-once: +Compare two words using alphabet-aware wreath-product ordering. + +Letters are mapped to their positions in *alphabet*, and the *i*-th entry of +*levels* is the level assigned to the *i*-th letter of *alphabet*. +Differences at higher levels dominate differences at lower levels, and +differences within one level are compared using len-lex ordering. + +:param alphabet: the ordered alphabet containing the letters of both words. +:type alphabet: Alphabet +:param levels: the level assigned to each letter of *alphabet*. +:type levels: list[int] +:param x: the first word. +:type x: str | list[int] +:param y: the second word. +:type y: str | list[int] +:returns: Whether *x* is less than *y*. +:rtype: bool + +:raises LibsemigroupsError: if a letter does not belong to *alphabet*, or its + position in *alphabet* is not a valid index into *levels*. + +.. doctest:: python + + >>> from libsemigroups_pybind11 import Alphabet, wr_cmp + >>> alphabet = Alphabet("bac") + >>> levels = [1, 1, 0] + >>> wr_cmp(alphabet, levels, "cbcc", "ccbc") + True +)pbdoc"); } } // namespace @@ -322,7 +402,7 @@ respectively, in new code. The recursive-path ordering, as described in :cite:`Jantzen2012aa` (Definition 1.2.14, page 24). - + This is deprecated; use :any:`Order.rpo` instead. )pbdoc") .value("none", Order::none) diff --git a/tests/test_order.py b/tests/test_order.py index 5805036f..b2e82d68 100644 --- a/tests/test_order.py +++ b/tests/test_order.py @@ -19,6 +19,7 @@ rev_rpo_cmp, rpo_cmp, shortlex_compare, + wr_cmp, ) @@ -73,3 +74,31 @@ def test_deprecated_comparisons(old_compare, new_compare): with pytest.deprecated_call(): result = old_compare("ab", "ba") assert result == new_compare("ab", "ba") + + +def test_wr_cmp_for_integer_words(): + """Check wreath-product comparison, validation, and its unchecked form.""" + levels = [0, 0, 1] + x = [0, 2] + y = [1, 2] + + assert wr_cmp(levels, x, y) + assert not wr_cmp(levels, y, x) + + with pytest.raises(LibsemigroupsError): + wr_cmp([0, 1], [0, 2], [0, 1]) + + +def test_wr_cmp_with_alphabet(): + """Check wreath-product comparison over an explicitly ordered alphabet.""" + alphabet = Alphabet("bac") + levels = [1, 1, 0] + + assert wr_cmp(alphabet, levels, "cbcc", "ccbc") + assert wr_cmp(alphabet, levels, "ac", "ca") + + word_alphabet = Alphabet([1, 0]) + assert wr_cmp(word_alphabet, [0, 0], [1], [0]) + + with pytest.raises(LibsemigroupsError): + wr_cmp(alphabet, levels, "d", "b")