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
6 changes: 6 additions & 0 deletions docs/source/main-algorithms/todd-coxeter/to-todd-coxeter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ Additionally, specify one of the following for *rtype*:
- ``(ToddCoxeter, list[int])`` for constructing a :any:`ToddCoxeter` on
words with type ``list[int]``.

The word type must be specified explicitly for this conversion. Using
``rtype=(ToddCoxeter,)`` raises a :any:`TypeError`, since the word type cannot
be inferred from *fpb* and *wg*.

This function converts the :any:`FroidurePin` object *fpb* into a
:any:`ToddCoxeter` object using the :any:`WordGraph` *wg* (which should be
either the :any:`FroidurePin.left_cayley_graph` or the
Expand Down Expand Up @@ -107,6 +111,8 @@ Additionally, specify the following for *rtype*:

- ``(ToddCoxeter,)`` for constructing a :any:`ToddCoxeter`.

The word type is inferred from *kb*.

This function converts the :any:`KnuthBendix` object *kb* into a
:any:`ToddCoxeter` object using the right Cayley graph of the semigroup
represented by *kb*.
Expand Down
22 changes: 20 additions & 2 deletions src/libsemigroups_pybind11/to.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,13 @@ def _nice_name(types: _Iterator[_Any]) -> str:
def to(*args, rtype: tuple):
"""Convert from one type of |libsemigroups_pybind11| object to another.

This function converts the the arguments specified in *args* to object of
This function converts the arguments specified in *args* to an object of
type *rtype*.

If a converter raises :any:`TypeError`, the error message lists the possible
values of *rtype* for the requested output class. Which values can be used
depends on *args*. The original exception is retained as the cause.

:param args: the objects to convert.
:param rtype: the type of object to convert to.
:type rtype: tuple
Expand Down Expand Up @@ -180,7 +184,21 @@ def to(*args, rtype: tuple):
f"but found: {_nice_name(rtype)}"
)
constructor = rtype[0]
return constructor(_RETURN_TYPE_TO_CONVERTER_FUNCTION[rtype](*cxx_args))
try:
result = _RETURN_TYPE_TO_CONVERTER_FUNCTION[rtype](*cxx_args)
except TypeError as error:
possible_rtypes = (
_nice_name(types)
for types in _RETURN_TYPE_TO_CONVERTER_FUNCTION
if types[0] is constructor
)
message = (
f"could not convert the arguments with rtype={_nice_name(rtype)}; "
f"possible values of rtype for {_nice_name(constructor)} are:"
"\n * " + "\n * ".join(possible_rtypes) + "\n"
)
raise TypeError(message) from error
return constructor(result)


__all__ = ["to"]
87 changes: 83 additions & 4 deletions tests/test_to.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
PresentationWord,
)
from libsemigroups_pybind11 import (
Alphabet,
Bipartition,
Congruence,
FroidurePin,
InversePresentation,
Kambites,
KnuthBendix,
LibsemigroupsError,
Order,
Presentation,
Stephen,
Expand Down Expand Up @@ -448,18 +450,40 @@ def test_to_ToddCoxeter_017():
# From FroidurePin


def test_to_ToddCoxeter_018():
@pytest.mark.parametrize("kind", [congruence_kind.onesided, congruence_kind.twosided])
def test_to_ToddCoxeter_018(kind):
S = FroidurePin(Transf([1, 3, 4, 2, 3]), Transf([3, 2, 1, 3, 3]))
tc = to(congruence_kind.twosided, S, S.right_cayley_graph(), rtype=(ToddCoxeter, str))
tc = to(kind, S, S.right_cayley_graph(), rtype=(ToddCoxeter, str))
assert tc.current_word_graph().number_of_nodes() == S.size() + 1
assert isinstance(tc, ToddCoxeter)
assert tc.py_template_params == (str,)
assert tc.number_of_classes() == S.size()


def test_to_ToddCoxeter_019():
@pytest.mark.parametrize("kind", [congruence_kind.onesided, congruence_kind.twosided])
def test_to_ToddCoxeter_019(kind):
S = FroidurePin(Transf([1, 3, 4, 2, 3]), Transf([3, 2, 1, 3, 3]))
tc = to(congruence_kind.twosided, S, S.right_cayley_graph(), rtype=(ToddCoxeter, list[int]))
tc = to(kind, S, S.right_cayley_graph(), rtype=(ToddCoxeter, list[int]))
assert tc.current_word_graph().number_of_nodes() == S.size() + 1
assert isinstance(tc, ToddCoxeter)
assert tc.py_template_params == (list[int],)
assert tc.number_of_classes() == S.size()


@pytest.mark.parametrize("kind", [congruence_kind.onesided, congruence_kind.twosided])
@pytest.mark.parametrize("unwrap", [False, True])
def test_to_todd_coxeter_missing_word_type(kind, unwrap):
# https://github.com/libsemigroups/libsemigroups_pybind11/issues/461
S = FroidurePin(Transf([1, 3, 4, 2, 3]), Transf([3, 2, 1, 3, 3]))
wg = S.right_cayley_graph()
if unwrap:
S = to_cxx(S)
with pytest.raises(TypeError, match="possible values of rtype for ToddCoxeter") as exc:
to(kind, S, wg, rtype=(ToddCoxeter,))
assert "(ToddCoxeter, str)" in str(exc.value)
assert "(ToddCoxeter, list[int])" in str(exc.value)
assert isinstance(exc.value.__cause__, TypeError)
assert "to_todd_coxeter(): incompatible function arguments" in str(exc.value.__cause__)


###############################################################################
Expand Down Expand Up @@ -1106,6 +1130,61 @@ def test_to_invalid_word_type():
assert message.endswith("but found: (Presentation, list[str])")


@pytest.mark.parametrize(
"rtype",
[
(Alphabet, str),
(Congruence, str),
(FroidurePin,),
(InversePresentation,),
(KnuthBendix,),
(Presentation,),
(ToddCoxeter,),
],
)
def test_to_conversion_error_suggests_rtypes(rtype):
with pytest.raises(TypeError, match="possible values of rtype") as exc:
to(object(), rtype=rtype)

name = rtype[0].__name__
message = str(exc.value)
assert f"possible values of rtype for {name}" in message
suggestions = [line for line in message.splitlines() if line.startswith(" * ")]
assert suggestions
assert all(line.startswith(f" * ({name}") for line in suggestions)
assert isinstance(exc.value.__cause__, TypeError)
assert "incompatible function arguments" in str(exc.value.__cause__)


def test_to_presentation_error_suggests_word_types():
S = FroidurePin(Transf([1, 0]))
with pytest.raises(TypeError, match="possible values of rtype for Presentation") as exc:
to(S, rtype=(Presentation,))
assert "(Presentation, str)" in str(exc.value)
assert "(Presentation, list[int])" in str(exc.value)
for word_type in (str, list[int]):
assert len(to(S, rtype=(Presentation, word_type)).alphabet()) == 1


def test_to_conversion_error_preserves_callback_cause():
error = TypeError("invalid letter mapping")

def bad_mapping(_):
raise error

with pytest.raises(TypeError, match="possible values of rtype for Presentation") as exc:
to(Presentation("ab"), bad_mapping, rtype=(Presentation, list[int]))
assert exc.value.__cause__ is error


@pytest.mark.parametrize("word_type", [str, list[int]])
def test_to_conversion_error_preserves_libsemigroups_error(word_type):
S = FroidurePin(Transf([1, 0]))
with pytest.raises(LibsemigroupsError, match="expected the 3rd argument") as exc:
to(congruence_kind.twosided, S, WordGraph(0, 0), rtype=(ToddCoxeter, word_type))
assert exc.value.__cause__ is None


def test_to_999():
x = 10
with pytest.raises(TypeError):
Expand Down
Loading