Skip to content

Commit 3ddb601

Browse files
Add decorator template_params_as_kwargs and use it
1 parent 6cb93b5 commit 3ddb601

File tree

6 files changed

+151
-129
lines changed

6 files changed

+151
-129
lines changed

docs/source/main-algorithms/todd-coxeter/class/index_to_word.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
The full license is in the file LICENSE, distributed with this software.
66
7-
.. currentmodule:: _libsemigroups_pybind11
7+
.. currentmodule:: libsemigroups_pybind11.todd_coxeter
88

99
Class index to word
1010
===================
@@ -14,6 +14,4 @@ can be used to convert the index of a congruence class to a representative word
1414
belonging to that congruence class.
1515

1616
.. automethod:: ToddCoxeter.current_word_of
17-
.. automethod:: ToddCoxeter.current_str_of
1817
.. automethod:: ToddCoxeter.word_of
19-
.. automethod:: ToddCoxeter.str_of

libsemigroups_pybind11/detail/decorators.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,45 @@ def wrapper(self, *args, **kwargs):
7777
return result
7878

7979
return wrapper
80+
81+
82+
def template_params_as_kwargs(**kwargs_map):
83+
"""
84+
Usage example:
85+
86+
@template_params_as_kwargs(
87+
Word={str: _str_normal_forms, List[int]: _word_normal_forms}
88+
)
89+
def foo(**kwargs):
90+
pass
91+
92+
will call _str_normal_forms(*args) on calls to foo(Word = str) and call
93+
_word_normal_forms on calls to foo(Word = List[int]).
94+
95+
Currently only works with a single kwarg
96+
"""
97+
98+
def decorator(func):
99+
@wraps(func)
100+
def wrapper(*args, **kwargs):
101+
if len(kwargs) != len(kwargs_map):
102+
raise TypeError(
103+
f"expected {len(kwargs_map)} keyword argument"
104+
f"{'s'[:len(kwargs_map)^1]}, but found {len(kwargs)}"
105+
)
106+
if kwargs_map.keys() != kwargs.keys():
107+
raise TypeError(
108+
f"expected keyword arguments {tuple(kwargs_map.keys())}, "
109+
f"but found {tuple(kwargs.keys())}"
110+
)
111+
for key, val in kwargs.items():
112+
if val in kwargs_map[key]:
113+
return kwargs_map[key][val](*args)
114+
raise TypeError(
115+
f'expected the value of the keyword argument "{key}" to belong in '
116+
f"{kwargs_map[key].keys()}, but found {val}"
117+
)
118+
119+
return wrapper
120+
121+
return decorator

libsemigroups_pybind11/knuth_bendix.py

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
from .detail.decorators import (
3535
may_return_positive_infinity as _may_return_positive_infinity,
36+
template_params_as_kwargs as _template_params_as_kwargs,
3637
)
3738

3839
_Presentation = (_PresentationStrings, _PresentationWords)
@@ -85,6 +86,14 @@ def KnuthBendix(*args, rewriter="RewriteTrie"): # pylint: disable=invalid-name
8586

