Modular Forms and Hecke Operators - Thematic Tutorials

Skip to content
Back to top
View this page
Edit this page

# Modular Forms and Hecke Operators

## Congruence subgroups

### Definition

A congruence subgroup is a subgroup of the group \(\mathrm{SL}_2(\ZZ)\) of determinant \(\pm 1\) integer matrices that contains

\[\Gamma(N) = \mathrm{Ker}(\mathrm{SL}_2(\ZZ) \to \mathrm{SL}_2(\ZZ/N\ZZ))\]

for some positive integer \(N\). Since \(\Gamma(N)\) has finite index in \(\mathrm{SL}_2(\ZZ)\), all congruence subgroups have finite index. The converse is not true, though in many other settings it is true (see [paper of Serre]).

The inverse image \(\Gamma_0(N)\) of the subgroup of upper triangular matrices in \(\mathrm{SL}_2(\ZZ/N\ZZ)\) is a congruence subgroup, as is the inverse image \(\Gamma_1(N)\) of the subgroup of matrices of the form \(\left(\begin{smallmatrix}1&*\\0&1\end{smallmatrix}\right)\). Also, for any subgroup \(H\subset (\ZZ/N\ZZ)^*\), the inverse image \(\Gamma_H(N)\) of the subgroup of \(\mathrm{SL}_2(\ZZ/N\ZZ)\) of all elements of the form \(\left(\begin{smallmatrix}a&*\\0&d\end{smallmatrix}\right)\) with \(d \in H\) is a congruence subgroup.

We can create each of the above congruence subgroups in Sage, using the Gamma0, Gamma1, and GammaH commands.

Sage

    sage: Gamma0(8)
    Congruence Subgroup Gamma0(8)
    sage: Gamma1(13)
    Congruence Subgroup Gamma1(13)
    sage: GammaH(11,[3])
    Congruence Subgroup Gamma_H(11) with H generated by [3]

Python

    >>> from sage.all import *
    >>> Gamma0(Integer(8))
    Congruence Subgroup Gamma0(8)
    >>> Gamma1(Integer(13))
    Congruence Subgroup Gamma1(13)
    >>> GammaH(Integer(11),[Integer(3)])
    Congruence Subgroup Gamma_H(11) with H generated by [3]

The second argument to the GammaH command is a list of generators of the subgroup \(H\) of \((\ZZ/N\ZZ)^*\).

### Generators

Sage can compute a list of generators for these subgroups. The algorithm Sage uses is a straightforward generic procedure that uses coset representatives for the congruence subgroup (which are easy to enumerate) to obtain a list of generators [[ref my modular forms book]].

The list of generators Sage computes is unfortunately large. Improving this would be an excellent Sage development project, which would involve much beautiful mathematics.

UPDATE (March 2012): The project referred to above has been carried out (by several people, notably Hartmut Monien, building on earlier work of Chris Kurth). Sage now uses a much more advanced algorithm based on Farey symbols which calculates a minimal set of generators.

Sage

    sage: Gamma0(2).gens()
    (
    [1 1]  [ 1 -1]
    [0 1], [ 2 -1]
    )
    sage: Gamma0(2).gens(algorithm="todd-coxeter") # the old implementation
    (
    [1 1]  [-1  0]  [ 1 -1]  [ 1 -1]  [-1  1]
    [0 1], [ 0 -1], [ 0  1], [ 2 -1], [-2  1]
    )
    sage: len(Gamma1(13).gens())
    15

Python

    >>> from sage.all import *
    >>> Gamma0(Integer(2)).gens()
    (
    [1 1]  [ 1 -1]
    [0 1], [ 2 -1]
    )
    >>> Gamma0(Integer(2)).gens(algorithm="todd-coxeter") # the old implementation
    (
    [1 1]  [-1  0]  [ 1 -1]  [ 1 -1]  [-1  1]
    [0 1], [ 0 -1], [ 0  1], [ 2 -1], [-2  1]
    )
    >>> len(Gamma1(Integer(13)).gens())
    15

## Modular Forms

### Definition

A modular form on a congruence subgroup \(\Gamma\) of integer weight \(k\) is a holomorphic function \(f(z)\) on the upper half plane

\[\mathfrak{h}^* = \{z \in \CC : \Im(z) > 0\}\cup \QQ \cup\{i\infty\}\]

such that for every matrix \(\left(\begin{smallmatrix}a&b\\c&d\end{smallmatrix}\right)\in\Gamma\), we have

\[f\left(\frac{az+b}{cz+d}\right) = (cz+d)^{k} f(z).\]

