Skip to content

Implement HyperellipticCurve.random_element() over finite fields #40536

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

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
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
125 changes: 125 additions & 0 deletions src/sage/schemes/curves/projective_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -1400,7 +1400,7 @@
sage: set_verbose(-1)
sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve([(x^2 + y^2 - y*z - 2*z^2)*(y*z - x^2 + 2*z^2)*z + y^5], P)
sage: C.ordinary_model() # long time (5 seconds)

Check warning on line 1403 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Warning: slow doctest:

slow doctest:: Test ran for 117.65s cpu, 117.66s wall Check ran for 0.00s cpu, 0.00s wall

Check warning on line 1403 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[p-z]*)

Warning: slow doctest:

slow doctest:: Test ran for 127.78s cpu, 127.77s wall Check ran for 0.00s cpu, 0.00s wall
Scheme morphism:
From: Projective Plane Curve over Number Field in a
with defining polynomial y^2 - 2 defined
Expand Down Expand Up @@ -1804,7 +1804,7 @@
sage: C = P.curve(z^2*y^3 - z*(33*x*z+2*x^2+8*z^2)*y^2
....: + (21*z^2+21*x*z-x^2)*(z^2+11*x*z-x^2)*y
....: + (x-18*z)*(z^2+11*x*z-x^2)^2)
sage: G0 = C.fundamental_group() # needs sirocco

Check warning on line 1807 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / Conda (macos, Python 3.11, all)

Warning: slow doctest:

slow doctest:: Test ran for 5.70s cpu, 4.13s wall Check ran for 0.00s cpu, 0.00s wall

Check warning on line 1807 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / Conda (macos, Python 3.12, all)

Warning: slow doctest:

slow doctest:: Test ran for 5.22s cpu, 3.78s wall Check ran for 0.00s cpu, 0.00s wall

Check warning on line 1807 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.11, all)

Warning: slow doctest:

slow doctest:: Test ran for 9.95s cpu, 9.96s wall Check ran for 0.00s cpu, 0.00s wall

Check warning on line 1807 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, all)

Warning: slow doctest:

slow doctest:: Test ran for 9.85s cpu, 9.58s wall Check ran for 0.00s cpu, 0.00s wall

Check warning on line 1807 in src/sage/schemes/curves/projective_curve.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, all, editable)

Warning: slow doctest:

slow doctest:: Test ran for 10.44s cpu, 9.89s wall Check ran for 0.00s cpu, 0.00s wall
sage: G.is_isomorphic(G0) # needs sirocco
True
sage: C = P.curve(z)
Expand Down Expand Up @@ -2272,6 +2272,131 @@

raise ValueError(f"No algorithm '{algorithm}' known")

def random_element(self):
"""
Return a random point on this elliptic/hyperelliptic curve, uniformly chosen
among all rational points.

ALGORITHM:

Choose the point at infinity with probability `1/(2q + 1)`.
Otherwise, take a random element from the field as x-coordinate
and compute the possible y-coordinates. Return the i-th
possible y-coordinate, where i is randomly chosen to be 0 or 1.
If the i-th y-coordinate does not exist (either there is no
point with the given x-coordinate or we hit a 2-torsion point
with i == 1), try again.

This gives a uniform distribution because you can imagine
`2q + 1` buckets, one for the point at infinity and 2 for each
element of the field (representing the x-coordinates). This
gives a 1-to-1 map of (hyper)elliptic curve points into buckets. At
every iteration, we simply choose a random bucket until we find
a bucket containing a point.

AUTHORS:

- Jeroen Demeyer (2014-09-09): choose points uniformly random,
see :issue:`16951`.

EXAMPLES::

sage: k = GF(next_prime(7^5))
sage: E = EllipticCurve(k,[2,4])
sage: P = E.random_element(); P # random
(16740 : 12486 : 1)
sage: type(P)
<class 'sage.schemes.elliptic_curves.ell_point.EllipticCurvePoint_finite_field'>
sage: P in E
True

::

sage: # needs sage.rings.finite_rings
sage: k.<a> = GF(7^5)
sage: E = EllipticCurve(k,[2,4])
sage: P = E.random_element(); P # random
(5*a^4 + 3*a^3 + 2*a^2 + a + 4 : 2*a^4 + 3*a^3 + 4*a^2 + a + 5 : 1)
sage: type(P)
<class 'sage.schemes.elliptic_curves.ell_point.EllipticCurvePoint_finite_field'>
sage: P in E
True

::

sage: # needs sage.rings.finite_rings
sage: k.<a> = GF(2^5)
sage: E = EllipticCurve(k,[a^2,a,1,a+1,1])
sage: P = E.random_element(); P # random
(a^4 + a : a^4 + a^3 + a^2 : 1)
sage: type(P)
<class 'sage.schemes.elliptic_curves.ell_point.EllipticCurvePoint_finite_field'>
sage: P in E
True

Ensure that the entire point set is reachable::

sage: E = EllipticCurve(GF(11), [2,1])
sage: S = set()
sage: while len(S) < E.cardinality():
....: S.add(E.random_element())

TESTS:

See :issue:`8311`::

sage: E = EllipticCurve(GF(3), [0,0,0,2,2])
sage: E.random_element()
(0 : 1 : 0)
sage: E.cardinality()
1

sage: E = EllipticCurve(GF(2), [0,0,1,1,1])
sage: E.random_point()
(0 : 1 : 0)
sage: E.cardinality()
1

sage: # needs sage.rings.finite_rings
sage: F.<a> = GF(4)
sage: E = EllipticCurve(F, [0, 0, 1, 0, a])
sage: E.random_point()
(0 : 1 : 0)
sage: E.cardinality()
1

Sampling from points on a hyperelliptic curve::

sage: R.<x,y> = GF(13)[]
sage: C = HyperellipticCurve(y^2 + 3*x^2*y - (x^5 + x + 1))
sage: P = C.random_element(); P # random
(0 : 1 : 0)
sage: P in C
True
"""
from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field
from sage.schemes.hyperelliptic_curves.hyperelliptic_finite_field import HyperellipticCurve_finite_field
if not isinstance(self, (EllipticCurve_finite_field, HyperellipticCurve_finite_field)):
raise NotImplementedError("only implemented for elliptic and hyperelliptic curves over finite fields")

