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
then the modular symbol
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
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
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
Note
There is one assumption for the correctness of these computations: The
Manin constant for the
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
E = EllipticCurve("5077a1") M = E.modular_symbol(implementation='num') M(0) M(1/123) Mn = E.modular_symbol_numerical(sign=-1, prec=30) Mn(3/123) # abs tol 1e-11
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
E = EllipticCurve([101,103]) E.conductor() M = E.modular_symbol(implementation='num', sign=-1) M
We can then compute the value M
. The value of
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
M(13/17) M(1/17,sign=+1) M(0, sign=+1)
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
M.approximative_value(13/17, prec=2) # abs tol 1e-4 M.approximative_value(13/17, prec=4) # abs tol 1e-6 M.approximative_value(0, sign=+1, prec=6) # abs tol 1e-8
There are a few other things that one can do with M
. The Manin
symbol
sage: M.manin_symbol(1,5)
-1
>>> from sage.all import *
>>> M.manin_symbol(Integer(1),Integer(5))
-1
M.manin_symbol(1,5)
In some cases useful, there is a function that returns all
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}
M.all_values_for_one_denominator(7)
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 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
E = EllipticCurve("664a1") M = E.modular_symbol(implementation='num') M(1/2)
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
C = EllipticCurve("11a1") E = C.quadratic_twist(101) M = E.modular_symbol(implementation='num') M(1/101)
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
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 numberssign
– 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
E = EllipticCurve("5077a1") M = E.modular_symbol(implementation='num') M(0) M(77/57) M(33/37, -1) M = E.modular_symbol(sign=-1, implementation='num') M(2/7) from sage.schemes.elliptic_curves.mod_sym_num \ import ModularSymbolNumerical M = ModularSymbolNumerical(EllipticCurve("11a1")) M(1/3, -1) M(1/2)
- all_values_for_one_denominator(m, sign=0)[source]¶
Given an integer
m
and asign
, this returns the modular symbols for all coprime to using partial sums. This is much quicker than computing them one by one.This will only work if
is relatively small and if the cusps are unitary.INPUT:
m
– a natural numbersign
– 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
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}
E = EllipticCurve('5077a1') M = E.modular_symbol(implementation='num') M.all_values_for_one_denominator(7) [M(a/7) for a in [1..6]] M.all_values_for_one_denominator(3,-1) E = EllipticCurve('11a1') M = E.modular_symbol(implementation='num') M.all_values_for_one_denominator(12) M.all_values_for_one_denominator(12, -1) E = EllipticCurve('20a1') M = E.modular_symbol(implementation='num') M.all_values_for_one_denominator(4) M.all_values_for_one_denominator(8)
- 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
– either +1 or -1, or 0 (default), in which case the sign passed to the class is takenprec
– 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
E = EllipticCurve("5077a1") M = E.modular_symbol(implementation='num') M.approximative_value(123/567) # abs tol 1e-11 M.approximative_value(123/567,prec=2) # abs tol 1e-9 E = EllipticCurve([11,88]) E.conductor() M = E.modular_symbol(implementation='num') M.approximative_value(0,prec=2) # abs tol 1e-11 M.approximative_value(1/7,prec=2) # abs tol 1e-11 M.approximative_value(1/7,prec=10) # abs tol 1e-11
- 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
E = EllipticCurve("11a1") M = E.modular_symbol(implementation='num') M(0) M.clear_cache() M(0)
- 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
E = EllipticCurve("15a4") M = E.modular_symbol(implementation='num') M.elliptic_curve()
- manin_symbol(u, v, sign=0)[source]¶
Given a pair
presenting a point in and hence a coset of , this computes the value of the Manin symbol .INPUT:
u
– integerv
– integer such that is a projective point modulosign
– 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
E = EllipticCurve('11a1') M = E.modular_symbol(implementation='num') M.manin_symbol(1,3) M.manin_symbol(1,3, sign=-1) M.manin_symbol(1,5) M.manin_symbol(1,5) E = EllipticCurve('14a1') M = E.modular_symbol(implementation='num') M.manin_symbol(1,2) M.manin_symbol(17,6) M.manin_symbol(-1,12)
- transportable_symbol(r, rr, sign=0)[source]¶
Return the symbol
where for some . 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 and .INPUT:
r
,rr
– two rational numberssign
– 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
E = EllipticCurve("11a1") M = E.modular_symbol(implementation='num') M.transportable_symbol(0/1,-2/7) E = EllipticCurve("37a1") M = E.modular_symbol(implementation='num') M.transportable_symbol(0/1,-1/19) M.transportable_symbol(0/1,-1/19,-1) E = EllipticCurve("5077a1") M = E.modular_symbol(implementation='num') M.transportable_symbol(0/1,-35/144) M.transportable_symbol(0/1,-35/144,-1) M.transportable_symbol(0/1, -7/31798) M.transportable_symbol(0/1, -7/31798, -1)