passagemath-groups 10.6.45__cp314-cp314-macosx_13_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_groups/.dylibs/libgap.10.dylib +0 -0
- passagemath_groups/.dylibs/libgmp.10.dylib +0 -0
- passagemath_groups/.dylibs/libreadline.8.2.dylib +0 -0
- passagemath_groups/.dylibs/libz.1.3.1.dylib +0 -0
- passagemath_groups/__init__.py +3 -0
- passagemath_groups-10.6.45.dist-info/METADATA +113 -0
- passagemath_groups-10.6.45.dist-info/RECORD +40 -0
- passagemath_groups-10.6.45.dist-info/WHEEL +6 -0
- passagemath_groups-10.6.45.dist-info/top_level.txt +3 -0
- sage/all__sagemath_groups.py +21 -0
- sage/geometry/all__sagemath_groups.py +1 -0
- sage/geometry/palp_normal_form.cpython-314-darwin.so +0 -0
- sage/geometry/palp_normal_form.pyx +401 -0
- sage/groups/abelian_gps/all.py +25 -0
- sage/groups/all.py +5 -0
- sage/groups/all__sagemath_groups.py +32 -0
- sage/groups/artin.py +1074 -0
- sage/groups/braid.py +3806 -0
- sage/groups/cactus_group.py +1001 -0
- sage/groups/cubic_braid.py +2052 -0
- sage/groups/finitely_presented.py +1896 -0
- sage/groups/finitely_presented_catalog.py +27 -0
- sage/groups/finitely_presented_named.py +592 -0
- sage/groups/fqf_orthogonal.py +579 -0
- sage/groups/free_group.py +944 -0
- sage/groups/group_exp.py +360 -0
- sage/groups/group_semidirect_product.py +504 -0
- sage/groups/kernel_subgroup.py +231 -0
- sage/groups/lie_gps/all.py +1 -0
- sage/groups/lie_gps/catalog.py +8 -0
- sage/groups/lie_gps/nilpotent_lie_group.py +945 -0
- sage/groups/misc_gps/all.py +1 -0
- sage/groups/misc_gps/misc_groups.py +11 -0
- sage/groups/misc_gps/misc_groups_catalog.py +33 -0
- sage/groups/raag.py +866 -0
- sage/groups/semimonomial_transformations/all.py +1 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation.cpython-314-darwin.so +0 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation.pxd +9 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation.pyx +346 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation_group.py +512 -0
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-groups
|
|
2
|
+
# sage.doctest: needs sage.rings.number_field
|
|
3
|
+
r"""
|
|
4
|
+
Cactus Groups
|
|
5
|
+
|
|
6
|
+
AUTHORS:
|
|
7
|
+
|
|
8
|
+
- Travis Scrimshaw (1-2023): initial version
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# ****************************************************************************
|
|
12
|
+
# Copyright (C) 2023 Travis Scrimshaw <tcscrims at gmail.com>
|
|
13
|
+
#
|
|
14
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
15
|
+
# https://www.gnu.org/licenses/
|
|
16
|
+
# ****************************************************************************
|
|
17
|
+
|
|
18
|
+
from sage.misc.cachefunc import cached_method
|
|
19
|
+
from sage.misc.lazy_attribute import lazy_attribute
|
|
20
|
+
from sage.structure.element import MultiplicativeGroupElement, parent
|
|
21
|
+
from sage.structure.unique_representation import UniqueRepresentation
|
|
22
|
+
from sage.structure.richcmp import richcmp
|
|
23
|
+
from sage.matrix.constructor import matrix
|
|
24
|
+
from sage.categories.groups import Groups
|
|
25
|
+
from sage.groups.group import Group
|
|
26
|
+
from sage.groups.kernel_subgroup import KernelSubgroup
|
|
27
|
+
from sage.combinat.permutation import Permutations
|
|
28
|
+
from sage.sets.family import Family
|
|
29
|
+
from sage.graphs.graph import Graph
|
|
30
|
+
from itertools import combinations
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CactusGroup(UniqueRepresentation, Group):
|
|
34
|
+
r"""
|
|
35
|
+
The cactus group.
|
|
36
|
+
|
|
37
|
+
The `n`-fruit cactus group `J_n` is the group generated by `s_{pq}`
|
|
38
|
+
for `1 \leq p < q \leq n` with relations:
|
|
39
|
+
|
|
40
|
+
- `s_{pq}^2 = 1`
|
|
41
|
+
- `s_{pq} s_{kl} = s_{kl} s_{pq}` if the intervals `[p, q]` and `[k, l]`
|
|
42
|
+
are disjoint, and
|
|
43
|
+
- `s_{pq} s_{kl} = s_{p+q-l,p+q-k} s_{pq}` if `[k, l] \subseteq [p, q]`.
|
|
44
|
+
|
|
45
|
+
INPUT:
|
|
46
|
+
|
|
47
|
+
- ``n`` -- integer
|
|
48
|
+
|
|
49
|
+
EXAMPLES:
|
|
50
|
+
|
|
51
|
+
We construct the cactus group `J_3` and do some basic computations::
|
|
52
|
+
|
|
53
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
54
|
+
sage: s12,s13,s23 = J3.group_generators()
|
|
55
|
+
sage: s12 * s13
|
|
56
|
+
s[1,2]*s[1,3]
|
|
57
|
+
sage: x = s12 * s23; x
|
|
58
|
+
s[1,2]*s[2,3]
|
|
59
|
+
sage: x^4
|
|
60
|
+
s[1,2]*s[2,3]*s[1,2]*s[2,3]*s[1,2]*s[2,3]*s[1,2]*s[2,3]
|
|
61
|
+
sage: s12 * s13 == s13 * s23
|
|
62
|
+
True
|
|
63
|
+
|
|
64
|
+
We verify the key equality in Lemma 2.3 in [White2015]_, which shows
|
|
65
|
+
that `J_5` is generated by `s_{1q}`::
|
|
66
|
+
|
|
67
|
+
sage: J5 = groups.misc.Cactus(5)
|
|
68
|
+
sage: gens = J5.group_generators()
|
|
69
|
+
sage: all(gens[(p,q)] == gens[(1,q)] * gens[(1,q-p+1)] * gens[(1,q)]
|
|
70
|
+
....: for p in range(1, 6) for q in range(p+1, 6))
|
|
71
|
+
True
|
|
72
|
+
"""
|
|
73
|
+
def __init__(self, n):
|
|
74
|
+
r"""
|
|
75
|
+
Initialize ``self``.
|
|
76
|
+
|
|
77
|
+
TESTS::
|
|
78
|
+
|
|
79
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
80
|
+
sage: it = iter(J3)
|
|
81
|
+
sage: elts = [next(it) for _ in range(100)]
|
|
82
|
+
sage: TestSuite(J3).run(elements=elts[::7], skip='_test_enumerated_set_contains')
|
|
83
|
+
|
|
84
|
+
We run this test separately because the words grow very long, very
|
|
85
|
+
quickly. This means the code needs to check a lot of normalizations,
|
|
86
|
+
so we limit the number of tests::
|
|
87
|
+
|
|
88
|
+
sage: J3._test_enumerated_set_contains(max_runs=500)
|
|
89
|
+
|
|
90
|
+
::
|
|
91
|
+
|
|
92
|
+
sage: J4 = groups.misc.Cactus(4)
|
|
93
|
+
sage: it = iter(J4)
|
|
94
|
+
sage: elts = [next(it) for _ in range(100)]
|
|
95
|
+
sage: TestSuite(J4).run(elements=elts[::8])
|
|
96
|
+
"""
|
|
97
|
+
self._n = n
|
|
98
|
+
ell = len(str(n))
|
|
99
|
+
names = ['s{}{}'.format('0' * (ell - len(str(i))) + str(i),
|
|
100
|
+
'0' * (ell - len(str(j))) + str(j))
|
|
101
|
+
for i in range(1, self._n + 1)
|
|
102
|
+
for j in range(i + 1, self._n + 1)]
|
|
103
|
+
cat = Groups().FinitelyGeneratedAsMagma()
|
|
104
|
+
if n > 2:
|
|
105
|
+
cat = cat.Infinite()
|
|
106
|
+
else:
|
|
107
|
+
cat = cat.Finite()
|
|
108
|
+
Group.__init__(self, category=cat)
|
|
109
|
+
# Group.__init__ doesn't take a "names" parameter
|
|
110
|
+
self._assign_names(names)
|
|
111
|
+
|
|
112
|
+
@lazy_attribute
|
|
113
|
+
def _WG(self):
|
|
114
|
+
r"""
|
|
115
|
+
Get the graph for the right-angled Coxeter group that ``self``
|
|
116
|
+
embeds into (set theoretically) and set the ``_subsets`` and
|
|
117
|
+
``_subsets_inv`` attributes.
|
|
118
|
+
|
|
119
|
+
We do this initialization lazily in order to make the creation of
|
|
120
|
+
the parent quick. However, this is used to normalize the product
|
|
121
|
+
of elements.
|
|
122
|
+
|
|
123
|
+
EXAMPLES::
|
|
124
|
+
|
|
125
|
+
sage: J4 = groups.misc.Cactus(4)
|
|
126
|
+
sage: J4._WG
|
|
127
|
+
Graph on 11 vertices
|
|
128
|
+
sage: J4._subsets
|
|
129
|
+
[frozenset({1, 2}), frozenset({1, 3}), frozenset({1, 4}), frozenset({2, 3}),
|
|
130
|
+
frozenset({2, 4}), frozenset({3, 4}), frozenset({1, 2, 3}), frozenset({1, 2, 4}),
|
|
131
|
+
frozenset({1, 3, 4}), frozenset({2, 3, 4}), frozenset({1, 2, 3, 4})]
|
|
132
|
+
"""
|
|
133
|
+
n = self._n
|
|
134
|
+
I = list(range(1, n+1))
|
|
135
|
+
PS = sum(([frozenset(A) for A in combinations(I, k)] for k in range(2,n+1)), [])
|
|
136
|
+
G = Graph([list(range(len(PS))),
|
|
137
|
+
[[i,j,-1] for j in range(1, len(PS)) for i in range(j)
|
|
138
|
+
if PS[i] & PS[j] not in [frozenset(), PS[i], PS[j]]]
|
|
139
|
+
], format='vertices_and_edges')
|
|
140
|
+
self._subsets = PS
|
|
141
|
+
self._subsets_inv = {X: i for i,X in enumerate(PS)}
|
|
142
|
+
return G
|
|
143
|
+
|
|
144
|
+
def right_angled_coxeter_group(self):
|
|
145
|
+
r"""
|
|
146
|
+
Return the right-angled Coxeter group that ``self``
|
|
147
|
+
(set-theoretically) embeds into.
|
|
148
|
+
|
|
149
|
+
This is defined following [Most2019]_, where it was called the
|
|
150
|
+
diagram group. It has generators (of order `2`) indexed by subsets
|
|
151
|
+
of `\{1, \ldots, n\}` that commute if and only if one subset is
|
|
152
|
+
contained in the other or they are disjoint. For the pure cactus
|
|
153
|
+
group, this is also a group homomorphism, otherwise it is a
|
|
154
|
+
group 1-cocycle [BCL2022]_.
|
|
155
|
+
|
|
156
|
+
EXAMPLES::
|
|
157
|
+
|
|
158
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
159
|
+
sage: J3.right_angled_coxeter_group()
|
|
160
|
+
Coxeter group over Rational Field with Coxeter matrix:
|
|
161
|
+
[ 1 -1 -1 2]
|
|
162
|
+
[-1 1 -1 2]
|
|
163
|
+
[-1 -1 1 2]
|
|
164
|
+
[ 2 2 2 1]
|
|
165
|
+
"""
|
|
166
|
+
from sage.rings.rational_field import QQ
|
|
167
|
+
from sage.combinat.root_system.coxeter_group import CoxeterGroup
|
|
168
|
+
from sage.combinat.root_system.coxeter_matrix import CoxeterMatrix
|
|
169
|
+
return CoxeterGroup(CoxeterMatrix(self._WG), base_ring=QQ)
|
|
170
|
+
|
|
171
|
+
def _repr_(self):
|
|
172
|
+
"""
|
|
173
|
+
Return a string representation of ``self``.
|
|
174
|
+
|
|
175
|
+
EXAMPLES::
|
|
176
|
+
|
|
177
|
+
sage: groups.misc.Cactus(3)
|
|
178
|
+
Cactus Group with 3 fruit
|
|
179
|
+
"""
|
|
180
|
+
return "Cactus Group with {} fruit".format(self._n)
|
|
181
|
+
|
|
182
|
+
def _latex_(self):
|
|
183
|
+
r"""
|
|
184
|
+
Return a latex representation of ``self``.
|
|
185
|
+
|
|
186
|
+
EXAMPLES::
|
|
187
|
+
|
|
188
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
189
|
+
sage: latex(J3)
|
|
190
|
+
J_{3}
|
|
191
|
+
"""
|
|
192
|
+
return "J_{{{}}}".format(self._n)
|
|
193
|
+
|
|
194
|
+
def n(self):
|
|
195
|
+
"""
|
|
196
|
+
Return the value `n`.
|
|
197
|
+
|
|
198
|
+
EXAMPLES::
|
|
199
|
+
|
|
200
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
201
|
+
sage: J3.n()
|
|
202
|
+
3
|
|
203
|
+
"""
|
|
204
|
+
return self._n
|
|
205
|
+
|
|
206
|
+
@cached_method
|
|
207
|
+
def group_generators(self):
|
|
208
|
+
"""
|
|
209
|
+
Return the group generators of ``self``.
|
|
210
|
+
|
|
211
|
+
EXAMPLES::
|
|
212
|
+
|
|
213
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
214
|
+
sage: J3.group_generators()
|
|
215
|
+
Finite family {(1, 2): s[1,2], (1, 3): s[1,3], (2, 3): s[2,3]}
|
|
216
|
+
"""
|
|
217
|
+
l = [(i, j) for i in range(1, self._n + 1)
|
|
218
|
+
for j in range(i + 1, self._n + 1)]
|
|
219
|
+
return Family(l, lambda x: self.element_class(self, [x]))
|
|
220
|
+
|
|
221
|
+
@cached_method
|
|
222
|
+
def gens(self) -> tuple:
|
|
223
|
+
"""
|
|
224
|
+
Return the generators of ``self`` as a tuple.
|
|
225
|
+
|
|
226
|
+
EXAMPLES::
|
|
227
|
+
|
|
228
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
229
|
+
sage: J3.gens()
|
|
230
|
+
(s[1,2], s[1,3], s[2,3])
|
|
231
|
+
"""
|
|
232
|
+
return tuple(self.group_generators())
|
|
233
|
+
|
|
234
|
+
def gen(self, i, j=None):
|
|
235
|
+
r"""
|
|
236
|
+
Return the `i`-th generator of ``self`` or the generator `s_{ij}`.
|
|
237
|
+
|
|
238
|
+
EXAMPLES::
|
|
239
|
+
|
|
240
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
241
|
+
sage: J3.gen(1)
|
|
242
|
+
s[1,3]
|
|
243
|
+
sage: J3.gen(1,2)
|
|
244
|
+
s[1,2]
|
|
245
|
+
sage: J3.gen(0,2)
|
|
246
|
+
Traceback (most recent call last):
|
|
247
|
+
...
|
|
248
|
+
ValueError: s[0,2] is not a valid generator
|
|
249
|
+
sage: J3.gen(1,4)
|
|
250
|
+
Traceback (most recent call last):
|
|
251
|
+
...
|
|
252
|
+
ValueError: s[1,4] is not a valid generator
|
|
253
|
+
sage: J3.gen(2,1)
|
|
254
|
+
Traceback (most recent call last):
|
|
255
|
+
...
|
|
256
|
+
ValueError: s[2,1] is not a valid generator
|
|
257
|
+
"""
|
|
258
|
+
if j is None:
|
|
259
|
+
return self.gens()[i]
|
|
260
|
+
if not (1 <= i < j <= self._n):
|
|
261
|
+
raise ValueError(f"s[{i},{j}] is not a valid generator")
|
|
262
|
+
return self.element_class(self, [(i,j)])
|
|
263
|
+
|
|
264
|
+
@cached_method
|
|
265
|
+
def one(self):
|
|
266
|
+
r"""
|
|
267
|
+
Return the identity element in ``self``.
|
|
268
|
+
|
|
269
|
+
EXAMPLES::
|
|
270
|
+
|
|
271
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
272
|
+
sage: J3.one()
|
|
273
|
+
1
|
|
274
|
+
"""
|
|
275
|
+
return self.element_class(self, [])
|
|
276
|
+
|
|
277
|
+
def _coerce_map_from_(self, G):
|
|
278
|
+
r"""
|
|
279
|
+
Return if there is a coerce map from ``G``.
|
|
280
|
+
|
|
281
|
+
EXAMPLES::
|
|
282
|
+
|
|
283
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
284
|
+
sage: J5 = groups.misc.Cactus(5)
|
|
285
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
286
|
+
sage: PJ5 = groups.misc.PureCactus(5)
|
|
287
|
+
|
|
288
|
+
sage: J3._coerce_map_from_(J5)
|
|
289
|
+
False
|
|
290
|
+
sage: J5._coerce_map_from_(J3)
|
|
291
|
+
True
|
|
292
|
+
sage: J3._coerce_map_from_(PJ3)
|
|
293
|
+
True
|
|
294
|
+
sage: J3._coerce_map_from_(PJ5)
|
|
295
|
+
False
|
|
296
|
+
sage: J5._coerce_map_from_(PJ3)
|
|
297
|
+
True
|
|
298
|
+
sage: J5._coerce_map_from_(PJ5)
|
|
299
|
+
True
|
|
300
|
+
"""
|
|
301
|
+
if isinstance(G, CactusGroup):
|
|
302
|
+
return G._n <= self._n
|
|
303
|
+
elif isinstance(G, PureCactusGroup):
|
|
304
|
+
return G.n() <= self._n
|
|
305
|
+
return super()._coerce_map_from_(G)
|
|
306
|
+
|
|
307
|
+
def _element_constructor_(self, x):
|
|
308
|
+
r"""
|
|
309
|
+
Construct an element of ``self`` from ``x``.
|
|
310
|
+
|
|
311
|
+
EXAMPLES::
|
|
312
|
+
|
|
313
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
314
|
+
sage: J5 = groups.misc.Cactus(5)
|
|
315
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
316
|
+
|
|
317
|
+
sage: J5(J3.an_element())
|
|
318
|
+
s[1,2]*s[2,3]*s[1,3]
|
|
319
|
+
sage: J3(J5.an_element())
|
|
320
|
+
s[1,2]*s[2,3]*s[1,3]
|
|
321
|
+
sage: it = iter(PJ3)
|
|
322
|
+
sage: [J3(next(it)) for _ in range(3)]
|
|
323
|
+
[1, s[1,2]*s[2,3]*s[1,2]*s[1,3], s[2,3]*s[1,2]*s[2,3]*s[1,3]]
|
|
324
|
+
|
|
325
|
+
sage: J3([[1,2], [1,3], [2,3]])
|
|
326
|
+
s[1,3]
|
|
327
|
+
"""
|
|
328
|
+
if parent(x) is self:
|
|
329
|
+
return x
|
|
330
|
+
if isinstance(x, PureCactusGroup.Element):
|
|
331
|
+
x = x.value
|
|
332
|
+
if isinstance(x, CactusGroup.Element):
|
|
333
|
+
if any(d[1] > self._n for d in x._data):
|
|
334
|
+
raise ValueError(f"{x} is not an element of {self}")
|
|
335
|
+
return self.element_class(self, x._data)
|
|
336
|
+
ret = self.element_class(self, x)
|
|
337
|
+
ret._normalize()
|
|
338
|
+
return ret
|
|
339
|
+
|
|
340
|
+
def _an_element_(self):
|
|
341
|
+
r"""
|
|
342
|
+
Return an element of ``self``.
|
|
343
|
+
|
|
344
|
+
TESTS::
|
|
345
|
+
|
|
346
|
+
sage: J1 = groups.misc.Cactus(1)
|
|
347
|
+
sage: J1._an_element_()
|
|
348
|
+
1
|
|
349
|
+
sage: J2 = groups.misc.Cactus(2)
|
|
350
|
+
sage: J2._an_element_()
|
|
351
|
+
s[1,2]
|
|
352
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
353
|
+
sage: x = J3._an_element_(); x
|
|
354
|
+
s[1,2]*s[2,3]*s[1,3]
|
|
355
|
+
|
|
356
|
+
sage: x._normalize()
|
|
357
|
+
sage: x
|
|
358
|
+
s[1,2]*s[2,3]*s[1,3]
|
|
359
|
+
"""
|
|
360
|
+
if self._n <= 1:
|
|
361
|
+
return self.one()
|
|
362
|
+
if self._n == 2:
|
|
363
|
+
return self.element_class(self, [(1, 2)])
|
|
364
|
+
return self.element_class(self, [(1, 2), (2, 3), (1, 3)])
|
|
365
|
+
|
|
366
|
+
def random_element(self, max_length=10):
|
|
367
|
+
r"""
|
|
368
|
+
Return a random element of ``self`` of length at most ``max_length``.
|
|
369
|
+
|
|
370
|
+
EXAMPLES::
|
|
371
|
+
|
|
372
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
373
|
+
sage: J3.random_element() # random
|
|
374
|
+
s[1,2]*s[2,3]*s[1,2]*s[1,3]
|
|
375
|
+
"""
|
|
376
|
+
from sage.misc.prandom import randint
|
|
377
|
+
l = randint(0, max_length)
|
|
378
|
+
gens = list(self.group_generators())
|
|
379
|
+
ret = self.one()
|
|
380
|
+
for _ in range(l):
|
|
381
|
+
ret *= gens[randint(0, len(gens) - 1)]
|
|
382
|
+
return ret
|
|
383
|
+
|
|
384
|
+
def bilinear_form(self, t=None):
|
|
385
|
+
r"""
|
|
386
|
+
Return the ``t``-bilinear form of ``self``.
|
|
387
|
+
|
|
388
|
+
We define a bilinear form `B` on the group algebra by
|
|
389
|
+
|
|
390
|
+
.. MATH::
|
|
391
|
+
|
|
392
|
+
B(s_{ij}, s_{pq}) = \begin{cases}
|
|
393
|
+
1 & \text{if } i = p, j = q, \\
|
|
394
|
+
-t & \text{if } [i, j] \not\subseteq [p, q] \text{ and }
|
|
395
|
+
[p, q] \not\subseteq [i, j], \\
|
|
396
|
+
0 & \text{otherwise}.
|
|
397
|
+
\end{cases}
|
|
398
|
+
|
|
399
|
+
In other words, it is `1` if `s_{ij} = s_{pq}`, `-t` if `s_{ij}`
|
|
400
|
+
and `s_{pq}` generate a free group, and `0` otherwise (they commute
|
|
401
|
+
or almost commute).
|
|
402
|
+
|
|
403
|
+
INPUT:
|
|
404
|
+
|
|
405
|
+
- ``t`` -- (default: `t` in `\ZZ[t]`) the variable `t`
|
|
406
|
+
|
|
407
|
+
EXAMPLES::
|
|
408
|
+
|
|
409
|
+
sage: J = groups.misc.Cactus(4)
|
|
410
|
+
sage: B = J.bilinear_form()
|
|
411
|
+
sage: B
|
|
412
|
+
[ 1 0 0 -t -t 0]
|
|
413
|
+
[ 0 1 0 0 -t -t]
|
|
414
|
+
[ 0 0 1 0 0 0]
|
|
415
|
+
[-t 0 0 1 0 -t]
|
|
416
|
+
[-t -t 0 0 1 0]
|
|
417
|
+
[ 0 -t 0 -t 0 1]
|
|
418
|
+
|
|
419
|
+
We reorder the generators so the bilinear form is more
|
|
420
|
+
"Coxeter-like". In particular, when we remove the generator
|
|
421
|
+
`s_{1,4}`, we recover the bilinear form in Example 6.2.5
|
|
422
|
+
of [DJS2003]_::
|
|
423
|
+
|
|
424
|
+
sage: J.gens()
|
|
425
|
+
(s[1,2], s[1,3], s[1,4], s[2,3], s[2,4], s[3,4])
|
|
426
|
+
sage: S = SymmetricGroup(6)
|
|
427
|
+
sage: g = S([1,4,6,2,5,3])
|
|
428
|
+
sage: B.permute_rows_and_columns(g, g)
|
|
429
|
+
sage: B
|
|
430
|
+
[ 1 -t 0 0 -t 0]
|
|
431
|
+
[-t 1 -t 0 0 0]
|
|
432
|
+
[ 0 -t 1 -t 0 0]
|
|
433
|
+
[ 0 0 -t 1 -t 0]
|
|
434
|
+
[-t 0 0 -t 1 0]
|
|
435
|
+
[ 0 0 0 0 0 1]
|
|
436
|
+
"""
|
|
437
|
+
if t is None:
|
|
438
|
+
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
|
|
439
|
+
from sage.rings.integer_ring import ZZ
|
|
440
|
+
t = PolynomialRing(ZZ, 't').gen()
|
|
441
|
+
R = t.parent()
|
|
442
|
+
ret = []
|
|
443
|
+
K = self.group_generators().keys()
|
|
444
|
+
for x in K:
|
|
445
|
+
for y in K:
|
|
446
|
+
if x is y:
|
|
447
|
+
ret.append(R.one())
|
|
448
|
+
elif (x[1] < y[0] or x[0] > y[1] or # Disjoint
|
|
449
|
+
(x[0] <= y[0] and y[1] <= x[1]) or # y <= x
|
|
450
|
+
(y[0] <= x[0] and x[1] <= y[1])): # x <= y
|
|
451
|
+
ret.append(R.zero())
|
|
452
|
+
else:
|
|
453
|
+
ret.append(-t)
|
|
454
|
+
return matrix(R, len(K), len(K), ret)
|
|
455
|
+
|
|
456
|
+
def geometric_representation_generators(self, t=None):
|
|
457
|
+
r"""
|
|
458
|
+
Return the matrices corresponding to the generators of ``self``.
|
|
459
|
+
|
|
460
|
+
We construct a representation over `R = \ZZ[t]` of `J_n` as follows.
|
|
461
|
+
Let `E` be the vector space over `R` spanned by `\{\epsilon_v\}_v`,
|
|
462
|
+
where `v` is a generator of `J_n`. Fix some generator `v`, and
|
|
463
|
+
let `E_v` denote the span of `\epsilon_u - \epsilon_{u'}`,
|
|
464
|
+
where `u'` is the reflected interval of `u` in `v`, over all
|
|
465
|
+
`u` such that `u \subset v`. Let `F_v` denote the orthogonal
|
|
466
|
+
complement of `R \epsilon_v \oplus E_v` with respect to the
|
|
467
|
+
:meth:`bilinear form <bilinear_form>` `B`. We define the action
|
|
468
|
+
of `v` on `E` by
|
|
469
|
+
|
|
470
|
+
.. MATH::
|
|
471
|
+
|
|
472
|
+
\rho(v) = -I |_{R\epsilon_v \oplus E_v} \oplus I |_{F_v}.
|
|
473
|
+
|
|
474
|
+
By Theorem 6.2.3 of [DJS2003]_, this defines a representation of `J_n`.
|
|
475
|
+
It is expected that this is a faithful representation (see
|
|
476
|
+
Remark 6.2.4 of [DJS2003]_). As this arises from a blow-up and an
|
|
477
|
+
analog of the geometric representation of the corresponding
|
|
478
|
+
Coxeter group (the symmetric group), we call this the
|
|
479
|
+
*geometric representation*.
|
|
480
|
+
|
|
481
|
+
INPUT:
|
|
482
|
+
|
|
483
|
+
- ``t`` -- (default: `t` in `\ZZ[t]`) the variable `t`
|
|
484
|
+
|
|
485
|
+
EXAMPLES::
|
|
486
|
+
|
|
487
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
488
|
+
sage: list(J3.geometric_representation_generators())
|
|
489
|
+
[
|
|
490
|
+
[ -1 0 2*t] [ 0 0 1] [ 1 0 0]
|
|
491
|
+
[ 0 1 0] [ 0 -1 0] [ 0 1 0]
|
|
492
|
+
[ 0 0 1], [ 1 0 0], [2*t 0 -1]
|
|
493
|
+
]
|
|
494
|
+
|
|
495
|
+
We ran the following code with ``max_tests = 15000`` and did
|
|
496
|
+
not find a counterexample to the faithfulness of this representation::
|
|
497
|
+
|
|
498
|
+
sage: visited = set([J3.one()])
|
|
499
|
+
sage: cur = set([(J3.one(), J3.one().to_matrix())])
|
|
500
|
+
sage: mats = set([J3.one().to_matrix()])
|
|
501
|
+
sage: RG = list(J3.geometric_representation_generators())
|
|
502
|
+
sage: count = 0
|
|
503
|
+
sage: max_tests = 1000
|
|
504
|
+
sage: while cur:
|
|
505
|
+
....: count += 1
|
|
506
|
+
....: if count >= max_tests:
|
|
507
|
+
....: break
|
|
508
|
+
....: elt, mat = cur.pop()
|
|
509
|
+
....: for g,r in zip(J3, RG):
|
|
510
|
+
....: val = elt * g
|
|
511
|
+
....: if val in visited:
|
|
512
|
+
....: continue
|
|
513
|
+
....: visited.add(val)
|
|
514
|
+
....: matp = mat * r
|
|
515
|
+
....: matp.set_immutable()
|
|
516
|
+
....: assert matp not in mats, f"not injective {val} \n{matp}"
|
|
517
|
+
....: mats.add(matp)
|
|
518
|
+
....: cur.add((val, matp))
|
|
519
|
+
"""
|
|
520
|
+
B = self.bilinear_form(t)
|
|
521
|
+
F = B.base_ring().fraction_field()
|
|
522
|
+
K = self.group_generators().keys()
|
|
523
|
+
from sage.modules.free_module import FreeModule
|
|
524
|
+
V = FreeModule(F, len(K))
|
|
525
|
+
basis = V.basis()
|
|
526
|
+
ret = {}
|
|
527
|
+
for ik, k in enumerate(K):
|
|
528
|
+
E = [basis[ik]]
|
|
529
|
+
ME = matrix(E)
|
|
530
|
+
# The only non-trivial elements are those reflected by the interval k
|
|
531
|
+
for low in range(k[0], k[1] + 1):
|
|
532
|
+
for high in range(low + 1, k[1] + 1):
|
|
533
|
+
v = (low, high)
|
|
534
|
+
vp = (k[0] + k[1] - high, k[0] + k[1] - low)
|
|
535
|
+
if v == vp:
|
|
536
|
+
continue
|
|
537
|
+
elt = basis[K.index(v)] - basis[K.index(vp)]
|
|
538
|
+
if elt not in ME.row_space():
|
|
539
|
+
E.append(elt)
|
|
540
|
+
ME = ME.stack(elt)
|
|
541
|
+
# Get the orthogonal complement wrt to the bilinear form B
|
|
542
|
+
Fv = (ME * B).right_kernel().basis_matrix()
|
|
543
|
+
T = ME.stack(Fv).transpose()
|
|
544
|
+
rho = matrix.diagonal(F, [-1] * len(E) + [1] * (len(K) - len(E)))
|
|
545
|
+
ret[k] = T * rho * ~T
|
|
546
|
+
return Family(K, lambda k: ret[k])
|
|
547
|
+
|
|
548
|
+
class Element(MultiplicativeGroupElement):
|
|
549
|
+
"""
|
|
550
|
+
An element of a cactus group.
|
|
551
|
+
"""
|
|
552
|
+
def __init__(self, parent, data):
|
|
553
|
+
"""
|
|
554
|
+
Initialize ``self``.
|
|
555
|
+
|
|
556
|
+
EXAMPLES::
|
|
557
|
+
|
|
558
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
559
|
+
sage: elt = J3.an_element()
|
|
560
|
+
sage: TestSuite(elt).run()
|
|
561
|
+
"""
|
|
562
|
+
self._data = tuple(data)
|
|
563
|
+
MultiplicativeGroupElement.__init__(self, parent)
|
|
564
|
+
|
|
565
|
+
def _repr_(self):
|
|
566
|
+
"""
|
|
567
|
+
Return a string representation of ``self``.
|
|
568
|
+
|
|
569
|
+
EXAMPLES::
|
|
570
|
+
|
|
571
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
572
|
+
sage: J3.one()
|
|
573
|
+
1
|
|
574
|
+
sage: J3.an_element()
|
|
575
|
+
s[1,2]*s[2,3]*s[1,3]
|
|
576
|
+
"""
|
|
577
|
+
if not self._data:
|
|
578
|
+
return '1'
|
|
579
|
+
return '*'.join(f"s[{i},{j}]" for i,j in self._data)
|
|
580
|
+
|
|
581
|
+
def _latex_(self):
|
|
582
|
+
"""
|
|
583
|
+
Return a latex representation of ``self``.
|
|
584
|
+
|
|
585
|
+
EXAMPLES::
|
|
586
|
+
|
|
587
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
588
|
+
sage: latex(J3.one())
|
|
589
|
+
1
|
|
590
|
+
sage: latex(J3.an_element())
|
|
591
|
+
s_{1,2} s_{2,3} s_{1,3}
|
|
592
|
+
"""
|
|
593
|
+
if not self._data:
|
|
594
|
+
return '1'
|
|
595
|
+
return " ".join(f"s_{{{i},{j}}}" for i,j in self._data)
|
|
596
|
+
|
|
597
|
+
def _unicode_art_(self):
|
|
598
|
+
"""
|
|
599
|
+
Return unicode art of ``self``.
|
|
600
|
+
|
|
601
|
+
EXAMPLES::
|
|
602
|
+
|
|
603
|
+
sage: J4 = groups.misc.Cactus(4)
|
|
604
|
+
sage: s12,s13,s14,s23,s24,s34 = J4.gens()
|
|
605
|
+
sage: unicode_art(s12 * s23 * s13)
|
|
606
|
+
s₁₂ s₂₃ s₁₃
|
|
607
|
+
sage: unicode_art(J4.one())
|
|
608
|
+
1
|
|
609
|
+
sage: J12 = groups.misc.Cactus(12)
|
|
610
|
+
sage: unicode_art(J12.gen(1,3))
|
|
611
|
+
s₁,₃
|
|
612
|
+
sage: unicode_art(J12.gen(3,11))
|
|
613
|
+
s₃,₁₁
|
|
614
|
+
"""
|
|
615
|
+
from sage.typeset.unicode_art import unicode_subscript, unicode_art
|
|
616
|
+
if not self._data:
|
|
617
|
+
return unicode_art('1')
|
|
618
|
+
if self.parent()._n < 10:
|
|
619
|
+
return unicode_art(' '.join('s{}{}'.format(unicode_subscript(p), unicode_subscript(q)) for p,q in self._data))
|
|
620
|
+
return unicode_art(' '.join('s{},{}'.format(unicode_subscript(p), unicode_subscript(q)) for p,q in self._data))
|
|
621
|
+
|
|
622
|
+
def __hash__(self):
|
|
623
|
+
r"""
|
|
624
|
+
Return the hash of ``self``.
|
|
625
|
+
|
|
626
|
+
EXAMPLES::
|
|
627
|
+
|
|
628
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
629
|
+
sage: elt = J3.gen(1,2) * J3.gen(2,3) * J3.gen(1,3)
|
|
630
|
+
sage: hash(elt) == hash((((1,2), (2,3), (1,3))))
|
|
631
|
+
True
|
|
632
|
+
"""
|
|
633
|
+
return hash(self._data)
|
|
634
|
+
|
|
635
|
+
def _richcmp_(self, other, op):
|
|
636
|
+
r"""
|
|
637
|
+
Compare ``self`` and ``other``.
|
|
638
|
+
|
|
639
|
+
EXAMPLES::
|
|
640
|
+
|
|
641
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
642
|
+
sage: elt = J3.an_element()
|
|
643
|
+
sage: elt == J3.one()
|
|
644
|
+
False
|
|
645
|
+
sage: elt != J3.one()
|
|
646
|
+
True
|
|
647
|
+
sage: s12,s13,s23 = J3.gens()
|
|
648
|
+
sage: elt == s12*s23*s13
|
|
649
|
+
True
|
|
650
|
+
"""
|
|
651
|
+
return richcmp(self._data, other._data, op)
|
|
652
|
+
|
|
653
|
+
def _mul_(self, other):
|
|
654
|
+
r"""
|
|
655
|
+
Return the product of ``self`` and ``other``.
|
|
656
|
+
|
|
657
|
+
EXAMPLES::
|
|
658
|
+
|
|
659
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
660
|
+
sage: s12,s13,s23 = J3.gens()
|
|
661
|
+
sage: s12*s23
|
|
662
|
+
s[1,2]*s[2,3]
|
|
663
|
+
sage: s12*s13
|
|
664
|
+
s[1,2]*s[1,3]
|
|
665
|
+
sage: s13*s12
|
|
666
|
+
s[2,3]*s[1,3]
|
|
667
|
+
sage: J3.one() * (s13*s12*s13*s12*s23*s13)
|
|
668
|
+
s[2,3]*s[1,2]*s[2,3]*s[1,3]
|
|
669
|
+
"""
|
|
670
|
+
ret = type(self)(self.parent(), self._data + other._data)
|
|
671
|
+
ret._normalize()
|
|
672
|
+
return ret
|
|
673
|
+
|
|
674
|
+
def __invert__(self):
|
|
675
|
+
r"""
|
|
676
|
+
Return the inverse of ``self``.
|
|
677
|
+
|
|
678
|
+
EXAMPLES::
|
|
679
|
+
|
|
680
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
681
|
+
sage: s12,s13,s23 = J3.gens()
|
|
682
|
+
sage: elt = s12*s23*s13
|
|
683
|
+
sage: ~elt
|
|
684
|
+
s[1,2]*s[2,3]*s[1,3]
|
|
685
|
+
sage: elt * elt
|
|
686
|
+
1
|
|
687
|
+
"""
|
|
688
|
+
ret = type(self)(self.parent(), reversed(self._data))
|
|
689
|
+
ret._normalize()
|
|
690
|
+
return ret
|
|
691
|
+
|
|
692
|
+
def to_permutation(self):
|
|
693
|
+
r"""
|
|
694
|
+
Return the image of ``self`` under the canonical projection
|
|
695
|
+
to the permutation group.
|
|
696
|
+
|
|
697
|
+
EXAMPLES::
|
|
698
|
+
|
|
699
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
700
|
+
sage: s12,s13,s23 = J3.gens()
|
|
701
|
+
sage: s12.to_permutation()
|
|
702
|
+
[2, 1, 3]
|
|
703
|
+
sage: s23.to_permutation()
|
|
704
|
+
[1, 3, 2]
|
|
705
|
+
sage: s13.to_permutation()
|
|
706
|
+
[3, 2, 1]
|
|
707
|
+
sage: elt = s12*s23*s13
|
|
708
|
+
sage: elt.to_permutation()
|
|
709
|
+
[1, 3, 2]
|
|
710
|
+
|
|
711
|
+
sage: J7 = groups.misc.Cactus(7)
|
|
712
|
+
sage: J7.group_generators()[3,6].to_permutation()
|
|
713
|
+
[1, 2, 6, 5, 4, 3, 7]
|
|
714
|
+
|
|
715
|
+
We check that this respects the multiplication order
|
|
716
|
+
of permutations::
|
|
717
|
+
|
|
718
|
+
sage: P3 = Permutations(3)
|
|
719
|
+
sage: elt = s12*s23
|
|
720
|
+
sage: elt.to_permutation() == P3(s12) * P3(s23)
|
|
721
|
+
True
|
|
722
|
+
sage: Permutations.options.mult='r2l'
|
|
723
|
+
sage: elt.to_permutation() == P3(s12) * P3(s23)
|
|
724
|
+
True
|
|
725
|
+
sage: Permutations.options.mult='l2r'
|
|
726
|
+
"""
|
|
727
|
+
n = self.parent().n()
|
|
728
|
+
P = Permutations(n)
|
|
729
|
+
ret = P.one()
|
|
730
|
+
for x in self._data:
|
|
731
|
+
lst = list(range(1, n + 1))
|
|
732
|
+
lst[x[0] - 1:x[1]] = list(reversed(lst[x[0] - 1:x[1]]))
|
|
733
|
+
ret *= P(lst)
|
|
734
|
+
return ret
|
|
735
|
+
|
|
736
|
+
def _matrix_(self):
|
|
737
|
+
r"""
|
|
738
|
+
Return ``self`` as a matrix.
|
|
739
|
+
|
|
740
|
+
The resulting matrix is the :meth:`geometric representation
|
|
741
|
+
<sage.groups.cactus_group.CactusGroup.geometric_representation_generators>`
|
|
742
|
+
of ``self``.
|
|
743
|
+
|
|
744
|
+
EXAMPLES::
|
|
745
|
+
|
|
746
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
747
|
+
sage: s12,s13,s23 = J3.gens()
|
|
748
|
+
sage: s12.to_matrix()
|
|
749
|
+
[ -1 0 2*t]
|
|
750
|
+
[ 0 1 0]
|
|
751
|
+
[ 0 0 1]
|
|
752
|
+
sage: (s12*s13).to_matrix()
|
|
753
|
+
[2*t 0 -1]
|
|
754
|
+
[ 0 -1 0]
|
|
755
|
+
[ 1 0 0]
|
|
756
|
+
sage: (s13*s23).to_matrix()
|
|
757
|
+
[2*t 0 -1]
|
|
758
|
+
[ 0 -1 0]
|
|
759
|
+
[ 1 0 0]
|
|
760
|
+
sage: (s13*s12).to_matrix()
|
|
761
|
+
[ 0 0 1]
|
|
762
|
+
[ 0 -1 0]
|
|
763
|
+
[ -1 0 2*t]
|
|
764
|
+
sage: all(x.to_matrix() * y.to_matrix() == (x*y).to_matrix()
|
|
765
|
+
....: for x in J3.gens() for y in J3.gens())
|
|
766
|
+
True
|
|
767
|
+
"""
|
|
768
|
+
G = self.parent().geometric_representation_generators()
|
|
769
|
+
ret = G[(1,2)].parent().one()
|
|
770
|
+
for x in self._data:
|
|
771
|
+
ret *= G[x]
|
|
772
|
+
ret.set_immutable()
|
|
773
|
+
return ret
|
|
774
|
+
|
|
775
|
+
to_matrix = _matrix_
|
|
776
|
+
|
|
777
|
+
def _normalize(self):
|
|
778
|
+
r"""
|
|
779
|
+
Return the word for ``self`` in normalized form.
|
|
780
|
+
|
|
781
|
+
ALGORITHM:
|
|
782
|
+
|
|
783
|
+
We perform the normalization by using the lexicographically
|
|
784
|
+
minimum reduced word for the corresponding right-angled Coxeter
|
|
785
|
+
group (RACG) element under the (set-theoretic) embedding
|
|
786
|
+
introduced by [Most2019]_. This embedding is a group 1-cocycle
|
|
787
|
+
and also realizes the cactus group as a subgroup of `W \rtimes S_n`,
|
|
788
|
+
where `W` is the RACG (see also [Yu2022]_). See Section 2
|
|
789
|
+
of [BCL2022]_ for precise statements.
|
|
790
|
+
|
|
791
|
+
TESTS::
|
|
792
|
+
|
|
793
|
+
sage: J6 = groups.misc.Cactus(6)
|
|
794
|
+
sage: s26 = J6.gen(2,6)
|
|
795
|
+
sage: s45 = J6.gen(4,5)
|
|
796
|
+
sage: s13 = J6.gen(1,3)
|
|
797
|
+
sage: s26 * s45 * s13 == s26 * s45 * s13 # indirect doctest
|
|
798
|
+
True
|
|
799
|
+
|
|
800
|
+
sage: J4 = groups.misc.Cactus(4)
|
|
801
|
+
sage: s12,s13,s14,s23,s24,s34 = J4.gens()
|
|
802
|
+
sage: s12 * (s12 * s23)
|
|
803
|
+
s[2,3]
|
|
804
|
+
sage: (s12 * s23) * s23
|
|
805
|
+
s[1,2]
|
|
806
|
+
sage: s23 * s13 * s34
|
|
807
|
+
s[2,3]*s[1,3]*s[3,4]
|
|
808
|
+
"""
|
|
809
|
+
P = self.parent()
|
|
810
|
+
n = P._n
|
|
811
|
+
G = P._WG # The defining graph
|
|
812
|
+
|
|
813
|
+
# Convert to an element in the right-angled Coxeter group
|
|
814
|
+
perm = list(range(1,n+1))
|
|
815
|
+
word = []
|
|
816
|
+
for p,q in self._data:
|
|
817
|
+
word.append(P._subsets_inv[frozenset(perm[p-1:q])])
|
|
818
|
+
perm[p-1:q] = reversed(perm[p-1:q])
|
|
819
|
+
|
|
820
|
+
# Normalize the word
|
|
821
|
+
# This code works for any right-angled Coxeter group
|
|
822
|
+
pos = 0
|
|
823
|
+
supp = sorted(set(word))
|
|
824
|
+
while pos < len(word) - 1:
|
|
825
|
+
cur = word[pos]
|
|
826
|
+
for i in supp:
|
|
827
|
+
if i > cur:
|
|
828
|
+
break
|
|
829
|
+
if G.has_edge(cur, i):
|
|
830
|
+
continue
|
|
831
|
+
did_swap = False
|
|
832
|
+
for j in range(pos+1, len(word)):
|
|
833
|
+
if word[j] == i:
|
|
834
|
+
word.pop(j)
|
|
835
|
+
if cur == i: # canceling s_i s_i = 1
|
|
836
|
+
word.pop(pos)
|
|
837
|
+
pos = -1 # start again at the beginning
|
|
838
|
+
break
|
|
839
|
+
word.insert(pos, i)
|
|
840
|
+
did_swap = True
|
|
841
|
+
break
|
|
842
|
+
if G.has_edge(i, word[j]):
|
|
843
|
+
break
|
|
844
|
+
if did_swap:
|
|
845
|
+
break
|
|
846
|
+
pos += 1
|
|
847
|
+
|
|
848
|
+
# Convert back
|
|
849
|
+
ret = []
|
|
850
|
+
perm = list(range(1,n+1))
|
|
851
|
+
for i in word:
|
|
852
|
+
X = P._subsets[i]
|
|
853
|
+
pos = [j for j,val in enumerate(perm) if val in X]
|
|
854
|
+
for j in range(len(pos)//2):
|
|
855
|
+
perm[pos[j]], perm[pos[-j-1]] = perm[pos[-j-1]], perm[pos[j]]
|
|
856
|
+
pos.sort()
|
|
857
|
+
assert all(pos[k] + 1 == pos[k+1] for k in range(len(pos)-1))
|
|
858
|
+
ret.append((pos[0]+1, pos[-1]+1))
|
|
859
|
+
|
|
860
|
+
self._data = tuple(ret)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
class PureCactusGroup(KernelSubgroup):
|
|
864
|
+
r"""
|
|
865
|
+
The pure cactus group.
|
|
866
|
+
|
|
867
|
+
The *pure cactus group* `PJ_n` is the kernel of the natural
|
|
868
|
+
surjection of the cactus group `J_n` onto the symmetric group
|
|
869
|
+
`S_n`. In particular, we have the following (non-split) exact sequence:
|
|
870
|
+
|
|
871
|
+
.. MATH::
|
|
872
|
+
|
|
873
|
+
1 \longrightarrow PJ_n \longrightarrow J_n \longrightarrow S_n
|
|
874
|
+
\longrightarrow 1.
|
|
875
|
+
"""
|
|
876
|
+
def __init__(self, n):
|
|
877
|
+
r"""
|
|
878
|
+
Initialize ``self``.
|
|
879
|
+
|
|
880
|
+
EXAMPLES::
|
|
881
|
+
|
|
882
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
883
|
+
sage: it = iter(PJ3)
|
|
884
|
+
sage: elts = [next(it) for _ in range(10)]
|
|
885
|
+
sage: TestSuite(PJ3).run(elements=elts)
|
|
886
|
+
"""
|
|
887
|
+
J = CactusGroup(n)
|
|
888
|
+
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
|
|
889
|
+
S = SymmetricGroup(n)
|
|
890
|
+
KernelSubgroup.__init__(self, S.coerce_map_from(J))
|
|
891
|
+
|
|
892
|
+
def _repr_(self):
|
|
893
|
+
"""
|
|
894
|
+
Return a string representation of ``self``.
|
|
895
|
+
|
|
896
|
+
EXAMPLES::
|
|
897
|
+
|
|
898
|
+
sage: groups.misc.PureCactus(3)
|
|
899
|
+
Pure Cactus Group with 3 fruit
|
|
900
|
+
"""
|
|
901
|
+
return "Pure Cactus Group with {} fruit".format(self.n())
|
|
902
|
+
|
|
903
|
+
def _latex_(self):
|
|
904
|
+
r"""
|
|
905
|
+
Return a latex representation of ``self``.
|
|
906
|
+
|
|
907
|
+
EXAMPLES::
|
|
908
|
+
|
|
909
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
910
|
+
sage: latex(PJ3)
|
|
911
|
+
PJ_{3}
|
|
912
|
+
"""
|
|
913
|
+
return "PJ_{{{}}}".format(self.n())
|
|
914
|
+
|
|
915
|
+
@cached_method
|
|
916
|
+
def n(self):
|
|
917
|
+
"""
|
|
918
|
+
Return the value `n`.
|
|
919
|
+
|
|
920
|
+
EXAMPLES::
|
|
921
|
+
|
|
922
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
923
|
+
sage: PJ3.n()
|
|
924
|
+
3
|
|
925
|
+
"""
|
|
926
|
+
return self.ambient().n()
|
|
927
|
+
|
|
928
|
+
def gen(self, i):
|
|
929
|
+
r"""
|
|
930
|
+
Return the ``i``-th generator of ``self``.
|
|
931
|
+
|
|
932
|
+
EXAMPLES::
|
|
933
|
+
|
|
934
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
935
|
+
sage: PJ3.gen(0)
|
|
936
|
+
s[2,3]*s[1,2]*s[2,3]*s[1,3]
|
|
937
|
+
sage: PJ3.gen(1)
|
|
938
|
+
s[1,2]*s[2,3]*s[1,2]*s[1,3]
|
|
939
|
+
sage: PJ3.gen(5)
|
|
940
|
+
Traceback (most recent call last):
|
|
941
|
+
...
|
|
942
|
+
IndexError: tuple index out of range
|
|
943
|
+
"""
|
|
944
|
+
return self.gens()[i]
|
|
945
|
+
|
|
946
|
+
@cached_method
|
|
947
|
+
def gens(self) -> tuple:
|
|
948
|
+
r"""
|
|
949
|
+
Return the generators of ``self``.
|
|
950
|
+
|
|
951
|
+
ALGORITHM:
|
|
952
|
+
|
|
953
|
+
We use :wikipedia:`Schreier's_lemma` and compute the traversal
|
|
954
|
+
using the lex minimum elements (defined by the order of the
|
|
955
|
+
generators of the ambient cactus group).
|
|
956
|
+
|
|
957
|
+
EXAMPLES:
|
|
958
|
+
|
|
959
|
+
We verify Corollary A.2 of [BCL2022]_::
|
|
960
|
+
|
|
961
|
+
sage: PJ3 = groups.misc.PureCactus(3)
|
|
962
|
+
sage: PJ3.gens()
|
|
963
|
+
(s[2,3]*s[1,2]*s[2,3]*s[1,3], s[1,2]*s[2,3]*s[1,2]*s[1,3])
|
|
964
|
+
sage: a, b = PJ3.gens()
|
|
965
|
+
sage: a * b # they are inverses of each other
|
|
966
|
+
1
|
|
967
|
+
|
|
968
|
+
sage: J3 = groups.misc.Cactus(3)
|
|
969
|
+
sage: gen = (J3.gen(1,2)*J3.gen(1,3))^3
|
|
970
|
+
sage: gen
|
|
971
|
+
s[1,2]*s[2,3]*s[1,2]*s[1,3]
|
|
972
|
+
sage: gen == b
|
|
973
|
+
True
|
|
974
|
+
"""
|
|
975
|
+
from sage.arith.misc import factorial
|
|
976
|
+
J = self.ambient()
|
|
977
|
+
G = J.gens()
|
|
978
|
+
one = J.one()
|
|
979
|
+
n = self.n()
|
|
980
|
+
nfac = factorial(n)
|
|
981
|
+
reprs = {one.to_permutation(): one}
|
|
982
|
+
next_level = [one]
|
|
983
|
+
while len(reprs) < nfac:
|
|
984
|
+
cur = next_level
|
|
985
|
+
next_level = []
|
|
986
|
+
for val in cur:
|
|
987
|
+
for g in G:
|
|
988
|
+
temp = val * g
|
|
989
|
+
p = temp.to_permutation()
|
|
990
|
+
if p in reprs:
|
|
991
|
+
continue
|
|
992
|
+
reprs[p] = temp
|
|
993
|
+
next_level.append(temp)
|
|
994
|
+
gens = []
|
|
995
|
+
for s in reprs.values():
|
|
996
|
+
for g in G:
|
|
997
|
+
val = s * g * ~(reprs[(s*g).to_permutation()])
|
|
998
|
+
if val == one or val in gens:
|
|
999
|
+
continue
|
|
1000
|
+
gens.append(val)
|
|
1001
|
+
return tuple([self(g) for g in gens])
|