def __iter__(self): """ Return an iterator over the elements of this group. EXAMPLES:: sage: G = AbelianGroup([2,3], names = "ab") sage: [a for a in G] [1, b, b^2, a, a*b, a*b^2] sage: L = list(G); L [1, b, b^2, a, a*b, a*b^2] The returned list is a reference; mutating it does not allow the user to (accidentally?) alter the computed generators:: sage: L[0] = 0 sage: list(G) [1, b, b^2, a, a*b, a*b^2] sage: G = AbelianGroup([1], names="a") sage: list(G) [1] sage: G = AbelianGroup([]) sage: G.list() [1] sage: list(G) [1] """ invs = self.invariants() for t in mrange(invs): yield AbelianGroupElement(self, t)
def mass_at_two_by_counting_mod_power(self, k): """ Computes the local mass at `p=2` assuming that it's stable `(mod 2^k)`. Note: This is **way** too slow to be useful, even when k=1!!! TO DO: Remove this routine, or try to compile it! INPUT: k -- an integer >= 1 OUTPUT: a rational number EXAMPLE:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.mass_at_two_by_counting_mod_power(1) 4 """ R = IntegerModRing(2**k) Q1 = self.base_change_to(R) n = self.dim() MS = MatrixSpace(R, n) ct = sum([1 for x in mrange([2**k] * (n**2)) if Q1(MS(x)) == Q1]) ## Count the solutions mod 2^k two_mass = ZZ(1)/2 * (ZZ(ct) / ZZ(2)**(k*n*(n-1)/2)) return two_mass
def mass_at_two_by_counting_mod_power(self, k): """ Computes the local mass at `p=2` assuming that it's stable `(mod 2^k)`. Note: This is **way** too slow to be useful, even when k=1!!! TO DO: Remove this routine, or try to compile it! INPUT: k -- an integer >= 1 OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.mass_at_two_by_counting_mod_power(1) 4 """ R = IntegerModRing(2**k) Q1 = self.base_change_to(R) n = self.dim() MS = MatrixSpace(R, n) ct = sum([1 for x in mrange([2**k] * (n**2)) if Q1(MS(x)) == Q1]) ## Count the solutions mod 2^k two_mass = ZZ(1) / 2 * (ZZ(ct) / ZZ(2)**(k * n * (n - 1) / 2)) return two_mass
def dual_vectors( self): """ Return a set of representatives for $L^#/L$. """ D,U,V = self.gram_matrix().smith_form() # hence D = U * self.gram_matrix() * V if None == self.__dual_vectors: W = V*D**-1 S = self.space() self.__dual_vectors = [ W*S(v) for v in mrange( D.diagonal())] return self.__dual_vectors
def cs_range( f, subset = None): """ For a symmetric semi-positive integral matrix $f$, return a list of all integral $n$-vectors $v$ such that $x^tfx - (v*x)^2 >= 0$ for all $x$. """ n = f.dimensions()[0] b = vector( s.isqrt() for s in f.diagonal()) zv = vector([0]*n) box = [b - vector(t) for t in mrange( (2*b + vector([1]*n)).list()) if b - vector(t) > zv] if subset: box = [v for v in box if v in subset] rge = [v for v in box if min( (f - matrix(v).transpose()*matrix(v)).eigenvalues()) >=0] return rge
def list(self): """ Return tuple of all elements of this group. EXAMPLES:: sage: G = AbelianGroup([2,3], names="ab") sage: Gd = G.dual_group(names="AB") sage: Gd.list() (1, B, B^2, A, A*B, A*B^2) """ if not (self.is_finite()): raise NotImplementedError("Group must be finite") invs = self.gens_orders() T = mrange(invs) n = self.order() L = tuple(self(t) for t in T) return L
def list(self): """ Return tuple of all elements of this group. EXAMPLES:: sage: G = AbelianGroup([2,3], names="ab") sage: Gd = G.dual_group(names="AB") sage: Gd.list() (1, B, B^2, A, A*B, A*B^2) """ if not(self.is_finite()): raise NotImplementedError("Group must be finite") invs = self.gens_orders() T = mrange(invs) n = self.order() L = tuple( self(t) for t in T ) return L
def __iter__(self): r""" Return an iterator of all ideal classes in this class group. EXAMPLES:: sage: K.<a> = NumberField(x^4 + 23) sage: G = K.class_group() sage: G Class group of order 3 with structure C3 of Number Field in a with defining polynomial x^4 + 23 sage: list(G) [Trivial principal fractional ideal class, Fractional ideal class (2, 1/4*a^3 - 1/4*a^2 + 1/4*a - 1/4), Fractional ideal class (2, 1/2*a^2 + 1/2)] sage: G.list() (Trivial principal fractional ideal class, Fractional ideal class (2, 1/4*a^3 - 1/4*a^2 + 1/4*a - 1/4), Fractional ideal class (2, 1/2*a^2 + 1/2)) TESTS:: sage: K.<a> = NumberField(x^2 + 1) sage: G = K.class_group() sage: G Class group of order 1 of Number Field in a with defining polynomial x^2 + 1 sage: list(G) [Trivial principal fractional ideal class] sage: G.list() (Trivial principal fractional ideal class,) """ from sage.misc.mrange import mrange orders = self.gens_orders() T = mrange(orders) g = self.gens() for t in T: I = self(1) for i, j in enumerate(t): I *= g[i]**j yield I if not T: yield self(1)
def list(self): """ Return list of all elements of this group. EXAMPLES:: sage: G = AbelianGroup([2,3], names = "ab") sage: Gd = DualAbelianGroup(G, names = "AB") sage: Gd.list() [1, B, B^2, A, A*B, A*B^2] """ try: return list(self.__list) except AttributeError: pass if not(self.is_finite()): raise NotImplementedError, "Group must be finite" invs = self.invariants() T = mrange(invs) n = self.order() L = [DualAbelianGroupElement(self, t) for t in T] self.__list = L return list(self.__list)
def Watson_mass_at_2(self): """ Returns the local mass of the quadratic form when `p=2`, according to Watson's Theorem 1 of "The 2-adic density of a quadratic form" in Mathematika 23 (1976), pp 94--106. INPUT: none OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.Watson_mass_at_2() ## WARNING: WE NEED TO CHECK THIS CAREFULLY! 384 """ ## Make a 0-dim'l quadratic form (for initialization purposes) Null_Form = copy.deepcopy(self) Null_Form.__init__(ZZ, 0) ## Step 0: Compute Jordan blocks and bounds of the scales to keep track of Jordan_Blocks = self.jordan_blocks_by_scale_and_unimodular(2) scale_list = [B[0] for B in Jordan_Blocks] s_min = min(scale_list) s_max = max(scale_list) ## Step 1: Compute dictionaries of the diagonal block and 2x2 block for each scale diag_dict = dict( (i, Null_Form) for i in range(s_min - 2, s_max + 4)) ## Initialize with the zero form dim2_dict = dict( (i, Null_Form) for i in range(s_min, s_max + 4)) ## Initialize with the zero form for (s, L) in Jordan_Blocks: i = 0 while (i < L.dim() - 1) and (L[i, i + 1] == 0): ## Find where the 2x2 blocks start i = i + 1 if i < (L.dim() - 1): diag_dict[s] = L.extract_variables(range(i)) ## Diagonal Form dim2_dict[s + 1] = L.extract_variables(range( i, L.dim())) ## Non-diagonal Form else: diag_dict[s] = L #print "diag_dict = ", diag_dict #print "dim2_dict = ", dim2_dict #print "Jordan_Blocks = ", Jordan_Blocks ## Step 2: Compute three dictionaries of invariants (for n_j, m_j, nu_j) n_dict = dict((j, 0) for j in range(s_min + 1, s_max + 2)) m_dict = dict((j, 0) for j in range(s_min, s_max + 4)) for (s, L) in Jordan_Blocks: n_dict[s + 1] = L.dim() if diag_dict[s].dim() == 0: m_dict[s + 1] = ZZ.one() / ZZ(2) * L.dim() else: m_dict[s + 1] = floor(ZZ(L.dim() - 1) / ZZ(2)) #print " ==>", ZZ(L.dim() - 1) / ZZ(2), floor(ZZ(L.dim() - 1) / ZZ(2)) nu_dict = dict((j, n_dict[j + 1] - 2 * m_dict[j + 1]) for j in range(s_min, s_max + 1)) nu_dict[s_max + 1] = 0 #print "n_dict = ", n_dict #print "m_dict = ", m_dict #print "nu_dict = ", nu_dict ## Step 3: Compute the e_j dictionary eps_dict = {} for j in range(s_min, s_max + 3): two_form = (diag_dict[j - 2] + diag_dict[j] + dim2_dict[j]).scale_by_factor(2) j_form = (two_form + diag_dict[j - 1]).base_change_to( IntegerModRing(4)) if j_form.dim() == 0: eps_dict[j] = 1 else: iter_vec = [4] * j_form.dim() alpha = sum([True for x in mrange(iter_vec) if j_form(x) == 0]) beta = sum([True for x in mrange(iter_vec) if j_form(x) == 2]) if alpha > beta: eps_dict[j] = 1 elif alpha == beta: eps_dict[j] = 0 else: eps_dict[j] = -1 #print "eps_dict = ", eps_dict ## Step 4: Compute the quantities nu, q, P, E for the local mass at 2 nu = sum([j * n_dict[j] * (ZZ(n_dict[j] + 1) / ZZ(2) + \ sum([n_dict[r] for r in range(j+1, s_max+2)])) for j in range(s_min+1, s_max+2)]) q = sum([ sgn(nu_dict[j - 1] * (n_dict[j] + sgn(nu_dict[j]))) for j in range(s_min + 1, s_max + 2) ]) P = prod([ prod([1 - QQ(4)**(-i) for i in range(1, m_dict[j] + 1)]) for j in range(s_min + 1, s_max + 2) ]) E = prod([ ZZ(1) / ZZ(2) * (1 + eps_dict[j] * QQ(2)**(-m_dict[j])) for j in range(s_min, s_max + 3) ]) #print "\nFinal Summary:" #print "nu =", nu #print "q = ", q #print "P = ", P #print "E = ", E ## Step 5: Compute the local mass for the prime 2. mass_at_2 = QQ(2)**(nu - q) * P / E return mass_at_2
def automorphisms(self): """ Return a list of the automorphisms of the quadratic form. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.number_of_automorphisms() # optional -- souvigner 48 sage: 2^3 * factorial(3) 48 sage: len(Q.automorphisms()) 48 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.number_of_automorphisms() # optional -- souvigner 16 sage: aut = Q.automorphisms() sage: len(aut) 16 sage: print([Q(M) == Q for M in aut]) [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True] sage: Q = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) sage: Q.automorphisms() [ [1 0 0] [-1 0 0] [0 1 0] [ 0 -1 0] [0 0 1], [ 0 0 -1] ] :: sage: Q = DiagonalQuadraticForm(ZZ, [1, -1]) sage: Q.automorphisms() Traceback (most recent call last): ... ValueError: not a definite form in QuadraticForm.automorphisms() """ ## only for definite forms if not self.is_definite(): raise ValueError("not a definite form in QuadraticForm.automorphisms()") ## Check for a cached value try: return self.__automorphisms except AttributeError: pass ## Find a basis of short vectors, and their lengths basis, pivot_lengths = self.basis_of_short_vectors(show_lengths=True) ## List the relevant vectors by length max_len = max(pivot_lengths) vector_list_by_length = self.short_primitive_vector_list_up_to_length(max_len + 1) ## Make the matrix A:e_i |--> v_i to our new basis. A = Matrix(basis).transpose() Ainv = A.inverse() #A1 = A.inverse() * A.det() #Q1 = A1.transpose() * self.matrix() * A1 ## This is the matrix of Q #Q = self.matrix() * A.det()**2 Q2 = A.transpose() * self.matrix() * A ## This is the matrix of Q in the new basis Q3 = self.matrix() ## Determine all automorphisms n = self.dim() Auto_list = [] #ct = 0 ## DIAGNOSTIC #print "n = " + str(n) #print "pivot_lengths = " + str(pivot_lengths) #print "vector_list_by_length = " + str(vector_list_by_length) #print "length of vector_list_by_length = " + str(len(vector_list_by_length)) for index_vec in mrange([len(vector_list_by_length[pivot_lengths[i]]) for i in range(n)]): M = Matrix([vector_list_by_length[pivot_lengths[i]][index_vec[i]] for i in range(n)]).transpose() #Q1 = self.matrix() #if self(M) == self: #ct += 1 #print "ct = ", ct, " M = " #print M #print if M.transpose() * Q3 * M == Q2: ## THIS DOES THE SAME THING! =( Auto_list.append(M * Ainv) ## Cache the answer and return the list self.__automorphisms = Auto_list self.__number_of_automorphisms = len(Auto_list) return Auto_list
def minkowski_reduction_for_4vars__SP(self): """ Find a Minkowski-reduced form equivalent to the given one. This means that Q(`v_k`) <= Q(`s_1 * v_1 + ... + s_n * v_n`) for all `s_i` where GCD(`s_k, ... s_n`) = 1. Note: When Q has dim <= 4 we can take all `s_i` in {1, 0, -1}. References: Schulze-Pillot's paper on "An algorithm for computing genera of ternary and quaternary quadratic forms", p138. Donaldson's 1979 paper "Minkowski Reduction of Integral Matrices", p203. EXAMPLES:: sage: Q = QuadraticForm(ZZ,4,[30,17,11,12,29,25,62,64,25,110]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 12 ] [ * 29 25 62 ] [ * * 64 25 ] [ * * * 110 ] sage: Q.minkowski_reduction_for_4vars__SP() (Quadratic form in 4 variables over Integer Ring with coefficients: [ 29 -17 25 4 ] [ * 30 -11 5 ] [ * * 64 0 ] [ * * * 77 ] , [ 0 1 0 0] [ 1 0 0 -1] [ 0 0 1 0] [ 0 0 0 1]) """ R = self.base_ring() n = self.dim() interior_reduced_flag = False Q = deepcopy(self) M = matrix(R, n, n) for i in range(n): M[i, i] = 1 ## Only allow 4-variable forms if n != 4: raise TypeError, "Oops! The given quadratic form has " + str(n) + \ " != 4 variables. =|" ## Step 1: Begin the reduction done_flag = False while done_flag == False: ## Loop through possible shorter vectors done_flag = True #print " j_range = ", range(n-1, -1, -1) for j in range(n - 1, -1, -1): for a_first in mrange([2 for i in range(j)]): y = [x - 1 for x in a_first] + [1] + [0 for k in range(n - 1 - j)] e_j = [0 for k in range(n)] e_j[j] = 1 #print "j = ", j ## Reduce if a shorter vector is found #print "y = ", y, " e_j = ", e_j, "\n" if Q(y) < Q(e_j): ## Further n=4 computations B_y_vec = Q.matrix() * vector(ZZ, y) ## SP's B = our self.matrix()/2 ## SP's A = coeff matrix of his B ## Here we compute the double of both and compare. B_sum = sum([abs(B_y_vec[i]) for i in range(4) if i != j]) A_sum = sum([abs(Q[i, j]) for i in range(4) if i != j]) B_max = max([abs(B_y_vec[i]) for i in range(4) if i != j]) A_max = max([abs(Q[i, j]) for i in range(4) if i != j]) if (B_sum < A_sum) or ((B_sum == A_sum) and (B_max < A_max)): ## Create the transformation matrix M_new = matrix(R, n, n) for k in range(n): M_new[k, k] = 1 for k in range(n): M_new[k, j] = y[k] ## Perform the reduction and restart the loop #print "Q_before = ", Q Q = Q(M_new) M = M * M_new done_flag = False ## DIAGNOSTIC #print "Q(y) = ", Q(y) #print "Q(e_j) = ", Q(e_j) #print "M_new = ", M_new #print "Q_after = ", Q #print if not done_flag: break if not done_flag: break ## Step 2: Order A by certain criteria for i in range(4): for j in range(i + 1, 4): ## Condition (a) if (Q[i, i] > Q[j, j]): Q.swap_variables(i, j, in_place=True) M_new = matrix(R, n, n) M_new[i, j] = -1 M_new[j, i] = 1 for r in range(4): if (r == i) or (r == j): M_new[r, r] = 0 else: M_new[r, r] = 1 M = M * M_new elif (Q[i, i] == Q[j, j]): i_sum = sum([abs(Q[i, k]) for k in range(4) if k != i]) j_sum = sum([abs(Q[j, k]) for k in range(4) if k != j]) ## Condition (b) if (i_sum > j_sum): Q.swap_variables(i, j, in_place=True) M_new = matrix(R, n, n) M_new[i, j] = -1 M_new[j, i] = 1 for r in range(4): if (r == i) or (r == j): M_new[r, r] = 0 else: M_new[r, r] = 1 M = M * M_new elif (i_sum == j_sum): for k in [ 2, 1, 0 ]: ## TO DO: These steps are a little redundant... Q1 = Q.matrix() c_flag = True for l in range(k + 1, 4): c_flag = c_flag and (abs(Q1[i, l]) == abs(Q1[j, l])) ## Condition (c) if c_flag and (abs(Q1[i, k]) > abs(Q1[j, k])): Q.swap_variables(i, j, in_place=True) M_new = matrix(R, n, n) M_new[i, j] = -1 M_new[j, i] = 1 for r in range(4): if (r == i) or (r == j): M_new[r, r] = 0 else: M_new[r, r] = 1 M = M * M_new ## Step 3: Order the signs for i in range(4): if Q[i, 3] < 0: Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R, n, n) for r in range(4): if r == i: M_new[r, r] = -1 else: M_new[r, r] = 1 M = M * M_new for i in range(4): j = 3 while (Q[i, j] == 0): j += -1 if (Q[i, j] < 0): Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R, n, n) for r in range(4): if r == i: M_new[r, r] = -1 else: M_new[r, r] = 1 M = M * M_new if Q[1, 2] < 0: ## Test a row 1 sign change if (Q[1,3] <= 0 and \ ((Q[1,3] < 0) or (Q[1,3] == 0 and Q[1,2] < 0) \ or (Q[1,3] == 0 and Q[1,2] == 0 and Q[1,1] < 0))): Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R, n, n) for r in range(4): if r == i: M_new[r, r] = -1 else: M_new[r, r] = 1 M = M * M_new elif (Q[2,3] <= 0 and \ ((Q[2,3] < 0) or (Q[2,3] == 0 and Q[2,2] < 0) \ or (Q[2,3] == 0 and Q[2,2] == 0 and Q[2,1] < 0))): Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R, n, n) for r in range(4): if r == i: M_new[r, r] = -1 else: M_new[r, r] = 1 M = M * M_new ## Return the results return Q, M
def minkowski_reduction(self): """ Find a Minkowski-reduced form equivalent to the given one. This means that .. math:: Q(v_k) <= Q(s_1 * v_1 + ... + s_n * v_n) for all `s_i` where GCD`(s_k, ... s_n) = 1`. Note: When Q has dim <= 4 we can take all `s_i` in {1, 0, -1}. References: Schulze-Pillot's paper on "An algorithm for computing genera of ternary and quaternary quadratic forms", p138. Donaldson's 1979 paper "Minkowski Reduction of Integral Matrices", p203. EXAMPLES:: sage: Q = QuadraticForm(ZZ,4,[30,17,11,12,29,25,62,64,25,110]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 12 ] [ * 29 25 62 ] [ * * 64 25 ] [ * * * 110 ] sage: Q.minkowski_reduction() (Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 -5 ] [ * 29 25 4 ] [ * * 64 0 ] [ * * * 77 ] , [ 1 0 0 0] [ 0 1 0 -1] [ 0 0 1 0] [ 0 0 0 1]) """ R = self.base_ring() n = self.dim() interior_reduced_flag = False Q = deepcopy(self) M = matrix(R, n, n) for i in range(n): M[i, i] = 1 ## Begin the reduction done_flag = False while done_flag == False: ## Loop through possible shorted vectors until done_flag = True #print " j_range = ", range(n-1, -1, -1) for j in range(n - 1, -1, -1): for a_first in mrange([2 for i in range(j)]): y = [x - 1 for x in a_first] + [1] + [0 for k in range(n - 1 - j)] e_j = [0 for k in range(n)] e_j[j] = 1 #print "j = ", j ## Reduce if a shorter vector is found #print "y = ", y, " e_j = ", e_j, "\n" if Q(y) < Q(e_j): ## Create the transformation matrix M_new = matrix(R, n, n) for k in range(n): M_new[k, k] = 1 for k in range(n): M_new[k, j] = y[k] ## Perform the reduction and restart the loop #print "Q_before = ", Q Q = Q(M_new) M = M * M_new done_flag = False ## DIAGNOSTIC #print "Q(y) = ", Q(y) #print "Q(e_j) = ", Q(e_j) #print "M_new = ", M_new #print "Q_after = ", Q #print if not done_flag: break if not done_flag: break ## Return the results return Q, M
def Watson_mass_at_2(self): """ Returns the local mass of the quadratic form when `p=2`, according to Watson's Theorem 1 of "The 2-adic density of a quadratic form" in Mathematika 23 (1976), pp 94--106. INPUT: none OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.Watson_mass_at_2() ## WARNING: WE NEED TO CHECK THIS CAREFULLY! 384 """ ## Make a 0-dim'l quadratic form (for initialization purposes) Null_Form = copy.deepcopy(self) Null_Form.__init__(ZZ, 0) ## Step 0: Compute Jordan blocks and bounds of the scales to keep track of Jordan_Blocks = self.jordan_blocks_by_scale_and_unimodular(2) scale_list = [B[0] for B in Jordan_Blocks] s_min = min(scale_list) s_max = max(scale_list) ## Step 1: Compute dictionaries of the diagonal block and 2x2 block for each scale diag_dict = dict((i, Null_Form) for i in range(s_min-2, s_max + 4)) ## Initialize with the zero form dim2_dict = dict((i, Null_Form) for i in range(s_min, s_max + 4)) ## Initialize with the zero form for (s,L) in Jordan_Blocks: i = 0 while (i < L.dim()-1) and (L[i,i+1] == 0): ## Find where the 2x2 blocks start i = i + 1 if i < (L.dim() - 1): diag_dict[s] = L.extract_variables(range(i)) ## Diagonal Form dim2_dict[s+1] = L.extract_variables(range(i, L.dim())) ## Non-diagonal Form else: diag_dict[s] = L #print "diag_dict = ", diag_dict #print "dim2_dict = ", dim2_dict #print "Jordan_Blocks = ", Jordan_Blocks ## Step 2: Compute three dictionaries of invariants (for n_j, m_j, nu_j) n_dict = dict((j,0) for j in range(s_min+1, s_max+2)) m_dict = dict((j,0) for j in range(s_min, s_max+4)) for (s,L) in Jordan_Blocks: n_dict[s+1] = L.dim() if diag_dict[s].dim() == 0: m_dict[s+1] = ZZ(1)/ZZ(2) * L.dim() else: m_dict[s+1] = floor(ZZ(L.dim() - 1) / ZZ(2)) #print " ==>", ZZ(L.dim() - 1) / ZZ(2), floor(ZZ(L.dim() - 1) / ZZ(2)) nu_dict = dict((j,n_dict[j+1] - 2*m_dict[j+1]) for j in range(s_min, s_max+1)) nu_dict[s_max+1] = 0 #print "n_dict = ", n_dict #print "m_dict = ", m_dict #print "nu_dict = ", nu_dict ## Step 3: Compute the e_j dictionary eps_dict = {} for j in range(s_min, s_max+3): two_form = (diag_dict[j-2] + diag_dict[j] + dim2_dict[j]).scale_by_factor(2) j_form = (two_form + diag_dict[j-1]).base_change_to(IntegerModRing(4)) if j_form.dim() == 0: eps_dict[j] = 1 else: iter_vec = [4] * j_form.dim() alpha = sum([True for x in mrange(iter_vec) if j_form(x) == 0]) beta = sum([True for x in mrange(iter_vec) if j_form(x) == 2]) if alpha > beta: eps_dict[j] = 1 elif alpha == beta: eps_dict[j] = 0 else: eps_dict[j] = -1 #print "eps_dict = ", eps_dict ## Step 4: Compute the quantities nu, q, P, E for the local mass at 2 nu = sum([j * n_dict[j] * (ZZ(n_dict[j] + 1) / ZZ(2) + \ sum([n_dict[r] for r in range(j+1, s_max+2)])) for j in range(s_min+1, s_max+2)]) q = sum([sgn(nu_dict[j-1] * (n_dict[j] + sgn(nu_dict[j]))) for j in range(s_min+1, s_max+2)]) P = prod([ prod([1 - QQ(4)**(-i) for i in range(1, m_dict[j]+1)]) for j in range(s_min+1, s_max+2)]) E = prod([ZZ(1)/ZZ(2) * (1 + eps_dict[j] * QQ(2)**(-m_dict[j])) for j in range(s_min, s_max+3)]) #print "\nFinal Summary:" #print "nu =", nu #print "q = ", q #print "P = ", P #print "E = ", E ## Step 5: Compute the local mass for the prime 2. mass_at_2 = QQ(2)**(nu - q) * P / E return mass_at_2
def minkowski_reduction_for_4vars__SP(self): """ Find a Minkowski-reduced form equivalent to the given one. This means that Q(`v_k`) <= Q(`s_1 * v_1 + ... + s_n * v_n`) for all `s_i` where GCD(`s_k, ... s_n`) = 1. Note: When Q has dim <= 4 we can take all `s_i` in {1, 0, -1}. References: Schulze-Pillot's paper on "An algorithm for computing genera of ternary and quaternary quadratic forms", p138. Donaldson's 1979 paper "Minkowski Reduction of Integral Matrices", p203. EXAMPLES:: sage: Q = QuadraticForm(ZZ,4,[30,17,11,12,29,25,62,64,25,110]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 12 ] [ * 29 25 62 ] [ * * 64 25 ] [ * * * 110 ] sage: Q.minkowski_reduction_for_4vars__SP() ( Quadratic form in 4 variables over Integer Ring with coefficients: [ 29 -17 25 4 ] [ * 30 -11 5 ] [ * * 64 0 ] [ * * * 77 ] , <BLANKLINE> [ 0 1 0 0] [ 1 0 0 -1] [ 0 0 1 0] [ 0 0 0 1] ) """ R = self.base_ring() n = self.dim() interior_reduced_flag = False Q = deepcopy(self) M = matrix(R, n, n) for i in range(n): M[i,i] = 1 ## Only allow 4-variable forms if n != 4: raise TypeError("Oops! The given quadratic form has " + str(n) + \ " != 4 variables. =|") ## Step 1: Begin the reduction done_flag = False while not done_flag: ## Loop through possible shorter vectors done_flag = True #print " j_range = ", range(n-1, -1, -1) for j in range(n-1, -1, -1): for a_first in mrange([2 for i in range(j)]): y = [x-1 for x in a_first] + [1] + [0 for k in range(n-1-j)] e_j = [0 for k in range(n)] e_j[j] = 1 #print "j = ", j ## Reduce if a shorter vector is found #print "y = ", y, " e_j = ", e_j, "\n" if Q(y) < Q(e_j): ## Further n=4 computations B_y_vec = Q.matrix() * vector(ZZ, y) ## SP's B = our self.matrix()/2 ## SP's A = coeff matrix of his B ## Here we compute the double of both and compare. B_sum = sum([abs(B_y_vec[i]) for i in range(4) if i != j]) A_sum = sum([abs(Q[i,j]) for i in range(4) if i != j]) B_max = max([abs(B_y_vec[i]) for i in range(4) if i != j]) A_max = max([abs(Q[i,j]) for i in range(4) if i != j]) if (B_sum < A_sum) or ((B_sum == A_sum) and (B_max < A_max)): ## Create the transformation matrix M_new = matrix(R, n, n) for k in range(n): M_new[k,k] = 1 for k in range(n): M_new[k,j] = y[k] ## Perform the reduction and restart the loop #print "Q_before = ", Q Q = Q(M_new) M = M * M_new done_flag = False ## DIAGNOSTIC #print "Q(y) = ", Q(y) #print "Q(e_j) = ", Q(e_j) #print "M_new = ", M_new #print "Q_after = ", Q #print if not done_flag: break if not done_flag: break ## Step 2: Order A by certain criteria for i in range(4): for j in range(i+1,4): ## Condition (a) if (Q[i,i] > Q[j,j]): Q.swap_variables(i,j,in_place=True) M_new = matrix(R,n,n) M_new[i,j] = -1 M_new[j,i] = 1 for r in range(4): if (r == i) or (r == j): M_new[r,r] = 0 else: M_new[r,r] = 1 M = M * M_new elif (Q[i,i] == Q[j,j]): i_sum = sum([abs(Q[i,k]) for k in range(4) if k != i]) j_sum = sum([abs(Q[j,k]) for k in range(4) if k != j]) ## Condition (b) if (i_sum > j_sum): Q.swap_variables(i,j,in_place=True) M_new = matrix(R,n,n) M_new[i,j] = -1 M_new[j,i] = 1 for r in range(4): if (r == i) or (r == j): M_new[r,r] = 0 else: M_new[r,r] = 1 M = M * M_new elif (i_sum == j_sum): for k in [2,1,0]: ## TO DO: These steps are a little redundant... Q1 = Q.matrix() c_flag = True for l in range(k+1,4): c_flag = c_flag and (abs(Q1[i,l]) == abs(Q1[j,l])) ## Condition (c) if c_flag and (abs(Q1[i,k]) > abs(Q1[j,k])): Q.swap_variables(i,j,in_place=True) M_new = matrix(R,n,n) M_new[i,j] = -1 M_new[j,i] = 1 for r in range(4): if (r == i) or (r == j): M_new[r,r] = 0 else: M_new[r,r] = 1 M = M * M_new ## Step 3: Order the signs for i in range(4): if Q[i,3] < 0: Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R,n,n) for r in range(4): if r == i: M_new[r,r] = -1 else: M_new[r,r] = 1 M = M * M_new for i in range(4): j = 3 while (Q[i,j] == 0): j += -1 if (Q[i,j] < 0): Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R,n,n) for r in range(4): if r == i: M_new[r,r] = -1 else: M_new[r,r] = 1 M = M * M_new if Q[1,2] < 0: ## Test a row 1 sign change if (Q[1,3] <= 0 and \ ((Q[1,3] < 0) or (Q[1,3] == 0 and Q[1,2] < 0) \ or (Q[1,3] == 0 and Q[1,2] == 0 and Q[1,1] < 0))): Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R,n,n) for r in range(4): if r == i: M_new[r,r] = -1 else: M_new[r,r] = 1 M = M * M_new elif (Q[2,3] <= 0 and \ ((Q[2,3] < 0) or (Q[2,3] == 0 and Q[2,2] < 0) \ or (Q[2,3] == 0 and Q[2,2] == 0 and Q[2,1] < 0))): Q.multiply_variable(-1, i, in_place=True) M_new = matrix(R,n,n) for r in range(4): if r == i: M_new[r,r] = -1 else: M_new[r,r] = 1 M = M * M_new ## Return the results return Q, M
def minkowski_reduction(self): """ Find a Minkowski-reduced form equivalent to the given one. This means that .. MATH:: Q(v_k) <= Q(s_1 * v_1 + ... + s_n * v_n) for all `s_i` where GCD`(s_k, ... s_n) = 1`. Note: When Q has dim <= 4 we can take all `s_i` in {1, 0, -1}. References: Schulze-Pillot's paper on "An algorithm for computing genera of ternary and quaternary quadratic forms", p138. Donaldson's 1979 paper "Minkowski Reduction of Integral Matrices", p203. EXAMPLES:: sage: Q = QuadraticForm(ZZ,4,[30,17,11,12,29,25,62,64,25,110]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 12 ] [ * 29 25 62 ] [ * * 64 25 ] [ * * * 110 ] sage: Q.minkowski_reduction() ( Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 -5 ] [ * 29 25 4 ] [ * * 64 0 ] [ * * * 77 ] , <BLANKLINE> [ 1 0 0 0] [ 0 1 0 -1] [ 0 0 1 0] [ 0 0 0 1] ) """ R = self.base_ring() n = self.dim() interior_reduced_flag = False Q = deepcopy(self) M = matrix(R, n, n) for i in range(n): M[i,i] = 1 ## Begin the reduction done_flag = False while not done_flag: ## Loop through possible shorted vectors until done_flag = True #print " j_range = ", range(n-1, -1, -1) for j in range(n-1, -1, -1): for a_first in mrange([2 for i in range(j)]): y = [x-1 for x in a_first] + [1] + [0 for k in range(n-1-j)] e_j = [0 for k in range(n)] e_j[j] = 1 #print "j = ", j ## Reduce if a shorter vector is found #print "y = ", y, " e_j = ", e_j, "\n" if Q(y) < Q(e_j): ## Create the transformation matrix M_new = matrix(R, n, n) for k in range(n): M_new[k,k] = 1 for k in range(n): M_new[k,j] = y[k] ## Perform the reduction and restart the loop #print "Q_before = ", Q Q = Q(M_new) M = M * M_new done_flag = False ## DIAGNOSTIC #print "Q(y) = ", Q(y) #print "Q(e_j) = ", Q(e_j) #print "M_new = ", M_new #print "Q_after = ", Q #print if not done_flag: break if not done_flag: break ## Return the results return Q, M
def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_precision='sturm', check_local_equivalence=True): """ Determines if the current quadratic form is equivalent to the given form over ZZ. If return_matrix is True, then we also return the transformation matrix M so that self(M) == other. INPUT: a QuadraticForm OUTPUT: boolean, and optionally a matrix EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: M = Matrix(ZZ, 4, 4, [1,2,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]) sage: Q1 = Q(M) sage: Q.(Q1) # optional -- souvigner True sage: MM = Q.is_globally_equivalent_to(Q1, return_matrix=True) # optional -- souvigner sage: Q(MM) == Q1 # optional -- souvigner True :: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) sage: Q3 = QuadraticForm(ZZ, 3, [8, 6, 5, 3, 4, 2]) sage: Q1.is_globally_equivalent_to(Q2) # optional -- souvigner False sage: Q1.is_globally_equivalent_to(Q3) # optional -- souvigner True sage: M = Q1.is_globally_equivalent_to(Q3, True) ; M # optional -- souvigner [-1 -1 0] [ 1 1 1] [-1 0 0] sage: Q1(M) == Q3 # optional -- souvigner True :: sage: Q = DiagonalQuadraticForm(ZZ, [1, -1]) sage: Q.is_globally_equivalent_to(Q) Traceback (most recent call last): ... ValueError: not a definite form in QuadraticForm.is_globally_equivalent_to() """ ## only for definite forms if not self.is_definite(): raise ValueError, "not a definite form in QuadraticForm.is_globally_equivalent_to()" ## Check that other is a QuadraticForm #if not isinstance(other, QuadraticForm): if not is_QuadraticForm(other): raise TypeError, "Oops! You must compare two quadratic forms, but the argument is not a quadratic form. =(" ## Now use the Souvigner code by default! =) return 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!) ## ---------------------------------- Unused Code below --------------------------------------------------------- ## Check if the forms are locally equivalent if (check_local_equivalence == True): if not self.is_locally_equivalent_to(other): return False ## 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) if check_theta_to_precision != None: if self.theta_series(check_theta_to_precision, var_str='', safe_flag=False) != other.theta_series(check_theta_to_precision, var_str='', safe_flag=False): return False ## Make all possible matrices which give an isomorphism -- can we do this more intelligently? ## ------------------------------------------------------------------------------------------ ## Find a basis of short vectors for one form, and try to match them with vectors of that length in the other one. basis_for_self, self_lengths = self.basis_of_short_vectors(show_lengths=True) max_len = max(self_lengths) short_vectors_of_other = other.short_vector_list_up_to_length(max_len + 1) ## Make the matrix A:e_i |--> v_i to our new basis. A = Matrix(basis_for_self).transpose() Q2 = A.transpose() * self.matrix() * A ## This is the matrix of 'self' in the new basis Q3 = other.matrix() ## Determine all automorphisms n = self.dim() Auto_list = [] ## DIAGNOSTIC #print "n = " + str(n) #print "pivot_lengths = " + str(pivot_lengths) #print "vector_list_by_length = " + str(vector_list_by_length) #print "length of vector_list_by_length = " + str(len(vector_list_by_length)) for index_vec in mrange([len(short_vectors_of_other[self_lengths[i]]) for i in range(n)]): M = Matrix([short_vectors_of_other[self_lengths[i]][index_vec[i]] for i in range(n)]).transpose() if M.transpose() * Q3 * M == Q2: if return_matrix: return A * M.inverse() else: return True ## If we got here, then there is no isomorphism return False
def minkowski_reduction(self): """ Find a Minkowski-reduced form equivalent to the given one. This means that .. MATH:: Q(v_k) <= Q(s_1 * v_1 + ... + s_n * v_n) for all `s_i` where GCD`(s_k, ... s_n) = 1`. Note: When Q has dim <= 4 we can take all `s_i` in {1, 0, -1}. References: Schulze-Pillot's paper on "An algorithm for computing genera of ternary and quaternary quadratic forms", p138. Donaldson's 1979 paper "Minkowski Reduction of Integral Matrices", p203. EXAMPLES:: sage: Q = QuadraticForm(ZZ,4,[30, 17, 11, 12, 29, 25, 62, 64, 25, 110]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 12 ] [ * 29 25 62 ] [ * * 64 25 ] [ * * * 110 ] sage: Q.minkowski_reduction() ( Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 -5 ] [ * 29 25 4 ] [ * * 64 0 ] [ * * * 77 ] , <BLANKLINE> [ 1 0 0 0] [ 0 1 0 -1] [ 0 0 1 0] [ 0 0 0 1] ) :: sage: Q=QuadraticForm(ZZ,4,[1, -2, 0, 0, 2, 0, 0, 2, 0, 2]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 1 -2 0 0 ] [ * 2 0 0 ] [ * * 2 0 ] [ * * * 2 ] sage: Q.minkowski_reduction() ( Quadratic form in 4 variables over Integer Ring with coefficients: [ 1 0 0 0 ] [ * 1 0 0 ] [ * * 2 0 ] [ * * * 2 ] , <BLANKLINE> [1 1 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1] ) :: sage: Q=QuadraticForm(ZZ,5,[2,2,0,0,0,2,2,0,0,2,2,0,2,2,2]) sage: Q.Gram_matrix() [2 1 0 0 0] [1 2 1 0 0] [0 1 2 1 0] [0 0 1 2 1] [0 0 0 1 2] sage: Q.minkowski_reduction() Traceback (most recent call last): ... NotImplementedError: This algorithm is only for dimensions less than 5 """ from sage.quadratic_forms.quadratic_form import QuadraticForm from sage.quadratic_forms.quadratic_form import matrix if not self.is_positive_definite(): raise TypeError( "Minkowksi reduction only works for positive definite forms") if self.dim() > 4: raise NotImplementedError( "This algorithm is only for dimensions less than 5") R = self.base_ring() n = self.dim() Q = deepcopy(self) M = matrix(R, n, n) for i in range(n): M[i, i] = 1 ## Begin the reduction done_flag = False while not done_flag: ## Loop through possible shorted vectors until done_flag = True #print " j_range = ", range(n-1, -1, -1) for j in range(n - 1, -1, -1): for a_first in mrange([3 for i in range(j)]): y = [x - 1 for x in a_first] + [1] + [0 for k in range(n - 1 - j)] e_j = [0 for k in range(n)] e_j[j] = 1 #print "j = ", j ## Reduce if a shorter vector is found #print "y = ", y, " e_j = ", e_j, "\n" if Q(y) < Q(e_j): ## Create the transformation matrix M_new = matrix(R, n, n) for k in range(n): M_new[k, k] = 1 for k in range(n): M_new[k, j] = y[k] ## Perform the reduction and restart the loop #print "Q_before = ", Q Q = QuadraticForm(M_new.transpose() * Q.matrix() * M_new) M = M * M_new done_flag = False ## DIAGNOSTIC #print "Q(y) = ", Q(y) #print "Q(e_j) = ", Q(e_j) #print "M_new = ", M_new #print "Q_after = ", Q #print if not done_flag: break if not done_flag: break ## Return the results return Q, M
def automorphisms(self): """ Return a list of the automorphisms of the quadratic form. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.number_of_automorphisms() # optional -- souvigner 48 sage: 2^3 * factorial(3) 48 sage: len(Q.automorphisms()) 48 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.number_of_automorphisms() # optional -- souvigner 16 sage: aut = Q.automorphisms() sage: len(aut) 16 sage: print([Q(M) == Q for M in aut]) [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True] sage: Q = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) sage: Q.automorphisms() [ [1 0 0] [-1 0 0] [0 1 0] [ 0 -1 0] [0 0 1], [ 0 0 -1] ] :: sage: Q = DiagonalQuadraticForm(ZZ, [1, -1]) sage: Q.automorphisms() Traceback (most recent call last): ... ValueError: not a definite form in QuadraticForm.automorphisms() """ ## only for definite forms if not self.is_definite(): raise ValueError("not a definite form in QuadraticForm.automorphisms()") ## Check for a cached value try: return self.__automorphisms except AttributeError: pass ## Find a basis of short vectors, and their lengths basis, pivot_lengths = self.basis_of_short_vectors(show_lengths=True) ## List the relevant vectors by length max_len = max(pivot_lengths) vector_list_by_length = self.short_primitive_vector_list_up_to_length(max_len + 1) ## Make the matrix A:e_i |--> v_i to our new basis. A = Matrix(basis).transpose() Ainv = A.inverse() # A1 = A.inverse() * A.det() # Q1 = A1.transpose() * self.matrix() * A1 ## This is the matrix of Q # Q = self.matrix() * A.det()**2 Q2 = A.transpose() * self.matrix() * A ## This is the matrix of Q in the new basis Q3 = self.matrix() ## Determine all automorphisms n = self.dim() Auto_list = [] # ct = 0 ## DIAGNOSTIC # print "n = " + str(n) # print "pivot_lengths = " + str(pivot_lengths) # print "vector_list_by_length = " + str(vector_list_by_length) # print "length of vector_list_by_length = " + str(len(vector_list_by_length)) for index_vec in mrange([len(vector_list_by_length[pivot_lengths[i]]) for i in range(n)]): M = Matrix([vector_list_by_length[pivot_lengths[i]][index_vec[i]] for i in range(n)]).transpose() # Q1 = self.matrix() # if self(M) == self: # ct += 1 # print "ct = ", ct, " M = " # print M # print if M.transpose() * Q3 * M == Q2: ## THIS DOES THE SAME THING! =( Auto_list.append(M * Ainv) ## Cache the answer and return the list self.__automorphisms = Auto_list self.__number_of_automorphisms = len(Auto_list) return Auto_list
def is_globally_equivalent_to(self, other, return_matrix=False, check_theta_to_precision='sturm', check_local_equivalence=True): """ Determines if the current quadratic form is equivalent to the given form over ZZ. If return_matrix is True, then we also return the transformation matrix M so that self(M) == other. INPUT: a QuadraticForm OUTPUT: boolean, and optionally a matrix EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: M = Matrix(ZZ, 4, 4, [1,2,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]) sage: Q1 = Q(M) sage: Q.(Q1) # optional -- souvigner True sage: MM = Q.is_globally_equivalent_to(Q1, return_matrix=True) # optional -- souvigner sage: Q(MM) == Q1 # optional -- souvigner True :: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) sage: Q3 = QuadraticForm(ZZ, 3, [8, 6, 5, 3, 4, 2]) sage: Q1.is_globally_equivalent_to(Q2) # optional -- souvigner False sage: Q1.is_globally_equivalent_to(Q3) # optional -- souvigner True sage: M = Q1.is_globally_equivalent_to(Q3, True) ; M # optional -- souvigner [-1 -1 0] [ 1 1 1] [-1 0 0] sage: Q1(M) == Q3 # optional -- souvigner True :: sage: Q = DiagonalQuadraticForm(ZZ, [1, -1]) sage: Q.is_globally_equivalent_to(Q) Traceback (most recent call last): ... ValueError: not a definite form in QuadraticForm.is_globally_equivalent_to() """ ## only for definite forms if not self.is_definite(): raise ValueError( "not a definite form in QuadraticForm.is_globally_equivalent_to()") ## Check that other is a QuadraticForm #if not isinstance(other, QuadraticForm): if not is_QuadraticForm(other): raise TypeError( "Oops! You must compare two quadratic forms, but the argument is not a quadratic form. =(" ) ## Now use the Souvigner code by default! =) return 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!) ## ---------------------------------- Unused Code below --------------------------------------------------------- ## Check if the forms are locally equivalent if (check_local_equivalence == True): if not self.is_locally_equivalent_to(other): return False ## 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) if check_theta_to_precision is not None: if self.theta_series( check_theta_to_precision, var_str='', safe_flag=False) != other.theta_series( check_theta_to_precision, var_str='', safe_flag=False): return False ## Make all possible matrices which give an isomorphism -- can we do this more intelligently? ## ------------------------------------------------------------------------------------------ ## Find a basis of short vectors for one form, and try to match them with vectors of that length in the other one. basis_for_self, self_lengths = self.basis_of_short_vectors( show_lengths=True) max_len = max(self_lengths) short_vectors_of_other = other.short_vector_list_up_to_length(max_len + 1) ## Make the matrix A:e_i |--> v_i to our new basis. A = Matrix(basis_for_self).transpose() Q2 = A.transpose() * self.matrix( ) * A ## This is the matrix of 'self' in the new basis Q3 = other.matrix() ## Determine all automorphisms n = self.dim() Auto_list = [] ## DIAGNOSTIC #print "n = " + str(n) #print "pivot_lengths = " + str(pivot_lengths) #print "vector_list_by_length = " + str(vector_list_by_length) #print "length of vector_list_by_length = " + str(len(vector_list_by_length)) for index_vec in mrange( [len(short_vectors_of_other[self_lengths[i]]) for i in range(n)]): M = Matrix([ short_vectors_of_other[self_lengths[i]][index_vec[i]] for i in range(n) ]).transpose() if M.transpose() * Q3 * M == Q2: if return_matrix: return A * M.inverse() else: return True ## If we got here, then there is no isomorphism return False