Path: blob/master/sage/quadratic_forms/quadratic_form__automorphisms.py
4079 views
"""1Automorphisms of Quadratic Forms2"""3from sage.interfaces.gp import gp4from sage.matrix.constructor import Matrix5from sage.rings.integer_ring import ZZ6from sage.misc.mrange import mrange78from sage.modules.free_module_element import vector9from sage.rings.arith import GCD10from sage.misc.sage_eval import sage_eval11from sage.misc.misc import SAGE_ROOT1213import tempfile, os14from random import random151617def basis_of_short_vectors(self, show_lengths=False, safe_flag=True):18"""19Return a basis for `ZZ^n` made of vectors with minimal lengths Q(`v`).2021The safe_flag allows us to select whether we want a copy of the22output, or the original output. By default safe_flag = True, so23we return a copy of the cached information. If this is set to24False, then the routine is much faster but the return values are25vulnerable to being corrupted by the user.2627OUTPUT:28a list of vectors, and optionally a list of values for each vector.2930EXAMPLES::3132sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])33sage: Q.basis_of_short_vectors()34[(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)]35sage: Q.basis_of_short_vectors(True)36([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)], [1, 3, 5, 7])3738"""39## Try to use the cached results40try:41## Return the appropriate result42if show_lengths:43if safe_flag:44return deep_copy(self.__basis_of_short_vectors), deepcopy(self.__basis_of_short_vectors_lengths)45else:46return self.__basis_of_short_vectors, self.__basis_of_short_vectors_lengths47else:48if safe_flag:49return deepcopy(self.__basis_of_short_vectors)50else:51return deepcopy(self.__basis_of_short_vectors)52except:53pass545556## Set an upper bound for the number of vectors to consider57Max_number_of_vectors = 100005859## Generate a PARI matrix string for the associated Hessian matrix60M_str = str(gp(self.matrix()))616263## Run through all possible minimal lengths to find a spanning set of vectors64n = self.dim()65#MS = MatrixSpace(QQ, n)66M1 = Matrix([[0]])67vec_len = 068while M1.rank() < n:6970## DIAGONSTIC7172#print "Starting with vec_len = ", vec_len73#print "M_str = ", M_str7475vec_len += 176gp_mat = gp.qfminim(M_str, vec_len, Max_number_of_vectors)[3].mattranspose()77number_of_vecs = ZZ(gp_mat.matsize()[1])78vector_list = []79for i in range(number_of_vecs):80#print "List at", i, ":", list(gp_mat[i+1,])81new_vec = vector([ZZ(x) for x in list(gp_mat[i+1,])])82vector_list.append(new_vec)838485## DIAGNOSTIC86#print "number_of_vecs = ", number_of_vecs87#print "vector_list = ", vector_list888990## Make a matrix from the short vectors91if len(vector_list) > 0:92M1 = Matrix(vector_list)939495## DIAGNOSTIC96#print "matrix of vectors = \n", M197#print "rank of the matrix = ", M1.rank()9899100101#print " vec_len = ", vec_len102#print M1103104105## Organize these vectors by length (and also introduce their negatives)106max_len = vec_len/2107vector_list_by_length = [[] for _ in range(max_len + 1)]108for v in vector_list:109l = self(v)110vector_list_by_length[l].append(v)111vector_list_by_length[l].append(vector([-x for x in v]))112113114## Make a matrix from the column vectors (in order of ascending length).115sorted_list = []116for i in range(len(vector_list_by_length)):117for v in vector_list_by_length[i]:118sorted_list.append(v)119sorted_matrix = Matrix(sorted_list).transpose()120121122## Determine a basis of vectors of minimal length123pivots = sorted_matrix.pivots()124basis = [sorted_matrix.column(i) for i in pivots]125pivot_lengths = [self(v) for v in basis]126127128## DIAGNOSTIC129#print "basis = ", basis130#print "pivot_lengths = ", pivot_lengths131132133## Cache the result134self.__basis_of_short_vectors = basis135self.__basis_of_short_vectors_lenghts = pivot_lengths136137138## Return the appropriate result139if show_lengths:140return basis, pivot_lengths141else:142return basis143144145146147def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False):148"""149Return a list of lists of short vectors `v`, sorted by length, with150Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of151length `i`. If the up_to_sign_flag is set to True, then only one of152the vectors of the pair `[v, -v]` is listed.153154Note: This processes the PARI/GP output to always give elements of type `ZZ`.155156OUTPUT:157a list of lists of vectors.158159EXAMPLES::160161sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])162sage: Q.short_vector_list_up_to_length(3)163[[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], []]164sage: Q.short_vector_list_up_to_length(4)165[[(0, 0, 0, 0)],166[(1, 0, 0, 0), [-1, 0, 0, 0]],167[],168[(0, 1, 0, 0), [0, -1, 0, 0]]]169sage: Q.short_vector_list_up_to_length(5)170[[(0, 0, 0, 0)],171[(1, 0, 0, 0), [-1, 0, 0, 0]],172[],173[(0, 1, 0, 0), [0, -1, 0, 0]],174[(1, 1, 0, 0),175[-1, -1, 0, 0],176(-1, 1, 0, 0),177[1, -1, 0, 0],178(2, 0, 0, 0),179[-2, 0, 0, 0]]]180sage: Q.short_vector_list_up_to_length(5, True)181[[(0, 0, 0, 0)],182[(1, 0, 0, 0)],183[],184[(0, 1, 0, 0)],185[(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]]186187"""188## Set an upper bound for the number of vectors to consider189Max_number_of_vectors = 10000190191## Generate a PARI matrix string for the associated Hessian matrix192M_str = str(gp(self.matrix()))193194## Generate the short vectors195gp_mat = gp.qfminim(M_str, 2*len_bound-2, Max_number_of_vectors)[3].mattranspose()196number_of_rows = gp_mat.matsize()[1]197gp_mat_vector_list = [vector([ZZ(x) for x in gp_mat[i+1,]]) for i in range(number_of_rows)]198199## Sort the vectors into lists by their length200vec_list = [[] for i in range(len_bound)]201for v in gp_mat_vector_list:202vec_list[self(v)].append(v)203if up_to_sign_flag == False:204vec_list[self(v)].append([-x for x in v])205206## Add the zero vector by hand207zero_vec = vector([ZZ(0) for _ in range(self.dim())])208vec_list[0].append(zero_vec)209210## Return the sorted list211return vec_list212213214215def short_primitive_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False):216"""217Return a list of lists of short primitive vectors `v`, sorted by length, with218Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of219length `i`. If the up_to_sign_flag is set to True, then only one of220the vectors of the pair `[v, -v]` is listed.221222Note: This processes the PARI/GP output to always give elements of type `ZZ`.223224OUTPUT:225a list of lists of vectors.226227EXAMPLES::228229sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])230sage: Q.short_vector_list_up_to_length(5, True)231[[(0, 0, 0, 0)],232[(1, 0, 0, 0)],233[],234[(0, 1, 0, 0)],235[(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]]236sage: Q.short_primitive_vector_list_up_to_length(5, True)237[[], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0)]]238239240"""241## Get a list of short vectors242full_vec_list = self.short_vector_list_up_to_length(len_bound, up_to_sign_flag)243244## Make a new list of the primitive vectors245prim_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!246247## Return the list of primitive vectors248return prim_vec_list249250251252253## ----------------------------------------------------------------------------------------------------254255256def automorphisms(self):257"""258Return a list of the automorphisms of the quadratic form.259260EXAMPLES::261262sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1])263sage: Q.number_of_automorphisms() # optional -- souvigner26448265sage: 2^3 * factorial(3)26648267sage: len(Q.automorphisms())26848269270::271272sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])273sage: Q.number_of_automorphisms() # optional -- souvigner27416275sage: aut = Q.automorphisms()276sage: len(aut)27716278sage: print([Q(M) == Q for M in aut])279[True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]280281sage: Q = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3])282sage: Q.automorphisms()283[284[1 0 0] [-1 0 0]285[0 1 0] [ 0 -1 0]286[0 0 1], [ 0 0 -1]287]288289::290291sage: Q = DiagonalQuadraticForm(ZZ, [1, -1])292sage: Q.automorphisms()293Traceback (most recent call last):294...295ValueError: not a definite form in QuadraticForm.automorphisms()296"""297## only for definite forms298if not self.is_definite():299raise ValueError, "not a definite form in QuadraticForm.automorphisms()"300301## Check for a cached value302try:303return self.__automorphisms304except:305pass306307308## Find a basis of short vectors, and their lengths309basis, pivot_lengths = self.basis_of_short_vectors(show_lengths=True)310311## List the relevant vectors by length312max_len = max(pivot_lengths)313vector_list_by_length = self.short_primitive_vector_list_up_to_length(max_len + 1)314315316## Make the matrix A:e_i |--> v_i to our new basis.317A = Matrix(basis).transpose()318Ainv = A.inverse()319#A1 = A.inverse() * A.det()320#Q1 = A1.transpose() * self.matrix() * A1 ## This is the matrix of Q321#Q = self.matrix() * A.det()**2322Q2 = A.transpose() * self.matrix() * A ## This is the matrix of Q in the new basis323Q3 = self.matrix()324325326## Determine all automorphisms327n = self.dim()328Auto_list = []329#ct = 0330331## DIAGNOSTIC332#print "n = " + str(n)333#print "pivot_lengths = " + str(pivot_lengths)334#print "vector_list_by_length = " + str(vector_list_by_length)335#print "length of vector_list_by_length = " + str(len(vector_list_by_length))336337for index_vec in mrange([len(vector_list_by_length[pivot_lengths[i]]) for i in range(n)]):338M = Matrix([vector_list_by_length[pivot_lengths[i]][index_vec[i]] for i in range(n)]).transpose()339#Q1 = self.matrix()340#if self(M) == self:341#ct += 1342#print "ct = ", ct, " M = "343#print M344345if M.transpose() * Q3 * M == Q2: ## THIS DOES THE SAME THING! =(346Auto_list.append(M * Ainv)347348349## Cache the answer and return the list350self.__automorphisms = Auto_list351self.__number_of_automorphisms = len(Auto_list)352return Auto_list353354355356357def number_of_automorphisms(self, recompute=False):358"""359Return a list of the number of automorphisms (of det 1 and -1) of360the quadratic form.361362If recompute is True, then we will recompute the cached result.363364OUTPUT:365an integer >= 2.366367EXAMPLES::368369sage: Q = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 1], unsafe_initialization=True, number_of_automorphisms=-1)370sage: Q.list_external_initializations()371['number_of_automorphisms']372sage: Q.number_of_automorphisms()373-1374sage: Q.number_of_automorphisms(recompute=True) # optional -- souvigner37548376sage: Q.list_external_initializations() # optional -- souvigner377[]378379::380381sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])382sage: Q.number_of_automorphisms() # optional -- souvigner383384384sage: 2^4 * factorial(4)385384386387::388389sage: Q = DiagonalQuadraticForm(ZZ, [1, -1])390sage: Q.number_of_automorphisms()391Traceback (most recent call last):392...393ValueError: not a definite form in QuadraticForm.number_of_automorphisms()394395"""396## only for definite forms397if not self.is_definite():398raise ValueError, "not a definite form in QuadraticForm.number_of_automorphisms()"399400## Try to use the cached version if we can401if not recompute:402try:403#print "Using the cached number of automorphisms."404#print "We stored", self.__number_of_automorphisms405return self.__number_of_automorphisms406except:407pass408409## Otherwise cache and return the result410#print "Recomputing the number of automorphisms based on the list of automorphisms."411#self.__number_of_automorphisms = len(self.automorphisms()) ## This is now deprecated.412self.__number_of_automorphisms = self.number_of_automorphisms__souvigner()413try:414self._external_initialization_list.remove('number_of_automorphisms')415except:416pass ## Do nothing if the removal fails, since it might not be in the list (causing an error)!417return self.__number_of_automorphisms418419420421def number_of_automorphisms__souvigner(self):422"""423Uses the Souvigner code to compute the number of automorphisms.424425EXAMPLES::426427sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1,1])428sage: Q.number_of_automorphisms__souvigner() # optional -- souvigner4293840430sage: 2^5 * factorial(5)4313840432433"""434## Write an input text file435F_filename = '/tmp/tmp_isom_input' + str(random()) + ".txt"436F = open(F_filename, 'w')437#F = tempfile.NamedTemporaryFile(prefix='tmp_auto_input', suffix=".txt") ## This fails because the Souvigner code doesn't like random filenames (hyphens are bad...)!438F.write("#1 \n")439n = self.dim()440F.write(str(n) + "x0 \n") ## Use the lower-triangular form441for i in range(n):442for j in range(i+1):443if i == j:444F.write(str(2 * self[i,j]) + " ")445else:446F.write(str(self[i,j]) + " ")447F.write("\n")448F.flush()449#print "Input filename = ", F.name450#os.system("less " + F.name)451452## Call the Souvigner automorphism code453souvigner_auto_path = SAGE_ROOT + "/local/bin/Souvigner_AUTO" ## FIX THIS LATER!!!454G1 = tempfile.NamedTemporaryFile(prefix='tmp_auto_ouput', suffix=".txt")455#print "Output filename = ", G1.name456os.system(souvigner_auto_path + " '" + F.name + "' > '" + G1.name +"'")457458459## Read the output460G2 = open(G1.name, 'r')461for line in G2:462if line.startswith("|Aut| = "):463num_of_autos = sage_eval(line.lstrip("|Aut| = "))464F.close()465G1.close()466G2.close()467os.system("rm -f " + F_filename)468#os.system("rm -f " + G1.name)469return num_of_autos470471## Raise and error if we're here:472raise RuntimeError, "Oops! There is a problem..."473474475476def set_number_of_automorphisms(self, num_autos):477"""478Set the number of automorphisms to be the value given. No error479checking is performed, to this may lead to erroneous results.480481The fact that this result was set externally is recorded in the482internal list of external initializations, accessible by the483method list_external_initializations().484485Return a list of the number of486automorphisms (of det 1 and -1) of the quadratic form.487488OUTPUT:489None490491EXAMPLES::492493sage: Q = DiagonalQuadraticForm(ZZ, [1, 1, 1])494sage: Q.list_external_initializations()495[]496sage: Q.set_number_of_automorphisms(-3)497sage: Q.number_of_automorphisms()498-3499sage: Q.list_external_initializations()500['number_of_automorphisms']501502"""503self.__number_of_automorphisms = num_autos504text = 'number_of_automorphisms'505if not text in self._external_initialization_list:506self._external_initialization_list.append(text)507508509510### TODO511# def Nipp_automorphism_testing(self):512# """513# Testing the automorphism routine against Nipp's Tables514#515# --- MOVE THIS ELSEWHERE!!! ---516#517# """518# for i in range(20):519# Q = QuadraticForm(ZZ, 4, Nipp[i][2])520# my_num = Q.number_of_automorphisms()521# nipp_num = Nipp.number_of_automorphisms(i)522# print " i = " + str(i) + " my_num = " + str(my_num) + " nipp_num = " + str(nipp_num)523#524525526527