Skip to content

Commit 2b52f71

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

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-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: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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: classical_modular_polynomial(3, Zmod(10))
69+
9*X^3*Y^3 + 2*X^3*Y^2 + 2*X^2*Y^3 + X^4 + 4*X^3*Y + 6*X^2*Y^2 + 4*X*Y^3 + Y^4
70+
sage: j = Mod(1728, 419)
71+
sage: classical_modular_polynomial(3, j)
72+
Y^4 + 230*Y^3 + 84*Y^2 + 118*Y + 329
73+
74+
TESTS::
75+
76+
sage: q = random_prime(50)^randrange(1,4)
77+
sage: j = GF(q).random_element()
78+
sage: l = random_prime(50)
79+
sage: Y = polygen(parent(j), 'Y')
80+
sage: classical_modular_polynomial(l,j) == classical_modular_polynomial(l)(j,Y)
81+
True
82+
"""
83+
l = ZZ(l)
84+
85+
if j is None:
86+
# We are supposed to return the generic modular polynomial. First
87+
# check if it is already in the cache, then check the database,
88+
# finally compute it using PARI.
89+
try:
90+
return _cache[l]
91+
except KeyError:
92+
pass
93+
94+
try:
95+
Phi = ZZ['X,Y'](_db[l])
96+
except ValueError:
97+
try:
98+
pari_Phi = pari.polmodular(l)
99+
except PariError:
100+
raise NotImplementedError('modular polynomial is not in database and computing it on the fly is not yet implemented')
101+
d = {(i,j): c for i,f in enumerate(pari_Phi) for j,c in enumerate(f)}
102+
Phi = ZZ['X,Y'](d)
103+
104+
if l <= classical_modular_polynomial.cache_bound:
105+
_cache[l] = Phi
106+
107+
return Phi
108+
109+
R,Y = parent(j)['Y'].objgen()
110+
111+
# If the generic polynomial is in the cache or the database, evaluating
112+
# it directly should always be faster than recomputing it from scratch.
113+
try:
114+
Phi = _cache[l]
115+
except KeyError:
116+
pass
117+
else:
118+
return Phi(j, Y)
119+
try:
120+
Phi = _db[l]
121+
except ValueError:
122+
pass
123+
else:
124+
if l <= classical_modular_polynomial.cache_bound:
125+
_cache[l] = ZZ['X,Y'](Phi)
126+
return Phi(j, Y)
127+
128+
# Now try to get the instantiated modular polynomial directly from PARI.
129+
# This should be slightly more efficient (in particular regarding memory
130+
# usage) than computing and evaluating the generic modular polynomial.
131+
try:
132+
pari_Phi = pari.polmodular(l, 0, j)
133+
except PariError:
134+
pass
135+
else:
136+
return R(pari_Phi)
137+
138+
# Nothing worked. Fall back to computing the generic modular polynomial
139+
# and simply evaluating it.
140+
return classical_modular_polynomial(l)(j, Y)
141+
142+
classical_modular_polynomial.cache_bound = 150

0 commit comments

Comments
 (0)