Skip to content

Commit a6ebb72

Browse files
committed
establish interface for instantiated classical modular polynomials
1 parent 6ea1fe9 commit a6ebb72

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

src/doc/en/reference/arithmetic_curves/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Maps between them
2525
sage/schemes/elliptic_curves/hom_scalar
2626
sage/schemes/elliptic_curves/hom_frobenius
2727
sage/schemes/elliptic_curves/isogeny_small_degree
28+
sage/schemes/elliptic_curves/mod_poly
2829

2930

3031
Elliptic curves over number fields

src/sage/schemes/elliptic_curves/all.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@
4040

4141
from .ell_curve_isogeny import EllipticCurveIsogeny, isogeny_codomain_from_kernel
4242

43+
from .mod_poly import classical_modular_polynomial
44+
4345
from .heegner import heegner_points, heegner_point
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
r"""
2+
Modular polynomials for elliptic curves
3+
4+
For a positive integer `\ell`, the classical modular polynomial
5+
`\Phi_\ell\in\ZZ[X,Y]` is characterized by the property that its
6+
zero set is exactly the set of pairs of `j`-invariants connected
7+
by a cyclic `\ell`-isogeny.
8+
9+
AUTHORS:
10+
11+
- Lorenz Panny (2023)
12+
"""
13+
14+
from sage.misc.cachefunc import cached_function
15+
from sage.structure.parent import Parent
16+
from sage.structure.element import parent, FieldElement
17+
18+
from sage.rings.integer_ring import ZZ
19+
from sage.rings.polynomial.polynomial_ring import polygen, polygens
20+
21+
from sage.libs.pari import pari
22+
from cypari2.handle_error import PariError
23+
24+
from sage.databases.db_modular_polynomials import ClassicalModularPolynomialDatabase
25+
_db = ClassicalModularPolynomialDatabase()
26+
27+
_cache = dict()
28+
29+
def classical_modular_polynomial(l, j=None):
30+
r"""
31+
Return the classical modular polynomial `\Phi_\ell`, either as a
32+
"generic" bivariate polynomial over `\ZZ`, or as an "instantiated"
33+
modular polynomial where one variable has been replaced by the
34+
given `j`-invariant.
35+
36+
Generic polynomials are cached up to a certain size of `\ell`,
37+
which significantly accelerates subsequent invocations with the
38+
same `\ell`. The default bound is `\ell \leq 150`, which can be
39+
adjusted by setting ``classical_modular_polynomial.cache_bound``
40+
to a different value. Beware that modular polynomials are very
41+
large and the amount of memory consumed by the cache will grow
42+
rapidly when the bound is set to a large value.
43+
44+
INPUT:
45+
46+
- ``l`` -- positive integer.
47+
48+
- ``j`` -- either ``None`` or a ring element.
49+
50+
- If ``None`` is given, the original modular polynomial
51+
is returned as an element of `\ZZ[X,Y]`.
52+
53+
- If a ring element `j \in R` is given, the evaluation
54+
`\Phi_\ell(j,Y)` is returned as an element of the
55+
univariate polynomial ring `R[Y]`.
56+
57+
ALGORITHMS:
58+
59+
- The Kohel database
60+
:class:`~sage.databases.db_modular_polynomials.ClassicalModularPolynomialDatabase`
61+
62+
- :pari:`polmodular`
63+
64+
EXAMPLES::
65+
66+
sage: classical_modular_polynomial(2)
67+
-X^2*Y^2 + X^3 + 1488*X^2*Y + 1488*X*Y^2 + Y^3 - 162000*X^2 + 40773375*X*Y - 162000*Y^2 + 8748000000*X + 8748000000*Y - 157464000000000
68+
sage: j = Mod(1728, 419)
69+
sage: classical_modular_polynomial(3, j)
70+
Y^4 + 230*Y^3 + 84*Y^2 + 118*Y + 329
71+
72+
TESTS::
73+
74+
sage: q = random_prime(50)^randrange(1,4)
75+
sage: j = GF(q).random_element()
76+
sage: l = random_prime(50)
77+
sage: Y = polygen(parent(j), 'Y')
78+
sage: classical_modular_polynomial(l,j) == classical_modular_polynomial(l)(j,Y)
79+
True
80+
"""
81+
l = ZZ(l)
82+
83+
if j is None:
84+
# We are supposed to return the generic modular polynomial. First
85+
# check if it is already in the cache, then check the database,
86+
# finally compute it using PARI.
87+
try:
88+
return _cache[l]
89+
except KeyError:
90+
pass
91+
92+
try:
93+
Phi = ZZ['X,Y'](_db[l])
94+
except ValueError:
95+
try:
96+
pari_Phi = pari.polmodular(l)
97+
except PariError:
98+
raise NotImplementedError('modular polynomial is not in database and computing it on the fly is not yet implemented')
99+
d = {(i,j): c for i,f in enumerate(pari_Phi) for j,c in enumerate(f)}
100+
Phi = ZZ['X,Y'](d)
101+
102+
if l <= classical_modular_polynomial.cache_bound:
103+
_cache[l] = Phi
104+
105+
return Phi
106+
107+
R,Y = parent(j)['Y'].objgen()
108+
109+
# If the generic polynomial is in the cache or the database, evaluating
110+
# it directly should always be faster than recomputing it from scratch.
111+
try:
112+
Phi = _cache[l]
113+
except KeyError:
114+
pass
115+
else:
116+
return Phi(j, Y)
117+
try:
118+
Phi = _db[l]
119+
except ValueError:
120+
pass
121+
else:
122+
if l <= classical_modular_polynomial.cache_bound:
123+
_cache[l] = ZZ['X,Y'](Phi)
124+
return Phi(j, Y)
125+
126+
# Now try to get the instantiated modular polynomial directly from PARI.
127+
# This should be slightly more efficient (in particular regarding memory
128+
# usage) than computing and evaluating the generic modular polynomial.
129+
try:
130+
pari_Phi = pari.polmodular(l, 0, j)
131+
except PariError:
132+
pass
133+
else:
134+
return R(pari_Phi)
135+
136+
# Nothing worked. Fall back to computing the generic modular polynomial
137+
# and simply evaluating it.
138+
return classical_modular_polynomial(l)(j, Y)
139+
140+
classical_modular_polynomial.cache_bound = 150

0 commit comments

Comments
 (0)