Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/source/data-structures/presentations/present-helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Contents
greedy_reduce_length
greedy_reduce_length_and_number_of_gens
index_rule
is_normalized
is_rule
is_strongly_compressible
length
longest_rule
Expand All @@ -71,6 +73,7 @@ Contents
strongly_compress
throw_if_bad_inverses
to_gap_string
try_detect_inverses

Full API
--------
Expand Down
6 changes: 6 additions & 0 deletions src/libsemigroups_pybind11/presentation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
presentation_balance as _balance,
presentation_add_cyclic_conjugates as _add_cyclic_conjugates,
presentation_index_rule as _index_rule,
presentation_is_normalized as _is_normalized,
presentation_is_rule as _is_rule,
presentation_try_detect_inverses as _try_detect_inverses,
)

from libsemigroups_pybind11.detail.cxx_wrapper import (
Expand Down Expand Up @@ -270,3 +273,6 @@ def __init__(self: _Self, *args, **kwargs) -> None:
balance = _wrap_cxx_free_fn(_balance)
add_cyclic_conjugates = _wrap_cxx_free_fn(_add_cyclic_conjugates)
index_rule = _wrap_cxx_free_fn(_index_rule)
is_normalized = _wrap_cxx_free_fn(_is_normalized)
is_rule = _wrap_cxx_free_fn(_is_rule)
try_detect_inverses = _wrap_cxx_free_fn(_try_detect_inverses)
86 changes: 86 additions & 0 deletions src/present.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,92 @@ This function returns the minimum index ``i`` of *lhs* such that ``p.rules[i +

:raises LibsemigroupsError: if ``p.throw_if_bad_alphabet_or_rules()`` throws.
)pbdoc");

m.def(
"presentation_is_normalized",
[](Presentation<Word>& p) { return presentation::is_normalized(p); },
py::arg("p"),
R"pbdoc(
:sig=(p: Presentation) -> bool:
:only-document-once:

Check if the presentation is normalized.

This function returns ``True`` if the :any:`Presentation.alphabet` of *p* is
``0`` to ``p.alphabet().size() - 1`` and ``False`` otherwise.

:param p: the presentation to check.
:type p: Presentation

:returns: Whether or not the presentation *p* is normalized.
:rtype: bool
)pbdoc");

m.def("presentation_is_rule",
&presentation::is_rule<Word>,
py::arg("p"),
py::arg("lhs"),
py::arg("rhs"),
R"pbdoc(
:sig=(p: Presentation, lhs: Word, rhs: Word) -> bool:
:only-document-once:

Check whether a rule belongs to a presentation.

This function returns ``True`` if *lhs* and *rhs* form a rule in *p*.
That is, if *lhs* occurs in an even index position in ``p.rules``
and *rhs* is the next item in ``p.rules``.

:param p: the presentation.
:type p: Presentation

:param lhs: the left-hand side of the rule.
:type lhs: :ref:`Word<pseudo_word_type_helper>`

:param rhs: the right-hand side of the rule.
:type rhs: :ref:`Word<pseudo_word_type_helper>`

:returns: Whether or not *lhs* and *rhs* form a rule in *p*.
:rtype: bool

:raises LibsemigroupsError:
if :any:`Presentation.throw_if_bad_alphabet_or_rules` throws.
)pbdoc");

m.def(
"presentation_try_detect_inverses",
[](Presentation<Word>& p) {
return presentation::try_detect_inverses(p);
},
py::arg("p"),
R"pbdoc(
:sig=(p: Presentation) -> tuple[str | list[int], str | list[int]]:
:only-document-once:

Try to detect group inverses.

This function tries to deduce group theoretic inverses defined by the rules of
the presentation *p* as following: the rules of the presentation where one
side has length 2 and the other has length 0 are detected. For any such rule we
remember that the first letter is a possible inverse of the second. If rules of
the form ``ab=1`` and ``ba=1`` are detected, then ``a`` has inverse ``b`` and
vice versa. If there are multiple different such rules and we deduce
conflicting values for the inverse of a letter, then an exception is
raised.

:param p: the presentation.
:type p: Presentation

:returns:
A tuple where the first item consists of letters such that an inverse was
detected; and the second item consists of the inverses such that the item in
position ``i`` is the inverse of the letter in position ``i``.
:rtype: tuple[str | list[int], str | list[int]]

:raises LibsemigroupsError:
if :any:`throw_if_bad_alphabet_or_rules` throws.
:raises LibsemigroupsError:
if conflicting inverses for any letter are detected.)pbdoc");
} // bind_present

template <typename Word>
Expand Down
37 changes: 36 additions & 1 deletion tests/test_present.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# pylint: disable=missing-function-docstring, invalid-name
# pylint: disable=comparison-with-callable, too-many-lines


import copy
import pytest
from libsemigroups_pybind11 import (
Expand Down Expand Up @@ -2200,3 +2199,39 @@ def test_presentation_index_rule():

assert presentation.index_rule(p, "aaaa", "") == 0
assert presentation.index_rule(p, "a", "") == UNDEFINED


def test_presentation_is_normalized():
p = Presentation("abc")
p.contains_empty_word(True)
p.rules = ["aaaa", ""]
assert not presentation.is_normalized(p)
p.alphabet("bac")
assert not presentation.is_normalized(p)

p = Presentation([0, 1, 2])
p.contains_empty_word(True)
p.rules = [[0] * 4, []]
assert presentation.is_normalized(p)


def test_presentation_is_rule():
p = Presentation("abc")
p.contains_empty_word(True)
p.rules = ["aaaa", ""]

assert not presentation.is_rule(p, "a" * 3, "")
assert presentation.is_rule(p, "a" * 4, "")
assert not presentation.is_rule(p, "", "a" * 4)
assert not presentation.is_rule(p, "ad" * 4, "")


def test_presentation_try_detect_inverses():
p = Presentation("abc")
p.contains_empty_word(True)
p.rules = ["aa", ""]

assert presentation.try_detect_inverses(p) == ("a", "a")
p.alphabet("abcdef")
p.rules = ["ab", "", "ba", "", "cd", "", "dc", "", "e", "ef"]
assert presentation.try_detect_inverses(p) == ("dcba", "cdab")
Loading