diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index e0f07c0191d..4567a0f0923 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -867,9 +867,9 @@ class EllipticCurveIsogeny(EllipticCurveHom): sage: E = EllipticCurve(j=GF(7)(0)) sage: phi = E.isogeny([E(0), E((0,1)), E((0,-1))]); phi - Isogeny of degree 3 - from Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 - to Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 + Composite morphism of degree 3: + From: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 + To: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 sage: phi2 = phi * phi; phi2 Composite morphism of degree 9 = 3^2: From: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 0c944f0f938..6bd9960ce58 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -1157,17 +1157,28 @@ def _Hom_(self, other, category=None): from sage.schemes.generic.homset import SchemeHomset_generic return SchemeHomset_generic(self, other, category=category) - def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, algorithm=None): + def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, algorithm=None, velu_sqrt_bound=None): r""" Return an elliptic-curve isogeny from this elliptic curve. The isogeny can be specified in two ways, by passing either a polynomial or a set of torsion points. The methods used are: + - Factored Isogenies (see + :mod:`~sage.schemes.elliptic_curves.hom_composite`): + Given a point, or a list of points which generate a + composite-order subgroup, decomposes the isogeny into + prime-degree steps. This can be used to construct isogenies + of extremely large, smooth degree. When applicable, this + algorithm is selected as default (see below). After factoring + the degree single isogenies are computed using the other + methods. + This algorithm is selected using ``algorithm="factored"``. + - Vélu's Formulas: Vélu's original formulas for computing isogenies. This algorithm is selected by giving as the - ``kernel`` parameter a single point, or a list of points, - generating a finite subgroup. + ``kernel`` parameter a single point generating a finite + subgroup. - Kohel's Formulas: Kohel's original formulas for computing isogenies. This algorithm is selected by giving as the @@ -1184,14 +1195,6 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al kernel point of odd order `\geq 5`. This algorithm is selected using ``algorithm="velusqrt"``. - - Factored Isogenies (see - :mod:`~sage.schemes.elliptic_curves.hom_composite`): - Given a list of points which generate a composite-order - subgroup, decomposes the isogeny into prime-degree steps. - This can be used to construct isogenies of extremely large, - smooth degree. - This algorithm is selected using ``algorithm="factored"``. - INPUT: - ``kernel`` -- a kernel: either a point on this curve, a list of @@ -1234,10 +1237,7 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al - ``check`` (default: ``True``) -- check whether the input is valid. Setting this to ``False`` can lead to significant speedups. - - ``algorithm`` -- string (optional). By default (when ``algorithm`` - is omitted), the "traditional" implementation - :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny` - is used. The other choices are: + - ``algorithm`` -- string (optional). The possible choices are: - ``"velusqrt"``: Use :class:`~sage.schemes.elliptic_curves.hom_velusqrt.EllipticCurveHom_velusqrt`. @@ -1246,8 +1246,36 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite` to decompose the isogeny into prime-degree steps. - The ``degree`` parameter is not supported when an ``algorithm`` - is specified. + - ``"traditional"``: Use + :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny`. + + When ``algorithm`` is not specified, and ``kernel`` is not ``None``, an + algorithm is selected using the following criteria: + + - if ``kernel`` is a list of multiple points, ``"factored"`` is selected. + + - If ``kernel`` is a single point, or a list containing a single point: + + - if the order of the point is unknown, ``"traditional"`` is selected. + + - If the order is known and composite, ``"factored"`` is selected. + + - If the order is known and prime, a choice between ``"velusqrt"`` and + ``"traditional"`` is done according to the ``velu_sqrt_bound`` + parameter (see below). + + If none of the previous apply, ``"traditional"`` is selected. + + - ``velu_sqrt_bound`` -- an integer (default: ``None``). Establish the highest + (prime) degree for which the ``"traditional"`` algorithm should be selected + instead of ``"velusqrt"``. If ``None``, the default value from + :class:`~sage.schemes.elliptic_curves.hom_velusqrt._VeluBoundObj` is used. + This value is initially set to 1000, but can be modified by the user. + If an integer is supplied and the isogeny computation goes through the + ``"factored"`` algorithm, the same integer is supplied to each factor. + + The ``degree`` parameter is not supported when an ``algorithm`` is + specified. OUTPUT: @@ -1283,9 +1311,9 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al sage: (P.order(), Q.order()) (7, 3) sage: phi = E.isogeny([P,Q]); phi - Isogeny of degree 21 - from Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 19 - to Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 19 + Composite morphism of degree 21 = 7*3: + From: Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 19 + To: Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 19 sage: phi(E.random_point()) # all points defined over GF(19) are in the kernel (0 : 1 : 0) @@ -1310,6 +1338,62 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al subgroup of Elliptic Curve defined by y^2 + x*y = x^3 + x + 2 over Finite Field of size 31 + Order of the point known and composite:: + + sage: E = EllipticCurve(GF(31), [1,0,0,1,2]) + sage: P = E(26, 4) + sage: assert P.order() == 12 + sage: print(P._order) + 12 + sage: E.isogeny(P) + Composite morphism of degree 12 = 2^2*3: + From: Elliptic Curve defined by y^2 + x*y = x^3 + x + 2 over Finite Field of size 31 + To: Elliptic Curve defined by y^2 + x*y = x^3 + 26*x + 8 over Finite Field of size 31 + + ``kernel`` is a list of points:: + + sage: E = EllipticCurve(GF(31), [1,0,0,1,2]) + sage: P = E(21,2) + sage: Q = E(7, 12) + sage: print(P.order()) + 6 + sage: print(Q.order()) + 2 + sage: E.isogeny([P, Q]) + Composite morphism of degree 12 = 2*3*2: + From: Elliptic Curve defined by y^2 + x*y = x^3 + x + 2 over Finite Field of size 31 + To: Elliptic Curve defined by y^2 + x*y = x^3 + 2*x + 26 over Finite Field of size 31 + + Multiple ways to set the `velu_sqrt_bound`:: + + sage: E = EllipticCurve_from_j(GF(97)(42)) + sage: P = E.gens()[0]*4 + sage: print(P.order()) + 23 + sage: E.isogeny(P) + Isogeny of degree 23 from Elliptic Curve defined by y^2 = x^3 + 6*x + 46 over Finite Field of size 97 to Elliptic Curve defined by y^2 = x^3 + 72*x + 29 over Finite Field of size 97 + sage: E.isogeny(P, velu_sqrt_bound=10) + Elliptic-curve isogeny (using square-root Vélu) of degree 23: + From: Elliptic Curve defined by y^2 = x^3 + 6*x + 46 over Finite Field of size 97 + To: Elliptic Curve defined by y^2 = x^3 + 95*x + 68 over Finite Field of size 97 + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _velu_sqrt_bound + sage: _velu_sqrt_bound.set(10) + sage: E.isogeny(P) + Elliptic-curve isogeny (using square-root Vélu) of degree 23: + From: Elliptic Curve defined by y^2 = x^3 + 6*x + 46 over Finite Field of size 97 + To: Elliptic Curve defined by y^2 = x^3 + 95*x + 68 over Finite Field of size 97 + sage: _velu_sqrt_bound.set(1000) # Reset bound + + If the order of the point is unknown, fall back to ``"traditional"``:: + + sage: E = EllipticCurve_from_j(GF(97)(42)) + sage: P = E(2, 39) + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _velu_sqrt_bound + sage: _velu_sqrt_bound.set(1) + sage: E.isogeny(P) + Isogeny of degree 46 from Elliptic Curve defined by y^2 = x^3 + 6*x + 46 over Finite Field of size 97 to Elliptic Curve defined by y^2 = x^3 + 87*x + 47 over Finite Field of size 97 + sage: _velu_sqrt_bound.set(1000) # Reset bound + .. SEEALSO:: - :class:`~sage.schemes.elliptic_curves.hom.EllipticCurveHom` @@ -1342,6 +1426,27 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al sage: phi = E.isogeny(E.lift_x(77347718128277853096420969229987528666)) sage: phi.codomain()._order 170141183460469231746191640949390434666 + + Check that ``"factored"`` recursively apply `velu_sqrt_bound`:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _velu_sqrt_bound + sage: _velu_sqrt_bound.get() + 1000 + sage: _velu_sqrt_bound.set(50) + sage: _velu_sqrt_bound.get() + 50 + sage: from sage.schemes.elliptic_curves import hom_composite + sage: p = 3217 + sage: E = EllipticCurve_from_j(GF(p)(42)) + sage: P = E.gens()[0] + sage: phis = hom_composite._compute_factored_isogeny_single_generator(P, velu_sqrt_bound=50) + sage: for phi in phis: + ....: print(phi) + ....: + Isogeny of degree 31 from Elliptic Curve defined by y^2 = x^3 + 114*x + 544 over Finite Field of size 3217 to Elliptic Curve defined by y^2 = x^3 + 277*x + 1710 over Finite Field of size 3217 + Elliptic-curve isogeny (using square-root Vélu) of degree 103: + From: Elliptic Curve defined by y^2 = x^3 + 277*x + 1710 over Finite Field of size 3217 + To: Elliptic Curve defined by y^2 = x^3 + 2979*x + 1951 over Finite Field of size 3217 """ if algorithm is not None and degree is not None: raise TypeError('cannot pass "degree" and "algorithm" parameters simultaneously') @@ -1350,7 +1455,36 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al return EllipticCurveHom_velusqrt(self, kernel, codomain=codomain, model=model) if algorithm == "factored": from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite - return EllipticCurveHom_composite(self, kernel, codomain=codomain, model=model) + return EllipticCurveHom_composite(self, kernel, codomain=codomain, model=model, velu_sqrt_bound=velu_sqrt_bound) + if algorithm == "traditional": + return EllipticCurveIsogeny(self, kernel, codomain, degree, model, check=check) + + if kernel is not None: + # Check for multiple points or point of known order + kernel_is_list = isinstance(kernel, list) or isinstance(kernel, tuple) + if kernel_is_list and kernel[0] in self and len(kernel) > 1: + from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite + return EllipticCurveHom_composite(self, kernel, codomain=codomain, model=model, velu_sqrt_bound=velu_sqrt_bound) + + if not kernel_is_list or (len(kernel) == 1 and kernel[0] in self): + # Single point on the curve; unpack the list for compatibility with velusqrt + if kernel_is_list: + kernel = kernel[0] + + known_order = hasattr(kernel, "_order") + + if known_order and kernel._order.is_pseudoprime(): + if not velu_sqrt_bound: + from sage.schemes.elliptic_curves.hom_velusqrt import _velu_sqrt_bound + velu_sqrt_bound = _velu_sqrt_bound.get() + + if kernel._order > velu_sqrt_bound: + from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + return EllipticCurveHom_velusqrt(self, kernel, codomain=codomain, model=model) + # Otherwise fall back to the standard case + elif known_order: + from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite + return EllipticCurveHom_composite(self, kernel, codomain=codomain, model=model, velu_sqrt_bound=velu_sqrt_bound) try: return EllipticCurveIsogeny(self, kernel, codomain, degree, model, check=check) except AttributeError as e: diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index bcfe8d5c1d2..eccbb917c11 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -487,10 +487,8 @@ def order(self): sage: E(0).order() == 1 True """ - try: + if hasattr(self, "_order"): return self._order - except AttributeError: - pass if self.is_zero(): self._order = Integer(1) return self._order diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index db3b1918926..fe450c89c5c 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -56,7 +56,7 @@ def __init__(self, *args, **kwds): 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: + Composite morphism 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='velusqrt') # indirect doctest @@ -755,11 +755,9 @@ def is_separable(self): sage: E = EllipticCurve(GF(7^2), [3,2]) sage: P = E.lift_x(1) sage: phi = EllipticCurveHom_composite(E, P); phi - Composite morphism of degree 7 = 7: - From: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 - over Finite Field in z2 of size 7^2 - To: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 - over Finite Field in z2 of size 7^2 + Composite morphism of degree 7: + From: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 over Finite Field in z2 of size 7^2 + To: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 over Finite Field in z2 of size 7^2 sage: phi.is_separable() True diff --git a/src/sage/schemes/elliptic_curves/hom_composite.py b/src/sage/schemes/elliptic_curves/hom_composite.py index ed87349bdcd..b9dcc197616 100644 --- a/src/sage/schemes/elliptic_curves/hom_composite.py +++ b/src/sage/schemes/elliptic_curves/hom_composite.py @@ -121,7 +121,7 @@ def _eval_factored_isogeny(phis, P): return P -def _compute_factored_isogeny_prime_power(P, l, n, split=.8): +def _compute_factored_isogeny_prime_power(P, l, n, split=.8, velu_sqrt_bound=None): r""" This method takes a point `P` of order `\ell^n` and returns a sequence of degree-`\ell` isogenies whose composition has @@ -147,6 +147,10 @@ def _compute_factored_isogeny_prime_power(P, l, n, split=.8): multiplications according to the parameter. The asymptotic complexity is `O(n \log(n) \ell)`. + The optional parameter ``velu_sqrt_bound`` prescribes the + point in which the computation of a single isogeny should be + performed using square root Velu instead of simple Velu. + .. NOTE:: As of July 2022, good values for ``split`` range somewhere @@ -201,7 +205,8 @@ def rec(Q, k): if k == 1: # base case: Q has order l - return [EllipticCurveIsogeny(Q.curve(), Q)] + Q._order = l # This was not cached before + return [Q.curve().isogeny(kernel=Q, velu_sqrt_bound=velu_sqrt_bound)] # recursive case: k > 1 and Q has order l^k @@ -219,12 +224,16 @@ def rec(Q, k): return rec(P, n) -def _compute_factored_isogeny_single_generator(P): +def _compute_factored_isogeny_single_generator(P, velu_sqrt_bound=None): """ This method takes a point `P` and returns a sequence of prime-degree isogenies whose composition has the subgroup generated by `P` as its kernel. + The optional parameter ``velu_sqrt_bound`` prescribes the + point in which the computation of a single isogeny should be + performed using square root Velu instead of simple Velu. + EXAMPLES:: sage: # needs sage.rings.finite_rings @@ -236,23 +245,41 @@ def _compute_factored_isogeny_single_generator(P): [2, 2, 3, 5, 7] sage: hom_composite._eval_factored_isogeny(phis, P) (0 : 1 : 0) + + :: + + sage: from sage.schemes.elliptic_curves import hom_composite + sage: p = 3217 + sage: E = EllipticCurve_from_j(GF(p)(42)) + sage: P = E.gens()[0] + sage: phis = hom_composite._compute_factored_isogeny_single_generator(P, velu_sqrt_bound=50) + sage: for phi in phis: + ....: print(phi) + Isogeny of degree 31 from Elliptic Curve defined by y^2 = x^3 + 114*x + 544 over Finite Field of size 3217 to Elliptic Curve defined by y^2 = x^3 + 277*x + 1710 over Finite Field of size 3217 + Elliptic-curve isogeny (using square-root Vélu) of degree 103: + From: Elliptic Curve defined by y^2 = x^3 + 277*x + 1710 over Finite Field of size 3217 + To: Elliptic Curve defined by y^2 = x^3 + 2979*x + 1951 over Finite Field of size 3217 """ phis = [] h = P.order() for l,e in P.order().factor(): h //= l**e - psis = _compute_factored_isogeny_prime_power(h*P, l, e) + psis = _compute_factored_isogeny_prime_power(h*P, l, e, velu_sqrt_bound=velu_sqrt_bound) P = _eval_factored_isogeny(psis, P) phis += psis return phis -def _compute_factored_isogeny(kernel): +def _compute_factored_isogeny(kernel, velu_sqrt_bound=None): """ This method takes a set of points on an elliptic curve and returns a sequence of isogenies whose composition has the subgroup generated by that subset as its kernel. + The optional parameter ``velu_sqrt_bound`` prescribes the + point in which the computation of a single isogeny should be + performed using square root Velu instead of simple Velu. + EXAMPLES:: sage: # needs sage.rings.finite_rings @@ -269,7 +296,7 @@ def _compute_factored_isogeny(kernel): ker = list(kernel) while ker: K = ker.pop(0) - psis = _compute_factored_isogeny_single_generator(K) + psis = _compute_factored_isogeny_single_generator(K, velu_sqrt_bound=velu_sqrt_bound) ker = [_eval_factored_isogeny(psis, P) for P in ker] phis += psis return phis @@ -280,7 +307,7 @@ class EllipticCurveHom_composite(EllipticCurveHom): _degree = None _phis = None - def __init__(self, E, kernel, codomain=None, model=None): + def __init__(self, E, kernel, codomain=None, model=None, velu_sqrt_bound=None): """ Construct a composite isogeny with given kernel (and optionally, prescribed codomain curve). The isogeny is decomposed into steps @@ -289,6 +316,13 @@ def __init__(self, E, kernel, codomain=None, model=None): The ``codomain`` and ``model`` parameters have the same meaning as for :class:`EllipticCurveIsogeny`. + The optional parameter ``velu_sqrt_bound`` prescribes the point + in which the computation of a single isogeny should be performed + using square root Velu instead of simple Velu. If not provided, + the system default is used (see + :class:`EllipticCurve_field.isogeny` for a more detailed + discussion. + EXAMPLES:: sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite @@ -354,7 +388,7 @@ def __init__(self, E, kernel, codomain=None, model=None): if P not in E: raise ValueError(f'given point {P} does not lie on {E}') - self._phis = _compute_factored_isogeny(kernel) + self._phis = _compute_factored_isogeny(kernel, velu_sqrt_bound=velu_sqrt_bound) if not self._phis: self._phis = [identity_morphism(E)] @@ -552,6 +586,10 @@ def _repr_(self): """ from itertools import groupby degs = [phi.degree() for phi in self._phis] + if len(degs) == 1: + return f'Composite morphism of degree {self._degree}:' \ + f'\n From: {self._domain}' \ + f'\n To: {self._codomain}' grouped = [(d, sum(1 for _ in g)) for d,g in groupby(degs)] degs_str = '*'.join(str(d) + (f'^{e}' if e > 1 else '') for d,e in grouped) return f'Composite morphism of degree {self._degree} = {degs_str}:' \ diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 33bccacd137..6c327f89ed6 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -135,6 +135,35 @@ from .ell_finite_field import EllipticCurve_finite_field from .hom import EllipticCurveHom, compare_via_evaluation +class _VeluBoundObj: + """ + Helper object to define the point in which isogeny + computation should start using square-roor Velu formulae + instead of Velu. + + EXAMPLES :: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _velu_sqrt_bound + sage: _velu_sqrt_bound.get() + 1000 + sage: _velu_sqrt_bound.set(50) + sage: _velu_sqrt_bound.get() + 50 + """ + def __init__(self): + self.bound = Integer(1000) + + def set(self, b): + self.bound = b + + def get(self): + return self.bound + + def __repr__(self): + return f"VeluSqrtBound Object with bound = {self.bound}" + + +_velu_sqrt_bound = _VeluBoundObj() def _choose_IJK(n): r"""