passagemath-groups 10.6.41__cp314-cp314t-musllinux_1_2_x86_64.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/__init__.py +3 -0
- passagemath_groups-10.6.41.dist-info/METADATA +113 -0
- passagemath_groups-10.6.41.dist-info/RECORD +36 -0
- passagemath_groups-10.6.41.dist-info/WHEEL +5 -0
- passagemath_groups-10.6.41.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-314t-x86_64-linux-musl.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-314t-x86_64-linux-musl.so +0 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation.pxd +9 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation.pyx +346 -0
- sage/groups/semimonomial_transformations/semimonomial_transformation_group.py +512 -0
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-groups
|
|
2
|
+
"""
|
|
3
|
+
Free Groups
|
|
4
|
+
|
|
5
|
+
Free groups and finitely presented groups are implemented as a wrapper
|
|
6
|
+
over the corresponding GAP objects.
|
|
7
|
+
|
|
8
|
+
A free group can be created by giving the number of generators, or their names.
|
|
9
|
+
It is also possible to create indexed generators::
|
|
10
|
+
|
|
11
|
+
sage: G.<x,y,z> = FreeGroup(); G
|
|
12
|
+
Free Group on generators {x, y, z}
|
|
13
|
+
sage: FreeGroup(3)
|
|
14
|
+
Free Group on generators {x0, x1, x2}
|
|
15
|
+
sage: FreeGroup('a,b,c')
|
|
16
|
+
Free Group on generators {a, b, c}
|
|
17
|
+
sage: FreeGroup(3,'t')
|
|
18
|
+
Free Group on generators {t0, t1, t2}
|
|
19
|
+
|
|
20
|
+
The elements can be created by operating with the generators, or by passing a list
|
|
21
|
+
with the indices of the letters to the group:
|
|
22
|
+
|
|
23
|
+
EXAMPLES::
|
|
24
|
+
|
|
25
|
+
sage: G.<a,b,c> = FreeGroup()
|
|
26
|
+
sage: a*b*c*a
|
|
27
|
+
a*b*c*a
|
|
28
|
+
sage: G([1,2,3,1])
|
|
29
|
+
a*b*c*a
|
|
30
|
+
sage: a * b / c * b^2
|
|
31
|
+
a*b*c^-1*b^2
|
|
32
|
+
sage: G([1,1,2,-1,-3,2])
|
|
33
|
+
a^2*b*a^-1*c^-1*b
|
|
34
|
+
|
|
35
|
+
You can use call syntax to replace the generators with a set of
|
|
36
|
+
arbitrary ring elements::
|
|
37
|
+
|
|
38
|
+
sage: g = a * b / c * b^2
|
|
39
|
+
sage: g(1,2,3)
|
|
40
|
+
8/3
|
|
41
|
+
sage: M1 = identity_matrix(2)
|
|
42
|
+
sage: M2 = matrix([[1,1],[0,1]])
|
|
43
|
+
sage: M3 = matrix([[0,1],[1,0]])
|
|
44
|
+
sage: g([M1, M2, M3])
|
|
45
|
+
[1 3]
|
|
46
|
+
[1 2]
|
|
47
|
+
|
|
48
|
+
AUTHORS:
|
|
49
|
+
|
|
50
|
+
- Miguel Angel Marco Buzunariz
|
|
51
|
+
- Volker Braun
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
# ****************************************************************************
|
|
55
|
+
# Copyright (C) 2012 Miguel Angel Marco Buzunariz <mmarco@unizar.es>
|
|
56
|
+
#
|
|
57
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
58
|
+
#
|
|
59
|
+
# The full text of the GPL is available at:
|
|
60
|
+
#
|
|
61
|
+
# https://www.gnu.org/licenses/
|
|
62
|
+
# ****************************************************************************
|
|
63
|
+
|
|
64
|
+
from sage.categories.groups import Groups
|
|
65
|
+
from sage.groups.group import Group
|
|
66
|
+
from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP
|
|
67
|
+
from sage.structure.unique_representation import CachedRepresentation
|
|
68
|
+
from sage.libs.gap.libgap import libgap
|
|
69
|
+
from sage.libs.gap.element import GapElement
|
|
70
|
+
from sage.rings.integer import Integer
|
|
71
|
+
from sage.rings.integer_ring import IntegerRing
|
|
72
|
+
from sage.misc.cachefunc import cached_method
|
|
73
|
+
from sage.misc.misc_c import prod
|
|
74
|
+
from sage.structure.sequence import Sequence
|
|
75
|
+
from sage.structure.element import coercion_model, parent
|
|
76
|
+
from sage.structure.richcmp import richcmp, richcmp_method
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_FreeGroup(x):
|
|
80
|
+
"""
|
|
81
|
+
Test whether ``x`` is a :class:`FreeGroup_class`.
|
|
82
|
+
|
|
83
|
+
INPUT:
|
|
84
|
+
|
|
85
|
+
- ``x`` -- anything
|
|
86
|
+
|
|
87
|
+
OUTPUT: boolean
|
|
88
|
+
|
|
89
|
+
EXAMPLES::
|
|
90
|
+
|
|
91
|
+
sage: from sage.groups.free_group import is_FreeGroup
|
|
92
|
+
sage: is_FreeGroup('a string') # needs sage.combinat
|
|
93
|
+
False
|
|
94
|
+
sage: is_FreeGroup(FreeGroup(0))
|
|
95
|
+
True
|
|
96
|
+
sage: is_FreeGroup(FreeGroup(index_set=ZZ)) # needs sage.combinat
|
|
97
|
+
True
|
|
98
|
+
"""
|
|
99
|
+
if isinstance(x, FreeGroup_class):
|
|
100
|
+
return True
|
|
101
|
+
from sage.groups.indexed_free_group import IndexedFreeGroup
|
|
102
|
+
return isinstance(x, IndexedFreeGroup)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _lexi_gen(zeroes=False):
|
|
106
|
+
"""
|
|
107
|
+
Return a generator object that produces variable names suitable for the
|
|
108
|
+
generators of a free group.
|
|
109
|
+
|
|
110
|
+
INPUT:
|
|
111
|
+
|
|
112
|
+
- ``zeroes`` -- boolean (default: ``False``); if ``True``, the
|
|
113
|
+
integers appended to the output string begin at zero at the
|
|
114
|
+
first iteration through the alphabet
|
|
115
|
+
|
|
116
|
+
OUTPUT:
|
|
117
|
+
|
|
118
|
+
Python generator object which outputs a character from the alphabet on each
|
|
119
|
+
``next()`` call in lexicographical order. The integer `i` is appended
|
|
120
|
+
to the output string on the `i`-th iteration through the alphabet.
|
|
121
|
+
|
|
122
|
+
EXAMPLES::
|
|
123
|
+
|
|
124
|
+
sage: from sage.groups.free_group import _lexi_gen
|
|
125
|
+
sage: itr = _lexi_gen()
|
|
126
|
+
sage: F = FreeGroup([next(itr) for i in [1..10]]); F
|
|
127
|
+
Free Group on generators {a, b, c, d, e, f, g, h, i, j}
|
|
128
|
+
sage: it = _lexi_gen()
|
|
129
|
+
sage: [next(it) for i in range(10)]
|
|
130
|
+
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
|
|
131
|
+
sage: itt = _lexi_gen(True)
|
|
132
|
+
sage: [next(itt) for i in range(10)]
|
|
133
|
+
['a0', 'b0', 'c0', 'd0', 'e0', 'f0', 'g0', 'h0', 'i0', 'j0']
|
|
134
|
+
sage: test = _lexi_gen()
|
|
135
|
+
sage: ls = [next(test) for i in range(3*26)]
|
|
136
|
+
sage: ls[2*26:3*26]
|
|
137
|
+
['a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2', 'i2', 'j2', 'k2', 'l2', 'm2',
|
|
138
|
+
'n2', 'o2', 'p2', 'q2', 'r2', 's2', 't2', 'u2', 'v2', 'w2', 'x2', 'y2', 'z2']
|
|
139
|
+
|
|
140
|
+
TESTS::
|
|
141
|
+
|
|
142
|
+
sage: from sage.groups.free_group import _lexi_gen
|
|
143
|
+
sage: test = _lexi_gen()
|
|
144
|
+
sage: ls = [next(test) for i in range(500)]
|
|
145
|
+
sage: ls[234], ls[260]
|
|
146
|
+
('a9', 'a10')
|
|
147
|
+
"""
|
|
148
|
+
count = Integer(0)
|
|
149
|
+
while True:
|
|
150
|
+
mwrap, ind = count.quo_rem(26)
|
|
151
|
+
if mwrap == 0 and not zeroes:
|
|
152
|
+
name = ''
|
|
153
|
+
else:
|
|
154
|
+
name = str(mwrap)
|
|
155
|
+
name = chr(ord('a') + ind) + name
|
|
156
|
+
yield name
|
|
157
|
+
count += 1
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class FreeGroupElement(ElementLibGAP):
|
|
161
|
+
"""
|
|
162
|
+
A wrapper of GAP's Free Group elements.
|
|
163
|
+
|
|
164
|
+
INPUT:
|
|
165
|
+
|
|
166
|
+
- ``x`` -- something that determines the group element; either a
|
|
167
|
+
:class:`~sage.libs.gap.element.GapElement` or the Tietze list
|
|
168
|
+
(see :meth:`Tietze`) of the group element
|
|
169
|
+
|
|
170
|
+
- ``parent`` -- the parent :class:`FreeGroup`
|
|
171
|
+
|
|
172
|
+
EXAMPLES::
|
|
173
|
+
|
|
174
|
+
sage: G = FreeGroup('a, b')
|
|
175
|
+
sage: x = G([1, 2, -1, -2])
|
|
176
|
+
sage: x
|
|
177
|
+
a*b*a^-1*b^-1
|
|
178
|
+
sage: y = G([2, 2, 2, 1, -2, -2, -2])
|
|
179
|
+
sage: y
|
|
180
|
+
b^3*a*b^-3
|
|
181
|
+
sage: x*y
|
|
182
|
+
a*b*a^-1*b^2*a*b^-3
|
|
183
|
+
sage: y*x
|
|
184
|
+
b^3*a*b^-3*a*b*a^-1*b^-1
|
|
185
|
+
sage: x^(-1)
|
|
186
|
+
b*a*b^-1*a^-1
|
|
187
|
+
sage: x == x*y*y^(-1)
|
|
188
|
+
True
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def __init__(self, parent, x):
|
|
192
|
+
"""
|
|
193
|
+
The Python constructor.
|
|
194
|
+
|
|
195
|
+
See :class:`FreeGroupElement` for details.
|
|
196
|
+
|
|
197
|
+
TESTS::
|
|
198
|
+
|
|
199
|
+
sage: G.<a,b> = FreeGroup()
|
|
200
|
+
sage: x = G([1, 2, -1, -1])
|
|
201
|
+
sage: x # indirect doctest
|
|
202
|
+
a*b*a^-2
|
|
203
|
+
sage: y = G([2, 2, 2, 1, -2, -2, -1])
|
|
204
|
+
sage: y # indirect doctest
|
|
205
|
+
b^3*a*b^-2*a^-1
|
|
206
|
+
|
|
207
|
+
sage: TestSuite(G).run()
|
|
208
|
+
sage: TestSuite(x).run()
|
|
209
|
+
"""
|
|
210
|
+
if not isinstance(x, GapElement):
|
|
211
|
+
try:
|
|
212
|
+
l = x.Tietze()
|
|
213
|
+
except AttributeError:
|
|
214
|
+
l = list(x)
|
|
215
|
+
if len(l) > 0:
|
|
216
|
+
if min(l) < -parent.ngens() or parent.ngens() < max(l):
|
|
217
|
+
raise ValueError('generators not in the group')
|
|
218
|
+
if 0 in l:
|
|
219
|
+
raise ValueError('zero does not denote a generator')
|
|
220
|
+
i = 0
|
|
221
|
+
while i < len(l)-1:
|
|
222
|
+
if l[i] == -l[i+1]:
|
|
223
|
+
l.pop(i)
|
|
224
|
+
l.pop(i)
|
|
225
|
+
if i > 0:
|
|
226
|
+
i = i-1
|
|
227
|
+
else:
|
|
228
|
+
i = i+1
|
|
229
|
+
AbstractWordTietzeWord = libgap.eval('AbstractWordTietzeWord')
|
|
230
|
+
x = AbstractWordTietzeWord(l, parent.gap().GeneratorsOfGroup())
|
|
231
|
+
ElementLibGAP.__init__(self, parent, x)
|
|
232
|
+
|
|
233
|
+
def __hash__(self):
|
|
234
|
+
r"""
|
|
235
|
+
TESTS::
|
|
236
|
+
|
|
237
|
+
sage: G.<a,b> = FreeGroup()
|
|
238
|
+
sage: hash(a*b*b*~a) == hash((1, 2, 2, -1))
|
|
239
|
+
True
|
|
240
|
+
"""
|
|
241
|
+
return hash(self.Tietze())
|
|
242
|
+
|
|
243
|
+
def _latex_(self):
|
|
244
|
+
r"""
|
|
245
|
+
Return a LaTeX representation.
|
|
246
|
+
|
|
247
|
+
OUTPUT: string; a valid LaTeX math command sequence
|
|
248
|
+
|
|
249
|
+
EXAMPLES::
|
|
250
|
+
|
|
251
|
+
sage: F.<a,b,c> = FreeGroup()
|
|
252
|
+
sage: f = F([1, 2, 2, -3, -1]) * c^15 * a^(-23)
|
|
253
|
+
sage: f._latex_()
|
|
254
|
+
'a\\cdot b^{2}\\cdot c^{-1}\\cdot a^{-1}\\cdot c^{15}\\cdot a^{-23}'
|
|
255
|
+
|
|
256
|
+
sage: F = FreeGroup(3)
|
|
257
|
+
sage: f = F([1, 2, 2, -3, -1]) * F.gen(2)^11 * F.gen(0)^(-12)
|
|
258
|
+
sage: f._latex_()
|
|
259
|
+
'x_{0}\\cdot x_{1}^{2}\\cdot x_{2}^{-1}\\cdot x_{0}^{-1}\\cdot x_{2}^{11}\\cdot x_{0}^{-12}'
|
|
260
|
+
|
|
261
|
+
sage: F.<a,b,c> = FreeGroup()
|
|
262
|
+
sage: G = F / (F([1, 2, 1, -3, 2, -1]), F([2, -1]))
|
|
263
|
+
sage: f = G([1, 2, 2, -3, -1]) * G.gen(2)^15 * G.gen(0)^(-23)
|
|
264
|
+
sage: f._latex_()
|
|
265
|
+
'a\\cdot b^{2}\\cdot c^{-1}\\cdot a^{-1}\\cdot c^{15}\\cdot a^{-23}'
|
|
266
|
+
|
|
267
|
+
sage: F = FreeGroup(4)
|
|
268
|
+
sage: G = F.quotient((F([1, 2, 4, -3, 2, -1]), F([2, -1])))
|
|
269
|
+
sage: f = G([1, 2, 2, -3, -1]) * G.gen(3)^11 * G.gen(0)^(-12)
|
|
270
|
+
sage: f._latex_()
|
|
271
|
+
'x_{0}\\cdot x_{1}^{2}\\cdot x_{2}^{-1}\\cdot x_{0}^{-1}\\cdot x_{3}^{11}\\cdot x_{0}^{-12}'
|
|
272
|
+
"""
|
|
273
|
+
import re
|
|
274
|
+
s = self._repr_()
|
|
275
|
+
s = re.sub('([a-z]|[A-Z])([0-9]+)', r'\g<1>_{\g<2>}', s)
|
|
276
|
+
s = re.sub(r'(\^)(-)([0-9]+)', r'\g<1>{\g<2>\g<3>}', s)
|
|
277
|
+
s = re.sub(r'(\^)([0-9]+)', r'\g<1>{\g<2>}', s)
|
|
278
|
+
s = s.replace('*', r'\cdot ')
|
|
279
|
+
return s
|
|
280
|
+
|
|
281
|
+
def __reduce__(self):
|
|
282
|
+
"""
|
|
283
|
+
Implement pickling.
|
|
284
|
+
|
|
285
|
+
TESTS::
|
|
286
|
+
|
|
287
|
+
sage: F.<a,b> = FreeGroup()
|
|
288
|
+
sage: a.__reduce__()
|
|
289
|
+
(Free Group on generators {a, b}, ((1,),))
|
|
290
|
+
sage: (a*b*a^-1).__reduce__()
|
|
291
|
+
(Free Group on generators {a, b}, ((1, 2, -1),))
|
|
292
|
+
"""
|
|
293
|
+
return (self.parent(), (self.Tietze(),))
|
|
294
|
+
|
|
295
|
+
@cached_method
|
|
296
|
+
def Tietze(self):
|
|
297
|
+
"""
|
|
298
|
+
Return the Tietze list of the element.
|
|
299
|
+
|
|
300
|
+
The Tietze list of a word is a list of integers that represent
|
|
301
|
+
the letters in the word. A positive integer `i` represents
|
|
302
|
+
the letter corresponding to the `i`-th generator of the group.
|
|
303
|
+
Negative integers represent the inverses of generators.
|
|
304
|
+
|
|
305
|
+
OUTPUT: tuple of integers
|
|
306
|
+
|
|
307
|
+
EXAMPLES::
|
|
308
|
+
|
|
309
|
+
sage: G.<a,b> = FreeGroup()
|
|
310
|
+
sage: a.Tietze()
|
|
311
|
+
(1,)
|
|
312
|
+
sage: x = a^2 * b^(-3) * a^(-2)
|
|
313
|
+
sage: x.Tietze()
|
|
314
|
+
(1, 1, -2, -2, -2, -1, -1)
|
|
315
|
+
|
|
316
|
+
TESTS::
|
|
317
|
+
|
|
318
|
+
sage: type(a.Tietze())
|
|
319
|
+
<... 'tuple'>
|
|
320
|
+
sage: type(a.Tietze()[0])
|
|
321
|
+
<class 'sage.rings.integer.Integer'>
|
|
322
|
+
"""
|
|
323
|
+
tl = self.gap().TietzeWordAbstractWord()
|
|
324
|
+
return tuple(tl.sage())
|
|
325
|
+
|
|
326
|
+
def fox_derivative(self, gen, im_gens=None, ring=None):
|
|
327
|
+
r"""
|
|
328
|
+
Return the Fox derivative of ``self`` with respect to a given
|
|
329
|
+
generator ``gen`` of the free group.
|
|
330
|
+
|
|
331
|
+
Let `F` be a free group with free generators
|
|
332
|
+
`x_1, x_2, \ldots, x_n`. Let `j \in \left\{ 1, 2, \ldots ,
|
|
333
|
+
n \right\}`. Let `a_1, a_2, \ldots, a_n` be `n`
|
|
334
|
+
invertible elements of a ring `A`. Let `a : F \to A^\times`
|
|
335
|
+
be the (unique) homomorphism from `F` to the multiplicative
|
|
336
|
+
group of invertible elements of `A` which sends each `x_i`
|
|
337
|
+
to `a_i`. Then, we can define a map `\partial_j : F \to A`
|
|
338
|
+
by the requirements that
|
|
339
|
+
|
|
340
|
+
.. MATH::
|
|
341
|
+
|
|
342
|
+
\partial_j (x_i) = \delta_{i, j}
|
|
343
|
+
\qquad \qquad \text{ for all indices } i \text{ and } j
|
|
344
|
+
|
|
345
|
+
and
|
|
346
|
+
|
|
347
|
+
.. MATH::
|
|
348
|
+
|
|
349
|
+
\partial_j (uv) = \partial_j(u) + a(u) \partial_j(v)
|
|
350
|
+
\qquad \qquad \text{ for all } u, v \in F .
|
|
351
|
+
|
|
352
|
+
This map `\partial_j` is called the `j`-th Fox derivative
|
|
353
|
+
on `F` induced by `(a_1, a_2, \ldots, a_n)`.
|
|
354
|
+
|
|
355
|
+
The most well-known case is when `A` is the group ring
|
|
356
|
+
`\ZZ [F]` of `F` over `\ZZ`, and when `a_i = x_i \in A`.
|
|
357
|
+
In this case, `\partial_j` is simply called the `j`-th
|
|
358
|
+
Fox derivative on `F`.
|
|
359
|
+
|
|
360
|
+
INPUT:
|
|
361
|
+
|
|
362
|
+
- ``gen`` -- the generator with respect to which the
|
|
363
|
+
derivative will be computed. If this is `x_j`, then the
|
|
364
|
+
method will return `\partial_j`.
|
|
365
|
+
|
|
366
|
+
- ``im_gens`` -- (optional) the images of the generators
|
|
367
|
+
(given as a list or iterable). This is the list
|
|
368
|
+
`(a_1, a_2, \ldots, a_n)`.
|
|
369
|
+
If not provided, it defaults to
|
|
370
|
+
`(x_1, x_2, \ldots, x_n)` in the group ring
|
|
371
|
+
`\ZZ [F]`.
|
|
372
|
+
|
|
373
|
+
- ``ring`` -- (optional) the ring in which the elements
|
|
374
|
+
of the list `(a_1, a_2, \ldots, a_n)` lie. If not
|
|
375
|
+
provided, this ring is inferred from these elements.
|
|
376
|
+
|
|
377
|
+
OUTPUT:
|
|
378
|
+
|
|
379
|
+
The fox derivative of ``self`` with respect to ``gen``
|
|
380
|
+
(induced by ``im_gens``).
|
|
381
|
+
By default, it is an element of the group algebra with
|
|
382
|
+
integer coefficients.
|
|
383
|
+
If ``im_gens`` are provided, the result lives in the
|
|
384
|
+
algebra where ``im_gens`` live.
|
|
385
|
+
|
|
386
|
+
EXAMPLES::
|
|
387
|
+
|
|
388
|
+
sage: G = FreeGroup(5)
|
|
389
|
+
sage: G.inject_variables()
|
|
390
|
+
Defining x0, x1, x2, x3, x4
|
|
391
|
+
sage: (~x0*x1*x0*x2*~x0).fox_derivative(x0)
|
|
392
|
+
-x0^-1 + x0^-1*x1 - x0^-1*x1*x0*x2*x0^-1
|
|
393
|
+
sage: (~x0*x1*x0*x2*~x0).fox_derivative(x1)
|
|
394
|
+
x0^-1
|
|
395
|
+
sage: (~x0*x1*x0*x2*~x0).fox_derivative(x2)
|
|
396
|
+
x0^-1*x1*x0
|
|
397
|
+
sage: (~x0*x1*x0*x2*~x0).fox_derivative(x3)
|
|
398
|
+
0
|
|
399
|
+
|
|
400
|
+
If ``im_gens`` is given, the images of the generators are
|
|
401
|
+
mapped to them::
|
|
402
|
+
|
|
403
|
+
sage: F = FreeGroup(3)
|
|
404
|
+
sage: a = F([2,1,3,-1,2])
|
|
405
|
+
sage: a.fox_derivative(F([1]))
|
|
406
|
+
x1 - x1*x0*x2*x0^-1
|
|
407
|
+
sage: R.<t> = LaurentPolynomialRing(ZZ)
|
|
408
|
+
sage: a.fox_derivative(F([1]),[t,t,t])
|
|
409
|
+
t - t^2
|
|
410
|
+
sage: S.<t1,t2,t3> = LaurentPolynomialRing(ZZ)
|
|
411
|
+
sage: a.fox_derivative(F([1]),[t1,t2,t3])
|
|
412
|
+
-t2*t3 + t2
|
|
413
|
+
sage: R.<x,y,z> = QQ[]
|
|
414
|
+
sage: a.fox_derivative(F([1]),[x,y,z])
|
|
415
|
+
-y*z + y
|
|
416
|
+
sage: a.inverse().fox_derivative(F([1]),[x,y,z])
|
|
417
|
+
(z - 1)/(y*z)
|
|
418
|
+
|
|
419
|
+
The optional parameter ``ring`` determines the ring `A`::
|
|
420
|
+
|
|
421
|
+
sage: u = a.fox_derivative(F([1]), [1,2,3], ring=QQ)
|
|
422
|
+
sage: u
|
|
423
|
+
-4
|
|
424
|
+
sage: parent(u)
|
|
425
|
+
Rational Field
|
|
426
|
+
sage: u = a.fox_derivative(F([1]), [1,2,3], ring=R)
|
|
427
|
+
sage: u
|
|
428
|
+
-4
|
|
429
|
+
sage: parent(u)
|
|
430
|
+
Multivariate Polynomial Ring in x, y, z over Rational Field
|
|
431
|
+
|
|
432
|
+
TESTS::
|
|
433
|
+
|
|
434
|
+
sage: F = FreeGroup(3)
|
|
435
|
+
sage: a = F([])
|
|
436
|
+
sage: a.fox_derivative(F([1]))
|
|
437
|
+
0
|
|
438
|
+
sage: R.<t> = LaurentPolynomialRing(ZZ)
|
|
439
|
+
sage: a.fox_derivative(F([1]),[t,t,t])
|
|
440
|
+
0
|
|
441
|
+
"""
|
|
442
|
+
if gen not in self.parent().generators():
|
|
443
|
+
raise ValueError("Fox derivative can only be computed with respect to generators of the group")
|
|
444
|
+
l = list(self.Tietze())
|
|
445
|
+
if im_gens is None:
|
|
446
|
+
F = self.parent()
|
|
447
|
+
R = F.algebra(IntegerRing())
|
|
448
|
+
R_basis = R.basis()
|
|
449
|
+
symb = [R_basis[a] for a in F.gens()]
|
|
450
|
+
symb += reversed([R_basis[a.inverse()] for a in F.gens()])
|
|
451
|
+
if ring is not None:
|
|
452
|
+
R = ring
|
|
453
|
+
symb = [R(i) for i in symb]
|
|
454
|
+
else:
|
|
455
|
+
if ring is None:
|
|
456
|
+
R = Sequence(im_gens).universe()
|
|
457
|
+
else:
|
|
458
|
+
R = ring
|
|
459
|
+
symb = list(im_gens)
|
|
460
|
+
symb += reversed([a**(-1) for a in im_gens])
|
|
461
|
+
i = gen.Tietze()[0]
|
|
462
|
+
# so gen is the i-th generator of the free group
|
|
463
|
+
|
|
464
|
+
a = R.zero()
|
|
465
|
+
coef = R.one()
|
|
466
|
+
while l:
|
|
467
|
+
b = l.pop(0)
|
|
468
|
+
if b == i:
|
|
469
|
+
a += coef * R.one()
|
|
470
|
+
coef *= symb[b-1]
|
|
471
|
+
elif b == -i:
|
|
472
|
+
a -= coef * symb[b]
|
|
473
|
+
coef *= symb[b]
|
|
474
|
+
elif b > 0:
|
|
475
|
+
coef *= symb[b-1]
|
|
476
|
+
else:
|
|
477
|
+
coef *= symb[b]
|
|
478
|
+
return a
|
|
479
|
+
|
|
480
|
+
@cached_method
|
|
481
|
+
def syllables(self):
|
|
482
|
+
r"""
|
|
483
|
+
Return the syllables of the word.
|
|
484
|
+
|
|
485
|
+
Consider a free group element `g = x_1^{n_1} x_2^{n_2} \cdots
|
|
486
|
+
x_k^{n_k}`. The uniquely-determined subwords `x_i^{e_i}`
|
|
487
|
+
consisting only of powers of a single generator are called the
|
|
488
|
+
syllables of `g`.
|
|
489
|
+
|
|
490
|
+
OUTPUT:
|
|
491
|
+
|
|
492
|
+
The tuple of syllables. Each syllable is given as a pair
|
|
493
|
+
`(x_i, e_i)` consisting of a generator and a nonzero integer.
|
|
494
|
+
|
|
495
|
+
EXAMPLES::
|
|
496
|
+
|
|
497
|
+
sage: G.<a,b> = FreeGroup()
|
|
498
|
+
sage: w = a^2 * b^-1 * a^3
|
|
499
|
+
sage: w.syllables()
|
|
500
|
+
((a, 2), (b, -1), (a, 3))
|
|
501
|
+
"""
|
|
502
|
+
g = self.gap().UnderlyingElement()
|
|
503
|
+
k = g.NumberSyllables().sage()
|
|
504
|
+
exponent_syllable = libgap.eval('ExponentSyllable')
|
|
505
|
+
generator_syllable = libgap.eval('GeneratorSyllable')
|
|
506
|
+
result = []
|
|
507
|
+
gen = self.parent().gen
|
|
508
|
+
for i in range(k):
|
|
509
|
+
exponent = exponent_syllable(g, i+1).sage()
|
|
510
|
+
generator = gen(generator_syllable(g, i+1).sage() - 1)
|
|
511
|
+
result.append((generator, exponent))
|
|
512
|
+
return tuple(result)
|
|
513
|
+
|
|
514
|
+
def __call__(self, *values):
|
|
515
|
+
"""
|
|
516
|
+
Replace the generators of the free group by corresponding
|
|
517
|
+
elements of the iterable ``values`` in the group element
|
|
518
|
+
``self``.
|
|
519
|
+
|
|
520
|
+
INPUT:
|
|
521
|
+
|
|
522
|
+
- ``*values`` -- a sequence of values, or a
|
|
523
|
+
list/tuple/iterable of the same length as the number of
|
|
524
|
+
generators of the free group
|
|
525
|
+
|
|
526
|
+
OUTPUT: the product of ``values`` in the order and with exponents
|
|
527
|
+
specified by ``self``
|
|
528
|
+
|
|
529
|
+
EXAMPLES::
|
|
530
|
+
|
|
531
|
+
sage: G.<a,b> = FreeGroup()
|
|
532
|
+
sage: w = a^2 * b^-1 * a^3
|
|
533
|
+
sage: w(1, 2)
|
|
534
|
+
1/2
|
|
535
|
+
sage: w(2, 1)
|
|
536
|
+
32
|
|
537
|
+
sage: w.subs(b=1, a=2) # indirect doctest
|
|
538
|
+
32
|
|
539
|
+
|
|
540
|
+
TESTS::
|
|
541
|
+
|
|
542
|
+
sage: w([1, 2])
|
|
543
|
+
1/2
|
|
544
|
+
sage: w((1, 2))
|
|
545
|
+
1/2
|
|
546
|
+
sage: w(i+1 for i in range(2))
|
|
547
|
+
1/2
|
|
548
|
+
|
|
549
|
+
Check that :issue:`25017` is fixed::
|
|
550
|
+
|
|
551
|
+
sage: F = FreeGroup(2)
|
|
552
|
+
sage: x0, x1 = F.gens()
|
|
553
|
+
sage: u = F(1)
|
|
554
|
+
sage: parent(u.subs({x1:x0})) is F
|
|
555
|
+
True
|
|
556
|
+
|
|
557
|
+
sage: F = FreeGroup(2)
|
|
558
|
+
sage: x0, x1 = F.gens()
|
|
559
|
+
sage: u = x0*x1
|
|
560
|
+
sage: u.subs({x0:3, x1:2})
|
|
561
|
+
6
|
|
562
|
+
sage: u.subs({x0:1r, x1:2r})
|
|
563
|
+
2
|
|
564
|
+
sage: M0 = matrix(ZZ,2,[1,1,0,1])
|
|
565
|
+
sage: M1 = matrix(ZZ,2,[1,0,1,1])
|
|
566
|
+
sage: u.subs({x0: M0, x1: M1})
|
|
567
|
+
[2 1]
|
|
568
|
+
[1 1]
|
|
569
|
+
|
|
570
|
+
TESTS::
|
|
571
|
+
|
|
572
|
+
sage: F.<x,y> = FreeGroup()
|
|
573
|
+
sage: F.one().subs(x=x, y=1)
|
|
574
|
+
Traceback (most recent call last):
|
|
575
|
+
...
|
|
576
|
+
TypeError: no common canonical parent for objects with parents: 'Free Group on generators {x, y}' and 'Integer Ring'
|
|
577
|
+
"""
|
|
578
|
+
if len(values) == 1:
|
|
579
|
+
try:
|
|
580
|
+
values = list(values[0])
|
|
581
|
+
except TypeError:
|
|
582
|
+
pass
|
|
583
|
+
G = self.parent()
|
|
584
|
+
if len(values) != G.ngens():
|
|
585
|
+
raise ValueError('number of values has to match the number of generators')
|
|
586
|
+
replace = dict(zip(G.gens(), values))
|
|
587
|
+
new_parent = coercion_model.common_parent(*[parent(v) for v in values])
|
|
588
|
+
try:
|
|
589
|
+
return new_parent.prod(replace[gen] ** power
|
|
590
|
+
for gen, power in self.syllables())
|
|
591
|
+
except AttributeError:
|
|
592
|
+
return prod(new_parent(replace[gen]) ** power
|
|
593
|
+
for gen, power in self.syllables())
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds):
|
|
597
|
+
"""
|
|
598
|
+
Construct a Free Group.
|
|
599
|
+
|
|
600
|
+
INPUT:
|
|
601
|
+
|
|
602
|
+
- ``n`` -- integer (default: ``None``); the number of
|
|
603
|
+
generators (if not specified the ``names`` are counted)
|
|
604
|
+
|
|
605
|
+
- ``names`` -- string or list/tuple/iterable of strings (default:
|
|
606
|
+
``'x'``); the generator names or name prefix
|
|
607
|
+
|
|
608
|
+
- ``index_set`` -- (optional) an index set for the generators; if
|
|
609
|
+
specified then the optional keyword ``abelian`` can be used
|
|
610
|
+
|
|
611
|
+
- ``abelian`` -- boolean (default: ``False``); whether to construct a free
|
|
612
|
+
abelian group or a free group
|
|
613
|
+
|
|
614
|
+
.. NOTE::
|
|
615
|
+
|
|
616
|
+
If you want to create a free group, it is currently preferential to
|
|
617
|
+
use ``Groups().free(...)`` as that does not load GAP.
|
|
618
|
+
|
|
619
|
+
EXAMPLES::
|
|
620
|
+
|
|
621
|
+
sage: G.<a,b> = FreeGroup(); G
|
|
622
|
+
Free Group on generators {a, b}
|
|
623
|
+
sage: H = FreeGroup('a, b')
|
|
624
|
+
sage: G is H
|
|
625
|
+
True
|
|
626
|
+
sage: FreeGroup(0)
|
|
627
|
+
Free Group on generators {}
|
|
628
|
+
|
|
629
|
+
The entry can be either a string with the names of the generators,
|
|
630
|
+
or the number of generators and the prefix of the names to be
|
|
631
|
+
given. The default prefix is ``'x'`` ::
|
|
632
|
+
|
|
633
|
+
sage: FreeGroup(3)
|
|
634
|
+
Free Group on generators {x0, x1, x2}
|
|
635
|
+
sage: FreeGroup(3, 'g')
|
|
636
|
+
Free Group on generators {g0, g1, g2}
|
|
637
|
+
sage: FreeGroup()
|
|
638
|
+
Free Group on generators {x}
|
|
639
|
+
|
|
640
|
+
We give two examples using the ``index_set`` option::
|
|
641
|
+
|
|
642
|
+
sage: FreeGroup(index_set=ZZ) # needs sage.combinat
|
|
643
|
+
Free group indexed by Integer Ring
|
|
644
|
+
sage: FreeGroup(index_set=ZZ, abelian=True) # needs sage.combinat
|
|
645
|
+
Free abelian group indexed by Integer Ring
|
|
646
|
+
|
|
647
|
+
TESTS::
|
|
648
|
+
|
|
649
|
+
sage: G1 = FreeGroup(2, 'a,b')
|
|
650
|
+
sage: G2 = FreeGroup('a,b')
|
|
651
|
+
sage: G3.<a,b> = FreeGroup()
|
|
652
|
+
sage: G1 is G2, G2 is G3
|
|
653
|
+
(True, True)
|
|
654
|
+
"""
|
|
655
|
+
# Support Freegroup('a,b') syntax
|
|
656
|
+
if n is not None:
|
|
657
|
+
try:
|
|
658
|
+
n = Integer(n)
|
|
659
|
+
except TypeError:
|
|
660
|
+
names = n
|
|
661
|
+
n = None
|
|
662
|
+
# derive n from counting names
|
|
663
|
+
if n is None:
|
|
664
|
+
if isinstance(names, str):
|
|
665
|
+
n = len(names.split(','))
|
|
666
|
+
else:
|
|
667
|
+
names = list(names)
|
|
668
|
+
n = len(names)
|
|
669
|
+
from sage.structure.category_object import normalize_names
|
|
670
|
+
names = normalize_names(n, names)
|
|
671
|
+
if index_set is not None or abelian:
|
|
672
|
+
if abelian:
|
|
673
|
+
from sage.groups.indexed_free_group import IndexedFreeAbelianGroup
|
|
674
|
+
return IndexedFreeAbelianGroup(index_set, names=names, **kwds)
|
|
675
|
+
|
|
676
|
+
from sage.groups.indexed_free_group import IndexedFreeGroup
|
|
677
|
+
return IndexedFreeGroup(index_set, names=names, **kwds)
|
|
678
|
+
return FreeGroup_class(names, **kwds)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
@richcmp_method
|
|
682
|
+
class FreeGroup_class(CachedRepresentation, Group, ParentLibGAP):
|
|
683
|
+
"""
|
|
684
|
+
A class that wraps GAP's FreeGroup.
|
|
685
|
+
|
|
686
|
+
See :func:`FreeGroup` for details.
|
|
687
|
+
|
|
688
|
+
TESTS::
|
|
689
|
+
|
|
690
|
+
sage: G = FreeGroup('a, b')
|
|
691
|
+
sage: TestSuite(G).run()
|
|
692
|
+
sage: G.category()
|
|
693
|
+
Category of infinite groups
|
|
694
|
+
"""
|
|
695
|
+
Element = FreeGroupElement
|
|
696
|
+
|
|
697
|
+
def __init__(self, generator_names, gap_group=None):
|
|
698
|
+
"""
|
|
699
|
+
Python constructor.
|
|
700
|
+
|
|
701
|
+
INPUT:
|
|
702
|
+
|
|
703
|
+
- ``generator_names`` -- tuple of strings; the names of the
|
|
704
|
+
generators
|
|
705
|
+
|
|
706
|
+
- ``libgap_free_group`` -- a LibGAP free group (default: ``None``);
|
|
707
|
+
the LibGAP free group to wrap (if ``None``, a suitable group will be
|
|
708
|
+
constructed)
|
|
709
|
+
|
|
710
|
+
TESTS::
|
|
711
|
+
|
|
712
|
+
sage: G.<a,b> = FreeGroup() # indirect doctest
|
|
713
|
+
sage: G
|
|
714
|
+
Free Group on generators {a, b}
|
|
715
|
+
sage: G.variable_names()
|
|
716
|
+
('a', 'b')
|
|
717
|
+
"""
|
|
718
|
+
if gap_group is None:
|
|
719
|
+
gap_group = libgap.FreeGroup(generator_names)
|
|
720
|
+
ParentLibGAP.__init__(self, gap_group)
|
|
721
|
+
if not generator_names:
|
|
722
|
+
cat = Groups().Finite()
|
|
723
|
+
else:
|
|
724
|
+
cat = Groups().Infinite()
|
|
725
|
+
Group.__init__(self, category=cat)
|
|
726
|
+
self._gen_names = generator_names
|
|
727
|
+
try:
|
|
728
|
+
self._assign_names(generator_names)
|
|
729
|
+
except ValueError:
|
|
730
|
+
pass
|
|
731
|
+
|
|
732
|
+
def __hash__(self):
|
|
733
|
+
"""
|
|
734
|
+
Make hashable.
|
|
735
|
+
|
|
736
|
+
EXAMPLES::
|
|
737
|
+
|
|
738
|
+
sage: F = FreeGroup(3)
|
|
739
|
+
sage: F.__hash__() == hash(F._gen_names)
|
|
740
|
+
True
|
|
741
|
+
"""
|
|
742
|
+
return hash(self._gen_names)
|
|
743
|
+
|
|
744
|
+
def __richcmp__(self, other, op):
|
|
745
|
+
"""
|
|
746
|
+
Rich comparison of ``self`` and ``other``.
|
|
747
|
+
|
|
748
|
+
EXAMPLES::
|
|
749
|
+
|
|
750
|
+
sage: G1 = FreeGroup('a, b')
|
|
751
|
+
sage: gg = libgap.FreeGroup('x', 'y')
|
|
752
|
+
sage: G2 = FreeGroup('a, b', gap_group=gg)
|
|
753
|
+
sage: G1 == G2
|
|
754
|
+
True
|
|
755
|
+
sage: G1 is G2
|
|
756
|
+
False
|
|
757
|
+
sage: G3 = FreeGroup('x, y')
|
|
758
|
+
sage: G1 == G3
|
|
759
|
+
False
|
|
760
|
+
sage: G2 == G3
|
|
761
|
+
False
|
|
762
|
+
"""
|
|
763
|
+
if not isinstance(other, self.__class__):
|
|
764
|
+
from sage.structure.richcmp import op_NE
|
|
765
|
+
return (op == op_NE)
|
|
766
|
+
return richcmp(self._gen_names, other._gen_names, op)
|
|
767
|
+
|
|
768
|
+
def _repr_(self):
|
|
769
|
+
"""
|
|
770
|
+
TESTS::
|
|
771
|
+
|
|
772
|
+
sage: G = FreeGroup('a, b')
|
|
773
|
+
sage: G # indirect doctest
|
|
774
|
+
Free Group on generators {a, b}
|
|
775
|
+
sage: G._repr_()
|
|
776
|
+
'Free Group on generators {a, b}'
|
|
777
|
+
"""
|
|
778
|
+
return 'Free Group on generators {' + ', '.join(self._gen_names) + '}'
|
|
779
|
+
|
|
780
|
+
def rank(self):
|
|
781
|
+
"""
|
|
782
|
+
Return the number of generators of ``self``.
|
|
783
|
+
|
|
784
|
+
Alias for :meth:`ngens`.
|
|
785
|
+
|
|
786
|
+
OUTPUT: integer
|
|
787
|
+
|
|
788
|
+
EXAMPLES::
|
|
789
|
+
|
|
790
|
+
sage: G = FreeGroup('a, b'); G
|
|
791
|
+
Free Group on generators {a, b}
|
|
792
|
+
sage: G.rank()
|
|
793
|
+
2
|
|
794
|
+
sage: H = FreeGroup(3, 'x')
|
|
795
|
+
sage: H
|
|
796
|
+
Free Group on generators {x0, x1, x2}
|
|
797
|
+
sage: H.rank()
|
|
798
|
+
3
|
|
799
|
+
"""
|
|
800
|
+
return self.ngens()
|
|
801
|
+
|
|
802
|
+
def _gap_init_(self):
|
|
803
|
+
"""
|
|
804
|
+
Return the string used to construct the object in gap.
|
|
805
|
+
|
|
806
|
+
EXAMPLES::
|
|
807
|
+
|
|
808
|
+
sage: G = FreeGroup(3)
|
|
809
|
+
sage: G._gap_init_()
|
|
810
|
+
'FreeGroup(["x0", "x1", "x2"])'
|
|
811
|
+
"""
|
|
812
|
+
gap_names = ['"' + s + '"' for s in self._gen_names]
|
|
813
|
+
gen_str = ', '.join(gap_names)
|
|
814
|
+
return 'FreeGroup(['+gen_str+'])'
|
|
815
|
+
|
|
816
|
+
def _element_constructor_(self, *args, **kwds):
|
|
817
|
+
"""
|
|
818
|
+
TESTS::
|
|
819
|
+
|
|
820
|
+
sage: G.<a,b> = FreeGroup()
|
|
821
|
+
sage: G([1, 2, 1]) # indirect doctest
|
|
822
|
+
a*b*a
|
|
823
|
+
sage: G([1, 2, -2, 1, 1, -2]) # indirect doctest
|
|
824
|
+
a^3*b^-1
|
|
825
|
+
|
|
826
|
+
sage: G( a.gap() )
|
|
827
|
+
a
|
|
828
|
+
sage: type(_)
|
|
829
|
+
<class 'sage.groups.free_group.FreeGroup_class_with_category.element_class'>
|
|
830
|
+
|
|
831
|
+
Check that conversion between free groups follow the convention that
|
|
832
|
+
names are preserved::
|
|
833
|
+
|
|
834
|
+
sage: F = FreeGroup('a,b')
|
|
835
|
+
sage: G = FreeGroup('b,a')
|
|
836
|
+
sage: G(F.gen(0))
|
|
837
|
+
a
|
|
838
|
+
sage: F(G.gen(0))
|
|
839
|
+
b
|
|
840
|
+
sage: a,b = F.gens()
|
|
841
|
+
sage: G(a^2*b^-3*a^-1)
|
|
842
|
+
a^2*b^-3*a^-1
|
|
843
|
+
|
|
844
|
+
Check that :issue:`17246` is fixed::
|
|
845
|
+
|
|
846
|
+
sage: F = FreeGroup(0)
|
|
847
|
+
sage: F([])
|
|
848
|
+
1
|
|
849
|
+
|
|
850
|
+
Check that 0 isn't considered the identity::
|
|
851
|
+
|
|
852
|
+
sage: F = FreeGroup('x')
|
|
853
|
+
sage: F(0)
|
|
854
|
+
Traceback (most recent call last):
|
|
855
|
+
...
|
|
856
|
+
TypeError: 'sage.rings.integer.Integer' object is not iterable
|
|
857
|
+
"""
|
|
858
|
+
if len(args) != 1:
|
|
859
|
+
return self.element_class(self, *args, **kwds)
|
|
860
|
+
x = args[0]
|
|
861
|
+
if x == 1 or x == [] or x == ():
|
|
862
|
+
return self.one()
|
|
863
|
+
try:
|
|
864
|
+
P = x.parent()
|
|
865
|
+
except AttributeError:
|
|
866
|
+
return self.element_class(self, x, **kwds)
|
|
867
|
+
if isinstance(P, FreeGroup_class):
|
|
868
|
+
names = {P._gen_names[abs(i)-1] for i in x.Tietze()}
|
|
869
|
+
if names.issubset(self._gen_names):
|
|
870
|
+
return self([i.sign()*(self._gen_names.index(P._gen_names[abs(i)-1])+1)
|
|
871
|
+
for i in x.Tietze()])
|
|
872
|
+
else:
|
|
873
|
+
raise ValueError('generators of %s not in the group' % x)
|
|
874
|
+
return self.element_class(self, x, **kwds)
|
|
875
|
+
|
|
876
|
+
def abelian_invariants(self):
|
|
877
|
+
r"""
|
|
878
|
+
Return the Abelian invariants of ``self``.
|
|
879
|
+
|
|
880
|
+
The Abelian invariants are given by a list of integers
|
|
881
|
+
`i_1 \dots i_j`, such that the abelianization of the
|
|
882
|
+
group is isomorphic to
|
|
883
|
+
|
|
884
|
+
.. MATH::
|
|
885
|
+
|
|
886
|
+
\ZZ / (i_1) \times \dots \times \ZZ / (i_j)
|
|
887
|
+
|
|
888
|
+
EXAMPLES::
|
|
889
|
+
|
|
890
|
+
sage: F.<a,b> = FreeGroup()
|
|
891
|
+
sage: F.abelian_invariants()
|
|
892
|
+
(0, 0)
|
|
893
|
+
"""
|
|
894
|
+
return (0,) * self.ngens()
|
|
895
|
+
|
|
896
|
+
def quotient(self, relations, **kwds):
|
|
897
|
+
"""
|
|
898
|
+
Return the quotient of ``self`` by the normal subgroup generated
|
|
899
|
+
by the given elements.
|
|
900
|
+
|
|
901
|
+
This quotient is a finitely presented groups with the same
|
|
902
|
+
generators as ``self``, and relations given by the elements of
|
|
903
|
+
``relations``.
|
|
904
|
+
|
|
905
|
+
INPUT:
|
|
906
|
+
|
|
907
|
+
- ``relations`` -- list/tuple/iterable with the elements of
|
|
908
|
+
the free group
|
|
909
|
+
- further named arguments, that are passed to the constructor
|
|
910
|
+
of a finitely presented group
|
|
911
|
+
|
|
912
|
+
OUTPUT:
|
|
913
|
+
|
|
914
|
+
A finitely presented group, with generators corresponding to
|
|
915
|
+
the generators of the free group, and relations corresponding
|
|
916
|
+
to the elements in ``relations``.
|
|
917
|
+
|
|
918
|
+
EXAMPLES::
|
|
919
|
+
|
|
920
|
+
sage: F.<a,b> = FreeGroup()
|
|
921
|
+
sage: F.quotient([a*b^2*a, b^3])
|
|
922
|
+
Finitely presented group < a, b | a*b^2*a, b^3 >
|
|
923
|
+
|
|
924
|
+
Division is shorthand for :meth:`quotient` ::
|
|
925
|
+
|
|
926
|
+
sage: F / [a*b^2*a, b^3]
|
|
927
|
+
Finitely presented group < a, b | a*b^2*a, b^3 >
|
|
928
|
+
|
|
929
|
+
Relations are converted to the free group, even if they are not
|
|
930
|
+
elements of it (if possible) ::
|
|
931
|
+
|
|
932
|
+
sage: F1.<a,b,c,d> = FreeGroup()
|
|
933
|
+
sage: F2.<a,b> = FreeGroup()
|
|
934
|
+
sage: r = a*b/a
|
|
935
|
+
sage: r.parent()
|
|
936
|
+
Free Group on generators {a, b}
|
|
937
|
+
sage: F1/[r]
|
|
938
|
+
Finitely presented group < a, b, c, d | a*b*a^-1 >
|
|
939
|
+
"""
|
|
940
|
+
from sage.groups.finitely_presented import FinitelyPresentedGroup
|
|
941
|
+
return FinitelyPresentedGroup(self,
|
|
942
|
+
tuple(map(self, relations)), **kwds)
|
|
943
|
+
|
|
944
|
+
__truediv__ = quotient
|