diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index 09fb3ffd3db..1acbabac265 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -2272,6 +2272,131 @@ def rational_points(self, algorithm='enum', sort=True): 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) + + sage: P in E + True + + :: + + sage: # needs sage.rings.finite_rings + sage: k. = 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) + + sage: P in E + True + + :: + + sage: # needs sage.rings.finite_rings + sage: k. = 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) + + 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. = 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. = 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): """ diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 5a97d2eda9b..fee15d24802 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -161,7 +161,7 @@ class EllipticCurveFactory(UniqueFactory): sage: R. = 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:: @@ -579,41 +579,19 @@ def coefficients_from_Weierstrass_polynomial(f): sage: R. = QQ[] sage: coefficients_from_Weierstrass_polynomial(-w^2 + z^3 + 1) [0, 0, 0, 0, 1] + sage: R. = 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): @@ -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) """ diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 515c22b4b8d..36c3f38ea43 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -41,7 +41,7 @@ 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 @@ -49,7 +49,7 @@ 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. @@ -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) - - sage: P in E - True - - :: - - sage: # needs sage.rings.finite_rings - sage: k. = 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) - - sage: P in E - True - - :: - - sage: # needs sage.rings.finite_rings - sage: k. = 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) - - 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. = 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. diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index b3c006b2c19..32ba30d25a1 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -29,7 +29,54 @@ from sage.structure.dynamic_class import dynamic_class -def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): +def _parse_multivariate_defining_equation(g): + """ + Parse a defining equation for a hyperelliptic curve. + The input `g` should have the form `g(x, y) = y^2 + h(x) y - f(x)`, + or a constant multiple of that. + + OUTPUT: tuple (f, h), each of them given as a list of coefficients. + """ + from sage.rings.polynomial.multi_polynomial import MPolynomial + if not isinstance(g, MPolynomial): + raise ValueError("must be a multivariate polynomial") + + variables = g.variables() + if len(variables) != 2: + raise ValueError("must be a polynomial in two variables") + + y, x = sorted(variables, key=g.degree) + if g.degree(y) != 2: + raise ValueError("must be a polynomial of degree 2 in a variable") + + f = [] + h = [] + for k, v in g: + dx = v.degree(x) + dy = v.degree(y) + if dy == 2: + if dx != 0: + raise ValueError(f"cannot have a term y*x^{dx}") + y2 = k + elif dy == 1: + while len(h) <= dx: + h.append(0) + h[dx] = k + else: + assert dy == 0 + while len(f) <= dx: + f.append(0) + f[dx] = -k + + if not y2.is_one(): + y2_inv = y2.inverse_of_unit() + f = [c * y2_inv for c in f] + h = [c * y2_inv for c in h] + + return f, h + + +def HyperellipticCurve(f, h=None, names=None, PP=None, check_squarefree=True): r""" Return the hyperelliptic curve `y^2 + h y = f`, for univariate polynomials `h` and `f`. If `h` @@ -76,6 +123,12 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): Hyperelliptic Curve over Finite Field in a of size 3^2 defined by y^2 + (x + a)*y = x^3 + x + 2 + Construct from defining polynomial:: + + sage: R. = QQ[] + sage: HyperellipticCurve(y^2 + 3*x^2*y - (x^5 + x + 1)) + Hyperelliptic Curve over Rational Field defined by y^2 + 3*x^2*y = x^5 + x + 1 + Characteristic two:: sage: # needs sage.rings.finite_rings @@ -200,6 +253,17 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): """ # F is the discriminant; use this for the type check # rather than f and h, one of which might be constant. + if h is None: + from sage.rings.polynomial.multi_polynomial import MPolynomial + if isinstance(f, MPolynomial) and len(f.parent().gens()) == 2: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.structure.element import get_coercion_model + P = PolynomialRing(f.base_ring(), 'x') + f, h = _parse_multivariate_defining_equation(f) + f, h = P(f), P(h) + else: + h = 0 + F = h**2 + 4 * f if not isinstance(F, Polynomial): raise TypeError(f"arguments f = {f} and h = {h} must be polynomials")