Skip to content

Commit

Permalink
Trac sagemath#34732: more opportunistic caching of elliptic-curve and…
Browse files Browse the repository at this point in the history
… point orders

This is a follow-up to sagemath#32786 with the following changes:

- Copy curve orders between domain and codomain for all
`EllipticCurveHom` instances of nonzero degree, rather than (previously)
just `EllipticCurveIsogeny` objects.
- Copy point orders when pushing a point through an isomorphism.
- Copy point orders when pushing a point through an isogeny of degree
coprime to the point order.
- Rearrange some computations in the Îlu code (sagemath#34303, sagemath#34614) to make
(better) use of cached orders; in particular, this unbreaks the use of
`.set_order()` on the kernel point prior to passing it to
`EllipticCurveHom_velusqrt`. [Thanks to Jonathan Komada Eriksen for
reporting this last issue.]

URL: https://trac.sagemath.org/34732
Reported by: lorenz
Ticket author(s): Lorenz Panny
Reviewer(s): Travis Scrimshaw
  • Loading branch information
Release Manager committed Nov 15, 2022
2 parents d883104 + 7962944 commit 6fda98d
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 67 deletions.
27 changes: 20 additions & 7 deletions src/sage/schemes/elliptic_curves/ell_curve_isogeny.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,12 +1018,6 @@ def __init__(self, E, kernel, codomain=None, degree=None, model=None, check=True
# Inheritance house keeping
self.__perform_inheritance_housekeeping()

# over finite fields, isogenous curves have the same number
# of rational points, hence we copy over cached curve orders
if self.__base_field.is_finite():
self._codomain._fetch_cached_order(self._domain)
self._domain._fetch_cached_order(self._codomain)

def _eval(self, P):
r"""
Less strict evaluation method for internal use.
Expand Down Expand Up @@ -1157,6 +1151,16 @@ def _call_(self, P):
Traceback (most recent call last):
...
TypeError: (20 : 90 : 1) fails to convert into the map's domain Elliptic Curve defined by y^2 = x^3 + 7*x over Number Field in th with defining polynomial x^2 + 3, but a `pushforward` method is not properly implemented
Check that copying the order over works::
sage: E = EllipticCurve(GF(431), [1,0])
sage: P, = E.gens()
sage: Q = 2^99*P; Q.order()
27
sage: phi = E.isogeny(3^99*P)
sage: phi(Q)._order
27
"""
if P.is_zero():
return self._codomain(0)
Expand Down Expand Up @@ -1187,7 +1191,16 @@ def _call_(self, P):
yP = self.__posti_ratl_maps[1](xP, yP)
xP = self.__posti_ratl_maps[0](xP)

return self._codomain(xP, yP)
Q = self._codomain(xP, yP)
if hasattr(P, '_order') and P._order.gcd(self._degree).is_one():
# TODO: For non-coprime degree, the order of the point
# gets reduced by a divisor of the degree when passing
# through the isogeny. We could run something along the
# lines of order_from_multiple() to determine the new
# order, but this probably shouldn't happen by default
# as it'll be detrimental to performance in some cases.
Q._order = P._order
return Q

def __getitem__(self, i):
r"""
Expand Down
47 changes: 0 additions & 47 deletions src/sage/schemes/elliptic_curves/ell_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,53 +1024,6 @@ def division_field(self, l, names='t', map=False, **kwds):
else:
return L

def _fetch_cached_order(self, other):
r"""
This method copies the ``_order`` member from ``other``
to ``self`` if their base field is the same and finite.
This is used in :class:`EllipticCurveIsogeny` to keep track of
an already computed curve order: According to Tate's theorem
[Tate1966b]_, isogenous elliptic curves over a finite field
have the same number of rational points.
EXAMPLES::
sage: E1 = EllipticCurve(GF(2^127-1), [1,2,3,4,5])
sage: E1.set_order(170141183460469231746191640949390434666)
sage: E2 = EllipticCurve(GF(2^127-1), [115649500210559831225094148253060920818, 36348294106991415644658737184600079491])
sage: E2._fetch_cached_order(E1)
sage: E2._order
170141183460469231746191640949390434666
TESTS::
sage: E3 = EllipticCurve(GF(17), [1,2,3,4,5])
sage: hasattr(E3, '_order')
False
sage: E3._fetch_cached_order(E1)
Traceback (most recent call last):
...
ValueError: curves have distinct base fields
::
sage: E4 = EllipticCurve([1,2,3,4,5])
sage: E4._fetch_cached_order(E1.change_ring(QQ))
sage: hasattr(E4, '_order')
False
"""
if hasattr(self, '_order') or not hasattr(other, '_order'):
return
F = self.base_field()
if F != other.base_field():
raise ValueError('curves have distinct base fields')
if not F.is_finite():
raise ValueError('base field must be finite')
n = getattr(other, '_order', None)
if n is not None:
self._order = n

def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, algorithm=None):
r"""
Return an elliptic-curve isogeny from this elliptic curve.
Expand Down
40 changes: 40 additions & 0 deletions src/sage/schemes/elliptic_curves/ell_finite_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,46 @@ def set_order(self, value, num_checks=8):

self._order = value

def _fetch_cached_order(self, other):
r"""
This method copies the ``_order`` member from ``other`` to
``self``. Both curves must have the same finite base field.
This is used in
:class:`~sage.schemes.elliptic_curves.hom.EllipticCurveHom`
to keep track of an already computed curve order: According
to Tate's theorem [Tate1966b]_, isogenous elliptic curves
over a finite field have the same number of rational points.
EXAMPLES::
sage: E1 = EllipticCurve(GF(2^127-1), [1,2,3,4,5])
sage: E1.set_order(170141183460469231746191640949390434666)
sage: E2 = EllipticCurve(GF(2^127-1), [115649500210559831225094148253060920818, 36348294106991415644658737184600079491])
sage: E2._fetch_cached_order(E1)
sage: E2._order
170141183460469231746191640949390434666
TESTS::
sage: E3 = EllipticCurve(GF(17), [1,2,3,4,5])
sage: hasattr(E3, '_order')
False
sage: E3._fetch_cached_order(E1)
Traceback (most recent call last):
...
ValueError: curves have distinct base fields
"""
if hasattr(self, '_order') or not hasattr(other, '_order'):
return
F = self.base_field()
if F != other.base_field():
raise ValueError('curves have distinct base fields')
n = getattr(other, '_order', None)
if n is not None:
self._order = n


# dict to hold precomputed coefficient vectors of supersingular j values (excluding 0, 1728):

supersingular_j_polynomials = {}
Expand Down
2 changes: 2 additions & 0 deletions src/sage/schemes/elliptic_curves/ell_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ def __call__(self, *args, **kwds):
(ell_point.EllipticCurvePoint_field,
ell_point.EllipticCurvePoint_number_field,
ell_point.EllipticCurvePoint)):
if P.curve() is self:
return P
# check if denominator of the point contains a factor of the
# characteristic of the base ring. if so, coerce the point to
# infinity.
Expand Down
33 changes: 33 additions & 0 deletions src/sage/schemes/elliptic_curves/hom.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ class EllipticCurveHom(Morphism):
"""
Base class for elliptic-curve morphisms.
"""
def __init__(self, *args, **kwds):
r"""
Constructor for elliptic-curve morphisms.
EXAMPLES::
sage: E = EllipticCurve(GF(257^2), [5,5])
sage: P = E.lift_x(1)
sage: E.isogeny(P) # indirect doctest
Isogeny of degree 127 from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2 to Elliptic Curve defined by y^2 = x^3 + 151*x + 22 over Finite Field in z2 of size 257^2
sage: E.isogeny(P, algorithm='factored') # indirect doctest
Composite morphism of degree 127 = 127:
From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2
To: Elliptic Curve defined by y^2 = x^3 + 151*x + 22 over Finite Field in z2 of size 257^2
sage: E.isogeny(P, algorithm='velusqrt') # indirect doctest
Elliptic-curve isogeny (using Îlu) of degree 127:
From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2
To: Elliptic Curve defined by y^2 = x^3 + 119*x + 231 over Finite Field in z2 of size 257^2
sage: E.montgomery_model(morphism=True) # indirect doctest
(Elliptic Curve defined by y^2 = x^3 + (199*z2+73)*x^2 + x over Finite Field in z2 of size 257^2,
Elliptic-curve morphism:
From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z2 of size 257^2
To: Elliptic Curve defined by y^2 = x^3 + (199*z2+73)*x^2 + x over Finite Field in z2 of size 257^2
Via: (u,r,s,t) = (88*z2 + 253, 208*z2 + 90, 0, 0))
"""
super().__init__(*args, **kwds)

# Over finite fields, isogenous curves have the same number of
# rational points, hence we copy over the cached curve orders.
if isinstance(self.base_ring(), finite_field_base.FiniteField) and self.degree():
self._codomain._fetch_cached_order(self._domain)
self._domain._fetch_cached_order(self._codomain)

def _repr_type(self):
"""
Return a textual representation of what kind of morphism
Expand Down
10 changes: 10 additions & 0 deletions src/sage/schemes/elliptic_curves/hom_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,16 @@ def _call_(self, P):
sage: R = E.lift_x(15/4 * (a+3))
sage: psi(R) # indirect doctest
(1033648757/303450 : 58397496786187/1083316500*a - 62088706165177/2166633000 : 1)
Check that copying the order over works::
sage: E = EllipticCurve(GF(431), [1,0])
sage: P, = E.gens()
sage: Q = 2^99*P; Q.order()
27
sage: phi = E.isogeny(3^99*P, algorithm='factored')
sage: phi(Q)._order
27
"""
return _eval_factored_isogeny(self._phis, P)

Expand Down
19 changes: 9 additions & 10 deletions src/sage/schemes/elliptic_curves/hom_velusqrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -896,24 +896,23 @@ def __init__(self, E, P, *, codomain=None, model=None, Q=None):
if codomain is not None and model is not None:
raise ValueError('cannot specify a codomain curve and model name simultaneously')

try:
self._raw_domain = E.short_weierstrass_model()
except ValueError:
raise NotImplementedError('only implemented for curves having a short Weierstrass model')
self._pre_iso = E.isomorphism_to(self._raw_domain)

try:
P = E(P)
except TypeError:
raise ValueError('given kernel point P does not lie on E')
self._P = self._pre_iso(P)

self._degree = self._P.order()
self._degree = P.order()
if self._degree % 2 != 1 or self._degree < 9:
raise NotImplementedError('only implemented for odd degrees >= 9')

try:
self._raw_domain = E.short_weierstrass_model()
except ValueError:
raise NotImplementedError('only implemented for curves having a short Weierstrass model')
self._pre_iso = E.isomorphism_to(self._raw_domain)
self._P = self._pre_iso(P)

if Q is not None:
self._Q = E(Q)
self._Q = self._pre_iso(E(Q))
EE = E
else:
self._Q = _point_outside_subgroup(self._P) # may extend base field
Expand Down
22 changes: 19 additions & 3 deletions src/sage/schemes/elliptic_curves/weierstrass_morphism.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,12 +634,28 @@ def __call__(self, P):
(-3/4 : 3/4 : 1)
sage: w(P).curve() == E.change_weierstrass_model((2,3,4,5))
True
TESTS:
Check that copying the order over works::
sage: E = EllipticCurve(GF(431^2), [1,0])
sage: i = next(a for a in E.automorphisms() if a^2 == -a^24)
sage: P,_ = E.gens()
sage: P._order
432
sage: i(P)._order
432
sage: E(i(P))._order
432
"""
if P[2] == 0:
return self._codomain(0)
return self._codomain.point(baseWI.__call__(self,
tuple(P._coords)),
check=False)
res = baseWI.__call__(self, tuple(P._coords))
Q = self._codomain.point(res, check=False)
if hasattr(P, '_order'):
Q._order = P._order
return Q

def __invert__(self):
r"""
Expand Down

0 comments on commit 6fda98d

Please sign in to comment.