def lseries_dokchitser(E, prec=53): """ Return the Dokchitser L-series object associated to the elliptic curve E, which may be defined over the rational numbers or a number field. Also prec is the number of bits of precision to which evaluation of the L-series occurs. INPUT: - E -- elliptic curve over a number field (or QQ) - prec -- integer (default: 53) precision in *bits* OUTPUT: - Dokchitser L-function object EXAMPLES:: A curve over Q(sqrt(5)), for which we have an optimized implementation:: sage: from psage.ellcurve.lseries.lseries_nf import lseries_dokchitser sage: K.<a> = NumberField(x^2-x-1); E = EllipticCurve([0,-a,a,0,0]) sage: L = lseries_dokchitser(E); L Dokchitser L-function of Elliptic Curve defined by y^2 + a*y = x^3 + (-a)*x^2 over Number Field in a with defining polynomial x^2 - x - 1 sage: L(1) 0.422214159001667 sage: L.taylor_series(1,5) 0.422214159001667 + 0.575883864741340*z - 0.102163426876721*z^2 - 0.158119743123727*z^3 + 0.120350687595265*z^4 + O(z^5) Higher precision:: sage: L = lseries_dokchitser(E, 200) sage: L(1) 0.42221415900166715092967967717023093014455137669360598558872 A curve over Q(i):: sage: K.<i> = NumberField(x^2 + 1) sage: E = EllipticCurve(K, [1,0]) sage: E.conductor().norm() 256 sage: L = lseries_dokchitser(E, 10) sage: L.taylor_series(1,5) 0.86 + 0.58*z - 0.62*z^2 + 0.19*z^3 + 0.18*z^4 + O(z^5) More examples:: sage: lseries_dokchitser(EllipticCurve([0,-1,1,0,0]), 10)(1) 0.25 sage: K.<i> = NumberField(x^2+1) sage: lseries_dokchitser(EllipticCurve(K, [0,-1,1,0,0]), 10)(1) 0.37 sage: K.<a> = NumberField(x^2-x-1) sage: lseries_dokchitser(EllipticCurve(K, [0,-1,1,0,0]), 10)(1) 0.72 sage: E = EllipticCurve([0,-1,1,0,0]) sage: E.quadratic_twist(2).rank() 1 sage: K.<d> = NumberField(x^2-2) sage: L = lseries_dokchitser(EllipticCurve(K, [0,-1,1,0,0]), 10) sage: L(1) 0 sage: L.taylor_series(1, 5) 0.58*z + 0.20*z^2 - 0.50*z^3 + 0.28*z^4 + O(z^5) You can use this function as an algorithm to compute the sign of the functional equation (global root number):: sage: E = EllipticCurve([1..5]) sage: E.root_number() -1 sage: L = lseries_dokchitser(E,32); L Dokchitser L-function of Elliptic Curve defined by y^2 + x*y = x^3 - x^2 + 4*x + 3 over Rational Field sage: L.eps -1 Over QQ, this isn't so useful (since Sage has a root_number method), but over number fields it is very useful:: sage: K.<a> = NumberField(x^2 - x - 1) sage: E1=EllipticCurve([0,-a-1,1,a,0]); E0 = EllipticCurve([0,-a,a,0,0]) sage: lseries_dokchitser(E1, 16).eps -1 sage: E1.rank() 1 sage: lseries_dokchitser(E0, 16).eps 1 sage: E0.rank() 0 """ # The code assumes in various places that we have a global minimal model, # for example, in anlist_sqrt5 above. E = E.global_minimal_model() # Check that we're over a number field. K = E.base_field() if not is_NumberField(K): raise TypeError("base field must be a number field") # Compute norm of the conductor -- awkward because QQ elements have no norm method (they should). N = E.conductor() if K != QQ: N = N.norm() # We guess the sign epsilon in the functional equation to be +1 # first. If our guess is wrong then we just choose the other # possibility. epsilon = 1 # Define the Dokchitser L-function object with all parameters set: L = Dokchitser(conductor=N * K.discriminant()**2, gammaV=[0] * K.degree() + [1] * K.degree(), weight=2, eps=epsilon, poles=[], prec=prec) # Find out how many coefficients of the Dirichlet series are needed # to compute to the requested precision. n = L.num_coeffs() # print "num coeffs = %s"%n # Compute the Dirichlet series coefficients coeffs = anlist(E, n)[1:] # Define a string that when evaluated in PARI defines a function # a(k), which returns the Dirichlet coefficient a_k. s = 'v=%s; a(k)=v[k];' % coeffs # Actually tell the L-series / PARI about the coefficients. L.init_coeffs('a(k)', pari_precode=s) # Test that the functional equation is satisfied. This will very, # very, very likely if we chose the sign of the functional # equation incorrectly, or made any mistake in computing the # Dirichlet series coefficients. tiny = max(1e-8, old_div(1.0, 2**(prec - 1))) if abs(L.check_functional_equation()) > tiny: # The test failed, so we try the other choice of functional equation. epsilon *= -1 L.eps = epsilon # It is not necessary to recreate L -- just tell PARI the different sign. L._gp_eval('sgn = %s' % epsilon) # Once again, verify that the functional equation is # satisfied. If it is, then we've got it. If it isn't, then # there is definitely some other subtle bug, probably in computed # the Dirichlet series coefficients. if abs(L.check_functional_equation()) > tiny: raise RuntimeError( "Functional equation not numerically satisfied for either choice of sign" ) L.rename('Dokchitser L-function of %s' % E) return L
class GaloisRepresentation( Lfunction): def __init__(self, thingy): """ Class representing a L-function coming from a Galois representation. Typically, dirichlet characters, artin reps, elliptic curves,... can give rise to such a class. It can be used for tensor two such together (mul below) and a L-function class can be extracted from it. """ # this is an important global variable. # it is the maximum of the imag parts of values s at # which we will compute L(s,.) self.max_imaginary_part = "40" if isinstance(thingy, sage.schemes.elliptic_curves.ell_rational_field.EllipticCurve_rational_field): self.init_elliptic_curve(thingy) elif isinstance(thingy, lmfdb.WebCharacter.WebDirichletCharacter): self.init_dir_char(thingy) elif isinstance(thingy, lmfdb.artin_representations.math_classes.ArtinRepresentation): self.init_artin_rep(thingy) elif (isinstance(thingy, list) and len(thingy) == 2 and isinstance(thingy[0],lmfdb.modular_forms.elliptic_modular_forms.backend.web_newforms.WebNewForm) and isinstance(thingy[1],sage.rings.integer.Integer) ): self.init_elliptic_modular_form(thingy[0],thingy[1]) elif (isinstance(thingy, list) and len(thingy) == 2 and isinstance(thingy[0], GaloisRepresentation) and isinstance(thingy[1], GaloisRepresentation) ): self.init_tensor_product(thingy[0], thingy[1]) else: raise ValueError("GaloisRepresentations are currently not implemented for that type (%s) of objects"%type(thingy)) # set a few common variables self.level = self.conductor self.degree = self.dim self.poles = [] self.residues = [] self.algebraic = True self.weight = self.motivic_weight + 1 ## Various ways to construct such a class def init_elliptic_curve(self, E): """ Returns the Galois rep of an elliptic curve over Q """ self.original_object = [E] self.object_type = "ellipticcurve" self.dim = 2 self.motivic_weight = 1 self.conductor = E.conductor() self.bad_semistable_primes = [ fa[0] for fa in self.conductor.factor() if fa[1]==1 ] self.bad_pot_good = [p for p in self.conductor.prime_factors() if E.j_invariant().valuation(p) > 0 ] self.sign = E.root_number() self.mu_fe = [] self.nu_fe = [ZZ(1)/ZZ(2)] self.gammaV = [0, 1] self.langlands = True self.selfdual = True self.primitive = True self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.coefficient_type = 2 self.coefficient_period = 0 self.besancon_bound = 50000 self.ld.gp().quit() def eu(p): """ Local Euler factor passed as a function whose input is a prime and whose output is a polynomial such that evaluated at p^-s, we get the inverse of the local factor of the L-function """ R = PolynomialRing(QQ, "T") T = R.gens()[0] N = self.conductor if N % p != 0 : # good reduction return 1 - E.ap(p) * T + p * T**2 elif N % (p**2) != 0: # multiplicative reduction return 1 - E.ap(p) * T else: return R(1) self.local_euler_factor = eu def init_dir_char(self, chi): """ Initiate with a Web Dirichlet character. """ self.original_object = [chi] chi = chi.chi.primitive_character() self.object_type = "dirichletcharacter" self.dim = 1 self.motivic_weight = 0 self.conductor = ZZ(chi.conductor()) self.bad_semistable_primes = [] self.bad_pot_good = self.conductor.prime_factors() if chi.is_odd(): aa = 1 bb = I else: aa = 0 bb = 1 self.sign = chi.gauss_sum_numerical() / (bb * float(sqrt(chi.modulus())) ) # this has now type python complex. later we need a gp complex self.sign = ComplexField()(self.sign) self.mu_fe = [aa] self.nu_fe = [] self.gammaV = [aa] self.langlands = True self.selfdual = (chi.multiplicative_order() <= 2) # rather than all( abs(chi(m).imag) < 0.0001 for m in range(chi.modulus() ) ) self.primitive = True self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.dirichlet_coefficients = [ chi(m) for m in range(self.numcoeff + 1) ] if self.selfdual: self.coefficient_type = 2 else: self.coefficient_type = 3 self.coefficient_period = chi.modulus() self.besancon_bound = 10000 def eu(p): """ local euler factor """ if self.selfdual: K = QQ else: K = ComplexField() R = PolynomialRing(K, "T") T = R.gens()[0] if self.conductor % p != 0: return 1 - ComplexField()(chi(p)) * T else: return R(1) self.local_euler_factor = eu self.ld.gp().quit() def init_artin_rep(self, rho): """ Initiate with an Artin representation """ self.original_object = [rho] self.object_type = "Artin representation" self.dim = rho.dimension() self.motivic_weight = 0 self.conductor = ZZ(rho.conductor()) self.bad_semistable_primes = [] self.bad_pot_good = self.conductor.prime_factors() self.sign = rho.root_number() self.mu_fe = rho.mu_fe() self.nu_fe = rho.nu_fe() self.gammaV = [0 for i in range(rho.number_of_eigenvalues_plus_one_complex_conjugation())] for i in range(rho.number_of_eigenvalues_minus_one_complex_conjugation() ): self.gammaV.append(1) self.langlands = rho.langlands() self.selfdual = rho.selfdual() self.primitive = rho.primitive() self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.coefficient_type = 0 self.coefficient_period = 0 self.besancon_bound = 3000 def eu(p): """ local euler factor """ f = rho.local_factor(p) co = [ZZ(round(x)) for x in f.coefficients(sparse=False)] R = PolynomialRing(QQ, "T") T = R.gens()[0] return sum( co[n] * T**n for n in range(len(co))) self.local_euler_factor = eu self.ld.gp().quit() def init_elliptic_modular_form(self, F, number): """ Initiate with an Elliptic Modular Form. """ self.number = number self.original_object = [[F,number]] self.object_type = "Elliptic Modular newform" self.dim = 2 self.weight = ZZ(F.weight()) self.motivic_weight = ZZ(F.weight()) - 1 self.conductor = ZZ(F.level()) self.bad_semistable_primes = [fa[0] for fa in self.conductor.factor() if fa[1]==1 ] # should be including primes of bad red that are pot good # however I don't know how to recognise them self.bad_pot_good = [] self.langlands = True self.mu_fe = [] self.nu_fe = [ZZ(F.weight()-1)/ZZ(2)] self.primitive = True self.selfdual = True self.coefficient_type = 2 self.coefficient_period = 0 self.sign = (-1) ** (self.weight / 2.) if self.conductor != 1: AL = F.atkin_lehner_eigenvalues() self.sign = AL[self.conductor] * self.sign self.gammaV = [0,1] self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.besancon_bound = 300 def eu(p): """ Local euler factor """ ans = F.q_expansion_embeddings(p + 1) K = ComplexField() R = PolynomialRing(K, "T") T = R.gens()[0] N = self.conductor if N % p != 0 : # good reduction return 1 - ans[p-1][self.number] * T + T**2 elif N % (p**2) != 0: # semistable reduction return 1 - ans[p-1][self.number] * T else: return R(1) self.local_euler_factor = eu self.ld.gp().quit() def init_tensor_product(self, V, W): """ We are given two Galois representations and we will return their tensor product. """ self.original_object = V.original_object + W.original_object self.object_type = "tensorproduct" self.V1 = V self.V2 = W self.dim = V.dim * W.dim self.motivic_weight = V.motivic_weight + W.motivic_weight self.langlands = False # status 2014 :) self.besancon_bound = min(V.besancon_bound, W.besancon_bound) bad2 = ZZ(W.conductor).prime_factors() bad_primes = [x for x in ZZ(V.conductor).prime_factors() if x in bad2] for p in bad_primes: if ( p not in V.bad_semistable_primes and p not in W.bad_semistable_primes) : # this condition above only applies to the current type of objects # for general reps we would have to test the lines below # to be certain that the formulae are correct. #if ((p not in V.bad_semistable_primes or p not in W.bad_pot_good) and #(p not in W.bad_semistable_primes or p not in V.bad_pot_good) and #(p not in V.bad_semistable_primes or p not in W.bad_semistable_primes)): raise NotImplementedError("Currently tensor products of Galois representations are only implemented under some conditions.", "The behaviour at %d is too wild (both factors must be semistable)." % p) # check for the possibily of getting poles if V.weight == W.weight and V.conductor == W.conductor : Vans = V.algebraic_coefficients(50) Wans = W.algebraic_coefficients(50) CC = ComplexField() if ((Vans[2] in ZZ and Wans[2] in ZZ and all(Vans[n] == Wans[n] for n in range(1,50) ) ) or all( CC(Vans[n]) == CC(Wans[n]) for n in range(1,50) ) ): raise NotImplementedError("It seems you are asking to tensor a "+ "Galois representation with its dual " + "which results in the L-function having "+ "a pole. This is not implemented here.") scommon = [x for x in V.bad_semistable_primes if x in W.bad_semistable_primes] N = W.conductor ** V.dim N *= V.conductor ** W.dim for p in bad_primes: n1_tame = V.dim - V.local_euler_factor(p).degree() n2_tame = W.dim - W.local_euler_factor(p).degree() nn = n1_tame * n2_tame N = N // p ** nn if p in scommon: # both are degree 1 in this case N = N // p self.conductor = N h1 = selberg_to_hodge(V.motivic_weight,V.mu_fe,V.nu_fe) h2 = selberg_to_hodge(W.motivic_weight,W.mu_fe,W.nu_fe) h = tensor_hodge(h1, h2) w,m,n = hodge_to_selberg(h) self.mu_fe = m self.nu_fe = n _, self.gammaV = gamma_factors(h) # this is used in getting the Dirichlet coefficients. self.bad_primes_info = [] for p in bad_primes: # we have to check if this works in all bad cases ! f1 = V.local_euler_factor(p) f2 = W.local_euler_factor(p) # might be dodgy if f1 or f2 is an approx to the Euler factor if p in scommon: E = tensor_local_factors(f1,f2,V.dim*W.dim) T = f1.parent().gens()[0] # right answer is E(T)*E(pT) self.bad_primes_info.append([p,E*E(p*T),1-T]) #bad_primes_info.append() with 1-T as the second argument is equivalent to taking the first argument as the results (it does a convolution, as in the next line) else: self.bad_primes_info.append([p,f1,f2]) CC = ComplexField() I = CC.gens()[0] self.sign = I ** root_number_at_oo(h) self.sign /= I ** (root_number_at_oo(h1) * V.dim) self.sign /= I ** (root_number_at_oo(h2) * W.dim) self.sign *= V.sign ** W.dim self.sign *= W.sign ** V.dim for p in bad_primes: if p not in V.bad_semistable_primes or p not in V.bad_semistable_primes: f1 = V.local_euler_factor(p) f2 = W.local_euler_factor(p) det1 = f1.leading_coefficient() * (-1) ** f1.degree() det2 = f2.leading_coefficient() * (-1) ** f2.degree() n1_tame = V.dim - f1.degree() n2_tame = W.dim - f2.degree() n1_wild = ZZ(V.conductor).valuation(p) - n1_tame n2_wild = ZZ(W.conductor).valuation(p) - n2_tame # additionally, we would need to correct this by # replacing det1 by chi1(p) if p is semistable for V # however for all the possible input this currently does # not affect the sign if p in V.bad_semistable_primes: chi1p = 1 # here else: chi1p = det1 if p in W.bad_semistable_primes: chi2p = 1 # here else: chi2p = det2 corr = chi1p ** n2_wild corr *= det1 ** n2_tame corr *= chi2p ** n1_wild corr *= det2 ** n1_tame corr *= (-1) ** (n1_tame * n2_tame) self.sign *= corr/corr.abs() #self.primitive = False self.set_dokchitser_Lfunction() # maybe we should change this to take as many coefficients as implemented # in other Lfunctions self.set_number_of_coefficients() someans = self.algebraic_coefficients(50) # why not. if all( x in ZZ for x in someans): self.selfdual = True else: CC = ComplexField() self.selfdual = all( CC(an).imag().abs() < 0.0001 for an in someans) self.coefficient_type = max(V.coefficient_type, W.coefficient_type) self.coefficient_period = ZZ(V.coefficient_period).lcm(W.coefficient_period) self.ld.gp().quit() ## These are used when creating the classes with the above def set_dokchitser_Lfunction(self): """ The L-function calling Dokchitser's code """ if hasattr(self, "sign"): # print type(self.sign) # type complex would yield an error here. self.ld = Dokchitser(conductor = self.conductor, gammaV = self.gammaV, weight = self.motivic_weight, eps = self.sign, poles = [], residues = []) else: # find the sign from the functional equation # this should be implemented later: # one can pass a variable 'x' to the function # checking the functional equation # and it will return a linear polynomial in x # such that the root must be the sign raise NotImplementedError def set_number_of_coefficients(self): """ Determines the number of coefficients needed using Dokchitser's Note is the number we SHOULD compute. However we will cap this to a smaller size later. """ if not hasattr(self, "ld"): self.set_dokchitser_Lfunction() # note the following line only sets all the variables in the # gp session of Dokchitser self.ld._gp_eval("MaxImaginaryPart = %s"%self.max_imaginary_part) self.numcoeff = self.ld.num_coeffs() # to be on the safe side, we make sure to have a min of terms if self.numcoeff < 50: self.numcoeff = 50 ## produce coefficients def algebraic_coefficients(self, number_of_terms): """ Computes the list [a1,a2,... of coefficients up to a bound. Note that [0] is a1. This is in the alg. normalisation, i.e. s <-> w+1-s """ if self.object_type == "ellipticcurve": return self.original_object[0].anlist(number_of_terms)[1:] elif self.object_type == "dirichletcharacter": chi = self.original_object[0].chi.primitive_character() return [ chi(m) for m in range(1, number_of_terms) ] elif self.object_type == "Artin representation": rho = self.original_object[0] return rho.coefficients_list(upperbound=number_of_terms) elif self.object_type == "Elliptic Modular newform": F = self.original_object[0][0] i = self.original_object[0][1] embeddings = F.q_expansion_embeddings(number_of_terms)[1:] return [x[i] for x in embeddings] elif self.object_type == "tensorproduct": V = self.V1 W = self.V2 L1 = V.algebraic_coefficients(number_of_terms) L2 = W.algebraic_coefficients(number_of_terms) return tensor_get_an(L1, L2, V.dim, W.dim, self.bad_primes_info) else: raise ValueError("You asked for a type that we don't have") def renormalise_coefficients(self): """ This turns a list of algebraically normalised coefficients as above into a list of automorphically normalised, i.e. s <-> 1-s """ # this also turns them into floats and complex. for n in range(len(self.dirichlet_coefficients)): self.dirichlet_coefficients[n] /= sqrt(float(n+1)**self.motivic_weight) ## The tensor product def __mul__(self, other): """ The tensor product of two galois representations is represented here by * """ return GaloisRepresentation([self,other]) ## various direct accessible functions def root_number(self): """ Root number """ return self.sign def dimension(self): """ Dimension = Degree """ return self.dim def cond(self): """ Conductor """ return self.conductor ## Now to the L-function itself def lfunction(self): """ This method replaces the class LFunction in lmfdb.lfunctions.Lfunction to generate the page for this sort of class. After asking for this method the object should have all methods and attributes as one of the subclasses of Lfunction in lmfdb.lfunctions.Lfunction. """ self.compute_kappa_lambda_Q_from_mu_nu() # when tensoring a modular form with a dim > 1 rep, we run # into having to compute a lot of coefficients and this will # take a lot of time. We cut it down and print a warning number_of_terms = min(self.numcoeff, self.besancon_bound) self.dirichlet_coefficients = self.algebraic_coefficients(number_of_terms+1) self.renormalise_coefficients() self.texname = "L(s,\\rho)" self.texnamecompleteds = "\\Lambda(s,\\rho)" self.texnamecompleted1ms = "\\Lambda(1-s, \\widehat{\\rho})" self.title = "$L(s,\\rho)$, where $\\rho$ is a Galois representation" self.credit = 'Workshop in Besancon, 2014' from lmfdb.lfunctions.Lfunction import generateSageLfunction generateSageLfunction(self) def Ltype(self): return "galoisrepresentation" # does not have keys in the previous sense really. def Lkey(self): return {"galoisrepresentation":self.title}
class GaloisRepresentation(Lfunction): def __init__(self, thingy): """ Class representing a L-function coming from a Galois representation. Typically, dirichlet characters, artin reps, elliptic curves,... can give rise to such a class. It can be used for tensor two such together (mul below) and a L-function class can be extracted from it. """ # this is an important global variable. # it is the maximum of the imag parts of values s at # which we will compute L(s,.) self.max_imaginary_part = "40" if isinstance( thingy, sage.schemes.elliptic_curves.ell_rational_field. EllipticCurve_rational_field): self.init_elliptic_curve(thingy) elif isinstance(thingy, lmfdb.WebCharacter.WebDirichletCharacter): self.init_dir_char(thingy) elif isinstance( thingy, lmfdb.artin_representations.math_classes.ArtinRepresentation): self.init_artin_rep(thingy) elif (isinstance(thingy, list) and len(thingy) == 2 and isinstance( thingy[0], lmfdb.classical_modular_forms.web_newform.WebNewform) and isinstance(thingy[1], sage.rings.integer.Integer)): self.init_elliptic_modular_form(thingy[0], thingy[1]) elif (isinstance(thingy, list) and len(thingy) == 2 and isinstance(thingy[0], GaloisRepresentation) and isinstance(thingy[1], GaloisRepresentation)): self.init_tensor_product(thingy[0], thingy[1]) else: raise ValueError( "GaloisRepresentations are currently not implemented for that type (%s) of objects" % type(thingy)) # set a few common variables self.level = self.conductor self.degree = self.dim self.poles = [] self.residues = [] self.algebraic = True self.weight = self.motivic_weight + 1 ## Various ways to construct such a class def init_elliptic_curve(self, E): """ Returns the Galois rep of an elliptic curve over Q """ self.original_object = [E] self.object_type = "ellipticcurve" self.dim = 2 self.motivic_weight = 1 self.conductor = E.conductor() self.bad_semistable_primes = [ fa[0] for fa in self.conductor.factor() if fa[1] == 1 ] self.bad_pot_good = [ p for p in self.conductor.prime_factors() if E.j_invariant().valuation(p) > 0 ] self.sign = E.root_number() self.mu_fe = [] self.nu_fe = [ZZ(1) / ZZ(2)] self.gammaV = [0, 1] self.langlands = True self.selfdual = True self.primitive = True self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.coefficient_type = 2 self.coefficient_period = 0 self.besancon_bound = 50000 self.ld.gp().quit() def eu(p): """ Local Euler factor passed as a function whose input is a prime and whose output is a polynomial such that evaluated at p^-s, we get the inverse of the local factor of the L-function """ R = PolynomialRing(QQ, "T") T = R.gens()[0] N = self.conductor if N % p != 0: # good reduction return 1 - E.ap(p) * T + p * T**2 elif N % (p**2) != 0: # multiplicative reduction return 1 - E.ap(p) * T else: return R(1) self.local_euler_factor = eu def init_dir_char(self, chi): """ Initiate with a Web Dirichlet character. """ self.original_object = [chi] chi = chi.chi.primitive_character() self.object_type = "dirichletcharacter" self.dim = 1 self.motivic_weight = 0 self.conductor = ZZ(chi.conductor()) self.bad_semistable_primes = [] self.bad_pot_good = self.conductor.prime_factors() if chi.is_odd(): aa = 1 bb = I else: aa = 0 bb = 1 self.sign = chi.gauss_sum_numerical() / (bb * float(sqrt(chi.modulus()))) # this has now type python complex. later we need a gp complex self.sign = ComplexField()(self.sign) self.mu_fe = [aa] self.nu_fe = [] self.gammaV = [aa] self.langlands = True self.selfdual = (chi.multiplicative_order() <= 2) # rather than all( abs(chi(m).imag) < 0.0001 for m in range(chi.modulus() ) ) self.primitive = True self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.dirichlet_coefficients = [ chi(m) for m in range(self.numcoeff + 1) ] if self.selfdual: self.coefficient_type = 2 else: self.coefficient_type = 3 self.coefficient_period = chi.modulus() self.besancon_bound = 10000 def eu(p): """ local euler factor """ if self.selfdual: K = QQ else: K = ComplexField() R = PolynomialRing(K, "T") T = R.gens()[0] if self.conductor % p != 0: return 1 - ComplexField()(chi(p)) * T else: return R(1) self.local_euler_factor = eu self.ld.gp().quit() def init_artin_rep(self, rho): """ Initiate with an Artin representation """ self.original_object = [rho] self.object_type = "Artin representation" self.dim = rho.dimension() self.motivic_weight = 0 self.conductor = ZZ(rho.conductor()) self.bad_semistable_primes = [] self.bad_pot_good = self.conductor.prime_factors() self.sign = rho.root_number() self.mu_fe = rho.mu_fe() self.nu_fe = rho.nu_fe() self.gammaV = [ 0 for i in range( rho.number_of_eigenvalues_plus_one_complex_conjugation()) ] for i in range( rho.number_of_eigenvalues_minus_one_complex_conjugation()): self.gammaV.append(1) self.langlands = rho.langlands() self.selfdual = rho.selfdual() self.primitive = rho.primitive() self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.coefficient_type = 0 self.coefficient_period = 0 self.besancon_bound = 3000 def eu(p): """ local euler factor """ f = rho.local_factor(p) co = [ZZ(round(x)) for x in f.coefficients(sparse=False)] R = PolynomialRing(QQ, "T") T = R.gens()[0] return sum(co[n] * T**n for n in range(len(co))) self.local_euler_factor = eu self.ld.gp().quit() def init_elliptic_modular_form(self, F, number): """ Initiate with an Elliptic Modular Form. """ self.number = number self.original_object = [[F, number]] self.object_type = "Elliptic Modular newform" self.dim = 2 self.weight = ZZ(F.weight) self.motivic_weight = ZZ(F.weight) - 1 self.conductor = ZZ(F.level) self.bad_semistable_primes = [ fa[0] for fa in self.conductor.factor() if fa[1] == 1 ] # should be including primes of bad red that are pot good # however I don't know how to recognise them self.bad_pot_good = [] self.langlands = True self.mu_fe = [] self.nu_fe = [ZZ(F.weight - 1) / ZZ(2)] self.primitive = True self.selfdual = True self.coefficient_type = 2 self.coefficient_period = 0 self.sign = (-1)**(self.weight / 2.) if self.conductor != 1: for p, ev in F.atkin_lehner_eigenvals: self.sign *= ev**(self.conductor.valuation(p)) self.gammaV = [0, 1] self.set_dokchitser_Lfunction() self.set_number_of_coefficients() self.besancon_bound = 300 def eu(p): """ Local euler factor """ # There was no function q_expansion_embeddings before the transition to postgres # so I'm not sure what this is supposed to do. ans = F.q_expansion_embeddings(p + 1) K = ComplexField() R = PolynomialRing(K, "T") T = R.gens()[0] N = self.conductor if N % p != 0: # good reduction return 1 - ans[p - 1][self.number] * T + T**2 elif N % (p**2) != 0: # semistable reduction return 1 - ans[p - 1][self.number] * T else: return R(1) self.local_euler_factor = eu self.ld.gp().quit() def init_tensor_product(self, V, W): """ We are given two Galois representations and we will return their tensor product. """ self.original_object = V.original_object + W.original_object self.object_type = "tensorproduct" self.V1 = V self.V2 = W self.dim = V.dim * W.dim self.motivic_weight = V.motivic_weight + W.motivic_weight self.langlands = False # status 2014 :) self.besancon_bound = min(V.besancon_bound, W.besancon_bound) bad2 = ZZ(W.conductor).prime_factors() bad_primes = [x for x in ZZ(V.conductor).prime_factors() if x in bad2] for p in bad_primes: if (p not in V.bad_semistable_primes and p not in W.bad_semistable_primes): # this condition above only applies to the current type of objects # for general reps we would have to test the lines below # to be certain that the formulae are correct. #if ((p not in V.bad_semistable_primes or p not in W.bad_pot_good) and #(p not in W.bad_semistable_primes or p not in V.bad_pot_good) and #(p not in V.bad_semistable_primes or p not in W.bad_semistable_primes)): raise NotImplementedError( "Currently tensor products of Galois representations are only implemented under some conditions.", "The behaviour at %d is too wild (both factors must be semistable)." % p) # check for the possibily of getting poles if V.weight == W.weight and V.conductor == W.conductor: Vans = V.algebraic_coefficients(50) Wans = W.algebraic_coefficients(50) CC = ComplexField() if ((Vans[2] in ZZ and Wans[2] in ZZ and all(Vans[n] == Wans[n] for n in range(1, 50))) or all(CC(Vans[n]) == CC(Wans[n]) for n in range(1, 50))): raise NotImplementedError( "It seems you are asking to tensor a " + "Galois representation with its dual " + "which results in the L-function having " + "a pole. This is not implemented here.") scommon = [ x for x in V.bad_semistable_primes if x in W.bad_semistable_primes ] N = W.conductor**V.dim N *= V.conductor**W.dim for p in bad_primes: n1_tame = V.dim - V.local_euler_factor(p).degree() n2_tame = W.dim - W.local_euler_factor(p).degree() nn = n1_tame * n2_tame N = N // p**nn if p in scommon: # both are degree 1 in this case N = N // p self.conductor = N h1 = selberg_to_hodge(V.motivic_weight, V.mu_fe, V.nu_fe) h2 = selberg_to_hodge(W.motivic_weight, W.mu_fe, W.nu_fe) h = tensor_hodge(h1, h2) w, m, n = hodge_to_selberg(h) self.mu_fe = m self.nu_fe = n _, self.gammaV = gamma_factors(h) # this is used in getting the Dirichlet coefficients. self.bad_primes_info = [] for p in bad_primes: # we have to check if this works in all bad cases ! f1 = V.local_euler_factor(p) f2 = W.local_euler_factor(p) # might be dodgy if f1 or f2 is an approx to the Euler factor if p in scommon: E = tensor_local_factors(f1, f2, V.dim * W.dim) T = f1.parent().gens()[0] # right answer is E(T)*E(pT) self.bad_primes_info.append( [p, E * E(p * T), 1 - T] ) #bad_primes_info.append() with 1-T as the second argument is equivalent to taking the first argument as the results (it does a convolution, as in the next line) else: self.bad_primes_info.append([p, f1, f2]) CC = ComplexField() I = CC.gens()[0] self.sign = I**root_number_at_oo(h) self.sign /= I**(root_number_at_oo(h1) * V.dim) self.sign /= I**(root_number_at_oo(h2) * W.dim) self.sign *= V.sign**W.dim self.sign *= W.sign**V.dim for p in bad_primes: if p not in V.bad_semistable_primes or p not in V.bad_semistable_primes: f1 = V.local_euler_factor(p) f2 = W.local_euler_factor(p) det1 = f1.leading_coefficient() * (-1)**f1.degree() det2 = f2.leading_coefficient() * (-1)**f2.degree() n1_tame = V.dim - f1.degree() n2_tame = W.dim - f2.degree() n1_wild = ZZ(V.conductor).valuation(p) - n1_tame n2_wild = ZZ(W.conductor).valuation(p) - n2_tame # additionally, we would need to correct this by # replacing det1 by chi1(p) if p is semistable for V # however for all the possible input this currently does # not affect the sign if p in V.bad_semistable_primes: chi1p = 1 # here else: chi1p = det1 if p in W.bad_semistable_primes: chi2p = 1 # here else: chi2p = det2 corr = chi1p**n2_wild corr *= det1**n2_tame corr *= chi2p**n1_wild corr *= det2**n1_tame corr *= (-1)**(n1_tame * n2_tame) self.sign *= corr / corr.abs() #self.primitive = False self.set_dokchitser_Lfunction() # maybe we should change this to take as many coefficients as implemented # in other Lfunctions self.set_number_of_coefficients() someans = self.algebraic_coefficients(50) # why not. if all(x in ZZ for x in someans): self.selfdual = True else: CC = ComplexField() self.selfdual = all(CC(an).imag().abs() < 0.0001 for an in someans) self.coefficient_type = max(V.coefficient_type, W.coefficient_type) self.coefficient_period = ZZ(V.coefficient_period).lcm( W.coefficient_period) self.ld.gp().quit() ## These are used when creating the classes with the above def set_dokchitser_Lfunction(self): """ The L-function calling Dokchitser's code """ if hasattr(self, "sign"): # print type(self.sign) # type complex would yield an error here. self.ld = Dokchitser(conductor=self.conductor, gammaV=self.gammaV, weight=self.motivic_weight, eps=self.sign, poles=[], residues=[]) else: # find the sign from the functional equation # this should be implemented later: # one can pass a variable 'x' to the function # checking the functional equation # and it will return a linear polynomial in x # such that the root must be the sign raise NotImplementedError def set_number_of_coefficients(self): """ Determines the number of coefficients needed using Dokchitser's Note is the number we SHOULD compute. However we will cap this to a smaller size later. """ if not hasattr(self, "ld"): self.set_dokchitser_Lfunction() # note the following line only sets all the variables in the # gp session of Dokchitser self.ld._gp_eval("MaxImaginaryPart = %s" % self.max_imaginary_part) self.numcoeff = self.ld.num_coeffs() # to be on the safe side, we make sure to have a min of terms if self.numcoeff < 50: self.numcoeff = 50 ## produce coefficients def algebraic_coefficients(self, number_of_terms): """ Computes the list [a1,a2,... of coefficients up to a bound. Note that [0] is a1. This is in the alg. normalisation, i.e. s <-> w+1-s """ if self.object_type == "ellipticcurve": return self.original_object[0].anlist(number_of_terms)[1:] elif self.object_type == "dirichletcharacter": chi = self.original_object[0].chi.primitive_character() return [chi(m) for m in range(1, number_of_terms)] elif self.object_type == "Artin representation": rho = self.original_object[0] return rho.coefficients_list(upperbound=number_of_terms) elif self.object_type == "Elliptic Modular newform": F = self.original_object[0][0] i = self.original_object[0][1] embeddings = F.q_expansion_embeddings(number_of_terms)[1:] return [x[i] for x in embeddings] elif self.object_type == "tensorproduct": V = self.V1 W = self.V2 L1 = V.algebraic_coefficients(number_of_terms) L2 = W.algebraic_coefficients(number_of_terms) return tensor_get_an(L1, L2, V.dim, W.dim, self.bad_primes_info) else: raise ValueError("You asked for a type that we don't have") def renormalise_coefficients(self): """ This turns a list of algebraically normalised coefficients as above into a list of automorphically normalised, i.e. s <-> 1-s """ # this also turns them into floats and complex. for n in range(len(self.dirichlet_coefficients)): self.dirichlet_coefficients[n] /= sqrt( float(n + 1)**self.motivic_weight) ## The tensor product def __mul__(self, other): """ The tensor product of two galois representations is represented here by * """ return GaloisRepresentation([self, other]) ## various direct accessible functions def root_number(self): """ Root number """ return self.sign def dimension(self): """ Dimension = Degree """ return self.dim def cond(self): """ Conductor """ return self.conductor ## Now to the L-function itself def lfunction(self): """ This method replaces the class LFunction in lmfdb.lfunctions.Lfunction to generate the page for this sort of class. After asking for this method the object should have all methods and attributes as one of the subclasses of Lfunction in lmfdb.lfunctions.Lfunction. """ self.compute_kappa_lambda_Q_from_mu_nu() # when tensoring a modular form with a dim > 1 rep, we run # into having to compute a lot of coefficients and this will # take a lot of time. We cut it down and print a warning number_of_terms = min(self.numcoeff, self.besancon_bound) self.dirichlet_coefficients = self.algebraic_coefficients( number_of_terms + 1) self.renormalise_coefficients() self.texname = "L(s,\\rho)" self.texnamecompleteds = "\\Lambda(s,\\rho)" self.texnamecompleted1ms = "\\Lambda(1-s, \\widehat{\\rho})" self.title = "$L(s,\\rho)$, where $\\rho$ is a Galois representation" self.credit = 'Workshop in Besancon, 2014' from lmfdb.lfunctions.Lfunction import generateSageLfunction generateSageLfunction(self) def Ltype(self): return "galoisrepresentation"
def lseries_dokchitser(E, prec=53): """ Return the Dokchitser L-series object associated to the elliptic curve E, which may be defined over the rational numbers or a number field. Also prec is the number of bits of precision to which evaluation of the L-series occurs. INPUT: - E -- elliptic curve over a number field (or QQ) - prec -- integer (default: 53) precision in *bits* OUTPUT: - Dokchitser L-function object EXAMPLES:: A curve over Q(sqrt(5)), for which we have an optimized implementation:: sage: from psage.ellcurve.lseries.lseries_nf import lseries_dokchitser sage: K.<a> = NumberField(x^2-x-1); E = EllipticCurve([0,-a,a,0,0]) sage: L = lseries_dokchitser(E); L Dokchitser L-function of Elliptic Curve defined by y^2 + a*y = x^3 + (-a)*x^2 over Number Field in a with defining polynomial x^2 - x - 1 sage: L(1) 0.422214159001667 sage: L.taylor_series(1,5) 0.422214159001667 + 0.575883864741340*z - 0.102163426876721*z^2 - 0.158119743123727*z^3 + 0.120350687595265*z^4 + O(z^5) Higher precision:: sage: L = lseries_dokchitser(E, 200) sage: L(1) 0.42221415900166715092967967717023093014455137669360598558872 A curve over Q(i):: sage: K.<i> = NumberField(x^2 + 1) sage: E = EllipticCurve(K, [1,0]) sage: E.conductor().norm() 256 sage: L = lseries_dokchitser(E, 10) sage: L.taylor_series(1,5) 0.86 + 0.58*z - 0.62*z^2 + 0.19*z^3 + 0.18*z^4 + O(z^5) More examples:: sage: lseries_dokchitser(EllipticCurve([0,-1,1,0,0]), 10)(1) 0.25 sage: K.<i> = NumberField(x^2+1) sage: lseries_dokchitser(EllipticCurve(K, [0,-1,1,0,0]), 10)(1) 0.37 sage: K.<a> = NumberField(x^2-x-1) sage: lseries_dokchitser(EllipticCurve(K, [0,-1,1,0,0]), 10)(1) 0.72 sage: E = EllipticCurve([0,-1,1,0,0]) sage: E.quadratic_twist(2).rank() 1 sage: K.<d> = NumberField(x^2-2) sage: L = lseries_dokchitser(EllipticCurve(K, [0,-1,1,0,0]), 10) sage: L(1) 0 sage: L.taylor_series(1, 5) 0.58*z + 0.20*z^2 - 0.50*z^3 + 0.28*z^4 + O(z^5) You can use this function as an algorithm to compute the sign of the functional equation (global root number):: sage: E = EllipticCurve([1..5]) sage: E.root_number() -1 sage: L = lseries_dokchitser(E,32); L Dokchitser L-function of Elliptic Curve defined by y^2 + x*y = x^3 - x^2 + 4*x + 3 over Rational Field sage: L.eps -1 Over QQ, this isn't so useful (since Sage has a root_number method), but over number fields it is very useful:: sage: K.<a> = NumberField(x^2 - x - 1) sage: E1=EllipticCurve([0,-a-1,1,a,0]); E0 = EllipticCurve([0,-a,a,0,0]) sage: lseries_dokchitser(E1, 16).eps -1 sage: E1.rank() 1 sage: lseries_dokchitser(E0, 16).eps 1 sage: E0.rank() 0 """ # The code asssumes in various places that we have a global minimal model, # for example, in anlist_sqrt5 above. E = E.global_minimal_model() # Check that we're over a number field. K = E.base_field() if not is_NumberField(K): raise TypeError, "base field must be a number field" # Compute norm of the conductor -- awkward because QQ elements have no norm method (they should). N = E.conductor() if K != QQ: N = N.norm() # We guess the sign epsilon in the functional equation to be +1 # first. If our guess is wrong then we just choose the other # possibility. epsilon = 1 # Define the Dokchitser L-function object with all parameters set: L = Dokchitser(conductor = N * K.discriminant()**2, gammaV = [0]*K.degree() + [1]*K.degree(), weight = 2, eps = epsilon, poles = [], prec = prec) # Find out how many coefficients of the Dirichlet series are needed # to compute to the requested precision. n = L.num_coeffs() # print "num coeffs = %s"%n # Compute the Dirichlet series coefficients coeffs = anlist(E, n)[1:] # Define a string that when evaluated in PARI defines a function # a(k), which returns the Dirichlet coefficient a_k. s = 'v=%s; a(k)=v[k];'%coeffs # Actually tell the L-series / PARI about the coefficients. L.init_coeffs('a(k)', pari_precode = s) # Test that the functional equation is satisfied. This will very, # very, very likely if we chose the sign of the functional # equation incorrectly, or made any mistake in computing the # Dirichlet series coefficients. tiny = max(1e-8, 1.0/2**(prec-1)) if abs(L.check_functional_equation()) > tiny: # The test failed, so we try the other choice of functional equation. epsilon *= -1 L.eps = epsilon # It is not necessary to recreate L -- just tell PARI the different sign. L._gp_eval('sgn = %s'%epsilon) # Once again, verify that the functional equation is # satisfied. If it is, then we've got it. If it isn't, then # there is definitely some other subtle bug, probably in computed # the Dirichlet series coefficients. if abs(L.check_functional_equation()) > tiny: raise RuntimeError, "Functional equation not numerically satisfied for either choice of sign" L.rename('Dokchitser L-function of %s'%E) return L