k = self.base_ring()
n = 2 * k.order() + 1

from sage.rings.integer_ring import ZZ
while True:
# Choose the point at infinity with probability 1/(2q + 1)
i = ZZ.random_element(n)
if not i:
return self(0, 1, 0)

v = self.lift_x(k.random_element(), all=True)
try:
return v[i % 2]
except IndexError:
pass

random_point = random_element


class IntegralProjectiveCurve(ProjectiveCurve_field):
"""
Expand Down
46 changes: 12 additions & 34 deletions src/sage/schemes/elliptic_curves/constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class EllipticCurveFactory(UniqueFactory):

sage: R.<x,y> = GF(5)[]
sage: EllipticCurve(x^3 + x^2 + 2 - y^2 - y*x)
Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 2 over Finite Field of size 5
Elliptic Curve defined by y^2 + x*y = x^3 + x^2 + 2 over Finite Field of size 5

We can also create elliptic curves by giving a smooth plane cubic with a rational point::

Expand Down Expand Up @@ -579,41 +579,19 @@ def coefficients_from_Weierstrass_polynomial(f):
sage: R.<w,z> = QQ[]
sage: coefficients_from_Weierstrass_polynomial(-w^2 + z^3 + 1)
[0, 0, 0, 0, 1]
sage: R.<u,v> = GF(13)[]
sage: EllipticCurve(u^2 + 2*v*u + 3*u - (v^3 + 4*v^2 + 5*v + 6)) # indirect doctest
Elliptic Curve defined by y^2 + 2*x*y + 3*y = x^3 + 4*x^2 + 5*x + 6 over Finite Field of size 13
"""
R = f.parent()
cubic_variables = [ x for x in R.gens() if f.degree(x) == 3 ]
quadratic_variables = [ y for y in R.gens() if f.degree(y) == 2 ]
try:
x = cubic_variables[0]
y = quadratic_variables[0]
except IndexError:
from sage.schemes.hyperelliptic_curves.constructor import _parse_multivariate_defining_equation
f, h = _parse_multivariate_defining_equation(f)
# OUTPUT: tuple (f, h), each of them given as a list of coefficients.
if len(f) != 4 or len(h) > 2:
raise ValueError('polynomial is not in long Weierstrass form')

a1 = a2 = a3 = a4 = a6 = 0
x3 = y2 = None
for coeff, mon in f:
if mon == x**3:
x3 = coeff
elif mon == x**2:
a2 = coeff
elif mon == x:
a4 = coeff
elif mon == 1:
a6 = coeff
elif mon == y**2:
y2 = -coeff
elif mon == x*y:
a1 = -coeff
elif mon == y:
a3 = -coeff
else:
raise ValueError('polynomial is not in long Weierstrass form')

if x3 != y2:
if not f[3].is_one():
raise ValueError('the coefficient of x^3 and -y^2 must be the same')
elif x3 != 1:
a1, a2, a3, a4, a6 = a1/x3, a2/x3, a3/x3, a4/x3, a6/x3
return [a1, a2, a3, a4, a6]
h += [0] * (2 - len(h))
return [h[1], f[2], h[0], f[1], f[0]]


