diff --git a/docs/source/index.rst b/docs/source/index.rst index 6442b387..f828ded6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -156,6 +156,7 @@ We would like to thank the authors and contributors of these projects! main-algorithms/action/index main-algorithms/congruence/index main-algorithms/core-classes/index + main-algorithms/du-narendran-rusinowitch/index main-algorithms/froidure-pin/index main-algorithms/kambites/index main-algorithms/knuth-bendix/index @@ -165,4 +166,3 @@ We would like to thank the authors and contributors of these projects! main-algorithms/schreier-sims/index main-algorithms/stephen/index main-algorithms/todd-coxeter/index - diff --git a/docs/source/libsemigroups.bib b/docs/source/libsemigroups.bib index eb366783..d2f6c4c0 100644 --- a/docs/source/libsemigroups.bib +++ b/docs/source/libsemigroups.bib @@ -662,3 +662,13 @@ @article{Stephen1987aa author = {Stephen, Joseph Buchanan}, month = {01}, year = {1987}} + +@article{Du2026aa, + author = {Du, Wei and Narendran, Paliath and Rusinowitch, Michael}, + doi = {10.1016/j.jlamp.2026.101126}, + journal = {Journal of Logical and Algebraic Methods in Programming}, + pages = {101126}, + title = {Inferring RPO symbol orderings}, + volume = {150}, + year = {2026} +} diff --git a/docs/source/main-algorithms/du-narendran-rusinowitch/index.rst b/docs/source/main-algorithms/du-narendran-rusinowitch/index.rst new file mode 100644 index 00000000..8a7a800e --- /dev/null +++ b/docs/source/main-algorithms/du-narendran-rusinowitch/index.rst @@ -0,0 +1,17 @@ +.. + 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. + +.. currentmodule:: libsemigroups_pybind11 + +Du-Narendran-Rusinowitch +======================== + +This page describes the implementation of the algorithm from +:cite:`Du2026aa` for finding an alphabet order that orients the rules of a +presentation with respect to recursive-path ordering. + +.. autofunction:: du_narendran_rusinowitch diff --git a/src/du-narendran-rusinowitch.cpp b/src/du-narendran-rusinowitch.cpp new file mode 100644 index 00000000..64e886bb --- /dev/null +++ b/src/du-narendran-rusinowitch.cpp @@ -0,0 +1,83 @@ +// +// libsemigroups_pybind11 +// Copyright (C) 2026 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 . +// + +// C++ stl headers.... +#include // for string + +// libsemigroups.... +#include // for du_narendran_... +#include // for Presentation +#include // for word_type + +// pybind11.... +#include // for arg, module +#include // for automatic conversions + +// libsemigroups_pybind11.... +#include "main.hpp" // for init_du_narendran_rusinowitch + +namespace libsemigroups { + namespace py = pybind11; + + namespace { + template + void bind_du_narendran_rusinowitch(py::module& m) { + m.def( + "du_narendran_rusinowitch", + [](Presentation const& p) { + return du_narendran_rusinowitch(p); + }, + py::arg("p"), + R"pbdoc( +:sig=(p: Presentation) -> str | list[int]: +:only-document-once: +Find an alphabet order that orients every rule using recursive-path ordering. + +This function returns the alphabet of *p*, ordered so that every rule +:math:`u \to v` satisfies :math:`u > v` with respect to recursive-path +ordering. It returns an empty word if no such order exists, or if the alphabet +of *p* is empty. + +:param p: the presentation whose rules are to be oriented. +:type p: Presentation + +:returns: An alphabet order orienting every rule, or an empty word if none + exists. +:rtype: str | list[int] + +:raises LibsemigroupsError: if the alphabet or rules of *p* are invalid. + +.. seealso:: + :any:`rpo_cmp` + +.. doctest:: + + >>> from libsemigroups_pybind11 import Presentation, du_narendran_rusinowitch + >>> p = Presentation("abcd") + >>> p.rules = ["a", "cc", "d", "bcc", "bccb", "c", "cccb", "bccc"] + >>> du_narendran_rusinowitch(p) + 'bcda' +)pbdoc"); + } + } // namespace + + void init_du_narendran_rusinowitch(py::module& m) { + bind_du_narendran_rusinowitch(m); + bind_du_narendran_rusinowitch(m); + } +} // namespace libsemigroups diff --git a/src/libsemigroups_pybind11/__init__.py b/src/libsemigroups_pybind11/__init__.py index 4d15bbd4..e655588f 100644 --- a/src/libsemigroups_pybind11/__init__.py +++ b/src/libsemigroups_pybind11/__init__.py @@ -43,6 +43,7 @@ from .congruence import Congruence from .detail.cxx_wrapper import wrap_cxx_free_fn as _wrap_cxx_free_fn from .detail.dot import Dot +from .du_narendran_rusinowitch import du_narendran_rusinowitch from .forest import PathsFromRoots, PathsToRoots from .froidure_pin import FroidurePin from .hpcombi import LIBSEMIGROUPS_HPCOMBI_ENABLED @@ -244,6 +245,7 @@ "ToddCoxeter", "Transf", # Free functions from submodules + "du_narendran_rusinowitch", "is_obviously_infinite", "to", "validate", diff --git a/src/libsemigroups_pybind11/du_narendran_rusinowitch.py b/src/libsemigroups_pybind11/du_narendran_rusinowitch.py new file mode 100644 index 00000000..7fd7e3f5 --- /dev/null +++ b/src/libsemigroups_pybind11/du_narendran_rusinowitch.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. + +"""Subpackage for importing and wrapping :any:`du_narendran_rusinowitch`.""" + +from _libsemigroups_pybind11 import du_narendran_rusinowitch as _du_narendran_rusinowitch + +from .detail.cxx_wrapper import wrap_cxx_free_fn as _wrap_cxx_free_fn + +du_narendran_rusinowitch = _wrap_cxx_free_fn(_du_narendran_rusinowitch) + +__all__ = ["du_narendran_rusinowitch"] diff --git a/src/main.cpp b/src/main.cpp index fd5c9afb..9f3ce12a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,6 +118,7 @@ namespace libsemigroups { init_bipart(m); init_blocks(m); init_cong(m); + init_du_narendran_rusinowitch(m); init_error(m); init_freeband(m); init_froidure_pin_base(m); // Must be before init_froidure_pin diff --git a/src/main.hpp b/src/main.hpp index 0f9742a4..b7ac53a9 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -45,6 +45,7 @@ namespace libsemigroups { void init_cong(py::module&); void init_constants(py::module&); void init_dot(py::module&); + void init_du_narendran_rusinowitch(py::module&); void init_error(py::module&); void init_forest(py::module&); void init_freeband(py::module&); diff --git a/tests/test_du_narendran_rusinowitch.py b/tests/test_du_narendran_rusinowitch.py new file mode 100644 index 00000000..c675553b --- /dev/null +++ b/tests/test_du_narendran_rusinowitch.py @@ -0,0 +1,60 @@ +# 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. + +"""Tests for du_narendran_rusinowitch.""" + +# pylint: disable=missing-function-docstring + +import pytest + +from libsemigroups_pybind11 import ( + Alphabet, + LibsemigroupsError, + Presentation, + du_narendran_rusinowitch, + rpo_cmp, +) + + +@pytest.mark.parametrize( + ("alphabet", "rules", "expected"), + [ + ("abcde", ["dbcbace", "cbbaec", "bcbad", "badbc"], "edcab"), + ( + [0, 1, 2, 3, 4], + [[3, 1, 2, 1, 0, 2, 4], [2, 1, 1, 0, 4, 2], [1, 2, 1, 0, 3], [1, 0, 3, 1, 2]], + [4, 3, 2, 0, 1], + ), + ], +) +def test_du_narendran_rusinowitch(alphabet, rules, expected): + p = Presentation(alphabet) + p.rules = rules + + assert du_narendran_rusinowitch(p) == expected + for lhs, rhs in list(zip(rules[::2], rules[1::2], strict=True)): + assert rpo_cmp(Alphabet(alphabet), rhs, lhs) + + +def test_du_narendran_rusinowitch_no_order_exists(): + p = Presentation("abcd") + p.rules = ["a", "b", "b", "c", "c", "d", "d", "a"] + + assert du_narendran_rusinowitch(p) == "" + + +def test_du_narendran_rusinowitch_empty_rules(): + p = Presentation([2, 0, 1]) + + assert du_narendran_rusinowitch(p) == [2, 0, 1] + + +def test_du_narendran_rusinowitch_invalid_presentation(): + p = Presentation("ab") + p.rules = ["a", "c"] + + with pytest.raises(LibsemigroupsError): + du_narendran_rusinowitch(p)