A cusp form is a modular form that vanishes at all of the cusps \(\QQ \cup \{i\infty\}\).

If \(\Gamma\) contains \(\Gamma_1(N)\) for some \(N\), then \(\left(\begin{smallmatrix}1&1\\0&1\end{smallmatrix}\right)\in\Gamma\), so the modular form condition implies that \(f(z) = f(z+1)\). This, coupled with the holomorphicity condition, implies that \(f(z)\) has a Fourier expansion

\[f(z) = \sum_{n=0}^{\infty} a_n e^{2\pi i n z}\]

with \(a_n\in\CC\). We let \(q = e^{2\pi i z}\), and call \(f = \sum_{n=0}^{\infty} a_n q^n\) the \(q\)-expansion of \(f\).

### Creation in Sage

Henceforth we assume that \(\Gamma\) is either \(\Gamma_1(N)\), \(\Gamma_0(N)\), or \(\Gamma_H(N)\) for some \(H\) and \(N\). The complex vector space \(M_k(\Gamma)\) of all modular forms of weight \(k\) on \(\Gamma\) is a finite dimensional vector space.

We create the space \(M_k(\Gamma)\) in Sage by typing `ModularForms(G, k)` where \(G\) is the congruence subgroup and \(k\) is the weight.

Sage

    sage: ModularForms(Gamma0(25), 4)
    Modular Forms space of dimension 11 for ...
    sage: S = CuspForms(Gamma0(25),4, prec=15); S
    Cuspidal subspace of dimension 5 of Modular Forms space ...
    sage: S.basis()
    [q + q^9 - 8*q^11 - 8*q^14 + O(q^15),
     q^2 - q^7 - q^8 - 7*q^12 + 7*q^13 + O(q^15),
     q^3 + q^7 - 2*q^8 - 6*q^12 - 5*q^13 + O(q^15),
     q^4 - q^6 - 3*q^9 + 5*q^11 - 2*q^14 + O(q^15),
     q^5 - 4*q^10 + O(q^15)]

Python

    >>> from sage.all import *
    >>> ModularForms(Gamma0(Integer(25)), Integer(4))
    Modular Forms space of dimension 11 for ...
    >>> S = CuspForms(Gamma0(Integer(25)),Integer(4), prec=Integer(15)); S
    Cuspidal subspace of dimension 5 of Modular Forms space ...
    >>> S.basis()
    [q + q^9 - 8*q^11 - 8*q^14 + O(q^15),
     q^2 - q^7 - q^8 - 7*q^12 + 7*q^13 + O(q^15),
     q^3 + q^7 - 2*q^8 - 6*q^12 - 5*q^13 + O(q^15),
     q^4 - q^6 - 3*q^9 + 5*q^11 - 2*q^14 + O(q^15),
     q^5 - 4*q^10 + O(q^15)]

### Dimension Formulas

Sage computes the dimensions of all these spaces using simple arithmetic formulas instead of actually computing bases for the spaces in question. In fact, Sage has the most general collection of modular forms dimension formulas of any software; type `help(sage.modular.dims)` to see a list of arithmetic functions that are used to implement these dimension formulas.

Sage

    sage: ModularForms(Gamma1(949284), 456).dimension()
    11156973844800
    sage: from sage.modular.dims import dimension_cusp_forms
    sage: a = [dimension_cusp_forms(Gamma0(N),2) for N in [1..25]]; a
    [0, 0, ..., 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 2, 2, 1, 0]
    sage: oeis(a)                                       # optional - internet
    0: A001617: Genus of modular group Gamma_0(n). Or, genus of modular curve X_0(n).

Python

    >>> from sage.all import *
    >>> ModularForms(Gamma1(Integer(949284)), Integer(456)).dimension()
    11156973844800
    >>> from sage.modular.dims import dimension_cusp_forms
    >>> a = [dimension_cusp_forms(Gamma0(N),Integer(2)) for N in (ellipsis_range(Integer(1),Ellipsis,Integer(25)))]; a
    [0, 0, ..., 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 2, 2, 1, 0]
    >>> oeis(a)                                       # optional - internet
    0: A001617: Genus of modular group Gamma_0(n). Or, genus of modular curve X_0(n).

Sage does not have simple formulas for dimensions of spaces of modular forms of weight \(1\), since such formulas perhaps do not exist.

### Diamond Bracket Operators

The space \(M_k(\Gamma_1(N))\) is equipped with an action of \((\ZZ/N\ZZ)^*\) by diamond bracket operators \(\langle d \rangle\), and this induces a decomposition

