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
sage/groups/braid.py ADDED
@@ -0,0 +1,3806 @@
1
+ # sage_setup: distribution = sagemath-groups
2
+ """
3
+ Braid groups
4
+
5
+ Braid groups are implemented as a particular case of finitely presented groups,
6
+ but with a lot of specific methods for braids.
7
+
8
+ A braid group can be created by giving the number of strands, and the name
9
+ of the generators::
10
+
11
+ sage: BraidGroup(3)
12
+ Braid group on 3 strands
13
+ sage: BraidGroup(3,'a')
14
+ Braid group on 3 strands
15
+ sage: BraidGroup(3,'a').gens()
16
+ (a0, a1)
17
+ sage: BraidGroup(3,'a,b').gens()
18
+ (a, b)
19
+
20
+ The elements can be created by operating with the generators, or by passing
21
+ a list with the indices of the letters to the group::
22
+
23
+ sage: B.<s0,s1,s2> = BraidGroup(4)
24
+ sage: s0*s1*s0
25
+ s0*s1*s0
26
+ sage: B([1,2,1])
27
+ s0*s1*s0
28
+
29
+ The mapping class action of the braid group over the free group is
30
+ also implemented, see :class:`MappingClassGroupAction` for an
31
+ explanation. This action is left multiplication of a free group
32
+ element by a braid::
33
+
34
+ sage: B.<b0,b1,b2> = BraidGroup()
35
+ sage: F.<f0,f1,f2,f3> = FreeGroup()
36
+ sage: B.strands() == F.rank() # necessary for the action to be defined
37
+ True
38
+ sage: f1 * b1
39
+ f1*f2*f1^-1
40
+ sage: f0 * b1
41
+ f0
42
+ sage: f1 * b1
43
+ f1*f2*f1^-1
44
+ sage: f1^-1 * b1
45
+ f1*f2^-1*f1^-1
46
+
47
+ AUTHORS:
48
+
49
+ - Miguel Angel Marco Buzunariz
50
+ - Volker Braun
51
+ - Søren Fuglede Jørgensen
52
+ - Robert Lipshitz
53
+ - Thierry Monteil: add a ``__hash__`` method consistent with the word
54
+ problem to ensure correct Cayley graph computations.
55
+ - Sebastian Oehms (July and Nov 2018): add other versions for
56
+ burau_matrix (unitary + simple, see :issue:`25760` and :issue:`26657`)
57
+ - Moritz Firsching (Sept 2021): Colored Jones polynomial
58
+ - Sebastian Oehms (May 2022): add :meth:`links_gould_polynomial`
59
+ """
60
+
61
+ ##############################################################################
62
+ # Copyright (C) 2012 Miguel Angel Marco Buzunariz <mmarco@unizar.es>
63
+ #
64
+ # Distributed under the terms of the GNU General Public License (GPL)
65
+ #
66
+ # The full text of the GPL is available at:
67
+ #
68
+ # https://www.gnu.org/licenses/
69
+ ##############################################################################
70
+
71
+ from itertools import combinations
72
+
73
+ from sage.categories.action import Action
74
+ from sage.categories.groups import Groups
75
+ from sage.combinat.permutation import Permutation, Permutations
76
+ from sage.combinat.subset import Subsets
77
+ from sage.features.sagemath import sage__libs__braiding
78
+ from sage.functions.generalized import sign
79
+ from sage.groups.artin import FiniteTypeArtinGroup, FiniteTypeArtinGroupElement
80
+ from sage.groups.finitely_presented import (
81
+ FinitelyPresentedGroup,
82
+ GroupMorphismWithGensImages,
83
+ )
84
+ from sage.groups.free_group import FreeGroup, is_FreeGroup
85
+ from sage.groups.perm_gps.permgroup_named import (SymmetricGroup,
86
+ SymmetricGroupElement)
87
+ from sage.libs.gap.libgap import libgap
88
+ from sage.matrix.constructor import identity_matrix, matrix
89
+ from sage.misc.cachefunc import cached_method
90
+ from sage.misc.lazy_attribute import lazy_attribute
91
+ from sage.misc.lazy_import import lazy_import
92
+ from sage.misc.misc_c import prod
93
+ from sage.rings.integer import Integer
94
+ from sage.rings.integer_ring import ZZ
95
+ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
96
+ from sage.sets.set import Set
97
+ from sage.structure.element import Expression
98
+ from sage.structure.richcmp import rich_to_bool, richcmp
99
+
100
+ lazy_import('sage.libs.braiding',
101
+ ['leftnormalform', 'rightnormalform', 'centralizer',
102
+ 'supersummitset', 'greatestcommondivisor',
103
+ 'leastcommonmultiple', 'conjugatingbraid', 'ultrasummitset',
104
+ 'thurston_type', 'rigidity', 'sliding_circuits', 'send_to_sss',
105
+ 'send_to_uss', 'send_to_sc', 'trajectory', 'cyclic_slidings'],
106
+ feature=sage__libs__braiding())
107
+ lazy_import('sage.knots.knot', 'Knot')
108
+
109
+
110
+ class Braid(FiniteTypeArtinGroupElement):
111
+ """
112
+ An element of a braid group.
113
+
114
+ It is a particular case of element of a finitely presented group.
115
+
116
+ EXAMPLES::
117
+
118
+ sage: B.<s0,s1,s2> = BraidGroup(4)
119
+ sage: B
120
+ Braid group on 4 strands
121
+ sage: s0*s1/s2/s1
122
+ s0*s1*s2^-1*s1^-1
123
+ sage: B((1, 2, -3, -2))
124
+ s0*s1*s2^-1*s1^-1
125
+ """
126
+ def _richcmp_(self, other, op):
127
+ """
128
+ Compare ``self`` and ``other``.
129
+
130
+ TESTS::
131
+
132
+ sage: # needs sage.libs.braiding
133
+ sage: B = BraidGroup(4)
134
+ sage: b = B([1, 2, 1])
135
+ sage: c = B([2, 1, 2])
136
+ sage: b == c #indirect doctest
137
+ True
138
+ sage: b < c^(-1)
139
+ True
140
+ sage: B([]) == B.one()
141
+ True
142
+ """
143
+ if self.Tietze() == other.Tietze():
144
+ return rich_to_bool(op, 0)
145
+ nfself = [i.Tietze() for i in self.left_normal_form()]
146
+ nfother = [i.Tietze() for i in other.left_normal_form()]
147
+ return richcmp(nfself, nfother, op)
148
+
149
+ def __hash__(self):
150
+ r"""
151
+ Return a hash value for ``self``.
152
+
153
+ EXAMPLES::
154
+
155
+ sage: # needs sage.libs.braiding
156
+ sage: B.<s0,s1,s2> = BraidGroup(4)
157
+ sage: hash(s0*s2) == hash(s2*s0)
158
+ True
159
+ sage: hash(s0*s1) == hash(s1*s0)
160
+ False
161
+ """
162
+ return hash(tuple(i.Tietze() for i in self.left_normal_form()))
163
+
164
+ def strands(self):
165
+ """
166
+ Return the number of strands in the braid.
167
+
168
+ EXAMPLES::
169
+
170
+ sage: B = BraidGroup(4)
171
+ sage: b = B([1, 2, -1, 3, -2])
172
+ sage: b.strands()
173
+ 4
174
+ """
175
+ return self.parent().strands()
176
+
177
+ def components_in_closure(self):
178
+ """
179
+ Return the number of components of the trace closure of the braid.
180
+
181
+ OUTPUT: a positive integer
182
+
183
+ EXAMPLES::
184
+
185
+ sage: B = BraidGroup(5)
186
+ sage: b = B([1, -3]) # Three disjoint unknots
187
+ sage: b.components_in_closure()
188
+ 3
189
+ sage: b = B([1, 2, 3, 4]) # The unknot
190
+ sage: b.components_in_closure()
191
+ 1
192
+ sage: B = BraidGroup(4)
193
+ sage: K11n42 = B([1, -2, 3, -2, 3, -2, -2, -1, 2, -3, -3, 2, 2])
194
+ sage: K11n42.components_in_closure()
195
+ 1
196
+ """
197
+ cycles = self.permutation().to_cycles(singletons=False)
198
+ return self.strands() - sum(len(c)-1 for c in cycles)
199
+
200
+ def burau_matrix(self, var='t', reduced=False):
201
+ r"""
202
+ Return the Burau matrix of the braid.
203
+
204
+ INPUT:
205
+
206
+ - ``var`` -- string (default: ``'t'``); the name of the
207
+ variable in the entries of the matrix
208
+ - ``reduced`` -- boolean (default: ``False``); whether to
209
+ return the reduced or unreduced Burau representation, can
210
+ be one of the following:
211
+
212
+ * ``True`` or ``'increasing'`` -- returns the reduced form using
213
+ the basis given by `e_1 - e_i` for `2 \leq i \leq n`
214
+ * ``'unitary'`` -- the unitary form according to Squier [Squ1984]_
215
+ * ``'simple'`` -- returns the reduced form using the basis given
216
+ by simple roots `e_i - e_{i+1}`, which yields the matrices
217
+ given on the Wikipedia page
218
+
219
+ OUTPUT:
220
+
221
+ The Burau matrix of the braid. It is a matrix whose entries
222
+ are Laurent polynomials in the variable ``var``. If ``reduced``
223
+ is ``True``, return the matrix for the reduced Burau representation
224
+ instead in the format specified. If ``reduced`` is ``'unitary'``,
225
+ a triple ``M, Madj, H`` is returned, where ``M`` is the Burau matrix
226
+ in the unitary form, ``Madj`` the adjoined to ``M`` and ``H``
227
+ the hermitian form.
228
+
229
+ EXAMPLES::
230
+
231
+ sage: B = BraidGroup(4)
232
+ sage: B.inject_variables()
233
+ Defining s0, s1, s2
234
+ sage: b = s0 * s1 / s2 / s1
235
+ sage: b.burau_matrix()
236
+ [ 1 - t 0 t - t^2 t^2]
237
+ [ 1 0 0 0]
238
+ [ 0 0 1 0]
239
+ [ 0 t^-2 -t^-2 + t^-1 -t^-1 + 1]
240
+ sage: s2.burau_matrix('x')
241
+ [ 1 0 0 0]
242
+ [ 0 1 0 0]
243
+ [ 0 0 1 - x x]
244
+ [ 0 0 1 0]
245
+ sage: s0.burau_matrix(reduced=True)
246
+ [-t 0 0]
247
+ [-t 1 0]
248
+ [-t 0 1]
249
+
250
+ Using the different reduced forms::
251
+
252
+ sage: b.burau_matrix(reduced='simple')
253
+ [ 1 - t -t^-1 + 1 -1]
254
+ [ 1 -t^-1 + 1 -1]
255
+ [ 1 -t^-1 0]
256
+
257
+ sage: M, Madj, H = b.burau_matrix(reduced='unitary')
258
+ sage: M
259
+ [-t^-2 + 1 t t^2]
260
+ [ t^-1 - t 1 - t^2 -t^3]
261
+ [ -t^-2 -t^-1 0]
262
+ sage: Madj
263
+ [ 1 - t^2 -t^-1 + t -t^2]
264
+ [ t^-1 -t^-2 + 1 -t]
265
+ [ t^-2 -t^-3 0]
266
+ sage: H
267
+ [t^-1 + t -1 0]
268
+ [ -1 t^-1 + t -1]
269
+ [ 0 -1 t^-1 + t]
270
+ sage: M * H * Madj == H
271
+ True
272
+
273
+ The adjoined matrix (``Madj`` in the above example) matches the
274
+ output of :meth:`sage.groups.artin.ArtinGroupElement.burau_matrix`::
275
+
276
+ sage: from sage.groups.artin import ArtinGroupElement
277
+ sage: Madj == ArtinGroupElement.burau_matrix(b)
278
+ True
279
+
280
+ sage: a = s0^2 * s1 * s0 * s2 *s1 * ~s0 * s1^3 * s0 * s2 * s1^-2 * s0
281
+ sage: a.burau_matrix(reduced='unitary')[1] == ArtinGroupElement.burau_matrix(a)
282
+ True
283
+
284
+ We verify Bigelow's example that in `B_5` the Burau representation
285
+ is not faithful::
286
+
287
+ sage: B.<s1,s2,s3,s4> = BraidGroup(5)
288
+ sage: psi1 = ~s3 * s2 * s1^2 * s2 * s4^3 * s3 * s2
289
+ sage: psi2 = ~s4 * s3 * s2 * s1^-2 * s2 * s1^2 * s2^2 * s1 * s4^5
290
+ sage: alpha = ~psi1 * s4 * psi1
291
+ sage: beta = ~psi2 * s4 * s3 * s2 * s1^2 * s2 * s3 * s4 * psi2
292
+ sage: elm = alpha * beta * ~alpha * ~beta
293
+ sage: elm.burau_matrix()
294
+ [1 0 0 0 0]
295
+ [0 1 0 0 0]
296
+ [0 0 1 0 0]
297
+ [0 0 0 1 0]
298
+ [0 0 0 0 1]
299
+ sage: elm.burau_matrix(reduced=True)
300
+ [1 0 0 0]
301
+ [0 1 0 0]
302
+ [0 0 1 0]
303
+ [0 0 0 1]
304
+ sage: elm.is_one()
305
+ False
306
+
307
+ REFERENCES:
308
+
309
+ - :wikipedia:`Burau_representation`
310
+ - [Squ1984]_
311
+ """
312
+ R = LaurentPolynomialRing(ZZ, var)
313
+ t = R.gen()
314
+ n = self.strands()
315
+ if not reduced:
316
+ M = identity_matrix(R, n)
317
+ for i in self.Tietze():
318
+ A = identity_matrix(R, n)
319
+ if i > 0:
320
+ A[i-1, i-1] = 1-t
321
+ A[i, i] = 0
322
+ A[i, i-1] = 1
323
+ A[i-1, i] = t
324
+ if i < 0:
325
+ A[-1-i, -1-i] = 0
326
+ A[-i, -i] = 1-t**(-1)
327
+ A[-1-i, -i] = 1
328
+ A[-i, -1-i] = t**(-1)
329
+ M = M * A
330
+
331
+ else:
332
+ if reduced is True or reduced == "increasing":
333
+ M = identity_matrix(R, n - 1)
334
+ for j in self.Tietze():
335
+ A = identity_matrix(R, n - 1)
336
+ if j > 1:
337
+ i = j - 1
338
+ A[i-1, i-1] = 1 - t
339
+ A[i, i] = 0
340
+ A[i, i-1] = 1
341
+ A[i-1, i] = t
342
+ if j < -1:
343
+ i = j + 1
344
+ A[-1-i, -1-i] = 0
345
+ A[-i, -i] = 1 - t**-1
346
+ A[-1-i, -i] = 1
347
+ A[-i, -1-i] = t**-1
348
+ if j == 1:
349
+ for k in range(n - 1):
350
+ A[k, 0] = -t
351
+ if j == -1:
352
+ A[0, 0] = -t**-1
353
+ for k in range(1, n - 1):
354
+ A[k, 0] = -1
355
+ M = M * A
356
+
357
+ elif reduced in ["simple", "unitary"]:
358
+ M = identity_matrix(R, n - 1)
359
+ for j in self.Tietze():
360
+ A = identity_matrix(R, n-1)
361
+ if j > 0:
362
+ A[j-1, j-1] = -t
363
+ if j > 1:
364
+ A[j-1, j-2] = t
365
+ if j < n-1:
366
+ A[j-1, j] = 1
367
+ if j < 0:
368
+ A[-j-1, -j-1] = -t**(-1)
369
+ if -j > 1:
370
+ A[-j-1, -j-2] = 1
371
+ if -j < n - 1:
372
+ A[-j-1, -j] = t**(-1)
373
+ M = M * A
374
+
375
+ else:
376
+ raise ValueError("invalid reduced type")
377
+
378
+ if reduced == "unitary":
379
+ # note: the roles of Madj and M are exchanged with respect
380
+ # to the Squier paper in order to match the convention in
381
+ # sage for instance in :meth:`_check_matrix` of
382
+ # :class:`UnitaryMatrixGroup_generic`
383
+
384
+ t_sq = R.hom([t**2], codomain=R)
385
+ Madj = matrix(R, n - 1, n - 1,
386
+ lambda i, j: t**(j - i) * t_sq(M[i, j]))
387
+
388
+ t_inv = R.hom([t**(-1)], codomain=R)
389
+ M = matrix(R, n - 1, n - 1,
390
+ lambda i, j: t_inv(Madj[j, i]))
391
+
392
+ # We see if the hermitian form has been cached
393
+ # in the parent
394
+ H = self.parent()._hermitian_form
395
+ if H is None:
396
+ # Defining the hermitian form
397
+ H = (t + t**(-1)) * identity_matrix(R, n - 1)
398
+ for i in range(n-2):
399
+ H[i, i + 1] = -1
400
+ H[i + 1, i] = -1
401
+ self.parent()._hermitian_form = H
402
+
403
+ return M, Madj, H
404
+
405
+ return M
406
+
407
+ def alexander_polynomial(self, var='t', normalized=True):
408
+ r"""
409
+ Return the Alexander polynomial of the closure of the braid.
410
+
411
+ INPUT:
412
+
413
+ - ``var`` -- string (default: ``'t'``); the name of the
414
+ variable in the entries of the matrix
415
+ - ``normalized`` -- boolean (default: ``True``); whether to
416
+ return the normalized Alexander polynomial
417
+
418
+ OUTPUT: the Alexander polynomial of the braid closure of the braid
419
+
420
+ This is computed using the reduced Burau representation. The
421
+ unnormalized Alexander polynomial is a Laurent polynomial,
422
+ which is only well-defined up to multiplication by plus or
423
+ minus times a power of `t`.
424
+
425
+ We normalize the polynomial by dividing by the largest power
426
+ of `t` and then if the resulting constant coefficient
427
+ is negative, we multiply by `-1`.
428
+
429
+ EXAMPLES:
430
+
431
+ We first construct the trefoil::
432
+
433
+ sage: B = BraidGroup(3)
434
+ sage: b = B([1,2,1,2])
435
+ sage: b.alexander_polynomial(normalized=False)
436
+ 1 - t + t^2
437
+ sage: b.alexander_polynomial()
438
+ t^-2 - t^-1 + 1
439
+
440
+ Next we construct the figure 8 knot::
441
+
442
+ sage: b = B([-1,2,-1,2])
443
+ sage: b.alexander_polynomial(normalized=False)
444
+ -t^-2 + 3*t^-1 - 1
445
+ sage: b.alexander_polynomial()
446
+ t^-2 - 3*t^-1 + 1
447
+
448
+ Our last example is the Kinoshita-Terasaka knot::
449
+
450
+ sage: B = BraidGroup(4)
451
+ sage: b = B([1,1,1,3,3,2,-3,-1,-1,2,-1,-3,-2])
452
+ sage: b.alexander_polynomial(normalized=False)
453
+ -t^-1
454
+ sage: b.alexander_polynomial()
455
+ 1
456
+
457
+ REFERENCES:
458
+
459
+ - :wikipedia:`Alexander_polynomial`
460
+ """
461
+ n = self.strands()
462
+ p = (self.burau_matrix(reduced=True) - identity_matrix(n - 1)).det()
463
+ K, t = LaurentPolynomialRing(ZZ, var).objgen()
464
+ if p == 0:
465
+ return K.zero()
466
+ qn = sum(t**i for i in range(n))
467
+ p //= qn
468
+ if normalized:
469
+ p *= t**(-p.degree())
470
+ if p.constant_coefficient() < 0:
471
+ p = -p
472
+ return p
473
+
474
+ def permutation(self, W=None):
475
+ """
476
+ Return the permutation induced by the braid in its strands.
477
+
478
+ INPUT:
479
+
480
+ - ``W`` -- (optional) the permutation group to project
481
+ ``self`` to; the default is ``self.parent().coxeter_group()``
482
+
483
+ OUTPUT: the image of ``self`` under the natural projection map to ``W``
484
+
485
+ EXAMPLES::
486
+
487
+ sage: B.<s0,s1,s2> = BraidGroup()
488
+ sage: S = SymmetricGroup(4)
489
+ sage: b = s0*s1/s2/s1
490
+ sage: c0 = b.permutation(W=S); c0
491
+ (1,4,2)
492
+ sage: c1 = b.permutation(W=Permutations(4)); c1
493
+ [4, 1, 3, 2]
494
+ sage: c1 == b.permutation()
495
+ True
496
+
497
+ The canonical section from the symmetric group to the braid group
498
+ (sending a permutation to its associated permutation braid)
499
+ can be recovered::
500
+
501
+ sage: B(c0)
502
+ s0*s1*s2*s1
503
+ sage: B(c0) == B(c1)
504
+ True
505
+ """
506
+ return self.coxeter_group_element(W)
507
+
508
+ def plot(self, color='rainbow', orientation='bottom-top', gap=0.05,
509
+ aspect_ratio=1, axes=False, **kwds):
510
+ """
511
+ Plot the braid.
512
+
513
+ The following options are available:
514
+
515
+ - ``color`` -- (default: ``'rainbow'``) the color of the
516
+ strands. Possible values are:
517
+
518
+ * ``'rainbow'``, uses :meth:`~sage.plot.colors.rainbow`
519
+ according to the number of strands.
520
+
521
+ * a valid color name for :meth:`~sage.plot.bezier_path`
522
+ and :meth:`~sage.plot.line`. Used for all strands.
523
+
524
+ * a list or a tuple of colors for each individual strand.
525
+
526
+ - ``orientation`` -- (default: ``'bottom-top'``) determines how
527
+ the braid is printed. The possible values are:
528
+
529
+ * ``'bottom-top'``, the braid is printed from bottom to top
530
+
531
+ * ``'top-bottom'``, the braid is printed from top to bottom
532
+
533
+ * ``'left-right'``, the braid is printed from left to right
534
+
535
+ - ``gap`` -- floating point number (default: 0.05); determines
536
+ the size of the gap left when a strand goes under another
537
+
538
+ - ``aspect_ratio`` -- floating point number (default:
539
+ ``1``); the aspect ratio
540
+
541
+ - ``**kwds`` -- other keyword options that are passed to
542
+ :meth:`~sage.plot.bezier_path` and :meth:`~sage.plot.line`
543
+
544
+ EXAMPLES::
545
+
546
+ sage: # needs sage.libs.braiding
547
+ sage: B = BraidGroup(3)
548
+ sage: a = B([2, 2, -1, -1])
549
+ sage: b = B([2, 1, 2, 1])
550
+ sage: c = b * a / b
551
+ sage: d = a.conjugating_braid(c)
552
+ sage: d * c / d == a
553
+ True
554
+ sage: d
555
+ s1*s0
556
+ sage: d * a / d == c
557
+ False
558
+
559
+ sage: B = BraidGroup(4, 's')
560
+ sage: b = B([1, 2, 3, 1, 2, 1])
561
+ sage: b.plot() # needs sage.plot
562
+ Graphics object consisting of 30 graphics primitives
563
+ sage: b.plot(color=["red", "blue", "red", "blue"]) # needs sage.plot
564
+ Graphics object consisting of 30 graphics primitives
565
+
566
+ sage: B.<s,t> = BraidGroup(3)
567
+ sage: b = t^-1*s^2
568
+ sage: b.plot(orientation='left-right', color='red') # needs sage.plot
569
+ Graphics object consisting of 12 graphics primitives
570
+ """
571
+ from sage.plot.bezier_path import bezier_path
572
+ from sage.plot.colors import rainbow
573
+ from sage.plot.plot import Graphics, line
574
+ if orientation == 'top-bottom':
575
+ orx = 0
576
+ ory = -1
577
+ nx = 1
578
+ ny = 0
579
+ elif orientation == 'left-right':
580
+ orx = 1
581
+ ory = 0
582
+ nx = 0
583
+ ny = -1
584
+ elif orientation == 'bottom-top':
585
+ orx = 0
586
+ ory = 1
587
+ nx = 1
588
+ ny = 0
589
+ else:
590
+ raise ValueError('unknown value for "orientation"')
591
+ n = self.strands()
592
+ if isinstance(color, (list, tuple)):
593
+ if len(color) != n:
594
+ raise TypeError(f"color (={color}) must contain exactly {n} colors")
595
+ col = list(color)
596
+ elif color == "rainbow":
597
+ col = rainbow(n)
598
+ else:
599
+ col = [color]*n
600
+ braid = self.Tietze()
601
+ a = Graphics()
602
+ op = gap
603
+ for i, m in enumerate(braid):
604
+ for j in range(n):
605
+ if m == j+1:
606
+ a += bezier_path([[(j*nx+i*orx, i*ory+j*ny), (j*nx+orx*(i+0.25), j*ny+ory*(i+0.25)),
607
+ (nx*(j+0.5)+orx*(i+0.5), ny*(j+0.5)+ory*(i+0.5))],
608
+ [(nx*(j+1)+orx*(i+0.75), ny*(j+1)+ory*(i+0.75)),
609
+ (nx*(j+1)+orx*(i+1), ny*(j+1)+ory*(i+1))]], color=col[j], **kwds)
610
+ elif m == j:
611
+ a += bezier_path([[(nx*j+orx*i, ny*j+ory*i), (nx*j+orx*(i+0.25), ny*j+ory*(i+0.25)),
612
+ (nx*(j-0.5+4*op)+orx*(i+0.5-2*op), ny*(j-0.5+4*op)+ory*(i+0.5-2*op)),
613
+ (nx*(j-0.5+2*op)+orx*(i+0.5-op), ny*(j-0.5+2*op)+ory*(i+0.5-op))]],
614
+ color=col[j], **kwds)
615
+ a += bezier_path([[(nx*(j-0.5-2*op)+orx*(i+0.5+op), ny*(j-0.5-2*op)+ory*(i+0.5+op)),
616
+ (nx*(j-0.5-4*op)+orx*(i+0.5+2*op), ny*(j-0.5-4*op)+ory*(i+0.5+2*op)),
617
+ (nx*(j-1)+orx*(i+0.75), ny*(j-1)+ory*(i+0.75)),
618
+ (nx*(j-1)+orx*(i+1), ny*(j-1)+ory*(i+1))]], color=col[j], **kwds)
619
+ col[j], col[j-1] = col[j-1], col[j]
620
+ elif -m == j+1:
621
+ a += bezier_path([[(nx*j+orx*i, ny*j+ory*i), (nx*j+orx*(i+0.25), ny*j+ory*(i+0.25)),
622
+ (nx*(j+0.5-4*op)+orx*(i+0.5-2*op), ny*(j+0.5-4*op)+ory*(i+0.5-2*op)),
623
+ (nx*(j+0.5-2*op)+orx*(i+0.5-op), ny*(j+0.5-2*op)+ory*(i+0.5-op))]],
624
+ color=col[j], **kwds)
625
+ a += bezier_path([[(nx*(j+0.5+2*op)+orx*(i+0.5+op), ny*(j+0.5+2*op)+ory*(i+0.5+op)),
626
+ (nx*(j+0.5+4*op)+orx*(i+0.5+2*op), ny*(j+0.5+4*op)+ory*(i+0.5+2*op)),
627
+ (nx*(j+1)+orx*(i+0.75), ny*(j+1)+ory*(i+0.75)),
628
+ (nx*(j+1)+orx*(i+1), ny*(j+1)+ory*(i+1))]], color=col[j], **kwds)
629
+ elif -m == j:
630
+ a += bezier_path([[(nx*j+orx*i, ny*j+ory*i), (nx*j+orx*(i+0.25), ny*j+ory*(i+0.25)),
631
+ (nx*(j-0.5)+orx*(i+0.5), ny*(j-0.5)+ory*(i+0.5))],
632
+ [(nx*(j-1)+orx*(i+0.75), ny*(j-1)+ory*(i+0.75)),
633
+ (nx*(j-1)+orx*(i+1), ny*(j-1)+ory*(i+1))]], color=col[j], **kwds)
634
+ col[j], col[j-1] = col[j-1], col[j]
635
+ else:
636
+ a += line([(nx*j+orx*i, ny*j+ory*i), (nx*j+orx*(i+1), ny*j+ory*(i+1))], color=col[j], **kwds)
637
+ a.set_aspect_ratio(aspect_ratio)
638
+ a.axes(axes)
639
+ return a
640
+
641
+ def plot3d(self, color='rainbow'):
642
+ """
643
+ Plot the braid in 3d.
644
+
645
+ The following option is available:
646
+
647
+ - ``color`` -- (default: ``'rainbow'``) the color of the
648
+ strands. Possible values are:
649
+
650
+ * ``'rainbow'``, uses :meth:`~sage.plot.colors.rainbow`
651
+ according to the number of strands.
652
+
653
+ * a valid color name for :meth:`~sage.plot.plot3d.bezier3d`.
654
+ Used for all strands.
655
+
656
+ * a list or a tuple of colors for each individual strand.
657
+
658
+ EXAMPLES::
659
+
660
+ sage: B = BraidGroup(4, 's')
661
+ sage: b = B([1, 2, 3, 1, 2, 1])
662
+ sage: b.plot3d() # needs sage.plot sage.symbolic
663
+ Graphics3d Object
664
+ sage: b.plot3d(color='red') # needs sage.plot sage.symbolic
665
+ Graphics3d Object
666
+ sage: b.plot3d(color=["red", "blue", "red", "blue"]) # needs sage.plot sage.symbolic
667
+ Graphics3d Object
668
+ """
669
+ from sage.plot.colors import rainbow
670
+ from sage.plot.plot3d.shapes2 import bezier3d
671
+ b = []
672
+ n = self.strands()
673
+ if isinstance(color, (list, tuple)):
674
+ if len(color) != n:
675
+ raise TypeError("color (=%s) must contain exactly %d colors" % (color, n))
676
+ col = list(color)
677
+ elif color == "rainbow":
678
+ col = rainbow(n)
679
+ else:
680
+ col = [color]*n
681
+ braid = self.Tietze()
682
+
683
+ for i, m in enumerate(braid):
684
+ for j in range(n):
685
+ if m == j+1:
686
+ b.append(bezier3d([[(0, j, i), (0, j, i+0.25), (0.25, j, i+0.25), (0.25, j+0.5, i+0.5)],
687
+ [(0.25, j+1, i+0.75), (0, j+1, i+0.75), (0, j+1, i+1)]], color=col[j]))
688
+ elif -m == j+1:
689
+ b.append(bezier3d([[(0, j, i), (0, j, i+0.25), (-0.25, j, i+0.25), (-0.25, j+0.5, i+0.5)],
690
+ [(-0.25, j+1, i+0.75), (0, j+1, i+0.75), (0, j+1, i+1)]], color=col[j]))
691
+ elif m == j:
692
+ b.append(bezier3d([[(0, j, i), (0, j, i+0.25), (-0.25, j, i+0.25), (-0.25, j-0.5, i+0.5)],
693
+ [(-0.25, j-1, i+0.75), (0, j-1, i+0.75), (0, j-1, i+1)]], color=col[j]))
694
+ col[j], col[j-1] = col[j-1], col[j]
695
+ elif -m == j:
696
+ b.append(bezier3d([[(0, j, i), (0, j, i+0.25), (0.25, j, i+0.25), (0.25, j-0.5, i+0.5)],
697
+ [(0.25, j-1, i+0.75), (0, j-1, i+0.75), (0, j-1, i+1)]], color=col[j]))
698
+ col[j], col[j-1] = col[j-1], col[j]
699
+ else:
700
+ b.append(bezier3d([[(0, j, i), (0, j, i+1)]], color=col[j]))
701
+ return sum(b)
702
+
703
+ def LKB_matrix(self, variables='x,y'):
704
+ r"""
705
+ Return the Lawrence-Krammer-Bigelow representation matrix.
706
+
707
+ The matrix is expressed in the basis `\{e_{i, j} \mid 1\leq i
708
+ < j \leq n\}`, where the indices are ordered
709
+ lexicographically. It is a matrix whose entries are in the
710
+ ring of Laurent polynomials on the given variables. By
711
+ default, the variables are ``'x'`` and ``'y'``.
712
+
713
+ INPUT:
714
+
715
+ - ``variables`` -- string (default: ``'x,y'``); a string
716
+ containing the names of the variables, separated by a comma
717
+
718
+ OUTPUT: the matrix corresponding to the Lawrence-Krammer-Bigelow
719
+ representation of the braid
720
+
721
+ EXAMPLES::
722
+
723
+ sage: B = BraidGroup(3)
724
+ sage: b = B([1, 2, 1])
725
+ sage: b.LKB_matrix()
726
+ [ 0 -x^4*y + x^3*y -x^4*y]
727
+ [ 0 -x^3*y 0]
728
+ [ -x^2*y x^3*y - x^2*y 0]
729
+ sage: c = B([2, 1, 2])
730
+ sage: c.LKB_matrix()
731
+ [ 0 -x^4*y + x^3*y -x^4*y]
732
+ [ 0 -x^3*y 0]
733
+ [ -x^2*y x^3*y - x^2*y 0]
734
+
735
+ REFERENCES:
736
+
737
+ - [Big2003]_
738
+ """
739
+ return self.parent()._LKB_matrix_(self.Tietze(), variab=variables)
740
+
741
+ def TL_matrix(self, drain_size, variab=None, sparse=True):
742
+ r"""
743
+ Return the matrix representation of the Temperley--Lieb--Jones
744
+ representation of the braid in a certain basis.
745
+
746
+ The basis is given by non-intersecting pairings of `(n+d)` points,
747
+ where `n` is the number of strands, `d` is given by ``drain_size``,
748
+ and the pairings satisfy certain rules. See
749
+ :meth:`~sage.groups.braid.BraidGroup_class.TL_basis_with_drain()`
750
+ for details.
751
+
752
+ We use the convention that the eigenvalues of the standard generators
753
+ are `1` and `-A^4`, where `A` is a variable of a Laurent
754
+ polynomial ring.
755
+
756
+ When `d = n - 2` and the variables are picked appropriately, the
757
+ resulting representation is equivalent to the reduced Burau
758
+ representation.
759
+
760
+ INPUT:
761
+
762
+ - ``drain_size`` -- integer between 0 and the number of strands
763
+ (both inclusive)
764
+
765
+ - ``variab`` -- variable (default: ``None``); the variable in the
766
+ entries of the matrices; if ``None``, then use a default variable
767
+ in `\ZZ[A,A^{-1}]`
768
+
769
+ - ``sparse`` -- boolean (default: ``True``); whether or not the
770
+ result should be given as a sparse matrix
771
+
772
+ OUTPUT: the matrix of the TL representation of the braid
773
+
774
+ The parameter ``sparse`` can be set to ``False`` if it is
775
+ expected that the resulting matrix will not be sparse. We
776
+ currently make no attempt at guessing this.
777
+
778
+ EXAMPLES:
779
+
780
+ Let us calculate a few examples for `B_4` with `d = 0`::
781
+
782
+ sage: B = BraidGroup(4)
783
+ sage: b = B([1, 2, -3])
784
+ sage: b.TL_matrix(0)
785
+ [1 - A^4 -A^-2]
786
+ [ -A^6 0]
787
+ sage: R.<x> = LaurentPolynomialRing(GF(2))
788
+ sage: b.TL_matrix(0, variab=x)
789
+ [1 + x^4 x^-2]
790
+ [ x^6 0]
791
+ sage: b = B([])
792
+ sage: b.TL_matrix(0)
793
+ [1 0]
794
+ [0 1]
795
+
796
+ Test of one of the relations in `B_8`::
797
+
798
+ sage: B = BraidGroup(8)
799
+ sage: d = 0
800
+ sage: B([4,5,4]).TL_matrix(d) == B([5,4,5]).TL_matrix(d)
801
+ True
802
+
803
+ An element of the kernel of the Burau representation, following
804
+ [Big1999]_::
805
+
806
+ sage: B = BraidGroup(6)
807
+ sage: psi1 = B([4, -5, -2, 1])
808
+ sage: psi2 = B([-4, 5, 5, 2, -1, -1])
809
+ sage: w1 = psi1^(-1) * B([3]) * psi1
810
+ sage: w2 = psi2^(-1) * B([3]) * psi2
811
+ sage: (w1 * w2 * w1^(-1) * w2^(-1)).TL_matrix(4)
812
+ [1 0 0 0 0]
813
+ [0 1 0 0 0]
814
+ [0 0 1 0 0]
815
+ [0 0 0 1 0]
816
+ [0 0 0 0 1]
817
+
818
+ REFERENCES:
819
+
820
+ - [Big1999]_
821
+ - [Jon2005]_
822
+ """
823
+ if variab is None:
824
+ R = LaurentPolynomialRing(ZZ, 'A')
825
+ else:
826
+ R = variab.parent()
827
+ rep = self.parent().TL_representation(drain_size, variab)
828
+ M = identity_matrix(R, self.parent().dimension_of_TL_space(drain_size),
829
+ sparse=sparse)
830
+ for i in self.Tietze():
831
+ if i > 0:
832
+ M = M*rep[i-1][0]
833
+ if i < 0:
834
+ M = M*rep[-i-1][1]
835
+ return M
836
+
837
+ def links_gould_matrix(self, symbolics=False):
838
+ r"""
839
+ Return the representation matrix of ``self`` according to the R-matrix
840
+ representation being attached to the quantum superalgebra `\mathfrak{sl}_q(2|1)`.
841
+
842
+ See [MW2012]_, section 3 and references given there.
843
+
844
+ INPUT:
845
+
846
+ - ``symbolics`` -- boolean (default: ``False``); if set to ``True`` the
847
+ coefficients will be contained in the symbolic ring. Per default they
848
+ are elements of a quotient ring of a three variate Laurent polynomial
849
+ ring.
850
+
851
+ OUTPUT: the representation matrix of ``self`` over the ring according
852
+ to the choice of the keyword ``symbolics`` (see the corresponding
853
+ explanation)
854
+
855
+ EXAMPLES::
856
+
857
+ sage: # needs sage.libs.singular
858
+ sage: Hopf = BraidGroup(2)([-1, -1])
859
+ sage: HopfLG = Hopf.links_gould_matrix()
860
+ sage: HopfLG.dimensions()
861
+ (16, 16)
862
+ sage: HopfLG.base_ring()
863
+ Univariate Quotient Polynomial Ring in Yrbar
864
+ over Multivariate Laurent Polynomial Ring in s0r, s1r
865
+ over Integer Ring with modulus Yr^2 + s0r^2*s1r^2 - s0r^2 - s1r^2 + 1
866
+ sage: HopfLGs = Hopf.links_gould_matrix(symbolics=True) # needs sage.symbolic
867
+ sage: HopfLGs.base_ring() # needs sage.symbolic
868
+ Symbolic Ring
869
+ """
870
+ rep = self.parent()._links_gould_representation(symbolics=symbolics)
871
+ M = rep[0][0].parent().one()
872
+ for i in self.Tietze():
873
+ if i > 0:
874
+ M = M * rep[i-1][0]
875
+ if i < 0:
876
+ M = M * rep[-i-1][1]
877
+ return M
878
+
879
+ @cached_method
880
+ def links_gould_polynomial(self, varnames=None, use_symbolics=False):
881
+ r"""
882
+ Return the Links-Gould polynomial of the closure of ``self``.
883
+
884
+ See [MW2012]_, section 3 and references given there.
885
+
886
+ INPUT:
887
+
888
+ - ``varnames`` -- string (default: ``'t0, t1'``)
889
+
890
+ OUTPUT: a Laurent polynomial in the given variable names
891
+
892
+ EXAMPLES::
893
+
894
+ sage: # needs sage.libs.singular
895
+ sage: Hopf = BraidGroup(2)([-1, -1])
896
+ sage: Hopf.links_gould_polynomial()
897
+ -1 + t1^-1 + t0^-1 - t0^-1*t1^-1
898
+ sage: _ == Hopf.links_gould_polynomial(use_symbolics=True) # needs sage.symbolic
899
+ True
900
+ sage: Hopf.links_gould_polynomial(varnames='a, b')
901
+ -1 + b^-1 + a^-1 - a^-1*b^-1
902
+ sage: _ == Hopf.links_gould_polynomial(varnames='a, b', use_symbolics=True) # needs sage.symbolic
903
+ True
904
+
905
+ REFERENCES:
906
+
907
+ - [MW2012]_
908
+ """
909
+ if varnames is not None:
910
+ poly = self.links_gould_polynomial(use_symbolics=use_symbolics)
911
+ R = LaurentPolynomialRing(ZZ, varnames)
912
+ t0, t1 = R.gens()
913
+ return poly(t0=t0, t1=t1)
914
+ varnames = 't0, t1'
915
+
916
+ rep = self.parent()._links_gould_representation(symbolics=use_symbolics)
917
+ ln = len(rep)
918
+ mu = rep[ln - 1] # quantum trace factor
919
+ M = mu * self.links_gould_matrix(symbolics=use_symbolics)
920
+ d1, d2 = M.dimensions()
921
+ e = d1 // 4
922
+ B = M.base_ring()
923
+ R = LaurentPolynomialRing(ZZ, varnames)
924
+
925
+ # partial quantum trace according to I. Marin section 2.5
926
+ part_trace = matrix(B, 4, 4, lambda i, j: sum(M[e * i + k, e * j + k]
927
+ for k in range(e)))
928
+ ptemp = part_trace[0, 0] # part_trace == psymb*M.parent().one()
929
+ if use_symbolics:
930
+ v1, v2 = R.variable_names()
931
+ pstr = str(ptemp._sympy_().simplify())
932
+ pstr = pstr.replace('t0', v1).replace('t1', v2)
933
+ F = R.fraction_field() # to make coercion work
934
+ return R(F(pstr))
935
+ else:
936
+ ltemp = ptemp.lift().constant_coefficient()
937
+ # Since the result of the calculation is known to be a Laurent polynomial
938
+ # in t0 and t1 all exponents of ltemp must be divisible by 2
939
+ L = ltemp.parent()
940
+ lred = L({(k[0]/2, k[1]/2): v for k, v in ltemp.monomial_coefficients().items()})
941
+ t0, t1 = R.gens()
942
+ return lred(t0, t1)
943
+
944
+ def tropical_coordinates(self) -> list:
945
+ r"""
946
+ Return the tropical coordinates of ``self`` in the braid group `B_n`.
947
+
948
+ OUTPUT: list of `2n` tropical integers
949
+
950
+ EXAMPLES::
951
+
952
+ sage: B = BraidGroup(3)
953
+ sage: b = B([1])
954
+ sage: tc = b.tropical_coordinates(); tc
955
+ [1, 0, 0, 2, 0, 1]
956
+ sage: tc[0].parent()
957
+ Tropical semiring over Integer Ring
958
+
959
+ sage: b = B([-2, -2, -1, -1, 2, 2, 1, 1])
960
+ sage: b.tropical_coordinates()
961
+ [1, -19, -12, 9, 0, 13]
962
+
963
+ REFERENCES:
964
+
965
+ - [DW2007]_
966
+ - [Deh2011]_
967
+ """
968
+ coord = [0, 1] * self.strands()
969
+ for s in self.Tietze():
970
+ k = 2*(abs(s)-1)
971
+ x1, y1, x2, y2 = coord[k:k+4]
972
+ if s > 0:
973
+ sign = 1
974
+ z = x1 - min(y1, 0) - x2 + max(y2, 0)
975
+ coord[k+1] = y2 - max(z, 0)
976
+ coord[k+3] = y1 + max(z, 0)
977
+ else:
978
+ sign = -1
979
+ z = x1 + min(y1, 0) - x2 - max(y2, 0)
980
+ coord[k+1] = y2 + min(z, 0)
981
+ coord[k+3] = y1 - min(z, 0)
982
+
983
+ coord[k] = x1 + sign*(max(y1, 0) + max(max(y2, 0) - sign*z, 0))
984
+ coord[k+2] = x2 + sign*(min(y2, 0) + min(min(y1, 0) + sign*z, 0))
985
+
986
+ from sage.rings.semirings.tropical_semiring import TropicalSemiring
987
+ T = TropicalSemiring(ZZ)
988
+ return [T(c) for c in coord]
989
+
990
+ def markov_trace(self, variab=None, normalized=True):
991
+ r"""
992
+ Return the Markov trace of the braid.
993
+
994
+ The normalization is so that in the underlying braid group
995
+ representation, the eigenvalues of the standard generators of
996
+ the braid group are `1` and `-A^4`.
997
+
998
+ INPUT:
999
+
1000
+ - ``variab`` -- variable (default: ``None``); the variable in the
1001
+ resulting polynomial; if ``None``, then use the variable `A`
1002
+ in `\ZZ[A,A^{-1}]`
1003
+
1004
+ - ``normalized`` -- boolean (default: ``True``); if specified to be
1005
+ ``False``, return instead a rescaled Laurent polynomial version of
1006
+ the Markov trace
1007
+
1008
+ OUTPUT:
1009
+
1010
+ If ``normalized`` is ``False``, return instead the Markov trace
1011
+ of the braid, normalized by a factor of `(A^2+A^{-2})^n`. The
1012
+ result is then a Laurent polynomial in ``variab``. Otherwise it
1013
+ is a quotient of Laurent polynomials in ``variab``.
1014
+
1015
+ EXAMPLES::
1016
+
1017
+ sage: B = BraidGroup(4)
1018
+ sage: b = B([1, 2, -3])
1019
+ sage: mt = b.markov_trace(); mt
1020
+ A^4/(A^12 + 3*A^8 + 3*A^4 + 1)
1021
+ sage: mt.factor() # needs sage.libs.pari
1022
+ A^4 * (A^4 + 1)^-3
1023
+
1024
+ We now give the non-normalized Markov trace::
1025
+
1026
+ sage: mt = b.markov_trace(normalized=False); mt
1027
+ A^-4 + 1
1028
+ sage: mt.parent()
1029
+ Univariate Laurent Polynomial Ring in A over Integer Ring
1030
+ """
1031
+ if variab is None:
1032
+ R = LaurentPolynomialRing(ZZ, 'A')
1033
+ A = R.gens()[0]
1034
+ one = ZZ.one()
1035
+ quantum_integer = lambda d: R({i: one for i in range(-2*d, 2*d+1, 4)})
1036
+ else:
1037
+ A = variab
1038
+ quantum_integer = lambda d: (A**(2*(d+1))-A**(-2*(d+1))) // (A**2-A**(-2))
1039
+
1040
+ n = self.strands()
1041
+ trace_sum = sum(quantum_integer(d) * self.TL_matrix(d, variab=variab).trace()
1042
+ for d in range(n+1) if (n+d) % 2 == 0)
1043
+
1044
+ if normalized:
1045
+ delta = A**2 + A**(-2)
1046
+ trace_sum = trace_sum / delta**n
1047
+ return trace_sum
1048
+
1049
+ @lazy_attribute
1050
+ def _jones_polynomial(self):
1051
+ """
1052
+ Cached version of the Jones polynomial in a generic variable
1053
+ with the Skein normalization.
1054
+
1055
+ The computation of the Jones polynomial uses the representation
1056
+ of the braid group on the Temperley--Lieb algebra. We cache the
1057
+ part of the calculation which does not depend on the choices of
1058
+ variables or normalizations.
1059
+
1060
+ .. SEEALSO::
1061
+
1062
+ :meth:`jones_polynomial`
1063
+
1064
+ TESTS::
1065
+
1066
+ sage: B = BraidGroup(9)
1067
+ sage: b = B([1, 2, 3, 4, 5, 6, 7, 8])
1068
+ sage: b.jones_polynomial() # needs sage.symbolic
1069
+ 1
1070
+
1071
+ sage: B = BraidGroup(2)
1072
+ sage: b = B([])
1073
+ sage: b._jones_polynomial # needs sage.symbolic
1074
+ -A^-2 - A^2
1075
+ sage: b = B([-1, -1, -1])
1076
+ sage: b._jones_polynomial # needs sage.symbolic
1077
+ -A^-16 + A^-12 + A^-4
1078
+ """
1079
+ trace = self.markov_trace(normalized=False)
1080
+ A = trace.parent().gens()[0]
1081
+ D = A**2 + A**(-2)
1082
+ exp_sum = self.exponent_sum()
1083
+ num_comp = self.components_in_closure()
1084
+ return (-1)**(num_comp-1) * A**(2*exp_sum) * trace // D
1085
+
1086
+ def jones_polynomial(self, variab=None, skein_normalization=False):
1087
+ r"""
1088
+ Return the Jones polynomial of the trace closure of the braid.
1089
+
1090
+ The normalization is so that the unknot has Jones polynomial `1`. If
1091
+ ``skein_normalization`` is ``True``, the variable of the result is
1092
+ replaced by a itself to the power of `4`, so that the result
1093
+ agrees with the conventions of [Lic1997]_ (which in particular differs
1094
+ slightly from the conventions used otherwise in this class), had
1095
+ one used the conventional Kauffman bracket variable notation directly.
1096
+
1097
+ If ``variab`` is ``None`` return a polynomial in the variable `A`
1098
+ or `t`, depending on the value ``skein_normalization``. In
1099
+ particular, if ``skein_normalization`` is ``False``, return the
1100
+ result in terms of the variable `t`, also used in [Lic1997]_.
1101
+
1102
+ INPUT:
1103
+
1104
+ - ``variab`` -- variable (default: ``None``); the variable in the
1105
+ resulting polynomial; if unspecified, use either a default variable
1106
+ in `\ZZ[A,A^{-1}]` or the variable `t` in the symbolic ring
1107
+
1108
+ - ``skein_normalization`` -- boolean (default: ``False``); determines
1109
+ the variable of the resulting polynomial
1110
+
1111
+ OUTPUT:
1112
+
1113
+ If ``skein_normalization`` if ``False``, this returns an element
1114
+ in the symbolic ring as the Jones polynomial of the closure might
1115
+ have fractional powers when the closure of the braid is not a knot.
1116
+ Otherwise the result is a Laurent polynomial in ``variab``.
1117
+
1118
+ EXAMPLES:
1119
+
1120
+ The unknot::
1121
+
1122
+ sage: B = BraidGroup(9)
1123
+ sage: b = B([1, 2, 3, 4, 5, 6, 7, 8])
1124
+ sage: b.jones_polynomial() # needs sage.symbolic
1125
+ 1
1126
+
1127
+ Two unlinked unknots::
1128
+
1129
+ sage: B = BraidGroup(2)
1130
+ sage: b = B([])
1131
+ sage: b.jones_polynomial() # needs sage.symbolic
1132
+ -sqrt(t) - 1/sqrt(t)
1133
+
1134
+ The Hopf link::
1135
+
1136
+ sage: B = BraidGroup(2)
1137
+ sage: b = B([-1,-1])
1138
+ sage: b.jones_polynomial() # needs sage.symbolic
1139
+ -1/sqrt(t) - 1/t^(5/2)
1140
+
1141
+ Different representations of the trefoil and one of its mirror::
1142
+
1143
+ sage: # needs sage.symbolic
1144
+ sage: B = BraidGroup(2)
1145
+ sage: b = B([-1, -1, -1])
1146
+ sage: b.jones_polynomial(skein_normalization=True)
1147
+ -A^-16 + A^-12 + A^-4
1148
+ sage: b.jones_polynomial()
1149
+ 1/t + 1/t^3 - 1/t^4
1150
+ sage: B = BraidGroup(3)
1151
+ sage: b = B([-1, -2, -1, -2])
1152
+ sage: b.jones_polynomial(skein_normalization=True)
1153
+ -A^-16 + A^-12 + A^-4
1154
+ sage: R.<x> = LaurentPolynomialRing(GF(2))
1155
+ sage: b.jones_polynomial(skein_normalization=True, variab=x)
1156
+ x^-16 + x^-12 + x^-4
1157
+ sage: B = BraidGroup(3)
1158
+ sage: b = B([1, 2, 1, 2])
1159
+ sage: b.jones_polynomial(skein_normalization=True)
1160
+ A^4 + A^12 - A^16
1161
+
1162
+ K11n42 (the mirror of the "Kinoshita-Terasaka" knot) and K11n34 (the
1163
+ mirror of the "Conway" knot)::
1164
+
1165
+ sage: B = BraidGroup(4)
1166
+ sage: b11n42 = B([1, -2, 3, -2, 3, -2, -2, -1, 2, -3, -3, 2, 2])
1167
+ sage: b11n34 = B([1, 1, 2, -3, 2, -3, 1, -2, -2, -3, -3])
1168
+ sage: bool(b11n42.jones_polynomial() == b11n34.jones_polynomial()) # needs sage.symbolic
1169
+ True
1170
+ """
1171
+ if skein_normalization:
1172
+ if variab is None:
1173
+ return self._jones_polynomial
1174
+ else:
1175
+ return self._jones_polynomial(variab)
1176
+ else:
1177
+ if variab is None:
1178
+ variab = 't'
1179
+ if not isinstance(variab, Expression):
1180
+ from sage.symbolic.ring import SR
1181
+ variab = SR(variab)
1182
+ # We force the result to be in the symbolic ring because of the expand
1183
+ return self._jones_polynomial(variab**(ZZ(1)/ZZ(4))).expand()
1184
+
1185
+ @cached_method
1186
+ def _enhanced_states(self):
1187
+ r"""
1188
+ Return the enhanced states of the closure of the braid diagram.
1189
+
1190
+ The states are collected in a dictionary, where the dictionary
1191
+ keys are tuples of quantum and annular grading.
1192
+ Each dictionary value is itself a dictionary with the
1193
+ dictionary keys being the homological grading, and the values
1194
+ a list of enhanced states with the corresponding homology,
1195
+ quantum and annular grading.
1196
+
1197
+ Each enhanced state is represented as a tuple containing:
1198
+
1199
+ - A tuple with the type of smoothing made at each crossing.
1200
+
1201
+ - A set with the circles marked as negative.
1202
+
1203
+ - A set with the circles marked as positive.
1204
+
1205
+ Each circle represented by a frozenset of tuples of the form
1206
+ (index of crossing, side where the circle passes the crossing)
1207
+
1208
+ EXAMPLES::
1209
+
1210
+ sage: # needs sage.graphs
1211
+ sage: B = BraidGroup(2)
1212
+ sage: b = B([1,1])
1213
+ sage: sorted((gr, sorted((d, [(sm,
1214
+ ....: sorted((sorted(A[0]), A[1]) for A in X),
1215
+ ....: sorted((sorted(A[0]), A[1]) for A in Y))
1216
+ ....: for sm, X, Y in data])
1217
+ ....: for d, data in v.items()))
1218
+ ....: for gr,v in b._enhanced_states().items())
1219
+ [((0, -2),
1220
+ [(0, [((0, 0), [([(0, 1), (1, 1)], 1), ([(0, 3), (1, 3)], 1)], [])])]),
1221
+ ((2, 0),
1222
+ [(0,
1223
+ [((0, 0), [([(0, 3), (1, 3)], 1)], [([(0, 1), (1, 1)], 1)]),
1224
+ ((0, 0), [([(0, 1), (1, 1)], 1)], [([(0, 3), (1, 3)], 1)])]),
1225
+ (1,
1226
+ [((1, 0), [([(0, 0), (0, 2), (1, 1), (1, 3)], 0)], []),
1227
+ ((0, 1), [([(0, 1), (0, 3), (1, 0), (1, 2)], 0)], [])]),
1228
+ (2, [((1, 1), [([(0, 0), (1, 2)], 0), ([(0, 2), (1, 0)], 0)], [])])]),
1229
+ ((4, 0),
1230
+ [(1,
1231
+ [((1, 0), [], [([(0, 0), (0, 2), (1, 1), (1, 3)], 0)]),
1232
+ ((0, 1), [], [([(0, 1), (0, 3), (1, 0), (1, 2)], 0)])]),
1233
+ (2,
1234
+ [((1, 1), [([(0, 2), (1, 0)], 0)], [([(0, 0), (1, 2)], 0)]),
1235
+ ((1, 1), [([(0, 0), (1, 2)], 0)], [([(0, 2), (1, 0)], 0)])])]),
1236
+ ((4, 2),
1237
+ [(0, [((0, 0), [], [([(0, 1), (1, 1)], 1), ([(0, 3), (1, 3)], 1)])])]),
1238
+ ((6, 0),
1239
+ [(2, [((1, 1), [], [([(0, 0), (1, 2)], 0), ([(0, 2), (1, 0)], 0)])])])]
1240
+ """
1241
+ from sage.functions.generalized import sgn
1242
+ from sage.graphs.graph import Graph
1243
+ crossinglist = self.Tietze()
1244
+ ncross = len(crossinglist)
1245
+ writhe = 0
1246
+
1247
+ # first build a "quadruply linked list", each crossing indicating its
1248
+ # previous and following neighbours
1249
+ last_crossing_in_row = [None] * self.strands()
1250
+ first_crossing_in_row = [None] * self.strands()
1251
+ crossings = [None] * ncross
1252
+ for i, cr in enumerate(crossinglist):
1253
+ writhe = writhe + sgn(cr)
1254
+ prevabove = last_crossing_in_row[abs(cr) - 1]
1255
+ prevbelow = last_crossing_in_row[abs(cr)]
1256
+ if prevabove is None:
1257
+ first_crossing_in_row[abs(cr) - 1] = i
1258
+ else:
1259
+ if abs(cr) == abs(crossings[prevabove]["cr"]):
1260
+ crossings[prevabove]["next_above"] = i
1261
+ else:
1262
+ crossings[prevabove]["next_below"] = i
1263
+ if prevbelow is None:
1264
+ first_crossing_in_row[abs(cr)] = i
1265
+ else:
1266
+ if abs(cr) == abs(crossings[prevbelow]["cr"]):
1267
+ crossings[prevbelow]["next_below"] = i
1268
+ else:
1269
+ crossings[prevbelow]["next_above"] = i
1270
+ crossings[i] = {"cr": cr,
1271
+ "prev_above": prevabove,
1272
+ "prev_below": prevbelow,
1273
+ "next_above": None,
1274
+ "next_below": None}
1275
+ last_crossing_in_row[abs(cr) - 1] = i
1276
+ last_crossing_in_row[abs(cr)] = i
1277
+ # tie up the ends of the list
1278
+ for k, i in enumerate(first_crossing_in_row):
1279
+ if i is not None:
1280
+ j = last_crossing_in_row[k]
1281
+ if abs(crossings[i]["cr"]) == k:
1282
+ crossings[i]["prev_below"] = j
1283
+ else:
1284
+ crossings[i]["prev_above"] = j
1285
+
1286
+ if abs(crossings[j]["cr"]) == k:
1287
+ crossings[j]["next_below"] = i
1288
+ else:
1289
+ crossings[j]["next_above"] = i
1290
+
1291
+ smoothings = []
1292
+ # generate all the resolutions
1293
+ for i in range(2**ncross):
1294
+ v = Integer(i).bits()
1295
+ v = v + [0]*(ncross - len(v))
1296
+ G = Graph()
1297
+ for j, cr in enumerate(crossings):
1298
+ if (v[j]*2-1)*sgn(cr["cr"]) == -1: # oriented resolution
1299
+ G.add_edge((j, cr["next_above"], abs(cr["cr"]) - 1), (j, 1))
1300
+ G.add_edge((cr["prev_above"], j, abs(cr["cr"]) - 1), (j, 1))
1301
+ G.add_edge((j, cr["next_below"], abs(cr["cr"])), (j, 3))
1302
+ G.add_edge((cr["prev_below"], j, abs(cr["cr"])), (j, 3))
1303
+ else:
1304
+ G.add_edge((j, cr["next_above"], abs(cr["cr"]) - 1), (j, 0))
1305
+ G.add_edge((j, cr["next_below"], abs(cr["cr"])), (j, 0))
1306
+ G.add_edge((cr["prev_above"], j, abs(cr["cr"]) - 1), (j, 2))
1307
+ G.add_edge((cr["prev_below"], j, abs(cr["cr"])), (j, 2))
1308
+ # add loops of strands without crossing
1309
+ for k, j in enumerate(first_crossing_in_row):
1310
+ if j is None:
1311
+ G.add_edge((ncross + k, ncross + k, k), (ncross + k, 4))
1312
+ sm = []
1313
+ for component in G.connected_components(sort=False):
1314
+ circle = set()
1315
+ trivial = 1
1316
+ # trivial switch: minus one means a circle is non-trivial.
1317
+ for vertex in component:
1318
+ if len(vertex) == 3:
1319
+ if vertex[1] <= vertex[0]: # flip triviality for every looping edge
1320
+ trivial *= -1
1321
+ else:
1322
+ circle.add(vertex)
1323
+ trivial = (1-trivial) // 2 # convert to 0 - trivial, 1 - non-trivial
1324
+ sm.append((frozenset(circle), trivial))
1325
+ smoothings.append((tuple(v), sm))
1326
+
1327
+ states = {}
1328
+ for sm in smoothings:
1329
+ iindex = (writhe - ncross) // 2 + sum(sm[0])
1330
+ for m in range(2**len(sm[1])):
1331
+ m = [2*x-1 for x in Integer(m).bits()]
1332
+ m = m + [-1]*(len(sm[1]) - len(m))
1333
+ qagrad = (writhe + iindex + sum(m),
1334
+ sum([x for i, x in enumerate(m) if sm[1][i][1] == 1]))
1335
+ circpos = set()
1336
+ circneg = set()
1337
+ for i, x in enumerate(m):
1338
+ if x == 1:
1339
+ circpos.add(sm[1][i])
1340
+ else:
1341
+ circneg.add(sm[1][i])
1342
+
1343
+ if qagrad in states:
1344
+ if iindex in states[qagrad]:
1345
+ states[qagrad][iindex].append((sm[0], circneg, circpos))
1346
+ else:
1347
+ states[qagrad][iindex] = [(sm[0], circneg, circpos)]
1348
+ else:
1349
+ states[qagrad] = {iindex: [(sm[0], circneg, circpos)]}
1350
+ return states
1351
+
1352
+ @cached_method
1353
+ def _annular_khovanov_complex_cached(self, qagrad, ring=None):
1354
+ r"""
1355
+ Return the annular Khovanov complex of the braid.
1356
+
1357
+ INPUT:
1358
+
1359
+ - ``qagrad`` -- tuple of the quantum and annular grading to compute
1360
+
1361
+ - ``ring`` -- (default: ``ZZ``) the coefficient ring
1362
+
1363
+ OUTPUT: the annular Khovanov complex of the braid in the given grading
1364
+
1365
+ .. NOTE::
1366
+
1367
+ This method is intended only as the cache for
1368
+ :meth:`annular_khovanov_complex`.
1369
+
1370
+ EXAMPLES::
1371
+
1372
+ sage: B = BraidGroup(3)
1373
+ sage: B([1,2,1,2])._annular_khovanov_complex_cached((5,-1)).homology() # needs sage.graphs
1374
+ {1: Z, 2: Z, 3: 0}
1375
+ """
1376
+ from sage.homology.chain_complex import ChainComplex
1377
+ if ring is None:
1378
+ ring = ZZ
1379
+ states = self._enhanced_states()
1380
+ if qagrad in states:
1381
+ bases = states[qagrad]
1382
+ else:
1383
+ # return trivial chain complexx
1384
+ return ChainComplex()
1385
+ C_differentials = {}
1386
+ for i in bases:
1387
+ if i+1 in bases:
1388
+ m = matrix(ring, len(bases[i+1]), len(bases[i]), sparse=True)
1389
+ for ii in range(m.nrows()):
1390
+ source = bases[i+1][ii]
1391
+ for jj in range(m.ncols()):
1392
+ target = bases[i][jj]
1393
+ difs = [index for index, value in enumerate(source[0]) if value != target[0][index]]
1394
+ if len(difs) == 1 and not (target[2].intersection(source[1]) or target[1].intersection(source[2])):
1395
+ m[ii, jj] = (-1)**sum(target[0][:difs[0]])
1396
+ else:
1397
+ m = matrix(ring, 0, len(bases[i]), sparse=True)
1398
+ C_differentials[i] = m
1399
+ return ChainComplex(C_differentials)
1400
+
1401
+ def annular_khovanov_complex(self, qagrad=None, ring=None):
1402
+ r"""
1403
+ Return the annular Khovanov complex of the closure of a braid,
1404
+ as defined in [BG2013]_.
1405
+
1406
+ INPUT:
1407
+
1408
+ - ``qagrad`` -- tuple of quantum and annular grading for which to compute
1409
+ the chain complex; if not specified all gradings are computed
1410
+
1411
+ - ``ring`` -- (default: ``ZZ``) the coefficient ring
1412
+
1413
+ OUTPUT:
1414
+
1415
+ The annular Khovanov complex of the braid, given as a dictionary whose
1416
+ keys are tuples of quantum and annular grading. If ``qagrad`` is
1417
+ specified only return the chain complex of that grading.
1418
+
1419
+ EXAMPLES::
1420
+
1421
+ sage: B = BraidGroup(3)
1422
+ sage: b = B([1,-2,1,-2])
1423
+ sage: C = b.annular_khovanov_complex(); C # needs sage.graphs
1424
+ {(-5, -1): Chain complex with at most 1 nonzero terms over Integer Ring,
1425
+ (-3, -3): Chain complex with at most 1 nonzero terms over Integer Ring,
1426
+ (-3, -1): Chain complex with at most 2 nonzero terms over Integer Ring,
1427
+ (-3, 1): Chain complex with at most 1 nonzero terms over Integer Ring,
1428
+ (-1, -1): Chain complex with at most 5 nonzero terms over Integer Ring,
1429
+ (-1, 1): Chain complex with at most 2 nonzero terms over Integer Ring,
1430
+ (1, -1): Chain complex with at most 2 nonzero terms over Integer Ring,
1431
+ (1, 1): Chain complex with at most 5 nonzero terms over Integer Ring,
1432
+ (3, -1): Chain complex with at most 1 nonzero terms over Integer Ring,
1433
+ (3, 1): Chain complex with at most 2 nonzero terms over Integer Ring,
1434
+ (3, 3): Chain complex with at most 1 nonzero terms over Integer Ring,
1435
+ (5, 1): Chain complex with at most 1 nonzero terms over Integer Ring}
1436
+ sage: C[1,-1].homology() # needs sage.graphs
1437
+ {1: Z x Z, 2: 0}
1438
+
1439
+ TESTS::
1440
+
1441
+ sage: C = BraidGroup(2)([]).annular_khovanov_complex() # needs sage.graphs
1442
+ sage: {qa: C[qa].homology() for qa in C} # needs sage.graphs
1443
+ {(-2, -2): {0: Z}, (0, 0): {0: Z x Z}, (2, 2): {0: Z}}
1444
+
1445
+ sage: BraidGroup(3)([-1]).annular_khovanov_complex((0,1), ZZ).differential() # needs sage.graphs
1446
+ {-2: [],
1447
+ -1: [0]
1448
+ [1]
1449
+ [1],
1450
+ 0: []}
1451
+ """
1452
+ if ring is None:
1453
+ ring = ZZ
1454
+ if qagrad is None:
1455
+ return {qa: self._annular_khovanov_complex_cached(qa, ring)
1456
+ for qa in self._enhanced_states()}
1457
+ return self._annular_khovanov_complex_cached(qagrad, ring)
1458
+
1459
+ def annular_khovanov_homology(self, qagrad=None, ring=ZZ):
1460
+ r"""
1461
+ Return the annular Khovanov homology of a closure of a braid.
1462
+
1463
+ INPUT:
1464
+
1465
+ - ``qagrad`` -- (optional) tuple of quantum and annular grading
1466
+ for which to compute the homology
1467
+
1468
+ - ``ring`` -- (default: ``ZZ``) the coefficient ring
1469
+
1470
+ OUTPUT:
1471
+
1472
+ If ``qagrad`` is ``None``, return a dictionary of homologies in all
1473
+ gradings indexed by grading. If ``qagrad`` is specified, return the
1474
+ homology of that grading.
1475
+
1476
+ .. NOTE::
1477
+
1478
+ This is a simple wrapper around :meth:`annular_khovanov_complex`
1479
+ to compute homology from it.
1480
+
1481
+ EXAMPLES::
1482
+
1483
+ sage: B = BraidGroup(4)
1484
+ sage: b = B([1,3,-2])
1485
+ sage: b.annular_khovanov_homology()
1486
+ {(-3, -4): {0: Z},
1487
+ (-3, -2): {-1: Z},
1488
+ (-1, -2): {-1: 0, 0: Z x Z x Z, 1: 0},
1489
+ (-1, 0): {-1: Z x Z},
1490
+ (1, -2): {1: Z x Z},
1491
+ (1, 0): {-1: 0, 0: Z x Z x Z x Z, 1: 0, 2: 0},
1492
+ (1, 2): {-1: Z},
1493
+ (3, 0): {1: Z x Z x Z, 2: 0},
1494
+ (3, 2): {-1: 0, 0: Z x Z x Z, 1: 0},
1495
+ (5, 0): {2: Z},
1496
+ (5, 2): {1: Z x Z},
1497
+ (5, 4): {0: Z}}
1498
+
1499
+ sage: B = BraidGroup(2)
1500
+ sage: b = B([1,1,1])
1501
+ sage: b.annular_khovanov_homology((7,0))
1502
+ {2: 0, 3: C2}
1503
+
1504
+ TESTS::
1505
+
1506
+ sage: b = BraidGroup(4)([1,-3])
1507
+ sage: b.annular_khovanov_homology((-4,-2))
1508
+ {-1: Z}
1509
+ sage: b.annular_khovanov_homology((0,2))
1510
+ {-1: Z}
1511
+ """
1512
+ if qagrad is None:
1513
+ C = self.annular_khovanov_complex(qagrad, ring)
1514
+ return {qa: C[qa].homology() for qa in C}
1515
+ return self.annular_khovanov_complex(qagrad, ring).homology()
1516
+
1517
+ @cached_method
1518
+ def left_normal_form(self, algorithm='libbraiding'):
1519
+ r"""
1520
+ Return the left normal form of the braid.
1521
+
1522
+ INPUT:
1523
+
1524
+ - ``algorithm`` -- string (default: ``'artin'``); must be one of the following:
1525
+
1526
+ * ``'artin'`` -- the general method for Artin groups is used
1527
+ * ``'libbraiding'`` -- the algorithm from the ``libbraiding`` package
1528
+
1529
+ OUTPUT:
1530
+
1531
+ A tuple of simple generators in the left normal form. The first
1532
+ element is a power of `\Delta`, and the rest are elements of the
1533
+ natural section lift from the corresponding symmetric group.
1534
+
1535
+ EXAMPLES::
1536
+
1537
+ sage: # needs sage.libs.braiding
1538
+ sage: B = BraidGroup(6)
1539
+ sage: B.one().left_normal_form()
1540
+ (1,)
1541
+ sage: b = B([-2, 2, -4, -4, 4, -5, -1, 4, -1, 1])
1542
+ sage: L1 = b.left_normal_form(); L1
1543
+ (s0^-1*s1^-1*s0^-1*s2^-1*s1^-1*s0^-1*s3^-1*s2^-1*s1^-1*s0^-1*s4^-1*s3^-1*s2^-1*s1^-1*s0^-1,
1544
+ s0*s2*s1*s0*s3*s2*s1*s0*s4*s3*s2*s1,
1545
+ s3)
1546
+ sage: L1 == b.left_normal_form()
1547
+ True
1548
+ sage: B([1]).left_normal_form(algorithm='artin')
1549
+ (1, s0)
1550
+ sage: B([-3]).left_normal_form(algorithm='artin')
1551
+ (s0^-1*s1^-1*s0^-1*s2^-1*s1^-1*s0^-1*s3^-1*s2^-1*s1^-1*s0^-1*s4^-1*s3^-1*s2^-1*s1^-1*s0^-1,
1552
+ s0*s1*s2*s3*s4*s0*s1*s2*s3*s1*s2*s0*s1*s0)
1553
+ sage: B = BraidGroup(3)
1554
+ sage: B([1,2,-1]).left_normal_form()
1555
+ (s0^-1*s1^-1*s0^-1, s1*s0, s0*s1)
1556
+ sage: B([1,2,1]).left_normal_form()
1557
+ (s0*s1*s0,)
1558
+ """
1559
+ if algorithm == 'libbraiding':
1560
+ lnf = leftnormalform(self)
1561
+ B = self.parent()
1562
+ return tuple([B.delta()**lnf[0][0]] + [B(b) for b in lnf[1:]])
1563
+ elif algorithm == 'artin':
1564
+ return FiniteTypeArtinGroupElement.left_normal_form.f(self)
1565
+ raise ValueError("invalid algorithm")
1566
+
1567
+ def _left_normal_form_coxeter(self):
1568
+ r"""
1569
+ Return the left normal form of the braid, in permutation form.
1570
+
1571
+ OUTPUT: tuple whose first element is the power of `\Delta`, and the
1572
+ rest are the permutations corresponding to the simple factors
1573
+
1574
+ EXAMPLES::
1575
+
1576
+ sage: B = BraidGroup(12)
1577
+ sage: B([2, 2, 2, 3, 1, 2, 3, 2, 1, -2])._left_normal_form_coxeter()
1578
+ (-1,
1579
+ [12, 11, 10, 9, 8, 7, 6, 5, 2, 4, 3, 1],
1580
+ [4, 1, 3, 2, 5, 6, 7, 8, 9, 10, 11, 12],
1581
+ [2, 3, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12],
1582
+ [3, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12],
1583
+ [2, 3, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12])
1584
+ sage: C = BraidGroup(6)
1585
+ sage: C([2, 3, -4, 2, 3, -5, 1, -2, 3, 4, 1, -2])._left_normal_form_coxeter()
1586
+ (-2, [3, 5, 4, 2, 6, 1], [1, 6, 3, 5, 2, 4], [5, 6, 2, 4, 1, 3],
1587
+ [3, 2, 4, 1, 5, 6], [1, 5, 2, 3, 4, 6])
1588
+
1589
+ .. NOTE::
1590
+
1591
+ For long braids this method is slower than ``algorithm='libbraiding'``.
1592
+
1593
+ .. TODO::
1594
+
1595
+ Remove this method and use the default one from
1596
+ :meth:`sage.groups.artin.FiniteTypeArtinGroupElement.left_normal_form`.
1597
+ """
1598
+ delta = 0
1599
+ Delta = self.parent()._coxeter_group.long_element()
1600
+ sr = self.parent()._coxeter_group.simple_reflections()
1601
+ tz = self.Tietze()
1602
+ if not tz:
1603
+ return (0,)
1604
+ form = []
1605
+ for i in tz:
1606
+ if i > 0:
1607
+ form.append(sr[i])
1608
+ else:
1609
+ delta += 1
1610
+ form = [Delta * a * Delta for a in form]
1611
+ form.append(Delta * sr[-i])
1612
+ i = j = 0
1613
+ while j < len(form):
1614
+ while i < len(form) - j - 1:
1615
+ e = form[i].idescents(from_zero=False)
1616
+ s = form[i + 1].descents(from_zero=False)
1617
+ S = set(s).difference(set(e))
1618
+ while S:
1619
+ a = list(S)[0]
1620
+ form[i] = form[i] * sr[a]
1621
+ form[i + 1] = sr[a] * form[i+1]
1622
+ e = form[i].idescents(from_zero=False)
1623
+ s = form[i + 1].descents(from_zero=False)
1624
+ S = set(s).difference(set(e))
1625
+ if form[i+1].length() == 0:
1626
+ form.pop(i+1)
1627
+ i = 0
1628
+ else:
1629
+ i += 1
1630
+ j += 1
1631
+ i = 0
1632
+ form = [a for a in form if a.length()]
1633
+ while form and form[0] == Delta:
1634
+ form.pop(0)
1635
+ delta -= 1
1636
+ return tuple([-delta] + form)
1637
+
1638
+ def right_normal_form(self):
1639
+ r"""
1640
+ Return the right normal form of the braid.
1641
+
1642
+ A tuple of simple generators in the right normal form. The last
1643
+ element is a power of `\Delta`, and the rest are elements of the
1644
+ natural section lift from the corresponding symmetric group.
1645
+
1646
+ EXAMPLES::
1647
+
1648
+ sage: # needs sage.libs.braiding
1649
+ sage: B = BraidGroup(4)
1650
+ sage: b = B([1, 2, 1, -2, 3, 1])
1651
+ sage: b.right_normal_form()
1652
+ (s1*s0, s0*s2, 1)
1653
+ """
1654
+ rnf = rightnormalform(self)
1655
+ B = self.parent()
1656
+ return tuple([B(b) for b in rnf[:-1]] + [B.delta()**rnf[-1][0]])
1657
+
1658
+ def centralizer(self) -> list:
1659
+ """
1660
+ Return a list of generators of the centralizer of the braid.
1661
+
1662
+ EXAMPLES::
1663
+
1664
+ sage: # needs sage.libs.braiding
1665
+ sage: B = BraidGroup(4)
1666
+ sage: b = B([2, 1, 3, 2])
1667
+ sage: b.centralizer()
1668
+ [s1*s0*s2*s1, s0*s2]
1669
+ """
1670
+ c = centralizer(self)
1671
+ B = self.parent()
1672
+ return [B._element_from_libbraiding(b) for b in c]
1673
+
1674
+ def super_summit_set(self) -> list:
1675
+ """
1676
+ Return a list with the super summit set of the braid.
1677
+
1678
+ EXAMPLES::
1679
+
1680
+ sage: # needs sage.libs.braiding
1681
+ sage: B = BraidGroup(3)
1682
+ sage: b = B([1, 2, -1, -2, -2, 1])
1683
+ sage: b.super_summit_set()
1684
+ [s0^-1*s1^-1*s0^-2*s1^2*s0^2,
1685
+ (s0^-1*s1^-1*s0^-1)^2*s1^2*s0^3*s1,
1686
+ (s0^-1*s1^-1*s0^-1)^2*s1*s0^3*s1^2,
1687
+ s0^-1*s1^-1*s0^-2*s1^-1*s0*s1^3*s0]
1688
+ """
1689
+ sss = supersummitset(self)
1690
+ B = self.parent()
1691
+ return [B._element_from_libbraiding(b) for b in sss]
1692
+
1693
+ def gcd(self, other):
1694
+ """
1695
+ Return the greatest common divisor of the two braids.
1696
+
1697
+ INPUT:
1698
+
1699
+ - ``other`` -- the other braid with respect with the gcd is computed
1700
+
1701
+ EXAMPLES::
1702
+
1703
+ sage: # needs sage.libs.braiding
1704
+ sage: B = BraidGroup(3)
1705
+ sage: b = B([1, 2, -1, -2, -2, 1])
1706
+ sage: c = B([1, 2, 1])
1707
+ sage: b.gcd(c)
1708
+ s0^-1*s1^-1*s0^-2*s1^2*s0
1709
+ sage: c.gcd(b)
1710
+ s0^-1*s1^-1*s0^-2*s1^2*s0
1711
+ """
1712
+ B = self.parent()
1713
+ b = greatestcommondivisor(self, other)
1714
+ return B._element_from_libbraiding(b)
1715
+
1716
+ def lcm(self, other):
1717
+ """
1718
+ Return the least common multiple of the two braids.
1719
+
1720
+ INPUT:
1721
+
1722
+ - ``other`` -- the other braid with respect with the lcm is computed
1723
+
1724
+ EXAMPLES::
1725
+
1726
+ sage: # needs sage.libs.braiding
1727
+ sage: B = BraidGroup(3)
1728
+ sage: b = B([1, 2, -1, -2, -2, 1])
1729
+ sage: c = B([1, 2, 1])
1730
+ sage: b.lcm(c)
1731
+ (s0*s1)^2*s0
1732
+ """
1733
+ B = self.parent()
1734
+ b = leastcommonmultiple(self, other)
1735
+ return B._element_from_libbraiding(b)
1736
+
1737
+ def conjugating_braid(self, other):
1738
+ r"""
1739
+ Return a conjugating braid, if it exists.
1740
+
1741
+ INPUT:
1742
+
1743
+ - ``other`` -- a braid in the same braid group as ``self``
1744
+
1745
+ OUTPUT:
1746
+
1747
+ A conjugating braid. More precisely, if the output is `d`, `o` equals
1748
+ ``other``, and `s` equals ``self`` then `o = d^{-1} \cdot s \cdot d`.
1749
+
1750
+ EXAMPLES::
1751
+
1752
+ sage: # needs sage.libs.braiding
1753
+ sage: B = BraidGroup(3)
1754
+ sage: B.one().conjugating_braid(B.one())
1755
+ 1
1756
+ sage: B.one().conjugating_braid(B.gen(0)) is None
1757
+ True
1758
+ sage: B.gen(0).conjugating_braid(B.gen(1))
1759
+ s1*s0
1760
+ sage: B.gen(0).conjugating_braid(B.gen(1).inverse()) is None
1761
+ True
1762
+ sage: a = B([2, 2, -1, -1])
1763
+ sage: b = B([2, 1, 2, 1])
1764
+ sage: c = b * a / b
1765
+ sage: d1 = a.conjugating_braid(c)
1766
+ sage: d1
1767
+ s1*s0
1768
+ sage: d1 * c / d1 == a
1769
+ True
1770
+ sage: d1 * a / d1 == c
1771
+ False
1772
+ sage: l = sage.groups.braid.conjugatingbraid(a,c) # needs sage.groups
1773
+ sage: d1 == B._element_from_libbraiding(l) # needs sage.groups
1774
+ True
1775
+ sage: b = B([2, 2, 2, 2, 1])
1776
+ sage: c = b * a / b
1777
+ sage: d1 = a.conjugating_braid(c)
1778
+ sage: len(d1.Tietze())
1779
+ 7
1780
+ sage: d1 * c / d1 == a
1781
+ True
1782
+ sage: d1 * a / d1 == c
1783
+ False
1784
+ sage: d1
1785
+ s1^2*s0^2*s1^2*s0
1786
+ sage: l = sage.groups.braid.conjugatingbraid(a,c) # needs sage.groups
1787
+ sage: d2 = B._element_from_libbraiding(l) # needs sage.groups
1788
+ sage: len(d2.Tietze()) # needs sage.groups
1789
+ 13
1790
+ sage: c.conjugating_braid(b) is None
1791
+ True
1792
+ """
1793
+ cb = conjugatingbraid(self, other)
1794
+ if not cb:
1795
+ return None
1796
+ B = self.parent()
1797
+ cb[0][0] %= 2
1798
+ return B._element_from_libbraiding(cb)
1799
+
1800
+ def is_conjugated(self, other) -> bool:
1801
+ """
1802
+ Check if the two braids are conjugated.
1803
+
1804
+ INPUT:
1805
+
1806
+ - ``other`` -- the other braid to check for conjugacy
1807
+
1808
+ EXAMPLES::
1809
+
1810
+ sage: # needs sage.libs.braiding
1811
+ sage: B = BraidGroup(3)
1812
+ sage: a = B([2, 2, -1, -1])
1813
+ sage: b = B([2, 1, 2, 1])
1814
+ sage: c = b * a / b
1815
+ sage: c.is_conjugated(a)
1816
+ True
1817
+ sage: c.is_conjugated(b)
1818
+ False
1819
+ """
1820
+ cb = conjugatingbraid(self, other)
1821
+ return bool(cb)
1822
+
1823
+ def pure_conjugating_braid(self, other):
1824
+ r"""
1825
+ Return a pure conjugating braid, i.e. a conjugating braid whose
1826
+ associated permutation is the identity, if it exists.
1827
+
1828
+ INPUT:
1829
+
1830
+ - ``other`` -- a braid in the same braid group as ``self``
1831
+
1832
+ OUTPUT:
1833
+
1834
+ A pure conjugating braid. More precisely, if the output is `d`, `o`
1835
+ equals ``other``, and `s` equals ``self`` then
1836
+ `o = d^{-1} \cdot s \cdot d`.
1837
+
1838
+ EXAMPLES::
1839
+
1840
+ sage: # needs sage.libs.braiding
1841
+ sage: B = BraidGroup(4)
1842
+ sage: B.one().pure_conjugating_braid(B.one())
1843
+ 1
1844
+ sage: B.one().pure_conjugating_braid(B.gen(0)) is None
1845
+ True
1846
+ sage: B.gen(0).pure_conjugating_braid(B.gen(1)) is None
1847
+ True
1848
+ sage: B.gen(0).conjugating_braid(B.gen(2).inverse()) is None
1849
+ True
1850
+ sage: a = B([1, 2, 3])
1851
+ sage: b = B([3, 2,])
1852
+ sage: c = b ^ 12 * a / b ^ 12
1853
+ sage: d1 = a.conjugating_braid(c)
1854
+ sage: len(d1.Tietze())
1855
+ 30
1856
+ sage: S = SymmetricGroup(4)
1857
+ sage: d1.permutation(W=S)
1858
+ (1,3)(2,4)
1859
+ sage: d1 * c / d1 == a
1860
+ True
1861
+ sage: d1 * a / d1 == c
1862
+ False
1863
+ sage: d2 = a.pure_conjugating_braid(c)
1864
+ sage: len(d2.Tietze())
1865
+ 24
1866
+ sage: d2.permutation(W=S)
1867
+ ()
1868
+ sage: d2 * c / d2 == a
1869
+ True
1870
+ sage: d2
1871
+ (s0*s1*s2^2*s1*s0)^4
1872
+ sage: a.conjugating_braid(b) is None
1873
+ True
1874
+ sage: a.pure_conjugating_braid(b) is None
1875
+ True
1876
+ sage: a1 = B([1])
1877
+ sage: a2 = B([2])
1878
+ sage: a1.conjugating_braid(a2)
1879
+ s1*s0
1880
+ sage: a1.permutation(W=S)
1881
+ (1,2)
1882
+ sage: a2.permutation(W=S)
1883
+ (2,3)
1884
+ sage: a1.pure_conjugating_braid(a2) is None
1885
+ True
1886
+ sage: (a1^2).conjugating_braid(a2^2)
1887
+ s1*s0
1888
+ sage: (a1^2).pure_conjugating_braid(a2^2) is None
1889
+ True
1890
+ """
1891
+ B = self.parent()
1892
+ n = B.strands()
1893
+ S = SymmetricGroup(n)
1894
+ p1 = self.permutation(W=S)
1895
+ p2 = other.permutation(W=S)
1896
+ if p1 != p2:
1897
+ return None
1898
+ b0 = self.conjugating_braid(other)
1899
+ if b0 is None:
1900
+ return None
1901
+ p3 = b0.permutation(W=S).inverse()
1902
+ if p3.is_one():
1903
+ return b0
1904
+ LP = {a.permutation(W=S): a for a in self.centralizer()}
1905
+ if p3 not in S.subgroup(LP):
1906
+ return None
1907
+ P = p3.word_problem(list(LP), display=False, as_list=True)
1908
+ b1 = prod(LP[S(a)] ** b for a, b in P)
1909
+ b0 = b1 * b0
1910
+ n0 = len(b0.Tietze())
1911
+ L = leftnormalform(b0)
1912
+ L[0][0] %= 2
1913
+ b2 = B._element_from_libbraiding(L)
1914
+ n2 = len(b2.Tietze())
1915
+ return b2 if n2 <= n0 else b0
1916
+
1917
+ def ultra_summit_set(self) -> list:
1918
+ """
1919
+ Return a list with the orbits of the ultra summit set of ``self``.
1920
+
1921
+ EXAMPLES::
1922
+
1923
+ sage: # needs sage.libs.braiding
1924
+ sage: B = BraidGroup(3)
1925
+ sage: a = B([2, 2, -1, -1, 2, 2])
1926
+ sage: b = B([2, 1, 2, 1])
1927
+ sage: b.ultra_summit_set()
1928
+ [[s0*s1*s0^2, (s0*s1)^2]]
1929
+ sage: a.ultra_summit_set()
1930
+ [[(s0^-1*s1^-1*s0^-1)^2*s1^3*s0^2*s1^3,
1931
+ (s0^-1*s1^-1*s0^-1)^2*s1^2*s0^2*s1^4,
1932
+ (s0^-1*s1^-1*s0^-1)^2*s1*s0^2*s1^5,
1933
+ s0^-1*s1^-1*s0^-2*s1^5*s0,
1934
+ (s0^-1*s1^-1*s0^-1)^2*s1^5*s0^2*s1,
1935
+ (s0^-1*s1^-1*s0^-1)^2*s1^4*s0^2*s1^2],
1936
+ [s0^-1*s1^-1*s0^-2*s1^-1*s0^2*s1^2*s0^3,
1937
+ s0^-1*s1^-1*s0^-2*s1^-1*s0*s1^2*s0^4,
1938
+ s0^-1*s1^-1*s0^-2*s1*s0^5,
1939
+ (s0^-1*s1^-1*s0^-1)^2*s1*s0^6*s1,
1940
+ s0^-1*s1^-1*s0^-2*s1^-1*s0^4*s1^2*s0,
1941
+ s0^-1*s1^-1*s0^-2*s1^-1*s0^3*s1^2*s0^2]]
1942
+ """
1943
+ uss = ultrasummitset(self)
1944
+ B = self.parent()
1945
+ return [[B._element_from_libbraiding(i) for i in s] for s in uss]
1946
+
1947
+ def thurston_type(self) -> str:
1948
+ """
1949
+ Return the thurston_type of ``self``.
1950
+
1951
+ OUTPUT: one of ``'reducible'``, ``'periodic'`` or ``'pseudo-anosov'``
1952
+
1953
+ EXAMPLES::
1954
+
1955
+ sage: # needs sage.libs.braiding
1956
+ sage: B = BraidGroup(3)
1957
+ sage: b = B([1, 2, -1])
1958
+ sage: b.thurston_type()
1959
+ 'reducible'
1960
+ sage: a = B([2, 2, -1, -1, 2, 2])
1961
+ sage: a.thurston_type()
1962
+ 'pseudo-anosov'
1963
+ sage: c = B([2, 1, 2, 1])
1964
+ sage: c.thurston_type()
1965
+ 'periodic'
1966
+ """
1967
+ return thurston_type(self)
1968
+
1969
+ def is_reducible(self) -> bool:
1970
+ """
1971
+ Check whether the braid is reducible.
1972
+
1973
+ EXAMPLES::
1974
+
1975
+ sage: # needs sage.libs.braiding
1976
+ sage: B = BraidGroup(3)
1977
+ sage: b = B([1, 2, -1])
1978
+ sage: b.is_reducible()
1979
+ True
1980
+ sage: a = B([2, 2, -1, -1, 2, 2])
1981
+ sage: a.is_reducible()
1982
+ False
1983
+ """
1984
+ return self.thurston_type() == 'reducible'
1985
+
1986
+ def is_periodic(self) -> bool:
1987
+ """
1988
+ Check whether the braid is periodic.
1989
+
1990
+ EXAMPLES::
1991
+
1992
+ sage: # needs sage.libs.braiding
1993
+ sage: B = BraidGroup(3)
1994
+ sage: a = B([2, 2, -1, -1, 2, 2])
1995
+ sage: b = B([2, 1, 2, 1])
1996
+ sage: a.is_periodic()
1997
+ False
1998
+ sage: b.is_periodic()
1999
+ True
2000
+ """
2001
+ return self.thurston_type() == 'periodic'
2002
+
2003
+ def is_pseudoanosov(self) -> bool:
2004
+ """
2005
+ Check if the braid is pseudo-Anosov.
2006
+
2007
+ EXAMPLES::
2008
+
2009
+ sage: # needs sage.libs.braiding
2010
+ sage: B = BraidGroup(3)
2011
+ sage: a = B([2, 2, -1, -1, 2, 2])
2012
+ sage: b = B([2, 1, 2, 1])
2013
+ sage: a.is_pseudoanosov()
2014
+ True
2015
+ sage: b.is_pseudoanosov()
2016
+ False
2017
+ """
2018
+ return self.thurston_type() == 'pseudo-anosov'
2019
+
2020
+ def rigidity(self):
2021
+ """
2022
+ Return the rigidity of ``self``.
2023
+
2024
+ EXAMPLES::
2025
+
2026
+ sage: # needs sage.libs.braiding
2027
+ sage: B = BraidGroup(3)
2028
+ sage: b = B([2, 1, 2, 1])
2029
+ sage: a = B([2, 2, -1, -1, 2, 2])
2030
+ sage: a.rigidity()
2031
+ 6
2032
+ sage: b.rigidity()
2033
+ 0
2034
+ """
2035
+ return Integer(rigidity(self))
2036
+
2037
+ def sliding_circuits(self) -> list:
2038
+ """
2039
+ Return the sliding circuits of the braid.
2040
+
2041
+ OUTPUT: list of sliding circuits. Each sliding circuit is itself
2042
+ a list of braids.
2043
+
2044
+ EXAMPLES::
2045
+
2046
+ sage: # needs sage.libs.braiding
2047
+ sage: B = BraidGroup(3)
2048
+ sage: a = B([2, 2, -1, -1, 2, 2])
2049
+ sage: a.sliding_circuits()
2050
+ [[(s0^-1*s1^-1*s0^-1)^2*s1^3*s0^2*s1^3],
2051
+ [s0^-1*s1^-1*s0^-2*s1^-1*s0^2*s1^2*s0^3],
2052
+ [s0^-1*s1^-1*s0^-2*s1^-1*s0^3*s1^2*s0^2],
2053
+ [(s0^-1*s1^-1*s0^-1)^2*s1^4*s0^2*s1^2],
2054
+ [(s0^-1*s1^-1*s0^-1)^2*s1^2*s0^2*s1^4],
2055
+ [s0^-1*s1^-1*s0^-2*s1^-1*s0*s1^2*s0^4],
2056
+ [(s0^-1*s1^-1*s0^-1)^2*s1^5*s0^2*s1],
2057
+ [s0^-1*s1^-1*s0^-2*s1^-1*s0^4*s1^2*s0],
2058
+ [(s0^-1*s1^-1*s0^-1)^2*s1*s0^2*s1^5],
2059
+ [s0^-1*s1^-1*s0^-2*s1*s0^5],
2060
+ [(s0^-1*s1^-1*s0^-1)^2*s1*s0^6*s1],
2061
+ [s0^-1*s1^-1*s0^-2*s1^5*s0]]
2062
+ sage: b = B([2, 1, 2, 1])
2063
+ sage: b.sliding_circuits()
2064
+ [[s0*s1*s0^2, (s0*s1)^2]]
2065
+ """
2066
+ slc = sliding_circuits(self)
2067
+ B = self.parent()
2068
+ return [[B._element_from_libbraiding(i) for i in s] for s in slc]
2069
+
2070
+ def mirror_image(self):
2071
+ r"""
2072
+ Return the image of ``self`` under the mirror involution (see
2073
+ :meth:`BraidGroup_class.mirror_involution`). The link closure of
2074
+ it is mirrored to the closure of ``self`` (see the example below
2075
+ of a positive amphicheiral knot).
2076
+
2077
+ EXAMPLES::
2078
+
2079
+ sage: B5 = BraidGroup(5)
2080
+ sage: b = B5((-1, 2, -3, -1, -3, 4, 2, -3, 2, 4, 2, -3)) # closure K12a_427
2081
+ sage: bm = b.mirror_image(); bm
2082
+ s0*s1^-1*s2*s0*s2*s3^-1*s1^-1*s2*s1^-1*s3^-1*s1^-1*s2
2083
+ sage: bm.is_conjugated(b) # needs sage.libs.braiding
2084
+ True
2085
+ sage: bm.is_conjugated(~b) # needs sage.libs.braiding
2086
+ False
2087
+ """
2088
+ return self.parent().mirror_involution()(self)
2089
+
2090
+ def reverse(self):
2091
+ r"""
2092
+ Return the reverse of ``self`` obtained by reversing the order of the
2093
+ generators in its word. This defines an anti-involution on the braid
2094
+ group. The link closure of it has the reversed orientation (see the
2095
+ example below of a non reversible knot).
2096
+
2097
+ EXAMPLES::
2098
+
2099
+ sage: b = BraidGroup(3)((1, 1, -2, 1, -2, 1, -2, -2)) # closure K8_17
2100
+ sage: br = b.reverse(); br
2101
+ s1^-1*(s1^-1*s0)^3*s0
2102
+ sage: br.is_conjugated(b) # needs sage.libs.braiding
2103
+ False
2104
+ """
2105
+ t = list(self.Tietze())
2106
+ t.reverse()
2107
+ return self.parent()(tuple(t))
2108
+
2109
+ def deformed_burau_matrix(self, variab='q'):
2110
+ r"""
2111
+ Return the deformed Burau matrix of the braid.
2112
+
2113
+ INPUT:
2114
+
2115
+ - ``variab`` -- variable (default: ``q``); the variable in the
2116
+ resulting laurent polynomial, which is the base ring for the
2117
+ free algebra constructed
2118
+
2119
+ OUTPUT: a matrix with elements in the free algebra ``self._algebra``
2120
+
2121
+ EXAMPLES::
2122
+
2123
+ sage: # needs sage.combinat
2124
+ sage: B = BraidGroup(4)
2125
+ sage: b = B([1, 2, -3, -2, 3, 1])
2126
+ sage: db = b.deformed_burau_matrix(); db
2127
+ [ ap_0*ap_5 ... bp_0*ap_1*cm_3*bp_4]
2128
+ ...
2129
+ [ bm_2*bm_3*cp_5 ... bm_2*am_3*bp_4]
2130
+
2131
+ We check how this relates to the nondeformed Burau matrix::
2132
+
2133
+ sage: # needs sage.combinat
2134
+ sage: def subs_gen(gen, q):
2135
+ ....: gen_str = str(gen)
2136
+ ....: v = q if 'p' in gen_str else 1/q
2137
+ ....: if 'b' in gen_str:
2138
+ ....: return v
2139
+ ....: elif 'a' in gen_str:
2140
+ ....: return 1 - v
2141
+ ....: else:
2142
+ ....: return 1
2143
+ sage: db_base = db.parent().base_ring()
2144
+ sage: q = db_base.base_ring().gen()
2145
+ sage: db_simp = db.subs({gen: subs_gen(gen, q)
2146
+ ....: for gen in db_base.gens()})
2147
+ sage: db_simp
2148
+ [ (1-2*q+q^2) (q-q^2) (q-q^2+q^3) (q^2-q^3)]
2149
+ [ (1-q) q 0 0]
2150
+ [ 0 0 (1-q) q]
2151
+ [ (q^-2) 0 -(q^-2-q^-1) -(q^-1-1)]
2152
+ sage: burau = b.burau_matrix(); burau
2153
+ [1 - 2*t + t^2 t - t^2 t - t^2 + t^3 t^2 - t^3]
2154
+ [ 1 - t t 0 0]
2155
+ [ 0 0 1 - t t]
2156
+ [ t^-2 0 -t^-2 + t^-1 -t^-1 + 1]
2157
+ sage: t = burau.parent().base_ring().gen()
2158
+ sage: burau.subs({t:q}).change_ring(db_base) == db_simp
2159
+ True
2160
+ """
2161
+ from sage.algebras.free_algebra import FreeAlgebra
2162
+
2163
+ R = LaurentPolynomialRing(ZZ, variab)
2164
+
2165
+ n = self.strands()
2166
+ tz = self.Tietze()
2167
+ m3 = len(tz) * 3
2168
+ plus = [i for i, tzi in enumerate(tz) if tzi > 0]
2169
+ minus = [i for i, tzi in enumerate(tz) if tzi < 0]
2170
+ gens_str = [f'{s}p_{i}' for i in plus for s in 'bca']
2171
+ gens_str += [f'{s}m_{i}' for i in minus for s in 'bca']
2172
+ alg_ZZ = FreeAlgebra(ZZ, m3, gens_str)
2173
+ gen_indices = {k: i for i, k in enumerate(plus + minus)}
2174
+ gens = [alg_ZZ.gens()[k:k + 3] for k in range(0, m3, 3)]
2175
+
2176
+ M = identity_matrix(alg_ZZ, n)
2177
+ for k, i in enumerate(tz):
2178
+ A = identity_matrix(alg_ZZ, n)
2179
+ b, c, a = gens[gen_indices[k]]
2180
+ # faster using row operations instead ?
2181
+ if i > 0:
2182
+ A[i-1, i-1] = a
2183
+ A[i, i] = 0
2184
+ A[i, i-1] = c
2185
+ A[i-1, i] = b
2186
+ if i < 0:
2187
+ A[-1-i, -1-i] = 0
2188
+ A[-i, -i] = a
2189
+ A[-1-i, -i] = c
2190
+ A[-i, -1-i] = b
2191
+ M = M * A
2192
+
2193
+ alg_R = FreeAlgebra(R, m3, gens_str)
2194
+ return M.change_ring(alg_R)
2195
+
2196
+ def _colored_jones_sum(self, N, qword):
2197
+ r"""
2198
+ Helper function to get the colored Jones polynomial.
2199
+
2200
+ INPUT:
2201
+
2202
+ - ``N`` -- integer; the number of colors
2203
+ - ``qword`` -- a right quantum word (possibly in unreduced form)
2204
+
2205
+ EXAMPLES::
2206
+
2207
+ sage: # needs sage.combinat
2208
+ sage: b = BraidGroup(2)([1,1,1])
2209
+ sage: db = b.deformed_burau_matrix()[1:,1:]; db
2210
+ [cp_0*ap_1*bp_2]
2211
+ sage: b._colored_jones_sum(2, db[0,0])
2212
+ 1 + q - q^2
2213
+ sage: b._colored_jones_sum(3, db[0,0])
2214
+ 1 + q^2 - q^5 - q^6 + q^7
2215
+ sage: b._colored_jones_sum(4, db[0,0])
2216
+ 1 + q^3 - q^8 - q^10 + q^13 + q^14 - q^15
2217
+ """
2218
+ rqword = RightQuantumWord(qword).reduced_word()
2219
+ alg = qword.parent()
2220
+ result = alg.base_ring().one()
2221
+ current_word = alg.one()
2222
+ # This seemingly infinite sum is always finite if the qword comes
2223
+ # from a sum of quantum determinants; because at some point
2224
+ # the break condition will become true.
2225
+ while True:
2226
+ current_word *= rqword
2227
+ new_rqw = RightQuantumWord(current_word)
2228
+ current_word = new_rqw.reduced_word()
2229
+ new_eps = new_rqw.eps(N)
2230
+ if not new_eps:
2231
+ break
2232
+ result += new_eps
2233
+ return result
2234
+
2235
+ def colored_jones_polynomial(self, N, variab=None, try_inverse=True):
2236
+ r"""
2237
+ Return the colored Jones polynomial of the trace closure of the braid.
2238
+
2239
+ INPUT:
2240
+
2241
+ - ``N`` -- integer; the number of colors
2242
+ - ``variab`` -- (default: `q`) the variable in the resulting
2243
+ Laurent polynomial
2244
+ - ``try_inverse`` -- boolean (default: ``True``); if ``True``,
2245
+ attempt a faster calculation by using the inverse of the braid
2246
+
2247
+ ALGORITHM:
2248
+
2249
+ The algorithm used is described in [HL2018]_. We follow their
2250
+ notation, but work in a suitable free algebra over a Laurent
2251
+ polynomial ring in one variable to simplify bookkeeping.
2252
+
2253
+ EXAMPLES::
2254
+
2255
+ sage: # needs sage.combinat
2256
+ sage: trefoil = BraidGroup(2)([1,1,1])
2257
+ sage: trefoil.colored_jones_polynomial(2)
2258
+ q + q^3 - q^4
2259
+ sage: trefoil.colored_jones_polynomial(4)
2260
+ q^3 + q^7 - q^10 + q^11 - q^13 - q^14 + q^15 - q^17
2261
+ + q^19 + q^20 - q^21
2262
+ sage: trefoil.inverse().colored_jones_polynomial(4)
2263
+ -q^-21 + q^-20 + q^-19 - q^-17 + q^-15 - q^-14 - q^-13
2264
+ + q^-11 - q^-10 + q^-7 + q^-3
2265
+
2266
+ sage: # needs sage.combinat
2267
+ sage: figure_eight = BraidGroup(3)([-1, 2, -1, 2])
2268
+ sage: figure_eight.colored_jones_polynomial(2)
2269
+ q^-2 - q^-1 + 1 - q + q^2
2270
+ sage: figure_eight.colored_jones_polynomial(3, 'Q')
2271
+ Q^-6 - Q^-5 - Q^-4 + 2*Q^-3 - Q^-2 - Q^-1 + 3 - Q - Q^2
2272
+ + 2*Q^3 - Q^4 - Q^5 + Q^6
2273
+ """
2274
+ if self.components_in_closure() != 1:
2275
+ raise ValueError("the number of components must be 1")
2276
+ if not hasattr(self, '_cj_with_q'):
2277
+ # Move to the __init__ if this class adds one
2278
+ self._cj_with_q = {}
2279
+ if N in self._cj_with_q:
2280
+ cj = self._cj_with_q[N]
2281
+ if variab is None:
2282
+ return cj
2283
+ if isinstance(variab, str):
2284
+ variab = LaurentPolynomialRing(ZZ, variab).gen()
2285
+ return cj.subs(q=variab)
2286
+
2287
+ db = self.deformed_burau_matrix('q')[1:, 1:]
2288
+ q = db.parent().base_ring().base_ring().gen()
2289
+ n = db.ncols()
2290
+ qword = sum((-1)**(s.cardinality() - 1)
2291
+ * (q * db[list(s), list(s)]).quantum_determinant(q)
2292
+ for s in Subsets(range(n)) if s)
2293
+ inverse_shorter = try_inverse
2294
+ if try_inverse:
2295
+ db_inv = self.inverse().deformed_burau_matrix('q')[1:, 1:]
2296
+ q_inv = db_inv.parent().base_ring().base_ring().gen()
2297
+ qword_inv = sum((-1)**(s.cardinality() - 1)
2298
+ * (q_inv*db_inv[list(s), list(s)]).quantum_determinant(q_inv)
2299
+ for s in Subsets(range(n)) if s)
2300
+ # Check if the inverse has a shorter expression at this point
2301
+ inverse_shorter = len(list(qword_inv)) < len(list(qword))
2302
+ use_inverse = try_inverse and inverse_shorter
2303
+ shorter_qword = qword_inv if use_inverse else qword
2304
+ knot = Knot(self.inverse()) if use_inverse else Knot(self)
2305
+ cj = (q**((N - 1) * (knot.writhe() - self.strands() + 1) / 2)
2306
+ * self._colored_jones_sum(N, shorter_qword))
2307
+ self._cj_with_q[N] = cj.subs({q: 1/q}) if use_inverse else cj
2308
+ return self.colored_jones_polynomial(N, variab, try_inverse)
2309
+
2310
+ def super_summit_set_element(self):
2311
+ r"""
2312
+ Return an element of the braid's super summit set and the conjugating
2313
+ braid.
2314
+
2315
+ EXAMPLES::
2316
+
2317
+ sage: B = BraidGroup(4)
2318
+ sage: b = B([1, 2, 1, 2, 3, -1, 2, 1, 3])
2319
+ sage: b.super_summit_set_element() # needs sage.libs.braiding
2320
+ (s0*s2*s0*s1*s2*s1*s0, s0^-1*s1^-1*s0^-1*s2^-1*s1^-1*s0^-1*s1*s0*s2*s1*s0)
2321
+ """
2322
+ to_sss = send_to_sss(self)
2323
+ B = self.parent()
2324
+ return tuple([B._element_from_libbraiding(b) for b in to_sss])
2325
+
2326
+ def ultra_summit_set_element(self):
2327
+ r"""
2328
+ Return an element of the braid's ultra summit set and the conjugating
2329
+ braid.
2330
+
2331
+ EXAMPLES::
2332
+
2333
+ sage: B = BraidGroup(4)
2334
+ sage: b = B([1, 2, 1, 2, 3, -1, 2, -1, 3])
2335
+ sage: b.ultra_summit_set_element() # needs sage.libs.braiding
2336
+ (s0*s1*s0*s2*s1, s0^-1*s1^-1*s0^-1*s2^-1*s1^-1*s0^-1*s1*s2*s1^2*s0)
2337
+ """
2338
+ to_uss = send_to_uss(self)
2339
+ B = self.parent()
2340
+ return tuple([B._element_from_libbraiding(b) for b in to_uss])
2341
+
2342
+ def sliding_circuits_element(self) -> tuple:
2343
+ r"""
2344
+ Return an element of the braid's sliding circuits, and the conjugating
2345
+ braid.
2346
+
2347
+ EXAMPLES::
2348
+
2349
+ sage: B = BraidGroup(4)
2350
+ sage: b = B([1, 2, 1, 2, 3, -1, 2, -1, 3])
2351
+ sage: b.sliding_circuits_element() # needs sage.libs.braiding
2352
+ (s0*s1*s0*s2*s1, s0^2*s1*s2)
2353
+ """
2354
+ to_sc = send_to_sc(self)
2355
+ B = self.parent()
2356
+ return tuple([B._element_from_libbraiding(b) for b in to_sc])
2357
+
2358
+ def trajectory(self) -> list:
2359
+ r"""
2360
+ Return the braid's trajectory.
2361
+
2362
+ EXAMPLES::
2363
+
2364
+ sage: B = BraidGroup(4)
2365
+ sage: b = B([1, 2, 1, 2, 3, -1, 2, -1, 3])
2366
+ sage: b.trajectory() # needs sage.libs.braiding
2367
+ [s0^-1*s1^-1*s0^-1*s2^-1*s1^-1*s2*s0*s1*s2*s1*s0^2*s1*s2^2,
2368
+ s0*s1*s2^3,
2369
+ s0*s1*s2*s1^2,
2370
+ s0*s1*s0*s2*s1]
2371
+ """
2372
+ traj = trajectory(self)
2373
+ B = self.parent()
2374
+ return [B._element_from_libbraiding(b) for b in traj]
2375
+
2376
+ def cyclic_slidings(self) -> list:
2377
+ r"""
2378
+ Return the braid's cyclic slidings.
2379
+
2380
+ OUTPUT: The braid's cyclic slidings. Each cyclic sliding is a list of braids.
2381
+
2382
+ EXAMPLES::
2383
+
2384
+ sage: B = BraidGroup(4)
2385
+ sage: b = B([1, 2, 1, 2, 3, -1, 2, 1])
2386
+ sage: b.cyclic_slidings() # needs sage.libs.braiding
2387
+ [[s0*s2*s1*s0*s1*s2, s0*s1*s2*s1*s0^2, s1*s0*s2^2*s1*s0],
2388
+ [s0*s1*s2*s1^2*s0, s0*s1*s2*s1*s0*s2, s1*s0*s2*s0*s1*s2]]
2389
+ """
2390
+ cs = cyclic_slidings(self)
2391
+ B = self.parent()
2392
+ return [[B._element_from_libbraiding(b) for b in t] for t in cs]
2393
+
2394
+
2395
+ class RightQuantumWord:
2396
+ """
2397
+ A right quantum word as in Definition 4.1 of [HL2018]_.
2398
+
2399
+ INPUT:
2400
+
2401
+ - ``words`` -- an element in a suitable free algebra over a Laurent
2402
+ polynomial ring in one variable; this input does not need to be in
2403
+ reduced form, but the monomials for the input can come in any order
2404
+
2405
+ EXAMPLES::
2406
+
2407
+ sage: # needs sage.combinat
2408
+ sage: from sage.groups.braid import RightQuantumWord
2409
+ sage: fig_8 = BraidGroup(3)([-1, 2, -1, 2])
2410
+ sage: (
2411
+ ....: bp_1, cp_1, ap_1,
2412
+ ....: bp_3, cp_3, ap_3,
2413
+ ....: bm_0, cm_0, am_0,
2414
+ ....: bm_2, cm_2, am_2
2415
+ ....: ) = fig_8.deformed_burau_matrix().parent().base_ring().gens()
2416
+ sage: q = bp_1.base_ring().gen()
2417
+ sage: RightQuantumWord(ap_1*cp_1 + q**3*bm_2*bp_1*am_0*cm_0)
2418
+ The right quantum word represented by
2419
+ q*cp_1*ap_1 + q^2*bp_1*cm_0*am_0*bm_2
2420
+ reduced from ap_1*cp_1 + q^3*bm_2*bp_1*am_0*cm_0
2421
+ """
2422
+ def __init__(self, words):
2423
+ r"""
2424
+ Initialize ``self``.
2425
+
2426
+ EXAMPLES::
2427
+
2428
+ sage: # needs sage.combinat
2429
+ sage: from sage.groups.braid import RightQuantumWord
2430
+ sage: fig_8 = BraidGroup(3)([-1, 2, -1, 2])
2431
+ sage: (
2432
+ ....: bp_1, cp_1, ap_1,
2433
+ ....: bp_3, cp_3, ap_3,
2434
+ ....: bm_0, cm_0, am_0,
2435
+ ....: bm_2, cm_2, am_2
2436
+ ....: ) = fig_8.deformed_burau_matrix().parent().base_ring().gens()
2437
+ sage: q = bp_1.base_ring().gen()
2438
+ sage: Q = RightQuantumWord(ap_1*cp_1 + q**3*bm_2*bp_1*am_0*cm_0)
2439
+ sage: TestSuite(Q).run(skip='_test_pickling')
2440
+ """
2441
+ self._algebra = words.parent()
2442
+ self.q = self._algebra.base_ring().gen()
2443
+ self.iq = ~self.q
2444
+ self.R = self._algebra.base_ring()
2445
+ self._unreduced_words = words
2446
+ self._gens = self._algebra._indices.gens()
2447
+ self._minus_begin = min((i for i, gen in enumerate(self._gens) if 'm' in str(gen)),
2448
+ default=len(self._gens)) // 3
2449
+ split = ((g, str(g), i) for i, g in enumerate(self._gens))
2450
+ self._recognize = {g: (s[0], s[1] == 'm', 3 * (i // 3))
2451
+ for g, s, i in split}
2452
+
2453
+ @lazy_attribute
2454
+ def tuples(self):
2455
+ r"""
2456
+ Get a representation of the right quantum word as a ``dict``, with
2457
+ keys monomials in the free algebra represented as tuples and
2458
+ values in elements the Laurent polynomial ring in one variable.
2459
+
2460
+ This is in the reduced form as outlined in Definition 4.1
2461
+ of [HL2018]_.
2462
+
2463
+ OUTPUT: a dict of tuples of ints corresponding to the exponents in the
2464
+ generators with values in the algebra's base ring
2465
+
2466
+ EXAMPLES::
2467
+
2468
+ sage: # needs sage.combinat
2469
+ sage: from sage.groups.braid import RightQuantumWord
2470
+ sage: fig_8 = BraidGroup(3)([-1, 2, -1, 2])
2471
+ sage: (
2472
+ ....: bp_1, cp_1, ap_1,
2473
+ ....: bp_3, cp_3, ap_3,
2474
+ ....: bm_0, cm_0, am_0,
2475
+ ....: bm_2, cm_2, am_2
2476
+ ....: ) = fig_8.deformed_burau_matrix().parent().base_ring().gens()
2477
+ sage: q = bp_1.base_ring().gen()
2478
+ sage: qw = RightQuantumWord(ap_1*cp_1 +
2479
+ ....: q**3*bm_2*bp_1*am_0*cm_0)
2480
+ sage: for key, value in qw.tuples.items():
2481
+ ....: print(key, value)
2482
+ ....:
2483
+ (0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0) q
2484
+ (1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0) q^2
2485
+ """
2486
+ from collections import defaultdict
2487
+ ret = defaultdict(self.R)
2488
+ convert = self._recognize
2489
+ q = self.q
2490
+ iq = self.iq
2491
+ for unreduced_monom, q_power in list(self._unreduced_words):
2492
+ ret_tuple = [0] * len(self._gens)
2493
+ for gen, exp in unreduced_monom:
2494
+ letter, is_minus, index = convert[gen]
2495
+ # This uses the relations in equations (4.1) and (4.2)
2496
+ # of [HL2018]_.
2497
+ if letter == 'a': # is_a
2498
+ ret_tuple[index + 2] += exp
2499
+ elif letter == 'b': # is_b
2500
+ j, k = ret_tuple[index + 1:index + 3]
2501
+ ret_tuple[index] += exp
2502
+ q_power *= q**(2*(k*exp + j*exp)) if is_minus else iq**(2*j*exp)
2503
+ else: # is_c
2504
+ k = ret_tuple[index + 2]
2505
+ ret_tuple[index + 1] += exp
2506
+ q_power *= iq**(k*exp) if is_minus else q**(k*exp)
2507
+ ret[tuple(ret_tuple)] += q_power
2508
+ return ret
2509
+
2510
+ def reduced_word(self):
2511
+ r"""
2512
+ Return the (reduced) right quantum word.
2513
+
2514
+ OUTPUT: an element in the free algebra
2515
+
2516
+ EXAMPLES::
2517
+
2518
+ sage: # needs sage.combinat
2519
+ sage: from sage.groups.braid import RightQuantumWord
2520
+ sage: fig_8 = BraidGroup(3)([-1, 2, -1, 2])
2521
+ sage: (
2522
+ ....: bp_1, cp_1, ap_1,
2523
+ ....: bp_3, cp_3, ap_3,
2524
+ ....: bm_0, cm_0, am_0,
2525
+ ....: bm_2, cm_2, am_2
2526
+ ....: ) = fig_8.deformed_burau_matrix().parent().base_ring().gens()
2527
+ sage: q = bp_1.base_ring().gen()
2528
+ sage: qw = RightQuantumWord(ap_1*cp_1 +
2529
+ ....: q**3*bm_2*bp_1*am_0*cm_0)
2530
+ sage: qw.reduced_word()
2531
+ q*cp_1*ap_1 + q^2*bp_1*cm_0*am_0*bm_2
2532
+
2533
+ TESTS:
2534
+
2535
+ Testing the equations (4.1) and (4.2) in [HL2018]_::
2536
+
2537
+ sage: # needs sage.combinat
2538
+ sage: RightQuantumWord(ap_3*bp_3).reduced_word()
2539
+ bp_3*ap_3
2540
+ sage: RightQuantumWord(ap_3*cp_3).reduced_word()
2541
+ q*cp_3*ap_3
2542
+ sage: RightQuantumWord(cp_3*bp_3).reduced_word()
2543
+ (q^-2)*bp_3*cp_3
2544
+ sage: RightQuantumWord(am_2*bm_2).reduced_word()
2545
+ q^2*bm_2*am_2
2546
+ sage: RightQuantumWord(am_2*cm_2).reduced_word()
2547
+ (q^-1)*cm_2*am_2
2548
+ sage: RightQuantumWord(cm_2*bm_2).reduced_word()
2549
+ q^2*bm_2*cm_2
2550
+
2551
+ .. TODO::
2552
+
2553
+ Parallelize this function, calculating all summands in the sum
2554
+ in parallel.
2555
+ """
2556
+ M = self._algebra._indices
2557
+
2558
+ def tuple_to_word(q_tuple):
2559
+ return M.prod(self._gens[i]**exp
2560
+ for i, exp in enumerate(q_tuple))
2561
+
2562
+ ret = {tuple_to_word(q_tuple): q_factor
2563
+ for q_tuple, q_factor in self.tuples.items() if q_factor}
2564
+ return self._algebra._from_dict(ret, remove_zeros=False)
2565
+
2566
+ def eps(self, N):
2567
+ r"""
2568
+ Evaluate the map `\mathcal{E}_N` for a braid.
2569
+
2570
+ INPUT:
2571
+
2572
+ - ``N`` -- integer; the number of colors
2573
+
2574
+ EXAMPLES::
2575
+
2576
+ sage: # needs sage.combinat
2577
+ sage: from sage.groups.braid import RightQuantumWord
2578
+ sage: B = BraidGroup(3)
2579
+ sage: b = B([1,-2,1,2])
2580
+ sage: db = b.deformed_burau_matrix()
2581
+ sage: q = db.parent().base_ring().base_ring().gen()
2582
+ sage: (bp_0, cp_0, ap_0,
2583
+ ....: bp_2, cp_2, ap_2,
2584
+ ....: bp_3, cp_3, ap_3,
2585
+ ....: bm_1, cm_1, am_1) = db.parent().base_ring().gens()
2586
+ sage: rqw = RightQuantumWord(
2587
+ ....: q^3*bp_2*bp_0*ap_0 + q*ap_3*bm_1*am_1*bp_0)
2588
+ sage: rqw.eps(3)
2589
+ -q^-1 + 2*q - q^5
2590
+ sage: rqw.eps(2)
2591
+ -1 + 2*q - q^2 + q^3 - q^4
2592
+
2593
+ TESTS::
2594
+
2595
+ sage: rqw.eps(1) # needs sage.combinat
2596
+ 0
2597
+
2598
+ .. TODO::
2599
+
2600
+ Parallelize this function, calculating all summands in the sum
2601
+ in parallel.
2602
+ """
2603
+ def eps_monom(q_tuple):
2604
+ r"""
2605
+ Evaluate the map `\mathcal{E}_N` for a single mononial.
2606
+ """
2607
+ q = self.q
2608
+ ret_q = q**sum((N - 1 - q_tuple[3*i + 2])*q_tuple[3*i + 1]
2609
+ for i in range(self._minus_begin))
2610
+ ret_q *= q**sum((N - 1)*(-q_tuple[rj])
2611
+ for rj in range(self._minus_begin * 3 + 1,
2612
+ len(q_tuple), 3))
2613
+ ret_q *= prod(prod(1 - q**(N - 1 - q_tuple[3*i + 1] - h)
2614
+ for h in range(q_tuple[3*i + 2]))
2615
+ for i in range(self._minus_begin))
2616
+ ret_q *= prod(prod(1 - q**(q_tuple[3*j + 1] + k + 1 - N)
2617
+ for k in range(q_tuple[3*j + 2]))
2618
+ for j in range(self._minus_begin,
2619
+ len(q_tuple)//3))
2620
+ return ret_q
2621
+
2622
+ return sum(q_factor * eps_monom(q_tuple)
2623
+ for q_tuple, q_factor in self.tuples.items())
2624
+
2625
+ def __repr__(self) -> str:
2626
+ r"""
2627
+ String representation of ``self``.
2628
+
2629
+ EXAMPLES::
2630
+
2631
+ sage: # needs sage.combinat
2632
+ sage: from sage.groups.braid import RightQuantumWord
2633
+ sage: b = BraidGroup(3)([1,2,-1,2,-1])
2634
+ sage: db = b.deformed_burau_matrix(); db[2,2]
2635
+ cp_1*am_2*bp_3
2636
+ sage: RightQuantumWord(db[2,2])
2637
+ The right quantum word represented by cp_1*bp_3*am_2 reduced from
2638
+ cp_1*am_2*bp_3
2639
+ """
2640
+ return ('The right quantum word represented by '
2641
+ + f'{str(self.reduced_word())} reduced from '
2642
+ + f'{str(self._unreduced_words)}')
2643
+
2644
+
2645
+ class BraidGroup_class(FiniteTypeArtinGroup):
2646
+ """
2647
+ The braid group on `n` strands.
2648
+
2649
+ EXAMPLES::
2650
+
2651
+ sage: B1 = BraidGroup(5)
2652
+ sage: B1
2653
+ Braid group on 5 strands
2654
+ sage: B2 = BraidGroup(3)
2655
+ sage: B1 == B2
2656
+ False
2657
+ sage: B2 is BraidGroup(3)
2658
+ True
2659
+ """
2660
+ Element = Braid
2661
+
2662
+ def __init__(self, names) -> None:
2663
+ """
2664
+ Python constructor.
2665
+
2666
+ INPUT:
2667
+
2668
+ - ``names`` -- tuple of strings; the names of the generators
2669
+
2670
+ TESTS::
2671
+
2672
+ sage: B1 = BraidGroup(5) # indirect doctest
2673
+ sage: B1
2674
+ Braid group on 5 strands
2675
+ sage: TestSuite(B1).run() # needs sage.libs.braiding
2676
+ sage: B1.category()
2677
+ Category of infinite groups
2678
+
2679
+ Check that :issue:`14081` is fixed::
2680
+
2681
+ sage: BraidGroup(2)
2682
+ Braid group on 2 strands
2683
+ sage: BraidGroup(('a',))
2684
+ Braid group on 2 strands
2685
+
2686
+ Check that :issue:`15505` is fixed::
2687
+
2688
+ sage: B = BraidGroup(4)
2689
+ sage: B.relations()
2690
+ (s0*s1*s0*s1^-1*s0^-1*s1^-1, s0*s2*s0^-1*s2^-1, s1*s2*s1*s2^-1*s1^-1*s2^-1)
2691
+ sage: B = BraidGroup('a,b,c,d,e,f')
2692
+ sage: B.relations()
2693
+ (a*b*a*b^-1*a^-1*b^-1,
2694
+ a*c*a^-1*c^-1,
2695
+ a*d*a^-1*d^-1,
2696
+ a*e*a^-1*e^-1,
2697
+ a*f*a^-1*f^-1,
2698
+ b*c*b*c^-1*b^-1*c^-1,
2699
+ b*d*b^-1*d^-1,
2700
+ b*e*b^-1*e^-1,
2701
+ b*f*b^-1*f^-1,
2702
+ c*d*c*d^-1*c^-1*d^-1,
2703
+ c*e*c^-1*e^-1,
2704
+ c*f*c^-1*f^-1,
2705
+ d*e*d*e^-1*d^-1*e^-1,
2706
+ d*f*d^-1*f^-1,
2707
+ e*f*e*f^-1*e^-1*f^-1)
2708
+
2709
+ sage: BraidGroup([])
2710
+ Traceback (most recent call last):
2711
+ ...
2712
+ ValueError: the number of strands must be at least 2
2713
+ """
2714
+ n = len(names)
2715
+ # n is the number of generators, not the number of strands
2716
+ # see issue 14081
2717
+ if n < 1:
2718
+ raise ValueError("the number of strands must be at least 2")
2719
+ free_group = FreeGroup(names)
2720
+ rels = []
2721
+ for i in range(1, n):
2722
+ rels.append(free_group([i, i + 1, i, -i - 1, -i, -i - 1]))
2723
+ rels.extend(free_group([i, j, -i, -j])
2724
+ for j in range(i + 2, n + 1))
2725
+ cat = Groups().Infinite()
2726
+ FinitelyPresentedGroup.__init__(self, free_group, tuple(rels),
2727
+ category=cat)
2728
+ self._nstrands = n + 1
2729
+ self._coxeter_group = Permutations(self._nstrands)
2730
+
2731
+ # For caching TL_representation()
2732
+ self._TL_representation_dict = {}
2733
+
2734
+ # For caching hermitian form of unitary Burau representation
2735
+ self._hermitian_form = None
2736
+
2737
+ def __reduce__(self) -> tuple:
2738
+ """
2739
+ TESTS::
2740
+
2741
+ sage: B = BraidGroup(3)
2742
+ sage: B.__reduce__()
2743
+ (<class 'sage.groups.braid.BraidGroup_class'>, (('s0', 's1'),))
2744
+ sage: B = BraidGroup(3, 'sigma')
2745
+ sage: B.__reduce__()
2746
+ (<class 'sage.groups.braid.BraidGroup_class'>, (('sigma0', 'sigma1'),))
2747
+ """
2748
+ return (BraidGroup_class, (self.variable_names(), ))
2749
+
2750
+ def _repr_(self) -> str:
2751
+ """
2752
+ Return a string representation.
2753
+
2754
+ TESTS::
2755
+
2756
+ sage: B1 = BraidGroup(5)
2757
+ sage: B1 # indirect doctest
2758
+ Braid group on 5 strands
2759
+ """
2760
+ return "Braid group on %s strands" % self._nstrands
2761
+
2762
+ def cardinality(self):
2763
+ """
2764
+ Return the number of group elements.
2765
+
2766
+ OUTPUT: Infinity
2767
+
2768
+ TESTS::
2769
+
2770
+ sage: B1 = BraidGroup(5)
2771
+ sage: B1.cardinality()
2772
+ +Infinity
2773
+ """
2774
+ from sage.rings.infinity import Infinity
2775
+ return Infinity
2776
+
2777
+ order = cardinality
2778
+
2779
+ def as_permutation_group(self):
2780
+ """
2781
+ Return an isomorphic permutation group.
2782
+
2783
+ OUTPUT: this raises a :exc:`ValueError` error since braid groups
2784
+ are infinite
2785
+
2786
+ TESTS::
2787
+
2788
+ sage: B = BraidGroup(4, 'g')
2789
+ sage: B.as_permutation_group()
2790
+ Traceback (most recent call last):
2791
+ ...
2792
+ ValueError: the group is infinite
2793
+ """
2794
+ raise ValueError("the group is infinite")
2795
+
2796
+ def strands(self):
2797
+ """
2798
+ Return the number of strands.
2799
+
2800
+ OUTPUT: integer
2801
+
2802
+ EXAMPLES::
2803
+
2804
+ sage: B = BraidGroup(4)
2805
+ sage: B.strands()
2806
+ 4
2807
+ """
2808
+ return self._nstrands
2809
+
2810
+ def _element_constructor_(self, x):
2811
+ """
2812
+ TESTS::
2813
+
2814
+ sage: B = BraidGroup(4)
2815
+ sage: B([1, 2, 3]) # indirect doctest
2816
+ s0*s1*s2
2817
+ sage: p = Permutation([3,1,2,4]); B(p)
2818
+ s0*s1
2819
+ sage: q = SymmetricGroup(4)((1,2)); B(q)
2820
+ s0
2821
+ """
2822
+ if not isinstance(x, (tuple, list)):
2823
+ if isinstance(x, (SymmetricGroupElement, Permutation)):
2824
+ x = self._standard_lift_Tietze(x)
2825
+ return self.element_class(self, x)
2826
+
2827
+ def _an_element_(self):
2828
+ """
2829
+ Return an element of the braid group.
2830
+
2831
+ This is used both for illustration and testing purposes.
2832
+
2833
+ EXAMPLES::
2834
+
2835
+ sage: B = BraidGroup(2)
2836
+ sage: B.an_element()
2837
+ s
2838
+ """
2839
+ return self.gen(0)
2840
+
2841
+ def some_elements(self) -> list:
2842
+ """
2843
+ Return a list of some elements of the braid group.
2844
+
2845
+ This is used both for illustration and testing purposes.
2846
+
2847
+ EXAMPLES::
2848
+
2849
+ sage: B = BraidGroup(3)
2850
+ sage: B.some_elements()
2851
+ [s0, s0*s1, (s0*s1)^3]
2852
+ """
2853
+ elements_list = [self.gen(0)]
2854
+ elements_list.append(self(range(1, self.strands())))
2855
+ elements_list.append(elements_list[-1]**self.strands())
2856
+ return elements_list
2857
+
2858
+ def _standard_lift_Tietze(self, p) -> tuple:
2859
+ """
2860
+ Helper for :meth:`_standard_lift_Tietze`.
2861
+
2862
+ INPUT:
2863
+
2864
+ - ``p`` -- a permutation
2865
+
2866
+ The standard lift of a permutation is the only braid with
2867
+ the following properties:
2868
+
2869
+ - The braid induces the given permutation.
2870
+
2871
+ - The braid is positive (that is, it can be written without
2872
+ using the inverses of the generators).
2873
+
2874
+ - Every two strands cross each other at most once.
2875
+
2876
+ OUTPUT: a shortest word that represents the braid, in Tietze list form
2877
+
2878
+ EXAMPLES::
2879
+
2880
+ sage: B = BraidGroup(5)
2881
+ sage: P = Permutation([5, 3, 1, 2, 4])
2882
+ sage: B._standard_lift_Tietze(P)
2883
+ (1, 2, 3, 4, 1, 2)
2884
+ """
2885
+ G = SymmetricGroup(self.strands())
2886
+ return tuple(G(p).reduced_word())
2887
+
2888
+ @cached_method
2889
+ def _links_gould_representation(self, symbolics=False):
2890
+ """
2891
+ Compute the representation matrices of the generators of the R-matrix
2892
+ representation being attached the quantum superalgebra `sl_q(2|1)`.
2893
+
2894
+ INPUT:
2895
+
2896
+ - ``symbolics`` -- boolean (default: ``False``); if set to ``True`` the
2897
+ coefficients will be contained in the symbolic ring. Per default they
2898
+ are elements of a quotient ring of a three variate Laurent polynomial
2899
+ ring.
2900
+
2901
+ OUTPUT:
2902
+
2903
+ A tuple of length equal to the number `n` of strands. The first `n-1`
2904
+ items are pairs of the representation matrices of the generators and
2905
+ their inverses. The last item is the quantum trace operator of the
2906
+ `n`-fold tensorproduct of the natural module.
2907
+
2908
+ TESTS::
2909
+
2910
+ sage: # needs sage.libs.singular
2911
+ sage: B = BraidGroup(3)
2912
+ sage: g1, g2, mu3 = B._links_gould_representation()
2913
+ sage: R1, R1I = g1
2914
+ sage: R2, R2I = g2
2915
+ sage: R1*R2*R1 == R2*R1*R2
2916
+ True
2917
+ """
2918
+ from sage.matrix.constructor import matrix
2919
+ n = self.strands()
2920
+ d = 4 # dimension of the natural module
2921
+ from sage.matrix.special import diagonal_matrix
2922
+ if symbolics:
2923
+ from sage.misc.functional import sqrt
2924
+ from sage.symbolic.ring import SR as BR
2925
+ t0, t1 = BR.var('t0, t1')
2926
+ s0 = sqrt(t0)
2927
+ s1 = sqrt(t1)
2928
+ Y = sqrt(-(t0 - 1)*(t1 - 1))
2929
+ sparse = False
2930
+ else:
2931
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
2932
+ LR = LaurentPolynomialRing(ZZ, 's0r, s1r')
2933
+ s0r, s1r = LR.gens()
2934
+ PR = PolynomialRing(LR, 'Yr')
2935
+ Yr = PR.gen()
2936
+ pqr = Yr**2 + (s0r**2 - 1) * (s1r**2 - 1)
2937
+ BR = PR.quotient_ring(pqr)
2938
+ s0 = BR(s0r)
2939
+ s1 = BR(s1r)
2940
+ t0 = BR(s0r**2)
2941
+ t1 = BR(s1r**2)
2942
+ Y = BR(Yr)
2943
+ sparse = True
2944
+
2945
+ # degree one quantum trace operator as defined in I. Marin
2946
+ mu = diagonal_matrix([t0**(-1), - t1, - t0**(-1), t1])
2947
+ if n == 2:
2948
+ # R-Matrix taken from I. Marin
2949
+ R = matrix(BR, {(0, 0): t0, (1, 4): s0, (2, 8): s0, (3, 12): 1,
2950
+ (4, 1): s0, (4, 4): t0 - 1, (5, 5): -1, (6, 6): t0*t1 - 1,
2951
+ (6, 9): -s0*s1, (6, 12): -Y*s0*s1, (7, 13): s1, (8, 2): s0,
2952
+ (8, 8): t0 - 1, (9, 6): -s0*s1, (9, 12): Y, (10, 10): -1,
2953
+ (11, 14): s1, (12, 3): 1, (12, 6): -Y*s0*s1, (12, 9): Y,
2954
+ (12, 12): -(t0 - 1)*(t1 - 1), (13, 7): s1, (13, 13): t1 - 1,
2955
+ (14, 11): s1, (14, 14): t1 - 1, (15, 15): t1}, sparse=sparse)
2956
+ RI = (~t0 + ~t1)*(1 + R) - ~t0*~t1*(R + R**2) - 1
2957
+
2958
+ # quantum trace operator on two fold tensor space
2959
+ E = mu.parent().one()
2960
+ mu2 = E.tensor_product(mu)
2961
+ return ([R, RI], mu2)
2962
+
2963
+ from sage.matrix.matrix_space import MatrixSpace
2964
+ Ed = MatrixSpace(BR, d, d, sparse=sparse).one()
2965
+ BGsub = BraidGroup(n-1)
2966
+ if n > 3:
2967
+ BG2 = BraidGroup(2)
2968
+ else:
2969
+ BG2 = BGsub
2970
+ g1 = list(BG2._links_gould_representation(symbolics=symbolics))
2971
+ mu2 = g1.pop()
2972
+ R, RI = g1[0]
2973
+ lg_sub = list(BGsub._links_gould_representation(symbolics=symbolics))
2974
+ musub = lg_sub.pop()
2975
+
2976
+ # extend former generators
2977
+ lg = [(g.tensor_product(Ed), gi.tensor_product(Ed)) for g, gi in lg_sub]
2978
+ En = MatrixSpace(BR, d**(n-2), d**(n-2), sparse=sparse).one()
2979
+
2980
+ # define new generator
2981
+ gn = En.tensor_product(R)
2982
+ gni = En.tensor_product(RI)
2983
+
2984
+ # quantum trace operator on n fold tensor space
2985
+ mun = musub.tensor_product(mu)
2986
+ return tuple(lg + [(gn, gni), mun])
2987
+
2988
+ @cached_method
2989
+ def _LKB_matrix_(self, braid, variab):
2990
+ """
2991
+ Compute the Lawrence-Krammer-Bigelow representation matrix.
2992
+
2993
+ The variables of the matrix must be given. This actual
2994
+ computation is done in this helper method for caching
2995
+ purposes.
2996
+
2997
+ INPUT:
2998
+
2999
+ - ``braid`` -- tuple of integers; the Tietze list of the
3000
+ braid
3001
+
3002
+ - ``variab`` -- string. The names of the variables that will
3003
+ appear in the matrix. They must be given as a string,
3004
+ separated by a comma
3005
+
3006
+ OUTPUT: the LKB matrix of the braid, with respect to the variables
3007
+
3008
+ TESTS::
3009
+
3010
+ sage: B = BraidGroup(3)
3011
+ sage: B._LKB_matrix_((2, 1, 2), 'x, y')
3012
+ [ 0 -x^4*y + x^3*y -x^4*y]
3013
+ [ 0 -x^3*y 0]
3014
+ [ -x^2*y x^3*y - x^2*y 0]
3015
+ sage: B._LKB_matrix_((1, 2, 1), 'x, y')
3016
+ [ 0 -x^4*y + x^3*y -x^4*y]
3017
+ [ 0 -x^3*y 0]
3018
+ [ -x^2*y x^3*y - x^2*y 0]
3019
+ sage: B._LKB_matrix_((-1, -2, -1, 2, 1, 2), 'x, y')
3020
+ [1 0 0]
3021
+ [0 1 0]
3022
+ [0 0 1]
3023
+ """
3024
+ n = self.strands()
3025
+ if len(braid) > 1:
3026
+ A = self._LKB_matrix_(braid[:1], variab)
3027
+ for i in braid[1:]:
3028
+ A = A*self._LKB_matrix_((i,), variab)
3029
+ return A
3030
+ n2 = [set(X) for X in combinations(range(n), 2)]
3031
+ R = LaurentPolynomialRing(ZZ, variab)
3032
+ q = R.gens()[0]
3033
+ t = R.gens()[1]
3034
+ if not braid:
3035
+ return identity_matrix(R, len(n2), sparse=True)
3036
+ A = matrix(R, len(n2), sparse=True)
3037
+ if braid[0] > 0:
3038
+ i = braid[0] - 1
3039
+ for m in range(len(n2)):
3040
+ j = min(n2[m])
3041
+ k = max(n2[m])
3042
+ if i == j-1:
3043
+ A[n2.index(Set([i, k])), m] = q
3044
+ A[n2.index(Set([i, j])), m] = q*q-q
3045
+ A[n2.index(Set([j, k])), m] = 1-q
3046
+ elif i == j and not j == k-1:
3047
+ A[n2.index(Set([j, k])), m] = 0
3048
+ A[n2.index(Set([j+1, k])), m] = 1
3049
+ elif k-1 == i and not k-1 == j:
3050
+ A[n2.index(Set([j, i])), m] = q
3051
+ A[n2.index(Set([j, k])), m] = 1-q
3052
+ A[n2.index(Set([i, k])), m] = (1-q)*q*t
3053
+ elif i == k:
3054
+ A[n2.index(Set([j, k])), m] = 0
3055
+ A[n2.index(Set([j, k+1])), m] = 1
3056
+ elif i == j and j == k-1:
3057
+ A[n2.index(Set([j, k])), m] = -t*q*q
3058
+ else:
3059
+ A[n2.index(Set([j, k])), m] = 1
3060
+ return A
3061
+ else:
3062
+ i = -braid[0]-1
3063
+ for m in range(len(n2)):
3064
+ j = min(n2[m])
3065
+ k = max(n2[m])
3066
+ if i == j-1:
3067
+ A[n2.index(Set([j-1, k])), m] = 1
3068
+ elif i == j and not j == k-1:
3069
+ A[n2.index(Set([j+1, k])), m] = q**(-1)
3070
+ A[n2.index(Set([j, k])), m] = 1-q**(-1)
3071
+ A[n2.index(Set([j, j+1])), m] = t**(-1)*q**(-1)-t**(-1)*q**(-2)
3072
+ elif k-1 == i and not k-1 == j:
3073
+ A[n2.index(Set([j, k-1])), m] = 1
3074
+ elif i == k:
3075
+ A[n2.index(Set([j, k+1])), m] = q**(-1)
3076
+ A[n2.index(Set([j, k])), m] = 1-q**(-1)
3077
+ A[n2.index(Set([k, k+1])), m] = -q**(-1)+q**(-2)
3078
+ elif i == j and j == k-1:
3079
+ A[n2.index(Set([j, k])), m] = -t**(-1)*q**(-2)
3080
+ else:
3081
+ A[n2.index(Set([j, k])), m] = 1
3082
+ return A
3083
+
3084
+ def dimension_of_TL_space(self, drain_size):
3085
+ """
3086
+ Return the dimension of a particular Temperley--Lieb representation
3087
+ summand of ``self``.
3088
+
3089
+ Following the notation of :meth:`TL_basis_with_drain`, the summand
3090
+ is the one corresponding to the number of drains being fixed to be
3091
+ ``drain_size``.
3092
+
3093
+ INPUT:
3094
+
3095
+ - ``drain_size`` -- integer between 0 and the number of strands
3096
+ (both inclusive)
3097
+
3098
+ EXAMPLES:
3099
+
3100
+ Calculation of the dimension of the representation of `B_8`
3101
+ corresponding to having `2` drains::
3102
+
3103
+ sage: B = BraidGroup(8)
3104
+ sage: B.dimension_of_TL_space(2)
3105
+ 28
3106
+
3107
+ The direct sum of endomorphism spaces of these vector spaces make up
3108
+ the entire Temperley--Lieb algebra::
3109
+
3110
+ sage: import sage.combinat.diagram_algebras as da # needs sage.combinat
3111
+ sage: B = BraidGroup(6)
3112
+ sage: dimensions = [B.dimension_of_TL_space(d)**2 for d in [0, 2, 4, 6]]
3113
+ sage: total_dim = sum(dimensions)
3114
+ sage: total_dim == len(list(da.temperley_lieb_diagrams(6))) # long time, needs sage.combinat
3115
+ True
3116
+ """
3117
+ n = self.strands()
3118
+ if drain_size > n:
3119
+ raise ValueError("number of drains must not exceed number of strands")
3120
+ if (n + drain_size) % 2 == 1:
3121
+ raise ValueError("parity of strands and drains must agree")
3122
+
3123
+ m = (n - drain_size) // 2
3124
+ return Integer(n-1).binomial(m) - Integer(n-1).binomial(m - 2)
3125
+
3126
+ def TL_basis_with_drain(self, drain_size):
3127
+ """
3128
+ Return a basis of a summand of the Temperley--Lieb--Jones
3129
+ representation of ``self``.
3130
+
3131
+ The basis elements are given by non-intersecting pairings of `n+d`
3132
+ points in a square with `n` points marked 'on the top' and `d` points
3133
+ 'on the bottom' so that every bottom point is paired with a top point.
3134
+ Here, `n` is the number of strands of the braid group, and `d` is
3135
+ specified by ``drain_size``.
3136
+
3137
+ A basis element is specified as a list of integers obtained by
3138
+ considering the pairings as obtained as the 'highest term' of
3139
+ trivalent trees marked by Jones--Wenzl projectors (see e.g. [Wan2010]_).
3140
+ In practice, this is a list of nonnegative integers whose first
3141
+ element is ``drain_size``, whose last element is `0`, and satisfying
3142
+ that consecutive integers have difference `1`. Moreover, the length
3143
+ of each basis element is `n + 1`.
3144
+
3145
+ Given these rules, the list of lists is constructed recursively
3146
+ in the natural way.
3147
+
3148
+ INPUT:
3149
+
3150
+ - ``drain_size`` -- integer between 0 and the number of strands
3151
+ (both inclusive)
3152
+
3153
+ OUTPUT: list of basis elements, each of which is a list of integers
3154
+
3155
+ EXAMPLES:
3156
+
3157
+ We calculate the basis for the appropriate vector space for `B_5` when
3158
+ `d = 3`::
3159
+
3160
+ sage: B = BraidGroup(5)
3161
+ sage: B.TL_basis_with_drain(3)
3162
+ [[3, 4, 3, 2, 1, 0],
3163
+ [3, 2, 3, 2, 1, 0],
3164
+ [3, 2, 1, 2, 1, 0],
3165
+ [3, 2, 1, 0, 1, 0]]
3166
+
3167
+ The number of basis elements hopefully corresponds to the general
3168
+ formula for the dimension of the representation spaces::
3169
+
3170
+ sage: B = BraidGroup(10)
3171
+ sage: d = 2
3172
+ sage: B.dimension_of_TL_space(d) == len(B.TL_basis_with_drain(d))
3173
+ True
3174
+ """
3175
+ def fill_out_forest(forest, treesize):
3176
+ # The basis elements are built recursively using this function,
3177
+ # which takes a collection of partial basis elements, given in
3178
+ # terms of trivalent trees (i.e. a 'forest') and extends each of
3179
+ # the trees by one branch.
3180
+ if not forest:
3181
+ raise ValueError("forest has to start with a tree")
3182
+ if forest[0][0] + treesize % 2 == 0:
3183
+ raise ValueError("parity mismatch in forest creation")
3184
+ # Loop over all trees
3185
+ newforest = list(forest)
3186
+ for tree in forest:
3187
+ if len(tree) < treesize:
3188
+ newtreeup = list(tree)
3189
+ newtreedown = list(tree)
3190
+ newforest.remove(tree) # Cut down the original tree
3191
+ # Add two greater trees, admissibly. We need to check two
3192
+ # things to ensure that the tree will eventually define a
3193
+ # basis elements: that its 'colour' is not too large, and
3194
+ # that it is positive.
3195
+ if tree[-1] < treesize - len(tree) + 1:
3196
+ newtreeup.append(tree[-1] + 1)
3197
+ newforest.append(newtreeup)
3198
+ if tree[-1] > 0:
3199
+ newtreedown.append(tree[-1] - 1)
3200
+ newforest.append(newtreedown)
3201
+ # Are we there yet?
3202
+ if len(newforest[0]) == treesize:
3203
+ return newforest
3204
+ return fill_out_forest(newforest, treesize)
3205
+
3206
+ n = self.strands()
3207
+ if drain_size > n:
3208
+ raise ValueError("number of drains must not exceed number of strands")
3209
+ if (n + drain_size) % 2 == 1:
3210
+ raise ValueError("parity of strands and drains must agree")
3211
+
3212
+ # We can now start the process: all we know is that our basis elements
3213
+ # have a drain size of d, so we use fill_out_forest to build all basis
3214
+ # elements out of this
3215
+ basis = [[drain_size]]
3216
+ forest = fill_out_forest(basis, n-1)
3217
+ for tree in forest:
3218
+ tree.extend([1, 0])
3219
+ return forest
3220
+
3221
+ @cached_method
3222
+ def _TL_action(self, drain_size):
3223
+ """
3224
+ Return a matrix representing the action of cups and caps on
3225
+ Temperley--Lieb diagrams corresponding to ``self``.
3226
+
3227
+ The action space is the space of non-crossing diagrams of `n+d`
3228
+ points, where `n` is the number of strands, and `d` is specified by
3229
+ ``drain_size``. As in :meth:`TL_basis_with_drain`, we put certain
3230
+ constraints on the diagrams.
3231
+
3232
+ We essentially calculate the action of the TL-algebra generators
3233
+ `e_i` on the algebra itself: the action of `e_i` on one of our basis
3234
+ diagrams is itself a basis diagram, and ``auxmat`` will store the
3235
+ index of this new basis diagram.
3236
+
3237
+ In some cases, the new diagram will connect two bottom points which
3238
+ we explicitly disallow (as such a diagram is not one of our basis
3239
+ elements). In this case, the corresponding ``auxmat`` entry will
3240
+ be `-1`.
3241
+
3242
+ This is used in :meth:`TL_representation` and could be included
3243
+ entirely in that method. They are split for purposes of caching.
3244
+
3245
+ INPUT:
3246
+
3247
+ - ``drain_size`` -- integer between 0 and the number of strands
3248
+ (both inclusive)
3249
+
3250
+ EXAMPLES::
3251
+
3252
+ sage: B = BraidGroup(4)
3253
+ sage: B._TL_action(2)
3254
+ [ 0 0 -1]
3255
+ [ 1 1 1]
3256
+ [-1 2 2]
3257
+ sage: B._TL_action(0)
3258
+ [1 1]
3259
+ [0 0]
3260
+ [1 1]
3261
+ sage: B = BraidGroup(6)
3262
+ sage: B._TL_action(2)
3263
+ [ 1 1 2 3 1 2 3 -1 -1]
3264
+ [ 0 0 5 6 5 5 6 5 6]
3265
+ [ 1 1 1 -1 4 4 8 8 8]
3266
+ [ 5 2 2 2 5 5 5 7 7]
3267
+ [-1 -1 3 3 8 6 6 8 8]
3268
+ """
3269
+ n = self.strands()
3270
+ basis = self.TL_basis_with_drain(drain_size)
3271
+ auxmat = matrix(n - 1, len(basis))
3272
+ for i in range(1, n): # For each of the e_i
3273
+ for v, tree in enumerate(basis): # For each basis element
3274
+ if tree[i - 1] < tree[i] and tree[i + 1] < tree[i]:
3275
+ # Here, for instance, we've created an unknot.
3276
+ auxmat[i-1, v] = v
3277
+ if tree[i-1] > tree[i] and tree[i+1] > tree[i]:
3278
+ newtree = list(tree)
3279
+ newtree[i] += 2
3280
+ auxmat[i-1, v] = basis.index(newtree)
3281
+ if tree[i-1] > tree[i] and tree[i+1] < tree[i]:
3282
+ newtree = list(tree)
3283
+ newtree[i-1] -= 2
3284
+ j = 2
3285
+ while newtree[i-j] != newtree[i] and i-j >= 0:
3286
+ newtree[i-j] -= 2
3287
+ j += 1
3288
+ if newtree in basis:
3289
+ auxmat[i-1, v] = basis.index(newtree)
3290
+ else:
3291
+ auxmat[i-1, v] = -1
3292
+ if tree[i-1] < tree[i] and tree[i+1] > tree[i]:
3293
+ newtree = list(tree)
3294
+ newtree[i+1] -= 2
3295
+ j = 2
3296
+ while newtree[i+j] != newtree[i] and i+j <= n:
3297
+ newtree[i+j] -= 2
3298
+ j += 1
3299
+ if newtree in basis:
3300
+ auxmat[i-1, v] = basis.index(newtree)
3301
+ else:
3302
+ auxmat[i-1, v] = -1
3303
+ return auxmat
3304
+
3305
+ def TL_representation(self, drain_size, variab=None):
3306
+ r"""
3307
+ Return representation matrices of the Temperley--Lieb--Jones
3308
+ representation of standard braid group generators and inverses
3309
+ of ``self``.
3310
+
3311
+ The basis is given by non-intersecting pairings of `(n+d)` points,
3312
+ where `n` is the number of strands, and `d` is given by
3313
+ ``drain_size``, and the pairings satisfy certain rules. See
3314
+ :meth:`TL_basis_with_drain()` for details. This basis has
3315
+ the useful property that all resulting entries can be regarded as
3316
+ Laurent polynomials.
3317
+
3318
+ We use the convention that the eigenvalues of the standard generators
3319
+ are `1` and `-A^4`, where `A` is the generator of the Laurent
3320
+ polynomial ring.
3321
+
3322
+ When `d = n - 2` and the variables are picked appropriately, the
3323
+ resulting representation is equivalent to the reduced Burau
3324
+ representation. When `d = n`, the resulting representation is
3325
+ trivial and 1-dimensional.
3326
+
3327
+ INPUT:
3328
+
3329
+ - ``drain_size`` -- integer between 0 and the number of strands
3330
+ (both inclusive)
3331
+ - ``variab`` -- variable (default: ``None``); the variable in the
3332
+ entries of the matrices; if ``None``, then use a default variable
3333
+ in `\ZZ[A,A^{-1}]`
3334
+
3335
+ OUTPUT: list of matrices corresponding to the representations of each
3336
+ of the standard generators and their inverses
3337
+
3338
+ EXAMPLES::
3339
+
3340
+ sage: B = BraidGroup(4)
3341
+ sage: B.TL_representation(0)
3342
+ [(
3343
+ [ 1 0] [ 1 0]
3344
+ [ A^2 -A^4], [ A^-2 -A^-4]
3345
+ ),
3346
+ (
3347
+ [-A^4 A^2] [-A^-4 A^-2]
3348
+ [ 0 1], [ 0 1]
3349
+ ),
3350
+ (
3351
+ [ 1 0] [ 1 0]
3352
+ [ A^2 -A^4], [ A^-2 -A^-4]
3353
+ )]
3354
+ sage: R.<A> = LaurentPolynomialRing(GF(2))
3355
+ sage: B.TL_representation(0, variab=A)
3356
+ [(
3357
+ [ 1 0] [ 1 0]
3358
+ [A^2 A^4], [A^-2 A^-4]
3359
+ ),
3360
+ (
3361
+ [A^4 A^2] [A^-4 A^-2]
3362
+ [ 0 1], [ 0 1]
3363
+ ),
3364
+ (
3365
+ [ 1 0] [ 1 0]
3366
+ [A^2 A^4], [A^-2 A^-4]
3367
+ )]
3368
+ sage: B = BraidGroup(8)
3369
+ sage: B.TL_representation(8)
3370
+ [([1], [1]),
3371
+ ([1], [1]),
3372
+ ([1], [1]),
3373
+ ([1], [1]),
3374
+ ([1], [1]),
3375
+ ([1], [1]),
3376
+ ([1], [1])]
3377
+ """
3378
+ if variab is None:
3379
+ if drain_size in self._TL_representation_dict:
3380
+ return self._TL_representation_dict[drain_size]
3381
+ R = LaurentPolynomialRing(ZZ, 'A')
3382
+ A = R.gens()[0]
3383
+ else:
3384
+ R = variab.parent()
3385
+ A = variab
3386
+
3387
+ n = self.strands()
3388
+ auxmat = self._TL_action(drain_size)
3389
+ dimension = auxmat.ncols()
3390
+ # The action of the sigma_i is given in terms of the actions of the
3391
+ # e_i which is what auxmat describes. Our choice of normalization means
3392
+ # that \sigma_i acts by the identity + A**2 e_i.
3393
+ rep_matrices = [] # The list which will store the actions of sigma_i
3394
+
3395
+ # Store the respective powers
3396
+ Ap2 = A**2
3397
+ Apm2 = A**(-2)
3398
+ Ap4 = -A**4
3399
+ Apm4 = -A**(-4)
3400
+
3401
+ for i in range(n-1): # For each \sigma_{i+1}
3402
+ rep_mat_new = identity_matrix(R, dimension, sparse=True)
3403
+ rep_mat_new_inv = identity_matrix(R, dimension, sparse=True)
3404
+ for v in range(dimension):
3405
+ new_mat_entry = auxmat[i, v]
3406
+ if new_mat_entry == v: # Did we create an unknot?
3407
+ rep_mat_new[v, v] = Ap4
3408
+ rep_mat_new_inv[v, v] = Apm4
3409
+ elif new_mat_entry >= 0:
3410
+ rep_mat_new[new_mat_entry, v] = Ap2
3411
+ rep_mat_new_inv[new_mat_entry, v] = Apm2
3412
+ rep_matrices.append((rep_mat_new, rep_mat_new_inv))
3413
+
3414
+ if variab is None: # Cache the result in this case
3415
+ for mat_pair in rep_matrices:
3416
+ mat_pair[0].set_immutable()
3417
+ mat_pair[1].set_immutable()
3418
+ self._TL_representation_dict[drain_size] = rep_matrices
3419
+
3420
+ return rep_matrices
3421
+
3422
+ def mapping_class_action(self, F):
3423
+ """
3424
+ Return the action of ``self`` in the free group F as mapping class
3425
+ group.
3426
+
3427
+ This action corresponds to the action of the braid over the
3428
+ punctured disk, whose fundamental group is the free group on
3429
+ as many generators as strands.
3430
+
3431
+ In Sage, this action is the result of multiplying a free group
3432
+ element with a braid. So you generally do not have to
3433
+ construct this action yourself.
3434
+
3435
+ OUTPUT: a :class:`MappingClassGroupAction`
3436
+
3437
+ EXAMPLES::
3438
+
3439
+ sage: B = BraidGroup(3)
3440
+ sage: B.inject_variables()
3441
+ Defining s0, s1
3442
+ sage: F.<a,b,c> = FreeGroup(3)
3443
+ sage: A = B.mapping_class_action(F)
3444
+ sage: A(a,s0)
3445
+ a*b*a^-1
3446
+ sage: a * s0 # simpler notation
3447
+ a*b*a^-1
3448
+ """
3449
+ return MappingClassGroupAction(self, F)
3450
+
3451
+ def _get_action_(self, S, op, self_on_left):
3452
+ """
3453
+ Let the coercion system discover actions of the braid group on free groups. ::
3454
+
3455
+ sage: B.<b0,b1,b2> = BraidGroup()
3456
+ sage: F.<f0,f1,f2,f3> = FreeGroup()
3457
+ sage: f1 * b1
3458
+ f1*f2*f1^-1
3459
+
3460
+ sage: from sage.structure.all import get_coercion_model
3461
+ sage: cm = get_coercion_model()
3462
+ sage: cm.explain(f1, b1, operator.mul)
3463
+ Action discovered.
3464
+ Right action by Braid group on 4 strands on Free Group on generators {f0, f1, f2, f3}
3465
+ Result lives in Free Group on generators {f0, f1, f2, f3}
3466
+ Free Group on generators {f0, f1, f2, f3}
3467
+ sage: cm.explain(b1, f1, operator.mul)
3468
+ Unknown result parent.
3469
+ """
3470
+ import operator
3471
+ if is_FreeGroup(S) and op == operator.mul and not self_on_left:
3472
+ return self.mapping_class_action(S)
3473
+ return None
3474
+
3475
+ def _element_from_libbraiding(self, nf):
3476
+ """
3477
+ Return the element of ``self`` corresponding to the output
3478
+ of libbraiding.
3479
+
3480
+ INPUT:
3481
+
3482
+ - ``nf`` -- list of lists, as returned by libbraiding
3483
+
3484
+ EXAMPLES::
3485
+
3486
+ sage: B = BraidGroup(5)
3487
+ sage: B._element_from_libbraiding([[-2], [2, 1], [1, 2], [2, 1]])
3488
+ (s0^-1*s1^-1*s0^-1*s2^-1*s1^-1*s0^-1*s3^-1*s2^-1*s1^-1*s0^-1)^2*s1*s0^2*s1^2*s0
3489
+ sage: B._element_from_libbraiding([[0]])
3490
+ 1
3491
+ """
3492
+ if len(nf) == 1:
3493
+ return self.delta()**nf[0][0]
3494
+ return self.delta()**nf[0][0] * prod(self(i) for i in nf[1:])
3495
+
3496
+ def mirror_involution(self):
3497
+ r"""
3498
+ Return the mirror involution of ``self``.
3499
+
3500
+ This automorphism maps a braid to another one by replacing
3501
+ each generator in its word by the inverse. In general this is
3502
+ different from the inverse of the braid since the order of the
3503
+ generators in the word is not reversed.
3504
+
3505
+ EXAMPLES::
3506
+
3507
+ sage: B = BraidGroup(4)
3508
+ sage: mirr = B.mirror_involution()
3509
+ sage: b = B((1,-2,-1,3,2,1))
3510
+ sage: bm = mirr(b); bm
3511
+ s0^-1*s1*s0*s2^-1*s1^-1*s0^-1
3512
+ sage: bm == ~b # needs sage.libs.braiding
3513
+ False
3514
+ sage: bm.is_conjugated(b) # needs sage.libs.braiding
3515
+ False
3516
+ sage: bm.is_conjugated(~b) # needs sage.libs.braiding
3517
+ True
3518
+ """
3519
+ gens_mirr = [~g for g in self.gens()]
3520
+ return self.hom(gens_mirr, check=False)
3521
+
3522
+ def presentation_two_generators(self, isomorphisms=False):
3523
+ r"""
3524
+ Construct a finitely presented group isomorphic to ``self`` with only two generators.
3525
+
3526
+ INPUT:
3527
+
3528
+ - ``isomorphism`` -- boolean (default: ``False``); if ``True``, then an isomorphism
3529
+ from ``self`` and the isomorphic group and its inverse is also returned
3530
+
3531
+ EXAMPLES::
3532
+
3533
+ sage: B = BraidGroup(3)
3534
+ sage: B.presentation_two_generators()
3535
+ Finitely presented group < x0, x1 | x1^3*x0^-2 >
3536
+ sage: B = BraidGroup(4)
3537
+ sage: G, hom1, hom2 = B.presentation_two_generators(isomorphisms=True)
3538
+ sage: G
3539
+ Finitely presented group < x0, x1 | x1^4*x0^-3, x0*x1*x0*x1^-2*x0^-1*x1^3*x0^-1*x1^-2 >
3540
+ sage: hom1(B.gen(0))
3541
+ x0*x1^-1
3542
+ sage: hom1(B.gen(1))
3543
+ x1*x0*x1^-2
3544
+ sage: hom1(B.gen(2))
3545
+ x1^2*x0*x1^-3
3546
+ sage: all(hom2(hom1(a)) == a for a in B.gens()) # needs sage.libs.braiding
3547
+ True
3548
+ sage: all(hom2(a) == B.one() for a in G.relations()) # needs sage.libs.braiding
3549
+ True
3550
+ """
3551
+ n = self.strands()
3552
+ F = FreeGroup(2, "x")
3553
+ rel = [n * (2,) + (n - 1) * (-1,)]
3554
+ rel += [(1,) + (j - 1) * (2,) + (1,) + j * (-2,) + (-1,) + (j + 1) * (2,) + (-1,) + j * (-2,)
3555
+ for j in range(2, n - 1)]
3556
+ G = F / rel
3557
+ if not isomorphisms:
3558
+ return G
3559
+ a1 = (1, -2)
3560
+ L1 = [j * (2,) + a1 + j * (-2,) for j in range(n - 1)]
3561
+ h1 = self.hom(codomain=G, im_gens=[G(a) for a in L1], check=False)
3562
+ a2 = tuple(range(1, n))
3563
+ L2 = [(1,) + a2, a2]
3564
+ h2 = G.hom(codomain=self, im_gens=[self(a) for a in L2], check=False)
3565
+ return (G, h1, h2)
3566
+
3567
+ def epimorphisms(self, H) -> list:
3568
+ r"""
3569
+ Return the epimorphisms from ``self`` to ``H``, up to automorphism of `H` passing
3570
+ through the :meth:`two generator presentation
3571
+ <presentation_two_generators>` of ``self``.
3572
+
3573
+ INPUT:
3574
+
3575
+ - ``H`` -- another group
3576
+
3577
+ EXAMPLES::
3578
+
3579
+ sage: B = BraidGroup(5)
3580
+ sage: B.epimorphisms(SymmetricGroup(5))
3581
+ [Generic morphism:
3582
+ From: Braid group on 5 strands
3583
+ To: Symmetric group of order 5! as a permutation group
3584
+ Defn: s0 |--> (1,5)
3585
+ s1 |--> (4,5)
3586
+ s2 |--> (3,4)
3587
+ s3 |--> (2,3)]
3588
+
3589
+ ALGORITHM:
3590
+
3591
+ Uses libgap's GQuotients function.
3592
+ """
3593
+ G, hom1, hom2 = self.presentation_two_generators(isomorphisms=True)
3594
+ from sage.misc.misc_c import prod
3595
+ HomSpace = self.Hom(H)
3596
+ G0g = libgap(self)
3597
+ Gg = libgap(G)
3598
+ Hg = libgap(H)
3599
+ gquotients = Gg.GQuotients(Hg)
3600
+ hom1g = libgap.GroupHomomorphismByImagesNC(G0g, Gg,
3601
+ [libgap(hom1(u))
3602
+ for u in self.gens()])
3603
+ g0quotients = [hom1g * h for h in gquotients]
3604
+ res = []
3605
+
3606
+ # the following closure is needed to attach a specific value of quo to
3607
+ # each function in the different morphisms
3608
+ def fmap(tup):
3609
+ return (lambda a: H(prod(tup[abs(i) - 1]**sign(i)
3610
+ for i in a.Tietze())))
3611
+
3612
+ for quo in g0quotients:
3613
+ tup = tuple(H(quo.ImageElm(i.gap()).sage()) for i in self.gens())
3614
+ fhom = GroupMorphismWithGensImages(HomSpace, fmap(tup))
3615
+ res.append(fhom)
3616
+ return res
3617
+
3618
+
3619
+ def BraidGroup(n=None, names='s'):
3620
+ """
3621
+ Construct a Braid Group.
3622
+
3623
+ INPUT:
3624
+
3625
+ - ``n`` -- integer or ``None`` (default). The number of
3626
+ strands. If not specified the ``names`` are counted and the
3627
+ group is assumed to have one more strand than generators.
3628
+
3629
+ - ``names`` -- string or list/tuple/iterable of strings (default:
3630
+ ``'x'``); the generator names or name prefix
3631
+
3632
+ EXAMPLES::
3633
+
3634
+ sage: B.<a,b> = BraidGroup(); B
3635
+ Braid group on 3 strands
3636
+ sage: H = BraidGroup('a, b')
3637
+ sage: B is H
3638
+ True
3639
+ sage: BraidGroup(3)
3640
+ Braid group on 3 strands
3641
+
3642
+ The entry can be either a string with the names of the generators,
3643
+ or the number of generators and the prefix of the names to be
3644
+ given. The default prefix is ``'s'`` ::
3645
+
3646
+ sage: B = BraidGroup(3); B.generators()
3647
+ (s0, s1)
3648
+ sage: BraidGroup(3, 'g').generators()
3649
+ (g0, g1)
3650
+
3651
+ Since the word problem for the braid groups is solvable, their Cayley graph
3652
+ can be locally obtained as follows (see :issue:`16059`)::
3653
+
3654
+ sage: def ball(group, radius):
3655
+ ....: ret = set()
3656
+ ....: ret.add(group.one())
3657
+ ....: for length in range(1, radius):
3658
+ ....: for w in Words(alphabet=group.gens(), length=length):
3659
+ ....: ret.add(prod(w))
3660
+ ....: return ret
3661
+ sage: B = BraidGroup(4)
3662
+ sage: GB = B.cayley_graph(elements=ball(B, 4), generators=B.gens()); GB # needs sage.combinat sage.graphs
3663
+ Digraph on 31 vertices
3664
+
3665
+ Since the braid group has nontrivial relations, this graph contains less
3666
+ vertices than the one associated to the free group (which is a tree)::
3667
+
3668
+ sage: F = FreeGroup(3)
3669
+ sage: GF = F.cayley_graph(elements=ball(F, 4), generators=F.gens()); GF # needs sage.combinat sage.graphs
3670
+ Digraph on 40 vertices
3671
+
3672
+ TESTS::
3673
+
3674
+ sage: G1 = BraidGroup(3, 'a,b')
3675
+ sage: G2 = BraidGroup('a,b')
3676
+ sage: G3.<a,b> = BraidGroup()
3677
+ sage: G1 is G2, G2 is G3
3678
+ (True, True)
3679
+ """
3680
+ # Support Freegroup('a,b') syntax
3681
+ if n is not None:
3682
+ try:
3683
+ n = Integer(n)-1
3684
+ except TypeError:
3685
+ names = n
3686
+ n = None
3687
+ # derive n from counting names
3688
+ if n is None:
3689
+ if isinstance(names, str):
3690
+ n = len(names.split(','))
3691
+ else:
3692
+ names = list(names)
3693
+ n = len(names)
3694
+ from sage.structure.category_object import normalize_names
3695
+ names = normalize_names(n, names)
3696
+ return BraidGroup_class(names)
3697
+
3698
+
3699
+ class MappingClassGroupAction(Action):
3700
+ r"""
3701
+ The right action of the braid group the free group as the mapping
3702
+ class group of the punctured disk.
3703
+
3704
+ That is, this action is the action of the braid over the punctured
3705
+ disk, whose fundamental group is the free group on as many
3706
+ generators as strands.
3707
+
3708
+ This action is defined as follows:
3709
+
3710
+ .. MATH::
3711
+
3712
+ x_j \cdot \sigma_i=\begin{cases}
3713
+ x_{j}\cdot x_{j+1}\cdot {x_j}^{-1} & \text{if $i=j$} \\
3714
+ x_{j-1} & \text{if $i=j-1$} \\
3715
+ x_{j} & \text{otherwise}
3716
+ \end{cases},
3717
+
3718
+ where `\sigma_i` are the generators of the braid group on `n`
3719
+ strands, and `x_j` the generators of the free group of rank `n`.
3720
+
3721
+ You should left multiplication of the free group element by the
3722
+ braid to compute the action. Alternatively, use the
3723
+ :meth:`~sage.groups.braid.BraidGroup_class.mapping_class_action`
3724
+ method of the braid group to construct this action.
3725
+
3726
+ EXAMPLES::
3727
+
3728
+ sage: B.<s0,s1,s2> = BraidGroup(4)
3729
+ sage: F.<x0,x1,x2,x3> = FreeGroup(4)
3730
+ sage: x0 * s1
3731
+ x0
3732
+ sage: x1 * s1
3733
+ x1*x2*x1^-1
3734
+ sage: x1^-1 * s1
3735
+ x1*x2^-1*x1^-1
3736
+
3737
+ sage: A = B.mapping_class_action(F)
3738
+ sage: A
3739
+ Right action by Braid group on 4 strands on Free Group
3740
+ on generators {x0, x1, x2, x3}
3741
+ sage: A(x0, s1)
3742
+ x0
3743
+ sage: A(x1, s1)
3744
+ x1*x2*x1^-1
3745
+ sage: A(x1^-1, s1)
3746
+ x1*x2^-1*x1^-1
3747
+ """
3748
+ def __init__(self, G, M) -> None:
3749
+ """
3750
+ TESTS::
3751
+
3752
+ sage: B = BraidGroup(3)
3753
+ sage: G = FreeGroup('a, b, c')
3754
+ sage: B.mapping_class_action(G) # indirect doctest
3755
+ Right action by Braid group on 3 strands on Free Group
3756
+ on generators {a, b, c}
3757
+ """
3758
+ import operator
3759
+ Action.__init__(self, G, M, False, operator.mul)
3760
+
3761
+ def _act_(self, b, x):
3762
+ """
3763
+ Return the action of ``b`` on ``x``.
3764
+
3765
+ INPUT:
3766
+
3767
+ - ``b`` -- a braid
3768
+
3769
+ - ``x`` -- a free group element
3770
+
3771
+ OUTPUT: a new braid
3772
+
3773
+ TESTS::
3774
+
3775
+ sage: B = BraidGroup(3)
3776
+ sage: G = FreeGroup('a, b, c')
3777
+ sage: A = B.mapping_class_action(G)
3778
+ sage: A(G.0, B.0) # indirect doctest
3779
+ a*b*a^-1
3780
+ sage: A(G.1, B.0) # indirect doctest
3781
+ a
3782
+ """
3783
+ t = x.Tietze()
3784
+ for j in b.Tietze():
3785
+ s = []
3786
+ for i in t:
3787
+ if j == i and i > 0:
3788
+ s += [i, i+1, -i]
3789
+ elif j == -i and i < 0:
3790
+ s += [-i, i-1, i]
3791
+ elif j == -i and i > 0:
3792
+ s += [i+1]
3793
+ elif j == i and i < 0:
3794
+ s += [i-1]
3795
+ elif i > 0 and j == i-1:
3796
+ s += [i-1]
3797
+ elif i < 0 and j == -i-1:
3798
+ s += [i+1]
3799
+ elif i > 0 and -j == i-1:
3800
+ s += [-i, i-1, i]
3801
+ elif i < 0 and j == i+1:
3802
+ s += [i, i+1, -i]
3803
+ else:
3804
+ s += [i]
3805
+ t = s
3806
+ return self.codomain()(t)