Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/quadratic_forms/quadratic_form__automorphisms.py
4079 views
1
"""
2
Automorphisms of Quadratic Forms
3
"""
4
from sage.interfaces.gp import gp
5
from sage.matrix.constructor import Matrix
6
from sage.rings.integer_ring import ZZ
7
from sage.misc.mrange import mrange
8
9
from sage.modules.free_module_element import vector
10
from sage.rings.arith import GCD
11
from sage.misc.sage_eval import sage_eval
12
from sage.misc.misc import SAGE_ROOT
13
14
import tempfile, os
15
from random import random
16
17
18
def basis_of_short_vectors(self, show_lengths=False, safe_flag=True):
19
"""
20
Return a basis for `ZZ^n` made of vectors with minimal lengths Q(`v`).
21
22
The safe_flag allows us to select whether we want a copy of the
23
output, or the original output. By default safe_flag = True, so
24
we return a copy of the cached information. If this is set to
25
False, then the routine is much faster but the return values are
26
vulnerable to being corrupted by the user.
27
28
OUTPUT:
29
a list of vectors, and optionally a list of values for each vector.
30
31
EXAMPLES::
32
33
sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])
34
sage: Q.basis_of_short_vectors()
35
[(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)]
36
sage: Q.basis_of_short_vectors(True)
37
([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)], [1, 3, 5, 7])
38
39
"""
40
## Try to use the cached results
41
try:
42
## Return the appropriate result
43
if show_lengths:
44
if safe_flag:
45
return deep_copy(self.__basis_of_short_vectors), deepcopy(self.__basis_of_short_vectors_lengths)
46
else:
47
return self.__basis_of_short_vectors, self.__basis_of_short_vectors_lengths
48
else:
49
if safe_flag:
50
return deepcopy(self.__basis_of_short_vectors)
51
else:
52
return deepcopy(self.__basis_of_short_vectors)
53
except:
54
pass
55
56
57
## Set an upper bound for the number of vectors to consider
58
Max_number_of_vectors = 10000
59
60
## Generate a PARI matrix string for the associated Hessian matrix
61
M_str = str(gp(self.matrix()))
62
63
64
## Run through all possible minimal lengths to find a spanning set of vectors
65
n = self.dim()
66
#MS = MatrixSpace(QQ, n)
67
M1 = Matrix([[0]])
68
vec_len = 0
69
while M1.rank() < n:
70
71
## DIAGONSTIC
72
#print
73
#print "Starting with vec_len = ", vec_len
74
#print "M_str = ", M_str
75
76
vec_len += 1
77
gp_mat = gp.qfminim(M_str, vec_len, Max_number_of_vectors)[3].mattranspose()
78
number_of_vecs = ZZ(gp_mat.matsize()[1])
79
vector_list = []
80
for i in range(number_of_vecs):
81
#print "List at", i, ":", list(gp_mat[i+1,])
82
new_vec = vector([ZZ(x) for x in list(gp_mat[i+1,])])
83
vector_list.append(new_vec)
84
85
86
## DIAGNOSTIC
87
#print "number_of_vecs = ", number_of_vecs
88
#print "vector_list = ", vector_list
89
90
91
## Make a matrix from the short vectors
92
if len(vector_list) > 0:
93
M1 = Matrix(vector_list)
94
95
96
## DIAGNOSTIC
97
#print "matrix of vectors = \n", M1
98
#print "rank of the matrix = ", M1.rank()
99
100
101
102
#print " vec_len = ", vec_len
103
#print M1
104
105
106
## Organize these vectors by length (and also introduce their negatives)
107
max_len = vec_len/2
108
vector_list_by_length = [[] for _ in range(max_len + 1)]
109
for v in vector_list:
110
l = self(v)
111
vector_list_by_length[l].append(v)
112
vector_list_by_length[l].append(vector([-x for x in v]))
113
114
115
## Make a matrix from the column vectors (in order of ascending length).
116
sorted_list = []
117
for i in range(len(vector_list_by_length)):
118
for v in vector_list_by_length[i]:
119
sorted_list.append(v)
120
sorted_matrix = Matrix(sorted_list).transpose()
121
122
123
## Determine a basis of vectors of minimal length
124
pivots = sorted_matrix.pivots()
125
basis = [sorted_matrix.column(i) for i in pivots]
126
pivot_lengths = [self(v) for v in basis]
127
128
129
## DIAGNOSTIC
130
#print "basis = ", basis
131
#print "pivot_lengths = ", pivot_lengths
132
133
134
## Cache the result
135
self.__basis_of_short_vectors = basis
136
self.__basis_of_short_vectors_lenghts = pivot_lengths
137
138
139
## Return the appropriate result
140
if show_lengths:
141
return basis, pivot_lengths
142
else:
143
return basis
144
145
146
147
148
def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False):
149
"""
150
Return a list of lists of short vectors `v`, sorted by length, with
151
Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of
152
length `i`. If the up_to_sign_flag is set to True, then only one of
153
the vectors of the pair `[v, -v]` is listed.
154
155
Note: This processes the PARI/GP output to always give elements of type `ZZ`.
156
157
OUTPUT:
158
a list of lists of vectors.
159
160
EXAMPLES::
161
162
sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])
163
sage: Q.short_vector_list_up_to_length(3)
164
[[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], []]
165
sage: Q.short_vector_list_up_to_length(4)
166
[[(0, 0, 0, 0)],
167
[(1, 0, 0, 0), [-1, 0, 0, 0]],
168
[],
169
[(0, 1, 0, 0), [0, -1, 0, 0]]]
170
sage: Q.short_vector_list_up_to_length(5)
171
[[(0, 0, 0, 0)],
172
[(1, 0, 0, 0), [-1, 0, 0, 0]],
173
[],
174
[(0, 1, 0, 0), [0, -1, 0, 0]],
175
[(1, 1, 0, 0),
176
[-1, -1, 0, 0],
177
(-1, 1, 0, 0),
178
[1, -1, 0, 0],
179
(2, 0, 0, 0),
180
[-2, 0, 0, 0]]]
181
sage: Q.short_vector_list_up_to_length(5, True)
182
[[(0, 0, 0, 0)],
183
[(1, 0, 0, 0)],
184
[],
185
[(0, 1, 0, 0)],
186
[(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]]
187
188
"""
189
## Set an upper bound for the number of vectors to consider
190
Max_number_of_vectors = 10000
191
192
## Generate a PARI matrix string for the associated Hessian matrix
193
M_str = str(gp(self.matrix()))
194
195
## Generate the short vectors
196
gp_mat = gp.qfminim(M_str, 2*len_bound-2, Max_number_of_vectors)[3].mattranspose()
197
number_of_rows = gp_mat.matsize()[1]
198
gp_mat_vector_list = [vector([ZZ(x) for x in gp_mat[i+1,]]) for i in range(number_of_rows)]
199
200
## Sort the vectors into lists by their length
201
vec_list = [[] for i in range(len_bound)]
202
for v in gp_mat_vector_list:
203
vec_list[self(v)].append(v)
204
if up_to_sign_flag == False:
205
vec_list[self(v)].append([-x for x in v])
206
207
## Add the zero vector by hand
208
zero_vec = vector([ZZ(0) for _ in range(self.dim())])
209
vec_list[0].append(zero_vec)
210
211
## Return the sorted list
212
return vec_list
213
214
215
216
def short_primitive_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False):
217
"""
218
Return a list of lists of short primitive vectors `v`, sorted by length, with
219
Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of
220
length `i`. If the up_to_sign_flag is set to True, then only one of
221
the vectors of the pair `[v, -v]` is listed.
222
223
Note: This processes the PARI/GP output to always give elements of type `ZZ`.
224
225
OUTPUT:
226
a list of lists of vectors.
227
228
EXAMPLES::
229
230
sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])
231
sage: Q.short_vector_list_up_to_length(5, True)
232
[[(0, 0, 0, 0)],
233
[(1, 0, 0, 0)],
234
[],
235
[(0, 1, 0, 0)],
236
[(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]]
237
sage: Q.short_primitive_vector_list_up_to_length(5, True)
238
[[], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0)]]
239
240
241
"""
242
## Get a list of short vectors
243
full_vec_list = self.short_vector_list_up_to_length(len_bound, up_to_sign_flag)
244
245
## Make a new list of the primitive vectors
246
prim_vec_list = [[v for v in L if GCD(list(v)) == 1] for L in full_vec_list] ## TO DO: Arrange for GCD to take a vector argument!
247
248
## Return the list of primitive vectors
249
return prim_vec_list
250
251
252
253
254
## ----------------------------------------------------------------------------------------------------
255
256
257
def automorphisms(self):
258
"""
259
Return a list of the automorphisms of the quadratic form.
260
261
EXAMPLES::
262
263
sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1])
264
sage: Q.number_of_automorphisms() # optional -- souvigner
265
48
266
sage: 2^3 * factorial(3)
267
48
268
sage: len(Q.automorphisms())
269
48
270
271
::
272
273
sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])
274
sage: Q.number_of_automorphisms() # optional -- souvigner
275
16
276
sage: aut = Q.automorphisms()
277
sage: len(aut)
278
16
279
sage: print([Q(M) == Q for M in aut])
280
[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]
281
282
sage: Q = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3])
283
sage: Q.automorphisms()
284
[
285
[1 0 0] [-1 0 0]
286
[0 1 0] [ 0 -1 0]
287
[0 0 1], [ 0 0 -1]
288
]
289
290
::
291
292
sage: Q = DiagonalQuadraticForm(ZZ, [1, -1])
293
sage: Q.automorphisms()
294
Traceback (most recent call last):
295
...
296
ValueError: not a definite form in QuadraticForm.automorphisms()
297
"""
298
## only for definite forms
299
if not self.is_definite():
300
raise ValueError, "not a definite form in QuadraticForm.automorphisms()"
301
302
## Check for a cached value
303
try:
304
return self.__automorphisms
305
except:
306
pass
307
308
309
## Find a basis of short vectors, and their lengths
310
basis, pivot_lengths = self.basis_of_short_vectors(show_lengths=True)
311
312
## List the relevant vectors by length
313
max_len = max(pivot_lengths)
314
vector_list_by_length = self.short_primitive_vector_list_up_to_length(max_len + 1)
315
316
317
## Make the matrix A:e_i |--> v_i to our new basis.
318
A = Matrix(basis).transpose()
319
Ainv = A.inverse()
320
#A1 = A.inverse() * A.det()
321
#Q1 = A1.transpose() * self.matrix() * A1 ## This is the matrix of Q
322
#Q = self.matrix() * A.det()**2
323
Q2 = A.transpose() * self.matrix() * A ## This is the matrix of Q in the new basis
324
Q3 = self.matrix()
325
326
327
## Determine all automorphisms
328
n = self.dim()
329
Auto_list = []
330
#ct = 0
331
332
## DIAGNOSTIC
333
#print "n = " + str(n)
334
#print "pivot_lengths = " + str(pivot_lengths)
335
#print "vector_list_by_length = " + str(vector_list_by_length)
336
#print "length of vector_list_by_length = " + str(len(vector_list_by_length))
337
338
for index_vec in mrange([len(vector_list_by_length[pivot_lengths[i]]) for i in range(n)]):
339
M = Matrix([vector_list_by_length[pivot_lengths[i]][index_vec[i]] for i in range(n)]).transpose()
340
#Q1 = self.matrix()
341
#if self(M) == self:
342
#ct += 1
343
#print "ct = ", ct, " M = "
344
#print M
345
#print
346
if M.transpose() * Q3 * M == Q2: ## THIS DOES THE SAME THING! =(
347
Auto_list.append(M * Ainv)
348
349
350
## Cache the answer and return the list
351
self.__automorphisms = Auto_list
352
self.__number_of_automorphisms = len(Auto_list)
353
return Auto_list
354
355
356
357
358
def number_of_automorphisms(self, recompute=False):
359
"""
360
Return a list of the number of automorphisms (of det 1 and -1) of
361
the quadratic form.
362
363
If recompute is True, then we will recompute the cached result.
364
365
OUTPUT:
366
an integer >= 2.
367
368
EXAMPLES::
369
370
sage: Q = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 1], unsafe_initialization=True, number_of_automorphisms=-1)
371
sage: Q.list_external_initializations()
372
['number_of_automorphisms']
373
sage: Q.number_of_automorphisms()
374
-1
375
sage: Q.number_of_automorphisms(recompute=True) # optional -- souvigner
376
48
377
sage: Q.list_external_initializations() # optional -- souvigner
378
[]
379
380
::
381
382
sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])
383
sage: Q.number_of_automorphisms() # optional -- souvigner
384
384
385
sage: 2^4 * factorial(4)
386
384
387
388
::
389
390
sage: Q = DiagonalQuadraticForm(ZZ, [1, -1])
391
sage: Q.number_of_automorphisms()
392
Traceback (most recent call last):
393
...
394
ValueError: not a definite form in QuadraticForm.number_of_automorphisms()
395
396
"""
397
## only for definite forms
398
if not self.is_definite():
399
raise ValueError, "not a definite form in QuadraticForm.number_of_automorphisms()"
400
401
## Try to use the cached version if we can
402
if not recompute:
403
try:
404
#print "Using the cached number of automorphisms."
405
#print "We stored", self.__number_of_automorphisms
406
return self.__number_of_automorphisms
407
except:
408
pass
409
410
## Otherwise cache and return the result
411
#print "Recomputing the number of automorphisms based on the list of automorphisms."
412
#self.__number_of_automorphisms = len(self.automorphisms()) ## This is now deprecated.
413
self.__number_of_automorphisms = self.number_of_automorphisms__souvigner()
414
try:
415
self._external_initialization_list.remove('number_of_automorphisms')
416
except:
417
pass ## Do nothing if the removal fails, since it might not be in the list (causing an error)!
418
return self.__number_of_automorphisms
419
420
421
422
def number_of_automorphisms__souvigner(self):
423
"""
424
Uses the Souvigner code to compute the number of automorphisms.
425
426
EXAMPLES::
427
428
sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1,1])
429
sage: Q.number_of_automorphisms__souvigner() # optional -- souvigner
430
3840
431
sage: 2^5 * factorial(5)
432
3840
433
434
"""
435
## Write an input text file
436
F_filename = '/tmp/tmp_isom_input' + str(random()) + ".txt"
437
F = open(F_filename, 'w')
438
#F = tempfile.NamedTemporaryFile(prefix='tmp_auto_input', suffix=".txt") ## This fails because the Souvigner code doesn't like random filenames (hyphens are bad...)!
439
F.write("#1 \n")
440
n = self.dim()
441
F.write(str(n) + "x0 \n") ## Use the lower-triangular form
442
for i in range(n):
443
for j in range(i+1):
444
if i == j:
445
F.write(str(2 * self[i,j]) + " ")
446
else:
447
F.write(str(self[i,j]) + " ")
448
F.write("\n")
449
F.flush()
450
#print "Input filename = ", F.name
451
#os.system("less " + F.name)
452
453
## Call the Souvigner automorphism code
454
souvigner_auto_path = SAGE_ROOT + "/local/bin/Souvigner_AUTO" ## FIX THIS LATER!!!
455
G1 = tempfile.NamedTemporaryFile(prefix='tmp_auto_ouput', suffix=".txt")
456
#print "Output filename = ", G1.name
457
os.system(souvigner_auto_path + " '" + F.name + "' > '" + G1.name +"'")
458
459
460
## Read the output
461
G2 = open(G1.name, 'r')
462
for line in G2:
463
if line.startswith("|Aut| = "):
464
num_of_autos = sage_eval(line.lstrip("|Aut| = "))
465
F.close()
466
G1.close()
467
G2.close()
468
os.system("rm -f " + F_filename)
469
#os.system("rm -f " + G1.name)
470
return num_of_autos
471
472
## Raise and error if we're here:
473
raise RuntimeError, "Oops! There is a problem..."
474
475
476
477
def set_number_of_automorphisms(self, num_autos):
478
"""
479
Set the number of automorphisms to be the value given. No error
480
checking is performed, to this may lead to erroneous results.
481
482
The fact that this result was set externally is recorded in the
483
internal list of external initializations, accessible by the
484
method list_external_initializations().
485
486
Return a list of the number of
487
automorphisms (of det 1 and -1) of the quadratic form.
488
489
OUTPUT:
490
None
491
492
EXAMPLES::
493
494
sage: Q = DiagonalQuadraticForm(ZZ, [1, 1, 1])
495
sage: Q.list_external_initializations()
496
[]
497
sage: Q.set_number_of_automorphisms(-3)
498
sage: Q.number_of_automorphisms()
499
-3
500
sage: Q.list_external_initializations()
501
['number_of_automorphisms']
502
503
"""
504
self.__number_of_automorphisms = num_autos
505
text = 'number_of_automorphisms'
506
if not text in self._external_initialization_list:
507
self._external_initialization_list.append(text)
508
509
510
511
### TODO
512
# def Nipp_automorphism_testing(self):
513
# """
514
# Testing the automorphism routine against Nipp's Tables
515
#
516
# --- MOVE THIS ELSEWHERE!!! ---
517
#
518
# """
519
# for i in range(20):
520
# Q = QuadraticForm(ZZ, 4, Nipp[i][2])
521
# my_num = Q.number_of_automorphisms()
522
# nipp_num = Nipp.number_of_automorphisms(i)
523
# print " i = " + str(i) + " my_num = " + str(my_num) + " nipp_num = " + str(nipp_num)
524
#
525
526
527