8687
# The next function (non_trivial_classes) is documented here not in the cpp
8788
# file because we add the additional kwarg Word.
89+
90+
91+
@_template_params_as_kwargs(
92+
Word={
93+
str: _knuth_bendix_str_non_trivial_classes,
94+
List[int]: _knuth_bendix_word_non_trivial_classes,
95+
}
96+
)
8897
def non_trivial_classes(
8998
kb1: KnuthBendix, kb2: KnuthBendix, **kwargs
9099
) -> List[List[str | List[int]]]:
@@ -154,28 +163,16 @@ def non_trivial_classes(
154163
>>> knuth_bendix.non_trivial_classes(kb1, kb2, Word=List[int])
155164
[[[1], [0, 1], [1, 1], [0, 1, 1], [0]]]
156165
"""
157-
if len(kwargs) != 1:
158-
raise TypeError(f"expected 1 keyword argument, but found {len(kwargs)}")
159-
if "Word" not in kwargs:
160-
raise TypeError(
161-
f'expected keyword argument "Word", but found "{next(iter(kwargs))}"'
162-
)
163-
if kwargs["Word"] is List[int]:
164-
return _knuth_bendix_word_non_trivial_classes(kb1, kb2)
165-
if kwargs["Word"] is str:
166-
return _knuth_bendix_str_non_trivial_classes(kb1, kb2)
167-
168-
val = kwargs["Word"]
169-
val = f'"{val}"' if isinstance(val, str) else val
170-
171-
raise TypeError(
172-
'expected the value of the keyword argument "Word" to be '
173-
f"List[int] or str, but found {val}"
174-
)
175166

176167

177168
# The next function (normal_forms) is documented here not in the cpp
178169
# file because we add the additional kwarg Word.
170+
@_template_params_as_kwargs(
171+
Word={
172+
str: _knuth_bendix_str_normal_forms,
173+
List[int]: _knuth_bendix_word_normal_forms,
174+
}
175+
)
179176
def normal_forms(kb: KnuthBendix, **kwargs) -> Iterator[str | List[int]]:
180177
r"""
181178
Returns an iterator yielding normal forms.
@@ -219,21 +216,3 @@ def normal_forms(kb: KnuthBendix, **kwargs) -> Iterator[str | List[int]]:
219216
>>> list(knuth_bendix.normal_forms(kb, Word=List[int]).min(1).max(3))
220217
[[97], [98], [99], [97, 97], [97, 98], [97, 99], [98, 97], [98, 98], [98, 99], [99, 97], [99, 98], [99, 99]]
221218
"""
222-
if len(kwargs) != 1:
223-
raise TypeError(f"expected 1 keyword argument, but found {len(kwargs)}")
224-
if "Word" not in kwargs:
225-
raise TypeError(
226-
f'expected keyword argument "Word", but found "{next(iter(kwargs))}"'
227-
)
228-
if kwargs["Word"] is List[int]:
229-
return _knuth_bendix_word_normal_forms(kb)
230-
if kwargs["Word"] is str:
231-
return _knuth_bendix_str_normal_forms(kb)
232-
233-
val = kwargs["Word"]
234-
val = f'"{val}"' if isinstance(val, str) else val
235-
236-
raise TypeError(
237-
'expected the value of the keyword argument "Word" to be '
238-
f"List[int] or str, but found {val}"
239-
)

libsemigroups_pybind11/todd_coxeter.py

Lines changed: 84 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,14 @@
2929
from .detail.decorators import (
3030
may_return_positive_infinity as _may_return_positive_infinity,
3131
may_return_undefined as _may_return_undefined,
32+
template_params_as_kwargs as _template_params_as_kwargs,
3233
)
3334

35+
36+
def noop():
37+
pass
38+
39+
3440
ToddCoxeter.number_of_classes = _may_return_positive_infinity(
3541
ToddCoxeter._number_of_classes
3642
)
@@ -44,9 +50,82 @@
4450
)
4551

4652

53+
ToddCoxeter.current_word_of = _template_params_as_kwargs(
54+
Word={
55+
str: ToddCoxeter._current_str_of,
56+
List[int]: ToddCoxeter._current_word_of,
57+
}
58+
)(noop)
59+
60+
61+
ToddCoxeter.current_word_of.__doc__ = """
62+
:sig=(i: int, **kwargs) -> List[int] | str:
63+
Returns a current word representing a class with given index.
64+
65+
This function returns the current word representing the class with index *i*.
66+
No enumeration is triggered by calls to this function, but
67+
:any:`current_word_graph` is standardized (using :any:`Order.shortlex`) if it
68+
is not already standardized. The output word is obtained by following a path in
69+
:any:`current_spanning_tree` from the node corresponding to index *i* back to
70+
the root of that tree.
71+
72+
:param i: the index of the class.
73+
:type i: int
74+
75+
:Keyword Arguments:
76+
* *Word* (``type``) -- type of the output words (must be ``str`` or ``List[int]``).
77+
78+
:returns: The word representing the *i*-th class.
79+
:rtype: List[int] | str
80+
81+
:raises LibsemigroupsError: if *i* is out of bounds.
82+
83+
:raises TypeError:
84+
if the keyword argument *Word* is not present, any other keyword
85+
argument is present, or is present but has value other than ``str`` or
86+
``List[int]``.
87+
"""
88+
89+
ToddCoxeter.word_of = _template_params_as_kwargs(
90+
Word={
91+
str: ToddCoxeter._str_of,
92+
List[int]: ToddCoxeter._word_of,
93+
}
94+
)(noop)
95+
96+
ToddCoxeter.word_of.__doc__ = """
97+
:sig=(i: int, **kwargs) -> List[int] | str:
98+
Returns a word representing a class with given index.
99+
100+
This function returns the word representing the class with index *i*. A full
101+
enumeration is triggered by calls to this function. The output word is obtained
102+
by following a path in :any:`current_spanning_tree` from the node corresponding
103+
to index *i* back to the root of that tree.
104+
105+
:param i: the index of the class.
106+
:type i: int
107+
108+
:Keyword Arguments:
109+
* *Word* (``type``) -- type of the output words (must be ``str`` or ``List[int]``).
110+
111+
:returns: The word representing the *i*-th class.
112+
:rtype: List[int]
113+
114+
:raises LibsemigroupsError: if *i* is out of bounds.
115+
116+
:raises TypeError:
117+
if the keyword argument *Word* is not present, any other keyword
118+
argument is present, or is present but has value other than ``str`` or
119+
``List[int]``.
120+
"""
121+
122+
47123
# The next function (normal_forms) is documented here not in the cpp
48124
# file because we add the additional kwarg Word.
49-
def normal_forms(kb: ToddCoxeter, **kwargs) -> Iterator[str | List[int]]:
125+
@_template_params_as_kwargs(
126+
Word={str: _str_normal_forms, List[int]: _word_normal_forms}
127+
)
128+
def normal_forms(kb: ToddCoxeter, **kwargs) -> Iterator[str | List[int]]: # pylint: disable=unused-argument
50129
r"""
51130
Returns an iterator yielding normal forms.
52131
@@ -70,29 +149,14 @@ def normal_forms(kb: ToddCoxeter, **kwargs) -> Iterator[str | List[int]]:
70149
argument is present, or is present but has value other than ``str`` or
71150
``List[int]``.
72151
"""
73-
if len(kwargs) != 1:
74-
raise TypeError(f"expected 1 keyword argument, but found {len(kwargs)}")
75-
if "Word" not in kwargs:
76-
raise TypeError(
77-
f'expected keyword argument "Word", but found "{next(iter(kwargs))}"'
78-
)
79-
if kwargs["Word"] is List[int]:
80-
return _word_normal_forms(kb)
81-
if kwargs["Word"] is str:
82-
return _str_normal_forms(kb)
83-
84-
val = kwargs["Word"]
85-
val = f'"{val}"' if isinstance(val, str) else val
86-
87-
raise TypeError(
88-
'expected the value of the keyword argument "Word" to be '
89-
f"List[int] or str, but found {val}"
90-
)
91152

92153

93154
# The next function (class_by_index) is documented here not in the cpp
94155
# file because we add the additional kwarg Word.
95-
def class_by_index(kb: ToddCoxeter, **kwargs) -> Iterator[str | List[int]]:
156+
@_template_params_as_kwargs(
157+
Word={str: _str_class_by_index, List[int]: _word_class_by_index}
158+
)
159+
def class_by_index(kb: ToddCoxeter, **kwargs) -> Iterator[str | List[int]]: # pylint: disable=unused-argument
96160
"""
97161
Returns an iterator yielding every word ``List[int]`` or ``str`` in the
98162
congruence class with given index.
@@ -121,24 +185,6 @@ class with index *n* in the congruence represented by the :any:`ToddCoxeter`
121185
argument is present, or is present but has value other than ``str`` or
122186
``List[int]``.
123187
"""
124-
if len(kwargs) != 1:
125-
raise TypeError(f"expected 1 keyword argument, but found {len(kwargs)}")
126-
if "Word" not in kwargs:
127-
raise TypeError(
128-
f'expected keyword argument "Word", but found "{next(iter(kwargs))}"'
129-
)
130-
if kwargs["Word"] is List[int]:
131-
return _word_class_by_index(kb)
132-
if kwargs["Word"] is str:
133-
return _str_class_by_index(kb)
134-
135-
val = kwargs["Word"]
136-
val = f'"{val}"' if isinstance(val, str) else val
137-
138-
raise TypeError(
139-
'expected the value of the keyword argument "Word" to be '
140-
f"List[int] or str, but found {val}"
141-
)
142188

143189

144190
# def fancy_dot(tc: ToddCoxeter) -> _Dot:

src/todd-coxeter.cpp

Lines changed: 8 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,59 +1720,16 @@ word graph is complete, and so the return value is never :any:`UNDEFINED`.
17201720
)pbdoc");
17211721

17221722
// FIXME(0) this causes a full enumeration
1723-
thing.def(
1724-
"current_word_of",
1725-
[](ToddCoxeter& self, size_t i) {
1726-
return todd_coxeter::current_word_of<word_type>(self, i);
1727-
},
1728-
py::arg("i"),
1729-
R"pbdoc(
1730-
Returns a current word representing a class with given index.
1731-
1732-
This function returns the current word representing the class with index *i*.
1733-
No enumeration is triggered by calls to this function, but
1734-
:any:`current_word_graph` is standardized (using :any:`Order.shortlex`) if it
1735-
is not already standardized. The output word is obtained by following a path in
1736-
:any:`current_spanning_tree` from the node corresponding to index *i* back to
1737-
the root of that tree.
1738-
1739-
:returns: The word representing the *i*-th class.
1740-
:rtype: List[int]
1741-
1742-
:param i: the index of the class.
1743-
:type i: int
1744-
1745-
:raises LibsemigroupsError: if *i* is out of bounds.
1746-
)pbdoc");
1747-
1748-
// TODO(0) current_word_of with kwarg Word as per normal_forms
1749-
thing.def(
1750-
"current_str_of",
1751-
[](ToddCoxeter& self, size_t i) {
1752-
return todd_coxeter::current_word_of<std::string>(self, i);
1753-
},
1754-
py::arg("i"),
1755-
R"pbdoc(
1756-
Returns a current word representing a class with given index.
1757-
1758-
This function returns the current word representing the class with index *i*.
1759-
No enumeration is triggered by calls to this function, but
1760-
:any:`current_word_graph` is standardized (using :any:`Order.shortlex`) if it
1761-
is not already standardized. The output word is obtained by following a path in
1762-
:any:`current_spanning_tree` from the node corresponding to index *i* back to
1763-
the root of that tree.
1764-
1765-
:returns: The word representing the *i*-th class.
1766-
:rtype: str
1767-
1768-
:param i: the index of the class.
1769-
:type i: int
1723+
thing.def("_current_word_of", [](ToddCoxeter& self, size_t i) {
1724+
return todd_coxeter::current_word_of<word_type>(self, i);
1725+
});
17701726

1771-
:raises LibsemigroupsError: if *i* is out of bounds.
1772-
)pbdoc");
1727+
thing.def("_current_str_of", [](ToddCoxeter& self, size_t i) {
1728+
return todd_coxeter::current_word_of<std::string>(self, i);
1729+
});
17731730

17741731
thing.def(
1775-
"word_of",
1732+
"_word_of",
17761733
[](ToddCoxeter& self, size_t i) {
17771734
return todd_coxeter::word_of(self, i);
17781735
},
@@ -1794,7 +1751,7 @@ to index *i* back to the root of that tree.
17941751
:raises LibsemigroupsError: if *i* is out of bounds.
17951752
)pbdoc");
17961753
thing.def(
1797-
"str_of",
1754+
"_str_of",
17981755
[](ToddCoxeter& self, size_t i) {
17991756
return todd_coxeter::word_of<std::string>(self, i);
18001757
},

tests/test_todd_coxeter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def test_attributes():
7676
assert not tc.contains([0, 0, 0], [0, 0])
7777
assert tc.currently_contains([0, 0, 0], [0, 0]) == tril.false
7878
assert tc.kind() == congruence_kind.onesided
79-
assert tc.word_of(1) == [0, 0]
79+
assert tc.word_of(1, Word=List[int]) == [0, 0]
8080
assert tc.index_of([0, 0]) == 1
8181
assert tc.number_of_generating_pairs() == 0
8282
assert tc.generating_pairs() == []

0 commit comments

Comments
 (0)