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)
Пример #2
0
    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))
Пример #3
0
    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)
Пример #5
0
    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
Пример #9
0
    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
Пример #10
0
    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))
Пример #11
0
 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))
Пример #13
0
 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
Пример #18
0
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))