\[M_k(\Gamma_1(N)) = \bigoplus_{\varepsilon:(\ZZ/N\ZZ)^* \to \CC^*} M_k(N,\varepsilon),\]

where the sum is over all complex characters of the finite abelian group \((\ZZ/N\ZZ)^*\). These characters are called Dirichlet characters, which are central in number theory.

The factors \(M_k(N,\varepsilon)\) then have bases whose \(q\)-expansions are elements of \(R[[q]]\), where \(R = \ZZ[\varepsilon]\) is the ring generated over \(\ZZ\) by the image of \(\varepsilon\). We illustrate this with \(N=k=5\) below, where DirichletGroup will be described later.

Sage

    sage: CuspForms(DirichletGroup(5).0, 5).basis()
    [q + (-zeta4 - 1)*q^2 + (6*zeta4 - 6)*q^3 - 14*zeta4*q^4 + (15*zeta4 + 20)*q^5 + O(q^6)]

Python

    >>> from sage.all import *
    >>> CuspForms(DirichletGroup(Integer(5)).gen(0), Integer(5)).basis()
    [q + (-zeta4 - 1)*q^2 + (6*zeta4 - 6)*q^3 - 14*zeta4*q^4 + (15*zeta4 + 20)*q^5 + O(q^6)]

### Dirichlet Characters

Use the command DirichletGroup(N,R) to create the group of all Dirichlet characters of modulus \(N\) taking values in the ring \(R\). If \(R\) is omitted, it defaults to a cyclotomic field.

Sage

    sage: G = DirichletGroup(8); G
    Group of Dirichlet characters modulo 8 with values in Cyclotomic Field of order 2 and degree 1
    sage: v = G.list(); v
    [Dirichlet character modulo 8 of conductor 1 mapping 7 |--> 1, 5 |--> 1,
    Dirichlet character modulo 8 of conductor 4 mapping 7 |--> -1, 5 |--> 1,
    Dirichlet character modulo 8 of conductor 8 mapping 7 |--> 1, 5 |--> -1,
    Dirichlet character modulo 8 of conductor 8 mapping 7 |--> -1, 5 |--> -1]
    sage: eps = G.0; eps
    Dirichlet character modulo 8 of conductor 4 mapping 7 |--> -1, 5 |--> 1
    sage: eps.values()
    [0, 1, 0, -1, 0, 1, 0, -1]

Python

    >>> from sage.all import *
    >>> G = DirichletGroup(Integer(8)); G
    Group of Dirichlet characters modulo 8 with values in Cyclotomic Field of order 2 and degree 1
    >>> v = G.list(); v
    [Dirichlet character modulo 8 of conductor 1 mapping 7 |--> 1, 5 |--> 1,
    Dirichlet character modulo 8 of conductor 4 mapping 7 |--> -1, 5 |--> 1,
    Dirichlet character modulo 8 of conductor 8 mapping 7 |--> 1, 5 |--> -1,
    Dirichlet character modulo 8 of conductor 8 mapping 7 |--> -1, 5 |--> -1]
    >>> eps = G.gen(0); eps
    Dirichlet character modulo 8 of conductor 4 mapping 7 |--> -1, 5 |--> 1
    >>> eps.values()
    [0, 1, 0, -1, 0, 1, 0, -1]

Sage both represents Dirichlet characters by giving a “matrix”, i.e., the list of images of canonical generators of \((\ZZ/N\ZZ)^*\), and as vectors modulo an integer \(n\). For years, I was torn between these two representations, until J. Quer and I realized that the best approach is to use both and make it easy to convert between them.

Sage

    sage: parent(eps.element())
    Vector space of dimension 2 over Ring of integers modulo 2

[Python]

    >>> from sage.all import *
    >>> parent(eps.element())
    Vector space of dimension 2 over Ring of integers modulo 2

Given a Dirichlet character, Sage also lets you compute the associated Jacobi and Gauss sums, generalized Bernoulli numbers, the conductor, Galois orbit, etc.

### Decomposing \(M_k(\Gamma_1(N))\)

Recall that Dirichlet characters give a decomposition

\[M_k(\Gamma_1(N)) = \bigoplus_{\varepsilon:(\ZZ/N\ZZ)^* \to \CC^*} M_k(N,\varepsilon).\]

Given a Dirichlet character \(\varepsilon\) we type ModularForms(eps, weight) to create the space of modular forms with that character and a given integer weight. For example, we create the space of forms of weight \(5\) with the character modulo \(8\) above that is \(-1\) on \(3\) and \(1\) on \(5\) as follows.

