passagemath-groups 10.6.30__cp313-cp313-macosx_13_0_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-10.6.30.dist-info/METADATA +112 -0
- passagemath_groups-10.6.30.dist-info/RECORD +39 -0
- passagemath_groups-10.6.30.dist-info/WHEEL +6 -0
- passagemath_groups-10.6.30.dist-info/top_level.txt +2 -0
- 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
- sage/all__sagemath_groups.py +21 -0
- sage/geometry/all__sagemath_groups.py +1 -0
- sage/geometry/palp_normal_form.cpython-313-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-313-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
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-groups
|
|
2
|
+
r"""
|
|
3
|
+
Orthogonal Groups of Torsion Quadratic Forms
|
|
4
|
+
|
|
5
|
+
The orthogonal group of a torsion quadratic module `T`
|
|
6
|
+
consists of all linear self-maps of `T` which preserve
|
|
7
|
+
the torsion quadratic form.
|
|
8
|
+
|
|
9
|
+
EXAMPLES::
|
|
10
|
+
|
|
11
|
+
sage: L = IntegralLattice("A2").twist(2) # needs sage.graphs
|
|
12
|
+
sage: T = L.discriminant_group() # needs sage.graphs
|
|
13
|
+
sage: Oq = T.orthogonal_group() # needs sage.graphs
|
|
14
|
+
|
|
15
|
+
The isometries act on elements of their domain::
|
|
16
|
+
|
|
17
|
+
sage: g = Oq(matrix(ZZ, 2, [0, 3, 1, 2])) # needs sage.graphs
|
|
18
|
+
sage: T.gen(0) * g # needs sage.graphs
|
|
19
|
+
(0, 3)
|
|
20
|
+
|
|
21
|
+
Isometries are represented with respect to
|
|
22
|
+
the Smith form generators of `T`::
|
|
23
|
+
|
|
24
|
+
sage: # needs sage.graphs
|
|
25
|
+
sage: L = IntegralLattice("A2").twist(2).direct_sum(IntegralLattice('U'))
|
|
26
|
+
sage: T = L.discriminant_group().normal_form()
|
|
27
|
+
sage: OT = T.orthogonal_group()
|
|
28
|
+
sage: g = matrix(2, 2, [1, 3, 1, 2])
|
|
29
|
+
sage: g = OT(g)
|
|
30
|
+
sage: g
|
|
31
|
+
[1 3]
|
|
32
|
+
[1 2]
|
|
33
|
+
sage: sf = T.smith_form_gens()
|
|
34
|
+
sage: matrix([s * g for s in T.smith_form_gens()])
|
|
35
|
+
[1 3]
|
|
36
|
+
[1 2]
|
|
37
|
+
|
|
38
|
+
AUTHORS:
|
|
39
|
+
|
|
40
|
+
- Simon Brandhorst (2020-01-08): initial version
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# ****************************************************************************
|
|
44
|
+
# Copyright (C) 2020 Simon Brandhorst <sbrandhorst@web.de>
|
|
45
|
+
#
|
|
46
|
+
# This program is free software: you can redistribute it and/or modify
|
|
47
|
+
# it under the terms of the GNU General Public License as published by
|
|
48
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
49
|
+
# (at your option) any later version.
|
|
50
|
+
# https://www.gnu.org/licenses/
|
|
51
|
+
# ****************************************************************************
|
|
52
|
+
from sage.libs.gap.libgap import libgap
|
|
53
|
+
from sage.groups.abelian_gps.abelian_aut import AbelianGroupAutomorphismGroup_subgroup, AbelianGroupAutomorphism, AbelianGroupAutomorphismGroup_gap
|
|
54
|
+
from sage.modules.torsion_quadratic_module import TorsionQuadraticModule
|
|
55
|
+
from sage.rings.integer_ring import ZZ
|
|
56
|
+
from sage.matrix.constructor import matrix
|
|
57
|
+
from sage.categories.action import Action
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FqfIsometry(AbelianGroupAutomorphism):
|
|
61
|
+
r"""
|
|
62
|
+
Isometry of a finite quadratic/bilinear form.
|
|
63
|
+
|
|
64
|
+
INPUT:
|
|
65
|
+
|
|
66
|
+
- ``parent`` -- the parent :class:`~FqfOrthogonalGroup`
|
|
67
|
+
- ``x`` -- a libgap element
|
|
68
|
+
- ``check`` -- boolean (default: ``True``)
|
|
69
|
+
|
|
70
|
+
EXAMPLES::
|
|
71
|
+
|
|
72
|
+
sage: q = matrix.diagonal([2/3])
|
|
73
|
+
sage: q = TorsionQuadraticForm(q)
|
|
74
|
+
sage: G = q.orthogonal_group()
|
|
75
|
+
sage: g = G(matrix(ZZ, 1, [2]))
|
|
76
|
+
sage: g
|
|
77
|
+
[2]
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def _repr_(self):
|
|
81
|
+
r"""
|
|
82
|
+
Return the string representation of ``self``.
|
|
83
|
+
|
|
84
|
+
EXAMPLES::
|
|
85
|
+
|
|
86
|
+
sage: q = matrix.diagonal([2/3, 4/3])
|
|
87
|
+
sage: q = TorsionQuadraticForm(q)
|
|
88
|
+
sage: G = q.orthogonal_group()
|
|
89
|
+
sage: g = G.one()
|
|
90
|
+
sage: g
|
|
91
|
+
[1 0]
|
|
92
|
+
[0 1]
|
|
93
|
+
"""
|
|
94
|
+
return str(self.matrix())
|
|
95
|
+
|
|
96
|
+
def __call__(self, x):
|
|
97
|
+
r"""
|
|
98
|
+
Return the image of ``x`` under ``self``.
|
|
99
|
+
|
|
100
|
+
EXAMPLES::
|
|
101
|
+
|
|
102
|
+
sage: q = matrix.diagonal([2/3, 4/3])
|
|
103
|
+
sage: q = TorsionQuadraticForm(q)
|
|
104
|
+
sage: G = q.orthogonal_group()
|
|
105
|
+
sage: g = G(matrix(ZZ, 2, [1, 0, 0, 2]))
|
|
106
|
+
sage: g
|
|
107
|
+
[1 0]
|
|
108
|
+
[0 2]
|
|
109
|
+
sage: x = q.0
|
|
110
|
+
sage: g(x)
|
|
111
|
+
(1, 0)
|
|
112
|
+
"""
|
|
113
|
+
if x in self.parent().invariant_form():
|
|
114
|
+
return x * self
|
|
115
|
+
else:
|
|
116
|
+
return AbelianGroupAutomorphism.__call__(self, x)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class FqfOrthogonalGroup(AbelianGroupAutomorphismGroup_subgroup):
|
|
120
|
+
r"""
|
|
121
|
+
Return a group of isometries of this torsion quadratic form.
|
|
122
|
+
|
|
123
|
+
Do not call this class directly instead use
|
|
124
|
+
:meth:`sage.modules.torsion_quadratic_module.orthogonal_group`.
|
|
125
|
+
|
|
126
|
+
INPUT:
|
|
127
|
+
|
|
128
|
+
- ``T`` -- a non degenerate torsion quadratic module
|
|
129
|
+
|
|
130
|
+
EXAMPLES::
|
|
131
|
+
|
|
132
|
+
sage: q = matrix.diagonal(QQ, [2/3, 2/3, 4/3])
|
|
133
|
+
sage: T = TorsionQuadraticForm(q)
|
|
134
|
+
sage: T
|
|
135
|
+
Finite quadratic module over Integer Ring with invariants (3, 3, 3)
|
|
136
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
137
|
+
[2/3 0 0]
|
|
138
|
+
[ 0 2/3 0]
|
|
139
|
+
[ 0 0 4/3]
|
|
140
|
+
sage: T.orthogonal_group()
|
|
141
|
+
Group of isometries of
|
|
142
|
+
Finite quadratic module over Integer Ring with invariants (3, 3, 3)
|
|
143
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
144
|
+
[2/3 0 0]
|
|
145
|
+
[ 0 2/3 0]
|
|
146
|
+
[ 0 0 4/3]
|
|
147
|
+
generated by 3 elements
|
|
148
|
+
sage: q = matrix.diagonal(QQ, [3/2, 1/4, 1/4])
|
|
149
|
+
sage: T = TorsionQuadraticForm(q)
|
|
150
|
+
sage: T.orthogonal_group().order()
|
|
151
|
+
8
|
|
152
|
+
|
|
153
|
+
Action on an invariant subquotient::
|
|
154
|
+
|
|
155
|
+
sage: T = TorsionQuadraticForm(matrix.diagonal([2/9] + [2/27]))
|
|
156
|
+
sage: S1 = 3 * T
|
|
157
|
+
sage: S2 = 9 * T
|
|
158
|
+
sage: Q = S1/S2
|
|
159
|
+
sage: G = T.orthogonal_group()
|
|
160
|
+
sage: g = G(matrix(ZZ, 2, [8, 0, 0, 1]))
|
|
161
|
+
sage: Q.1 * g
|
|
162
|
+
(0, 1)
|
|
163
|
+
"""
|
|
164
|
+
Element = FqfIsometry
|
|
165
|
+
|
|
166
|
+
def __init__(self, ambient, gens, fqf, check=False):
|
|
167
|
+
r"""
|
|
168
|
+
TESTS::
|
|
169
|
+
|
|
170
|
+
sage: q = matrix.diagonal(QQ, [2/3, 2/3, 4/3])
|
|
171
|
+
sage: T = TorsionQuadraticForm(q)
|
|
172
|
+
sage: Oq = T.orthogonal_group()
|
|
173
|
+
sage: TestSuite(Oq).run()
|
|
174
|
+
"""
|
|
175
|
+
# We act on the smith form generators
|
|
176
|
+
# because they are independent
|
|
177
|
+
if not isinstance(fqf, TorsionQuadraticModule):
|
|
178
|
+
raise TypeError("input must be a torsion quadratic module")
|
|
179
|
+
if not isinstance(ambient, AbelianGroupAutomorphismGroup_gap):
|
|
180
|
+
raise TypeError("input must be a torsion quadratic module")
|
|
181
|
+
if not fqf.invariants() == ambient.domain().gens_orders():
|
|
182
|
+
raise ValueError("invariants of the abelian groups do not match")
|
|
183
|
+
gens = [ambient(g) for g in gens]
|
|
184
|
+
self._invariant_form = fqf
|
|
185
|
+
AbelianGroupAutomorphismGroup_subgroup.__init__(self, ambient, gens)
|
|
186
|
+
if check and any(not self._preserves_form(g) for g in self.gens()):
|
|
187
|
+
raise ValueError("a generator does not preserve the quadratic form")
|
|
188
|
+
|
|
189
|
+
def invariant_form(self):
|
|
190
|
+
r"""
|
|
191
|
+
Return the torsion quadratic form left invariant.
|
|
192
|
+
|
|
193
|
+
EXAMPLES::
|
|
194
|
+
|
|
195
|
+
sage: q = matrix.diagonal(QQ, [2/3])
|
|
196
|
+
sage: T = TorsionQuadraticForm(q)
|
|
197
|
+
sage: Oq = T.orthogonal_group()
|
|
198
|
+
sage: Oq.invariant_form() is T
|
|
199
|
+
True
|
|
200
|
+
"""
|
|
201
|
+
return self._invariant_form
|
|
202
|
+
|
|
203
|
+
def _element_constructor_(self, x, check=True):
|
|
204
|
+
r"""
|
|
205
|
+
Construct an element from ``x`` and handle conversions.
|
|
206
|
+
|
|
207
|
+
INPUT:
|
|
208
|
+
|
|
209
|
+
- ``x`` -- something that converts in can be:
|
|
210
|
+
|
|
211
|
+
* a libgap element
|
|
212
|
+
* an integer matrix in the covering matrix ring
|
|
213
|
+
* a class:`sage.modules.fg_pid.fgp_morphism.FGP_Morphism`
|
|
214
|
+
defining an automorphism -- the domain of ``x`` must have
|
|
215
|
+
invariants equal to ``self.domain().gens_orders()``
|
|
216
|
+
* something that acts on the invariant form module
|
|
217
|
+
|
|
218
|
+
EXAMPLES::
|
|
219
|
+
|
|
220
|
+
sage: # needs sage.graphs
|
|
221
|
+
sage: L = IntegralLattice("A2").twist(2).direct_sum(IntegralLattice("A2"))
|
|
222
|
+
sage: q = L.discriminant_group()
|
|
223
|
+
sage: OL = L.orthogonal_group()
|
|
224
|
+
sage: Oq = q.orthogonal_group()
|
|
225
|
+
sage: f = matrix(ZZ, 2, [0, 1, -1, -1])
|
|
226
|
+
sage: f = matrix.block_diagonal([f, f])
|
|
227
|
+
sage: f = OL(f)
|
|
228
|
+
sage: fbar = Oq(f)
|
|
229
|
+
sage: fbar
|
|
230
|
+
[1 3]
|
|
231
|
+
[3 4]
|
|
232
|
+
|
|
233
|
+
Note that the following does not work since it may lead to ambiguities, see :issue:`30669`::
|
|
234
|
+
|
|
235
|
+
sage: Oq(f.matrix()) # needs sage.graphs
|
|
236
|
+
Traceback (most recent call last):
|
|
237
|
+
...
|
|
238
|
+
ValueError: ...
|
|
239
|
+
|
|
240
|
+
But a matrix in the covering works::
|
|
241
|
+
|
|
242
|
+
sage: fbar == Oq(fbar.matrix()) # needs sage.graphs
|
|
243
|
+
True
|
|
244
|
+
|
|
245
|
+
TESTS::
|
|
246
|
+
|
|
247
|
+
sage: # needs sage.graphs
|
|
248
|
+
sage: all(x*f==x*fbar for x in q.gens())
|
|
249
|
+
True
|
|
250
|
+
sage: L = IntegralLattice("A2").twist(3)
|
|
251
|
+
sage: OL = L.orthogonal_group()
|
|
252
|
+
sage: assert OL(OL.0.matrix()) == OL.0
|
|
253
|
+
sage: q = L.discriminant_group()
|
|
254
|
+
sage: Oq = q.orthogonal_group()
|
|
255
|
+
sage: assert Oq(Oq.0.matrix()) == Oq.0
|
|
256
|
+
"""
|
|
257
|
+
from sage.libs.gap.element import GapElement
|
|
258
|
+
if not isinstance(x, GapElement):
|
|
259
|
+
try:
|
|
260
|
+
# if there is an action try that
|
|
261
|
+
gen = self.invariant_form().smith_form_gens()
|
|
262
|
+
x = matrix(ZZ, [(g*x).vector() for g in gen])
|
|
263
|
+
except TypeError:
|
|
264
|
+
pass
|
|
265
|
+
f = AbelianGroupAutomorphismGroup_subgroup._element_constructor_(self, x, check=True)
|
|
266
|
+
if check:
|
|
267
|
+
# double check that the form is preserved
|
|
268
|
+
# this is expensive
|
|
269
|
+
if not self._preserves_form(f):
|
|
270
|
+
raise ValueError("not an isometry")
|
|
271
|
+
return f
|
|
272
|
+
|
|
273
|
+
def _preserves_form(self, f):
|
|
274
|
+
r"""
|
|
275
|
+
Return if ``f`` preserves the form.
|
|
276
|
+
|
|
277
|
+
INPUT:
|
|
278
|
+
|
|
279
|
+
- ``f`` -- something that acts on the domain
|
|
280
|
+
|
|
281
|
+
EXAMPLES::
|
|
282
|
+
|
|
283
|
+
sage: T = TorsionQuadraticForm(matrix([1/4]))
|
|
284
|
+
sage: G = T.orthogonal_group()
|
|
285
|
+
sage: g = G.gen(0)
|
|
286
|
+
sage: G._preserves_form(g)
|
|
287
|
+
True
|
|
288
|
+
"""
|
|
289
|
+
g = self.invariant_form().smith_form_gens()
|
|
290
|
+
gf = tuple(h*f for h in g)
|
|
291
|
+
n = len(g)
|
|
292
|
+
for i in range(n):
|
|
293
|
+
if gf[i].q() != g[i].q():
|
|
294
|
+
return False
|
|
295
|
+
for j in range(i+1, n):
|
|
296
|
+
if g[i].b(g[j]) != gf[i].b(gf[j]):
|
|
297
|
+
return False
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
def _get_action_(self, S, op, self_on_left):
|
|
301
|
+
r"""
|
|
302
|
+
Provide the coercion system with an action.
|
|
303
|
+
|
|
304
|
+
EXAMPLES::
|
|
305
|
+
|
|
306
|
+
sage: q = matrix.diagonal([2/3, 4/3])
|
|
307
|
+
sage: q = TorsionQuadraticForm(q)
|
|
308
|
+
sage: G = q.orthogonal_group()
|
|
309
|
+
sage: G._get_action_(q, operator.mul, False)
|
|
310
|
+
Right action by Group of isometries of
|
|
311
|
+
Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
312
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
313
|
+
[2/3 0]
|
|
314
|
+
[ 0 4/3]
|
|
315
|
+
generated by 2 elements on Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
316
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
317
|
+
[2/3 0]
|
|
318
|
+
[ 0 4/3]
|
|
319
|
+
"""
|
|
320
|
+
import operator
|
|
321
|
+
if op == operator.mul and not self_on_left:
|
|
322
|
+
T = self.invariant_form()
|
|
323
|
+
if S == T:
|
|
324
|
+
return ActionOnFqf(self, S)
|
|
325
|
+
try:
|
|
326
|
+
if S.is_submodule(T):
|
|
327
|
+
# check if the submodule is invariant
|
|
328
|
+
if all(T(s)*g in S for s in S.gens() for g in self.gens()):
|
|
329
|
+
return ActionOnFqf(self, S, on_subquotient=True)
|
|
330
|
+
elif S.V().is_submodule(T.V()) and T.W().is_submodule(S.W()): # is a subquotient
|
|
331
|
+
Q1 = S.V()/T.W()
|
|
332
|
+
Q2 = S.W()/T.W()
|
|
333
|
+
if (
|
|
334
|
+
all(T(q) * g in Q1 for q in Q1.gens() for g in self.gens()) and
|
|
335
|
+
all(T(q) * g in Q2 for q in Q2.gens() for g in self.gens())
|
|
336
|
+
):
|
|
337
|
+
return ActionOnFqf(self, S, on_subquotient=True)
|
|
338
|
+
except AttributeError:
|
|
339
|
+
pass
|
|
340
|
+
try:
|
|
341
|
+
return AbelianGroupAutomorphismGroup_subgroup._get_action_(self, S, op, self_on_left)
|
|
342
|
+
except AttributeError:
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
def _subgroup_constructor(self, libgap_subgroup):
|
|
346
|
+
r"""
|
|
347
|
+
Create a subgroup from the input.
|
|
348
|
+
|
|
349
|
+
See :class:`~sage.groups.libgap_wrapper`. Override this in derived
|
|
350
|
+
classes.
|
|
351
|
+
|
|
352
|
+
EXAMPLES::
|
|
353
|
+
|
|
354
|
+
sage: q = TorsionQuadraticForm(matrix.diagonal([2/3, 2/3]))
|
|
355
|
+
sage: G = q.orthogonal_group()
|
|
356
|
+
sage: G.subgroup(G.gens()[:1]) # indirect doctest
|
|
357
|
+
Group of isometries of
|
|
358
|
+
Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
359
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
360
|
+
[2/3 0]
|
|
361
|
+
[ 0 2/3]
|
|
362
|
+
generated by 1 elements
|
|
363
|
+
"""
|
|
364
|
+
generators = libgap_subgroup.GeneratorsOfGroup()
|
|
365
|
+
generators = tuple(self(g, check=False) for g in generators)
|
|
366
|
+
return FqfOrthogonalGroup(self, generators, self.invariant_form(), check=False)
|
|
367
|
+
|
|
368
|
+
def _repr_(self):
|
|
369
|
+
r"""
|
|
370
|
+
The string representation of ``self``.
|
|
371
|
+
|
|
372
|
+
EXAMPLES::
|
|
373
|
+
|
|
374
|
+
sage: q = TorsionQuadraticForm(matrix.diagonal([2/3,2/3]))
|
|
375
|
+
sage: q.orthogonal_group()
|
|
376
|
+
Group of isometries of
|
|
377
|
+
Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
378
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
379
|
+
[2/3 0]
|
|
380
|
+
[ 0 2/3]
|
|
381
|
+
generated by 2 elements
|
|
382
|
+
"""
|
|
383
|
+
return "Group of isometries of \n%s\ngenerated by %s elements" % (self.invariant_form(), len(self.gens()))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class ActionOnFqf(Action):
|
|
387
|
+
r"""
|
|
388
|
+
Action on a finite quadratic module.
|
|
389
|
+
|
|
390
|
+
INPUT:
|
|
391
|
+
|
|
392
|
+
- ``orthogonal_grp`` -- an instance of :class:`GroupOfIsometries`
|
|
393
|
+
- ``fqf`` -- a torsion quadratic module
|
|
394
|
+
- ``on_subquotient`` -- boolean (default: ``False``)
|
|
395
|
+
- ``is_left`` -- boolean (default: ``False``)
|
|
396
|
+
|
|
397
|
+
EXAMPLES::
|
|
398
|
+
|
|
399
|
+
sage: q = matrix.diagonal([2/3, 4/3])
|
|
400
|
+
sage: q = TorsionQuadraticForm(q)
|
|
401
|
+
sage: G = q.orthogonal_group()
|
|
402
|
+
sage: g = G(matrix.diagonal([2, 2]))
|
|
403
|
+
sage: g
|
|
404
|
+
[2 0]
|
|
405
|
+
[0 2]
|
|
406
|
+
sage: x = q.0
|
|
407
|
+
sage: x * g
|
|
408
|
+
(2, 0)
|
|
409
|
+
"""
|
|
410
|
+
def __init__(self, orthogonal_grp, fqf, on_subquotient=False, is_left=False):
|
|
411
|
+
r"""
|
|
412
|
+
Initialize the action.
|
|
413
|
+
|
|
414
|
+
TESTS::
|
|
415
|
+
|
|
416
|
+
sage: from sage.groups.fqf_orthogonal import ActionOnFqf
|
|
417
|
+
sage: q = matrix.diagonal([2/3, 4/3])
|
|
418
|
+
sage: q = TorsionQuadraticForm(q)
|
|
419
|
+
sage: G = q.orthogonal_group()
|
|
420
|
+
sage: A = ActionOnFqf(G, q, is_left=True)
|
|
421
|
+
Traceback (most recent call last):
|
|
422
|
+
...
|
|
423
|
+
ValueError: the action is from the right
|
|
424
|
+
"""
|
|
425
|
+
import operator
|
|
426
|
+
self._on_subquotient = on_subquotient
|
|
427
|
+
if is_left:
|
|
428
|
+
raise ValueError("the action is from the right")
|
|
429
|
+
Action.__init__(self, orthogonal_grp, fqf, is_left, operator.mul)
|
|
430
|
+
|
|
431
|
+
def _act_(self, g, a):
|
|
432
|
+
r"""
|
|
433
|
+
This defines the group action.
|
|
434
|
+
|
|
435
|
+
INPUT:
|
|
436
|
+
|
|
437
|
+
- ``a`` -- an element of the invariant submodule
|
|
438
|
+
- ``g`` -- an element of the acting group
|
|
439
|
+
|
|
440
|
+
OUTPUT: an element of the invariant submodule
|
|
441
|
+
|
|
442
|
+
EXAMPLES::
|
|
443
|
+
|
|
444
|
+
sage: from sage.groups.fqf_orthogonal import ActionOnFqf
|
|
445
|
+
sage: q = matrix.diagonal([2/3, 4/3])
|
|
446
|
+
sage: q = TorsionQuadraticForm(q)
|
|
447
|
+
sage: g1 = 2 * matrix.identity(2)
|
|
448
|
+
sage: g2 = matrix(ZZ, 2, [1, 0, 0, 2])
|
|
449
|
+
sage: G = q.orthogonal_group(gens=[g1, g2])
|
|
450
|
+
sage: A = ActionOnFqf(G, q)
|
|
451
|
+
sage: A
|
|
452
|
+
Right action by Group of isometries of
|
|
453
|
+
Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
454
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
455
|
+
[2/3 0]
|
|
456
|
+
[ 0 4/3]
|
|
457
|
+
generated by 2 elements on Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
458
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
459
|
+
[2/3 0]
|
|
460
|
+
[ 0 4/3]
|
|
461
|
+
sage: x = q.an_element()
|
|
462
|
+
sage: g = G.an_element()
|
|
463
|
+
sage: A(x, g).parent()
|
|
464
|
+
Finite quadratic module over Integer Ring with invariants (3, 3)
|
|
465
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
466
|
+
[2/3 0]
|
|
467
|
+
[ 0 4/3]
|
|
468
|
+
sage: q = TorsionQuadraticForm(matrix.diagonal([2/3, 2/3, 6/8, 1/4]))
|
|
469
|
+
sage: G = q.orthogonal_group()
|
|
470
|
+
sage: q2 = q.primary_part(2)
|
|
471
|
+
sage: g = G(matrix.diagonal([1, 7]))
|
|
472
|
+
sage: q2.gen(1) * g
|
|
473
|
+
(0, 3)
|
|
474
|
+
"""
|
|
475
|
+
if self.is_left():
|
|
476
|
+
pass
|
|
477
|
+
# this would be a left action but... we do not allow it.
|
|
478
|
+
# v = (a.vector()*g.matrix().inverse())
|
|
479
|
+
# P = a.parent()
|
|
480
|
+
# return P.linear_combination_of_smith_form_gens(v)
|
|
481
|
+
elif self._on_subquotient:
|
|
482
|
+
S = a.parent()
|
|
483
|
+
T = g.parent().invariant_form()
|
|
484
|
+
return S(T(a)*g)
|
|
485
|
+
else:
|
|
486
|
+
v = (a.vector()*g.matrix())
|
|
487
|
+
P = a.parent()
|
|
488
|
+
return P.linear_combination_of_smith_form_gens(v)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _isom_fqf(A, B=None):
|
|
492
|
+
r"""
|
|
493
|
+
Return isometries from `A` to `B`.
|
|
494
|
+
|
|
495
|
+
INPUT:
|
|
496
|
+
|
|
497
|
+
- ``A`` -- a torsion quadratic module
|
|
498
|
+
- ``B`` -- (default: ``None``) a torsion quadratic module
|
|
499
|
+
|
|
500
|
+
OUTPUT: list of generators of the orthogonal group of A
|
|
501
|
+
|
|
502
|
+
If ``B`` is given, this returns instead a single isometry of `A` and `B`
|
|
503
|
+
or raises a :exc:`ValueError` if `A` and `B` are not isometric.
|
|
504
|
+
|
|
505
|
+
EXAMPLES::
|
|
506
|
+
|
|
507
|
+
sage: q = matrix.diagonal(QQ, 3*[2/3])
|
|
508
|
+
sage: q = TorsionQuadraticForm(q)
|
|
509
|
+
sage: gens = sage.groups.fqf_orthogonal._isom_fqf(q, q)
|
|
510
|
+
sage: q1 = q.submodule_with_gens(gens)
|
|
511
|
+
sage: q1.gram_matrix_quadratic() == q.gram_matrix_quadratic()
|
|
512
|
+
True
|
|
513
|
+
|
|
514
|
+
TESTS::
|
|
515
|
+
|
|
516
|
+
sage: for p in primes_first_n(7)[1:]: # long time
|
|
517
|
+
....: q = matrix.diagonal(QQ, 3 * [2/p])
|
|
518
|
+
....: q = TorsionQuadraticForm(q)
|
|
519
|
+
....: assert q.orthogonal_group().order()==GO(3, p).order()
|
|
520
|
+
"""
|
|
521
|
+
def orbits(G, L):
|
|
522
|
+
r"""
|
|
523
|
+
Return the orbits of `L` under `G`.
|
|
524
|
+
|
|
525
|
+
INPUT:
|
|
526
|
+
|
|
527
|
+
- ``G`` -- an fqf_orthognal group
|
|
528
|
+
- ``L`` -- list of tuples of elements of the domain of ``G``
|
|
529
|
+
|
|
530
|
+
OUTPUT: list of orbit representatives of `L`
|
|
531
|
+
"""
|
|
532
|
+
D = G.invariant_form()
|
|
533
|
+
A = G.domain()
|
|
534
|
+
L = libgap([[A(g).gap() for g in f] for f in L])
|
|
535
|
+
orb = G.gap().Orbits(L,libgap.OnTuples)
|
|
536
|
+
orb = [g[0] for g in orb]
|
|
537
|
+
orb = [[D.linear_combination_of_smith_form_gens(A(g).exponents()) for g in f] for f in orb]
|
|
538
|
+
return orb
|
|
539
|
+
|
|
540
|
+
if B is None:
|
|
541
|
+
B = A
|
|
542
|
+
automorphisms = True
|
|
543
|
+
else:
|
|
544
|
+
automorphisms = False
|
|
545
|
+
if A.invariants() != B.invariants():
|
|
546
|
+
raise ValueError("torsion quadratic modules are not isometric")
|
|
547
|
+
n = len(A.smith_form_gens())
|
|
548
|
+
# separating the different primes here would speed things up
|
|
549
|
+
b_cand = [[b for b in B if b.q() == a.q() and b.order() == a.order()] for a in A.smith_form_gens()]
|
|
550
|
+
|
|
551
|
+
G = B.orthogonal_group(())
|
|
552
|
+
ambient = G.ambient()
|
|
553
|
+
waiting = [[]]
|
|
554
|
+
while len(waiting) > 0:
|
|
555
|
+
# f is an i-partial isometry
|
|
556
|
+
f = waiting.pop()
|
|
557
|
+
i = len(f)
|
|
558
|
+
if i == n:
|
|
559
|
+
# f is a full isometry
|
|
560
|
+
if not automorphisms:
|
|
561
|
+
return f
|
|
562
|
+
g = ambient(matrix(f))
|
|
563
|
+
if g not in G:
|
|
564
|
+
G = B.orthogonal_group(tuple(ambient(s.matrix()) for s in G.gens())+(g,))
|
|
565
|
+
waiting = orbits(G, waiting)
|
|
566
|
+
continue
|
|
567
|
+
# extend f to an i+1 - partial isometry in all possible ways
|
|
568
|
+
a = A.smith_form_gens()[i]
|
|
569
|
+
card = ZZ.prod(A.smith_form_gen(k).order() for k in range(i+1))
|
|
570
|
+
for b in b_cand[i]:
|
|
571
|
+
if all(b.b(f[k]) == a.b(A.smith_form_gens()[k]) for k in range(i)):
|
|
572
|
+
fnew = f + [b]
|
|
573
|
+
# check that the elements of fnew are independent
|
|
574
|
+
if B.submodule(fnew).cardinality() == card:
|
|
575
|
+
waiting.append(fnew)
|
|
576
|
+
if not automorphisms:
|
|
577
|
+
raise ValueError("torsion quadratic modules are not isometric")
|
|
578
|
+
gens = G.gap().SmallGeneratingSet()
|
|
579
|
+
return [G(g).matrix() for g in gens]
|