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.
Files changed (40) hide show
  1. passagemath_groups/.dylibs/libgap.10.dylib +0 -0
  2. passagemath_groups/.dylibs/libgmp.10.dylib +0 -0
  3. passagemath_groups/.dylibs/libreadline.8.2.dylib +0 -0
  4. passagemath_groups/.dylibs/libz.1.3.1.dylib +0 -0
  5. passagemath_groups/__init__.py +3 -0
  6. passagemath_groups-10.6.45.dist-info/METADATA +113 -0
  7. passagemath_groups-10.6.45.dist-info/RECORD +40 -0
  8. passagemath_groups-10.6.45.dist-info/WHEEL +6 -0
  9. passagemath_groups-10.6.45.dist-info/top_level.txt +3 -0
  10. sage/all__sagemath_groups.py +21 -0
  11. sage/geometry/all__sagemath_groups.py +1 -0
  12. sage/geometry/palp_normal_form.cpython-314-darwin.so +0 -0
  13. sage/geometry/palp_normal_form.pyx +401 -0
  14. sage/groups/abelian_gps/all.py +25 -0
  15. sage/groups/all.py +5 -0
  16. sage/groups/all__sagemath_groups.py +32 -0
  17. sage/groups/artin.py +1074 -0
  18. sage/groups/braid.py +3806 -0
  19. sage/groups/cactus_group.py +1001 -0
  20. sage/groups/cubic_braid.py +2052 -0
  21. sage/groups/finitely_presented.py +1896 -0
  22. sage/groups/finitely_presented_catalog.py +27 -0
  23. sage/groups/finitely_presented_named.py +592 -0
  24. sage/groups/fqf_orthogonal.py +579 -0
  25. sage/groups/free_group.py +944 -0
  26. sage/groups/group_exp.py +360 -0
  27. sage/groups/group_semidirect_product.py +504 -0
  28. sage/groups/kernel_subgroup.py +231 -0
  29. sage/groups/lie_gps/all.py +1 -0
  30. sage/groups/lie_gps/catalog.py +8 -0
  31. sage/groups/lie_gps/nilpotent_lie_group.py +945 -0
  32. sage/groups/misc_gps/all.py +1 -0
  33. sage/groups/misc_gps/misc_groups.py +11 -0
  34. sage/groups/misc_gps/misc_groups_catalog.py +33 -0
  35. sage/groups/raag.py +866 -0
  36. sage/groups/semimonomial_transformations/all.py +1 -0
  37. sage/groups/semimonomial_transformations/semimonomial_transformation.cpython-314-darwin.so +0 -0
  38. sage/groups/semimonomial_transformations/semimonomial_transformation.pxd +9 -0
  39. sage/groups/semimonomial_transformations/semimonomial_transformation.pyx +346 -0
  40. 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])