def EllipticCurve_from_c4c6(c4, c6):
Expand All @@ -625,7 +603,7 @@ def EllipticCurve_from_c4c6(c4, c6):

sage: E = EllipticCurve_from_c4c6(17, -2005)
sage: E
Elliptic Curve defined by y^2 = x^3 - 17/48*x + 2005/864 over Rational Field
Elliptic Curve defined by y^2 = x^3 - 17/48*x + 2005/864 over Rational Field
sage: E.c_invariants()
(17, -2005)
"""
Expand Down
114 changes: 2 additions & 112 deletions src/sage/schemes/elliptic_curves/ell_finite_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.polynomial_ring import polygen
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.schemes.curves.projective_curve import Hasse_bounds
from sage.schemes.curves.projective_curve import Hasse_bounds, ProjectivePlaneCurve_finite_field
from sage.structure.element import Element

from . import ell_point
from .constructor import EllipticCurve
from .ell_field import EllipticCurve_field


class EllipticCurve_finite_field(EllipticCurve_field):
class EllipticCurve_finite_field(EllipticCurve_field, ProjectivePlaneCurve_finite_field):
r"""
Elliptic curve over a finite field.

Expand Down Expand Up @@ -287,116 +287,6 @@ def count_points(self, n=1):

return [self.cardinality(extension_degree=i) for i in range(1, n + 1)]

def random_element(self):
"""
Return a random point on this elliptic curve, uniformly chosen
among all rational points.

ALGORITHM:

Choose the point at infinity with probability `1/(2q + 1)`.
Otherwise, take a random element from the field as x-coordinate
and compute the possible y-coordinates. Return the i-th
possible y-coordinate, where i is randomly chosen to be 0 or 1.
If the i-th y-coordinate does not exist (either there is no
point with the given x-coordinate or we hit a 2-torsion point
with i == 1), try again.

This gives a uniform distribution because you can imagine
`2q + 1` buckets, one for the point at infinity and 2 for each
element of the field (representing the x-coordinates). This
gives a 1-to-1 map of elliptic curve points into buckets. At
every iteration, we simply choose a random bucket until we find
a bucket containing a point.

AUTHORS:

- Jeroen Demeyer (2014-09-09): choose points uniformly random,
see :issue:`16951`.

EXAMPLES::

sage: k = GF(next_prime(7^5))
sage: E = EllipticCurve(k,[2,4])
sage: P = E.random_element(); P # random
(16740 : 12486 : 1)
sage: type(P)
<class 'sage.schemes.elliptic_curves.ell_point.EllipticCurvePoint_finite_field'>
sage: P in E
True

::

sage: # needs sage.rings.finite_rings
sage: k.<a> = GF(7^5)
sage: E = EllipticCurve(k,[2,4])
sage: P = E.random_element(); P # random
(5*a^4 + 3*a^3 + 2*a^2 + a + 4 : 2*a^4 + 3*a^3 + 4*a^2 + a + 5 : 1)
sage: type(P)
<class 'sage.schemes.elliptic_curves.ell_point.EllipticCurvePoint_finite_field'>
sage: P in E
True

::

sage: # needs sage.rings.finite_rings
sage: k.<a> = GF(2^5)
sage: E = EllipticCurve(k,[a^2,a,1,a+1,1])
sage: P = E.random_element(); P # random
(a^4 + a : a^4 + a^3 + a^2 : 1)
sage: type(P)
<class 'sage.schemes.elliptic_curves.ell_point.EllipticCurvePoint_finite_field'>
sage: P in E
True

Ensure that the entire point set is reachable::

sage: E = EllipticCurve(GF(11), [2,1])
sage: S = set()
sage: while len(S) < E.cardinality():
....: S.add(E.random_element())

TESTS:

See :issue:`8311`::

sage: E = EllipticCurve(GF(3), [0,0,0,2,2])
sage: E.random_element()
(0 : 1 : 0)
sage: E.cardinality()
1

sage: E = EllipticCurve(GF(2), [0,0,1,1,1])
sage: E.random_point()
(0 : 1 : 0)
sage: E.cardinality()
1

sage: # needs sage.rings.finite_rings
sage: F.<a> = GF(4)
sage: E = EllipticCurve(F, [0, 0, 1, 0, a])
sage: E.random_point()
(0 : 1 : 0)
sage: E.cardinality()
1
"""
k = self.base_field()
n = 2 * k.order() + 1

while True:
# Choose the point at infinity with probability 1/(2q + 1)
i = ZZ.random_element(n)
if not i:
return self.point(0)

v = self.lift_x(k.random_element(), all=True)
try:
return v[i % 2]
except IndexError:
pass

random_point = random_element

def trace_of_frobenius(self):
r"""
Return the trace of Frobenius acting on this elliptic curve.
Expand Down
Loading
Loading