Path: blob/master/sage/quadratic_forms/quadratic_form__equivalence_testing.py
4084 views
"""1Equivalence Testing2"""34from sage.matrix.constructor import Matrix5from sage.misc.mrange import mrange6from sage.rings.arith import hilbert_symbol, prime_divisors, is_prime, valuation, GCD, legendre_symbol7from sage.rings.integer_ring import ZZ89from quadratic_form import is_QuadraticForm101112from sage.misc.misc import SAGE_ROOT1314import tempfile, os1516from random import random171819################################################################################20## Routines to test if two quadratic forms over ZZ are globally equivalent. ##21## (For now, we require both forms to be positive definite.) ##22################################################################################2324def is_globally_equivalent__souvigner(self, other, return_transformation=False):25"""26Uses the Souvigner code to compute the number of automorphisms.2728INPUT:29a QuadraticForm3031OUTPUT:32boolean, and optionally a matrix3334EXAMPLES::3536sage: Q = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5])37sage: Q1 = QuadraticForm(ZZ, 3, [8, 6, 5, 3, 4, 2])38sage: M = Q.is_globally_equivalent__souvigner(Q1, True) ; M # optional -- souvigner39[ 0 0 -1]40[ 1 0 0]41[-1 1 1]42sage: Q1(M) == Q # optional -- souvigner43True4445"""46## Write an input text file47F_filename = '/tmp/tmp_isom_input' + str(random()) + ".txt"48F = open(F_filename, 'w')49#F = tempfile.NamedTemporaryFile(prefix='tmp_isom_input', suffix=".txt") ## This failed because it may have hyphens, which are interpreted badly by the Souvigner code.50F.write("\n #1 \n")5152## Write the first form53n = self.dim()54F.write(str(n) + "x0 \n") ## Use the lower-triangular form55for i in range(n):56for j in range(i+1):57if i == j:58F.write(str(2 * self[i,j]) + " ")59else:60F.write(str(self[i,j]) + " ")61F.write("\n")6263## Write the second form64F.write("\n")65n = self.dim()66F.write(str(n) + "x0 \n") ## Use the lower-triangular form67for i in range(n):68for j in range(i+1):69if i == j:70F.write(str(2 * other[i,j]) + " ")71else:72F.write(str(other[i,j]) + " ")73F.write("\n")74F.flush()75#print "Input filename = ", F.name76#os.system("less " + F.name)7778## Call the Souvigner automorphism code79souvigner_isom_path = SAGE_ROOT + "/local/bin/Souvigner_ISOM"80G1 = tempfile.NamedTemporaryFile(prefix='tmp_isom_ouput', suffix=".txt")81#print "Output filename = ", G1.name82#print "Executing the shell command: " + souvigner_isom_path + " '" + F.name + "' > '" + G1.name + "'"83os.system(souvigner_isom_path + " '" + F.name + "' > '" + G1.name +"'")848586## Read the output87G2 = open(G1.name, 'r')88line = G2.readline()89if line.startswith("Error:"):90raise RuntimeError, "There is a problem using the souvigner code... " + line91elif line.find("not isomorphic") != -1: ## Checking if this text appears, if so then they're not isomorphic!92return False93else:94## Decide whether to read the transformation matrix, and return true95if not return_transformation:96F.close()97G1.close()98G2.close()99os.system("rm -f " + F_filename)100return True101else:102## Try to read the isomorphism matrix103M = Matrix(ZZ, n, n)104for i in range(n):105new_row_text = G2.readline().split()106#print new_row_text107for j in range(n):108M[i,j] = new_row_text[j]109110## Remove temporary files and return the value111F.close()112G1.close()113G2.close()114os.system("rm -f " + F_filename)115if return_transformation:116return M.transpose()117else:118return True119#return True, M120121## Raise and error if we're here:122raise RuntimeError, "Oops! There is a problem..."123124125126def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_precision='sturm', check_local_equivalence=True):127"""128Determines if the current quadratic form is equivalent to the129given form over ZZ. If return_matrix is True, then we also return130the transformation matrix M so that self(M) == other.131132INPUT:133a QuadraticForm134135OUTPUT:136boolean, and optionally a matrix137138EXAMPLES::139140sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])141sage: M = Matrix(ZZ, 4, 4, [1,2,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])142sage: Q1 = Q(M)143sage: Q.(Q1) # optional -- souvigner144True145sage: MM = Q.is_globally_equivalent_to(Q1, return_matrix=True) # optional -- souvigner146sage: Q(MM) == Q1 # optional -- souvigner147True148149::150151sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5])152sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3])153sage: Q3 = QuadraticForm(ZZ, 3, [8, 6, 5, 3, 4, 2])154sage: Q1.is_globally_equivalent_to(Q2) # optional -- souvigner155False156sage: Q1.is_globally_equivalent_to(Q3) # optional -- souvigner157True158sage: M = Q1.is_globally_equivalent_to(Q3, True) ; M # optional -- souvigner159[-1 -1 0]160[ 1 1 1]161[-1 0 0]162sage: Q1(M) == Q3 # optional -- souvigner163True164165::166167sage: Q = DiagonalQuadraticForm(ZZ, [1, -1])168sage: Q.is_globally_equivalent_to(Q)169Traceback (most recent call last):170...171ValueError: not a definite form in QuadraticForm.is_globally_equivalent_to()172173"""174## only for definite forms175if not self.is_definite():176raise ValueError, "not a definite form in QuadraticForm.is_globally_equivalent_to()"177178## Check that other is a QuadraticForm179#if not isinstance(other, QuadraticForm):180if not is_QuadraticForm(other):181raise TypeError, "Oops! You must compare two quadratic forms, but the argument is not a quadratic form. =("182183184## Now use the Souvigner code by default! =)185return other.is_globally_equivalent__souvigner(self, return_matrix) ## Note: We switch this because the Souvigner code has the opposite mapping convention to us. (It takes the second argument to the first!)186187188189## ---------------------------------- Unused Code below ---------------------------------------------------------190191## Check if the forms are locally equivalent192if (check_local_equivalence == True):193if not self.is_locally_equivalent_to(other):194return False195196## Check that the forms have the same theta function up to the desired precision (this can be set so that it determines the cusp form)197if check_theta_to_precision != None:198if self.theta_series(check_theta_to_precision, var_str='', safe_flag=False) != other.theta_series(check_theta_to_precision, var_str='', safe_flag=False):199return False200201202## Make all possible matrices which give an isomorphism -- can we do this more intelligently?203## ------------------------------------------------------------------------------------------204205## Find a basis of short vectors for one form, and try to match them with vectors of that length in the other one.206basis_for_self, self_lengths = self.basis_of_short_vectors(show_lengths=True)207max_len = max(self_lengths)208short_vectors_of_other = other.short_vector_list_up_to_length(max_len + 1)209210## Make the matrix A:e_i |--> v_i to our new basis.211A = Matrix(basis_for_self).transpose()212Q2 = A.transpose() * self.matrix() * A ## This is the matrix of 'self' in the new basis213Q3 = other.matrix()214215## Determine all automorphisms216n = self.dim()217Auto_list = []218219## DIAGNOSTIC220#print "n = " + str(n)221#print "pivot_lengths = " + str(pivot_lengths)222#print "vector_list_by_length = " + str(vector_list_by_length)223#print "length of vector_list_by_length = " + str(len(vector_list_by_length))224225for index_vec in mrange([len(short_vectors_of_other[self_lengths[i]]) for i in range(n)]):226M = Matrix([short_vectors_of_other[self_lengths[i]][index_vec[i]] for i in range(n)]).transpose()227if M.transpose() * Q3 * M == Q2:228if return_matrix:229return A * M.inverse()230else:231return True232233## If we got here, then there is no isomorphism234return False235236237def is_locally_equivalent_to(self, other, check_primes_only=False, force_jordan_equivalence_test=False):238"""239Determines if the current quadratic form (defined over ZZ) is240locally equivalent to the given form over the real numbers and the241`p`-adic integers for every prime p.242243This works by comparing the local Jordan decompositions at every244prime, and the dimension and signature at the real place.245246INPUT:247a QuadraticForm248249OUTPUT:250boolean251252EXAMPLES::253254sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5])255sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3])256sage: Q1.is_globally_equivalent_to(Q2) # optional -- souvigner257False258sage: Q1.is_locally_equivalent_to(Q2)259True260261"""262## TO IMPLEMENT:263if self.det() == 0:264raise NotImplementedError, "OOps! We need to think about whether this still works for degenerate forms... especially check the signature."265266## Check that both forms have the same dimension and base ring267if (self.dim() != other.dim()) or (self.base_ring() != other.base_ring()):268return False269270## Check that the determinant and level agree271if (self.det() != other.det()) or (self.level() != other.level()):272return False273274## -----------------------------------------------------275276## Test equivalence over the real numbers277if self.signature() != other.signature():278return False279280## Test equivalence over Z_p for all primes281if (self.base_ring() == ZZ) and (force_jordan_equivalence_test == False):282283## Test equivalence with Conway-Sloane genus symbols (default over ZZ)284if self.CS_genus_symbol_list() != other.CS_genus_symbol_list():285return False286else:287## Test equivalence via the O'Meara criterion.288for p in prime_divisors(ZZ(2) * self.det()):289#print "checking the prime p = ", p290if not self.has_equivalent_Jordan_decomposition_at_prime(other, p):291return False292293## All tests have passed!294return True295296297298299def has_equivalent_Jordan_decomposition_at_prime(self, other, p):300"""301Determines if the given quadratic form has a Jordan decomposition302equivalent to that of self.303304INPUT:305a QuadraticForm306307OUTPUT:308boolean309310EXAMPLES::311312sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3])313sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6])314sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11])315sage: [Q1.level(), Q2.level(), Q3.level()]316[44, 44, 44]317sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2)318False319sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11)320False321sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2)322False323sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11)324True325sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2)326True327sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11)328False329330"""331## Sanity Checks332#if not isinstance(other, QuadraticForm):333if type(other) != type(self):334raise TypeError, "Oops! The first argument must be of type QuadraticForm."335if not is_prime(p):336raise TypeError, "Oops! The second argument must be a prime number."337338## Get the relevant local normal forms quickly339self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag= False)340other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False)341342## DIAGNOSTIC343#print "self_jordan = ", self_jordan344#print "other_jordan = ", other_jordan345346347## Check for the same number of Jordan components348if len(self_jordan) != len(other_jordan):349return False350351352## Deal with odd primes: Check that the Jordan component scales, dimensions, and discriminants are the same353if p != 2:354for i in range(len(self_jordan)):355if (self_jordan[i][0] != other_jordan[i][0]) \356or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \357or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1):358return False359360## All tests passed for an odd prime.361return True362363364## For p = 2: Check that all Jordan Invariants are the same.365elif p == 2:366367## Useful definition368t = len(self_jordan) ## Define t = Number of Jordan components369370371## Check that all Jordan Invariants are the same (scale, dim, and norm)372for i in range(t):373if (self_jordan[i][0] != other_jordan[i][0]) \374or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \375or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)):376return False377378## DIAGNOSTIC379#print "Passed the Jordan invariant test."380381382## Use O'Meara's isometry test 93:29 on p277.383## ------------------------------------------384385## List of norms, scales, and dimensions for each i386scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)]387norm_list = [ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2)) for i in range(t)]388dim_list = [(self_jordan[i][1].dim()) for i in range(t)]389390## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain391## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!) -- NOT SO GOOD...392## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.)393j = 0394self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])]395other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])]396self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor(ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ]397other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor(ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ]398399for j in range(1, t):400self_chain_det_list.append(self_chain_det_list[j-1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]))401other_chain_det_list.append(other_chain_det_list[j-1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]))402self_hasse_chain_list.append(self_hasse_chain_list[j-1] \403* hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \404* self_jordan[j][1].hasse_invariant__OMeara(2))405other_hasse_chain_list.append(other_hasse_chain_list[j-1] \406* hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \407* other_jordan[j][1].hasse_invariant__OMeara(2))408409410## SANITY CHECK -- check that the scale powers are strictly increasing411for i in range(1, len(scale_list)):412if scale_list[i-1] >= scale_list[i]:413raise RuntimeError, "Oops! There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!"414415416## DIAGNOSTIC417#print "scale_list = ", scale_list418#print "norm_list = ", norm_list419#print "dim_list = ", dim_list420421#print "self_chain_det_list = ", self_chain_det_list422#print "other_chain_det_list = ", other_chain_det_list423#print "self_hasse_chain_list = ", self_hasse_chain_list424#print "other_hasse_chain_det_list = ", other_hasse_chain_list425426427## Test O'Meara's two conditions428for i in range(t-1):429430## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8).431modulus = norm_list[i] * norm_list[i+1] / (scale_list[i] ** 2)432if modulus > 8:433modulus = 8434if (modulus > 1) and (((self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1):435#print "Failed when i =", i, " in condition 1."436return False437438## Check O'Meara's condition (ii) when appropriate439if norm_list[i+1] % (4 * norm_list[i]) == 0:440if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \441!= other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2): ## Nipp conditions442#print "Failed when i =", i, " in condition 2."443return False444445446## All tests passed for the prime 2.447return True448449else:450raise TypeError, "Oops! This should not have happened."451452453454455456457458459460