def eis_phipsi(phi, psi, k, prec=10, t=1, cmplx=False): r""" Return Fourier expansion of Eisenstein series at the cusp oo. INPUT: - ``phi`` -- Dirichlet character. - ``psi`` -- Dirichlet character. - ``k`` -- integer, the weight of the Eistenstein series. - ``prec`` -- integer (default: 10). - ``t`` -- integer (default: 1). OUTPUT: The Fourier expansion of the Eisenstein series $E_k^{\phi,\psi, t}$ (as defined by [Diamond-Shurman]). EXAMPLES: sage: phi = DirichletGroup(3)[1] sage: psi = DirichletGroup(5)[1] sage: E = eisenstein_series_at_inf(phi, psi, 4) """ N1, N2 = phi.level(), psi.level() N = N1 * N2 #The Fourier expansion of the Eisenstein series at infinity is in the field Q(zeta_Ncyc) Ncyc = lcm([euler_phi(N1), euler_phi(N2)]) if cmplx == True: CC = ComplexField(53) pi = ComplexField().pi() I = ComplexField().gen() R = CC zeta = CC(exp(2 * pi * I / Ncyc)) else: R = CyclotomicField(Ncyc) zeta = R.zeta(Ncyc) phi, psi = phi.base_extend(R), psi.base_extend(R) Q = PowerSeriesRing(R, 'q') q = Q.gen() s = O(q**prec) #Weight 2 with trivial characters is calculated separately if k == 2 and phi.conductor() == 1 and psi.conductor() == 1: if t == 1: raise TypeError('E_2 is not a modular form.') s = 1 / 24 * (t - 1) for m in srange(1, prec): for n in srange(1, prec / m + 1): s += n * (q**(m * n) - t * q**(m * n * t)) return s + O(q**prec) if psi.level() == 1 and k == 1: s -= phi.bernoulli(k) / k elif phi.level() == 1: s -= psi.bernoulli(k) / k for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += 2 * phi(m) * psi(n) * n**(k - 1) * q**(m * n * t) return s + O(q**prec)
def local_coordinates_at_infinity(self, prec=20, name='t'): """ For the genus `g` hyperelliptic curve `y^2 = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = x^g/y` is the local parameter at infinity INPUT: - ``prec`` -- desired precision of the local coordinates - ``name`` -- generator of the power series ring (default: ``t``) OUTPUT: `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = x^g/y` is the local parameter at infinity EXAMPLES:: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^5-5*x^2+1) sage: x,y = H.local_coordinates_at_infinity(10) sage: x t^-2 + 5*t^4 - t^8 - 50*t^10 + O(t^12) sage: y t^-5 + 10*t - 2*t^5 - 75*t^7 + 50*t^11 + O(t^12) :: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^3-x+1) sage: x,y = H.local_coordinates_at_infinity(10) sage: x t^-2 + t^2 - t^4 - t^6 + 3*t^8 + O(t^12) sage: y t^-3 + t - t^3 - t^5 + 3*t^7 - 10*t^11 + O(t^12) AUTHOR: - Jennifer Balakrishnan (2007-12) """ g = self.genus() pol = self.hyperelliptic_polynomials()[0] K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) t = K.gen() L = PolynomialRing(K, 'x') x = L.gen() i = 0 w = (x**g / t)**2 - pol wprime = w.derivative(x) if pol.degree() == 2 * g + 1: x = t**-2 else: x = t**-1 for i in range((RR(log(prec + 2) / log(2))).ceil()): x = x - w(x) / wprime(x) y = x**g / t return x + O(t**(prec + 2)), y + O(t**(prec + 2))
def local_coordinates_at_nonweierstrass(self, P, prec=20, name='t'): """ For a non-Weierstrass point `P = (a,b)` on the hyperelliptic curve `y^2 = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = x - a` is the local parameter. INPUT: - ``P = (a, b)`` -- a non-Weierstrass point on self - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: ``t``) OUTPUT: `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = x - a` is the local parameter at `P` EXAMPLES:: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^5-23*x^3+18*x^2+40*x) sage: P = H(1,6) sage: x,y = H.local_coordinates_at_nonweierstrass(P,prec=5) sage: x 1 + t + O(t^5) sage: y 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5) sage: Q = H(-2,12) sage: x,y = H.local_coordinates_at_nonweierstrass(Q,prec=5) sage: x -2 + t + O(t^5) sage: y 12 - 19/2*t - 19/32*t^2 + 61/256*t^3 - 5965/24576*t^4 + O(t^5) AUTHOR: - Jennifer Balakrishnan (2007-12) """ d = P[1] if d == 0: raise TypeError( "P = %s is a Weierstrass point. Use local_coordinates_at_weierstrass instead!" % P) pol = self.hyperelliptic_polynomials()[0] L = PowerSeriesRing(self.base_ring(), name) t = L.gen() L.set_default_prec(prec) K = PowerSeriesRing(L, 'x') pol = K(pol) x = K.gen() b = P[0] f = pol(t + b) for i in range((RR(log(prec) / log(2))).ceil()): d = (d + f / d) / 2 return t + b + O(t**(prec)), d + O(t**(prec))
def eisenstein_series_at_inf(phi, psi, k, prec=10, t=1, base_ring=None): r""" Return Fourier expansion of Eistenstein series at a cusp. INPUT: - ``phi`` -- Dirichlet character. - ``psi`` -- Dirichlet character. - ``k`` -- integer, the weight of the Eistenstein series. - ``prec`` -- integer (default: 10). - ``t`` -- integer (default: 1). OUTPUT: The Fourier expansion of the Eisenstein series $E_k^{\phi,\psi, t}$ (as defined by [Diamond-Shurman]) at the specific cusp. EXAMPLES: sage: phi = DirichletGroup(3)[1] sage: psi = DirichletGroup(5)[1] sage: E = eisenstein_series_at_inf(phi, psi, 4) """ N1, N2 = phi.level(), psi.level() N = N1 * N2 #The Fourier expansion of the Eisenstein series at infinity is in the field Q(zeta_Ncyc) Ncyc = lcm([euler_phi(N1), euler_phi(N2)]) if base_ring == None: base_ring = CyclotomicField(Ncyc) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() s = O(q**prec) #Weight 2 with trivial characters is calculated separately if k == 2 and phi.conductor() == 1 and psi.conductor() == 1: if t == 1: raise TypeError('E_2 is not a modular form.') s = 1 / 24 * (t - 1) for m in srange(1, prec): for n in srange(1, prec / m): s += n * (q**(m * n) - t * q**(m * n * t)) return s + O(q**prec) if psi.level() == 1 and k == 1: s -= phi.bernoulli(k) / k elif phi.level() == 1: s -= psi.bernoulli(k) / k for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += 2 * base_ring(phi(m)) * base_ring( psi(n)) * n**(k - 1) * q**(m * n * t) return s + O(q**prec)
def hz_pullback(self, mu): r""" Compute the pullbacks to Hirzebruch--Zagier curves. This computes the pullback f(\tau * \mu, \tau * \mu') of f to the embedded half-plane H * (\mu, \mu') where \mu' is the conjugate of \mu. The result is a modular form of level equal to the norm of \mu. INPUT: - ``mu`` -- a totally-positive integer in the base-field K. OUTPUT: an OrthogonalModularForm for a signature (2, 1) lattice WARNING: the output's weyl vector is not implemented EXAMPLES:: sage: from weilrep import * sage: x = var('x') sage: K.<sqrt13> = NumberField(x * x - 13) sage: HMF(K).eisenstein_series(2, 15).hz_pullback(4 - sqrt13) 1 + 24*q + O(q^2) """ K = self.base_field() mu = K(mu) nn = mu.norm() tt = mu.trace() if tt <= 0 or nn <= 0: raise ValueError( 'You called hz_pullback with a number that is not totally-positive!' ) d = K.discriminant() a = isqrt((tt * tt - 4 * nn) / d) h = self.fourier_expansion() t, = PowerSeriesRing(QQ, 't').gens() d = K.discriminant() prec = floor(h.prec() / (tt / 2 + 2 * a / sqrt(d))) if d % 4: f = sum([ p[n] * t**((i * tt + n * a) / 2) for i, p in enumerate(h.list()) for n in p.exponents() ]) + O(t**prec) else: f = sum([ p[n] * t**((i * tt + 2 * n * a) / 2) for i, p in enumerate(h.list()) for n in p.exponents() ]) + O(t**prec) return OrthogonalModularFormLorentzian(self.weight(), WeilRep(matrix([[-2 * nn]])), f, scale=self.scale(), weylvec=vector([0]), qexp_representation='shimura')
def eis_G(a, b, N, k, Q=None, t=1, prec=20): if Q == None: Q = PowerSeriesRing(QQ, 'q') R = Q.base_ring() q = Q.gen() a = ZZ(a % N) b = ZZ(b % N) s = 0 if k == 1: if a == 0 and not b == 0: s = QQ(1) / QQ(2) - QQ(b % N) / QQ(N) elif b == 0 and not a == 0: s = QQ(1) / QQ(2) - QQ(a % N) / QQ(N) elif k > 1: if b == 0: s = -N**(k - 1) * ber_pol(QQ(a % N) / QQ(N), k) / QQ(k) #If a == 0 or b ==0 the loop has to start at 1 starta, startb = 0, 0 if a == 0: starta = 1 if b == 0: startb = 1 for v in srange(starta, (prec / t + a) / N): for w in srange(startb, (prec / t / abs((-a + v * N)) + b) / N + 1): s += q**(t * (a + v * N) * (b + w * N)) * (a + v * N)**(k - 1) if (-a + v * N) > 0 and (-b + w * N) > 0: s += (-1)**k * q**(t * (-a + v * N) * (-b + w * N)) * (-a + v * N)**(k - 1) return s + O(q**floor(prec))
def eis_H(a, b, N, k, Q=None, t=1, prec=10): if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q') R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() a = ZZ(a % N) b = ZZ(b % N) s = 0 if k == 1: if a == 0 and not b == 0: s = -QQ(1) / QQ(2) * (1 + zetaN**b) / (1 - zetaN**b) elif b == 0 and not a == 0: s = -QQ(1) / QQ(2) * (1 + zetaN**a) / (1 - zetaN**a) elif a != 0 and b != 0: s = -QQ(1) / QQ(2) * ((1 + zetaN**a) / (1 - zetaN**a) + (1 + zetaN**b) / (1 - zetaN**b)) elif k > 1: s = hurwitz_hat(-b, N, 1 - k, zetaN) for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += (zetaN**(-a * m - b * n) + (-1)**k * zetaN**(a * m + b * n)) * n**(k - 1) * q**(m * n) return s + O(q**floor(prec))
def pad(self, target_param_level): """ When constructed the q-expansions are in the default parameter $q_N$, where $N$ is the level of the form. When taking products, the parameters should be in $q_{N'}$, where $N'$ is the target level. This helper function peforms that renormalization by padding with zeros. """ try: assert (target_param_level % self.param_level == 0) except AssertionError: print( "target parameter level should be a multiple of current parameter level" ) return shift = int(target_param_level / self.param_level) R = self.series.base_ring() Q = PowerSeriesRing(R, 'q' + str(target_param_level)) qN = Q.gen() s = 0 for i in range(self.series.prec() * shift): if i % shift == 0: s += self.series[int(i / shift)] * qN**i else: s += 0 * qN**i s += O(qN**(self.series.prec() * shift + shift - 1)) self.series = s self.param_level = target_param_level
def J_inv_ZZ(self): r""" Return the rational Fourier expansion of ``J_inv``, where the parameter ``d`` is replaced by ``1``. This is the main function used to determine all Fourier expansions! .. NOTE: The Fourier expansion of ``J_inv`` for ``d!=1`` is given by ``J_inv_ZZ(q/d)``. .. TODO: The functions that are used in this implementation are products of hypergeometric series with other, elementary, functions. Implement them and clean up this representation. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.series_constructor import MFSeriesConstructor sage: MFSeriesConstructor(prec=3).J_inv_ZZ() q^-1 + 31/72 + 1823/27648*q + O(q^2) sage: MFSeriesConstructor(group=5, prec=3).J_inv_ZZ() q^-1 + 79/200 + 42877/640000*q + O(q^2) sage: MFSeriesConstructor(group=5, prec=3).J_inv_ZZ().parent() Laurent Series Ring in q over Rational Field sage: MFSeriesConstructor(group=infinity, prec=3).J_inv_ZZ() q^-1 + 3/8 + 69/1024*q + O(q^2) """ F1 = lambda a, b: self._series_ring([ZZ(0)] + [ rising_factorial(a, k) * rising_factorial(b, k) / (ZZ(k).factorial())**2 * sum( ZZ(1) / (a + j) + ZZ(1) / (b + j) - ZZ(2) / ZZ(1 + j) for j in range(ZZ(0), ZZ(k))) for k in range(ZZ(1), ZZ(self._prec + 1)) ], ZZ(self._prec + 1)) F = lambda a, b, c: self._series_ring([ rising_factorial(a, k) * rising_factorial(b, k) / rising_factorial( c, k) / ZZ(k).factorial() for k in range(ZZ(0), ZZ(self._prec + 1)) ], ZZ(self._prec + 1)) a = self._group.alpha() b = self._group.beta() Phi = F1(a, b) / F(a, b, ZZ(1)) q = self._series_ring.gen() # the current implementation of power series reversion is slow # J_inv_ZZ = ZZ(1) / ((q*Phi.exp()).reverse()) temp_f = (q * Phi.exp()).polynomial() new_f = temp_f.revert_series(temp_f.degree() + 1) J_inv_ZZ = ZZ(1) / (new_f + O(q**(temp_f.degree() + 1))) return J_inv_ZZ
def __init__(self, f, x0, singular_data, order=None): r"""Initialize a PuiseuxTSeries using a set of :math:`\pi = \{\tau\}` data. Parameters ---------- f, x, y : polynomial A plane algebraic curve. x0 : complex The x-center of the Puiseux series expansion. singular_data : list The output of :func:`singular`. t : variable The variable in which the Puiseux t series is represented. """ R = f.parent() x, y = R.gens() extension_polynomial, xpart, ypart = singular_data L = LaurentSeriesRing(ypart.base_ring(), 't') t = L.gen() self.f = f self.t = t self._xpart = xpart self._ypart = ypart # store x-part attributes. handle the centered at infinity case self.x0 = x0 if x0 == infinity: x0 = QQ(0) self.center = x0 # extract and store information about the x-part of the puiseux series xpart = xpart(t, 0) xpartshift = xpart - x0 ramification_index, xcoefficient = xpartshift.laurent_polynomial( ).dict().popitem() self.xcoefficient = xcoefficient self.ramification_index = QQ(ramification_index).numerator() self.xpart = xpart # extract and store information about the y-part of the puiseux series self.ypart = L(ypart(t, 0)) self._initialize_extension(extension_polynomial) # determine the initial order. See the order property val = L(ypart(t, O(t))).prec() self._singular_order = 0 if val == infinity else val self._regular_order = self._p.degree(x) # extend to have at least two elements self.extend(nterms=1) # the curve, x-part, and terms output by puiseux make the puiseux # series unique. any mutability only adds terms self.__parent = self.ypart.parent() self._hash = hash((self.f, self.xpart, self.ypart))
def a(offset, f): if not f: return O(q**f.prec()) val = f.valuation() prec = f.prec() return (q**val * R([(i + offset) * f[i] for i in range(val, prec)])).add_bigoh(prec - floor(offset))
def eis_E(cv, dv, N, k, Q=None, param_level=1, prec=10): r""" Computes the coefficient of the Eisenstein series for $\Gamma(N)$. Not intended to be called by user. INPUT: - cv - int, the first coordinate of the vector determining the \Gamma(N) Eisenstein series - dv - int, the second coordinate of the vector determining the \Gamma(N) Eisenstein series - N - int, the level of the Eisenstein series to be computed - k - int, the weight of the Eisenstein seriess to be computed - Q - power series ring, the ring containing the q-expansion to be computed - param_level - int, the parameter of the returned series will be q_{param_level} - prec - int, the precision. The series in q_{param_level} will be truncated after prec coefficients OUTPUT: - an element of the ring Q, which is the Fourier expansion of the Eisenstein series """ if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q') R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() cv = cv % N dv = dv % N #if dv == 0 and cv == 0 and k == 2: # raise ValueError("E_2 is not a modular form") if k == 1: if cv == 0 and dv == 0: raise ValueError("that shouldn't have happened...") elif cv == 0 and dv != 0: s = QQ(1) / QQ(2) * (1 + zetaN**dv) / (1 - zetaN**dv) elif cv != 0: s = QQ(1) / QQ(2) - QQ(cv) / QQ(N) + floor(QQ(cv) / QQ(N)) elif k > 1: if cv == 0: s = hurwitz_hat(QQ(dv), QQ(N), 1 - k, zetaN) else: s = 0 for n1 in xrange(1, prec): # this is n/m in DS for n2 in xrange(1, prec / n1 + 1): # this is m in DS if Mod(n1, N) == Mod(cv, N): s += n2**(k - 1) * zetaN**(dv * n2) * q**(n1 * n2) if Mod(n1, N) == Mod(-cv, N): s += (-1)**k * n2**(k - 1) * zetaN**(-dv * n2) * q**(n1 * n2) return s + O(q**floor(prec))
def _compute_nth_coeff(self, n, twist=None): r""" Computes the coefficient of T^n. """ #TODO: Check that n is not too big #TODO implement twist Phis = self._Phis p = Phis.parent().prime() if n == 0: return sum( [self._basic_integral(a, 0, twist) for a in range(1, p)]) p_prec, var_prec = Phis.precision_absolute() max_j = Phis.parent().coefficient_module().length_of_moments(p_prec) ans_prec = max_j - (n / (p - 1)).floor() - min( max_j, n) - (max_j / p).floor() if ans_prec == 0: return self._coefficient_ring(0) #prec = self._Phis.parent()#precision_absolute()[0] #Not quite right, probably #print "@@@@n =", n, "prec =", prec cjns = list(logp_binom(n, p, max_j + 1)) #print cjns teich = Phis.parent().base_ring().base_ring().teichmuller #Next line should work but loses precision!!! ans = sum([ cjns[j] * sum([((~teich(a))**j) * self._basic_integral(a, j, twist) for a in range(1, p)]) for j in range(1, min(max_j, len(cjns))) ]) #Instead do this messed up thing w = ans.parent().gen() #ans = 0*w #for j in range(1,min(max_j, len(cjns))): # ans_term = [0*w] * var_prec # for a in range(1,p): # term = (((~teich(a)) ** j) * self._basic_integral(a, j, twist)).list() # for i in range(min(var_prec, len(term))): # ans_term[i] += term[i] # ans += cjns[j] * sum([ans_term[i] * w**i for i in range(var_prec)]) #print ans_prec ans_prec = O(p**ans_prec) #print ans_prec #print ans for i in range(ans.degree() + 1): ans += ans_prec * w**i return ans
def eis_F(cv, dv, N, k, Q=None, prec=10, t=1): """ Computes the coefficient of the Eisenstein series for $\Gamma(N)$. Not indented to be called by user. INPUT: - cv - int, the first coordinate of the vector determining the \Gamma(N) Eisenstein series - dv - int, the second coordinate of the vector determining the \Gamma(N) Eisenstein series - N - int, the level of the Eisenstein series to be computed - k - int, the weight of the Eisenstein seriess to be computed - Q - power series ring, the ring containing the q-expansion to be computed - param_level - int, the parameter of the returned series will be q_{param_level} - prec - int, the precision. The series in q_{param_level} will be truncated after prec coefficients OUTPUT: - an element of the ring Q, which is the Fourier expansion of the Eisenstein series """ if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q{}'.format(N)) R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() s = 0 if k == 1: if cv % N == 0 and dv % N != 0: s = QQ(1) / QQ(2) * (1 + zetaN**dv) / (1 - zetaN**dv) elif cv % N != 0: s = QQ(1) / QQ(2) - QQ(cv) / QQ(N) + floor(QQ(cv) / QQ(N)) elif k > 1: s = -ber_pol(QQ(cv) / QQ(N) - floor(QQ(cv) / QQ(N)), k) / QQ(k) for n1 in xrange(1, ceil(prec / QQ(t))): # this is n/m in DS for n2 in xrange(1, ceil(prec / QQ(t) / QQ(n1)) + 1): # this is m in DS if Mod(n1, N) == Mod(cv, N): s += N**(1 - k) * n1**(k - 1) * zetaN**(dv * n2) * q**(t * n1 * n2) if Mod(n1, N) == Mod(-cv, N): s += (-1)**k * N**(1 - k) * n1**(k - 1) * zetaN**( -dv * n2) * q**(t * n1 * n2) return s + O(q**floor(prec))
def apply_triangular_matrix(self, delta): """ If self is a q-expansion and q = exp(2*pi*I*tau), then applying a triangular matrix [[a,b],[0,d]] to tau gives a well-defined q-expansion as long as the base ring contains zeta_d. Warning: The correct slash-action would also multiply with det(delta)^k/2 but we carefully avoid that in all applications of apply_triangular_matrix. """ R = self.series.base_ring() a, b, d = delta[0][0], delta[0][1], delta[1][1] """ Reduce the fraction a/d """ a2, d2 = (a / d).numerator(), (a / d).denominator() b2, d3 = (b / d).numerator(), (b / d).denominator() zetad = R.zeta(d3 * self.param_level) Q = PowerSeriesRing(R, 'q' + str(self.param_level * d2)) qNd = Q.gen() s = 0 for i in range(self.series.prec()): s += self.series[i] * zetad**(i * b2) * qNd**(i * a2) s = s + O(qNd**(self.series.prec())) s = s * d**(-self.weight) return QExpansion(self.level * d2, self.weight, s, self.param_level * d2)
def prune(self, tar_param_level): """ Prunes terms from the q-expansion to return a series in the desired parameter """ try: assert (self.param_level % tar_param_level == 0) except AssertionError: print( "target parameter level should be a divsor of current parameter level" ) return R = self.series.base_ring() Q = PowerSeriesRing(R, 'q' + str(tar_param_level)) q = Q.gen() s = 0 for i in range(self.series.prec() * tar_param_level // self.param_level): s += self.series[i * (self.param_level // tar_param_level)] * q**i s += O(q**(self.series.prec() * tar_param_level // self.param_level)) self.series = s self.param_level = tar_param_level
def product_space(chi, k, weights=False, base_ring=None, verbose=False): r""" Computes all eisenstein series, and products of pairs of eisenstein series of lower weight, lying in the space of modular forms of weight $k$ and nebentypus $\chi$. INPUT: - chi - Dirichlet character, the nebentypus of the target space - k - an integer, the weight of the target space OUTPUT: - a matrix of coefficients of q-expansions, which are the products of Eisenstein series in M_k(chi). WARNING: It is only for principal chi that we know that the resulting space is the whole space of modular forms. """ if weights == False: weights = srange(1, k / 2 + 1) weight_dict = {} weight_dict[-1] = [w for w in weights if w % 2] # Odd weights weight_dict[1] = [w for w in weights if not w % 2] # Even weights try: N = chi.modulus() except AttributeError: if chi.parent() == ZZ: N = chi chi = DirichletGroup(N)[0] Id = DirichletGroup(1)[0] if chi(-1) != (-1)**k: raise ValueError('chi(-1)!=(-1)^k') sturm = ModularForms(N, k).sturm_bound() + 1 if N > 1: target_dim = dimension_modular_forms(chi, k) else: target_dim = dimension_modular_forms(1, k) D = DirichletGroup(N) # product_space should ideally be called over number fields. Over complex # numbers the exact linear algebra solutions might not exist. if base_ring == None: base_ring = CyclotomicField(euler_phi(N)) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() d = len(D) prim_chars = [phi.primitive_character() for phi in D] divs = divisors(N) products = Matrix(base_ring, []) indexlist = [] rank = 0 if verbose: print(D) print('Sturm bound', sturm) #TODO: target_dim needs refinment in the case of weight 2. print('Target dimension', target_dim) for i in srange(0, d): # First character phi = prim_chars[i] M1 = phi.conductor() for j in srange(0, d): # Second character psi = prim_chars[j] M2 = psi.conductor() if not M1 * M2 in divs: continue parity = psi(-1) * phi(-1) for t1 in divs: if not M1 * M2 * t1 in divs: continue #TODO: THE NEXT CONDITION NEEDS TO BE CORRECTED. THIS IS JUST A TEST if phi.bar() == psi and not ( k == 2): #and i==0 and j==0 and t1==1): E = eisenstein_series_at_inf(phi, psi, k, sturm, t1, base_ring).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([k, i, j, t1]) rank += 1 if verbose: print('Added ', [k, i, j, t1]) print('Rank is now', rank) if rank == target_dim: return products, indexlist for t in divs: if not M1 * M2 * t1 * t in divs: continue for t2 in divs: if not M1 * M2 * t1 * t2 * t in divs: continue for l in weight_dict[parity]: if l == 1 and phi.is_odd(): continue if i == 0 and j == 0 and (l == 2 or l == k - 2): continue #TODO: THE NEXT CONDITION NEEDS TO BE REMOVED. THIS IS JUST A TEST if l == 2 or l == k - 2: continue E1 = eisenstein_series_at_inf( phi, psi, l, sturm, t1 * t, base_ring) E2 = eisenstein_series_at_inf( phi**(-1), psi**(-1), k - l, sturm, t2 * t, base_ring) #If chi is non-principal this needs to be changed to be something like chi*phi^(-1) instead of phi^(-1) E = (E1 * E2 + O(q**sturm)).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([l, k - l, i, j, t1, t2, t]) rank += 1 if verbose: print('Added ', [l, k - l, i, j, t1, t2, t]) print('Rank', rank) if rank == target_dim: return products, indexlist return products, indexlist
def e_phipsi(phi, psi, k, t=1, prec=5, mat=Matrix([[1, 0], [0, 1]]), base_ring=None): r""" Computes the Eisenstein series attached to the characters psi, phi as defined on p129 of Diamond--Shurman hit by mat \in \SL_2(\Z) INPUT: - psi, a primitive Dirichlet character - phi, a primitive Dirichlet character - k, int -- the weight - t, int -- the shift - prec, the desired absolute precision, can be fractional. The expansion will be up to O(q_w^(floor(w*prec))), where w is the width of the cusp. - mat, a matrix - typically taking $i\infty$ to some other cusp - base_ring, a ring - the ring in which the modular forms are defined OUTPUT: - an instance of QExpansion """ chi2 = phi chi1 = psi try: assert (QQ(chi1(-1)) * QQ(chi2(-1)) == (-1)**k) except AssertionError: print("Parity of characters must match parity of weight") return None N1 = chi1.level() N2 = chi2.level() N = t * N1 * N2 mat2, Tn = find_correct_matrix(mat, N) #By construction gamma = mat2 * Tn * mat**(-1) is in Gamma0(N) so if E is our Eisenstein series we can evaluate E|mat = chi(gamma) * E|mat2*Tn. #Since E|mat2 has a Fourier expansion in qN, the matrix Tn acts as a a twist. The value c_gamma = chi(gamma) is calculated below. #The point of swapping mat with mat2 is that mat2 satisfies C|N, C>0, (A,N)=1 and N|B and our formulas for the coefficients require this condition. A, B, C, D = mat2.list() gamma = mat2 * Tn * mat**(-1) if base_ring == None: Nbig = lcm(N, euler_phi(N1 * N2)) base_ring = CyclotomicField(Nbig, 'zeta' + str(Nbig)) zetaNbig = base_ring.gen() zetaN = zetaNbig**(Nbig / N) else: zetaN = base_ring.zeta(N) g = gcd(t, C) g1 = gcd(N1 * g, C) g2 = gcd(N2 * g, C) #Resulting Eisenstein series will have Fourier expansion in q_N**(g1g2)=q_(N/gcd(N,g1g2))**(g1g2/gcd(N,g1g2)) Q = PowerSeriesRing(base_ring, 'q' + str(N / gcd(N, g1 * g2))) qN = Q.gen() zeta_Cg = zetaN**(N / (C / g)) zeta_tmp = zeta_Cg**(inverse_mod(-A * ZZ(t / g), C / g)) #Calculating a few values that will be used repeatedly chi1bar_vals = [base_ring(chi1.bar()(i)) for i in range(N1)] cp_list1 = [i for i in range(N1) if gcd(i, N1) == 1] chi2bar_vals = [base_ring(chi2.bar()(i)) for i in range(N2)] cp_list2 = [i for i in range(N2) if gcd(i, N2) == 1] #Computation of the Fourier coefficients ser = O(qN**floor(prec * N / gcd(N, g1 * g2))) for n in range(1, ceil(prec * N / QQ(g1 * g2)) + 1): f = 0 for m in divisors(n) + list(map(lambda x: -x, divisors(n))): a = 0 for r1 in cp_list1: b = 0 if ((C / g1) * r1 - QQ(n) / QQ(m)) % ((N1 * g) / g1) == 0: for r2 in cp_list2: if ((C / g2) * r2 - m) % ((N2 * g) / g2) == 0: b += chi2bar_vals[r2] * zeta_tmp**( (n / m - (C / g1) * r1) / ((N1 * g) / g1) * (m - (C / g2) * r2) / ((N2 * g) / g2)) a += chi1bar_vals[r1] * b a *= sign(m) * m**(k - 1) f += a f *= zetaN**(inverse_mod(A, N) * (g1 * g2) / C * n) #The additional factor zetaN**(n*Tn[0][1]) comes from the twist by Tn ser += zetaN**(n * g1 * g2 * Tn[0][1]) * f * qN**(n * ( (g1 * g2) / gcd(N, g1 * g2))) #zk(chi1, chi2, c) gauss1 = base_ring(gauss_sum_corr(chi1.bar())) gauss2 = base_ring(gauss_sum_corr(chi2.bar())) zk = 2 * (N2 * t / QQ(g2))**(k - 1) * (t / g) * gauss1 * gauss2 #The following is a temporary fix for a bug in sage if base_ring == CC: G = DirichletGroup(N1 * N2, CC) G[0] #Otherwise chi1.bar().extend(N1*N2).base_extend(CC) or chi2.bar().extend(N1*N2).base_extend(CC) will produce an error #Constant term #c_gamma comes from replacing mat with mat2. c_gamma = chi1bar_vals[gamma[1][1] % N1] * chi2bar_vals[gamma[1][1] % N2] if N1.divides(C) and ZZ(C / N1).divides(t) and gcd(t / (C / N1), N1) == 1: ser += (-1)**(k - 1) * gauss2 / QQ( N2 * (g2 / g)**(k - 1)) * chi1bar_vals[(-A * t / g) % N1] * Sk( chi1.bar().extend(N1 * N2).base_extend(base_ring) * chi2.extend(N1 * N2).base_extend(base_ring), k) elif k == 1 and N2.divides(C) and ZZ(C / N2).divides(t) and gcd( t / (C / N2), N2) == 1: ser += gauss1 / QQ(N1) * chi2bar_vals[(-A * t / g) % N2] * Sk( chi1.extend(N1 * N2).base_extend(base_ring) * chi2.bar().extend(N1 * N2).base_extend(base_ring), k) return QExpansion( N, k, 2 / zk * c_gamma * ser + O(qN**floor(prec * N / gcd(N, g1 * g2))), N / gcd(N, g1 * g2))