Skip to content

Commit e9e1e9e

Browse files
committed
more algorithms for .torsion_basis() over {finite, number, ""} fields
1 parent 28a7d04 commit e9e1e9e

File tree

3 files changed

+255
-14
lines changed

3 files changed

+255
-14
lines changed

src/sage/schemes/elliptic_curves/ell_field.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,123 @@ def division_field(self, n, names='t', map=False, **kwds):
11371137
L = L, F_to_K.post_compose(K_to_L)
11381138
return L
11391139

1140+
def torsion_basis(self, n, *, algorithm=None):
1141+
r"""
1142+
Return a basis of the `n`-torsion subgroup of this elliptic curve.
1143+
1144+
INPUT:
1145+
1146+
- ``n`` -- integer
1147+
1148+
- ``algorithm`` -- string (default: ``None``).
1149+
Over general fields, only ``"divpoly"`` is available,
1150+
and over number fields, additionally ``"structure"``.
1151+
If ``algorithm`` is ``None``, the method attempts to
1152+
select the most suitable algorithm automatically.
1153+
1154+
EXAMPLES::
1155+
1156+
sage: E = EllipticCurve('15a1')
1157+
sage: E.torsion_basis(2)
1158+
((-13/4 : 9/8 : 1), (-1 : 0 : 1))
1159+
sage: E.torsion_basis(4)
1160+
Traceback (most recent call last):
1161+
...
1162+
ValueError: curve does not have full rational 2^2-torsion
1163+
1164+
::
1165+
1166+
sage: E = EllipticCurve('11a2')
1167+
sage: E.torsion_subgroup()
1168+
Torsion Subgroup isomorphic to Trivial group
1169+
associated to the Elliptic Curve defined by y^2 + y = x^3 - x^2 - 7820*x - 263580
1170+
over Rational Field
1171+
sage: EE = E.change_ring(E.division_field(5))
1172+
sage: EE.torsion_subgroup()
1173+
Torsion Subgroup isomorphic to Z/5 + Z/5
1174+
associated to the Elliptic Curve defined by y^2 + y = x^3 + (-1)*x^2 + (-7820)*x + (-263580)
1175+
over Number Field in t with defining polynomial x^20 - 5*x^19 + 15*x^18 - 35*x^17 + 70*x^16 - 77*x^15 + 20*x^14 - 35*x^13 + 815*x^12 - 4380*x^11 + 9489*x^10 - 11860*x^9 + 4555*x^8 + 13055*x^7 + 12890*x^6 - 30338*x^5 + 11785*x^4 - 4380*x^3 - 13680*x^2 - 8640*x + 20736
1176+
sage: EE.torsion_basis(5, algorithm='divpoly')
1177+
((595183/1928000*t^19 - 35292739/17352000*t^18 + 131419817/17352000*t^17 - 40329101/1928000*t^16 + 413372581/8676000*t^15 - 1379566363/17352000*t^14 + 376817699/4338000*t^13 - 1250892533/17352000*t^12 + 4879989161/17352000*t^11 - 7497101897/4338000*t^10 + 10429892351/1928000*t^9 - 15364091329/1446000*t^8 + 227824771789/17352000*t^7 - 114698642023/17352000*t^6 + 14739878027/8676000*t^5 - 46311365527/8676000*t^4 + 181688916383/17352000*t^3 - 55952844001/4338000*t^2 + 2240720131/361500*t - 199444/30125
1178+
: 67907087/13014000*t^19 - 224388017/6507000*t^18 + 2323846/18075*t^17 - 2312207969/6507000*t^16 + 10537958483/13014000*t^15 - 17608556113/13014000*t^14 + 19262352991/13014000*t^13 - 3191379809/2602800*t^12 + 30969621131/6507000*t^11 - 42357264953/1446000*t^10 + 398711684093/4338000*t^9 - 2352287312003/13014000*t^8 + 582276824761/2602800*t^7 - 367880125799/3253500*t^6 + 363704361121/13014000*t^5 - 73759615534/813375*t^4 + 2318110141133/13014000*t^3 - 7627182061/34704*t^2 + 3195593808/30125*t + 48027902/30125
1179+
: 1),
1180+
(104/150625*t^19 - 312/150625*t^18 + 728/150625*t^17 - 1456/150625*t^16 + 5723/301250*t^15 - 416/150625*t^14 + 728/150625*t^13 - 16952/150625*t^12 + 91104/150625*t^11 - 617317/301250*t^10 + 246688/150625*t^9 - 94744/150625*t^8 - 271544/150625*t^7 - 268112/150625*t^6 + 12021881/301250*t^5 - 245128/150625*t^4 + 91104/150625*t^3 + 284544/150625*t^2 + 179712/150625*t - 13340968/150625
1181+
: 3531/301250*t^19 - 10593/301250*t^18 + 24717/301250*t^17 - 24717/150625*t^16 + 48692/150625*t^15 - 7062/150625*t^14 + 24717/301250*t^13 - 575553/301250*t^12 + 1546578/150625*t^11 - 5246148/150625*t^10 + 4187766/150625*t^9 - 3216741/301250*t^8 - 9219441/301250*t^7 - 4551459/150625*t^6 + 102245379/150625*t^5 - 8322567/301250*t^4 + 1546578/150625*t^3 + 4830408/150625*t^2 + 3050784/150625*t - 95627989/150625
1182+
: 1))
1183+
sage: EE.torsion_basis(5, algorithm='structure')
1184+
((595183/1928000*t^19 - 35292739/17352000*t^18 + 131419817/17352000*t^17 - 40329101/1928000*t^16 + 413372581/8676000*t^15 - 1379566363/17352000*t^14 + 376817699/4338000*t^13 - 1250892533/17352000*t^12 + 4879989161/17352000*t^11 - 7497101897/4338000*t^10 + 10429892351/1928000*t^9 - 15364091329/1446000*t^8 + 227824771789/17352000*t^7 - 114698642023/17352000*t^6 + 14739878027/8676000*t^5 - 46311365527/8676000*t^4 + 181688916383/17352000*t^3 - 55952844001/4338000*t^2 + 2240720131/361500*t - 199444/30125
1185+
: 67907087/13014000*t^19 - 224388017/6507000*t^18 + 2323846/18075*t^17 - 2312207969/6507000*t^16 + 10537958483/13014000*t^15 - 17608556113/13014000*t^14 + 19262352991/13014000*t^13 - 3191379809/2602800*t^12 + 30969621131/6507000*t^11 - 42357264953/1446000*t^10 + 398711684093/4338000*t^9 - 2352287312003/13014000*t^8 + 582276824761/2602800*t^7 - 367880125799/3253500*t^6 + 363704361121/13014000*t^5 - 73759615534/813375*t^4 + 2318110141133/13014000*t^3 - 7627182061/34704*t^2 + 3195593808/30125*t + 48027902/30125
1186+
: 1),
1187+
(104/150625*t^19 - 312/150625*t^18 + 728/150625*t^17 - 1456/150625*t^16 + 5723/301250*t^15 - 416/150625*t^14 + 728/150625*t^13 - 16952/150625*t^12 + 91104/150625*t^11 - 617317/301250*t^10 + 246688/150625*t^9 - 94744/150625*t^8 - 271544/150625*t^7 - 268112/150625*t^6 + 12021881/301250*t^5 - 245128/150625*t^4 + 91104/150625*t^3 + 284544/150625*t^2 + 179712/150625*t - 13340968/150625
1188+
: 3531/301250*t^19 - 10593/301250*t^18 + 24717/301250*t^17 - 24717/150625*t^16 + 48692/150625*t^15 - 7062/150625*t^14 + 24717/301250*t^13 - 575553/301250*t^12 + 1546578/150625*t^11 - 5246148/150625*t^10 + 4187766/150625*t^9 - 3216741/301250*t^8 - 9219441/301250*t^7 - 4551459/150625*t^6 + 102245379/150625*t^5 - 8322567/301250*t^4 + 1546578/150625*t^3 + 4830408/150625*t^2 + 3050784/150625*t - 95627989/150625
1189+
: 1))
1190+
1191+
.. SEEALSO::
1192+
1193+
Use :meth:`~sage.schemes.elliptic_curves.ell_field.EllipticCurve_field.division_field`
1194+
to determine a field extension containing the full `n`-torsion subgroup.
1195+
1196+
ALGORITHM:
1197+
1198+
If ``algorithm`` is ``divpoly``, this method uses division
1199+
polynomials to construct a basis of the `n`-torsion. The
1200+
complexity of this approach scales with the size of the prime
1201+
factors of `n`.
1202+
1203+
If ``algorithm`` is ``"structure"``, this method calls
1204+
:meth:`torsion_subgroup` and
1205+
:meth:`AdditiveAbelianGroupWrapper.torsion_subgroup`.
1206+
"""
1207+
if algorithm is None:
1208+
if hasattr(self, 'torsion_subgroup') and self.torsion_subgroup.is_in_cache():
1209+
algorithm = 'structure'
1210+
else:
1211+
algorithm = 'divpoly'
1212+
1213+
if algorithm == 'structure':
1214+
1215+
T = self.torsion_subgroup().torsion_subgroup(n)
1216+
if T.invariants() != (n, n):
1217+
raise ValueError(f'curve does not have full rational {n}-torsion')
1218+
return tuple(P.element() for P in T.gens())
1219+
1220+
elif algorithm == 'divpoly':
1221+
1222+
from sage.groups.generic import has_order
1223+
1224+
n = ZZ(n)
1225+
if n < 2:
1226+
raise ValueError('computing an n-torsion basis only makes sense for n >= 2')
1227+
1228+
P = Q = self.zero()
1229+
1230+
for l,m in n.factor():
1231+
pts = filter(bool, self.zero().division_points(l))
1232+
Pl = next(pts)
1233+
for Ql in pts:
1234+
if not Pl.weil_pairing(Ql, l).is_one():
1235+
break
1236+
else:
1237+
raise ValueError(f'curve does not have full rational {l}-torsion')
1238+
1239+
for i in range(1, m):
1240+
try:
1241+
Pl = Pl.division_points(l)[0]
1242+
Ql = Ql.division_points(l)[0]
1243+
except (StopIteration, IndexError):
1244+
raise ValueError(f'curve does not have full rational {l}^{i+1}-torsion')
1245+
# assert has_order(Pl.weil_pairing(Ql, l**(i+1)), l**(i+1), operation='*')
1246+
1247+
P += Pl
1248+
Q += Ql
1249+
1250+
# assert has_order(P.weil_pairing(Q, n), n, operation='*')
1251+
P._order = Q._order = n
1252+
return P, Q
1253+
1254+
else:
1255+
raise ValueError(f'unknown algorithm {algorithm!r}')
1256+
11401257
def _Hom_(self, other, category=None):
11411258
r"""
11421259
Hook to make :class:`~sage.categories.homset.Hom`

src/sage/schemes/elliptic_curves/ell_finite_field.py

Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -991,10 +991,18 @@ def abelian_group(self):
991991
self.gens.set_cache(gens)
992992
return AdditiveAbelianGroupWrapper(self.point_homset(), gens, orders)
993993

994-
def torsion_basis(self, n):
994+
def torsion_basis(self, n, *, algorithm=None):
995995
r"""
996-
Return a basis of the `n`-torsion subgroup of this elliptic curve,
997-
assuming it is fully rational.
996+
Return a basis of the `n`-torsion subgroup of this elliptic curve.
997+
998+
INPUT:
999+
1000+
- ``n`` -- integer
1001+
1002+
- ``algorithm`` -- string (default: ``None``).
1003+
Currently available choices are ``"random"``, ``"structure"``,
1004+
and ``"divpoly"``. If ``algorithm`` is ``None``, the method
1005+
attempts to select the most suitable algorithm automatically.
9981006
9991007
EXAMPLES::
10001008
@@ -1017,7 +1025,7 @@ def torsion_basis(self, n):
10171025
sage: E.torsion_basis(23)
10181026
Traceback (most recent call last):
10191027
...
1020-
ValueError: curve does not have full rational 23-torsion
1028+
ValueError: curve does not have full rational 23-torsion, or very unlikely event
10211029
sage: F = E.division_field(23); F
10221030
Finite Field in t of size 101^11
10231031
sage: EE = E.change_ring(F)
@@ -1035,25 +1043,140 @@ def torsion_basis(self, n):
10351043
+ 55*z11^5 + 23*z11^4 + 17*z11^3 + 90*z11^2 + 91*z11 + 68
10361044
: 1)
10371045
1046+
TESTS:
1047+
1048+
Check on random curves that all three algorithms return
1049+
equivalent results::
1050+
1051+
sage: while True:
1052+
....: p = random_prime(100)
1053+
....: e = randrange(1,4)
1054+
....: E = choice(EllipticCurve(j=GF((p,e)).random_element()).twists())
1055+
....: ds = E.abelian_group().invariants()
1056+
....: if len(ds) < 2:
1057+
....: continue
1058+
....: ns = set(gcd(ds).divisors()) - {1}
1059+
....: if ns: break
1060+
sage: n = choice(list(ns))
1061+
sage: assert n >= 2
1062+
sage: P1, Q1 = E.torsion_basis(n, algorithm='random')
1063+
sage: A1 = AdditiveAbelianGroupWrapper.from_generators([P1, Q1])
1064+
sage: P2, Q2 = E.torsion_basis(n, algorithm='structure')
1065+
sage: A2 = AdditiveAbelianGroupWrapper.from_generators([P2, Q2])
1066+
sage: P3, Q3 = E.torsion_basis(n, algorithm='divpoly')
1067+
sage: A3 = AdditiveAbelianGroupWrapper.from_generators([P3, Q3])
1068+
sage: assert A1 == A2
1069+
sage: assert A1 == A3
1070+
sage: assert A2 == A3
1071+
10381072
.. SEEALSO::
10391073
10401074
Use :meth:`~sage.schemes.elliptic_curves.ell_field.EllipticCurve_field.division_field`
1041-
to determine a field extension containing the full `\ell`-torsion subgroup.
1075+
to determine a field extension containing the full `n`-torsion subgroup.
10421076
10431077
ALGORITHM:
10441078
1045-
This method currently uses :meth:`abelian_group` and
1079+
If ``algorithm`` is ``"random"``, this method repeatedly
1080+
samples random points on the curve and distills a basis of the
1081+
`n`-torsion from them. This requires point counting.
1082+
1083+
If ``algorithm`` is ``divpoly``, this method uses division
1084+
polynomials to construct a basis of the `n`-torsion. The
1085+
complexity of this approach scales with the size of the prime
1086+
factors of `n`. This algorithm is usually much slower than
1087+
the others, but there might be situations in which it is
1088+
useful.
1089+
1090+
If ``algorithm`` is ``"structure"``, this method calls
1091+
:meth:`abelian_group` and
10461092
:meth:`AdditiveAbelianGroupWrapper.torsion_subgroup`.
1093+
Theoretically, this involves performing a superset of the work
1094+
of the ``"random"`` method, so it should never be the best
1095+
choice, but due to implementation details (PARI vs. Sage) this
1096+
approach can be faster than the ``"random"`` algorithm in
1097+
practice.
10471098
"""
1048-
# TODO: In many cases this is not the fastest algorithm.
1049-
# Alternatives include factoring division polynomials and
1050-
# random sampling (like PARI's ellgroup, but with a milder
1051-
# termination condition). We should implement these too
1052-
# and figure out when to use which.
1053-
T = self.abelian_group().torsion_subgroup(n)
1054-
if T.invariants() != (n, n):
1099+
n = ZZ(n)
1100+
if n < 2:
1101+
raise ValueError('computing an n-torsion basis only makes sense for n >= 2')
1102+
1103+
if self.base_field().characteristic().divides(n):
10551104
raise ValueError(f'curve does not have full rational {n}-torsion')
1056-
return tuple(P.element() for P in T.gens())
1105+
1106+
if algorithm is None:
1107+
if self.abelian_group.is_in_cache():
1108+
algorithm = 'structure'
1109+
else:
1110+
algorithm = 'random'
1111+
1112+
if algorithm == 'random':
1113+
# similar to AdditiveAbelianGroupWrapper.from_generators()
1114+
from sage.arith.misc import xlcm
1115+
1116+
N = self.cardinality()
1117+
P = Q = self.zero()
1118+
P._order = ZZ(1)
1119+
1120+
for step in range(999):
1121+
# check P,Q is a basis of the subgroup <P,Q> with ord(Q) | ord(P)
1122+
assert Q._order.divides(P._order)
1123+
# assert generic.has_order(P.weil_pairing(Q, P._order), Q._order, operation='*')
1124+
1125+
if Q._order == n:
1126+
P *= P._order // n
1127+
assert hasattr(P, '_order')
1128+
break
1129+
1130+
cof = N.prime_to_m_part(n // Q._order)
1131+
T = cof * self.random_point()
1132+
T.set_order(multiple=N//cof, check=False)
1133+
1134+
# extend P using T as much as possible
1135+
m, k1, k2 = xlcm(P._order, T._order)
1136+
m1 = P._order // k1
1137+
m2 = T._order // k2
1138+
P = m1 * P + m2 * T
1139+
P._order = m
1140+
1141+
if not step:
1142+
continue
1143+
1144+
# remove the P component from T
1145+
l = generic.order_from_multiple(P.weil_pairing(T, P._order), P._order, operation='*')
1146+
x = (l * T).log(l * P)
1147+
T -= x * P
1148+
T.set_order(multiple=P._order, check=False)
1149+
1150+
# for Q we only need the n-torsion part of T
1151+
T *= T._order // n.gcd(T._order)
1152+
1153+
# extend Q as much as possible
1154+
Q, m = generic.merge_points((Q, Q._order), (T, T._order))
1155+
Q._order = m
1156+
1157+
# remove the P component from Q
1158+
l = generic.order_from_multiple(P.weil_pairing(Q, P._order), P._order, operation='*')
1159+
y = (l * Q).log(l * P)
1160+
Q -= y * P
1161+
Q.set_order(multiple=P._order, check=False)
1162+
1163+
else:
1164+
raise ValueError(f'curve does not have full rational {n}-torsion, or very unlikely event')
1165+
1166+
# assert generic.has_order(P.weil_pairing(Q, n), n, operation='*')
1167+
1168+
return P, Q
1169+
1170+
elif algorithm == 'divpoly':
1171+
return super().torsion_basis(n, algorithm=algorithm)
1172+
1173+
elif algorithm == 'structure':
1174+
T = self.abelian_group().torsion_subgroup(n)
1175+
if T.invariants() != (n, n):
1176+
raise ValueError(f'curve does not have full rational {n}-torsion')
1177+
return tuple(P.element() for P in T.gens())
1178+
1179+
raise ValueError(f'unknown algorithm {algorithm!r}')
10571180

10581181
def is_isogenous(self, other, field=None, proof=True):
10591182
"""

src/sage/schemes/elliptic_curves/ell_rational_field.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4076,6 +4076,7 @@ def _torsion_bound(self, number_of_places=20):
40764076
k += 1
40774077
return bound
40784078

4079+
@cached_method
40794080
def torsion_subgroup(self):
40804081
r"""
40814082
Return the torsion subgroup of this elliptic curve.

0 commit comments

Comments
 (0)