def short_primitive_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short primitive vectors `v`, sorted by length, with Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of length `i`. If the up_to_sign_flag is set to True, then only one of the vectors of the pair `[v, -v]` is listed. Note: This processes the PARI/GP output to always give elements of type `ZZ`. OUTPUT: a list of lists of vectors. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.short_vector_list_up_to_length(5, True) [[(0, 0, 0, 0)], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]] sage: Q.short_primitive_vector_list_up_to_length(5, True) [[], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0)]] """ ## Get a list of short vectors full_vec_list = self.short_vector_list_up_to_length(len_bound, up_to_sign_flag) ## Make a new list of the primitive vectors 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! ## Return the list of primitive vectors return prim_vec_list
def conv(N): file = "data/%s" % N if not os.path.exists(file): raise RuntimeError, "Data for level %s does not exist." % N F = open(file).read() i = F.find(":=") if i == -1: raise RuntimeError, "Syntax error in file for level %s." % N F = F[i + 2:] TRANS = [("[*", "["), ("*]", "]"), ("<","["), (">","]"), \ (";",""), ("\n",""), ("^","**")] for z, w in TRANS: F = F.replace(z, w) X = [] # Define x so the eval below works. R = PolynomialRing(RationalField()) x = R.gen() print "starting eval." #print "F = ", F for f in eval(F): print "creating object from f=", f[:4] cp = {} disc = 0 for z in f[5]: g = R(z[1]) disc = GCD(disc, g.discriminant()) cp[z[0]] = g X.append(ModularForm(f[0], f[1], f[2], f[3], f[4], cp, disc)) return X
def torsion_multiple(self): """ Returns a multiple of the order of the torsion subgroup of the corresponding abelian variety. """ T = 0 for p in self.charpolys.keys(): if self.N % p != 0: f = self.charpolys[p] T = GCD(T, long(f(p + 1))) return T
def congruence_multiple(self, f, anemic=True): """ Return an integer C such that any prime of congruence between this modular form and f is a divisor of C. If anemic=True (the default), include only coefficients a_p with p coprime to the levels of self and f. This function returns the gcd of the resultants of each of the charpolys of coefficients of self and f that are both known. """ C = 0 N = self.N * f.N for p in self.charpolys.keys(): if p in f.charpolys.keys(): if (not anemic) or (anemic and N % p != 0): ap = self.charpolys[p] bp = f.charpolys[p] C = GCD(C, ap.resultant(bp)) return C
def gcd(self): """ Returns the greatest common divisor of the coefficients of the quadratic form (as a polynomial). EXAMPLES:: sage: Q = QuadraticForm(ZZ, 4, range(1, 21, 2)) sage: Q.gcd() 1 :: sage: Q = QuadraticForm(ZZ, 4, range(0, 20, 2)) sage: Q.gcd() 2 """ if self.base_ring() != ZZ: raise TypeError("Oops! The given quadratic form must be defined over ZZ.") return GCD(self.coefficients())
def order(self): """ Return the order of this element. OUTPUT: An integer or ``infinity``. EXAMPLES:: sage: F = AbelianGroup(3,[7,8,9]) sage: Fd = F.dual_group() sage: A,B,C = Fd.gens() sage: (B*C).order() 72 sage: F = AbelianGroup(3,[7,8,9]); F Multiplicative Abelian group isomorphic to C7 x C8 x C9 sage: F.gens()[2].order() 9 sage: a,b,c = F.gens() sage: (b*c).order() 72 sage: G = AbelianGroup(3,[7,8,9]) sage: type((G.0 * G.1).order())==Integer True """ M = self.parent() order = M.gens_orders() L = self.exponents() N = LCM([ order[i] / GCD(order[i], L[i]) for i in range(len(order)) if L[i] != 0 ]) if N == 0: return infinity else: return ZZ(N)
def order(self): """ Returns the (finite) order of this element or Infinity if this element does not have finite order. EXAMPLES:: sage: F = AbelianGroup(3,[7,8,9]); F Multiplicative Abelian Group isomorphic to C7 x C8 x C9 sage: F.gens()[2].order() 9 sage: a,b,c = F.gens() sage: (b*c).order() 72 sage: G = AbelianGroup(3,[7,8,9]) sage: type((G.0 * G.1).order())==Integer True """ M = self.parent() if self == M(1): return Integer(1) invs = M.invariants() if self in M.gens(): o = invs[list(M.gens()).index(self)] if o == 0: return infinity return o L = list(self.list()) N = LCM([ invs[i] // GCD(invs[i], L[i]) for i in range(len(invs)) if L[i] != 0 ]) if N == 0: return infinity else: return Integer(N)
def CyclicCodeFromGeneratingPolynomial(n,g,ignore=True): r""" If g is a polynomial over GF(q) which divides `x^n-1` then this constructs the code "generated by g" (ie, the code associated with the principle ideal `gR` in the ring `R = GF(q)[x]/(x^n-1)` in the usual way). The option "ignore" says to ignore the condition that (a) the characteristic of the base field does not divide the length (the usual assumption in the theory of cyclic codes), and (b) `g` must divide `x^n-1`. If ignore=True, instead of returning an error, a code generated by `gcd(x^n-1,g)` is created. EXAMPLES:: sage: P.<x> = PolynomialRing(GF(3),"x") sage: g = x-1 sage: C = CyclicCodeFromGeneratingPolynomial(4,g); C Linear code of length 4, dimension 3 over Finite Field of size 3 sage: P.<x> = PolynomialRing(GF(4,"a"),"x") sage: g = x^3+1 sage: C = CyclicCodeFromGeneratingPolynomial(9,g); C Linear code of length 9, dimension 6 over Finite Field in a of size 2^2 sage: P.<x> = PolynomialRing(GF(2),"x") sage: g = x^3+x+1 sage: C = CyclicCodeFromGeneratingPolynomial(7,g); C Linear code of length 7, dimension 4 over Finite Field of size 2 sage: C.gen_mat() [1 1 0 1 0 0 0] [0 1 1 0 1 0 0] [0 0 1 1 0 1 0] [0 0 0 1 1 0 1] sage: g = x+1 sage: C = CyclicCodeFromGeneratingPolynomial(4,g); C Linear code of length 4, dimension 3 over Finite Field of size 2 sage: C.gen_mat() [1 1 0 0] [0 1 1 0] [0 0 1 1] On the other hand, CyclicCodeFromPolynomial(4,x) will produce a ValueError including a traceback error message: "`x` must divide `x^4 - 1`". You will also get a ValueError if you type :: sage: P.<x> = PolynomialRing(GF(4,"a"),"x") sage: g = x^2+1 followed by CyclicCodeFromGeneratingPolynomial(6,g). You will also get a ValueError if you type :: sage: P.<x> = PolynomialRing(GF(3),"x") sage: g = x^2-1 sage: C = CyclicCodeFromGeneratingPolynomial(5,g); C Linear code of length 5, dimension 4 over Finite Field of size 3 followed by C = CyclicCodeFromGeneratingPolynomial(5,g,False), with a traceback message including "`x^2 + 2` must divide `x^5 - 1`". """ P = g.parent() x = P.gen() F = g.base_ring() p = F.characteristic() if not(ignore) and p.divides(n): raise ValueError, 'The characteristic %s must not divide %s'%(p,n) if not(ignore) and not(g.divides(x**n-1)): raise ValueError, '%s must divide x^%s - 1'%(g,n) gn = GCD([g,x**n-1]) d = gn.degree() coeffs = Sequence(gn.list()) r1 = Sequence(coeffs+[0]*(n - d - 1)) Sn = SymmetricGroup(n) s = Sn.gens()[0] # assumes 1st gen of S_n is (1,2,...,n) rows = [permutation_action(s**(-i),r1) for i in range(n-d)] MS = MatrixSpace(F,n-d,n) return LinearCode(MS(rows))
def CyclicCodeFromGeneratingPolynomial(n,g,ignore=True): r""" If g is a polynomial over GF(q) which divides `x^n-1` then this constructs the code "generated by g" (ie, the code associated with the principle ideal `gR` in the ring `R = GF(q)[x]/(x^n-1)` in the usual way). The option "ignore" says to ignore the condition that (a) the characteristic of the base field does not divide the length (the usual assumption in the theory of cyclic codes), and (b) `g` must divide `x^n-1`. If ignore=True, instead of returning an error, a code generated by `gcd(x^n-1,g)` is created. EXAMPLES:: sage: P.<x> = PolynomialRing(GF(3),"x") sage: g = x-1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(4,g); C Linear code of length 4, dimension 3 over Finite Field of size 3 sage: P.<x> = PolynomialRing(GF(4,"a"),"x") sage: g = x^3+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(9,g); C Linear code of length 9, dimension 6 over Finite Field in a of size 2^2 sage: P.<x> = PolynomialRing(GF(2),"x") sage: g = x^3+x+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(7,g); C Linear code of length 7, dimension 4 over Finite Field of size 2 sage: C.gen_mat() [1 1 0 1 0 0 0] [0 1 1 0 1 0 0] [0 0 1 1 0 1 0] [0 0 0 1 1 0 1] sage: g = x+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(4,g); C Linear code of length 4, dimension 3 over Finite Field of size 2 sage: C.gen_mat() [1 1 0 0] [0 1 1 0] [0 0 1 1] On the other hand, CyclicCodeFromPolynomial(4,x) will produce a ValueError including a traceback error message: "`x` must divide `x^4 - 1`". You will also get a ValueError if you type :: sage: P.<x> = PolynomialRing(GF(4,"a"),"x") sage: g = x^2+1 followed by CyclicCodeFromGeneratingPolynomial(6,g). You will also get a ValueError if you type :: sage: P.<x> = PolynomialRing(GF(3),"x") sage: g = x^2-1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(5,g); C Linear code of length 5, dimension 4 over Finite Field of size 3 followed by C = CyclicCodeFromGeneratingPolynomial(5,g,False), with a traceback message including "`x^2 + 2` must divide `x^5 - 1`". """ P = g.parent() x = P.gen() F = g.base_ring() p = F.characteristic() if not(ignore) and p.divides(n): raise ValueError, 'The characteristic %s must not divide %s'%(p,n) if not(ignore) and not(g.divides(x**n-1)): raise ValueError, '%s must divide x^%s - 1'%(g,n) gn = GCD([g,x**n-1]) d = gn.degree() coeffs = Sequence(gn.list()) r1 = Sequence(coeffs+[0]*(n - d - 1)) Sn = SymmetricGroup(n) s = Sn.gens()[0] # assumes 1st gen of S_n is (1,2,...,n) rows = [permutation_action(s**(-i),r1) for i in range(n-d)] MS = MatrixSpace(F,n-d,n) return LinearCode(MS(rows))
def cyclotomic_cosets(q, n, t = None): r""" INPUT: q,n,t positive integers (or t=None) Some type-checking of inputs is performed. OUTPUT: q-cyclotomic cosets mod n (or, if t is not None, the q-cyclotomic coset mod n containing t) Let q, n be relatively print positive integers and let `A = q^{ZZ}`. The group A acts on ZZ/nZZ by multiplication. The orbits of this action are "cyclotomic cosets", or more precisely "q-cyclotomic cosets mod n". Sometimes the smallest element of the coset is called the "coset leader". The algorithm will always return the cosets as sorted lists of lists, so the coset leader will always be the first element in the list. These cosets arise in the theory of duadic codes and minimal polynomials of finite fields. Fix a primitive element `z` of `GF(q^k)`. The minimal polynomial of `z^s` over `GF(q)` is given by .. math:: M_s(x) = \prod_{i \in C_s} (x-z^i), where `C_s` is the q-cyclotomic coset mod n containing s, `n = q^k - 1`. EXAMPLES:: sage: cyclotomic_cosets(2,11) [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] sage: cyclotomic_cosets(2,15) [[0], [1, 2, 4, 8], [3, 6, 9, 12], [5, 10], [7, 11, 13, 14]] sage: cyclotomic_cosets(2,15,5) [5, 10] sage: cyclotomic_cosets(3,16) [[0], [1, 3, 9, 11], [2, 6], [4, 12], [5, 7, 13, 15], [8], [10, 14]] sage: F.<z> = GF(2^4, "z") sage: P.<x> = PolynomialRing(F,"x") sage: a = z^5 sage: a.minimal_polynomial() x^2 + x + 1 sage: prod([x-z^i for i in [5, 10]]) x^2 + x + 1 sage: cyclotomic_cosets(3,2,0) [0] sage: cyclotomic_cosets(3,2,1) [1] sage: cyclotomic_cosets(3,2,2) [0] This last output looks strange but is correct, since the elements of the cosets are in ZZ/nZZ and 2 = 0 in ZZ/2ZZ. """ from sage.misc.misc import srange if not(t==None) and type(t)<>Integer: raise TypeError, "Optional input %s must None or an integer."%t if q<2 or n<2: raise TypeError, "Inputs %s and %s must be > 1."%(q,n) if GCD(q,n) <> 1: raise TypeError, "Inputs %s and %s must be relative prime."%(q,n) if t<>None and type(t)==Integer: S = Set([t*q**i%n for i in srange(n)]) L = list(S) L.sort() return L ccs = Set([]) ccs_list = [[0]] for s in range(1,n): if not(s in ccs): S = Set([s*q**i%n for i in srange(n)]) L = list(S) L.sort() ccs = ccs.union(S) ccs_list.append(L) return ccs_list
def has_equivalent_Jordan_decomposition_at_prime(self, other, p): """ Determines if the given quadratic form has a Jordan decomposition equivalent to that of self. INPUT: a QuadraticForm OUTPUT: boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3]) sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6]) sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11]) sage: [Q1.level(), Q2.level(), Q3.level()] [44, 44, 44] sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11) False """ ## Sanity Checks #if not isinstance(other, QuadraticForm): if not isinstance(other, type(self)): raise TypeError( "Oops! The first argument must be of type QuadraticForm.") if not is_prime(p): raise TypeError("Oops! The second argument must be a prime number.") ## Get the relevant local normal forms quickly self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) ## DIAGNOSTIC #print "self_jordan = ", self_jordan #print "other_jordan = ", other_jordan ## Check for the same number of Jordan components if len(self_jordan) != len(other_jordan): return False ## Deal with odd primes: Check that the Jordan component scales, dimensions, and discriminants are the same if p != 2: for i in range(len(self_jordan)): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1): return False ## All tests passed for an odd prime. return True ## For p = 2: Check that all Jordan Invariants are the same. elif p == 2: ## Useful definition t = len(self_jordan) ## Define t = Number of Jordan components ## Check that all Jordan Invariants are the same (scale, dim, and norm) for i in range(t): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)): return False ## DIAGNOSTIC #print "Passed the Jordan invariant test." ## Use O'Meara's isometry test 93:29 on p277. ## ------------------------------------------ ## List of norms, scales, and dimensions for each i scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)] norm_list = [ ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2)) for i in range(t) ] dim_list = [(self_jordan[i][1].dim()) for i in range(t)] ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!) -- NOT SO GOOD... ## 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.) j = 0 self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]) ] other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]) ] self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor( ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ] other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor( ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ] for j in range(1, t): self_chain_det_list.append(self_chain_det_list[j - 1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) other_chain_det_list.append(other_chain_det_list[j - 1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) self_hasse_chain_list.append(self_hasse_chain_list[j-1] \ * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \ * self_jordan[j][1].hasse_invariant__OMeara(2)) other_hasse_chain_list.append(other_hasse_chain_list[j-1] \ * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \ * other_jordan[j][1].hasse_invariant__OMeara(2)) ## SANITY CHECK -- check that the scale powers are strictly increasing for i in range(1, len(scale_list)): if scale_list[i - 1] >= scale_list[i]: raise RuntimeError( "Oops! There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!" ) ## DIAGNOSTIC #print "scale_list = ", scale_list #print "norm_list = ", norm_list #print "dim_list = ", dim_list #print #print "self_chain_det_list = ", self_chain_det_list #print "other_chain_det_list = ", other_chain_det_list #print "self_hasse_chain_list = ", self_hasse_chain_list #print "other_hasse_chain_det_list = ", other_hasse_chain_list ## Test O'Meara's two conditions for i in range(t - 1): ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8). modulus = norm_list[i] * norm_list[i + 1] / (scale_list[i]**2) if modulus > 8: modulus = 8 if (modulus > 1) and (( (self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1): #print "Failed when i =", i, " in condition 1." return False ## Check O'Meara's condition (ii) when appropriate if norm_list[i + 1] % (4 * norm_list[i]) == 0: if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \ != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2): ## Nipp conditions #print "Failed when i =", i, " in condition 2." return False ## All tests passed for the prime 2. return True else: raise TypeError("Oops! This should not have happened.")
def local_normal_form(self, p): """ Returns the a locally integrally equivalent quadratic form over the p-adic integers Z_p which gives the Jordan decomposition. The Jordan components are written as sums of blocks of size <= 2 and are arranged by increasing scale, and then by increasing norm. (This is equivalent to saying that we put the 1x1 blocks before the 2x2 blocks in each Jordan component.) INPUT: `p` -- a positive prime number. OUTPUT: a quadratic form over ZZ WARNING: Currently this only works for quadratic forms defined over ZZ. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [10,4,1]) sage: Q.local_normal_form(5) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 6 ] :: sage: Q.local_normal_form(3) Quadratic form in 2 variables over Integer Ring with coefficients: [ 10 0 ] [ * 15 ] sage: Q.local_normal_form(2) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 6 ] """ ## Sanity Checks if (self.base_ring() != IntegerRing()): raise NotImplementedError( "Oops! This currently only works for quadratic forms defined over IntegerRing(). =(" ) if not ((p >= 2) and is_prime(p)): raise TypeError("Oops! p is not a positive prime number. =(") ## Some useful local variables Q = copy.deepcopy(self) Q.__init__(self.base_ring(), self.dim(), self.coefficients()) ## Prepare the final form to return Q_Jordan = copy.deepcopy(self) Q_Jordan.__init__(self.base_ring(), 0) while Q.dim() > 0: n = Q.dim() ## Step 1: Find the minimally p-divisible matrix entry, preferring diagonals ## ------------------------------------------------------------------------- (min_i, min_j) = Q.find_entry_with_minimal_scale_at_prime(p) if min_i == min_j: min_val = valuation(2 * Q[min_i, min_j], p) else: min_val = valuation(Q[min_i, min_j], p) ## Error if we still haven't seen non-zero coefficients! if (min_val == Infinity): raise RuntimeError("Oops! The original matrix is degenerate. =(") ## Step 2: Arrange for the upper leftmost entry to have minimal valuation ## ---------------------------------------------------------------------- if (min_i == min_j): block_size = 1 Q.swap_variables(0, min_i, in_place=True) else: ## Work in the upper-left 2x2 block, and replace it by its 2-adic equivalent form Q.swap_variables(0, min_i, in_place=True) Q.swap_variables(1, min_j, in_place=True) ## 1x1 => make upper left the smallest if (p != 2): block_size = 1 Q.add_symmetric(1, 0, 1, in_place=True) ## 2x2 => replace it with the appropriate 2x2 matrix else: block_size = 2 ## DIAGNOSTIC #print "\n Finished Step 2 \n"; #print "\n Q is: \n" + str(Q) + "\n"; #print " p is: " + str(p) #print " min_val is: " + str( min_val) #print " block_size is: " + str(block_size) #print "\n Starting Step 3 \n" ## Step 3: Clear out the remaining entries ## --------------------------------------- min_scale = p**min_val ## This is the minimal valuation of the Hessian matrix entries. ##DIAGNOSTIC #print "Starting Step 3:" #print "----------------" #print " min_scale is: " + str(min_scale) ## Perform cancellation over Z by ensuring divisibility if (block_size == 1): a = 2 * Q[0, 0] for j in range(block_size, n): b = Q[0, j] g = GCD(a, b) ## DIAGNSOTIC #print "Cancelling from a 1x1 block:" #print "----------------------------" #print " Cancelling entry with index (" + str(upper_left) + ", " + str(j) + ")" #print " entry = " + str(b) #print " gcd = " + str(g) #print " a = " + str(a) #print " b = " + str(b) #print " a/g = " + str(a/g) + " (used for stretching)" #print " -b/g = " + str(-b/g) + " (used for cancelling)" ## Sanity Check: a/g is a p-unit if valuation(g, p) != valuation(a, p): raise RuntimeError( "Oops! We have a problem with our rescaling not preserving p-integrality!" ) Q.multiply_variable( ZZ(a / g), j, in_place=True ) ## Ensures that the new b entry is divisible by a Q.add_symmetric(ZZ(-b / g), j, 0, in_place=True) ## Performs the cancellation elif (block_size == 2): a1 = 2 * Q[0, 0] a2 = Q[0, 1] b1 = Q[1, 0] ## This is the same as a2 b2 = 2 * Q[1, 1] big_det = (a1 * b2 - a2 * b1) small_det = big_det / (min_scale * min_scale) ## Cancels out the rows/columns of the 2x2 block for j in range(block_size, n): a = Q[0, j] b = Q[1, j] ## Ensures an integral result (scale jth row/column by big_det) Q.multiply_variable(big_det, j, in_place=True) ## Performs the cancellation (by producing -big_det * jth row/column) Q.add_symmetric(ZZ(-(a * b2 - b * a2)), j, 0, in_place=True) Q.add_symmetric(ZZ(-(-a * b1 + b * a1)), j, 1, in_place=True) ## Now remove the extra factor (non p-unit factor) in big_det we introduced above Q.divide_variable(ZZ(min_scale * min_scale), j, in_place=True) ## DIAGNOSTIC #print "Cancelling out a 2x2 block:" #print "---------------------------" #print " a1 = " + str(a1) #print " a2 = " + str(a2) #print " b1 = " + str(b1) #print " b2 = " + str(b2) #print " big_det = " + str(big_det) #print " min_scale = " + str(min_scale) #print " small_det = " + str(small_det) #print " Q = \n", Q ## Uses Cassels's proof to replace the remaining 2 x 2 block if (((1 + small_det) % 8) == 0): Q[0, 0] = 0 Q[1, 1] = 0 Q[0, 1] = min_scale elif (((5 + small_det) % 8) == 0): Q[0, 0] = min_scale Q[1, 1] = min_scale Q[0, 1] = min_scale else: raise RuntimeError( "Error in LocalNormal: Impossible behavior for a 2x2 block! \n" ) ## Check that the cancellation worked, extract the upper-left block, and trim Q to handle the next block. for i in range(block_size): for j in range(block_size, n): if Q[i, j] != 0: raise RuntimeError( "Oops! The cancellation didn't work properly at entry (" + str(i) + ", " + str(j) + ").") Q_Jordan = Q_Jordan + Q.extract_variables(range(block_size)) Q = Q.extract_variables(range(block_size, n)) return Q_Jordan
def is_a_splitting(S1, S2, n, return_automorphism=False): """ Check wether ``(S1,S2)`` is a splitting of `\ZZ/n\ZZ`. A splitting of `R = \ZZ/n\ZZ` is a pair of subsets of `R` which is a partition of `R \\backslash \{0\}` and such that there exists an element `r` of `R` such that `r S_1 = S_2` and `r S_2 = S_1` (where `r S` is the point-wise multiplication of the elements of `S` by `r`). Splittings are useful for computing idempotents in the quotient ring `Q = GF(q)[x]/(x^n-1)`. INPUT: - ``S1, S2`` -- disjoint sublists partitioning ``[1, 2, ..., n-1]`` - ``n`` (integer) - ``return_automorphism`` (boolean) -- whether to return the automorphism exchanging `S_1` and `S_2`. OUTPUT: If ``return_automorphism is False`` (default) the function returns boolean values. Otherwise, it returns a pair ``(b, r)`` where ``b`` is a boolean indicating whether `S1`, `S2` is a splitting of `n`, and `r` is such that `r S_1 = S_2` and `r S_2 = S_1` (if `b` is ``False``, `r` is equal to ``None``). EXAMPLES:: sage: from sage.coding.code_constructions import is_a_splitting sage: is_a_splitting([1,2],[3,4],5) True sage: is_a_splitting([1,2],[3,4],5,return_automorphism=True) (True, 4) sage: is_a_splitting([1,3],[2,4,5,6],7) False sage: is_a_splitting([1,3,4],[2,5,6],7) False sage: for P in SetPartitions(6,[3,3]): ....: res,aut= is_a_splitting(P[0],P[1],7,return_automorphism=True) ....: if res: ....: print aut, P[0], P[1] 6 {1, 2, 3} {4, 5, 6} 3 {1, 2, 4} {3, 5, 6} 6 {1, 3, 5} {2, 4, 6} 6 {1, 4, 5} {2, 3, 6} We illustrate now how to find idempotents in quotient rings:: sage: n = 11; q = 3 sage: C = Zmod(n).cyclotomic_cosets(q); C [[0], [1, 3, 4, 5, 9], [2, 6, 7, 8, 10]] sage: S1 = C[1] sage: S2 = C[2] sage: is_a_splitting(S1,S2,11) True sage: F = GF(q) sage: P.<x> = PolynomialRing(F,"x") sage: I = Ideal(P,[x^n-1]) sage: Q.<x> = QuotientRing(P,I) sage: i1 = -sum([x^i for i in S1]); i1 2*x^9 + 2*x^5 + 2*x^4 + 2*x^3 + 2*x sage: i2 = -sum([x^i for i in S2]); i2 2*x^10 + 2*x^8 + 2*x^7 + 2*x^6 + 2*x^2 sage: i1^2 == i1 True sage: i2^2 == i2 True sage: (1-i1)^2 == 1-i1 True sage: (1-i2)^2 == 1-i2 True We return to dealing with polynomials (rather than elements of quotient rings), so we can construct cyclic codes:: sage: P.<x> = PolynomialRing(F,"x") sage: i1 = -sum([x^i for i in S1]) sage: i2 = -sum([x^i for i in S2]) sage: i1_sqrd = (i1^2).quo_rem(x^n-1)[1] sage: i1_sqrd == i1 True sage: i2_sqrd = (i2^2).quo_rem(x^n-1)[1] sage: i2_sqrd == i2 True sage: C1 = codes.CyclicCodeFromGeneratingPolynomial(n,i1) sage: C2 = codes.CyclicCodeFromGeneratingPolynomial(n,1-i2) sage: C1.dual_code() == C2 True This is a special case of Theorem 6.4.3 in [HP]_. """ R = IntegerModRing(n) S1 = set(R(x) for x in S1) S2 = set(R(x) for x in S2) # we first check whether (S1,S2) is a partition of R - {0} if (len(S1) + len(S2) != n - 1 or len(S1) != len(S2) or R.zero() in S1 or R.zero() in S2 or not S1.isdisjoint(S2)): if return_automorphism: return False, None else: return False # now that we know that (S1,S2) is a partition, we look for an invertible # element b that maps S1 to S2 by multiplication for b in range(2, n): if GCD(b, n) == 1 and all(b * x in S2 for x in S1): if return_automorphism: return True, b else: return True if return_automorphism: return False, None else: return False
def complementary_subform_to_vector(self, v): """ Finds the `(n-1)`-dim'l quadratic form orthogonal to the vector `v`. Note: This is usually not a direct summand! Technical Notes: There is a minor difference in the cancellation code here (form the C++ version) since the notation Q `[i,j]` indexes coefficients of the quadratic polynomial here, not the symmetric matrix. Also, it produces a better splitting now, for the full lattice (as opposed to a sublattice in the C++ code) since we now extend `v` to a unimodular matrix. INPUT: `v` -- a list of self.dim() integers OUTPUT: a QuadraticForm over `ZZ` EXAMPLES:: sage: Q1 = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q1.complementary_subform_to_vector([1,0,0,0]) Quadratic form in 3 variables over Integer Ring with coefficients: [ 3 0 0 ] [ * 5 0 ] [ * * 7 ] :: sage: Q1.complementary_subform_to_vector([1,1,0,0]) Quadratic form in 3 variables over Integer Ring with coefficients: [ 12 0 0 ] [ * 5 0 ] [ * * 7 ] :: sage: Q1.complementary_subform_to_vector([1,1,1,1]) Quadratic form in 3 variables over Integer Ring with coefficients: [ 624 -480 -672 ] [ * 880 -1120 ] [ * * 1008 ] """ n = self.dim() ## Copy the quadratic form Q = deepcopy(self) ## Find the first non-zero component of v, and call it nz (Note: 0 <= nz < n) nz = 0 while (nz < n) and (v[nz] == 0): nz += 1 ## Abort if v is the zero vector if nz == n: raise TypeError, "Oops, v cannot be the zero vector! =(" ## Make the change of basis matrix new_basis = extend_to_primitive(matrix(ZZ, n, 1, v)) ## Change Q (to Q1) to have v as its nz-th basis vector Q1 = Q(new_basis) ## Pick out the value Q(v) of the vector d = Q1[0, 0] #print Q1 ## For each row/column, perform elementary operations to cancel them out. for i in range(1, n): ## Check if the (i,0)-entry is divisible by d, ## and stretch its row/column if not. if Q1[i, 0] % d != 0: Q1 = Q1.multiply_variable(d / GCD(d, Q1[i, 0] // 2), i) #print "After scaling at i =", i #print Q1 ## Now perform the (symmetric) elementary operations to cancel out the (i,0) entries/ Q1 = Q1.add_symmetric(-(Q1[i, 0] / 2) / (GCD(d, Q1[i, 0] // 2)), i, 0) #print "After cancelling at i =", i #print Q1 ## Check that we're done! done_flag = True for i in range(1, n): if Q1[0, i] != 0: done_flag = False if done_flag == False: raise RuntimeError, "There is a problem cancelling out the matrix entries! =O" ## Return the complementary matrix return Q1.extract_variables(range(1, n))