Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fast path for Vélu isogenies with a single kernel generator #36805

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 90 additions & 29 deletions src/sage/schemes/elliptic_curves/ell_curve_isogeny.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ def __init__(self, E, kernel, codomain=None, degree=None, model=None, check=True
self.__algorithm = algorithm

if algorithm == 'velu':
self.__init_from_kernel_list(kernel)
self.__init_from_kernel_gens(kernel)
elif algorithm == 'kohel':
self.__init_from_kernel_polynomial(kernel)
else:
Expand Down Expand Up @@ -1870,7 +1870,7 @@ def __setup_post_isomorphism(self, codomain, model):
# Setup function for Velu's formula
#

def __init_from_kernel_list(self, kernel_gens):
def __init_from_kernel_gens(self, kernel_gens):
r"""
Private function that initializes the isogeny from a list of
points which generate the kernel (For Vélu's formulas.)
Expand All @@ -1884,7 +1884,7 @@ def __init_from_kernel_list(self, kernel_gens):
Isogeny of degree 2
from Elliptic Curve defined by y^2 = x^3 + 6*x over Finite Field of size 7
to Elliptic Curve defined by y^2 = x^3 + 4*x over Finite Field of size 7
sage: phi._EllipticCurveIsogeny__init_from_kernel_list([E(0), E((0,0))])
sage: phi._EllipticCurveIsogeny__init_from_kernel_gens([E(0), E((0,0))])

The following example demonstrates the necessity of avoiding any calls
to P.order(), since such calls involve factoring the group order which
Expand All @@ -1907,6 +1907,14 @@ def __init_from_kernel_list(self, kernel_gens):
if not P.has_finite_order():
raise ValueError("given kernel contains point of infinite order")

self.__kernel_mod_sign = {}
self.__v = self.__w = 0

# Fast path: The kernel is given by a single generating point.
if len(kernel_gens) == 1 and kernel_gens[0]:
self.__init_from_kernel_point(kernel_gens[0])
return

# Compute a list of points in the subgroup generated by the
# points in kernel_gens. This is very naive: when finite
# subgroups are implemented better, this could be simplified,
Expand All @@ -1927,12 +1935,86 @@ def all_multiples(itr, terminal):

self._degree = Integer(len(kernel_set))
self.__kernel_list = list(kernel_set)
self.__sort_kernel_list()
self.__init_from_kernel_list()

#
# Precompute the values in Velu's Formula.
#
def __sort_kernel_list(self):
def __update_kernel_data(self, xQ, yQ):
r"""
Internal helper function to update some data coming from the
kernel points of this isogeny when using Vélu's formulas.

TESTS:

The following example inherently exercises this function::

sage: E = EllipticCurve(GF(7), [0,0,0,-1,0])
sage: P = E((4,2))
sage: phi = EllipticCurveIsogeny(E, [P,P]); phi # implicit doctest
Isogeny of degree 4
from Elliptic Curve defined by y^2 = x^3 + 6*x over Finite Field of size 7
to Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 7
"""
a1, a2, a3, a4, _ = self._domain.a_invariants()

gxQ = (3*xQ + 2*a2)*xQ + a4 - a1*yQ
gyQ = -2*yQ - a1*xQ - a3

uQ = gyQ**2

if 2*yQ == -a1*xQ - a3: # Q is 2-torsion
vQ = gxQ
else: # Q is not 2-torsion
vQ = 2*gxQ - a1*gyQ

self.__kernel_mod_sign[xQ] = yQ, gxQ, gyQ, vQ, uQ

self.__v += vQ
self.__w += uQ + xQ*vQ

def __init_from_kernel_point(self, ker):
r"""
Private function with functionality equivalent to
:meth:`__init_from_kernel_list`, but optimized for when
the kernel is given by a single point.

TESTS:

The following example inherently exercises this function::

sage: E = EllipticCurve(GF(7), [0,0,0,-1,0])
sage: P = E((4,2))
sage: phi = EllipticCurveIsogeny(E, P); phi # implicit doctest
Isogeny of degree 4
from Elliptic Curve defined by y^2 = x^3 + 6*x over Finite Field of size 7
to Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 7

We check that the result is the same as for :meth:`__init_from_kernel_list`::

sage: psi = EllipticCurveIsogeny(E, [P, P]); psi
Isogeny of degree 4
from Elliptic Curve defined by y^2 = x^3 + 6*x over Finite Field of size 7
to Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 7
sage: phi == psi
True
"""
self._degree = Integer(1)

Q, prevQ = ker, self._domain(0)

while Q and Q != -prevQ:
self.__update_kernel_data(*Q.xy())

if Q == -Q:
self._degree += 1
break

prevQ = Q
Q += ker
self._degree += 2

def __init_from_kernel_list(self):
r"""
Private function that sorts the list of points in the kernel
(For Vélu's formulas). Sorts out the 2-torsion points, and
Expand All @@ -1944,43 +2026,22 @@ def __sort_kernel_list(self):

sage: E = EllipticCurve(GF(7), [0,0,0,-1,0])
sage: P = E((4,2))
sage: phi = EllipticCurveIsogeny(E, P); phi
sage: phi = EllipticCurveIsogeny(E, [P,P]); phi # implicit doctest
Isogeny of degree 4
from Elliptic Curve defined by y^2 = x^3 + 6*x over Finite Field of size 7
to Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 7
sage: phi._EllipticCurveIsogeny__sort_kernel_list()
"""
a1, a2, a3, a4, _ = self._domain.a_invariants()

self.__kernel_mod_sign = {}
v = w = 0

for Q in self.__kernel_list:

if Q.is_zero():
continue

xQ,yQ = Q.xy()
xQ, yQ = Q.xy()

if xQ in self.__kernel_mod_sign:
continue

gxQ = (3*xQ + 2*a2)*xQ + a4 - a1*yQ
gyQ = -2*yQ - a1*xQ - a3

uQ = gyQ**2

if 2*yQ == -a1*xQ - a3: # Q is 2-torsion
vQ = gxQ
else: # Q is not 2-torsion
vQ = 2*gxQ - a1*gyQ

self.__kernel_mod_sign[xQ] = yQ, gxQ, gyQ, vQ, uQ

v += vQ
w += uQ + xQ*vQ

self.__v, self.__w = v, w
self.__update_kernel_data(xQ, yQ)

#
# Velu's formula computing the codomain curve
Expand Down
Loading