Example #1
0
def gl2_subgroup_data(label):
    try:
        data = db.gps_gl2zhat.lookup(label)
        if data is None:
            data = db.gps_gl2zhat.lucky({'Slabel':label})
            if data is None:
                raise ValueError
    except ValueError:
        return "Unable to locate data for GL(2,Zhat) subgroup with label: %s" % label

    row_wrap = lambda cap, val: "<tr><td>%s: </td><td>%s</td></tr>\n" % (cap, val)
    matrix = lambda m: r'$\begin{bmatrix}%s&%s\\%s&%s\end{bmatrix}$' % (m[0],m[1],m[2],m[3])
    info = '<table>\n'
    info += row_wrap('Subgroup <b>%s</b>' % (label),  "<small>" + ', '.join([matrix(m) for m in data['generators']]) + "</small>")
    info += "<tr><td></td><td></td></tr>\n"
    info += row_wrap('Level', data['level'])
    info += row_wrap('Index', data['index'])
    info += row_wrap('Genus', data['genus'])
    def ratcusps(c,r):
        if not c:
            return ""
        if not r:
            return " (none of which are rational)"
        if r == c:
            return " (all of which are rational)"
        if r == 1:
            return " (one of which is rational)"
        else:
            return " (of which %s are rational)" % r

    info += row_wrap('Cusps', "%s%s" % (data['cusps'], ratcusps(data['cusps'],data['rational_cusps'])))
    info += row_wrap('Contains $-1$', "yes" if data['quadratic_twists'][0] == label else "no")
    if label != data['label']:
        info += row_wrap('LMFDB label', data['label'])
    if data.get('CPlabel'):
        info += row_wrap('Cummins & Pauli label', "<a href=%scsg%sM.html#level%s>%s</a>" % (CP_URL_PREFIX, data['genus'], data['level'], data['CPlabel']))
    if data.get('RZBlabel'):
        info += row_wrap('Rouse & Zureick-Brown label', "<a href={prefix}{label}.html>{label}</a>".format(prefix= RZB_URL_PREFIX, label=data['RZBlabel']))
    if data.get('Slabel') and label != data.get('Slabel'):
        info += row_wrap('Sutherland label', data['Slabel'])
    if data.get('SZlabel'):
        info += row_wrap('Sutherland & Zywina label', data['SZlabel'])
    N = ZZ(data['level'])
    ell = integer_prime_divisors(N)[0]
    e = N.valuation(ell)
    if e == 1:
        info += row_wrap("Cyclic %s-isogeny field degree" % (ell), min([r[1] for r in data['isogeny_orbits'] if r[0] == ell]))
        info += row_wrap("Cyclic %s-torsion field degree" % (ell), min([r[1] for r in data['orbits'] if r[0] == ell]))
        info += row_wrap("Full %s-torsion field degree" % (ell), ell*(ell-1)*(ell-1)*(ell+1) // data['index'])
    else:
        info += row_wrap("Cyclic %s${}^n$-isogeny field degrees" % (ell), ", ".join(["%s"%(min([r[1] for r in data['isogeny_orbits'] if r[0] == ell**n])) for n in range(1,e+1)]))
        info += row_wrap("Cyclic %s${}^n$-torsion field degrees" % (ell), ", ".join(["%s"%(min([r[1] for r in data['orbits'] if r[0] == ell**n])) for n in range(1,e+1)]))
        info += row_wrap("Full %s${}^n$-torsion field degrees" % (ell), ", ".join(["%s"%(ell*(ell-1)*(ell-1)*(ell+1)*ell**(4*n) // data['index']) for n in range(1,e+1)]))
    if data['genus'] > 0:
        info += row_wrap('Newforms', ''.join(['<a href="%s">%s</a>' % (cmf_url_for_label(x), x) for x in data['newforms']]))
        info += row_wrap('Analytic rank', data['rank'])
        if data['genus'] == 1 and data['model']:
            info += row_wrap('Model', '<a href="%s">%s</a>' % (url_for('ec.by_ec_label',label=data['model']), data['model']))
    info += "</table>\n"
    return info
Example #2
0
def factor_out_p(val, p):
    val = ZZ(val)
    if val == 0 or val == -1:
        return str(val)
    if val == 1:
        return '+1'
    s = 1
    if val < 0:
        s = -1
        val = -val
    ord = val.valuation(p)
    val = val / p**ord
    out = ''
    if s == -1:
        out += '-'
    else:
        out += '+'
    if ord == 1:
        out += str(p)
    elif ord > 1:
        out += '%d^{%d}' % (p, ord)
    if val > 1:
        if ord == 1:
            out += r'\cdot '
        out += str(val)
    return out
Example #3
0
def factor_out_p(val, p):
    val = ZZ(val)
    if val == 0 or val == -1:
        return str(val)
    if val==1:
        return '+1'
    s = 1
    if val<0:
        s = -1
        val = -val
    ord = val.valuation(p)
    val = val/p**ord
    out = ''
    if s == -1:
        out += '-'
    else:
        out += '+'
    if ord==1:
        out +=  str(p)
    elif ord>1:
        out +=  '%d^{%d}' % (p, ord)
    if val>1:
        if ord ==1:
            out += r'\cdot '
        out += str(val)
    return out
Example #4
0
def l_order_point(E, order, l):
    v = ZZ.valuation(l)
    factor = l**v(order)
    P = (order // factor) * E.random_point()
    while P == E(0, 1, 0):
        P = (order // factor) * E.random_point()
    Q = l * P
    while Q != E(0, 1, 0):
        P = Q
        Q = l * P
    return P
Example #5
0
def is_torsion_bicyclic(E, l):
    order = E.order()
    v = ZZ.valuation(l)
    n = v(order)
    if n < 2:
        return False
    D = E.trace_of_frobenius()**2 - 4 * E.base_field().order()
    if not (l**2).divides(D):
        return False
    Phi = ClassicalModularPolynomialDatabase()[l]
    x = PolynomialRing(E.base_field(), 'x').gen()
    return len(Phi(E.j_invariant(), x).roots()) > 2
Example #6
0
def twoadic(line):
    r""" Parses one line from a 2adic file.  Returns the label and a dict
    containing fields with keys '2adic_index', '2adic_log_level',
    '2adic_gens' and '2adic_label'.

    Input line fields:

    conductor iso number ainvs index level gens label

    Sample input lines:

    110005 a 2 [1,-1,1,-185793,29503856] 12 4 [[3,0,0,1],[3,2,2,3],[3,0,0,3]] X24
    27 a 1 [0,0,1,0,-7] inf inf [] CM
    """
    data = split(line)
    assert len(data) == 8
    label = data[0] + data[1] + data[2]
    model = data[7]
    if model == 'CM':
        return label, {
            '2adic_index': int(0),
            '2adic_log_level': None,
            '2adic_gens': None,
            '2adic_label': None,
        }

    index = int(data[4])
    level = ZZ(data[5])
    log_level = int(level.valuation(2))
    assert 2**log_level == level
    if data[6] == '[]':
        gens = []
    else:
        gens = data[6][1:-1].replace('],[', '];[').split(';')
        gens = [[int(c) for c in g[1:-1].split(',')] for g in gens]

    return label, {
        '2adic_index': index,
        '2adic_log_level': log_level,
        '2adic_gens': gens,
        '2adic_label': model,
    }
Example #7
0
def twoadic(line):
    r""" Parses one line from a 2adic file.  Returns the label and a dict
    containing fields with keys '2adic_index', '2adic_log_level',
    '2adic_gens' and '2adic_label'.

    Input line fields:

    conductor iso number ainvs index level gens label

    Sample input lines:

    110005 a 2 [1,-1,1,-185793,29503856] 12 4 [[3,0,0,1],[3,2,2,3],[3,0,0,3]] X24
    27 a 1 [0,0,1,0,-7] inf inf [] CM
    """
    data = split(line)
    assert len(data)==8
    label = data[0] + data[1] + data[2]
    model = data[7]
    if model == 'CM':
        return label, {
            '2adic_index': int(0),
            '2adic_log_level': None,
            '2adic_gens': None,
            '2adic_label': None,
        }

    index = int(data[4])
    level = ZZ(data[5])
    log_level = int(level.valuation(2))
    assert 2**log_level==level
    if data[6]=='[]':
        gens=[]
    else:
        gens = data[6][1:-1].replace('],[','];[').split(';')
        gens = [[int(c) for c in g[1:-1].split(',')] for g in gens]

    return label, {
            '2adic_index': index,
            '2adic_log_level': log_level,
            '2adic_gens': gens,
            '2adic_label': model,
    }
Example #8
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"
Example #9
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"