Modular symbols by numerical integration#
We describe here the method for computing modular symbols by numerical approximations of the integral of the modular form on a path between cusps.
More precisely, let \(E\) be an elliptic curve and \(f\) the newform associated to the isogeny class of \(E\). If
then the modular symbol \([r]^{+}\) is defined as the quotient of the real part of \(\lambda(\infty\to r)\) by the least positive real period of \(E\). Similarly for the negative modular symbol, it is the quotient of the imaginary part of the above by the smallest positive imaginary part of a period on the imaginary axis.
The theorem of Manin-Drinfeld shows that the modular symbols are rational numbers with small denominator. They are used for the computation of special values of the L-function of \(E\) twisted by Dirichlet characters and for the computation of \(p\)-adic L-functions.
ALGORITHM:
The implementation of modular symbols in eclib and directly in sage uses the algorithm described in Cremona’s book [Cre1997] and Stein’s book [St2007]. First the space of all modular symbols of the given level is computed, then the space corresponding to the given newform is determined. Even if these initial steps may take a while, the evaluation afterwards is instantaneous. All computations are done with rational numbers and hence are exact.
Instead the method used here (see [Wu2018] for details) is by evaluating the above integrals \(\lambda(r\to r')\) by numerical approximation. Since we know precise bounds on the denominator, we can make rigorous estimates on the error to guarantee that the result is proven to be the correct rational number.
The paths over which we integrate are split up and Atkin-Lehner operators are used to compute the badly converging part of the integrals by using the Fourier expansion at other cusps than \(\infty\).
Note
There is one assumption for the correctness of these computations: The Manin constant for the \(X_0\)-optimal curve should be \(1\) if the curve lies outside the Cremona tables. This is known for all curves in the Cremona table, but only conjectured for general curves.
EXAMPLES:
The most likely usage for the code is through the functions
modular_symbol
with implementation set to “num” and through
modular_symbol_numerical
:
sage: E = EllipticCurve("5077a1")
sage: M = E.modular_symbol(implementation="num")
sage: M(0)
0
sage: M(1/123)
4
sage: Mn = E.modular_symbol_numerical(sign=-1, prec=30)
sage: Mn(3/123) # abs tol 1e-11
3.00000000000018
>>> from sage.all import *
>>> E = EllipticCurve("5077a1")
>>> M = E.modular_symbol(implementation="num")
>>> M(Integer(0))
0
>>> M(Integer(1)/Integer(123))
4
>>> Mn = E.modular_symbol_numerical(sign=-Integer(1), prec=Integer(30))
>>> Mn(Integer(3)/Integer(123)) # abs tol 1e-11
3.00000000000018
In more details. A numerical modular symbols M
is created from an
elliptic curve with a chosen sign
(though the other sign will also be
accessible, too):
sage: E = EllipticCurve([101,103])
sage: E.conductor()
35261176
sage: M = E.modular_symbol(implementation="num", sign=-1)
sage: M
Numerical modular symbol attached to
Elliptic Curve defined by y^2 = x^3 + 101*x + 103 over Rational Field
>>> from sage.all import *
>>> E = EllipticCurve([Integer(101),Integer(103)])
>>> E.conductor()
35261176
>>> M = E.modular_symbol(implementation="num", sign=-Integer(1))
>>> M
Numerical modular symbol attached to
Elliptic Curve defined by y^2 = x^3 + 101*x + 103 over Rational Field
We can then compute the value \([13/17]^{-}\) and \([1/17]^{+}\) by calling
the function M
. The value of \([0]^{+}=0\) tells us that the rank of
this curve is positive:
sage: M(13/17)
-1/2
sage: M(1/17,sign=+1)
-3
sage: M(0, sign=+1)
0
>>> from sage.all import *
>>> M(Integer(13)/Integer(17))
-1/2
>>> M(Integer(1)/Integer(17),sign=+Integer(1))
-3
>>> M(Integer(0), sign=+Integer(1))
0
One can compute the numerical approximation to these rational numbers to any proven binary precision:
sage: M.approximative_value(13/17, prec=2) # abs tol 1e-4
-0.500003172770455
sage: M.approximative_value(13/17, prec=4) # abs tol 1e-6
-0.500000296037388
sage: M.approximative_value(0, sign=+1, prec=6) # abs tol 1e-8
0.000000000000000
>>> from sage.all import *
>>> M.approximative_value(Integer(13)/Integer(17), prec=Integer(2)) # abs tol 1e-4
-0.500003172770455
>>> M.approximative_value(Integer(13)/Integer(17), prec=Integer(4)) # abs tol 1e-6
-0.500000296037388
>>> M.approximative_value(Integer(0), sign=+Integer(1), prec=Integer(6)) # abs tol 1e-8
0.000000000000000
There are a few other things that one can do with M
. The Manin
symbol \(M(c:d)\) for a point \((c:d)\) in the projective line can be
computed.:
sage: M.manin_symbol(1,5)
-1
>>> from sage.all import *
>>> M.manin_symbol(Integer(1),Integer(5))
-1
In some cases useful, there is a function that returns all \([a/m]^{+}\) for a fixed denominator \(m\). This is rather quicker for small \(m\) than computing them individually:
sage: M.all_values_for_one_denominator(7)
{1/7: 0, 2/7: 3/2, 3/7: 3/2, 4/7: -3/2, 5/7: -3/2, 6/7: 0}
>>> from sage.all import *
>>> M.all_values_for_one_denominator(Integer(7))
{1/7: 0, 2/7: 3/2, 3/7: 3/2, 4/7: -3/2, 5/7: -3/2, 6/7: 0}
Finally a word of warning. The algorithm is fast when the cusps involved
are unitary. If the curve is semistable, all cusps are unitary. But
rational numbers with a prime \(p\) dividing the denominator once, but the
conductor more than once, are very difficult. For instance for the above
example, a seemingly harmless command like M(1/2)
would take a very
very long time to return a value. However it is possible to compute them
for smaller conductors:
sage: E = EllipticCurve("664a1")
sage: M = E.modular_symbol(implementation="num")
sage: M(1/2)
0
>>> from sage.all import *
>>> E = EllipticCurve("664a1")
>>> M = E.modular_symbol(implementation="num")
>>> M(Integer(1)/Integer(2))
0
The problem with non-unitary cusps is dealt with rather easily when one can twist to a semistable curve, like in this example:
sage: C = EllipticCurve("11a1")
sage: E = C.quadratic_twist(101)
sage: M = E.modular_symbol(implementation="num")
sage: M(1/101)
41
>>> from sage.all import *
>>> C = EllipticCurve("11a1")
>>> E = C.quadratic_twist(Integer(101))
>>> M = E.modular_symbol(implementation="num")
>>> M(Integer(1)/Integer(101))
41
AUTHORS:
Chris Wuthrich (2013-16)
- class sage.schemes.elliptic_curves.mod_sym_num.ModularSymbolNumerical[source]#
Bases:
object
This class assigning to an elliptic curve over \(\QQ\) a modular symbol. Unlike the other implementations this class does not precompute a basis for this space. Instead at each call, it evaluates the integral using numerical approximation. All bounds are very strictly implemented and the output is a correct proven rational number.
INPUT:
E
– an elliptic curve over the rational numbers.sign
– either -1 or +1 (default). This sets the default value ofsign
throughout the class. Both are still accessible.
OUTPUT: a modular symbol
EXAMPLES:
sage: E = EllipticCurve("5077a1") sage: M = E.modular_symbol(implementation="num") sage: M(0) 0 sage: M(77/57) -1 sage: M(33/37, -1) 2 sage: M = E.modular_symbol(sign=-1, implementation="num") sage: M(2/7) 2 sage: from sage.schemes.elliptic_curves.mod_sym_num \ ....: import ModularSymbolNumerical sage: M = ModularSymbolNumerical(EllipticCurve("11a1")) sage: M(1/3, -1) 1/2 sage: M(1/2) -4/5
>>> from sage.all import * >>> E = EllipticCurve("5077a1") >>> M = E.modular_symbol(implementation="num") >>> M(Integer(0)) 0 >>> M(Integer(77)/Integer(57)) -1 >>> M(Integer(33)/Integer(37), -Integer(1)) 2 >>> M = E.modular_symbol(sign=-Integer(1), implementation="num") >>> M(Integer(2)/Integer(7)) 2 >>> from sage.schemes.elliptic_curves.mod_sym_num import ModularSymbolNumerical >>> M = ModularSymbolNumerical(EllipticCurve("11a1")) >>> M(Integer(1)/Integer(3), -Integer(1)) 1/2 >>> M(Integer(1)/Integer(2)) -4/5
- all_values_for_one_denominator(m, sign=0)[source]#
Given an integer
m
and asign
, this returns the modular symbols \([a/m]\) for all \(a\) coprime to \(m\) using partial sums. This is much quicker than computing them one by one.This will only work if \(m\) is relatively small and if the cusps \(a/m\) are unitary.
INPUT:
m
– a natural numbersign
– optional either +1 or -1, or 0 (default), in which case the sign passed to the class is taken.
OUTPUT: a dictionary of fractions with denominator \(m\) giving rational numbers.
EXAMPLES:
sage: E = EllipticCurve('5077a1') sage: M = E.modular_symbol(implementation="num") sage: M.all_values_for_one_denominator(7) {1/7: 3, 2/7: 0, 3/7: -3, 4/7: -3, 5/7: 0, 6/7: 3} sage: [M(a/7) for a in [1..6]] [3, 0, -3, -3, 0, 3] sage: M.all_values_for_one_denominator(3,-1) {1/3: 4, 2/3: -4} sage: E = EllipticCurve('11a1') sage: M = E.modular_symbol(implementation="num") sage: M.all_values_for_one_denominator(12) {1/12: 1/5, 5/12: -23/10, 7/12: -23/10, 11/12: 1/5} sage: M.all_values_for_one_denominator(12, -1) {1/12: 0, 5/12: 1/2, 7/12: -1/2, 11/12: 0} sage: E = EllipticCurve('20a1') sage: M = E.modular_symbol(implementation="num") sage: M.all_values_for_one_denominator(4) {1/4: 0, 3/4: 0} sage: M.all_values_for_one_denominator(8) {1/8: 1/2, 3/8: -1/2, 5/8: -1/2, 7/8: 1/2}
>>> from sage.all import * >>> E = EllipticCurve('5077a1') >>> M = E.modular_symbol(implementation="num") >>> M.all_values_for_one_denominator(Integer(7)) {1/7: 3, 2/7: 0, 3/7: -3, 4/7: -3, 5/7: 0, 6/7: 3} >>> [M(a/Integer(7)) for a in (ellipsis_range(Integer(1),Ellipsis,Integer(6)))] [3, 0, -3, -3, 0, 3] >>> M.all_values_for_one_denominator(Integer(3),-Integer(1)) {1/3: 4, 2/3: -4} >>> E = EllipticCurve('11a1') >>> M = E.modular_symbol(implementation="num") >>> M.all_values_for_one_denominator(Integer(12)) {1/12: 1/5, 5/12: -23/10, 7/12: -23/10, 11/12: 1/5} >>> M.all_values_for_one_denominator(Integer(12), -Integer(1)) {1/12: 0, 5/12: 1/2, 7/12: -1/2, 11/12: 0} >>> E = EllipticCurve('20a1') >>> M = E.modular_symbol(implementation="num") >>> M.all_values_for_one_denominator(Integer(4)) {1/4: 0, 3/4: 0} >>> M.all_values_for_one_denominator(Integer(8)) {1/8: 1/2, 3/8: -1/2, 5/8: -1/2, 7/8: 1/2}
- approximative_value(r, sign=0, prec=20, use_twist=True)[source]#
The numerical modular symbol evaluated at rational.
It returns a real number, which should be equal to a rational number to the given binary precision
prec
. In practice the precision is often much higher. See the examples below.INPUT:
r
– a rational (or integer)sign
– optional either +1 or -1, or 0 (default), in which case the sign passed to the class is taken.prec
– an integer (default 20)use_twist
– True (default) allows to use a quadratic twist of the curve to lower the conductor.
OUTPUT: a real number
EXAMPLES:
sage: E = EllipticCurve("5077a1") sage: M = E.modular_symbol(implementation="num") sage: M.approximative_value(123/567) # abs tol 1e-11 -4.00000000000845 sage: M.approximative_value(123/567,prec=2) # abs tol 1e-9 -4.00002815242902 sage: E = EllipticCurve([11,88]) sage: E.conductor() 1715296 sage: M = E.modular_symbol(implementation="num") sage: M.approximative_value(0,prec=2) # abs tol 1e-11 -0.0000176374317982166 sage: M.approximative_value(1/7,prec=2) # abs tol 1e-11 0.999981178147778 sage: M.approximative_value(1/7,prec=10) # abs tol 1e-11 0.999999972802649
>>> from sage.all import * >>> E = EllipticCurve("5077a1") >>> M = E.modular_symbol(implementation="num") >>> M.approximative_value(Integer(123)/Integer(567)) # abs tol 1e-11 -4.00000000000845 >>> M.approximative_value(Integer(123)/Integer(567),prec=Integer(2)) # abs tol 1e-9 -4.00002815242902 >>> E = EllipticCurve([Integer(11),Integer(88)]) >>> E.conductor() 1715296 >>> M = E.modular_symbol(implementation="num") >>> M.approximative_value(Integer(0),prec=Integer(2)) # abs tol 1e-11 -0.0000176374317982166 >>> M.approximative_value(Integer(1)/Integer(7),prec=Integer(2)) # abs tol 1e-11 0.999981178147778 >>> M.approximative_value(Integer(1)/Integer(7),prec=Integer(10)) # abs tol 1e-11 0.999999972802649
- clear_cache()[source]#
Clear the cached values in all methods of this class
EXAMPLES:
sage: E = EllipticCurve("11a1") sage: M = E.modular_symbol(implementation="num") sage: M(0) 1/5 sage: M.clear_cache() sage: M(0) 1/5
>>> from sage.all import * >>> E = EllipticCurve("11a1") >>> M = E.modular_symbol(implementation="num") >>> M(Integer(0)) 1/5 >>> M.clear_cache() >>> M(Integer(0)) 1/5
- elliptic_curve()[source]#
Return the elliptic curve of this modular symbol.
EXAMPLES:
sage: E = EllipticCurve("15a4") sage: M = E.modular_symbol(implementation="num") sage: M.elliptic_curve() Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field
>>> from sage.all import * >>> E = EllipticCurve("15a4") >>> M = E.modular_symbol(implementation="num") >>> M.elliptic_curve() Elliptic Curve defined by y^2 + x*y + y = x^3 + x^2 + 35*x - 28 over Rational Field
- manin_symbol(u, v, sign=0)[source]#
Given a pair \((u,v)\) presenting a point in \(\mathbb{P}^1(\mathbb{Z}/N\mathbb{Z})\) and hence a coset of \(\Gamma_0(N)\), this computes the value of the Manin symbol \(M(u:v)\).
INPUT:
u
– an integerv
– an integer such that \((u:v)\) is a projective point modulo \(N\)sign
– optional either +1 or -1, or 0 (default), in which case the sign passed to the class is taken.
EXAMPLES:
sage: E = EllipticCurve('11a1') sage: M = E.modular_symbol(implementation="num") sage: M.manin_symbol(1,3) -1/2 sage: M.manin_symbol(1,3, sign=-1) -1/2 sage: M.manin_symbol(1,5) 1 sage: M.manin_symbol(1,5) 1 sage: E = EllipticCurve('14a1') sage: M = E.modular_symbol(implementation="num") sage: M.manin_symbol(1,2) -1/2 sage: M.manin_symbol(17,6) -1/2 sage: M.manin_symbol(-1,12) -1/2
>>> from sage.all import * >>> E = EllipticCurve('11a1') >>> M = E.modular_symbol(implementation="num") >>> M.manin_symbol(Integer(1),Integer(3)) -1/2 >>> M.manin_symbol(Integer(1),Integer(3), sign=-Integer(1)) -1/2 >>> M.manin_symbol(Integer(1),Integer(5)) 1 >>> M.manin_symbol(Integer(1),Integer(5)) 1 >>> E = EllipticCurve('14a1') >>> M = E.modular_symbol(implementation="num") >>> M.manin_symbol(Integer(1),Integer(2)) -1/2 >>> M.manin_symbol(Integer(17),Integer(6)) -1/2 >>> M.manin_symbol(-Integer(1),Integer(12)) -1/2
- transportable_symbol(r, rr, sign=0)[source]#
Return the symbol \([r']^+ - [r]^+\) where \(r'=\gamma(r)\) for some \(\gamma\in\Gamma_0(N)\). These symbols can be computed by transporting the path into the upper half plane close to one of the unitary cusps. Here we have implemented it only to move close to \(i\infty\) and \(0\).
INPUT:
r
andrr
– two rational numberssign
– optional either +1 or -1, or 0 (default), in which case the sign passed to the class is taken.
OUTPUT: a rational number
EXAMPLES:
sage: E = EllipticCurve("11a1") sage: M = E.modular_symbol(implementation="num") sage: M.transportable_symbol(0/1,-2/7) -1/2 sage: E = EllipticCurve("37a1") sage: M = E.modular_symbol(implementation="num") sage: M.transportable_symbol(0/1,-1/19) 0 sage: M.transportable_symbol(0/1,-1/19,-1) 0 sage: E = EllipticCurve("5077a1") sage: M = E.modular_symbol(implementation="num") sage: M.transportable_symbol(0/1,-35/144) -3 sage: M.transportable_symbol(0/1,-35/144,-1) 0 sage: M.transportable_symbol(0/1, -7/31798) 0 sage: M.transportable_symbol(0/1, -7/31798, -1) -5
>>> from sage.all import * >>> E = EllipticCurve("11a1") >>> M = E.modular_symbol(implementation="num") >>> M.transportable_symbol(Integer(0)/Integer(1),-Integer(2)/Integer(7)) -1/2 >>> E = EllipticCurve("37a1") >>> M = E.modular_symbol(implementation="num") >>> M.transportable_symbol(Integer(0)/Integer(1),-Integer(1)/Integer(19)) 0 >>> M.transportable_symbol(Integer(0)/Integer(1),-Integer(1)/Integer(19),-Integer(1)) 0 >>> E = EllipticCurve("5077a1") >>> M = E.modular_symbol(implementation="num") >>> M.transportable_symbol(Integer(0)/Integer(1),-Integer(35)/Integer(144)) -3 >>> M.transportable_symbol(Integer(0)/Integer(1),-Integer(35)/Integer(144),-Integer(1)) 0 >>> M.transportable_symbol(Integer(0)/Integer(1), -Integer(7)/Integer(31798)) 0 >>> M.transportable_symbol(Integer(0)/Integer(1), -Integer(7)/Integer(31798), -Integer(1)) -5