Esempio n. 1
0
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
Esempio n. 2
0
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}
Esempio n. 3
0
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"
Esempio n. 4
0
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