diff --git a/docs/source/data-structures/presentations/present-helpers.rst b/docs/source/data-structures/presentations/present-helpers.rst index 99f3f3def..d615e9f56 100644 --- a/docs/source/data-structures/presentations/present-helpers.rst +++ b/docs/source/data-structures/presentations/present-helpers.rst @@ -34,6 +34,7 @@ Contents .. autosummary:: :signatures: short + add_commutator_rule add_cyclic_conjugates add_identity_rules add_inverse_rules @@ -43,6 +44,7 @@ Contents are_rules_sorted balance change_alphabet + commutator contains_rule first_unused_letter greedy_reduce_length diff --git a/etc/check-params.py b/etc/check-params.py index 4801d38f9..74870d911 100644 --- a/etc/check-params.py +++ b/etc/check-params.py @@ -3,6 +3,7 @@ from glob import iglob from bs4 import BeautifulSoup +from bs4.element import Tag BOLD_TEXT = "\033[1m" YELLOW = "\033[93m" @@ -20,7 +21,7 @@ def warn(message): print(YELLOW + f"WARNING: {message}" + END_COLOUR) -def extract_signature(func, func_name) -> tuple[dict[str, str], str]: +def extract_signature(func: Tag, func_name: str) -> tuple[dict[str, str], str]: """Extract the parameters and typehints from the signature of a function This function interrogates the signature of a function and returns: @@ -31,6 +32,8 @@ def extract_signature(func, func_name) -> tuple[dict[str, str], str]: return_typehint = "" sig = func.find("dt", class_="sig sig-object py") for param in sig.find_all("em", class_="sig-param"): + if param.find("span", class_="keyword-only-separator"): + continue param_component = param.find_all("span", class_="n") if len(param_component) == 0 or len(param_component) > 2: warn(f"unexpected element in doc of {func_name}. Skipping . . .") @@ -44,7 +47,7 @@ def extract_signature(func, func_name) -> tuple[dict[str, str], str]: return param_to_typehint, return_typehint -def extract_documented_signature(func, name) -> tuple[dict[str, str], str]: +def extract_documented_signature(func: Tag, name: str) -> tuple[dict[str, str], str]: """Extract the parameters and typehints from the docstring of a function This function interrogates the docstring of a function and returns: diff --git a/src/libsemigroups_pybind11/detail/cxx_wrapper.py b/src/libsemigroups_pybind11/detail/cxx_wrapper.py index 741204c3d..315640118 100644 --- a/src/libsemigroups_pybind11/detail/cxx_wrapper.py +++ b/src/libsemigroups_pybind11/detail/cxx_wrapper.py @@ -192,8 +192,12 @@ def wrap_cxx_free_fn(cxx_free_fn: Pybind11Type) -> Callable: returned function. """ - def cxx_free_fn_wrapper(*args): - return to_py(cxx_free_fn(*(to_cxx(x) for x in args))) + def cxx_free_fn_wrapper(*args, **kwargs): + return to_py( + cxx_free_fn( + *(to_cxx(x) for x in args), **{key: to_cxx(value) for key, value in kwargs.items()} + ) + ) update_wrapper(cxx_free_fn_wrapper, cxx_free_fn) return cxx_free_fn_wrapper diff --git a/src/libsemigroups_pybind11/presentation/__init__.py b/src/libsemigroups_pybind11/presentation/__init__.py index a916b1d2c..78232d8d6 100644 --- a/src/libsemigroups_pybind11/presentation/__init__.py +++ b/src/libsemigroups_pybind11/presentation/__init__.py @@ -13,6 +13,7 @@ InversePresentationWord as _InversePresentationWord, PresentationString as _PresentationString, PresentationWord as _PresentationWord, + presentation_add_commutator_rule as _add_commutator_rule, presentation_add_cyclic_conjugates as _add_cyclic_conjugates, presentation_add_identity_rules as _add_identity_rules, presentation_add_inverse_rules as _add_inverse_rules, @@ -22,6 +23,7 @@ presentation_are_rules_sorted as _are_rules_sorted, presentation_balance as _balance, presentation_change_alphabet as _change_alphabet, + presentation_commutator as _commutator, presentation_contains_rule as _contains_rule, presentation_first_unused_letter as _first_unused_letter, presentation_greedy_reduce_length as _greedy_reduce_length, @@ -227,6 +229,7 @@ def __init__(self: _Self, *args, **kwargs) -> None: ######################################################################## +add_commutator_rule = _wrap_cxx_free_fn(_add_commutator_rule) add_identity_rules = _wrap_cxx_free_fn(_add_identity_rules) add_inverse_rules = _wrap_cxx_free_fn(_add_inverse_rules) add_rule = _wrap_cxx_free_fn(_add_rule) @@ -234,6 +237,7 @@ def __init__(self: _Self, *args, **kwargs) -> None: add_zero_rules = _wrap_cxx_free_fn(_add_zero_rules) are_rules_sorted = _wrap_cxx_free_fn(_are_rules_sorted) change_alphabet = _wrap_cxx_free_fn(_change_alphabet) +commutator = _wrap_cxx_free_fn(_commutator) contains_rule = _wrap_cxx_free_fn(_contains_rule) first_unused_letter = _wrap_cxx_free_fn(_first_unused_letter) greedy_reduce_length = _wrap_cxx_free_fn(_greedy_reduce_length) diff --git a/src/present.cpp b/src/present.cpp index a74f0b0a0..95bd9bff9 100644 --- a/src/present.cpp +++ b/src/present.cpp @@ -565,6 +565,272 @@ alphabet of *p*, and where :math:`z` is the second parameter. :raises LibsemigroupsError: if *z* is not a letter in ``p.alphabet()``. :complexity: Linear in the number of rules.)pbdoc"); + m.def( + "presentation_commutator", + [](Presentation const& p, Word const& x, Word const& y) { + return presentation::commutator(p, x, y); + }, + py::arg("p"), + py::arg("x"), + py::arg("y"), + R"pbdoc( +:sig=(p: Presentation, x: Word, y: Word) -> Word: +:only-document-once: + +Return the commutator of two words. + +Returns the word :math:`x^{-1}y^{-1}xy`, after attempting to detect inverses +from the rules in *p*, using :any:`try_detect_inverses`. + +:param p: the presentation. +:type p: Presentation + +:param x: the first word in the commutator. +:type x: :ref:`Word` + +:param y: the second word in the commutator. +:type y: :ref:`Word` + +:returns: The commutator :math:`x^{-1}y^{-1}xy`. +:rtype: :ref:`Word` + +:raises LibsemigroupsError: + if *x* or *y* contains a letter not belonging to ``p.alphabet()``, if + :any:`try_detect_inverses` throws, or if *x* or *y* contains a letter for + which no inverse was detected. +)pbdoc"); + m.def( + "presentation_commutator", + [](Presentation const& p, + Word const& x, + Word const& y, + Word const& inverses) { + return presentation::commutator(p, x, y, inverses); + }, + py::arg("p"), + py::arg("x"), + py::arg("y"), + py::arg("inverses"), + R"pbdoc( +:sig=(p: Presentation, x: Word, y: Word, inverses: Word) -> Word: +:only-document-once: + +Return the commutator of two words. + +Returns the word :math:`x^{-1}y^{-1}xy`. The letter ``a`` with index ``i`` in +*inverses* is the inverse of the letter in ``p.alphabet()`` with index ``i``. + +:param p: the presentation. +:type p: Presentation + +:param x: the first word in the commutator. +:type x: :ref:`Word` + +:param y: the second word in the commutator. +:type y: :ref:`Word` + +:param inverses: the inverses of the letters in p.alphabet(). +:type inverses: :ref:`Word` + +:returns: The commutator :math:`x^{-1}y^{-1}xy`. +:rtype: :ref:`Word` + +:raises LibsemigroupsError: + if *inverses* are not valid inverses for ``p.alphabet()``, or if *x* or + *y* contains a letter not belonging to ``p.alphabet()``. +)pbdoc"); + m.def( + "presentation_commutator", + [](Word const& x, + Word const& y, + Word const& alphabet, + Word const& inverses) { + return presentation::commutator(x, y, alphabet, inverses); + }, + py::arg("x"), + py::arg("y"), + py::arg("alphabet"), + py::arg("inverses"), + R"pbdoc( +:sig=(x: Word, y: Word, alphabet: Word, inverses: Word) -> Word: +:only-document-once: + +Return the commutator of two words. + +Returns the word :math:`x^{-1}y^{-1}xy`. The letter ``a`` with index ``i`` in +*inverses* is the inverse of the letter in *alphabet* with index ``i``. + +:param x: the first word in the commutator. +:type x: :ref:`Word` + +:param y: the second word in the commutator. +:type y: :ref:`Word` + +:param alphabet: + the alphabet, which should be a superset of the letters in x and y. +:type alphabet: :ref:`Word` + +:param inverses: the inverses of the letters in alphabet. +:type inverses: :ref:`Word` + +:returns: The commutator :math:`x^{-1}y^{-1}xy`. +:rtype: :ref:`Word` + +:raises LibsemigroupsError: + if *alphabet* contains duplicates, if *inverses* are not valid inverses + for *alphabet*, or if *x* or *y* contains a letter not belonging to + *alphabet*. +)pbdoc"); + m.def( + "presentation_add_commutator_rule", + [](Presentation_& p, + Word const& x, + Word const& y, + Word const& alphabet, + Word const& inverses, + typename Presentation_::letter_type id) { + return presentation::add_commutator_rule( + p, x, y, alphabet, inverses, id); + }, + py::arg("p"), + py::arg("x"), + py::arg("y"), + py::arg("alphabet"), + py::arg("inverses"), + py::kw_only(), // This is so id must be specified by a key-word + py::arg("id") + = static_cast(UNDEFINED), + R"pbdoc( +:sig=(p: Presentation, x: Word, y: Word, alphabet: Word, inverses: Word, *, id: Letter = UNDEFINED) -> None: +:only-document-once: + +Add a commutator rule. + +Adds the rule :math:`x^{-1}y^{-1}xy = id` to *p*. The letter ``a`` with index +``i`` in *inverses* is the inverse of the letter in *alphabet* with index +``i``. If *id* is :any:`UNDEFINED`, then the right-hand side is the empty +word. + +:param p: the presentation. +:type p: Presentation + +:param x: the first word in the commutator. +:type x: :ref:`Word` + +:param y: the second word in the commutator. +:type y: :ref:`Word` + +:param alphabet: + the alphabet, which should be a superset of the letters in *x* and *y*. +:type alphabet: :ref:`Word` + +:param inverses: the inverses of the letters in alphabet. +:type inverses: :ref:`Word` + +:param id: + the identity letter, or :any:`UNDEFINED` for the empty word. This is a + keyword-only argument. +:type id: :ref:`Letter` + +:raises LibsemigroupsError: + if *alphabet*, *inverses*, *x*, *y*, or *id* contains a letter not + belonging to ``p.alphabet()``; if *alphabet* contains duplicates; if + *inverses* are not valid inverses for *alphabet*; or if *x* or *y* + contains a letter not belonging to *alphabet*. +)pbdoc"); + m.def( + "presentation_add_commutator_rule", + [](Presentation_& p, + Word const& x, + Word const& y, + Word const& inverses, + typename Presentation_::letter_type id) { + return presentation::add_commutator_rule(p, x, y, inverses, id); + }, + py::arg("p"), + py::arg("x"), + py::arg("y"), + py::arg("inverses"), + py::kw_only(), // This is so id must be specified by a key-word + py::arg("id") + = static_cast(UNDEFINED), + R"pbdoc( +:sig=(p: Presentation, x: Word, y: Word, inverses: Word, *, id: Letter = UNDEFINED) -> None: +:only-document-once: + +Add a commutator rule. + +Adds the rule :math:`x^{-1}y^{-1}xy = id` to *p*. The letter ``a`` with index +``i`` in *inverses* is the inverse of the letter in ``p.alphabet()`` with +index ``i``. If *id* is :any:`UNDEFINED`, then the right-hand side is the +empty word. + +:param p: the presentation. +:type p: Presentation + +:param x: the first word in the commutator. +:type x: :ref:`Word` + +:param y: the second word in the commutator. +:type y: :ref:`Word` + +:param inverses: the inverses of the letters in p.alphabet(). +:type inverses: :ref:`Word` + +:param id: + the identity letter, or :any:`UNDEFINED` for the empty word. This is a + keyword-only argument. +:type id: :ref:`Letter` + + +:raises LibsemigroupsError: + if *inverses*, *x*, *y*, or *id* contains a letter not belonging to + ``p.alphabet()``, or if *inverses* are not valid inverses for + ``p.alphabet()``. +)pbdoc"); + m.def( + "presentation_add_commutator_rule", + [](Presentation_& p, + Word const& x, + Word const& y, + typename Presentation_::letter_type id) { + return presentation::add_commutator_rule(p, x, y, id); + }, + py::arg("p"), + py::arg("x"), + py::arg("y"), + py::kw_only(), // This is so id must be specified by a key-word + py::arg("id") + = static_cast(UNDEFINED), + R"pbdoc( +:sig=(p: Presentation, x: Word, y: Word, *, id: Letter = UNDEFINED) -> None: +:only-document-once: + +Add a commutator rule. + +Adds the rule :math:`x^{-1}y^{-1}xy = id` to *p*, after attempting to detect +inverses from the rules in *p*, using :any:`try_detect_inverses`. If +*id* is :any:`UNDEFINED`, then the right-hand side is the empty word. + +:param p: the presentation. +:type p: Presentation + +:param x: the first word in the commutator. +:type x: :ref:`Word` + +:param y: the second word in the commutator. +:type y: :ref:`Word` + +:param id: + the identity letter, or :any:`UNDEFINED` for the empty word. This is a + keyword-only argument. +:type id: :ref:`Letter` + +:raises LibsemigroupsError: + if *x*, *y*, or *id* contains a letter not belonging to + ``p.alphabet()``, if :any:`try_detect_inverses` throws, or if *x* or + *y* contains a letter for which no inverse was detected. +)pbdoc"); m.def( "presentation_are_rules_sorted", [](Presentation_ const& p) { diff --git a/tests/test_present.py b/tests/test_present.py index 65a0d1a0f..87bf262c2 100644 --- a/tests/test_present.py +++ b/tests/test_present.py @@ -194,6 +194,280 @@ def check_add_identity_rules(W): ] +def check_commutator_errors(W): # pylint: disable=too-many-statements + # Alphabet specified, inverses specified + + # Words not over the alphabet + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([0]), W([]), W([]), W([])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([]), W([0]), W([]), W([])) + + # Incorrect number of inverses + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([]), W([]), W([0]), W([])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([]), W([]), W([]), W([0])) + + # Alphabet and inverses contain duplicates + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([]), W([]), W([0, 0]), W([0, 1])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([]), W([]), W([0, 1]), W([0, 0])) + + # Invalid inverses + with pytest.raises(LibsemigroupsError): + presentation.commutator(W([]), W([]), W([0, 1, 2]), W([1, 2, 0])) + + # Alphabet inferred, inverses specified + + p = Presentation(W([])) + p.contains_empty_word(True) + + # Words not over the presentation's alphabet + p.alphabet(W([0])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([1]), W([]), W([0])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([1]), W([0])) + + # Incorrect number of inverses + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([]), W([])) + + # Inverses contain letters not in the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([]), W([1])) + + # Inverses contain duplicates + p.alphabet(W([0, 1])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([]), W([0, 0])) + + # Invalid duplicates + p.alphabet(W([0, 1, 2])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([]), W([1, 2, 0])) + + # Alphabet inferred, inverses inferred + + p.init() + p.alphabet(W([])) + p.contains_empty_word(True) + + p.alphabet(W([0])) + # Words not over presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([1]), W([])) + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([1])) + + p.alphabet(W([0, 1])) + presentation.add_rule(p, W([0, 0]), W([])) + presentation.add_rule(p, W([0, 1]), W([])) + # The rules provided don't provide consistent inverses + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([]), W([])) + + p.init() + p.contains_empty_word(True) + p.alphabet(W([0, 1, 2])) + + # 0 and 1 have inverses, but 2 does not + presentation.add_rule(p, W([0, 1]), W([])) + presentation.add_rule(p, W([1, 0]), W([])) + + # Words are not over the letters that have inverses + with pytest.raises(LibsemigroupsError): + presentation.commutator(p, W([0, 1, 2]), W([1, 2])) + + +def check_commutator(W): + p = Presentation(W([])) + p.contains_empty_word(True) + + # alphabet specified, inverses specified + assert presentation.commutator(W([]), W([]), W([]), W([])) == W([]) + + assert presentation.commutator(W([0]), W([]), W([0]), W([1])) == W([1, 0]) + assert presentation.commutator(W([]), W([0]), W([0]), W([1])) == W([1, 0]) + assert presentation.commutator(W([0, 1]), W([]), W([0, 1]), W([2, 3])) == W([3, 2, 0, 1]) + assert presentation.commutator(W([]), W([0, 1]), W([0, 1]), W([2, 3])) == W([3, 2, 0, 1]) + assert presentation.commutator(W([]), W([0, 1]), W([0, 1]), W([1, 0])) == W([0, 1, 0, 1]) + assert presentation.commutator(W([0, 1]), W([]), W([0, 1]), W([1, 0])) == W([0, 1, 0, 1]) + + assert presentation.commutator(W([0, 1, 2]), W([1, 0, 1]), W([0, 1, 2]), W([3, 4, 5])) == W( + [5, 4, 3, 4, 3, 4, 0, 1, 2, 1, 0, 1] + ) + assert presentation.commutator(W([0, 1, 2]), W([1, 0, 1]), W([0, 1, 2]), W([2, 1, 0])) == W( + [0, 1, 2, 1, 2, 1, 0, 1, 2, 1, 0, 1] + ) + + # alphabet inferred, inverses specified + assert presentation.commutator(p, W([]), W([]), W([])) == W([]) + + p.alphabet(W([0, 1, 2])) + assert presentation.commutator(p, W([0, 1]), W([1]), W([1, 0, 2])) == W([0, 1, 0, 0, 1, 1]) + assert presentation.commutator(p, W([0, 1]), W([1]), W([0, 1, 2])) == W([1, 0, 1, 0, 1, 1]) + assert presentation.commutator(p, W([0, 1]), W([1]), W([0, 2, 1])) == W([2, 0, 2, 0, 1, 1]) + + # alphabet inferred, inverses inferred + assert presentation.commutator(p, W([]), W([])) == W([]) + + p.alphabet(W([0, 1, 2])) + presentation.add_rule(p, W([0, 2]), W([])) + presentation.add_rule(p, W([2, 0]), W([])) + assert presentation.commutator(p, W([0, 0]), W([2])) == W([2, 2, 0, 0, 0, 2]) + + +def check_add_commutator_rule_errors(W): # pylint: disable=too-many-statements + p = Presentation(W([])) + + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([0]), W([]), W([]), W([])) + + p.contains_empty_word(True) + p.alphabet(W([0])) + + # The words are not over the provided alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([0]), W([]), W([]), W([])) + + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([0]), W([]), W([])) + + # The words are not over the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([1]), W([]), W([0]), W([0])) + + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([1]), W([0]), W([0])) + + # The provided alphabet and inverses are not over the presentation's + # alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([1]), W([])) + + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([]), W([1])) + + # Alphabet and inverses contain duplicates + p.alphabet(W([0, 1])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([0, 0]), W([0, 1])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([0, 1]), W([0, 0])) + + # The inverses are not valid + p.alphabet(W([0, 1, 2])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([0, 1, 2]), W([1, 2, 0])) + + # The id is not in the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([0, 1, 2]), W([2, 1, 0]), id=W([4])[0]) + + p.init() + p.contains_empty_word(True) + p.alphabet(W([0])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([0]), W([]), W([])) + + p.alphabet(W([0])) + # The words are not over the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([1]), W([]), W([0])) + presentation.add_commutator_rule(p, W([]), W([1]), W([0])) + + # The provided inverses are not over the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([1])) + + # Inverses contain duplicates + p.alphabet(W([0, 1])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([0, 0])) + + # The inverses are not valid + p.alphabet(W([0, 1, 2])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([1, 2, 0])) + + # The id is not in the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), W([2, 1, 0]), id=W([4])[0]) + + p.contains_empty_word(True) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([0]), W([])) + + p.init() + p.contains_empty_word(True) + p.alphabet(W([0])) + # The words are not over the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([1]), W([])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([1])) + + p.alphabet(W([0, 1])) + presentation.add_rule(p, W([0, 0]), W([])) + presentation.add_rule(p, W([0, 1]), W([])) + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([])) + + p.init() + p.contains_empty_word(True) + p.alphabet(W([0, 1, 2])) + # 0 and 1 have inverses, but 2 does not + presentation.add_rule(p, W([0, 1]), W([])) + presentation.add_rule(p, W([1, 0]), W([])) + + # The words are not over the subset of the presentation's alphabet that + # has inverses + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([0, 1, 2]), W([1, 2])) + + # The id is not in the presentation's alphabet + with pytest.raises(LibsemigroupsError): + presentation.add_commutator_rule(p, W([]), W([]), id=W([3])[0]) + + +def check_add_commutator_rule(W): + p = Presentation(W([0, 1, 2, 3])) + p.contains_empty_word(True) + presentation.add_commutator_rule(p, W([0]), W([1]), W([0, 1]), W([2, 3])) + presentation.add_commutator_rule(p, W([2, 0]), W([1]), W([2, 1, 0]), W([0, 3, 2]), id=W([0])[0]) + + assert p.rules == [W([2, 3, 0, 1]), W([]), W([2, 0, 3, 2, 0, 1]), W([0])] + + p.init() + p.contains_empty_word(True) + p.alphabet(W([0, 1, 2, 3])) + presentation.add_commutator_rule(p, W([0]), W([1]), W([2, 3, 0, 1])) + presentation.add_commutator_rule(p, W([2, 0]), W([1]), W([2, 3, 0, 1]), id=W([0])[0]) + + assert p.rules == [W([2, 3, 0, 1]), W([]), W([2, 0, 3, 2, 0, 1]), W([0])] + + p.init() + p.contains_empty_word(True) + p.alphabet(W([0, 1, 2, 3])) + presentation.add_rule(p, W([0, 2]), W([])) + presentation.add_rule(p, W([2, 0]), W([])) + presentation.add_commutator_rule(p, W([2, 0]), W([0])) + presentation.add_commutator_rule(p, W([2, 0]), W([0]), id=W([0])[0]) + assert p.rules == [ + W([0, 2]), + W([]), + W([2, 0]), + W([]), + W([2, 0, 2, 2, 0, 0]), + W([]), + W([2, 0, 2, 2, 0, 0]), + W([0]), + ] + + def check_add_inverse_rules(W): p = Presentation(W([0, 1, 2])) presentation.add_rule(p, W([0, 1, 2, 1]), W([0, 0])) @@ -1353,3 +1627,23 @@ def test_presentation_try_detect_inverses(): ) letters, inverses = "".join(letters), "".join(inverses) assert (letters, inverses) == ("abcd", "badc") + + +def test_commutator_errors(): + check_commutator_errors(to_word) + check_commutator_errors(to_string) + + +def test_commutator(): + check_commutator(to_word) + check_commutator(to_string) + + +def test_add_commutator_rule_errors(): + check_add_commutator_rule_errors(to_word) + check_add_commutator_rule_errors(to_string) + + +def test_add_commutator_rule(): + check_add_commutator_rule(to_word) + check_add_commutator_rule(to_string)