def possible_isogeny_degrees(E, verbose=False): r""" Return a list of primes `\ell` sufficient to generate the isogeny class of `E`. INPUT: - ``E`` -- An elliptic curve defined over a number field. OUTPUT: A finite list of primes `\ell` such that every curve isogenous to this curve can be obtained by a finite sequence of isogenies of degree one of the primes in the list. ALGORITHM: For curves without CM, the set may be taken to be the finite set of primes at which the Galois representation is not surjective, since the existence of an `\ell`-isogeny is equivalent to the image of the mod-`\ell` Galois representation being contained in a Borel subgroup. For curves with CM by the order `O` of discriminant `d`, the Galois representation is always non-surjective and the curve will admit `\ell`-isogenies for infinitely many primes `\ell`, but there are (of course) only finitely many codomains `E'`. The primes can be divided according to the discriminant `d'` of the CM order `O'` associated to `E`: either `O=O'`, or one contains the other with index `\ell`, since `\ell O\subset O'` and vice versa. Case (1): `O=O'`. The degrees of all isogenies between `E` and `E'` are precisely the integers represented by one of the classes of binary quadratic forms `Q` of discriminant `d`. Hence to obtain all possible isomorphism classes of codomain `E'`, we need only use one prime `\ell` represented by each such class `Q`. It would in fact suffice to use primes represented by forms which generate the class group. Here we simply omit the principal class and one from each pair of inverse classes, and include a prime represented by each of the remaining forms. Case (2): `[O':O]=\ell`: so `d=\ell^2d;`. We include all prime divisors of `d`. Case (3): `[O:O']=\ell`: we may assume that `\ell` does not divide `d` as we have already included these, so `\ell` either splits or is inert in `O`; the class numbers satisfy `h(O')=(\ell\pm1)h(O)` accordingly. We include all primes `\ell` such that `\ell\pm1` divides the degree `[K:\QQ]`. For curves with only potential CM we proceed as in the CM case, using `2[K:\QQ]` instead of `[K:\QQ]`. EXAMPLES: For curves without CM we determine the primes at which the mod `p` Galois representation is reducible, i.e. contained in a Borel subgroup:: sage: from sage.schemes.elliptic_curves.isogeny_class import possible_isogeny_degrees sage: E = EllipticCurve('11a1') sage: possible_isogeny_degrees(E) [5] We check that in this case `E` really does have rational `5`-isogenies:: sage: [phi.degree() for phi in E.isogenies_prime_degree()] [5, 5] Over an extension field:: sage: E3 = E.change_ring(CyclotomicField(3)) sage: possible_isogeny_degrees(E3) [5] sage: [phi.degree() for phi in E3.isogenies_prime_degree()] [5, 5] For curves with CM by a quadratic order of class number greater than `1`, we use the structure of the class group to only give one prime in each ideal class:: sage: pol = PolynomialRing(QQ,'x')([1,-3,5,-5,5,-3,1]) sage: L.<a> = NumberField(pol) sage: j = hilbert_class_polynomial(-23).roots(L,multiplicities=False)[0] sage: E = EllipticCurve(j=j) sage: from sage.schemes.elliptic_curves.isogeny_class import possible_isogeny_degrees sage: possible_isogeny_degrees(E, verbose=True) CM case, discriminant = -23 initial primes: {2} upward primes: {} downward ramified primes: {} downward split primes: {2, 3} downward inert primes: {5} primes generating the class group: [2] Complete set of primes: {2, 3, 5} [2, 3, 5] """ if E.has_cm(): d = E.cm_discriminant() if verbose: print("CM case, discriminant = %s" % d) from sage.libs.pari.all import pari from sage.sets.all import Set from sage.rings.arith import kronecker_symbol n = E.base_field().absolute_degree() if not E.has_rational_cm(): n *= 2 divs = n.divisors() data = pari(d).quadclassunit() # This has 4 components: the class number, class group # structure (ignored), class group generators (as quadratic # forms) and regulator (=1 since d<0, ignored). h = data[0].sage() # We must have 2*h dividing n, and will need the quotient so # see if the j-invariants of any proper sub-orders could lie # in the same field n_over_2h = n//(2*h) # Collect possible primes. First put in 2, and also 3 for # discriminant -3 (special case because of units): L = Set([ZZ(2), ZZ(3)]) if d==-3 else Set([ZZ(2)]) if verbose: print ("initial primes: %s" % L) # Step 1: "vertical" primes l such that the isogenous curve # has CM by an order whose index is l or 1/l times the index # of the order O of discriminant d. The latter case can only # happen when l^2 divides d. # Compute the ramified primes ram_l = d.odd_part().prime_factors() # if the CM is not rational we include all ramified primes, # which is simpler than using the class group later: if not E.has_rational_cm(): L1 = Set(ram_l) L += L1 if verbose: print ("ramified primes: %s" % L1) else: # Find the "upward" primes (index divided by l): L1 = Set([l for l in ram_l if d.valuation(l)>1]) L += L1 if verbose: print ("upward primes: %s" % L1) # Find the "downward" primes (index multiplied by l, class # number multiplied by l-kronecker_symbol(d,l)): # (a) ramified primes; the suborder has class number l*h, so l # must divide n/2h: L1 = Set([l for l in ram_l if l.divides(n_over_2h)]) L += L1 if verbose: print ("downward ramified primes: %s" % L1) # (b) split primes; the suborder has class number (l-1)*h, so # l-1 must divide n/2h: L1 = Set([lm1+1 for lm1 in divs if (lm1+1).is_prime() and kronecker_symbol(d,lm1+1)==+1]) L += L1 if verbose: print ("downward split primes: %s" % L1) # (c) inert primes; the suborder has class number (l+1)*h, so # l+1 must divide n/2h: L1 = Set([lp1-1 for lp1 in divs if (lp1-1).is_prime() and kronecker_symbol(d,lp1-1)==-1]) L += L1 if verbose: print ("downward inert primes: %s" % L1) # Now find primes represented by each form of discriminant d. # In the rational CM case, we use all forms associated to # generators of the class group, otherwise only forms of order # 2: if E.has_rational_cm(): from sage.quadratic_forms.binary_qf import BinaryQF Qs = [BinaryQF(list(q)) for q in data[2]] L1 = [Q.small_prime_value() for Q in Qs] if verbose: print("primes generating the class group: %s" % L1) L += Set(L1) # Return sorted list if verbose: print("Complete set of primes: %s" % L) return sorted(list(L)) # Non-CM case if verbose: print("Non-CM case, using Galois representation") return E.galois_representation().reducible_primes()
def is_a_splitting(S1, S2, n): """ INPUT: S1, S2 are disjoint sublists partitioning [1, 2, ..., n-1] n1 is an integer OUTPUT: a, b where a is True or False, depending on whether S1, S2 form a "splitting" of n (ie, if there is a b1 such that b\*S1=S2 (point-wise multiplication mod n), and b is a splitting (if a = True) or 0 (if a = False) Splittings are useful for computing idempotents in the quotient ring `Q = GF(q)[x]/(x^n-1)`. For EXAMPLES:: sage: from sage.coding.code_constructions import is_a_splitting sage: n = 11; q = 3 sage: C = cyclotomic_cosets(q,n); 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, 2) 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 = CyclicCodeFromGeneratingPolynomial(n,i1) sage: C2 = CyclicCodeFromGeneratingPolynomial(n,1-i2) sage: C1.dual_code() == C2 True This is a special case of Theorem 6.4.3 in [HP]_. """ if Set(S1).union(Set(S2)) <> Set(range(1, n)): raise TypeError, "Lists must partition [1,2,...,n-1]." if n < 3: raise TypeError, "Input %s must be > 2." % n for b in range(2, n): SS1 = Set([b * x % n for x in S1]) SS2 = Set([b * x % n for x in S2]) if SS1 == Set(S2) and SS2 == Set(S1): return True, b return False, 0
def local_good_density_congruence_even(self, m, Zvec, NZvec): """ Finds the Good-type local density of Q representing `m` at `p=2`. (Assuming Q is given in local diagonal form.) The additional congruence condition arguments Zvec and NZvec can be either a list of indices or None. Zvec = [] is equivalent to Zvec = None which both impose no additional conditions, but NZvec = [] returns no solutions always while NZvec = None imposes no additional condition. WARNING: Here the indices passed in Zvec and NZvec represent indices of the solution vector `x` of Q(`x`) = `m (mod p^k)`, and *not* the Jordan components of Q. They therefore are required (and assumed) to include either all or none of the indices of a given Jordan component of Q. This is only important when `p=2` since otherwise all Jordan blocks are 1x1, and so there the indices and Jordan blocks coincide. TO DO: Add type checking for Zvec, NZvec, and that Q is in local normal form. INPUT: Q -- quadratic form assumed to be block diagonal and 2-integral `p` -- a prime number `m` -- an integer Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3]) sage: Q.local_good_density_congruence_even(1, None, None) 1 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Q.local_good_density_congruence_even(1, None, None) 1 sage: Q.local_good_density_congruence_even(2, None, None) 3/2 sage: Q.local_good_density_congruence_even(3, None, None) 1 sage: Q.local_good_density_congruence_even(4, None, None) 1/2 :: sage: Q = QuadraticForm(ZZ, 4, range(10)) sage: Q[0,0] = 5 sage: Q[1,1] = 10 sage: Q[2,2] = 15 sage: Q[3,3] = 20 sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 5 1 2 3 ] [ * 10 5 6 ] [ * * 15 8 ] [ * * * 20 ] sage: Q.theta_series(20) 1 + 2*q^5 + 2*q^10 + 2*q^14 + 2*q^15 + 2*q^16 + 2*q^18 + O(q^20) sage: Q.local_normal_form(2) Quadratic form in 4 variables over Integer Ring with coefficients: [ 0 1 0 0 ] [ * 0 0 0 ] [ * * 0 1 ] [ * * * 0 ] sage: Q.local_good_density_congruence_even(1, None, None) 3/4 sage: Q.local_good_density_congruence_even(2, None, None) 1 sage: Q.local_good_density_congruence_even(5, None, None) 3/4 """ n = self.dim() ## Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") ## Find the indices of x for which the associated Jordan blocks are non-zero mod 8 TO DO: Move this to special Jordan block code separately! ## ------------------------------------------------------------------------------- Not8vec = [] for i in range(n): ## DIAGNOSTIC verbose(" i = " + str(i)) verbose(" n = " + str(n)) verbose(" Not8vec = " + str(Not8vec)) nz_flag = False ## Check if the diagonal entry isn't divisible 8 if ((self[i,i] % 8) != 0): nz_flag = True ## Check appropriate off-diagonal entries aren't divisible by 8 else: ## Special check for first off-diagonal entry if ((i == 0) and ((self[i,i+1] % 8) != 0)): nz_flag = True ## Special check for last off-diagonal entry elif ((i == n-1) and ((self[i-1,i] % 8) != 0)): nz_flag = True ## Check for the middle off-diagonal entries else: if ( (i > 0) and (i < n-1) and (((self[i,i+1] % 8) != 0) or ((self[i-1,i] % 8) != 0)) ): nz_flag = True ## Remember the (vector) index if it's not part of a Jordan block of norm divisible by 8 if nz_flag: Not8vec += [i] ## Compute the number of Good-type solutions mod 8: ## ------------------------------------------------ ## Setup the indexing sets for additional zero congruence solutions Q_Not8 = self.extract_variables(Not8vec) Not8 = Set(Not8vec) Is8 = Set(range(n)) - Not8 Z = Set(Zvec) Z_Not8 = Not8.intersection(Z) Z_Is8 = Is8.intersection(Z) Is8_minus_Z = Is8 - Z_Is8 ## DIAGNOSTIC verbose("Z = " + str(Z)) verbose("Z_Not8 = " + str(Z_Not8)) verbose("Z_Is8 = " + str(Z_Is8)) verbose("Is8_minus_Z = " + str(Is8_minus_Z)) ## Take cases on the existence of additional non-zero congruence conditions (mod 2) if NZvec is None: total = (4 ** len(Z_Is8)) * (8 ** len(Is8_minus_Z)) \ * Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(Z_Not8), None) else: ZNZ = Z + Set(NZvec) ZNZ_Not8 = Not8.intersection(ZNZ) ZNZ_Is8 = Is8.intersection(ZNZ) Is8_minus_ZNZ = Is8 - ZNZ_Is8 ## DIAGNOSTIC verbose("ZNZ = " + str(ZNZ)) verbose("ZNZ_Not8 = " + str(ZNZ_Not8)) verbose("ZNZ_Is8 = " + str(ZNZ_Is8)) verbose("Is8_minus_ZNZ = " + str(Is8_minus_ZNZ)) total = (4 ** len(Z_Is8)) * (8 ** len(Is8_minus_Z)) \ * Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(Z_Not8), None) \ - (4 ** len(ZNZ_Is8)) * (8 ** len(Is8_minus_ZNZ)) \ * Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(ZNZ_Not8), None) ## DIAGNOSTIC verbose("total = " + str(total)) ## Return the associated Good-type representation density good_density = QQ(total) / 8**(n-1) return good_density
def set(self): r""" The set of values of the probability space taking possibly nonzero probability (a subset of the domain). """ return Set(self.function().keys())
def __classcall_private__(cls, p): r""" This function tries to recognize the input (it can be either a list or a tuple of pairs, or a fix-point free involution given as a list or as a permutation), constructs the parent (enumerated set of PerfectMatchings of the ground set) and calls the __init__ function to construct our object. EXAMPLES:: sage: m=PerfectMatching([('a','e'),('b','c'),('d','f')]);m PerfectMatching [('a', 'e'), ('b', 'c'), ('d', 'f')] sage: isinstance(m,PerfectMatching) True sage: n=PerfectMatching([3, 8, 1, 7, 6, 5, 4, 2]);n PerfectMatching [(1, 3), (2, 8), (4, 7), (5, 6)] sage: n.parent() Set of perfect matchings of {1, 2, 3, 4, 5, 6, 7, 8} sage: PerfectMatching([(1, 4), (2, 3), (5, 6)]).is_non_crossing() True The function checks that the given list or permutation is a valid perfect matching (i.e. a list of pairs with pairwise disjoint elements or a fixpoint-free involution) and raises a ValueError otherwise: sage: PerfectMatching([(1, 2, 3), (4, 5)]) Traceback (most recent call last): ... ValueError: [(1, 2, 3), (4, 5)] is not a valid perfect matching: all elements of the list must be pairs If you know your datas are in a good format, use directly ``PerfectMatchings(objects)(data)``. TESTS:: sage: m=PerfectMatching([('a','e'),('b','c'),('d','f')]) sage: TestSuite(m).run() sage: m=PerfectMatching([]) sage: TestSuite(m).run() sage: PerfectMatching(6) Traceback (most recent call last): ... ValueError: cannot convert p (= 6) to a PerfectMatching sage: PerfectMatching([(1,2,3)]) Traceback (most recent call last): ... ValueError: [(1, 2, 3)] is not a valid perfect matching: all elements of the list must be pairs sage: PerfectMatching([(1,1)]) Traceback (most recent call last): ... ValueError: [(1, 1)] is not a valid perfect matching: there are some repetitions sage: PerfectMatching(Permutation([4,2,1,3])) Traceback (most recent call last): ... ValueError: The permutation p (= [4, 2, 1, 3]) is not a fixed point free involution """ # we have to extract from the argument p the set of objects of the # matching and the list of pairs. # First case: p is a list (resp tuple) of lists (resp tuple). if (isinstance(p, list) or isinstance(p, tuple)) and (all( [isinstance(x, list) or isinstance(x, tuple) for x in p])): objects = Set(flatten(p)) data = (map(tuple, p)) #check if the data are correct if not all([len(t) == 2 for t in data]): raise ValueError, ("%s is not a valid perfect matching:\n" "all elements of the list must be pairs" % p) if len(objects) < 2 * len(data): raise ValueError, ("%s is not a valid perfect matching:\n" "there are some repetitions" % p) # Second case: p is a permutation or a list of integers, we have to # check if it is a fix-point-free involution. elif ((isinstance(p, list) and all( map(lambda x: (isinstance(x, Integer) or isinstance(x, int)), p))) or isinstance(p, Permutation_class)): p = Permutation(p) n = len(p) if not (p.cycle_type() == [2 for i in range(n // 2)]): raise ValueError, ("The permutation p (= %s) is not a " "fixed point free involution" % p) objects = Set(range(1, n + 1)) data = p.to_cycles() # Third case: p is already a perfect matching, we return p directly elif isinstance(p, PerfectMatching): return p else: raise ValueError, "cannot convert p (= %s) to a PerfectMatching" % p # Finally, we create the parent and the element using the element # class of the parent. Note: as this function is private, when we # create an object via parent.element_class(...), __init__ is directly # executed and we do not have an infinite loop. return PerfectMatchings(objects)(data)
def block_design_checker(self, t, v, k, lmbda, type=None): """ This is *not* a wrapper for GAP Design's IsBlockDesign. The GAP Design function IsBlockDesign http://www.gap-system.org/Manuals/pkg/design/htm/CHAP004.htm apparently simply checks the record structure and no mathematical properties. Instead, the function below checks some necessary (but not sufficient) "easy" identities arising from the identity. INPUT: - ``t`` - the t as in "t-design" - ``v`` - the number of points - ``k`` - the number of blocks incident to a point - ``lmbda`` - each t-tuple of points should be incident with lmbda blocks - ``type`` - can be 'simple' or 'binary' or 'connected' Depending on the option, this wraps IsBinaryBlockDesign, IsSimpleBlockDesign, or IsConnectedBlockDesign. - Binary: no block has a repeated element. - Simple: no block is repeated. - Connected: its incidence graph is a connected graph. WARNING: This is very fast but can return false positives. EXAMPLES:: sage: from sage.combinat.designs.block_design import BlockDesign sage: BD = BlockDesign(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]) sage: BD.parameters() (2, 7, 3, 1) sage: BD.block_design_checker(2, 7, 3, 1) True sage: BD.block_design_checker(2, 7, 3, 1,"binary") True sage: BD.block_design_checker(2, 7, 3, 1,"connected") True sage: BD.block_design_checker(2, 7, 3, 1,"simple") True """ from sage.sets.set import Set if not(v == len(self.points())): return False b = lmbda*binomial(v,t)/binomial(k,t) r = int(b*k/v) if not(b == len(self.blocks())): return False if not(ZZ(v).divides(b*k)): return False A = self.incidence_matrix() #k = sum(A.columns()[0]) #r = sum(A.rows()[0]) for i in range(b): if not(sum(A.columns()[i]) == k): return False for i in range(v): if not(sum(A.rows()[i]) == r): return False gD = self._gap_() if type==None: return True if type=="binary": for b in self.blocks(): if len(b)!=len(Set(b)): return False return True if type=="simple": B = self.blocks() for b in B: if B.count(b)>1: return False return True if type=="connected": Gamma = self.incidence_graph() if Gamma.is_connected(): return True else: return False
def _LKB_matrix_(self, braid, variab): """ Compute the Lawrence-Krammer-Bigelow representation matrix. The variables of the matrix must be given. This actual computation is done in this helper method for caching purposes. INPUT: - ``braid`` -- tuple of integers. The Tietze list of the braid. - ``variab`` -- string. the names of the variables that will appear in the matrix. They must be given as a string, separated by a comma OUTPUT: The LKB matrix of the braid, with respect to the variables. TESTS:: sage: B=BraidGroup(3) sage: B._LKB_matrix_((2, 1, 2), 'x, y') [ 0 -x^4*y + x^3*y -x^4*y] [ 0 -x^3*y 0] [ -x^2*y x^3*y - x^2*y 0] sage: B._LKB_matrix_((1, 2, 1), 'x, y') [ 0 -x^4*y + x^3*y -x^4*y] [ 0 -x^3*y 0] [ -x^2*y x^3*y - x^2*y 0] sage: B._LKB_matrix_((-1, -2, -1, 2, 1, 2), 'x, y') [1 0 0] [0 1 0] [0 0 1] """ n = self.strands() if len(braid) > 1: A = self._LKB_matrix_(braid[:1], variab) for i in braid[1:]: A = A * self._LKB_matrix_((i, ), variab) return A l = list(Set(range(n)).subsets(2)) R = LaurentPolynomialRing(IntegerRing(), variab) q = R.gens()[0] t = R.gens()[1] if len(braid) == 0: return identity_matrix(R, len(l), sparse=True) A = matrix(R, len(l), sparse=True) if braid[0] > 0: i = braid[0] - 1 for m in range(len(l)): j = min(l[m]) k = max(l[m]) if i == j - 1: A[l.index(Set([i, k])), m] = q A[l.index(Set([i, j])), m] = q * q - q A[l.index(Set([j, k])), m] = 1 - q elif i == j and not j == k - 1: A[l.index(Set([j, k])), m] = 0 A[l.index(Set([j + 1, k])), m] = 1 elif k - 1 == i and not k - 1 == j: A[l.index(Set([j, i])), m] = q A[l.index(Set([j, k])), m] = 1 - q A[l.index(Set([i, k])), m] = (1 - q) * q * t elif i == k: A[l.index(Set([j, k])), m] = 0 A[l.index(Set([j, k + 1])), m] = 1 elif i == j and j == k - 1: A[l.index(Set([j, k])), m] = -t * q * q else: A[l.index(Set([j, k])), m] = 1 return A else: i = -braid[0] - 1 for m in range(len(l)): j = min(l[m]) k = max(l[m]) if i == j - 1: A[l.index(Set([j - 1, k])), m] = 1 elif i == j and not j == k - 1: A[l.index(Set([j + 1, k])), m] = q**(-1) A[l.index(Set([j, k])), m] = 1 - q**(-1) A[l.index(Set([j, j + 1])), m] = t**(-1) * q**(-1) - t**(-1) * q**(-2) elif k - 1 == i and not k - 1 == j: A[l.index(Set([j, k - 1])), m] = 1 elif i == k: A[l.index(Set([j, k + 1])), m] = q**(-1) A[l.index(Set([j, k])), m] = 1 - q**(-1) A[l.index(Set([k, k + 1])), m] = -q**(-1) + q**(-2) elif i == j and j == k - 1: A[l.index(Set([j, k])), m] = -t**(-1) * q**(-2) else: A[l.index(Set([j, k])), m] = 1 return A
def line_covectors(self): r""" Return a dictionary with pairs of normal vectors as keys and a list containing the line covectors as value (up to multiplication by -1). The line covectors encode the sign pattern of all the vectors in ``self`` on the intersection of two hyperplanes for vector configurations in dimension 3. OUTPUT a dictionary. EXAMPLES:: sage: from cn_hyperarr.vector_classes import * sage: vc = VectorConfiguration([[1,0,0],[0,1,0],[0,0,1],[1,1,0],[0,1,1],[1,1,1]]) sage: vc.line_covectors() {{4, 5}: ((0, 1, -1, 1, 0, 0), (0, -1, 1, -1, 0, 0)), {3, 5}: ((1, -1, 0, 0, -1, 0), (-1, 1, 0, 0, 1, 0)), {3, 4}: ((1, -1, 1, 0, 0, 1), (-1, 1, -1, 0, 0, -1)), {2, 5}: ((-1, 1, 0, 0, 1, 0), (1, -1, 0, 0, -1, 0)), {2, 4}: ((-1, 0, 0, -1, 0, -1), (1, 0, 0, 1, 0, 1)), {2, 3}: ((-1, 1, 0, 0, 1, 0), (1, -1, 0, 0, -1, 0)), {1, 5}: ((1, 0, -1, 1, -1, 0), (-1, 0, 1, -1, 1, 0)), {1, 4}: ((1, 0, 0, 1, 0, 1), (-1, 0, 0, -1, 0, -1)), {1, 3}: ((0, 0, -1, 0, -1, -1), (0, 0, 1, 0, 1, 1)), {1, 2}: ((1, 0, 0, 1, 0, 1), (-1, 0, 0, -1, 0, -1)), {0, 5}: ((0, -1, 1, -1, 0, 0), (0, 1, -1, 1, 0, 0)), {0, 4}: ((0, -1, 1, -1, 0, 0), (0, 1, -1, 1, 0, 0)), {0, 3}: ((0, 0, 1, 0, 1, 1), (0, 0, -1, 0, -1, -1)), {0, 2}: ((0, -1, 0, -1, -1, -1), (0, 1, 0, 1, 1, 1)), {0, 1}: ((0, 0, 1, 0, 1, 1), (0, 0, -1, 0, -1, -1))} TESTS: The ambient dimension should be 3:: sage: vc = VectorConfiguration([[1], [2], [3]]) sage: vc.line_covectors() Traceback (most recent call last): ... AssertionError: The ambient dimension should be 3 TODO: Make it work in higher dimension. """ assert self.ambient_dimension( ) == 3, "The ambient dimension should be 3" nb_hyperpl = self.n_vectors() hic_dict = {} for index_1, index_2 in combinations(range(nb_hyperpl), 2): new_key = Set([index_1, index_2]) vector_1 = self[index_1] vector_2 = self[index_2] intersection = vector_1.cross_product(vector_2) new_value = list() for index in range(nb_hyperpl): dot_prod = intersection.dot_product(self[index]) if dot_prod == 0: new_value.append(0) elif dot_prod < 0: new_value.append(-1) else: new_value.append(1) hic1 = Covector(new_value) hic2 = Covector([-v for v in new_value]) hic_dict[new_key] = tuple([hic1, hic2]) return hic_dict
def forcing_oriented_graph(self): r""" Return the forcing oriented graph for a 3-dimensional vector configuration. This is the directed graph on shards. The hyperplane arrangement with base region corresponding to the acyclic vector configuration is congruence normal if and only if the directed graph on shards is acyclic. OUTPUT: An oriented graph EXAMPLES:: sage: from cn_hyperarr.vector_classes import * sage: vc = VectorConfiguration([[1,0,0],[0,1,0],[0,0,1],[1,1,0],[0,1,1],[1,1,1]]) sage: fog = vc.forcing_oriented_graph(); fog Digraph on 11 vertices sage: fog.num_edges() 24 sage: tau = AA((1+sqrt(5))/2) sage: ncn = [[2*tau+1,2*tau,tau],[2*tau+2,2*tau+1,tau+1],[1,1,1],[tau+1,tau+1,tau],[2*tau,2*tau,tau],[tau+1,tau+1,1],[1,1,0],[0,1,0],[1,0,0],[tau+1,tau,tau]] sage: ncn_conf = VectorConfiguration(ncn); sage: ncn_fog = ncn_conf.forcing_oriented_graph(); ncn_fog Digraph on 29 vertices sage: ncn_fog.num_edges() 96 sage: ncn_fog.is_directed_acyclic() False TESTS: The ambient dimension should be 3:: sage: vc = VectorConfiguration([[1],[2],[3]]) sage: vc.forcing_oriented_graph() Traceback (most recent call last): ... AssertionError: The ambient dimension is not 3 """ nb_pts = self.n_vectors() shard_covectors = self.shard_covectors() hic = self.line_covectors() G = DiGraph() for shard_cov in shard_covectors: G.add_vertex(shard_cov) shard_index = shard_cov.index(0) potential_shards = (v for v in G.vertices() if v.index(0) != shard_index) for other_shard_cov in potential_shards: other_shard_index = other_shard_cov.index(0) # Get the potential ordering of forcing: forcing = True if other_shard_cov[shard_index] in [1, -1]: # shard_cov -> other_shard_cov first_cov, second_cov = shard_cov, other_shard_cov elif shard_cov[other_shard_index] in [1, -1]: # other_shard_cov -> shard_cov first_cov, second_cov = other_shard_cov, shard_cov else: forcing = False if forcing: # A forcing takes place hic1, hic2 = hic[Set([shard_index, other_shard_index])] sic1 = hic1 & shard_cov & other_shard_cov sic2 = hic2 & shard_cov & other_shard_cov if sic1 == hic1 or sic2 == hic2: G.add_edge(first_cov, second_cov) return G.copy(immutable=True)
def _non_surjective(E, patience=100): r""" Returns a list of primes `p` including all primes for which the mod-`p` representation might not be surjective. INPUT: - ``E`` - EllipticCurve (over a number field). - ``A`` - int (a bound on the number of traces of Frobenius to use while trying to prove surjectivity). OUTPUT: - ``list`` - A list of primes where mod-`p` representation is very likely not surjective. At any prime not in this list, the representation is definitely surjective. If E has CM, a ValueError is raised. EXAMPLES:: sage: K = NumberField(x**2 - 29, 'a'); a = K.gen() sage: E = EllipticCurve([1, 0, ((5 + a)/2)**2, 0, 0]) sage: sage.schemes.elliptic_curves.gal_reps_number_field._non_surjective(E) # See Section 5.10 of [Serre72]. [3, 5, 29] sage: E = EllipticCurve_from_j(1728).change_ring(K) # CM sage: sage.schemes.elliptic_curves.gal_reps_number_field._non_surjective(E) Traceback (most recent call last): ... ValueError: The curve E should not have CM. """ E = _over_numberfield(E) K = E.base_field() exceptional_primes = [2, 3, 5, 7, 11, 13, 17, 19] # The possible primes l unramified in K/QQ for which the image of the mod l # Galois representation could be contained in an exceptional subgroup. # Find the places of additive reduction. SA = [] for P, n in E.conductor().factor(): if n > 1: SA.append(P) # TODO: All we really require is a list of primes that include all # primes at which E has additive reduction. Perhaps we can speed # up things by doing something less time-consuming here that produces # a list with some extra terms? (Of course, the longer this list is, # the slower the rest of the computation is, so it is not clear that # this would help...) char = lambda P: P.smallest_integer( ) # cheaper than constructing the residue field bad_primes = exceptional_primes bad_primes += [char(P) for P in SA] bad_primes += K.discriminant().prime_factors() bad_primes += _semistable_reducible_primes(E) bad_primes += _possible_normalizers(E, SA) bad_primes = list(Set(bad_primes)) return _exceptionals(E, bad_primes, patience)
def coefficients_from_j(j, minimal_twist=True): """ Return Weierstrass coefficients `(a_1, a_2, a_3, a_4, a_6)` for an elliptic curve with given `j`-invariant. INPUT: See :func:`EllipticCurve_from_j`. EXAMPLES:: sage: from sage.schemes.elliptic_curves.constructor import coefficients_from_j sage: coefficients_from_j(0) [0, 0, 1, 0, 0] sage: coefficients_from_j(1728) [0, 0, 0, -1, 0] sage: coefficients_from_j(1) [1, 0, 0, 36, 3455] The ``minimal_twist`` parameter (ignored except over `\\QQ` and True by default) controls whether or not a minimal twist is computed:: sage: coefficients_from_j(100) [0, 1, 0, 3392, 307888] sage: coefficients_from_j(100, minimal_twist=False) [0, 0, 0, 488400, -530076800] """ try: K = j.parent() except AttributeError: K = rings.RationalField() if K not in _Fields: K = K.fraction_field() char = K.characteristic() if char == 2: if j == 0: return Sequence([0, 0, 1, 0, 0], universe=K) else: return Sequence([1, 0, 0, 0, 1 / j], universe=K) if char == 3: if j == 0: return Sequence([0, 0, 0, 1, 0], universe=K) else: return Sequence([0, j, 0, 0, -j**2], universe=K) if K is rings.RationalField(): # we construct the minimal twist, i.e. the curve with minimal # conductor with this j_invariant: if j == 0: return Sequence([0, 0, 1, 0, 0], universe=K) # 27a3 if j == 1728: return Sequence([0, 0, 0, -1, 0], universe=K) # 32a2 if not minimal_twist: k = j - 1728 return Sequence([0, 0, 0, -3 * j * k, -2 * j * k**2], universe=K) n = j.numerator() m = n - 1728 * j.denominator() a4 = -3 * n * m a6 = -2 * n * m**2 # Now E=[0,0,0,a4,a6] has j-invariant j=n/d from sage.sets.set import Set for p in Set(n.prime_divisors() + m.prime_divisors()): e = min(a4.valuation(p) // 2, a6.valuation(p) // 3) if e > 0: p = p**e a4 /= p**2 a6 /= p**3 # Now E=[0,0,0,a4,a6] is minimal at all p != 2,3 tw = [-1, 2, -2, 3, -3, 6, -6] E1 = EllipticCurve([0, 0, 0, a4, a6]) Elist = [E1] + [E1.quadratic_twist(t) for t in tw] Elist.sort(key=lambda E: E.conductor()) return Sequence(Elist[0].ainvs()) # defaults for all other fields: if j == 0: return Sequence([0, 0, 0, 0, 1], universe=K) if j == 1728: return Sequence([0, 0, 0, 1, 0], universe=K) k = j - 1728 return Sequence([0, 0, 0, -3 * j * k, -2 * j * k**2], universe=K)
def isogeny_bound(self, A=100): r""" Returns a list of primes `p` including all primes for which the image of the mod-`p` representation is contained in a Borel. .. NOTE:: For the actual list of primes `p` at which the representation is reducible see :meth:`reducible_primes()`. INPUT: - ``A`` - int (a bound on the number of traces of Frobenius to use while trying to prove the mod-`p` representation is not contained in a Borel). OUTPUT: - ``list`` - A list of primes which contains (but may not be equal to) all `p` for which the image of the mod-`p` representation is contained in a Borel subgroup. At any prime not in this list, the image is definitely not contained in a Borel. If E has `CM` defined over `K`, the list [0] is returned. EXAMPLES:: sage: K = NumberField(x**2 - 29, 'a'); a = K.gen() sage: E = EllipticCurve([1, 0, ((5 + a)/2)**2, 0, 0]) sage: rho = E.galois_representation() sage: rho.isogeny_bound() # See Section 5.10 of [Serre72]. [3, 5] sage: K = NumberField(x**2 + 1, 'a') sage: EllipticCurve_from_j(K(1728)).galois_representation().isogeny_bound() # CM over K [0] sage: EllipticCurve_from_j(K(0)).galois_representation().isogeny_bound() # CM NOT over K [2, 3] sage: E = EllipticCurve_from_j(K(2268945/128)) # c.f. [Sutherland12] sage: E.galois_representation().isogeny_bound() # No 7-isogeny, but... [7] """ E = _over_numberfield(self.E) K = E.base_field() char = lambda P: P.smallest_integer( ) # cheaper than constructing the residue field # semistable reducible primes (function raises an error for CM curves) try: bad_primes = _semistable_reducible_primes(E) except ValueError: return [0] # primes of additive reduction bad_primesK = (K.ideal(E.c4()) + K.ideal(E.discriminant())).prime_factors() bad_primes += [char(P) for P in bad_primesK] # ramified primes bad_primes += K.absolute_discriminant().prime_factors() # remove repeats: bad_primes = list(Set(bad_primes)) return _maybe_borels(E, bad_primes, A)
def isogeny_bound(self, A=100): r""" Returns a list of primes `p` including all primes for which the image of the mod-`p` representation is contained in a Borel. .. NOTE:: For the actual list of primes `p` at which the representation is reducible see :meth:`reducible_primes()`. INPUT: - ``A`` - int (a bound on the number of traces of Frobenius to use while trying to prove the mod-`p` representation is not contained in a Borel). OUTPUT: - ``list`` - A list of primes which contains (but may not be equal to) all `p` for which the image of the mod-`p` representation is contained in a Borel subgroup. At any prime not in this list, the image is definitely not contained in a Borel. If E has `CM` defined over `K`, the list [0] is returned. EXAMPLES:: sage: K = NumberField(x**2 - 29, 'a'); a = K.gen() sage: E = EllipticCurve([1, 0, ((5 + a)/2)**2, 0, 0]) sage: rho = E.galois_representation() sage: rho.isogeny_bound() # See Section 5.10 of [Serre72]. [3, 5] sage: K = NumberField(x**2 + 1, 'a') sage: EllipticCurve_from_j(K(1728)).galois_representation().isogeny_bound() # CM over K [0] sage: EllipticCurve_from_j(K(0)).galois_representation().isogeny_bound() # CM NOT over K [2, 3] sage: E = EllipticCurve_from_j(K(2268945/128)) # c.f. [Sutherland12] sage: E.galois_representation().isogeny_bound() # No 7-isogeny, but... [7] For curves with rational CM, there are infinitely many primes `p` for which the mod-`p` representation is reducible, and [0] is returned:: sage: K.<a> = NumberField(x^2-x+1) sage: E = EllipticCurve([0,0,0,0,a]) sage: E.has_rational_cm() True sage: rho = E.galois_representation() sage: rho.isogeny_bound() [0] An example (an elliptic curve with everywhere good reduction over an imaginary quadratic field with quite large discriminant), which failed until fixed at :trac:`21776`:: sage: K.<a> = NumberField(x^2 - x + 112941801) sage: E = EllipticCurve([a+1,a-1,a,-23163076*a + 266044005933275,57560769602038*a - 836483958630700313803]) sage: E.conductor().norm() 1 sage: GR = E.galois_representation() sage: GR.isogeny_bound() [] """ if self.E.has_rational_cm(): return [0] E = _over_numberfield(self.E) K = E.base_field() char = lambda P: P.smallest_integer( ) # cheaper than constructing the residue field # semistable reducible primes (we are now not in the CM case) bad_primes = _semistable_reducible_primes(E) # primes of additive reduction bad_primesK = (K.ideal(E.c4()) + K.ideal(E.discriminant())).prime_factors() bad_primes += [char(P) for P in bad_primesK] # ramified primes bad_primes += K.absolute_discriminant().prime_factors() # remove repeats: bad_primes = list(Set(bad_primes)) return _maybe_borels(E, bad_primes, A)
def _compute(self, verbose=False): """ Computes the list of curves, the matrix and prime-degree isogenies. EXAMPLES:: sage: K.<i> = QuadraticField(-1) sage: E = EllipticCurve(K, [0,0,0,0,1]) sage: C = E.isogeny_class() sage: C2 = C.copy() sage: C2._mat sage: C2._compute() sage: C2._mat [1 3 6 2] [3 1 2 6] [6 2 1 3] [2 6 3 1] sage: C2._compute(verbose=True) possible isogeny degrees: [2, 3] -actual isogeny degrees: {2, 3} -added curve #1 (degree 2)... -added tuple [0, 1, 2]... -added tuple [1, 0, 2]... -added curve #2 (degree 3)... -added tuple [0, 2, 3]... -added tuple [2, 0, 3]...... relevant degrees: [2, 3]... -now completing the isogeny class... -processing curve #1... -added tuple [1, 0, 2]... -added tuple [0, 1, 2]... -added curve #3... -added tuple [1, 3, 3]... -added tuple [3, 1, 3]... -processing curve #2... -added tuple [2, 3, 2]... -added tuple [3, 2, 2]... -added tuple [2, 0, 3]... -added tuple [0, 2, 3]... -processing curve #3... -added tuple [3, 2, 2]... -added tuple [2, 3, 2]... -added tuple [3, 1, 3]... -added tuple [1, 3, 3]...... isogeny class has size 4 Sorting permutation = {0: 1, 1: 2, 2: 0, 3: 3} Matrix = [1 3 6 2] [3 1 2 6] [6 2 1 3] [2 6 3 1] TESTS: Check that :trac:`19030` is fixed (codomains of reverse isogenies were wrong):: sage: K.<i> = NumberField(x^2+1) sage: E = EllipticCurve([1, i + 1, 1, -72*i + 8, 95*i + 146]) sage: C = E.isogeny_class() sage: curves = C.curves sage: isos = C.isogenies() sage: isos[0][3].codomain() == curves[3] True """ from sage.schemes.elliptic_curves.ell_curve_isogeny import fill_isogeny_matrix, unfill_isogeny_matrix from sage.matrix.all import MatrixSpace from sage.sets.set import Set self._maps = None E = self.E.global_minimal_model(semi_global=True) degs = possible_isogeny_degrees(E) if verbose: import sys sys.stdout.write(" possible isogeny degrees: %s" % degs) sys.stdout.flush() isogenies = E.isogenies_prime_degree(degs) if verbose: sys.stdout.write(" -actual isogeny degrees: %s" % Set([phi.degree() for phi in isogenies])) sys.stdout.flush() # Add all new codomains to the list and collect degrees: curves = [E] ncurves = 1 degs = [] # tuples (i,j,l,phi) where curve i is l-isogenous to curve j via phi tuples = [] def add_tup(t): for T in [t, [t[1],t[0],t[2],0]]: if not T in tuples: tuples.append(T) if verbose: sys.stdout.write(" -added tuple %s..." % T[:3]) sys.stdout.flush() for phi in isogenies: E2 = phi.codomain() d = ZZ(phi.degree()) if not any([E2.is_isomorphic(E3) for E3 in curves]): curves.append(E2) if verbose: sys.stdout.write(" -added curve #%s (degree %s)..." % (ncurves,d)) sys.stdout.flush() add_tup([0,ncurves,d,phi]) ncurves += 1 if not d in degs: degs.append(d) if verbose: sys.stdout.write("... relevant degrees: %s..." % degs) sys.stdout.write(" -now completing the isogeny class...") sys.stdout.flush() i = 1 while i < ncurves: E1 = curves[i] if verbose: sys.stdout.write(" -processing curve #%s..." % i) sys.stdout.flush() isogenies = E1.isogenies_prime_degree(degs) for phi in isogenies: E2 = phi.codomain() d = phi.degree() js = [j for j,E3 in enumerate(curves) if E2.is_isomorphic(E3)] if js: # seen codomain already -- up to isomorphism j = js[0] if phi.codomain()!=curves[j]: iso = E2.isomorphism_to(curves[j]) phi.set_post_isomorphism(iso) assert phi.domain()==curves[i] and phi.codomain()==curves[j] add_tup([i,j,d,phi]) else: curves.append(E2) if verbose: sys.stdout.write(" -added curve #%s..." % ncurves) sys.stdout.flush() add_tup([i,ncurves,d,phi]) ncurves += 1 i += 1 if verbose: print("... isogeny class has size %s" % ncurves) # key function for sorting if E.has_rational_cm(): key_function = lambda E: (-E.cm_discriminant(),flatten([list(ai) for ai in E.ainvs()])) else: key_function = lambda E: flatten([list(ai) for ai in E.ainvs()]) self.curves = sorted(curves,key=key_function) perm = dict([(i,self.curves.index(E)) for i,E in enumerate(curves)]) if verbose: print "Sorting permutation = %s" % perm mat = MatrixSpace(ZZ,ncurves)(0) self._maps = [[0]*ncurves for i in range(ncurves)] for i,j,l,phi in tuples: if phi!=0: mat[perm[i],perm[j]] = l self._maps[perm[i]][perm[j]] = phi self._mat = fill_isogeny_matrix(mat) if verbose: print "Matrix = %s" % self._mat if not E.has_rational_cm(): self._qfmat = None return # In the CM case, we will have found some "horizontal" # isogenies of composite degree and would like to replace them # by isogenies of prime degree, mainly to make the isogeny # graph look better. We also construct a matrix whose entries # are not degrees of cyclic isogenies, but rather quadratic # forms (in 1 or 2 variables) representing the isogeny # degrees. For this we take a short cut: properly speaking, # when `\text{End}(E_1)=\text{End}(E_2)=O`, the set # `\text{Hom}(E_1,E_2)` is a rank `1` projective `O`-module, # hence has a well-defined ideal class associated to it, and # hence (using an identification between the ideal class group # and the group of classes of primitive quadratic forms of the # same discriminant) an equivalence class of quadratic forms. # But we currently only care about the numbers represented by # the form, i.e. which genus it is in rather than the exact # class. So it suffices to find one form of the correct # discriminant which represents one isogeny degree from `E_1` # to `E_2` in order to obtain a form which represents all such # degrees. if verbose: print("Creating degree matrix (CM case)") allQs = {} # keys: discriminants d # values: lists of equivalence classes of # primitive forms of discriminant d def find_quadratic_form(d,n): if not d in allQs: from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives allQs[d] = BinaryQF_reduced_representatives(d, primitive_only=True) # now test which of the Qs represents n for Q in allQs[d]: if Q.solve_integer(n): return Q raise ValueError("No form of discriminant %d represents %s" %(d,n)) mat = self._mat qfmat = [[0 for i in range(ncurves)] for j in range(ncurves)] for i, E1 in enumerate(self.curves): for j, E2 in enumerate(self.curves): if j<i: qfmat[i][j] = qfmat[j][i] mat[i,j] = mat[j,i] elif i==j: qfmat[i][j] = [1] # mat[i,j] already 1 else: d = E1.cm_discriminant() if d != E2.cm_discriminant(): qfmat[i][j] = [mat[i,j]] # mat[i,j] already unique else: # horizontal isogeny q = find_quadratic_form(d,mat[i,j]) qfmat[i][j] = list(q) mat[i,j] = q.small_prime_value() self._mat = mat self._qfmat = qfmat if verbose: print("new matrix = %s" % mat) print("matrix of forms = %s" % qfmat)
def test_finite_lattice(L): """ Test several functions on a given finite lattice. The function contains tests of different kinds: - Implications of Boolean properties. Examples: a distributive lattice is modular, a dismantlable and distributive lattice is planar, a simple lattice can not be constructible by Day's doublings. - Dual and self-dual properties. Examples: Dual of a modular lattice is modular, dual of an atomic lattice is co-atomic. - Certificate tests. Example: certificate for a non-complemented lattice must be an element without a complement. - Verification of some property by known property or by a random test. Examples: A lattice is distributive iff join-primes are exactly join-irreducibles and an interval of a relatively complemented lattice is complemented. - Set inclusions. Example: Every co-atom must be meet-irreducible. - And several other tests. Example: The skeleton of a pseudocomplemented lattice must be Boolean. EXAMPLES:: sage: from sage.tests.finite_poset import test_finite_lattice sage: L = posets.RandomLattice(10, 0.98) sage: test_finite_lattice(L) is None # Long time True """ from sage.combinat.posets.lattices import LatticePoset from sage.sets.set import Set from sage.combinat.subset import Subsets from sage.misc.prandom import randint from sage.misc.flatten import flatten from sage.misc.misc import attrcall if L.cardinality() < 4: # Special cases should be tested in specific TESTS-sections. return None all_props = set(list(implications) + flatten(implications.values())) P = {x: test_attrcall('is_' + x, L) for x in all_props} ### Relations between boolean-valued properties ### # Direct one-property implications for prop1 in implications: if P[prop1]: for prop2 in implications[prop1]: if not P[prop2]: raise ValueError("error: %s should implicate %s" % (prop1, prop2)) # Impossible combinations for p1, p2 in mutually_exclusive: if P[p1] and P[p2]: raise ValueError( "error: %s and %s should be impossible combination" % (p1, p2)) # Two-property implications for p1, p2, p3 in two_to_one: if P[p1] and P[p2] and not P[p3]: raise ValueError("error: %s and %s, so should be %s" % (p1, p2, p3)) Ldual = L.dual() # Selfdual properties for p in selfdual_properties: if P[p] != test_attrcall('is_' + p, Ldual): raise ValueError("selfdual property %s error" % p) # Dual properties and elements for p1, p2 in dual_properties: if P[p1] != test_attrcall('is_' + p2, Ldual): raise ValueError("dual properties error %s" % p1) for e1, e2 in dual_elements: if set(attrcall(e1)(L)) != set(attrcall(e2)(Ldual)): raise ValueError("dual elements error %s" % e1) ### Certificates ### # Test for "yes"-certificates if P['supersolvable']: a = L.is_supersolvable(certificate=True)[1] S = Subsets(L).random_element() if L.is_chain_of_poset(S): if not L.sublattice(a + list(S)).is_distributive(): raise ValueError("certificate error in is_supersolvable") if P['dismantlable']: elms = L.is_dismantlable(certificate=True)[1] if len(elms) != L.cardinality(): raise ValueError("certificate error 1 in is_dismantlable") elms = elms[:randint(0, len(elms) - 1)] L_ = L.sublattice([x for x in L if x not in elms]) if L_.cardinality() != L.cardinality() - len(elms): raise ValueError("certificate error 2 in is_dismantlable") if P['vertically_decomposable']: c = L.is_vertically_decomposable(certificate=True)[1] if c == L.bottom() or c == L.top(): raise ValueError( "certificate error 1 in is_vertically_decomposable") e = L.random_element() if L.compare_elements(c, e) is None: raise ValueError( "certificate error 2 in is_vertically_decomposable") # Test for "no"-certificates if not P['atomic']: a = L.is_atomic(certificate=True)[1] if a in L.atoms() or a not in L.join_irreducibles(): raise ValueError("certificate error in is_atomic") if not P['coatomic']: a = L.is_coatomic(certificate=True)[1] if a in L.coatoms() or a not in L.meet_irreducibles(): raise ValueError("certificate error in is_coatomic") if not P['complemented']: a = L.is_complemented(certificate=True)[1] if L.complements(a) != []: raise ValueError("compl. error 1") if not P['sectionally_complemented']: a, b = L.is_sectionally_complemented(certificate=True)[1] L_ = L.sublattice(L.interval(L.bottom(), a)) if L_.is_complemented(): raise ValueError("sec. compl. error 1") if len(L_.complements(b)) > 0: raise ValueError("sec. compl. error 2") if not P['cosectionally_complemented']: a, b = L.is_cosectionally_complemented(certificate=True)[1] L_ = L.sublattice(L.interval(a, L.top())) if L_.is_complemented(): raise ValueError("cosec. compl. error 1") if len(L_.complements(b)) > 0: raise ValueError("cosec. compl. error 2") if not P['relatively_complemented']: a, b, c = L.is_relatively_complemented(certificate=True)[1] I = L.interval(a, c) if len(I) != 3 or b not in I: raise ValueError("rel. compl. error 1") if not P['upper_semimodular']: a, b = L.is_upper_semimodular(certificate=True)[1] if not set(L.lower_covers(a)).intersection(set( L.lower_covers(b))) or set(L.upper_covers(a)).intersection( set(L.upper_covers(b))): raise ValueError("certificate error in is_upper_semimodular") if not P['lower_semimodular']: a, b = L.is_lower_semimodular(certificate=True)[1] if set(L.lower_covers(a)).intersection(set( L.lower_covers(b))) or not set(L.upper_covers(a)).intersection( set(L.upper_covers(b))): raise ValueError("certificate error in is_lower_semimodular") if not P['distributive']: x, y, z = L.is_distributive(certificate=True)[1] if L.meet(x, L.join(y, z)) == L.join(L.meet(x, y), L.meet(x, z)): raise ValueError("certificate error in is_distributive") if not P['modular']: x, a, b = L.is_modular(certificate=True)[1] if not L.is_less_than(x, b) or L.join(x, L.meet(a, b)) == L.meet( L.join(x, a), b): raise ValueError("certificate error in is_modular") if not P['pseudocomplemented']: a = L.is_pseudocomplemented(certificate=True)[1] L_ = L.subposet([e for e in L if L.meet(e, a) == L.bottom()]) if L_.has_top(): raise ValueError("certificate error in is_pseudocomplemented") if not P['join_pseudocomplemented']: a = L.is_join_pseudocomplemented(certificate=True)[1] L_ = L.subposet([e for e in L if L.join(e, a) == L.top()]) if L_.has_bottom(): raise ValueError("certificate error in is_join_pseudocomplemented") if not P['join_semidistributive']: e, x, y = L.is_join_semidistributive(certificate=True)[1] if L.join(e, x) != L.join(e, y) or L.join(e, x) == L.join( e, L.meet(x, y)): raise ValueError("certificate error in is_join_semidistributive") if not P['meet_semidistributive']: e, x, y = L.is_meet_semidistributive(certificate=True)[1] if L.meet(e, x) != L.meet(e, y) or L.meet(e, x) == L.meet( e, L.join(x, y)): raise ValueError("certificate error in is_meet_semidistributive") if not P['simple']: c = L.is_simple(certificate=True)[1] if len(L.congruence([c[randint(0, len(c) - 1)]])) == 1: raise ValueError("certificate error in is_simple") if not P['isoform']: c = L.is_isoform(certificate=True)[1] if len(c) == 1: raise ValueError("certificate error in is_isoform") if all( L.subposet(c[i]).is_isomorphic(L.subposet(c[i + 1])) for i in range(len(c) - 1)): raise ValueError("certificate error in is_isoform") if not P['uniform']: c = L.is_uniform(certificate=True)[1] if len(c) == 1: raise ValueError("certificate error in is_uniform") if all(len(c[i]) == len(c[i + 1]) for i in range(len(c) - 1)): raise ValueError("certificate error in is_uniform") if not P['regular']: c = L.is_regular(certificate=True)[1] if len(c[0]) == 1: raise ValueError("certificate error 1 in is_regular") if Set(c[1]) not in c[0]: raise ValueError("certificate error 2 in is_regular") if L.congruence([c[1]]) == c[0]: raise ValueError("certificate error 3 in is_regular") if not P['subdirectly_reducible']: x, y = L.is_subdirectly_reducible(certificate=True)[1] a = L.random_element() b = L.random_element() c = L.congruence([[a, b]]) if len(c) != L.cardinality(): for c_ in c: if x in c_: if y not in c_: raise ValueError( "certificate error 1 in is_subdirectly_reducible") break else: raise ValueError( "certificate error 2 in is_subdirectly_reducible") if not P['join_distributive']: a = L.is_join_distributive(certificate=True)[1] L_ = L.sublattice(L.interval(a, L.join(L.upper_covers(a)))) if L_.is_distributive(): raise ValueError("certificate error in is_join_distributive") if not P['meet_distributive']: a = L.is_meet_distributive(certificate=True)[1] L_ = L.sublattice(L.interval(L.meet(L.lower_covers(a)), a)) if L_.is_distributive(): raise ValueError("certificate error in is_meet_distributive") ### Other ### # Other ways to recognize some boolean property if P['distributive'] != (set(L.join_primes()) == set( L.join_irreducibles())): raise ValueError( "every join-irreducible of a distributive lattice should be join-prime" ) if P['distributive'] != (set(L.meet_primes()) == set( L.meet_irreducibles())): raise ValueError( "every meet-irreducible of a distributive lattice should be meet-prime" ) if P['join_semidistributive'] != all( L.canonical_joinands(e) is not None for e in L): raise ValueError( "every element of join-semidistributive lattice should have canonical joinands" ) if P['meet_semidistributive'] != all( L.canonical_meetands(e) is not None for e in L): raise ValueError( "every element of meet-semidistributive lattice should have canonical meetands" ) # Random verification of a Boolean property if P['relatively_complemented']: a = L.random_element() b = L.random_element() if not L.sublattice(L.interval(a, b)).is_complemented(): raise ValueError("rel. compl. error 3") if P['sectionally_complemented']: a = L.random_element() if not L.sublattice(L.interval(L.bottom(), a)).is_complemented(): raise ValueError("sec. compl. error 3") if P['cosectionally_complemented']: a = L.random_element() if not L.sublattice(L.interval(a, L.top())).is_complemented(): raise ValueError("cosec. compl. error 2") # Element set inclusions for s1, s2 in set_inclusions: if not set(attrcall(s1)(L)).issubset(set(attrcall(s2)(L))): raise ValueError("%s should be a subset of %s" % (s1, s2)) # Sublattice-closed properties L_ = L.sublattice(Subsets(L).random_element()) for p in sublattice_closed: if P[p] and not test_attrcall('is_' + p, L_): raise ValueError("property %s should apply to sublattices" % p) # Some sublattices L_ = L.center() # Center is a Boolean lattice if not L_.is_atomic() or not L_.is_distributive(): raise ValueError("error in center") if P['pseudocomplemented']: L_ = L.skeleton() # Skeleton is a Boolean lattice if not L_.is_atomic() or not L_.is_distributive(): raise ValueError("error in skeleton") L_ = L.frattini_sublattice() S = Subsets(L).random_element() if L.sublattice(S) == L and L.sublattice([e for e in S if e not in L_]) != L: raise ValueError("error in Frattini sublattice") L_ = L.maximal_sublattices() L_ = L_[randint(0, len(L_) - 1)] e = L.random_element() if e not in L_ and L.sublattice(list(L_) + [e]) != L: raise ValueError("error in maximal_sublattices") # Reverse functions: vertical composition and decomposition L_ = reduce(lambda a, b: a.vertical_composition(b), L.vertical_decomposition(), LatticePoset()) if not L.is_isomorphic(L_): raise ValueError("error in vertical [de]composition") # Meet and join a = L.random_element() b = L.random_element() m = L.meet(a, b) j = L.join(a, b) m_ = L.subposet([ e for e in L.principal_lower_set(a) if e in L.principal_lower_set(b) ]).top() j_ = L.subposet([ e for e in L.principal_upper_set(a) if e in L.principal_upper_set(b) ]).bottom() if m != m_ or m != Ldual.join(a, b): raise ValueError("error in meet") if j != j_ or j != Ldual.meet(a, b): raise ValueError("error in join") # Misc misc e = L.neutral_elements() e = e[randint(0, len(e) - 1)] a = L.random_element() b = L.random_element() if not L.sublattice([e, a, b]).is_distributive(): raise ValueError("error in neutral_elements")
def EllipticCurve_from_j(j): """ Return an elliptic curve with given `j`-invariant. EXAMPLES:: sage: E = EllipticCurve_from_j(0); E; E.j_invariant(); E.label() Elliptic Curve defined by y^2 + y = x^3 over Rational Field 0 '27a3' sage: E = EllipticCurve_from_j(1728); E; E.j_invariant(); E.label() Elliptic Curve defined by y^2 = x^3 - x over Rational Field 1728 '32a2' sage: E = EllipticCurve_from_j(1); E; E.j_invariant() Elliptic Curve defined by y^2 + x*y = x^3 + 36*x + 3455 over Rational Field 1 """ try: K = j.parent() except AttributeError: K = rings.RationalField() if not rings.is_Field(K): K = K.fraction_field() char=K.characteristic() if char==2: if j == 0: return EllipticCurve(K, [ 0, 0, 1, 0, 0 ]) else: return EllipticCurve(K, [ 1, 0, 0, 0, 1/j ]) if char == 3: if j==0: return EllipticCurve(K, [ 0, 0, 0, 1, 0 ]) else: return EllipticCurve(K, [ 0, j, 0, 0, -j**2 ]) if K is rings.RationalField(): # we construct the minimal twist, i.e. the curve with minimal # conductor with this j_invariant: if j == 0: return EllipticCurve(K, [ 0, 0, 1, 0, 0 ]) # 27a3 if j == 1728: return EllipticCurve(K, [ 0, 0, 0, -1, 0 ]) # 32a2 n = j.numerator() m = n-1728*j.denominator() a4 = -3*n*m a6 = -2*n*m**2 # Now E=[0,0,0,a4,a6] has j-invariant j=n/d from sage.sets.set import Set for p in Set(n.prime_divisors()+m.prime_divisors()): e = min(a4.valuation(p)//2,a6.valuation(p)//3) if e>0: p = p**e a4 /= p**2 a6 /= p**3 # Now E=[0,0,0,a4,a6] is minimal at all p != 2,3 tw = [-1,2,-2,3,-3,6,-6] E1 = EllipticCurve([0,0,0,a4,a6]) Elist = [E1] + [E1.quadratic_twist(t) for t in tw] crv_cmp = lambda E,F: cmp(E.conductor(),F.conductor()) Elist.sort(cmp=crv_cmp) return Elist[0] # defaults for all other fields: if j == 0: return EllipticCurve(K, [ 0, 0, 0, 0, 1 ]) if j == 1728: return EllipticCurve(K, [ 0, 0, 0, 1, 0 ]) k=j-1728 return EllipticCurve(K, [0,0,0,-3*j*k, -2*j*k**2])
def support(self): return [self._ptdict[P] for P in Set([d for d in self._data])]
def FinitelyGeneratedAbelianPresentation(int_list): r""" Return canonical presentation of finitely generated abelian group. INPUT: - ``int_list`` -- List of integers defining the group to be returned, the defining list is reduced to the invariants of the input list before generating the corresponding group. OUTPUT: Finitely generated abelian group, `\ZZ_{n_1} \times \ZZ_{n_2} \times \cdots \times \ZZ_{n_k}` as a finite presentation, where `n_i` forms the invariants of the input list. EXAMPLES:: sage: groups.presentation.FGAbelian([2,2]) Finitely presented group < a, b | a^2, b^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([2,3]) Finitely presented group < a | a^6 > sage: groups.presentation.FGAbelian([2,4]) Finitely presented group < a, b | a^2, b^4, a^-1*b^-1*a*b > You can create free abelian groups:: sage: groups.presentation.FGAbelian([0]) Finitely presented group < a | > sage: groups.presentation.FGAbelian([0,0]) Finitely presented group < a, b | a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,0,0]) Finitely presented group < a, b, c | a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > And various infinite abelian groups:: sage: groups.presentation.FGAbelian([0,2]) Finitely presented group < a, b | a^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,2,2]) Finitely presented group < a, b, c | a^2, b^2, a^-1*c^-1*a*c, a^-1*b^-1*a*b, c^-1*b^-1*c*b > Outputs are reduced to minimal generators and relations:: sage: groups.presentation.FGAbelian([3,5,2,7,3]) Finitely presented group < a, b | a^3, b^210, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([3,210]) Finitely presented group < a, b | a^3, b^210, a^-1*b^-1*a*b > The trivial group is an acceptable output:: sage: groups.presentation.FGAbelian([]) Finitely presented group < | > sage: groups.presentation.FGAbelian([1]) Finitely presented group < | > sage: groups.presentation.FGAbelian([1,1,1,1,1,1,1,1,1,1]) Finitely presented group < | > Input list must consist of positive integers:: sage: groups.presentation.FGAbelian([2,6,3,9,-4]) Traceback (most recent call last): ... ValueError: input list must contain nonnegative entries sage: groups.presentation.FGAbelian([2,'a',4]) Traceback (most recent call last): ... TypeError: unable to convert x (=a) to an integer TESTS:: sage: ag = groups.presentation.FGAbelian([2,2]) sage: ag.as_permutation_group().is_isomorphic(groups.permutation.KleinFour()) True sage: G = groups.presentation.FGAbelian([2,4,8]) sage: C2 = CyclicPermutationGroup(2) sage: C4 = CyclicPermutationGroup(4) sage: C8 = CyclicPermutationGroup(8) sage: gg = (C2.direct_product(C4)[0]).direct_product(C8)[0] sage: gg.is_isomorphic(G.as_permutation_group()) True sage: all([groups.presentation.FGAbelian([i]).as_permutation_group().is_isomorphic(groups.presentation.Cyclic(i).as_permutation_group()) for i in [2..35]]) True """ from sage.groups.free_group import _lexi_gen check_ls = [Integer(x) for x in int_list if Integer(x) >= 0] if len(check_ls) != len(int_list): raise ValueError('input list must contain nonnegative entries') col_sp = diagonal_matrix(int_list).column_space() invariants = FGP_Module(ZZ**(len(int_list)), col_sp).invariants() name_gen = _lexi_gen() F = FreeGroup([name_gen.next() for i in invariants]) ret_rls = [F([i+1])**invariants[i] for i in range(len(invariants)) if invariants[i]!=0] # Build commutator relations gen_pairs = list(Set(F.gens()).subsets(2)) ret_rls = ret_rls + [x[0]**(-1)*x[1]**(-1)*x[0]*x[1] for x in gen_pairs] return FinitelyPresentedGroup(F, tuple(ret_rls))
def EllipticCurve_from_j(j, minimal_twist=True): """ Return an elliptic curve with given `j`-invariant. INPUT: - ``j`` -- an element of some field. - ``minimal_twist`` (boolean, default True) -- If True and ``j`` is in `\QQ`, the curve returned is a minimal twist, i.e. has minimal conductor. If `j` is not in `\QQ` this parameter is ignored. OUTPUT: An elliptic curve with `j`-invariant `j`. EXAMPLES:: sage: E = EllipticCurve_from_j(0); E; E.j_invariant(); E.label() Elliptic Curve defined by y^2 + y = x^3 over Rational Field 0 '27a3' sage: E = EllipticCurve_from_j(1728); E; E.j_invariant(); E.label() Elliptic Curve defined by y^2 = x^3 - x over Rational Field 1728 '32a2' sage: E = EllipticCurve_from_j(1); E; E.j_invariant() Elliptic Curve defined by y^2 + x*y = x^3 + 36*x + 3455 over Rational Field 1 The ``minimal_twist`` parameter (ignored except over `\QQ` and True by default) controls whether or not a minimal twist is computed:: sage: EllipticCurve_from_j(100) Elliptic Curve defined by y^2 = x^3 + x^2 + 3392*x + 307888 over Rational Field sage: _.conductor() 33129800 sage: EllipticCurve_from_j(100, minimal_twist=False) Elliptic Curve defined by y^2 = x^3 + 488400*x - 530076800 over Rational Field sage: _.conductor() 298168200 Since computing the minimal twist requires factoring both `j` and `j-1728` the following example would take a long time without setting ``minimal_twist`` to False:: sage: E = EllipticCurve_from_j(2^256+1,minimal_twist=False) sage: E.j_invariant() == 2^256+1 True """ try: K = j.parent() except AttributeError: K = rings.RationalField() if K not in _Fields: K = K.fraction_field() char = K.characteristic() if char == 2: if j == 0: return EllipticCurve(K, [0, 0, 1, 0, 0]) else: return EllipticCurve(K, [1, 0, 0, 0, 1 / j]) if char == 3: if j == 0: return EllipticCurve(K, [0, 0, 0, 1, 0]) else: return EllipticCurve(K, [0, j, 0, 0, -j**2]) if K is rings.RationalField(): # we construct the minimal twist, i.e. the curve with minimal # conductor with this j_invariant: if j == 0: return EllipticCurve(K, [0, 0, 1, 0, 0]) # 27a3 if j == 1728: return EllipticCurve(K, [0, 0, 0, -1, 0]) # 32a2 if not minimal_twist: k = j - 1728 return EllipticCurve(K, [0, 0, 0, -3 * j * k, -2 * j * k**2]) n = j.numerator() m = n - 1728 * j.denominator() a4 = -3 * n * m a6 = -2 * n * m**2 # Now E=[0,0,0,a4,a6] has j-invariant j=n/d from sage.sets.set import Set for p in Set(n.prime_divisors() + m.prime_divisors()): e = min(a4.valuation(p) // 2, a6.valuation(p) // 3) if e > 0: p = p**e a4 /= p**2 a6 /= p**3 # Now E=[0,0,0,a4,a6] is minimal at all p != 2,3 tw = [-1, 2, -2, 3, -3, 6, -6] E1 = EllipticCurve([0, 0, 0, a4, a6]) Elist = [E1] + [E1.quadratic_twist(t) for t in tw] crv_cmp = lambda E, F: cmp(E.conductor(), F.conductor()) Elist.sort(cmp=crv_cmp) return Elist[0] # defaults for all other fields: if j == 0: return EllipticCurve(K, [0, 0, 0, 0, 1]) if j == 1728: return EllipticCurve(K, [0, 0, 0, 1, 0]) k = j - 1728 return EllipticCurve(K, [0, 0, 0, -3 * j * k, -2 * j * k**2])
class PositiveIntegersOrderedByDivisibilityFacade(UniqueRepresentation, Parent): r""" An example of a facade poset: the positive integers ordered by divisibility This class provides a minimal implementation of a facade poset EXAMPLES:: sage: P = Posets().example("facade"); P An example of a facade poset: the positive integers ordered by divisibility sage: P(5) 5 sage: P(0) Traceback (most recent call last): ... ValueError: Can't coerce `0` in any parent `An example of a facade poset: the positive integers ordered by divisibility` is a facade for sage: 3 in P True sage: 0 in P False """ element_class = type(Set([])) def __init__(self): r""" EXAMPLES:: sage: P = Posets().example("facade"); P An example of a facade poset: the positive integers ordered by divisibility sage: P.category() Category of facade posets sage: type(P) <class 'sage.categories.examples.posets.PositiveIntegersOrderedByDivisibilityFacade_with_category'> sage: TestSuite(P).run() """ Parent.__init__(self, facade=(PositiveIntegers(), ), category=Posets()) def _repr_(self): r""" TESTS:: sage: S = Posets().example("facade") sage: S._repr_() 'An example of a facade poset: the positive integers ordered by divisibility' """ return "An example of a facade poset: the positive integers ordered by divisibility" def le(self, x, y): r""" Returns whether `x` is divisible by `y` EXAMPLES:: sage: P = Posets().example("facade") sage: P.le(3, 6) True sage: P.le(3, 3) True sage: P.le(3, 7) False """ return x.divides(y)
def steiner_triple_system(n): r""" Returns a Steiner Triple System A Steiner Triple System (STS) of a set `\{0,...,n-1\}` is a family `S` of 3-sets such that for any `i \not = j` there exists exactly one set of `S` in which they are both contained. It can alternatively be thought of as a factorization of the complete graph `K_n` with triangles. A Steiner Triple System of a `n`-set exists if and only if `n \equiv 1 \pmod 6` or `n \equiv 3 \pmod 6`, in which case one can be found through Bose's and Skolem's constructions, respectively [AndHonk97]_. INPUT: - ``n`` returns a Steiner Triple System of `\{0,...,n-1\}` EXAMPLE: A Steiner Triple System on `9` elements :: sage: sts = designs.steiner_triple_system(9) sage: sts Incidence structure with 9 points and 12 blocks sage: list(sts) [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7], [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]] As any pair of vertices is covered once, its parameters are :: sage: sts.parameters(t=2) (2, 9, 3, 1) An exception is raised for invalid values of ``n`` :: sage: designs.steiner_triple_system(10) Traceback (most recent call last): ... EmptySetError: Steiner triple systems only exist for n = 1 mod 6 or n = 3 mod 6 REFERENCE: .. [AndHonk97] A short course in Combinatorial Designs, Ian Anderson, Iiro Honkala, Internet Editions, Spring 1997, http://www.utu.fi/~honkala/designs.ps """ name = "Steiner Triple System on " + str(n) + " elements" if n % 6 == 3: t = (n - 3) // 6 Z = range(2 * t + 1) T = lambda (x, y): x + (2 * t + 1) * y sts = [[(i,0),(i,1),(i,2)] for i in Z] + \ [[(i,k),(j,k),(((t+1)*(i+j)) % (2*t+1),(k+1)%3)] for k in range(3) for i in Z for j in Z if i != j] elif n % 6 == 1: t = (n - 1) // 6 N = range(2 * t) T = lambda (x, y): x + y * t * 2 if (x, y) != (-1, -1) else n - 1 L1 = lambda i, j: (i + j) % ((n - 1) // 3) L = lambda i, j: L1(i, j) // 2 if L1(i, j) % 2 == 0 else t + (L1(i, j) - 1) // 2 sts = [[(i,0),(i,1),(i,2)] for i in range(t)] + \ [[(-1,-1),(i,k),(i-t,(k+1) % 3)] for i in range(t,2*t) for k in [0,1,2]] + \ [[(i,k),(j,k),(L(i,j),(k+1) % 3)] for k in [0,1,2] for i in N for j in N if i < j] else: raise EmptySetError( "Steiner triple systems only exist for n = 1 mod 6 or n = 3 mod 6") from sage.sets.set import Set sts = Set(map(lambda x: Set(map(T, x)), sts)) return BlockDesign(n, sts, name=name)
def local_zero_density_congruence(self, p, m, Zvec=None, NZvec=None): """ Finds the Zero-type local density of Q representing `m` at `p`, allowing certain congruence conditions mod p. INPUT: Q -- quadratic form assumed to be block diagonal and `p`-integral `p` -- a prime number `m` -- an integer Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3]) sage: Q.local_zero_density_congruence(2, 2, None, None) 0 sage: Q.local_zero_density_congruence(2, 4, None, None) 1/2 sage: Q.local_zero_density_congruence(3, 6, None, None) 0 sage: Q.local_zero_density_congruence(3, 9, None, None) 2/9 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Q.local_zero_density_congruence(2, 2, None, None) 0 sage: Q.local_zero_density_congruence(2, 4, None, None) 1/4 sage: Q.local_zero_density_congruence(3, 6, None, None) 0 sage: Q.local_zero_density_congruence(3, 9, None, None) 8/81 """ ## DIAGNOSTIC verbose(" In local_zero_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) verbose(" m = " + str(m)) verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) ## Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] n = self.dim() ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") p2 = p * p ## Check some conditions for no zero-type solutions to exist if ((m % (p2) != 0) or (NZvec is not None)): return 0 ## Use the reduction procedure to return the result return self.local_density_congruence(p, m / p2, None, None) / p**(self.dim() - 2)
def _normal_label(g, comb_emb, external_face): r""" Helper function to schnyder method for computing coordinates in the plane to plot a planar graph with no edge crossings. Constructs a normal labelling of a triangular graph g, given the planar combinatorial embedding of g and a designated external face. Returns labels dictionary. The normal label is constructed by first contracting the graph down to its external face, then expanding the graph back to the original while simultaneously adding angle labels. INPUT: - g -- the graph to find the normal labeling of (g must be triangulated) - ``comb_emb`` -- a planar combinatorial embedding of g - ``external_face`` -- the list of three edges in the external face of g OUTPUT: x -- tuple with entries x[0] = dict of dicts of normal labeling for each vertex of g and each adjacent neighbors u,v (u < v) of vertex: { vertex : { (u,v): angel_label } } x[1] = (v1,v2,v3) tuple of the three vertices of the external face. EXAMPLES:: sage: from sage.graphs.schnyder import _triangulate, _normal_label, _realizer sage: g = Graph(graphs.CycleGraph(7)) sage: g.is_planar(set_embedding=True) True sage: faces = g.faces(g._embedding) sage: _triangulate(g, g._embedding) [(2, 0), (4, 2), (6, 4), (1, 3), (6, 1), (3, 5), (4, 0), (6, 3)] sage: tn = _normal_label(g, g._embedding, faces[0]) sage: _realizer(g, tn) ({0: [<sage.graphs.schnyder.TreeNode object at ...>]}, (0, 1, 2)) """ contracted = [] contractible = [] labels = {} external_vertices = sorted( [external_face[0][0], external_face[1][0], external_face[2][0]]) v1, v2, v3 = external_vertices v1_neighbors = Set(g.neighbors(v1)) neighbor_count = {} for v in g.vertices(): neighbor_count[v] = 0 for v in g.neighbors(v1): neighbor_count[v] = len(v1_neighbors.intersection(Set(g.neighbors(v)))) for v in v1_neighbors: if v in [v1, v2, v3]: continue if neighbor_count[v] == 2: contractible.append(v) # contraction phase: while g.order() > 3: try: v = contractible.pop() except Exception: raise RuntimeError( 'Contractible list is empty but graph still has %d vertices. (Expected 3.)' % g.order()) break # going to contract v v_neighbors = Set(g.neighbors(v)) contracted.append( (v, v_neighbors, v_neighbors - v1_neighbors - Set([v1]))) g.delete_vertex(v) v1_neighbors -= Set([v]) for w in v_neighbors - v1_neighbors - Set([v1]): # adding edge (v1, w) g.add_edge((v1, w)) if g.order() == 3: break v1_neighbors += v_neighbors - Set([v1]) contractible = [] for w in g.neighbors(v1): if (len(v1_neighbors.intersection(Set( g.neighbors(w))))) == 2 and w not in [v1, v2, v3]: contractible.append(w) # expansion phase: v1, v2, v3 = g.vertices() # always in sorted order labels[v1] = {(v2, v3): 1} labels[v2] = {(v1, v3): 2} labels[v3] = {(v1, v2): 3} while len(contracted): v, new_neighbors, neighbors_to_delete = contracted.pop() # going to add back vertex v labels[v] = {} for w in neighbors_to_delete: g.delete_edge((v1, w)) if len(neighbors_to_delete) == 0: # we are adding v into the face new_neighbors w1, w2, w3 = sorted(new_neighbors) labels[v] = { (w1, w2): labels[w3].pop((w1, w2)), (w2, w3): labels[w1].pop((w2, w3)), (w1, w3): labels[w2].pop((w1, w3)) } labels[w1][tuple(sorted((w2, v)))] = labels[v][(w2, w3)] labels[w1][tuple(sorted((w3, v)))] = labels[v][(w2, w3)] labels[w2][tuple(sorted((w1, v)))] = labels[v][(w1, w3)] labels[w2][tuple(sorted((w3, v)))] = labels[v][(w1, w3)] labels[w3][tuple(sorted((w1, v)))] = labels[v][(w1, w2)] labels[w3][tuple(sorted((w2, v)))] = labels[v][(w1, w2)] else: new_neighbors_set = Set(new_neighbors) angles_out_of_v1 = set() vertices_in_order = [] l = [] for angle in labels[v1].keys(): if len(Set(angle).intersection(new_neighbors_set)) == 2: angles_out_of_v1.add(angle) l = l + list(angle) # find a unique element in l l.sort() i = 0 while i < len(l): if l[i] == l[i + 1]: i += 2 else: break angle_set = Set(angles_out_of_v1) vertices_in_order.append(l[i]) while len(angles_out_of_v1) > 0: for angle in angles_out_of_v1: if vertices_in_order[-1] in angle: break if angle[0] == vertices_in_order[-1]: vertices_in_order.append(angle[1]) else: vertices_in_order.append(angle[0]) angles_out_of_v1.remove(angle) w = vertices_in_order # is w[0] a 2 or a 3? top_label = labels[w[0]][tuple(sorted((v1, w[1])))] if top_label == 3: bottom_label = 2 else: bottom_label = 3 i = 0 while i < len(w) - 1: labels[v][tuple(sorted((w[i], w[i + 1])))] = 1 labels[w[i]][tuple(sorted((w[i + 1], v)))] = top_label labels[w[i + 1]][tuple(sorted((w[i], v)))] = bottom_label i += 1 labels[v][tuple(sorted((v1, w[0])))] = bottom_label labels[v][tuple(sorted((v1, w[-1])))] = top_label labels[w[0]][tuple(sorted((v1, v)))] = top_label labels[w[-1]][tuple(sorted((v1, v)))] = bottom_label labels[v1][tuple(sorted((w[0], v)))] = 1 labels[v1][tuple(sorted((w[-1], v)))] = 1 # delete all the extra labels for angle in angle_set: labels[v1].pop(angle) labels[w[0]].pop(tuple(sorted((v1, w[1])))) labels[w[-1]].pop(tuple(sorted((v1, w[-2])))) i = 1 while i < len(w) - 1: labels[w[i]].pop(tuple(sorted((v1, w[i + 1])))) labels[w[i]].pop(tuple(sorted((v1, w[i - 1])))) i += 1 for w in new_neighbors: g.add_edge((v, w)) return labels, (v1, v2, v3)
def local_badI_density_congruence(self, p, m, Zvec=None, NZvec=None): """ Finds the Bad-type I local density of Q representing `m` at `p`. (Assuming that p > 2 and Q is given in local diagonal form.) INPUT: Q -- quadratic form assumed to be block diagonal and `p`-integral `p` -- a prime number `m` -- an integer Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3]) sage: Q.local_badI_density_congruence(2, 1, None, None) 0 sage: Q.local_badI_density_congruence(2, 2, None, None) 1 sage: Q.local_badI_density_congruence(2, 4, None, None) 0 sage: Q.local_badI_density_congruence(3, 1, None, None) 0 sage: Q.local_badI_density_congruence(3, 6, None, None) 0 sage: Q.local_badI_density_congruence(3, 9, None, None) 0 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Q.local_badI_density_congruence(2, 1, None, None) 0 sage: Q.local_badI_density_congruence(2, 2, None, None) 0 sage: Q.local_badI_density_congruence(2, 4, None, None) 0 sage: Q.local_badI_density_congruence(3, 2, None, None) 0 sage: Q.local_badI_density_congruence(3, 6, None, None) 0 sage: Q.local_badI_density_congruence(3, 9, None, None) 0 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,3,9]) sage: Q.local_badI_density_congruence(3, 1, None, None) 0 sage: Q.local_badI_density_congruence(3, 3, None, None) 4/3 sage: Q.local_badI_density_congruence(3, 6, None, None) 4/3 sage: Q.local_badI_density_congruence(3, 9, None, None) 0 sage: Q.local_badI_density_congruence(3, 18, None, None) 0 """ ## DIAGNOSTIC verbose(" In local_badI_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) verbose(" m = " + str(m)) verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) ## Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] n = self.dim() ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") ## Define the indexing set S_0, and determine if S_1 is empty: ## ----------------------------------------------------------- S0 = [] S1_empty_flag = True ## This is used to check if we should be computing BI solutions at all! ## (We should really to this earlier, but S1 must be non-zero to proceed.) ## Find the valuation of each variable (which will be the same over 2x2 blocks), ## remembering those of valuation 0 and if an entry of valuation 1 exists. for i in range(n): ## Compute the valuation of each index, allowing for off-diagonal terms if (self[i, i] == 0): if (i == 0): val = valuation(self[i, i + 1], p) ## Look at the term to the right else: if (i == n - 1): val = valuation(self[i - 1, i], p) ## Look at the term above else: val = valuation( self[i, i + 1] + self[i - 1, i], p ) ## Finds the valuation of the off-diagonal term since only one isn't zero else: val = valuation(self[i, i], p) if (val == 0): S0 += [i] elif (val == 1): S1_empty_flag = False ## Need to have a non-empty S1 set to proceed with Bad-type I reduction... ## Check that S1 is non-empty and p|m to proceed, otherwise return no solutions. if S1_empty_flag or m % p != 0: return 0 ## Check some conditions for no bad-type I solutions to exist if (NZvec is not None) and (len(Set(S0).intersection(Set(NZvec))) != 0): return 0 ## Check that the form is primitive... WHY DO WE NEED TO DO THIS?!? if (S0 == []): print(" Using Q = " + str(self)) print(" and p = " + str(p)) raise RuntimeError("Oops! The form is not primitive!") ## DIAGNOSTIC verbose(" m = " + str(m) + " p = " + str(p)) verbose(" S0 = " + str(S0)) verbose(" len(S0) = " + str(len(S0))) ## Make the form Qnew for the reduction procedure: ## ----------------------------------------------- Qnew = deepcopy(self) ## TO DO: DO THIS WITHOUT A copy(). =) for i in range(n): if i in S0: Qnew[i, i] = p * Qnew[i, i] if ((p == 2) and (i < n - 1)): Qnew[i, i + 1] = p * Qnew[i, i + 1] else: Qnew[i, i] = Qnew[i, i] / p if ((p == 2) and (i < n - 1)): Qnew[i, i + 1] = Qnew[i, i + 1] / p ## DIAGNOSTIC verbose("\n\n Check of Bad-type I reduction: \n") verbose(" Q is " + str(self)) verbose(" Qnew is " + str(Qnew)) verbose(" p = " + str(p)) verbose(" m / p = " + str(m / p)) verbose(" NZvec " + str(NZvec)) ## Do the reduction Zvec_geq_1 = list(Set([i for i in Zvec if i not in S0])) if NZvec is None: NZvec_geq_1 = NZvec else: NZvec_geq_1 = list(Set([i for i in NZvec if i not in S0])) return QQ(p**(1 - len(S0))) * Qnew.local_good_density_congruence( p, m / p, Zvec_geq_1, NZvec_geq_1)
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 local_good_density_congruence_odd(self, p, m, Zvec, NZvec): """ Finds the Good-type local density of Q representing `m` at `p`. (Assuming that `p` > 2 and Q is given in local diagonal form.) The additional congruence condition arguments Zvec and NZvec can be either a list of indices or None. Zvec = [] is equivalent to Zvec = None which both impose no additional conditions, but NZvec = [] returns no solutions always while NZvec = None imposes no additional condition. TO DO: Add type checking for Zvec, NZvec, and that Q is in local normal form. INPUT: Q -- quadratic form assumed to be diagonal and p-integral `p` -- a prime number `m` -- an integer Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3]) sage: Q.local_good_density_congruence_odd(3, 1, None, None) 2/3 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Q.local_good_density_congruence_odd(3, 1, None, None) 8/9 """ n = self.dim() ## Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") ## Assuming Q is diagonal, find the indices of the p-unit (diagonal) entries UnitVec = [i for i in range(n) if (self[i, i] % p) != 0] NonUnitVec = list(Set(range(n)) - Set(UnitVec)) ## Take cases on the existence of additional non-zero congruence conditions (mod p) UnitVec_minus_Zvec = list(Set(UnitVec) - Set(Zvec)) NonUnitVec_minus_Zvec = list(Set(NonUnitVec) - Set(Zvec)) Q_Unit_minus_Zvec = self.extract_variables(UnitVec_minus_Zvec) if (NZvec is None): if m % p != 0: total = Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum( p, m) * p**len(NonUnitVec_minus_Zvec) ## m != 0 (mod p) else: total = (Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum( p, m) - 1) * p**len(NonUnitVec_minus_Zvec) ## m == 0 (mod p) else: UnitVec_minus_ZNZvec = list(Set(UnitVec) - (Set(Zvec) + Set(NZvec))) NonUnitVec_minus_ZNZvec = list( Set(NonUnitVec) - (Set(Zvec) + Set(NZvec))) Q_Unit_minus_ZNZvec = self.extract_variables(UnitVec_minus_ZNZvec) if m % p != 0: ## m != 0 (mod p) total = Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_Zvec) \ - Q_Unit_minus_ZNZvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_ZNZvec) else: ## m == 0 (mod p) total = (Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_Zvec) \ - (Q_Unit_minus_ZNZvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_ZNZvec) ## Return the Good-type representation density good_density = QQ(total) / p**(n - 1) return good_density
def BCHCode(n, delta, F, b=0): r""" A 'Bose-Chaudhuri-Hockenghem code' (or BCH code for short) is the largest possible cyclic code of length n over field F=GF(q), whose generator polynomial has zeros (which contain the set) `Z = \{a^{b},a^{b+1}, ..., a^{b+delta-2}\}`, where a is a primitive `n^{th}` root of unity in the splitting field `GF(q^m)`, b is an integer `0\leq b\leq n-delta+1` and m is the multiplicative order of q modulo n. (The integers `b,...,b+delta-2` typically lie in the range `1,...,n-1`.) The integer `delta \geq 1` is called the "designed distance". The length n of the code and the size q of the base field must be relatively prime. The generator polynomial is equal to the least common multiple of the minimal polynomials of the elements of the set `Z` above. Special cases are b=1 (resulting codes are called 'narrow-sense' BCH codes), and `n=q^m-1` (known as 'primitive' BCH codes). It may happen that several values of delta give rise to the same BCH code. The largest one is called the Bose distance of the code. The true minimum distance, d, of the code is greater than or equal to the Bose distance, so `d\geq delta`. EXAMPLES:: sage: FF.<a> = GF(3^2,"a") sage: x = PolynomialRing(FF,"x").gen() sage: L = [b.minpoly() for b in [a,a^2,a^3]]; g = LCM(L) sage: f = x^(8)-1 sage: g.divides(f) True sage: C = CyclicCode(8,g); C Linear code of length 8, dimension 4 over Finite Field of size 3 sage: C.minimum_distance() 4 sage: C = BCHCode(8,3,GF(3),1); C Linear code of length 8, dimension 4 over Finite Field of size 3 sage: C.minimum_distance() 4 sage: C = BCHCode(8,3,GF(3)); C Linear code of length 8, dimension 5 over Finite Field of size 3 sage: C.minimum_distance() 3 sage: C = BCHCode(26, 5, GF(5), b=1); C Linear code of length 26, dimension 10 over Finite Field of size 5 """ from sage.misc.misc import srange q = F.order() R = IntegerModRing(n) m = R(q).multiplicative_order() FF = GF(q**m, "z") z = FF.gen() e = z.multiplicative_order() / n a = z**e # order n P = PolynomialRing(F, "x") x = P.gen() cosets = Set([]) for i in srange(b, b + delta - 1): cosets = cosets.union(Set(cyclotomic_cosets(q, n, i))) L0 = [a**j for j in cosets] L1 = [P(ai.minpoly()) for ai in L0] g = P(LCM(L1)) #print cosets, "\n", g, "\n", (x**n-1).factor(), "\n", L1, "\n", g.divides(x**n-1) if not (g.divides(x**n - 1)): raise ValueError, "BCH codes does not exist with the given input." return CyclicCodeFromGeneratingPolynomial(n, g)
def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): """ Finds the Bad-type II local density of Q representing `m` at `p`. (Assuming that `p` > 2 and Q is given in local diagonal form.) INPUT: Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number `m` -- an integer Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3]) sage: Q.local_badII_density_congruence(2, 1, None, None) 0 sage: Q.local_badII_density_congruence(2, 2, None, None) 0 sage: Q.local_badII_density_congruence(2, 4, None, None) 0 sage: Q.local_badII_density_congruence(3, 1, None, None) 0 sage: Q.local_badII_density_congruence(3, 6, None, None) 0 sage: Q.local_badII_density_congruence(3, 9, None, None) 0 sage: Q.local_badII_density_congruence(3, 27, None, None) 0 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,3,9,9]) sage: Q.local_badII_density_congruence(3, 1, None, None) 0 sage: Q.local_badII_density_congruence(3, 3, None, None) 0 sage: Q.local_badII_density_congruence(3, 6, None, None) 0 sage: Q.local_badII_density_congruence(3, 9, None, None) 4/27 sage: Q.local_badII_density_congruence(3, 18, None, None) 4/9 """ ## DIAGNOSTIC verbose(" In local_badII_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) verbose(" m = " + str(m)) verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) ## Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] n = self.dim() ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") ## Define the indexing sets S_i: ## ----------------------------- S0 = [] S1 = [] S2plus = [] for i in range(n): ## Compute the valuation of each index, allowing for off-diagonal terms if (self[i, i] == 0): if (i == 0): val = valuation(self[i, i + 1], p) ## Look at the term to the right elif (i == n - 1): val = valuation(self[i - 1, i], p) ## Look at the term above else: val = valuation( self[i, i + 1] + self[i - 1, i], p ) ## Finds the valuation of the off-diagonal term since only one isn't zero else: val = valuation(self[i, i], p) ## Sort the indices into disjoint sets by their valuation if (val == 0): S0 += [i] elif (val == 1): S1 += [i] elif (val >= 2): S2plus += [i] ## Check that S2 is non-empty and p^2 divides m to proceed, otherwise return no solutions. p2 = p * p if (S2plus == []) or (m % p2 != 0): return 0 ## Check some conditions for no bad-type II solutions to exist if (NZvec is not None) and (len(Set(S2plus).intersection(Set(NZvec))) == 0): return 0 ## Check that the form is primitive... WHY IS THIS NECESSARY? if (S0 == []): print(" Using Q = " + str(self)) print(" and p = " + str(p)) raise RuntimeError("Oops! The form is not primitive!") ## DIAGNOSTIC verbose("\n Entering BII routine ") verbose(" S0 is " + str(S0)) verbose(" S1 is " + str(S1)) verbose(" S2plus is " + str(S2plus)) verbose(" m = " + str(m) + " p = " + str(p)) ## Make the form Qnew for the reduction procedure: ## ----------------------------------------------- Qnew = deepcopy(self) ## TO DO: DO THIS WITHOUT A copy(). =) for i in range(n): if i in S2plus: Qnew[i, i] = Qnew[i, i] / p2 if (p == 2) and (i < n - 1): Qnew[i, i + 1] = Qnew[i, i + 1] / p2 ## DIAGNOSTIC verbose("\n\n Check of Bad-type II reduction: \n") verbose(" Q is " + str(self)) verbose(" Qnew is " + str(Qnew)) ## Perform the reduction formula Zvec_geq_2 = list(Set([i for i in Zvec if i in S2plus])) if NZvec is None: NZvec_geq_2 = NZvec else: NZvec_geq_2 = list(Set([i for i in NZvec if i in S2plus])) return QQ(p**(len(S2plus) + 2 - n)) \ * (Qnew.local_density_congruence(p, m / p2, Zvec_geq_2, NZvec_geq_2) \ - Qnew.local_density_congruence(p, m / p2, S2plus , NZvec_geq_2))
def local_good_density_congruence(self, p, m, Zvec=None, NZvec=None): """ Finds the Good-type local density of Q representing `m` at `p`. (Front end routine for parity specific routines for p.) TO DO: Add Documentation about the additional congruence conditions Zvec and NZvec. INPUT: Q -- quadratic form assumed to be block diagonal and p-integral `p` -- a prime number `m` -- an integer Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3]) sage: Q.local_good_density_congruence(2, 1, None, None) 1 sage: Q.local_good_density_congruence(3, 1, None, None) 2/3 :: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Q.local_good_density_congruence(2, 1, None, None) 1 sage: Q.local_good_density_congruence(3, 1, None, None) 8/9 """ ## DIAGNOSTIC verbose(" In local_good_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) verbose(" m = " + str(m)) verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) ## Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] n = self.dim() ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") ## Check that Q is in local normal form -- should replace this with a diagonalization check? ## (it often may not be since the reduction procedure ## often mixes up the order of the valuations...) # #if (self != self.local_normal_form(p)) # print "Warning in local_good_density_congruence: Q is not in local normal form! \n"; ## Decide which routine to use to compute the Good-type density if (p > 2): return self.local_good_density_congruence_odd(p, m, Zvec, NZvec) if (p == 2): return self.local_good_density_congruence_even(m, Zvec, NZvec) raise RuntimeError("\n Error in Local_Good_Density: The 'prime' p = " + str(p) + " is < 2. \n")
def pSelmerGroup(K, S, p, proof=None, debug=False): r""" Return the ``p``-Selmer group `K(S,p)` of the number field ``K`` with respect to the prime ideals in ``S`` INPUT: - ``K`` (number field) -- a number field, or `\QQ`. - ``S`` (list) -- a list of prime ideals in ``K``, or prime numbers when ``K`` is `\QQ`. - ``p`` (prime) -- a prime number. - ``proof`` - if True then compute the class group provably correctly. Default is True. Call :meth:`proof.number_field` to change this default globally. - ``debug`` (boolean, default ``False``) -- debug flag. OUTPUT: (tuple) ``KSp``, ``KSp_gens``, ``from_KSp``, ``to_KSp`` where - ``KSp`` is an abstract vector space over `GF(p)` isomorphic to `K(S,p)`; - ``KSp_gens`` is a list of elements of `K^*` generating `K(S,p)`; - ``from_KSp`` is a function from ``KSp`` to `K^*` implementing the isomorphism from the abstract `K(S,p)` to `K(S,p)` as a subgroup of `K^*/(K^*)^p`; - ``to_KSP`` is a partial function from `K^*` to ``KSp``, defined on elements `a` whose image in `K^*/(K^*)^p` lies in `K(S,p)`, mapping them via the inverse isomorphism to the abstract vector space ``KSp``. ALGORITHM: The list of generators of `K(S,p)` is the concatenation of three sublists, called ``alphalist``, ``betalist`` and ``ulist`` in the code. Only ``alphalist`` depends on the primes in `S`. - ``ulist`` is a basis for `U/U^p` where `U` is the unit group. This is the list of fundamental units, including the generator of the group of roots of unity if its order is divisible by `p`. These have valuation `0` at all primes. - ``betalist`` is a list of the generators of the `p`'th powers of ideals which generate the `p`-torsion in the class group (so is empty if the class number is prime to `p`). These have valuation divisible by `p` at all primes. - ``alphalist`` is a list of generators for each ideal `A` in a basis of those ideals supported on `S` (modulo `p`'th powers of ideals) which are `p`'th powers in the class group. We find `B` such that `A/B^p` is principal and take a generator of it, for each `A` in a generating set. As a special case, if all the ideals in `S` are principal then ``alphalist`` is a list of their generators. The map from the abstract space to `K^*` is easy: we just take the product of the generators to powers given by the coefficient vector. No attempt is made to reduce the resulting product modulo `p`'th powers. The reverse map is more complicated. Given `a\in K^*`: - write the principal ideal `(a)` in the form `AB^p` with `A` supported by `S` and `p`'th power free. If this fails, then `a` does not represent an element of `K(S,p)` and an error is raised. - set `I_S` to be the group of ideals spanned by `S` mod `p`'th powers, and `I_{S,p}` the subgroup of `I_S` which maps to `0` in `C/C^p`. - Convert `A` to an element of `I_{S,p}`, hence find the coordinates of `a` with respect to the generators in ``alphalist``. - after dividing out by `A`, now `(a)=B^p` (with a different `a` and `B`). Write the ideal class `[B]`, whose `p`'th power is trivial, in terms of the generators of `C[p]`; then `B=(b)B_1`, where the coefficients of `B_1` with respect to generators of `C[p]` give the coordinates of the result with respect to the generators in ``betalist``. - after dividing out by `B`, and by `b^p`, we now have `(a)=(1)`, so `a` is a unit, which can be expressed in terms of the unit generators. EXAMPLES: Over `\QQ` the the unit contribution is trivial unless `p=2` and the class group is trivial:: sage: from sage.rings.number_field.selmer_group import pSelmerGroup sage: QS2, gens, fromQS2, toQS2 = pSelmerGroup(QQ, [2,3], 2) sage: QS2 Vector space of dimension 3 over Finite Field of size 2 sage: gens [2, 3, -1] sage: a = fromQS2([1,1,1]); a.factor() -1 * 2 * 3 sage: toQS2(-6) (1, 1, 1) sage: QS3, gens, fromQS3, toQS3 = pSelmerGroup(QQ, [2,13], 3) sage: QS3 Vector space of dimension 2 over Finite Field of size 3 sage: gens [2, 13] sage: a = fromQS3([5,4]); a.factor() 2^5 * 13^4 sage: toQS3(a) (2, 1) sage: toQS3(a) == QS3([5,4]) True A real quadratic field with class number 2, where the fundamental unit is a generator, and the class group provides another generator when `p=2`:: sage: K.<a> = QuadraticField(-5) sage: K.class_number() 2 sage: P2 = K.ideal(2, -a+1) sage: P3 = K.ideal(3, a+1) sage: P5 = K.ideal(a) sage: KS2, gens, fromKS2, toKS2 = pSelmerGroup(K, [P2, P3, P5], 2) sage: KS2 Vector space of dimension 4 over Finite Field of size 2 sage: gens [a + 1, a, 2, -1] Each generator must have even valuation at primes not in `S`:: sage: [K.ideal(g).factor() for g in gens] [(Fractional ideal (2, a + 1)) * (Fractional ideal (3, a + 1)), Fractional ideal (-a), (Fractional ideal (2, a + 1))^2, 1] sage: toKS2(10) (0, 0, 1, 1) sage: fromKS2([0,0,1,1]) -2 sage: K(10/(-2)).is_square() True sage: KS3, gens, fromKS3, toKS3 = pSelmerGroup(K, [P2, P3, P5], 3) sage: KS3 Vector space of dimension 3 over Finite Field of size 3 sage: gens [1/2, 1/4*a + 1/4, a] The ``to`` and ``from`` maps are inverses of each other:: sage: K.<a> = QuadraticField(-5) sage: S = K.ideal(30).support() sage: KS2, gens, fromKS2, toKS2 = pSelmerGroup(K, S, 2) sage: KS2 Vector space of dimension 5 over Finite Field of size 2 sage: assert all(toKS2(fromKS2(v))==v for v in KS2) sage: KS3, gens, fromKS3, toKS3 = pSelmerGroup(K, S, 3) sage: KS3 Vector space of dimension 4 over Finite Field of size 3 sage: assert all(toKS3(fromKS3(v))==v for v in KS3) """ from sage.rings.number_field.number_field import proof_flag from sage.modules.free_module import VectorSpace from sage.sets.set import Set proof = proof_flag(proof) # Input check: p and all P in S must be prime. Remove any repeats in S. S = list(Set(S)) if not all(P.is_prime() for P in S): raise ValueError("elements of S must all be prime") if not p.is_prime(): raise ValueError("p must be prime") F = GF(p) # Step 1. The unit contribution: all fundamental units, and also the # generating root of unity if its order is a multiple of p; we just # take generators of U/U^p. These have valuation 0 everywhere. hK = 1 if K == QQ else K.class_number(proof=proof) C = K.class_group() if K == QQ else K.class_group(proof=proof) hKp = (hK%p == 0) # flag whether the class number is divisible by p if K == QQ: if p == 2: ulist = [QQ(-1)] else: ulist = [] else: U = K.unit_group(proof=proof) ulist = U.gens_values() if U.zeta_order() % p: ulist = ulist[1:] if debug: print("{} generators in ulist = {}".format(len(ulist),ulist)) # Step 2. The class group contribution: generators of the p'th # powers of ideals generating the p-torsion in the class group. # These have valuation divisible by p everywhere. if hKp: betalist = [_ideal_generator(c ** n) for c, n in zip(C.gens_ideals(), C.gens_orders()) if n % p == 0] else: betalist = [] if debug: print("{} generators in betalist = {}".format(len(betalist),betalist)) # Step 3. The part depending on S: one generator for each ideal A # in a basis of those ideals supported on S (modulo p'th powers of # ideals) which is a p'th power in the class group. We find B # such that A/B^p is principal and take a generator of that, for # each A in a generating set. # As a special case, when the class number is 1 we just take # generators of the primes in S. if hK > 1: T, f = basis_for_p_cokernel(S, C, p) alphalist = [_ideal_generator(I / _root_ideal(I, C, p) ** p) for I in T] else: f = lambda x:x alphalist = [_ideal_generator(P) for P in S] if debug: print("{} generators in alphalist = {}".format(len(alphalist), alphalist)) # Now we have the generators of K(S,p), and define K(S,p) as an # abstract vector space: KSp_gens = alphalist + betalist + ulist KSp = VectorSpace(GF(p), len(KSp_gens)) if debug: print("Generators of K(S,p) = {} (dimension {})".format(KSp_gens, len(KSp_gens))) # Now we define maps in each direction between the abstract space and K^*. # Define the easy map from KSp into K^*: def from_KSp(v): return prod([g ** vi for g, vi in zip(KSp_gens, v)], K(1)) # Define the hard map from (a subgroup of) K^* to KSp: def to_KSp(a): # Check that a is in K(S,p): if not a: raise ValueError("argument {} should be nonzero".format(a)) try: a = K(a) except ValueError: raise ValueError("argument {} should be in {}".format(a, K)) if not all(P in S or a.valuation(P) % p == 0 for P in a.support()): raise ValueError("argument {} should have valuations divisible by {} at all primes in {}".format(a, p, S)) # 1. (a) is a p'th power mod ideals in S, say (a)=AB^p, where # A is supported on S and is a linear combination of the # ideals T above. Find the exponents of the P_i in S in A: S_vals = [F(a.valuation(P)) for P in S] avec = list(f(S_vals)) # coordinates of A w.r.t ideals in T (mod p'th powers) a1 = prod((alpha ** e for alpha, e in zip(alphalist,avec)), K(1)) a /= a1 if debug: print("alpha component is {} with coords {}".format(a1,avec)) if K == QQ: print("continuing with quotient {} whose ideal should be a {}'th power: {}".format(a,p,a.factor())) else: print("continuing with quotient {} whose ideal should be a {}'th power: {}".format(a,p,K.ideal(a).factor())) # 2. Now (a) is a p'th power, say (a)=B^p. # Find B and the exponents of [B] w.r.t. basis of C[p]: supp = a.support() vals = [a.valuation(P) for P in supp] if debug: assert all(v % p == 0 for v in vals) one = K(1) if K == QQ else K.ideal(1) aa = a.abs() if K == QQ else K.ideal(a) B = prod((P ** (v // p) for P, v in zip(supp,vals)), one) if debug: assert B ** p == aa print("B={}".format(B)) print("a={}".format(a)) if hKp: bvec = _coords_in_C_p(B, C, p) a2 = prod((beta ** e for beta, e in zip(betalist, bvec)), K(1)) a /= a2 supp = a.support() vals = [a.valuation(P) for P in supp] if debug: assert all(v % p == 0 for v in vals) B = prod((P ** (v // p) for P, v in zip(supp, vals)), one) if debug: assert B ** p == aa else: bvec = [] a2 = 1 if debug: print("beta component is {} with coords {}".format(a2,bvec)) print("continuing with quotient {} which should be a p'th power times a unit".format(a)) # 3. Now (a) = (c)^p for some c, so a/c^p is a unit if K != QQ: assert B.is_principal() if debug: print("B={}".format(B)) a3 = B if K==QQ else _ideal_generator(B) if debug: print("a3={}".format(a3)) a /= a3 ** p if debug: print("dividing by {}th power of {}".format(p,a3)) print("continuing with quotient {} which should be a unit".format(a)) #4. Now a is a unit # NB not a.is_unit which is true for all a in K^*. One could # also test K.ring_of_integers()(a).is_unit(). if debug: if K == QQ: assert a.abs()==1 else: assert K.ideal(a).is_one() if K == QQ: if p == 2: cvec = [1] if a == -1 else [0] else: cvec = [] else: cvec = coords_in_U_mod_p(a,U,p) if debug: print("gamma component has coords {}".format(cvec)) return KSp(avec + bvec + cvec) return KSp, KSp_gens, from_KSp, to_KSp