def zeta__exact(n): r""" Returns the exact value of the Riemann Zeta function The argument must be a critical value, namely either positive even or negative odd. See for example [Iwa1972]_, p13, Special value of `\zeta(2k)` EXAMPLES: Let us test the accuracy for negative special values:: sage: RR = RealField(100) sage: for i in range(1,10): ....: print("zeta({}): {}".format(1-2*i, RR(zeta__exact(1-2*i)) - zeta(RR(1-2*i)))) zeta(-1): 0.00000000000000000000000000000 zeta(-3): 0.00000000000000000000000000000 zeta(-5): 0.00000000000000000000000000000 zeta(-7): 0.00000000000000000000000000000 zeta(-9): 0.00000000000000000000000000000 zeta(-11): 0.00000000000000000000000000000 zeta(-13): 0.00000000000000000000000000000 zeta(-15): 0.00000000000000000000000000000 zeta(-17): 0.00000000000000000000000000000 Let us test the accuracy for positive special values:: sage: all(abs(RR(zeta__exact(2*i))-zeta(RR(2*i))) < 10**(-28) for i in range(1,10)) True TESTS:: sage: zeta__exact(4) 1/90*pi^4 sage: zeta__exact(-3) 1/120 sage: zeta__exact(0) -1/2 sage: zeta__exact(5) Traceback (most recent call last): ... TypeError: n must be a critical value (i.e. even > 0 or odd < 0) REFERENCES: - [Iwa1972]_ - [IR1990]_ - [Was1997]_ """ if n < 0: return bernoulli(1 - n) / (n - 1) elif n > 1: if (n % 2 == 0): return ZZ(-1)**(n // 2 + 1) * ZZ(2)**( n - 1) * pi**n * bernoulli(n) / factorial(n) else: raise TypeError( "n must be a critical value (i.e. even > 0 or odd < 0)") elif n == 1: return infinity elif n == 0: return QQ((-1, 2))
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 == None: Zvec = [] n = self.dim() ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec != None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec != 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 == True) or (m % p != 0): return 0 ## Check some conditions for no bad-type I solutions to exist if (NZvec != 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 == 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 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 == None: Zvec = [] n = self.dim() ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec != None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec != 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 != 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 == 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))
from sage.rings.rational_field import QQ from ore_algebra import DifferentialOperators IVP = collections.namedtuple("IVP", ["dop", "ini"]) DiffOps_a, a, Da = DifferentialOperators(QQ, 'a') koutschan1 = IVP( dop=(1315013644371957611900 * a**2 + 263002728874391522380 * a + 13150136443719576119) * Da**3 + (2630027288743915223800 * a**2 + 16306169190212274387560 * a + 1604316646133788286518) * Da**2 + (1315013644371957611900 * a**2 - 39881765316802329075320 * a + 35449082663034775873349) * Da + (-278967152068515080896550 + 6575068221859788059500 * a), ini=[ QQ(5494216492395559) / 3051757812500000000000000000000, QQ(6932746783438351) / 610351562500000000000000000000, 1 / QQ(2) * QQ(1142339612827789) / 19073486328125000000000000000 ]) y, z = PolynomialRing(QQ, ['y', 'z']).gens() salvy1_pol = (16 * y**6 * z**2 + 8 * y**5 * z**3 + y**4 * z**4 + 128 * y**5 * z**2 + 48 * y**4 * z**3 + 4 * y**3 * z**4 + 32 * y**5 * z + 372 * y**4 * z**2 + 107 * y**3 * z**3 + 6 * y**2 * z**4 + 88 * y**4 * z + 498 * y**3 * z**2 + 113 * y**2 * z**3 + 4 * y * z**4 + 16 * y**4 + 43 * y**3 * z + 311 * y**2 * z**2 + 57 * y * z**3 + z**4 + 24 * y**3 - 43 * y**2 * z + 72 * y * z**2 + 11 * z**3 + 12 * y**2 - 30 * y * z - z**2 + 2 * y) DiffOps_z, z, Dz = DifferentialOperators(QQ, 'z') salvy1_dop = (
def Kitaoka_mass_at_2(self): """ Returns the local mass of the quadratic form when `p=2`, according to Theorem 5.6.3 on pp108--9 of Kitaoka's Book "The Arithmetic of Quadratic Forms". INPUT: none OUTPUT: a rational number > 0 EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.Kitaoka_mass_at_2() ## WARNING: WE NEED TO CHECK THIS CAREFULLY! 1/2 """ ## Make a 0-dim'l quadratic form (for initialization purposes) Null_Form = copy.deepcopy(self) Null_Form.__init__(ZZ, 0) ## Step 0: Compute Jordan blocks and bounds of the scales to keep track of Jordan_Blocks = self.jordan_blocks_by_scale_and_unimodular(2) scale_list = [B[0] for B in Jordan_Blocks] s_min = min(scale_list) s_max = max(scale_list) ## Step 1: Compute dictionaries of the diagonal block and 2x2 block for each scale diag_dict = dict( (i, Null_Form) for i in range(s_min - 2, s_max + 4)) ## Initialize with the zero form dim2_dict = dict( (i, Null_Form) for i in range(s_min, s_max + 4)) ## Initialize with the zero form for (s, L) in Jordan_Blocks: i = 0 while (i < L.dim() - 1) and (L[i, i + 1] == 0): ## Find where the 2x2 blocks start i = i + 1 if i < (L.dim() - 1): diag_dict[s] = L.extract_variables(range(i)) ## Diagonal Form dim2_dict[s + 1] = L.extract_variables(range( i, L.dim())) ## Non-diagonal Form else: diag_dict[s] = L #print "diag_dict = ", diag_dict #print "dim2_dict = ", dim2_dict #print "Jordan_Blocks = ", Jordan_Blocks ################## START EDITING HERE ################## ## Compute q := sum of the q_j q = 0 for j in range(s_min, s_max + 1): if diag_dict[j].dim( ) > 0: ## Check that N_j is odd (i.e. rep'ns an odd #) if diag_dict[j + 1].dim() == 0: q += Jordan_Blocks[j][1].dim( ) ## When N_{j+1} is "even", add n_j else: q += Jordan_Blocks[j][1].dim( ) + 1 ## When N_{j+1} is "odd", add n_j + 1 ## Compute P = product of the P_j P = QQ(1) for j in range(s_min, s_max + 1): tmp_m = dim2_dict[j].dim() / 2 P *= prod([QQ(1) - QQ(4**(-k)) for j in range(1, tmp_m + 1)]) ## Compute the product E := prod_j (1 / E_j) E = QQ(1) for j in range(s_min - 1, s_max + 2): if (diag_dict[j-1].dim() == 0) and (diag_dict[j+1].dim() == 0) and \ ((diag_dict[j].dim() != 2) or (((diag_dict[j][0,0] - diag_dict[j][1,1]) % 4) != 0)): ## Deal with the complicated case: tmp_m = dim2_dict[j].dim() / 2 if dim2_dict[j].is_hyperbolic(2): E *= 2 / (1 + 2**(-tmp_m)) else: E *= 2 / (1 - 2**(-tmp_m)) else: E *= 2 ## DIAGNOSTIC #print "\nFinal Summary:" #print "nu =", nu #print "q = ", q #print "P = ", P #print "E = ", E ## Compute the exponent w w = QQ(0) for j in range(s_min, s_max + 1): n_j = Jordan_Blocks[j][1].dim() for k in range(j + 1, s_max + 1): n_k = Jordan_Blocks[k][1].dim() w += j * n_j * (n_k + QQ(n_j + 1) / 2) ## Step 5: Compute the local mass for the prime 2. mass_at_2 = (QQ(2)**(w - q)) * P * E return mass_at_2
def braid_monodromy(f): r""" Compute the braid monodromy of a projection of the curve defined by a polynomial INPUT: - ``f`` -- a polynomial with two variables, over a number field with an embedding in the complex numbers. OUTPUT: A list of braids. The braids correspond to paths based in the same point; each of this paths is the conjugated of a loop around one of the points in the discriminant of the projection of ``f``. .. NOTE:: The projection over the `x` axis is used if there are no vertical asymptotes. Otherwise, a linear change of variables is done to fall into the previous case. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy sage: R.<x,y> = QQ[] sage: f = (x^2-y^3)*(x+3*y-5) sage: braid_monodromy(f) # optional - sirocco [s1*s0*(s1*s2)^2*s0*s2^2*s0^-1*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*(s0*s2^-1*s1*s2*s1*s2^-1)^2*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, s1*s0*s2*s0^-1*s2*s1^-1] """ global roots_interval_cache (x, y) = f.parent().gens() F = f.base_ring() g = f.radical() d = g.degree(y) while not g.coefficient(y**d) in F: g = g.subs({x: x + y}) d = g.degree(y) disc = discrim(g) V = corrected_voronoi_diagram(tuple(disc)) G = Graph() for reg in V.regions().values(): G = G.union(reg.vertex_graph()) E = Graph() for reg in V.regions().values(): if reg.rays() or reg.lines(): E = E.union(reg.vertex_graph()) p = next(E.vertex_iterator()) geombasis = geometric_basis(G, E, p) segs = set([]) for p in geombasis: for s in zip(p[:-1], p[1:]): if (s[1], s[0]) not in segs: segs.add((s[0], s[1])) I = QQbar.gen() segs = [(a[0] + I * a[1], b[0] + I * b[1]) for (a, b) in segs] vertices = list(set(flatten(segs))) tocacheverts = [(g, v) for v in vertices] populate_roots_interval_cache(tocacheverts) gfac = g.factor() try: braidscomputed = list( braid_in_segment([(gfac, seg[0], seg[1]) for seg in segs])) except ChildProcessError: # hack to deal with random fails first time braidscomputed = list( braid_in_segment([(gfac, seg[0], seg[1]) for seg in segs])) segsbraids = dict() for braidcomputed in braidscomputed: seg = (braidcomputed[0][0][1], braidcomputed[0][0][2]) beginseg = (QQ(seg[0].real()), QQ(seg[0].imag())) endseg = (QQ(seg[1].real()), QQ(seg[1].imag())) b = braidcomputed[1] segsbraids[(beginseg, endseg)] = b segsbraids[(endseg, beginseg)] = b.inverse() B = b.parent() result = [] for path in geombasis: braidpath = B.one() for i in range(len(path) - 1): x0 = tuple(path[i].vector()) x1 = tuple(path[i + 1].vector()) braidpath = braidpath * segsbraids[(x0, x1)] result.append(braidpath) return result
def _closed_form(hyp): a, b, z = hyp.operands() a, b = a.operands(), b.operands() p, q = len(a), len(b) if z == 0: return Integer(1) if p == q == 0: return exp(z) if p == 1 and q == 0: return (1 - z) ** (-a[0]) if p == 0 and q == 1: # TODO: make this require only linear time def _0f1(b, z): F12 = cosh(2 * sqrt(z)) F32 = sinh(2 * sqrt(z)) / (2 * sqrt(z)) if 2 * b == 1: return F12 if 2 * b == 3: return F32 if 2 * b > 3: return ((b - 2) * (b - 1) / z * (_0f1(b - 2, z) - _0f1(b - 1, z))) if 2 * b < 1: return (_0f1(b + 1, z) + z / (b * (b + 1)) * _0f1(b + 2, z)) raise ValueError # Can evaluate 0F1 in terms of elementary functions when # the parameter is a half-integer if 2 * b[0] in ZZ and b[0] not in ZZ: return _0f1(b[0], z) # Confluent hypergeometric function if p == 1 and q == 1: aa, bb = a[0], b[0] if aa * 2 == 1 and bb * 2 == 3: t = sqrt(-z) return sqrt(pi) / 2 * erf(t) / t if a == 1 and b == 2: return (exp(z) - 1) / z n, m = aa, bb if n in ZZ and m in ZZ and m > 0 and n > 0: rf = rising_factorial if m <= n: return (exp(z) * sum(rf(m - n, k) * (-z) ** k / factorial(k) / rf(m, k) for k in range(n - m + 1))) else: T = sum(rf(n - m + 1, k) * z ** k / (factorial(k) * rf(2 - m, k)) for k in range(m - n)) U = sum(rf(1 - n, k) * (-z) ** k / (factorial(k) * rf(2 - m, k)) for k in range(n)) return (factorial(m - 2) * rf(1 - m, n) * z ** (1 - m) / factorial(n - 1) * (T - exp(z) * U)) if p == 2 and q == 1: R12 = QQ('1/2') R32 = QQ('3/2') def _2f1(a, b, c, z): """ Evaluation of 2F1(a, b; c; z), assuming a, b, c positive integers or half-integers """ if b == c: return (1 - z) ** (-a) if a == c: return (1 - z) ** (-b) if a == 0 or b == 0: return Integer(1) if a > b: a, b = b, a if b >= 2: F1 = _2f1(a, b - 1, c, z) F2 = _2f1(a, b - 2, c, z) q = (b - 1) * (z - 1) return (((c - 2 * b + 2 + (b - a - 1) * z) * F1 + (b - c - 1) * F2) / q) if c > 2: # how to handle this case? if a - c + 1 == 0 or b - c + 1 == 0: raise NotImplementedError F1 = _2f1(a, b, c - 1, z) F2 = _2f1(a, b, c - 2, z) r1 = (c - 1) * (2 - c - (a + b - 2 * c + 3) * z) r2 = (c - 1) * (c - 2) * (1 - z) q = (a - c + 1) * (b - c + 1) * z return (r1 * F1 + r2 * F2) / q if (a, b, c) == (R12, 1, 2): return (2 - 2 * sqrt(1 - z)) / z if (a, b, c) == (1, 1, 2): return -log(1 - z) / z if (a, b, c) == (1, R32, R12): return (1 + z) / (1 - z) ** 2 if (a, b, c) == (1, R32, 2): return 2 * (1 / sqrt(1 - z) - 1) / z if (a, b, c) == (R32, 2, R12): return (1 + 3 * z) / (1 - z) ** 3 if (a, b, c) == (R32, 2, 1): return (2 + z) / (2 * (sqrt(1 - z) * (1 - z) ** 2)) if (a, b, c) == (2, 2, 1): return (1 + z) / (1 - z) ** 3 raise NotImplementedError aa, bb = a cc, = b if z == 1: return (gamma(cc) * gamma(cc - aa - bb) / gamma(cc - aa) / gamma(cc - bb)) if ((aa * 2) in ZZ and (bb * 2) in ZZ and (cc * 2) in ZZ and aa > 0 and bb > 0 and cc > 0): try: return _2f1(aa, bb, cc, z) except NotImplementedError: pass return hyp
def H_value(self, p, f, t, ring=None): """ Return the trace of the Frobenius, computed in terms of Gauss sums using the hypergeometric trace formula. INPUT: - `p` -- a prime number - `f` -- an integer such that `q = p^f` - `t` -- a rational parameter - ``ring`` -- optional (default ``UniversalCyclotomicfield``) The ring could be also ``ComplexField(n)`` or ``QQbar``. OUTPUT: an integer .. WARNING:: This is apparently working correctly as can be tested using ComplexField(70) as value ring. Using instead UniversalCyclotomicfield, this is much slower than the `p`-adic version :meth:`padic_H_value`. EXAMPLES: With values in the UniversalCyclotomicField (slow):: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(alpha_beta=([1/2]*4,[0]*4)) sage: [H.H_value(3,i,-1) for i in range(1,3)] [0, -12] sage: [H.H_value(5,i,-1) for i in range(1,3)] [-4, 276] sage: [H.H_value(7,i,-1) for i in range(1,3)] # not tested [0, -476] sage: [H.H_value(11,i,-1) for i in range(1,3)] # not tested [0, -4972] sage: [H.H_value(13,i,-1) for i in range(1,3)] # not tested [-84, -1420] With values in ComplexField:: sage: [H.H_value(5,i,-1, ComplexField(60)) for i in range(1,3)] [-4, 276] REFERENCES: - [BeCoMe]_ (Theorem 1.3) - [Benasque2009]_ """ alpha = self._alpha beta = self._beta if 0 in alpha: H = self.swap_alpha_beta() return (H.H_value(p, f, ~t, ring)) if ring is None: ring = UniversalCyclotomicField() t = QQ(t) gamma = self.gamma_array() q = p**f m = {r: beta.count(QQ((r, q - 1))) for r in range(q - 1)} D = -min(self.zigzag(x, flip_beta=True) for x in alpha + beta) # also: D = (self.weight() + 1 - m[0]) // 2 M = self.M_value() Fq = GF(q) gen = Fq.multiplicative_generator() zeta_q = ring.zeta(q - 1) tM = Fq(M / t) for k in range(q - 1): if gen**k == tM: teich = zeta_q**k break gauss_table = [gauss_sum(zeta_q**r, Fq) for r in range(q - 1)] sigma = sum(q**(D + m[0] - m[r]) * prod(gauss_table[(-v * r) % (q - 1)]**gv for v, gv in gamma.items()) * teich**r for r in range(q - 1)) resu = ZZ(-1)**m[0] / (1 - q) * sigma if not ring.is_exact(): resu = resu.real_part().round() return resu
def padic_H_value(self, p, f, t, prec=20): """ Return the `p`-adic trace of Frobenius, computed using the Gross-Koblitz formula. INPUT: - `p` -- a prime number - `f` -- an integer such that `q = p^f` - `t` -- a rational parameter - ``prec`` -- precision (optional, default 20) OUTPUT: an integer EXAMPLES: From Benasque report [Benasque2009]_, page 8:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(alpha_beta=([1/2]*4,[0]*4)) sage: [H.padic_H_value(3,i,-1) for i in range(1,3)] [0, -12] sage: [H.padic_H_value(5,i,-1) for i in range(1,3)] [-4, 276] sage: [H.padic_H_value(7,i,-1) for i in range(1,3)] [0, -476] sage: [H.padic_H_value(11,i,-1) for i in range(1,3)] [0, -4972] From [Roberts2015]_ (but note conventions regarding `t`):: sage: H = Hyp(gamma_list=[-6,-1,4,3]) sage: t = 189/125 sage: H.padic_H_value(13,1,1/t) 0 REFERENCES: - [MagmaHGM]_ """ alpha = self._alpha beta = self._beta if 0 in alpha: H = self.swap_alpha_beta() return (H.padic_H_value(p, f, ~t, prec)) t = QQ(t) gamma = self.gamma_array() q = p**f m = {r: beta.count(QQ((r, q - 1))) for r in range(q - 1)} M = self.M_value() D = -min(self.zigzag(x, flip_beta=True) for x in alpha + beta) # also: D = (self.weight() + 1 - m[0]) // 2 gauss_table = [ padic_gauss_sum(r, p, f, prec, factored=True) for r in range(q - 1) ] p_ring = Zp(p, prec=prec) teich = p_ring.teichmuller(M / t) sigma = sum(q**(D + m[0] - m[r]) * (-p)**(sum(gauss_table[(v * r) % (q - 1)][0] * gv for v, gv in gamma.items()) // (p - 1)) * prod(gauss_table[(v * r) % (q - 1)][1]**gv for v, gv in gamma.items()) * teich**r for r in range(q - 1)) resu = ZZ(-1)**m[0] / (1 - q) * sigma return IntegerModRing(p**prec)(resu).lift_centered()
def braid_in_segment(f, x0, x1): """ Return the braid formed by the `y` roots of ``f`` when `x` moves from ``x0`` to ``x1``. INPUT: - ``f`` -- a polynomial in two variables - ``x0`` -- a complex number - ``x1`` -- a complex number OUTPUT: A braid. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import braid_in_segment # optional - sirocco sage: R.<x,y> = QQ[] sage: f = x^2 + y^3 sage: x0 = CC(1,0) sage: x1 = CC(1, 0.5) sage: braid_in_segment(f, x0, x1) # optional - sirocco s1 TESTS: Check that :trac:`26503` is fixed:: sage: wp = QQ['t']([1, 1, 1]).roots(QQbar)[0][0] sage: Kw.<wp> = NumberField(wp.minpoly(), embedding=wp) sage: R.<x, y> = Kw[] sage: z = -wp - 1 sage: f = y*(y + z)*x*(x - 1)*(x - y)*(x + z*y - 1)*(x + z*y + wp) sage: from sage.schemes.curves import zariski_vankampen as zvk sage: g = f.subs({x: x + 2*y}) sage: p1 = QQbar(sqrt(-1/3)) sage: p2 = QQbar(1/2+sqrt(-1/3)/2) sage: B = zvk.braid_in_segment(g,CC(p1),CC(p2)) # optional - sirocco sage: B.left_normal_form() # optional - sirocco (1, s5) """ CC = ComplexField(64) (x, y) = f.variables() I = QQbar.gen() X0 = QQ(x0.real()) + I * QQ(x0.imag()) X1 = QQ(x1.real()) + I * QQ(x1.imag()) F0 = QQbar[y](f(X0, y)) y0s = F0.roots(multiplicities=False) strands = [followstrand(f, x0, x1, CC(a)) for a in y0s] complexstrands = [[(a[0], CC(a[1], a[2])) for a in b] for b in strands] centralbraid = braid_from_piecewise(complexstrands) initialstrands = [] y0aps = [c[0][1] for c in complexstrands] used = [] for y0ap in y0aps: distances = [((y0ap - y0).norm(), y0) for y0 in y0s] y0 = sorted(distances)[0][1] if y0 in used: raise ValueError("different roots are too close") used.append(y0) initialstrands.append([(0, y0), (1, y0ap)]) initialbraid = braid_from_piecewise(initialstrands) F1 = QQbar[y](f(X1, y)) y1s = F1.roots(multiplicities=False) finalstrands = [] y1aps = [c[-1][1] for c in complexstrands] used = [] for y1ap in y1aps: distances = [((y1ap - y1).norm(), y1) for y1 in y1s] y1 = sorted(distances)[0][1] if y1 in used: raise ValueError("different roots are too close") used.append(y1) finalstrands.append([(0, y1ap), (1, y1)]) finallbraid = braid_from_piecewise(finalstrands) return initialbraid * centralbraid * finallbraid
def _adjust_bounds(self): r""" TESTS:: sage: from surface_dynamics import FatGraphs sage: FatGraphs(g=0, nv_max=4, vertex_min_degree=3) Traceback (most recent call last): ... ValueError: infinitely many fat graphs sage: FatGraphs(g=0, nf_max=4, vertex_min_degree=3) FatGraphs(g=0, nf_min=2, nf_max=4, ne_min=1, ne_max=4, nv_min=1, nv_max=3, vertex_min_degree=3) sage: F = FatGraphs(g=0, nv=2, ne=1, nf=2) sage: F EmptySet sage: F.cardinality_and_weighted_cardinality() (0, 0) sage: FatGraphs(ne=0).list() [FatGraph('()', '()', '()')] sage: FatGraphs(ne=1).list() [FatGraph('(0,1)', '(0,1)', '(0)(1)'), FatGraph('(0)(1)', '(0,1)', '(0,1)')] sage: FatGraphs(ne=2).list() [FatGraph('(0,1,2,3)', '(0,2)(1,3)', '(0,1,2,3)'), FatGraph('(0,2,1)(3)', '(0,1)(2,3)', '(0,2,3)(1)'), FatGraph('(0,2)(1,3)', '(0,1)(2,3)', '(0,3)(1,2)'), FatGraph('(0,3,2,1)', '(0,1)(2,3)', '(0,2)(1)(3)'), FatGraph('(0,2)(1)(3)', '(0,1)(2,3)', '(0,1,2,3)')] """ # variable order: v, e, f, g from sage.geometry.polyhedron.constructor import Polyhedron eqns = [(-2, 1, -1, 1, 2)] # -2 + v - e + f + 2g = 0 ieqs = [ (-self._vmin, 1, 0, 0, 0), # -vim + v >= 0 (-self._emin, 0, 1, 0, 0), # -emin + e >= 0 (-self._fmin, 0, 0, 1, 0), # -fmin + f >= 0 (-self._gmin, 0, 0, 0, 1), # -gmin + g >= 0 (0, -self._vertex_min_degree, 2, 0, 0) ] # -v_min_degree*v + 2e >= 0 if self._vmax is not None: ieqs.append((self._vmax - 1, -1, 0, 0, 0)) # v < vmax if self._emax is not None: ieqs.append((self._emax - 1, 0, -1, 0, 0)) # e < emax if self._fmax is not None: ieqs.append((self._fmax - 1, 0, 0, -1, 0)) # f < fmax if self._gmax is not None: ieqs.append((self._gmax - 1, 0, 0, 0, -1)) # g < gmax P = Polyhedron(ieqs=ieqs, eqns=eqns, ambient_dim=4, base_ring=QQ) if P.is_empty(): self._vmin = self._vmax = 1 self._emin = self._emax = 0 self._fmin = self._fmax = 1 self._gmin = self._gmax = 0 self._vertex_min_degree = 0 return if not P.is_compact(): raise ValueError('infinitely many fat graphs') half = QQ((1, 2)) self._vmin, self._vmax = minmax([v[0] for v in P.vertices_list()]) self._vmin = self._vmin.floor() self._vmax = (self._vmax + half).ceil() self._emin, self._emax = minmax([v[1] for v in P.vertices_list()]) self._emin = self._emin.floor() self._emax = (self._emax + half).ceil() self._fmin, self._fmax = minmax([v[2] for v in P.vertices_list()]) self._fmin = self._fmin.floor() self._fmax = (self._fmax + half).ceil() self._gmin, self._gmax = minmax([v[3] for v in P.vertices_list()]) self._gmin = self._gmin.floor() self._gmax = (self._gmax + half).ceil()
def padic_H_value(self, p, f, t, prec=None): """ Return the `p`-adic trace of Frobenius, computed using the Gross-Koblitz formula. INPUT: - `p` -- a prime number - `f` -- an integer such that `q = p^f` - `t` -- a rational parameter - ``prec`` -- precision (optional, default 20) OUTPUT: an integer EXAMPLES: From Benasque report [Benasque2009]_, page 8:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(alpha_beta=([1/2]*4,[0]*4)) sage: [H.padic_H_value(3,i,-1) for i in range(1,3)] [0, -12] sage: [H.padic_H_value(5,i,-1) for i in range(1,3)] [-4, 276] sage: [H.padic_H_value(7,i,-1) for i in range(1,3)] [0, -476] sage: [H.padic_H_value(11,i,-1) for i in range(1,3)] [0, -4972] From [Roberts2015]_ (but note conventions regarding `t`):: sage: H = Hyp(gamma_list=[-6,-1,4,3]) sage: t = 189/125 sage: H.padic_H_value(13,1,1/t) 0 REFERENCES: - [MagmaHGM]_ """ alpha = self._alpha beta = self._beta if 0 in alpha: H = self.swap_alpha_beta() return H.padic_H_value(p, f, ~t, prec) t = QQ(t) gamma = self.gamma_array() q = p**f # m = {r: beta.count(QQ((r, q - 1))) for r in range(q - 1)} m = defaultdict(lambda: 0) for r in range(q - 1): u = QQ((r, q - 1)) if u in beta: m[r] = beta.count(u) M = self.M_value() D = -min(self.zigzag(x, flip_beta=True) for x in alpha + beta) # also: D = (self.weight() + 1 - m[0]) // 2 if prec is None: prec = (self.weight() * f) // 2 + ceil(log(self.degree(), p)) + 1 # For some reason, working in Qp instead of Zp is much faster; # it appears to avoid some costly conversions. p_ring = Qp(p, prec=prec) teich = p_ring.teichmuller(M / t) gauss_table = [ padic_gauss_sum(r, p, f, prec, factored=True, algorithm='sage', parent=p_ring) for r in range(q - 1) ] sigma = sum( ((-p)**(sum(gauss_table[(v * r) % (q - 1)][0] * gv for v, gv in gamma.items()) // (p - 1)) * prod(gauss_table[(v * r) % (q - 1)][1]**gv for v, gv in gamma.items()) * teich**r) << (f * (D + m[0] - m[r])) for r in range(q - 1)) resu = ZZ(-1)**m[0] / (1 - q) * sigma return IntegerModRing(p**prec)(resu).lift_centered()
def Min(Fun, p, ubRes, conj, all_orbits=False): r""" Local loop for Affine_minimal, where we check minimality at the prime p. First we bound the possible k in our transformations A = zp^k + b. See Theorems 3.3.2 and 3.3.3 in [Molnar]_. INPUT: - ``Fun`` -- a dynamical system on projective space - ``p`` - a prime - ``ubRes`` -- integer, the upper bound needed for Th. 3.3.3 in [Molnar]_ - ``conj`` -- a 2x2 matrix keeping track of the conjugation - ``all_orbits`` -- boolean; whether or not to use ``==`` in the inequalities to find all orbits OUTPUT: - boolean -- ``True`` if ``Fun`` is minimal at ``p``, ``False`` otherwise - a dynamical system on projective space minimal at ``p`` EXAMPLES:: sage: P.<x,y> = ProjectiveSpace(QQ, 1) sage: f = DynamicalSystem_projective([149*x^2 + 39*x*y + y^2, -8*x^2 + 137*x*y + 33*y^2]) sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import Min sage: Min(f, 3, -27000000, matrix(QQ,[[1, 0],[0, 1]])) [ Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to (157*x^2 + 72*x*y + 3*y^2 : -24*x^2 + 121*x*y + 54*y^2) , <BLANKLINE> [3 1] [0 1] ] """ d = Fun.degree() AffFun = Fun.dehomogenize(1) R = AffFun.coordinate_ring() if R.is_field(): #want the polynomial ring not the fraction field R = R.ring() F = R(AffFun[0].numerator()) G = R(AffFun[0].denominator()) dG = G.degree() # all_orbits scales bounds for >= and <= if searching for orbits instead of min model if dG > (d+1)/2: lowerBound = (-2*(G[dG]).valuation(p)/(2*dG - d + 1) + 1).floor() - int(all_orbits) else: lowerBound = (-2*(F[d]).valuation(p)/(d-1) + 1).floor() - int(all_orbits) upperBound = 2*(ubRes.valuation(p)) + int(all_orbits) if upperBound < lowerBound: # There are no possible transformations to reduce the resultant. if not all_orbits: return [Fun, conj] return [] # Looping over each possible k, we search for transformations to reduce # the resultant of F/G all_found = [] k = lowerBound Qb = PolynomialRing(QQ,'b') b = Qb.gen(0) Q = PolynomialRing(Qb,'z') z = Q.gen(0) while k <= upperBound: A = (p**k)*z + b Ft = Q(F(A) - b*G(A)) Gt = Q((p**k)*G(A)) Fcoeffs = Ft.coefficients(sparse=False) Gcoeffs = Gt.coefficients(sparse=False) coeffs = Fcoeffs + Gcoeffs RHS = (d + 1) * k / 2 # If there is some b such that Res(phi^A) < Res(phi), we must have # ord_p(c) > RHS for each c in coeffs. # Make sure constant coefficients in coeffs satisfy the inequality. if all(QQ(c).valuation(p) > RHS - int(all_orbits) for c in coeffs if c.degree() == 0): # Constant coefficients in coeffs have large enough valuation, so # check the rest. We start by checking if simply picking b=0 works. if all(c(0).valuation(p) > RHS - int(all_orbits) for c in coeffs): # A = z*p^k satisfies the inequalities, and F/G is not minimal # "Conjugating by", p,"^", k, "*z +", 0 newconj = matrix(QQ, 2, 2, [p**k, 0, 0, 1]) minFun = Fun.conjugate(newconj) minFun.normalize_coordinates() if not all_orbits: return [minFun, conj*newconj] all_found.append([p, k, 0]) # Otherwise we search if any value of b will work. We start by # finding a minimum bound on the valuation of b that is necessary. # See Theorem 3.3.5 in [Molnar, M.Sc. thesis]. bval = max(bCheck(coeff, RHS, p, b) for coeff in coeffs if coeff.degree() > 0) # We scale the coefficients in coeffs, so that we may assume # ord_p(b) is at least 0 scaledCoeffs = [coeff(b*(p**bval)) for coeff in coeffs] # We now scale the inequalities, ord_p(coeff) > RHS, so that # coeff is in ZZ[b] scale = QQ(max(coeff.denominator() for coeff in scaledCoeffs)) normalizedCoeffs = [coeff * scale for coeff in scaledCoeffs] scaleRHS = RHS + scale.valuation(p) # We now search for integers that satisfy the inequality # ord_p(coeff) > RHS. See Lemma 3.3.6 in [Molnar, M.Sc. thesis]. bound = (scaleRHS + 1 - int(all_orbits)).floor() all_blift = blift(normalizedCoeffs, bound, p, k, all_orbits=all_orbits) # If bool is true after lifting, we have a solution b, and F/G # is not minimal. for boolval, sol in all_blift: if boolval: #Rescale, conjugate and return new map bsol = QQ(sol * (p**bval)) #only add 'minimal orbit element' while bsol.abs() >= p**k: if bsol < 0: bsol += p**k else: bsol -= p**k #"Conjugating by ", p,"^", k, "*z +", bsol newconj = matrix(QQ, 2, 2, [p**k, bsol, 0, 1]) minFun = Fun.conjugate(newconj) minFun.normalize_coordinates() if not all_orbits: return [minFun, conj*newconj] if [p,k,bsol] not in all_found: all_found.append([p, k, bsol]) k = k + 1 if not all_orbits: return [Fun, conj] return all_found
def blift(LF, Li, p, k, S=None, all_orbits=False): r""" Search for a solution to the given list of inequalities. If found, lift the solution to an appropriate valuation. See Lemma 3.3.6 in [Molnar]_ INPUT: - ``LF`` -- a list of integer polynomials in one variable (the normalized coefficients) - ``Li`` -- an integer, the bound on coefficients - ``p`` -- a prime - ``k`` -- the scaling factor that makes the solution a ``p``-adic integer - ``S`` -- polynomial ring to use - ``all_orbits`` -- boolean; whether or not to use ``==`` in the inequalities to find all orbits OUTPUT: - boolean -- whether or not the lift is successful - integer -- the lift EXAMPLES:: sage: R.<b> = PolynomialRing(QQ) sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import blift sage: blift([8*b^3 + 12*b^2 + 6*b + 1, 48*b^2 + 483*b + 117, 72*b + 1341,\ ....: -24*b^2 + 411*b + 99, -144*b + 1233, -216*b], 2, 3, 2) [[True, 4]] """ P = LF[0].parent() #Determine which inequalities are trivial, and scale the rest, so that we only lift #as many times as needed. keepScaledIneqs = [scale(P(coeff),Li,p) for coeff in LF if coeff != 0] keptVals = [i[2] for i in keepScaledIneqs if i[0]] if keptVals: # Determine the valuation to lift until. # liftval = max(keptVals) pass else: #All inequalities are satisfied. if all_orbits: return [[True, t] for t in range(p)] return [[True, 1]] if S is None: S = PolynomialRing(Zmod(p), 'b') keptScaledIneqs = [S(i[1]) for i in keepScaledIneqs if i[0]] #We need a solution for each polynomial on the left hand side of the inequalities, #so we need only find a solution for their gcd. g = gcd(keptScaledIneqs) rts = g.roots(multiplicities=False) good = [] for r in rts: #Recursively try to lift each root r_initial = QQ(r) newInput = P([r_initial, p]) LG = [F(newInput) for F in LF] new_good = blift(LG, Li, p, k, S=S) for lift,lifted in new_good: if lift: #Lift successful. if not all_orbits: return [[True, r_initial + p*lifted]] #only need up to SL(2,ZZ) equivalence #this helps control the size of the resulting coefficients if r_initial + p*lifted < p**k: good.append([True, r_initial + p*lifted]) else: new_r = r_initial + p*lifted - p**k while new_r > p**k: new_r -= p**k if [True, new_r] not in good: good.append([True, new_r]) if good: return good #Lift non successful. return [[False,0]]
def roots_interval(f, x0): """ Find disjoint intervals that isolate the roots of a polynomial for a fixed value of the first variable. INPUT: - ``f`` -- a bivariate squarefree polynomial - ``x0`` -- a value where the first coordinate will be fixed The intervals are taken as big as possible to be able to detect when two approximate roots of `f(x_0, y)` correspond to the same exact root. The result is given as a dictionary, where the keys are approximations to the roots with rational real and imaginary parts, and the values are intervals containing them. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import roots_interval sage: R.<x,y> = QQ[] sage: f = y^3 - x^2 sage: ri = roots_interval(f, 1) sage: ri {-138907099/160396102*I - 1/2: -1.? - 1.?*I, 138907099/160396102*I - 1/2: -1.? + 1.?*I, 1: 1.? + 0.?*I} sage: [r.endpoints() for r in ri.values()] [(0.566987298107781 - 0.433012701892219*I, 1.43301270189222 + 0.433012701892219*I, 0.566987298107781 + 0.433012701892219*I, 1.43301270189222 - 0.433012701892219*I), (-0.933012701892219 - 1.29903810567666*I, -0.0669872981077806 - 0.433012701892219*I, -0.933012701892219 - 0.433012701892219*I, -0.0669872981077806 - 1.29903810567666*I), (-0.933012701892219 + 0.433012701892219*I, -0.0669872981077806 + 1.29903810567666*I, -0.933012701892219 + 1.29903810567666*I, -0.0669872981077806 + 0.433012701892219*I)] """ x, y = f.parent().gens() I = QQbar.gen() fx = QQbar[y](f.subs({x: QQ(x0.real()) + I * QQ(x0.imag())})) roots = fx.roots(QQbar, multiplicities=False) result = {} for i in range(len(roots)): r = roots[i] prec = 53 IF = ComplexIntervalField(prec) CF = ComplexField(prec) divisor = 4 diam = min((CF(r) - CF(r0)).abs() for r0 in roots[:i] + roots[i + 1:]) / divisor envelop = IF(diam) * IF((-1, 1), (-1, 1)) while not newton(fx, r, r + envelop) in r + envelop: prec += 53 IF = ComplexIntervalField(prec) CF = ComplexField(prec) divisor *= 2 diam = min([(CF(r) - CF(r0)).abs() for r0 in roots[:i] + roots[i + 1:]]) / divisor envelop = IF(diam) * IF((-1, 1), (-1, 1)) qapr = QQ(CF(r).real()) + QQbar.gen() * QQ(CF(r).imag()) if qapr not in r + envelop: raise ValueError("Could not approximate roots with exact values") result[qapr] = r + envelop return result
def __init__(self): self.count = ZZ(0) self.weighted_count = QQ(0)
def braid_in_segment(g, x0, x1): """ Return the braid formed by the `y` roots of ``f`` when `x` moves from ``x0`` to ``x1``. INPUT: - ``g`` -- a polynomial factorization in two variables - ``x0`` -- a complex number - ``x1`` -- a complex number OUTPUT: A braid. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import braid_in_segment # optional - sirocco sage: R.<x,y> = QQ[] sage: f = x^2 + y^3 sage: x0 = CC(1,0) sage: x1 = CC(1, 0.5) sage: braid_in_segment(f.factor(), x0, x1) # optional - sirocco s1 TESTS: Check that :trac:`26503` is fixed:: sage: wp = QQ['t']([1, 1, 1]).roots(QQbar)[0][0] sage: Kw.<wp> = NumberField(wp.minpoly(), embedding=wp) sage: R.<x, y> = Kw[] sage: z = -wp - 1 sage: f = y*(y + z)*x*(x - 1)*(x - y)*(x + z*y - 1)*(x + z*y + wp) sage: from sage.schemes.curves import zariski_vankampen as zvk sage: g = f.subs({x: x + 2*y}) sage: p1 = QQbar(sqrt(-1/3)) sage: p2 = QQbar(1/2+sqrt(-1/3)/2) sage: B = zvk.braid_in_segment(g.factor(),CC(p1),CC(p2)) # optional - sirocco sage: B # optional - sirocco s5*s3^-1 """ (x, y) = g.value().parent().gens() I = QQbar.gen() X0 = QQ(x0.real()) + I * QQ(x0.imag()) X1 = QQ(x1.real()) + I * QQ(x1.imag()) intervals = {} precision = {} y0s = [] for (f, naux) in g: if f.variables() == (y, ): F0 = QQbar[y](f.base_ring()[y](f)) else: F0 = QQbar[y](f(X0, y)) y0sf = F0.roots(multiplicities=False) y0s += list(y0sf) precision[f] = 53 while True: CIFp = ComplexIntervalField(precision[f]) intervals[f] = [r.interval(CIFp) for r in y0sf] if not any( a.overlaps(b) for a, b in itertools.combinations(intervals[f], 2)): break precision[f] *= 2 strands = [ followstrand(f[0], [p[0] for p in g if p[0] != f[0]], x0, x1, i.center(), precision[f[0]]) for f in g for i in intervals[f[0]] ] complexstrands = [[(QQ(a[0]), QQ(a[1]), QQ(a[2])) for a in b] for b in strands] centralbraid = braid_from_piecewise(complexstrands) initialstrands = [] finalstrands = [] initialintervals = roots_interval_cached(g.value(), X0) finalintervals = roots_interval_cached(g.value(), X1) for cs in complexstrands: ip = cs[0][1] + I * cs[0][2] fp = cs[-1][1] + I * cs[-1][2] matched = 0 for center, interval in initialintervals.items(): if ip in interval: initialstrands.append([(0, center.real(), center.imag()), (1, cs[0][1], cs[0][2])]) matched += 1 if matched == 0: raise ValueError("unable to match braid endpoint with root") if matched > 1: raise ValueError("braid endpoint mathes more than one root") matched = 0 for center, interval in finalintervals.items(): if fp in interval: finalstrands.append([(0, cs[-1][1], cs[-1][2]), (1, center.real(), center.imag())]) matched += 1 if matched == 0: raise ValueError("unable to match braid endpoint with root") if matched > 1: raise ValueError("braid endpoint mathes more than one root") initialbraid = braid_from_piecewise(initialstrands) finalbraid = braid_from_piecewise(finalstrands) return initialbraid * centralbraid * finalbraid
def __call__(self, cm, aut): self.count += ZZ(1) self.weighted_count += QQ( (1, (1 if aut is None else aut.group_cardinality())))
def is_anisotropic(self, p): """ Checks if the quadratic form is anisotropic over the p-adic numbers `Q_p`. INPUT: `p` -- a prime number > 0 OUTPUT: boolean EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1]) sage: Q.is_anisotropic(2) True sage: Q.is_anisotropic(3) True sage: Q.is_anisotropic(5) False :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: Q.is_anisotropic(2) False sage: Q.is_anisotropic(3) False sage: Q.is_anisotropic(5) False :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p), p, -p*least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] """ n = self.dim() D = self.det() ## TO DO: Should check that p is prime if (n >= 5): return False if (n == 4): return (QQ(D).is_padic_square(p) and (self.hasse_invariant(p) == -hilbert_symbol(-1, -1, p))) if (n == 3): return (self.hasse_invariant(p) != hilbert_symbol(-1, -D, p)) if (n == 2): return (not QQ(-D).is_padic_square(p)) if (n == 1): return (self[0, 0] != 0) raise NotImplementedError( "Oops! We haven't established a convention for 0-dim'l quadratic forms... =(" )
def bch_iterator(X=None, Y=None): r""" A generator function which returns successive terms of the Baker-Campbell-Hausdorff formula. INPUT: - ``X`` -- (optional) an element of a Lie algebra - ``Y`` -- (optional) an element of a Lie algebra The BCH formula is an expression for `\log(\exp(X)\exp(Y))` as a sum of Lie brackets of ``X`` and ``Y`` with rational coefficients. In arbitrary Lie algebras, the infinite sum is only guaranteed to converge for ``X`` and ``Y`` close to zero. If the elements ``X`` and ``Y`` are not given, then the iterator will return successive terms of the abstract BCH formula, i.e., the BCH formula for the generators of the free Lie algebra on 2 generators. If the Lie algebra containing ``X`` and ``Y`` is not nilpotent, the iterator will output infinitely many elements. If the Lie algebra is nilpotent, the number of elements outputted is equal to the nilpotency step. EXAMPLES: The terms of the abstract BCH formula up to fifth order brackets:: sage: from sage.algebras.lie_algebras.bch import bch_iterator sage: bch = bch_iterator() sage: next(bch) X + Y sage: next(bch) 1/2*[X, Y] sage: next(bch) 1/12*[X, [X, Y]] + 1/12*[[X, Y], Y] sage: next(bch) 1/24*[X, [[X, Y], Y]] sage: next(bch) -1/720*[X, [X, [X, [X, Y]]]] + 1/180*[X, [X, [[X, Y], Y]]] + 1/360*[[X, [X, Y]], [X, Y]] + 1/180*[X, [[[X, Y], Y], Y]] + 1/120*[[X, Y], [[X, Y], Y]] - 1/720*[[[[X, Y], Y], Y], Y] For nilpotent Lie algebras the BCH formula only has finitely many terms:: sage: L = LieAlgebra(QQ, 2, step=3) sage: L.inject_variables() Defining X_1, X_2, X_12, X_112, X_122 sage: [Z for Z in bch_iterator(X_1, X_2)] [X_1 + X_2, 1/2*X_12, 1/12*X_112 + 1/12*X_122] sage: [Z for Z in bch_iterator(X_1 + X_2, X_12)] [X_1 + X_2 + X_12, 1/2*X_112 - 1/2*X_122, 0] The elements ``X`` and ``Y`` don't need to be elements of the same Lie algebra if there is a coercion from one to the other:: sage: L = LieAlgebra(QQ, 3, step=2) sage: L.inject_variables() Defining X_1, X_2, X_3, X_12, X_13, X_23 sage: S = L.subalgebra(X_1, X_2) sage: bch1 = [Z for Z in bch_iterator(S(X_1), S(X_2))]; bch1 [X_1 + X_2, 1/2*X_12] sage: bch1[0].parent() == S True sage: bch2 = [Z for Z in bch_iterator(S(X_1), X_3)]; bch2 [X_1 + X_3, 1/2*X_13] sage: bch2[0].parent() == L True The BCH formula requires a coercion from the rationals:: sage: L.<X,Y,Z> = LieAlgebra(ZZ, 2, step=2) sage: bch = bch_iterator(X, Y); next(bch) Traceback (most recent call last): ... TypeError: the BCH formula is not well defined since Integer Ring has no coercion from Rational Field TESTS: Compare to the BCH formula up to degree 5 given by wikipedia:: sage: from sage.algebras.lie_algebras.bch import bch_iterator sage: bch = bch_iterator() sage: L.<X,Y> = LieAlgebra(QQ) sage: L = L.Lyndon() sage: computed_BCH = L.sum(next(bch) for k in range(5)) sage: wikiBCH = X + Y + 1/2*L[X,Y] + 1/12*(L[X,[X,Y]] + L[Y,[Y,X]]) sage: wikiBCH += -1/24*L[Y,[X,[X,Y]]] sage: wikiBCH += -1/720*(L[Y,[Y,[Y,[Y,X]]]] + L[X,[X,[X,[X,Y]]]]) sage: wikiBCH += 1/360*(L[X,[Y,[Y,[Y,X]]]] + L[Y,[X,[X,[X,Y]]]]) sage: wikiBCH += 1/120*(L[Y,[X,[Y,[X,Y]]]] + L[X,[Y,[X,[Y,X]]]]) sage: computed_BCH == wikiBCH True ALGORITHM: The BCH formula `\log(\exp(X)\exp(Y)) = \sum_k Z_k` is computed starting from `Z_1 = X + Y`, by the recursion .. MATH:: (m+1)Z_{m+1} = \frac{1}{2}[X - Y, Z_m] + \sum_{2\leq 2p \leq m}\frac{B_{2p}}{(2p)!}\sum_{k_1+\cdots+k_{2p}=m} [Z_{k_1}, [\cdots [Z_{k_{2p}}, X + Y]\cdots], where `B_{2p}` are the Bernoulli numbers, see Lemma 2.15.3. in [Var1984]_. .. WARNING:: The time needed to compute each successive term increases exponentially. For example on one machine iterating through `Z_{11},...,Z_{18}` for a free Lie algebra, computing each successive term took 4-5 times longer, going from 0.1s for `Z_{11}` to 21 minutes for `Z_{18}`. """ if X is None or Y is None: L = LieAlgebra(QQ, ['X', 'Y']).Lyndon() X, Y = L.lie_algebra_generators() else: X, Y = canonical_coercion(X, Y) L = X.parent() R = L.base_ring() if not R.has_coerce_map_from(QQ): raise TypeError("the BCH formula is not well defined since %s " "has no coercion from %s" % (R, QQ)) xdif = X - Y Z = [0, X + Y] # 1-based indexing for convenience m = 1 yield Z[1] while True: m += 1 if L in LieAlgebras.Nilpotent and m > L.step(): return # apply the recursion formula of [Var1984] Zm = ~QQ(2 * m) * xdif.bracket(Z[-1]) for p in range(1, (m - 1) // 2 + 1): partitions = IntegerListsLex(m - 1, length=2 * p, min_part=1) coeff = bernoulli(2 * p) / QQ(m * factorial(2 * p)) for kvec in partitions: W = Z[1] for k in kvec: W = Z[k].bracket(W) Zm += coeff * W Z.append(Zm) yield Zm
def jordan_blocks_by_scale_and_unimodular(self, p, safe_flag=True): r""" Return a list of pairs `(s_i, L_i)` where `L_i` is a maximal `p^{s_i}`-unimodular Jordan component which is further decomposed into block diagonals of block size `\le 2`. For each `L_i` the 2x2 blocks are listed after the 1x1 blocks (which follows from the convention of the :meth:`local_normal_form` method). .. NOTE:: The decomposition of each `L_i` into smaller blocks is not unique! The ``safe_flag`` argument allows us to select whether we want a copy of the output, or the original output. By default ``safe_flag = True``, so we return a copy of the cached information. If this is set to ``False``, then the routine is much faster but the return values are vulnerable to being corrupted by the user. INPUT: - `p` -- a prime number > 0. OUTPUT: A list of pairs `(s_i, L_i)` where: - `s_i` is an integer, - `L_i` is a block-diagonal unimodular quadratic form over `\ZZ_p`. .. note:: These forms `L_i` are defined over the `p`-adic integers, but by a matrix over `\ZZ` (or `\QQ`?). EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,9,5,7]) sage: Q.jordan_blocks_by_scale_and_unimodular(3) [(0, Quadratic form in 3 variables over Integer Ring with coefficients: [ 1 0 0 ] [ * 5 0 ] [ * * 7 ]), (2, Quadratic form in 1 variables over Integer Ring with coefficients: [ 1 ])] :: sage: Q2 = QuadraticForm(ZZ, 2, [1,1,1]) sage: Q2.jordan_blocks_by_scale_and_unimodular(2) [(-1, Quadratic form in 2 variables over Integer Ring with coefficients: [ 2 2 ] [ * 2 ])] sage: Q = Q2 + Q2.scale_by_factor(2) sage: Q.jordan_blocks_by_scale_and_unimodular(2) [(-1, Quadratic form in 2 variables over Integer Ring with coefficients: [ 2 2 ] [ * 2 ]), (0, Quadratic form in 2 variables over Integer Ring with coefficients: [ 2 2 ] [ * 2 ])] """ ## Try to use the cached result try: if safe_flag: return copy.deepcopy(self.__jordan_blocks_by_scale_and_unimodular_dict[p]) else: return self.__jordan_blocks_by_scale_and_unimodular_dict[p] except Exception: ## Initialize the global dictionary if it doesn't exist if not hasattr(self, '__jordan_blocks_by_scale_and_unimodular_dict'): self.__jordan_blocks_by_scale_and_unimodular_dict = {} ## Deal with zero dim'l forms if self.dim() == 0: return [] ## Find the Local Normal form of Q at p Q1 = self.local_normal_form(p) ## Parse this into Jordan Blocks n = Q1.dim() tmp_Jordan_list = [] i = 0 start_ind = 0 if (n >= 2) and (Q1[0,1] != 0): start_scale = valuation(Q1[0,1], p) - 1 else: start_scale = valuation(Q1[0,0], p) while (i < n): ## Determine the size of the current block if (i == n-1) or (Q1[i,i+1] == 0): block_size = 1 else: block_size = 2 ## Determine the valuation of the current block if block_size == 1: block_scale = valuation(Q1[i,i], p) else: block_scale = valuation(Q1[i,i+1], p) - 1 ## Process the previous block if the valuation increased if block_scale > start_scale: tmp_Jordan_list += [(start_scale, Q1.extract_variables(range(start_ind, i)).scale_by_factor(ZZ(1) / (QQ(p)**(start_scale))))] start_ind = i start_scale = block_scale ## Increment the index i += block_size ## Add the last block tmp_Jordan_list += [(start_scale, Q1.extract_variables(range(start_ind, n)).scale_by_factor(ZZ(1) / QQ(p)**(start_scale)))] ## Cache the result self.__jordan_blocks_by_scale_and_unimodular_dict[p] = tmp_Jordan_list ## Return the result return tmp_Jordan_list
def Min(Fun, p, ubRes, conj): r""" Local loop for Affine_minimal, where we check minimality at the prime p. First we bound the possible k in our transformations A = zp^k + b. See Theorems 3.3.2 and 3.3.3 in [Molnar]_. INPUT: - ``Fun`` -- a projective space morphisms. - ``p`` - a prime. - ``ubRes`` -- integer, the upper bound needed for Th. 3.3.3 in [Molnar]_. - ``conj`` -- a 2x2 matrix keeping track of the conjugation. OUTPUT: - Boolean -- ``True`` if ``Fun`` is minimal at ``p``, ``False`` otherwise. - a projective morphism minimal at ``p``. EXAMPLES:: sage: P.<x,y> = ProjectiveSpace(QQ, 1) sage: H = End(P) sage: f = H([149*x^2 + 39*x*y + y^2, -8*x^2 + 137*x*y + 33*y^2]) sage: from sage.schemes.projective.endPN_minimal_model import Min sage: Min(f, 3, -27000000, matrix(QQ,[[1, 0],[0, 1]])) ( Scheme endomorphism of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to (181*x^2 + 313*x*y + 81*y^2 : -24*x^2 + 73*x*y + 151*y^2) , [3 4] [0 1] ) """ d = Fun.degree() AffFun = Fun.dehomogenize(1) R = AffFun.coordinate_ring() if R.is_field(): #want the polynomial ring not the fraction field R = R.ring() F = R(AffFun[0].numerator()) G = R(AffFun[0].denominator()) dG = G.degree() if dG > (d + 1) / 2: lowerBound = (-2 * (G[dG]).valuation(p) / (2 * dG - d + 1) + 1).floor() else: lowerBound = (-2 * (F[d]).valuation(p) / (d - 1) + 1).floor() upperBound = 2 * (ubRes.valuation(p)) if upperBound < lowerBound: #There are no possible transformations to reduce the resultant. return Fun, conj else: #Looping over each possible k, we search for transformations to reduce the #resultant of F/G k = lowerBound Qb = PolynomialRing(QQ, 'b') b = Qb.gen(0) Q = PolynomialRing(Qb, 'z') z = Q.gen(0) while k <= upperBound: A = (p**k) * z + b Ft = Q(F(A) - b * G(A)) Gt = Q((p**k) * G(A)) Fcoeffs = Ft.coefficients(sparse=False) Gcoeffs = Gt.coefficients(sparse=False) coeffs = Fcoeffs + Gcoeffs RHS = (d + 1) * k / 2 #If there is some b such that Res(phi^A) < Res(phi), we must have ord_p(c) > #RHS for each c in coeffs. #Make sure constant coefficients in coeffs satisfy the inequality. if all( QQ(c).valuation(p) > RHS for c in coeffs if c.degree() == 0): #Constant coefficients in coeffs have large enough valuation, so check #the rest. We start by checking if simply picking b=0 works if all(c(0).valuation(p) > RHS for c in coeffs): #A = z*p^k satisfies the inequalities, and F/G is not minimal #"Conjugating by", p,"^", k, "*z +", 0 newconj = matrix(QQ, 2, 2, [p**k, 0, 0, 1]) minFun = Fun.conjugate(newconj) conj = conj * newconj minFun.normalize_coordinates() return minFun, conj #Otherwise we search if any value of b will work. We start by finding a #minimum bound on the valuation of b that is necessary. See Theorem 3.3.5 #in [Molnar, M.Sc. thesis]. bval = max([ bCheck(coeff, RHS, p, b) for coeff in coeffs if coeff.degree() > 0 ]) #We scale the coefficients in coeffs, so that we may assume ord_p(b) is #at least 0 scaledCoeffs = [coeff(b * (p**bval)) for coeff in coeffs] #We now scale the inequalities, ord_p(coeff) > RHS, so that coeff is in #ZZ[b] scale = QQ(max([coeff.denominator() for coeff in scaledCoeffs])) normalizedCoeffs = [coeff * scale for coeff in scaledCoeffs] scaleRHS = RHS + scale.valuation(p) #We now search for integers that satisfy the inequality ord_p(coeff) > #RHS. See Lemma 3.3.6 in [Molnar, M.Sc. thesis]. bound = (scaleRHS + 1).floor() bool, sol = blift(normalizedCoeffs, bound, p) #If bool is true after lifting, we have a solution b, and F/G is not #minimal. if bool: #Rescale, conjugate and return new map bsol = QQ(sol * (p**bval)) #"Conjugating by ", p,"^", k, "*z +", bsol newconj = matrix(QQ, 2, 2, [p**k, bsol, 0, 1]) minFun = Fun.conjugate(newconj) conj = conj * newconj minFun.normalize_coordinates() return minFun, conj k = k + 1 return Fun, conj
def Watson_mass_at_2(self): """ Returns the local mass of the quadratic form when `p=2`, according to Watson's Theorem 1 of "The 2-adic density of a quadratic form" in Mathematika 23 (1976), pp 94--106. INPUT: none OUTPUT: a rational number EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.Watson_mass_at_2() ## WARNING: WE NEED TO CHECK THIS CAREFULLY! 384 """ ## Make a 0-dim'l quadratic form (for initialization purposes) Null_Form = copy.deepcopy(self) Null_Form.__init__(ZZ, 0) ## Step 0: Compute Jordan blocks and bounds of the scales to keep track of Jordan_Blocks = self.jordan_blocks_by_scale_and_unimodular(2) scale_list = [B[0] for B in Jordan_Blocks] s_min = min(scale_list) s_max = max(scale_list) ## Step 1: Compute dictionaries of the diagonal block and 2x2 block for each scale diag_dict = dict( (i, Null_Form) for i in range(s_min - 2, s_max + 4)) ## Initialize with the zero form dim2_dict = dict( (i, Null_Form) for i in range(s_min, s_max + 4)) ## Initialize with the zero form for (s, L) in Jordan_Blocks: i = 0 while (i < L.dim() - 1) and (L[i, i + 1] == 0): ## Find where the 2x2 blocks start i = i + 1 if i < (L.dim() - 1): diag_dict[s] = L.extract_variables(range(i)) ## Diagonal Form dim2_dict[s + 1] = L.extract_variables(range( i, L.dim())) ## Non-diagonal Form else: diag_dict[s] = L #print "diag_dict = ", diag_dict #print "dim2_dict = ", dim2_dict #print "Jordan_Blocks = ", Jordan_Blocks ## Step 2: Compute three dictionaries of invariants (for n_j, m_j, nu_j) n_dict = dict((j, 0) for j in range(s_min + 1, s_max + 2)) m_dict = dict((j, 0) for j in range(s_min, s_max + 4)) for (s, L) in Jordan_Blocks: n_dict[s + 1] = L.dim() if diag_dict[s].dim() == 0: m_dict[s + 1] = ZZ(1) / ZZ(2) * L.dim() else: m_dict[s + 1] = floor(ZZ(L.dim() - 1) / ZZ(2)) #print " ==>", ZZ(L.dim() - 1) / ZZ(2), floor(ZZ(L.dim() - 1) / ZZ(2)) nu_dict = dict((j, n_dict[j + 1] - 2 * m_dict[j + 1]) for j in range(s_min, s_max + 1)) nu_dict[s_max + 1] = 0 #print "n_dict = ", n_dict #print "m_dict = ", m_dict #print "nu_dict = ", nu_dict ## Step 3: Compute the e_j dictionary eps_dict = {} for j in range(s_min, s_max + 3): two_form = (diag_dict[j - 2] + diag_dict[j] + dim2_dict[j]).scale_by_factor(2) j_form = (two_form + diag_dict[j - 1]).base_change_to( IntegerModRing(4)) if j_form.dim() == 0: eps_dict[j] = 1 else: iter_vec = [4] * j_form.dim() alpha = sum([True for x in mrange(iter_vec) if j_form(x) == 0]) beta = sum([True for x in mrange(iter_vec) if j_form(x) == 2]) if alpha > beta: eps_dict[j] = 1 elif alpha == beta: eps_dict[j] = 0 else: eps_dict[j] = -1 #print "eps_dict = ", eps_dict ## Step 4: Compute the quantities nu, q, P, E for the local mass at 2 nu = sum([j * n_dict[j] * (ZZ(n_dict[j] + 1) / ZZ(2) + \ sum([n_dict[r] for r in range(j+1, s_max+2)])) for j in range(s_min+1, s_max+2)]) q = sum([ sgn(nu_dict[j - 1] * (n_dict[j] + sgn(nu_dict[j]))) for j in range(s_min + 1, s_max + 2) ]) P = prod([ prod([1 - QQ(4)**(-i) for i in range(1, m_dict[j] + 1)]) for j in range(s_min + 1, s_max + 2) ]) E = prod([ ZZ(1) / ZZ(2) * (1 + eps_dict[j] * QQ(2)**(-m_dict[j])) for j in range(s_min, s_max + 3) ]) #print "\nFinal Summary:" #print "nu =", nu #print "q = ", q #print "P = ", P #print "E = ", E ## Step 5: Compute the local mass for the prime 2. mass_at_2 = QQ(2)**(nu - q) * P / E return mass_at_2
def screen_to_math_coordinates(self, x, y): x = QQ(x) y = QQ(y) return self.vector_space()(((self._field(x) - self._tx) / self._s, (-self._field(y) + self._ty) / self._s))
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 == None: Zvec = [] ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec != None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec != 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 == True): 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 == 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 epsinv(F, target, prec=53, target_tol=0.001, z=None, emb=None): """ Compute a bound on the hyperbolic distance. The true minimum will be within the computed bound. It is computed as the inverse of epsilon_F from [HS2018]_. INPUT: - ``F`` -- binary form of degree at least 3 with no multiple roots - ``target`` -- positive real number. The value we want to attain, i.e., the value we are taking the inverse of - ``prec``-- positive integer. precision to use in CC - ``target_tol`` -- positive real number. The tolerance with which we attain the target value. - ``z`` -- complex number. ``z_0`` covariant for F. - ``emb`` -- embedding into CC OUTPUT: a real number delta satisfying target + target_tol > eps_F(delta) > target. EXAMPLES:: sage: from sage.rings.polynomial.binary_form_reduce import epsinv sage: R.<x,y> = QQ[] sage: epsinv(-2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3, 31.5022020249597) 4.02520895942207 """ def coshdelta(z): #The cosh of the hyperbolic distance from z = t+uj to j return (z.norm() + 1) / (2 * z.imag()) def RQ(delta): # this is the quotient R(F_0,z)/R(F_0,z(F)) for a generic z # at distance delta from j. See Lemma 4.2 in [HS2018]. cd = cosh(delta).n(prec=prec) sd = sinh(delta).n(prec=prec) return prod( [cd + (cost * phi[0] + sint * phi[1]) * sd for phi in phis]) def epsF(delta): pol = RQ(delta) #get R quotient in terms of z S = PolynomialRing(C, 'v') g = S([(i - d) * pol[i - d] for i in range(2 * d + 1)]) # take derivative drts = [ e for e in g.roots(ring=C, multiplicities=False) if (e.norm() - 1).abs() < 0.1 ] # find min return min([pol(r / r.abs()).real() for r in drts]) C = ComplexField(prec=prec) if F.base_ring() != C: if emb is None: compF = F.change_ring(C) else: compF = F.change_ring(emb) else: compF = F R = F.parent() d = F.degree() if z is None: z, th = covariant_z0(F, prec=prec, emb=emb) else: #need to do our own input checking if R.ngens() != 2 or any([sum(t) != d for t in F.exponents()]): raise TypeError('must be a binary form') if d < 3: raise ValueError('must be at least degree 3') f = F.subs({R.gen(1): 1}).univariate_polynomial() #now we have a single variable polynomial x = f.parent().gen() if (max([ex for p,ex in f.roots(ring=C)]) >= QQ(d)/2)\ or (f.degree() < QQ(d)/2): raise ValueError('cannot have root with multiplicity >= deg(F)/2') R = RealField(prec=prec) PR = PolynomialRing(R, 't') t = PR.gen(0) # compute phi_1, ..., phi_k # first find F_0 and its roots # this change of variables on f moves z(f) to j, i.e. produces F_0 rts = f(z.imag() * t + z.real()).roots(ring=C) phis = [] # stereographic projection of roots for r, e in rts: phis.extend( [[2 * r.real() / (r.norm() + 1), (r.norm() - 1) / (r.norm() + 1)]]) if d != f.degree(): # include roots at infinity phis.extend([(d - f.degree()) * [0, 1]]) # for writing RQ in terms of generic z to minimize LC = LaurentSeriesRing(C, 'u', default_prec=2 * d + 2) u = LC.gen(0) cost = (u + u**(-1)) / 2 sint = (u - u**(-1)) / (2 * C.gen(0)) # first find an interval containing the desired value # then use regula falsi on log eps_F # d -> delta value in interval [0,1] # v in value in interval [1,epsF(1)] dl = R(0.0) vl = R(1.0) du = R(1.0) vu = epsF(du) while vu < target: # compute the next value of epsF for delta = 2*delta dl = du vl = vu du *= 2 vu = epsF(du) # now dl < delta <= du logt = target.log() l2 = (vu.log() - logt).n(prec=prec) l1 = (vl.log() - logt).n(prec=prec) dn = (dl * l2 - du * l1) / (l2 - l1) vn = epsF(dn) dl = du vl = vu du = dn vu = vn while (du - dl).abs() >= target_tol or max(vl, vu) < target: l2 = (vu.log() - logt).n(prec=prec) l1 = (vl.log() - logt).n(prec=prec) dn = (dl * l2 - du * l1) / (l2 - l1) vn = epsF(dn) dl = du vl = vu du = dn vu = vn return max(dl, du)
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 == None: Zvec = [] ## Sanity Check on Zvec and NZvec: ## ------------------------------- Sn = Set(range(n)) if (Zvec != None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec != 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 == 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 gamma__exact(n): """ Evaluates the exact value of the `\Gamma` function at an integer or half-integer argument. EXAMPLES:: sage: gamma__exact(4) 6 sage: gamma__exact(3) 2 sage: gamma__exact(2) 1 sage: gamma__exact(1) 1 sage: gamma__exact(1/2) sqrt(pi) sage: gamma__exact(3/2) 1/2*sqrt(pi) sage: gamma__exact(5/2) 3/4*sqrt(pi) sage: gamma__exact(7/2) 15/8*sqrt(pi) sage: gamma__exact(-1/2) -2*sqrt(pi) sage: gamma__exact(-3/2) 4/3*sqrt(pi) sage: gamma__exact(-5/2) -8/15*sqrt(pi) sage: gamma__exact(-7/2) 16/105*sqrt(pi) TESTS:: sage: gamma__exact(1/3) Traceback (most recent call last): ... TypeError: you must give an integer or half-integer argument """ from sage.all import sqrt # SANITY CHECK if (not n in QQ) or (denominator(n) > 2): raise TypeError("you must give an integer or half-integer argument") if denominator(n) == 1: if n <= 0: return infinity if n > 0: return factorial(n - 1) else: ans = QQ(1) while (n != QQ(1) / 2): if n < 0: ans *= QQ(1) / n n += 1 elif n > 0: n += -1 ans *= n ans *= sqrt(pi) return ans
def _find_scaling_period(self): r""" Uses the integral period map of the modular symbol implementation in sage in order to determine the scaling. The resulting modular symbol is correct only for the `X_0`-optimal curve, at least up to a possible factor +- a power of 2. EXAMPLES:: sage: E = EllipticCurve('11a1') sage: m = sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1,normalize='period') sage: m._e (1/5, 1) sage: E = EllipticCurve('11a2') sage: m = sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1,normalize='period') sage: m._e (1, 5) sage: E = EllipticCurve('121b2') sage: m = sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1,normalize='period') sage: m._e (0, 11/2, 0, 11/2, 11/2, 0, 0, -3, 2, 1/2, -1, 3/2) TESTS:: sage: E = EllipticCurve('19a1') sage: m = E.modular_symbol(sign=+1, implementation='sage', normalize='none') sage: m._find_scaling_period() sage: m._scaling 1 sage: E = EllipticCurve('19a2') sage: m = E.modular_symbol(sign=+1, implementation='sage', normalize='none') sage: m._scaling 1 sage: m._find_scaling_period() sage: m._scaling 3 """ P = self._modsym.integral_period_mapping() self._e = P.matrix().transpose().row(0) self._e /= 2 E = self._E try: crla = parse_cremona_label(E.label()) except RuntimeError: # raised when curve is outside of the table print( "Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by a rational number." ) self._scaling = 1 else: cr0 = Integer(crla[0]).str() + crla[1] + '1' E0 = EllipticCurve(cr0) if self._sign == 1: q = E0.period_lattice().basis()[0] / E.period_lattice().basis( )[0] else: q = E0.period_lattice().basis()[1].imag() / E.period_lattice( ).basis()[1].imag() if E0.real_components() == 1: q *= 2 if E.real_components() == 1: q /= 2 q = QQ(int(round(q * 200))) / 200 verbose('scale modular symbols by %s' % q) self._scaling = q c = self(0) # required, to change base point from oo to 0 if c < 0: c *= -1 self._scaling *= -1 self._at_zero = c self._e *= self._scaling
def extract(cls, obj): """ Takes an object extracted by the json parser and decodes the special-formating dictionaries used to store special types. """ if isinstance(obj, dict) and 'data' in obj: if len(obj) == 2 and '__ComplexList__' in obj: return [complex(*v) for v in obj['data']] elif len(obj) == 2 and '__QQList__' in obj: return [QQ(tuple(v)) for v in obj['data']] elif len(obj) == 3 and '__NFList__' in obj and 'base' in obj: base = cls.extract(obj['base']) return [cls._extract(base, c) for c in obj['data']] elif len(obj) == 2 and '__IntDict__' in obj: return {Integer(k): cls.extract(v) for k, v in obj['data']} elif len(obj) == 3 and '__Vector__' in obj and 'base' in obj: base = cls.extract(obj['base']) return vector([cls._extract(base, v) for v in obj['data']]) elif len(obj) == 2 and '__Rational__' in obj: return Rational(*obj['data']) elif len(obj) == 3 and '__RealLiteral__' in obj and 'prec' in obj: return LmfdbRealLiteral(RealField(obj['prec']), obj['data']) elif len(obj) == 2 and '__complex__' in obj: return complex(*obj['data']) elif len(obj) == 3 and '__Complex__' in obj and 'prec' in obj: return ComplexNumber(ComplexField(obj['prec']), *obj['data']) elif len(obj) == 3 and '__NFElt__' in obj and 'parent' in obj: return cls._extract(cls.extract(obj['parent']), obj['data']) elif len(obj) == 3 and ('__NFRelative__' in obj or '__NFAbsolute__' in obj) and 'vname' in obj: poly = cls.extract(obj['data']) return NumberField(poly, name=obj['vname']) elif len(obj) == 2 and '__NFCyclotomic__' in obj: return CyclotomicField(obj['data']) elif len(obj) == 2 and '__IntegerRing__' in obj: return ZZ elif len(obj) == 2 and '__RationalField__' in obj: return QQ elif len( obj) == 3 and '__RationalPoly__' in obj and 'vname' in obj: return QQ[obj['vname']]([QQ(tuple(v)) for v in obj['data']]) elif len( obj ) == 4 and '__Poly__' in obj and 'vname' in obj and 'base' in obj: base = cls.extract(obj['base']) return base[obj['vname']]( [cls._extract(base, c) for c in obj['data']]) elif len( obj ) == 5 and '__PowerSeries__' in obj and 'vname' in obj and 'base' in obj and 'prec' in obj: base = cls.extract(obj['base']) prec = infinity if obj['prec'] == 'inf' else int(obj['prec']) return base[[obj['vname'] ]]([cls._extract(base, c) for c in obj['data']], prec=prec) elif len(obj) == 2 and '__date__' in obj: return datetime.datetime.strptime(obj['data'], "%Y-%m-%d").date() elif len(obj) == 2 and '__time__' in obj: return datetime.datetime.strptime(obj['data'], "%H:%M:%S.%f").time() elif len(obj) == 2 and '__datetime__' in obj: return datetime.datetime.strptime(obj['data'], "%Y-%m-%d %H:%M:%S.%f") return obj