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