Sage

    sage: ModularForms(eps,5)
    Modular Forms space of dimension 6, character [-1, 1] and
    weight 5 over Rational Field
    sage: sum([ModularForms(eps,5).dimension() for eps in v])
    11
    sage: ModularForms(Gamma1(8),5)
    Modular Forms space of dimension 11 ...

Python

    >>> from sage.all import *
    >>> ModularForms(eps,Integer(5))
    Modular Forms space of dimension 6, character [-1, 1] and
    weight 5 over Rational Field
    >>> sum([ModularForms(eps,Integer(5)).dimension() for eps in v])
    11
    >>> ModularForms(Gamma1(Integer(8)),Integer(5))
    Modular Forms space of dimension 11 ...

Note

Exercise: Compute the dimensions of all spaces \(M_2(37,\varepsilon)\) for all Dirichlet characters \(\varepsilon\).

### Hecke Operators

The space \(M_k(\Gamma)\) is equipped with an action of a commuting ring \(\mathbb{T}\) of Hecke operators \(T_n\) for \(n\geq 1\). A standard computational problem in the theory of modular forms is to compute an explicit basis of \(q\)-expansion for \(M_k(\Gamma)\) along with matrices for the action of any Hecke operator \(T_n\), and to compute the subspace \(S_k(\Gamma)\) of cusp forms.

Sage

    sage: M = ModularForms(Gamma0(11),4)
    sage: M.basis()
    [q + 3*q^3 - 6*q^4 - 7*q^5 + O(q^6),
     q^2 - 4*q^3 + 2*q^4 + 8*q^5 + O(q^6),
     1 + O(q^6),
     q + 9*q^2 + 28*q^3 + 73*q^4 + 126*q^5 + O(q^6)]
    sage: M.hecke_matrix(2)
    [0 2 0 0]
    [1 2 0 0]
    [0 0 9 0]
    [0 0 0 9]

Python

    >>> from sage.all import *
    >>> M = ModularForms(Gamma0(Integer(11)),Integer(4))
    >>> M.basis()
    [q + 3*q^3 - 6*q^4 - 7*q^5 + O(q^6),
     q^2 - 4*q^3 + 2*q^4 + 8*q^5 + O(q^6),
     1 + O(q^6),
     q + 9*q^2 + 28*q^3 + 73*q^4 + 126*q^5 + O(q^6)]
    >>> M.hecke_matrix(Integer(2))
    [0 2 0 0]
    [1 2 0 0]
    [0 0 9 0]
    [0 0 0 9]

We can also compute Hecke operators on the cuspidal subspace.

Sage

    sage: S = M.cuspidal_subspace()
    sage: S.hecke_matrix(2)
    [0 2]
    [1 2]
    sage: S.hecke_matrix(3)
    [ 3 -8]
    [-4 -5]

[Python]

    >>> from sage.all import *
    >>> S = M.cuspidal_subspace()
    >>> S.hecke_matrix(Integer(2))
    [0 2]
    [1 2]
    >>> S.hecke_matrix(Integer(3))
    [ 3 -8]
    [-4 -5]

### Hecke Operator on \(M_k(\Gamma_1(N))\)

At the time these lectures were first written, Sage didn’t yet implement computation of the Hecke operators on \(M_k(\Gamma_1(N))\), but these have subsequently been added:

Sage

    sage: M = ModularForms(Gamma1(5),2)
    sage: M
    Modular Forms space of dimension 3 for Congruence Subgroup
    Gamma1(5) of weight 2 over Rational Field
    sage: M.hecke_matrix(2)
    [ -21    0 -240]
    [  -2    0  -23]
    [   2    1   24]

Python

    >>> from sage.all import *
    >>> M = ModularForms(Gamma1(Integer(5)),Integer(2))
    >>> M
    Modular Forms space of dimension 3 for Congruence Subgroup
    Gamma1(5) of weight 2 over Rational Field
    >>> M.hecke_matrix(Integer(2))
    [ -21    0 -240]
    [  -2    0  -23]
    [   2    1   24]

These are calculated by first calculating Hecke operators on modular symbols for \(\Gamma_1(N)\), which is a \(\mathbb{T}\)-module that is isomorphic to \(M_k(\Gamma_1(N))\) (see Modular Symbols).

Sage

    sage: ModularSymbols(Gamma1(5),2,sign=1).hecke_matrix(2)
    [ 2  1  1]
    [ 1  2 -1]
    [ 0  0 -1]

[Python]

    >>> from sage.all import *
    >>> ModularSymbols(Gamma1(Integer(5)),Integer(2),sign=Integer(1)).hecke_matrix(Integer(2))
    [ 2  1  1]
    [ 1  2 -1]
    [ 0  0 -1]