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
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
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
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
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
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, }
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, }
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"
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"