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