From 8a97e7730baf94d981ba30de932ab38564406058 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 2 Dec 2024 21:53:33 +0000 Subject: [PATCH 01/56] start work on porting to sage --- src/sage/schemes/all.py | 2 + .../hyperelliptic_curves_smooth_model/all.py | 2 + .../hyperelliptic_constructor.py | 341 ++ .../hyperelliptic_finite_field.py | 1702 +++++++ .../hyperelliptic_g2.py | 192 + .../hyperelliptic_generic.py | 1459 ++++++ .../hyperelliptic_padic_field.py | 1425 ++++++ .../hyperelliptic_rational_field.py | 129 + .../invariants.py | 434 ++ .../jacobian_g2_generic.py | 30 + .../jacobian_g2_homset_inert.py | 12 + .../jacobian_g2_homset_ramified.py | 12 + .../jacobian_g2_homset_split.py | 12 + .../jacobian_generic.py | 230 + .../jacobian_homset_generic.py | 664 +++ .../jacobian_homset_inert.py | 24 + .../jacobian_homset_ramified.py | 12 + .../jacobian_homset_split.py | 269 ++ .../jacobian_morphism.py | 368 ++ .../meson.build | 31 + .../mestre.py | 375 ++ .../monsky_washnitzer.py | 3999 +++++++++++++++++ .../weighted_projective_curve.py | 12 + .../weighted_projective_homset.py | 46 + .../weighted_projective_point.py | 389 ++ .../weighted_projective_space.py | 369 ++ src/sage/schemes/meson.build | 1 + 27 files changed, 12541 insertions(+) create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/all.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py diff --git a/src/sage/schemes/all.py b/src/sage/schemes/all.py index f5126a0dee1..90ca2a53bd3 100644 --- a/src/sage/schemes/all.py +++ b/src/sage/schemes/all.py @@ -45,3 +45,5 @@ from sage.schemes.cyclic_covers.all import * from sage.schemes.berkovich.all import * + +from sage.schemes.hyperelliptic_curves_smooth_model.all import * diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py new file mode 100644 index 00000000000..4e197e56905 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -0,0 +1,2 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.constructor import HyperellipticCurveSmoothModel + diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py new file mode 100644 index 00000000000..7c25c8aa5f9 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -0,0 +1,341 @@ +r""" + Constructor for hyperelliptic curves using the smooth model + + In Sage, a hyperelliptic curve of genus `g` is always + specified by an (affine) equation in Weierstrass form + + .. MATH:: + + y^2 + h(x) y = f(x), + + for some polynomials `h` and `f`. This defines a smooth + model in weighted projective space `\PP(1 : g + 1 : 1)` + + .. MATH:: + Y^2 + H(X,Z) Y = F(X,Z), + + where `H` is the degree `g + 1` homogenization of `h`, + and `F` is the degree `2 g + 2` homogenization of `f`. + + There are either 0, 1 or 2 points at infinity (`Z=0`), + in which case we say that the hyperelliptic curve is + inert, ramified or split, respectively. + + + EXAMPLES: + + We create a hyperelliptic curve over the rationals:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 + + This hyperelliptic curve has no points at infinity, + i.e. `H` is inert:: + sage: H.points_at_infinity() + [] + sage: H.is_inert() + True + + We can extend the base field to obtain a hyperelliptic curve + with two points at infinity:: + sage: K. = QQ.extension(x^2+x-1) + sage: HK = H.change_ring(K) + sage: HK.points_at_infinity() + [[1 : alpha : 0], [1 : -alpha - 1 : 0]] + sage: HK.is_split() + True + + The construction of hyperelliptic curves is supported over different fields. + The correct class is chosen automatically:: + sage: F = FiniteField(13) + sage: S. = PolynomialRing(F) + sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF + Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^3 + x)*y = x^5 + x^4 + x^3 + x^2 + x + 1 + sage: type(HF) + + sage: Q5 = Qp(5,10) + sage: T. = Q5[] + sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 + Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) + sage: type(H5) + + + The input polynomials need not be monic:: + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(3*x^5+1) + Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 + + The polynomials f and h need to define a smooth curve of genus at + least one. In particular polynomials defining elliptic curves are + allowed as input. + sage: E = HyperellipticCurveSmoothModel(x^3+1) + sage: E.genus() + 1 + sage: HyperellipticCurveSmoothModel(x) + Traceback (most recent call last): + ... + ValueError: arguments f = x and h = 0 must define a curve of genus at least one. + + The following polynomials define a singular curve and are + not allowed as input:: + sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) + Traceback (most recent call last): + ... + ValueError: not a hyperelliptic curve: singularity in the provided affine patch + +Adapted from /hyperelliptic/constructor.py + +AUTHORS: + +- David Kohel (2006): initial version +- Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# 2019 Anna Somoza +# 2024 Sabrina Kunzweiler, Gareth Ma, Giacomo Pope +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.categories.finite_fields import FiniteFields +from sage.rings.abc import pAdicField +from sage.rings.integer import Integer +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import RationalField +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_finite_field import ( + HyperellipticCurveSmoothModel_finite_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_g2 import ( + HyperellipticCurveSmoothModel_g2, + HyperellipticCurveSmoothModel_g2_finite_field, + HyperellipticCurveSmoothModel_g2_padic_field, + HyperellipticCurveSmoothModel_g2_rational_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic import ( + HyperellipticCurveSmoothModel_generic, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_padic_field import ( + HyperellipticCurveSmoothModel_padic_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_rational_field import ( + HyperellipticCurveSmoothModel_rational_field, +) + +""" +TODO: + +- We currently cannot support the construction of curves over rings + +""" + + +def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): + r""" + Constructor function for creating a hyperelliptic curve with + smooth model with polynomials f, h. + + In Sage, a hyperelliptic curve of genus `g` is always + specified by an (affine) equation in Weierstrass form + + .. MATH:: + + y^2 + h(x) y = f(x), + + for some polynomials `h` and `f`. This defines a smooth + model in weighted projective space `\PP(1 : g + 1 : 1)` + + .. MATH:: + Y^2 + H(X,Z) Y = F(X,Z), + + where `H` is the degree `g + 1` homogenization of `h`, + and `F` is the degree `2 g + 2` homogenization of `f`. + + INPUT: + + - ``f`` -- polynomial + + - ``h`` (default: ``0``) -- polynomial + + - ``check_squarefree`` (default: ``True``) -- test if + the input defines a hyperelliptic curve + + EXAMPLES: + + We create a hyperelliptic curve over the rationals:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 + + This hyperelliptic curve has no points at infinity, + i.e. `H` is inert:: + sage: H.points_at_infinity() + [] + sage: H.is_inert() + True + + We can extend the base field to obtain a hyperelliptic curve + with two points at infinity:: + sage: K. = QQ.extension(x^2+x-1) + sage: HK = H.change_ring(K) + sage: HK.points_at_infinity() + [[1 : alpha : 0], [1 : -alpha - 1 : 0]] + sage: HK.is_split() + True + + The construction of hyperelliptic curves is supported over different fields. + The correct class is chosen automatically:: + sage: F = FiniteField(13) + sage: S. = PolynomialRing(F) + sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF + Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^3 + x)*y = x^5 + x^4 + x^3 + x^2 + x + 1 + sage: type(HF) + + sage: Q5 = Qp(5,10) + sage: T. = Q5[] + sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 + Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) + sage: type(H5) + + + The input polynomials need not be monic:: + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(3*x^5+1) + Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 + + The polynomials f and h need to define a smooth curve of genus at + least one. In particular polynomials defining elliptic curves are + allowed as input. + sage: E = HyperellipticCurveSmoothModel(x^3+1) + sage: E.genus() + 1 + sage: HyperellipticCurveSmoothModel(x) + Traceback (most recent call last): + ... + ValueError: arguments f = x and h = 0 must define a curve of genus at least one. + + The following polynomials define a singular curve and are + not allowed as input:: + sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) + Traceback (most recent call last): + ... + ValueError: not a hyperelliptic curve: singularity in the provided affine patch + + """ + + # --------------------------- + # Internal Helper functions + # --------------------------- + + def __genus(f, h): + """ + Helper function to compute the genus of a hyperelliptic curve + defined by `y^2 + h(x)y = f(x)`. + """ + # Some classes still have issues with degrees returning `int` + # rather than Sage Integer types + df = Integer(f.degree()) + dh_2 = 2 * Integer(h.degree()) + if dh_2 < df: + return (df - 1) // 2 + return (dh_2 - 1) // 2 + + def __check_no_affine_singularities(f, h): + """ + Helper function which determines whether there are any + affine singularities in the curve `y^2 + h(x)y = f(x)`. + """ + if f.base_ring().characteristic() == 2: + if h.is_zero(): + return False + elif h.is_constant(): + return True + return h.gcd(f.derivative() ** 2 - f * h.derivative() ** 2).is_one() + + if h.is_zero(): + return f.gcd(f.derivative()).is_one() + + g1 = h**2 + 4 * f + g2 = 2 * f.derivative() + h * h.derivative() + return g1.gcd(g2).is_one() + + def __defining_polynomial(f, h): + """ + Compute the (homogenised weighted projective) defining polynomial of + the hyperelliptic curve. + """ + X, Y, Z = PolynomialRing(f.base_ring(), names="X, Y, Z").gens() + + # Some classes still have issues with degrees returning `int` + d = max(Integer(h.degree()), (Integer(f.degree()) + 1) // 2) + F = sum(f[i] * X**i * Z ** (2 * d - i) for i in range(2 * d + 1)) + + if h.is_zero(): + G = Y**2 - F + else: + H = sum(h[i] * X**i * Z ** (d - i) for i in range(d + 1)) + G = Y**2 + H * Y - F + + return G + + # ------------------------------------------- + # Typechecking and projective model creation + # ------------------------------------------- + + # Check the polynomials are of the right type + F = h**2 + 4 * f + if not isinstance(F, Polynomial): + raise TypeError(f"arguments {f = } and {h = } must be polynomials") + + # Store the hyperelliptic polynomials as the correct type + polynomial_ring = F.parent() + base_ring = F.base_ring() + f = polynomial_ring(f) + h = polynomial_ring(h) + + # Ensure that there are no affine singular points + if check_squarefree and not __check_no_affine_singularities(f, h): + raise ValueError("singularity in the provided affine patch") + + # Compute the genus of the curve from f, h + genus = __genus(f, h) + if genus == 0: + raise ValueError(f"arguments {f = } and {h = } must define a curve of genus at least one.") + + # Compute the smooth model for the hyperelliptic curve + # using a weighted projective space (via Toric Variety) + defining_polynomial = __defining_polynomial(f, h) + + # ----------------------- + # Class selection + # ----------------------- + + # Special class for finite fields + if base_ring in FiniteFields(): + if genus == 2: + cls = HyperellipticCurveSmoothModel_g2_finite_field + else: + cls = HyperellipticCurveSmoothModel_finite_field + # Special class for pAdic fields + elif isinstance(base_ring, pAdicField): + if genus == 2: + cls = HyperellipticCurveSmoothModel_g2_padic_field + else: + cls = HyperellipticCurveSmoothModel_padic_field + # Special class for rational fields + elif isinstance(base_ring, RationalField): + if genus == 2: + cls = HyperellipticCurveSmoothModel_g2_rational_field + else: + cls = HyperellipticCurveSmoothModel_rational_field + # Default class for all other fields + elif genus == 2: + cls = HyperellipticCurveSmoothModel_g2 + else: + cls = HyperellipticCurveSmoothModel_generic + + return cls(defining_polynomial, f, h, genus) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py new file mode 100644 index 00000000000..78308a8dacf --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -0,0 +1,1702 @@ +from sage.arith.misc import binomial +from sage.libs.pari.all import pari +from sage.matrix.constructor import identity_matrix, matrix +from sage.misc.cachefunc import cached_method +from sage.misc.functional import rank +from sage.misc.prandom import choice +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF +from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ +from sage.rings.real_mpfr import RR +from sage.schemes.hyperelliptic_curves.hypellfrob import hypellfrob +from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic + + +class HyperellipticCurveSmoothModel_finite_field( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + """ + TODO: write some examples of this class + """ + + def __init__(self, projective_model, f, h, genus): + super().__init__(projective_model, f, h, genus) + + def random_point(self): + """ + Return a random point on this hyperelliptic curve, uniformly chosen + among all rational points. + + EXAMPLES:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: C.random_point() # random + (4 : 1 : 1) + sage: type(C.random_point()) + + """ + k = self.base_ring() + 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 and not self.is_inert(): + # Deal with that there is more than one point at infinity + return choice(self.points_at_infinity()) + v = self.lift_x(k.random_element(), all=True) + try: + return v[i % 2] + except IndexError: + pass + + def rational_points_iterator(self): + """ + Return all the points on this hyperelliptic curve as an iterator. + + EXAMPLES:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: list(C.rational_points_iterator()) + [(1 : 0 : 0), (2 : 2 : 1), (2 : 5 : 1), (3 : 0 : 1), (4 : 1 : 1), + (4 : 6 : 1), (5 : 0 : 1), (6 : 2 : 1), (6 : 5 : 1)] + sage: _ == C.points() + True + + .. SEEALSO:: :meth:`points` + """ + # TODO: this is very silly but works + yield from self.points_at_infinity() + for x in self.base_ring(): + yield from self.lift_x(x, all=True) + + @cached_method + def points(self): + """ + Return all the points on this hyperelliptic curve. + + EXAMPLES:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: C.points() + [(1 : 0 : 0), (2 : 2 : 1), (2 : 5 : 1), (3 : 0 : 1), (4 : 1 : 1), + (4 : 6 : 1), (5 : 0 : 1), (6 : 2 : 1), (6 : 5 : 1)] + + :: + + sage: x = polygen(GF(121, 'a')) + sage: C = HyperellipticCurveSmoothModel(x^5 + x - 1, x^2 + 2) + sage: len(C.points()) + 122 + + As we use the smooth model we also can work with the case + of an even degree model:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^6 - 1) + sage: C.points() + [(1 : 1 : 0), (1 : 6 : 0), (1 : 0 : 1), (2 : 0 : 1), (3 : 0 : 1), + (4 : 0 : 1), (5 : 0 : 1), (6 : 0 : 1)] + + Conics are allowed (the issue reported at :issue:`11800` + has been resolved):: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(3*x^2 + 5*x + 1) + sage: H.points() + [(0 : 1 : 1), (0 : 6 : 1), (1 : 3 : 1), (1 : 4 : 1), (2 : 3 : 1), + (2 : 4 : 1), (3 : 1 : 1), (3 : 6 : 1)] + + .. SEEALSO:: :meth:`rational_points_iterator` + """ + return list(self.rational_points_iterator()) + + rational_points = points + + def count_points_matrix_traces(self, n=1, M=None, N=None): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field by computing traces of powers of the frobenius + matrix. + This requires less `p`-adic precision than computing the charpoly + of the matrix when `n < g` where `g` is the genus of the curve. + + EXAMPLES:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^19 + t + 1) + sage: H.count_points_matrix_traces(3) + [49491, 2500024375, 124992509154249] + + TESTS: + + Check that :issue:`18831` is fixed:: + + sage: R. = PolynomialRing(GF(11)) + sage: H = HyperellipticCurveSmoothModel(t^5 - t + 1) + sage: H.count_points_matrix_traces() + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 15 + """ + if N is None: + N = self._frobenius_coefficient_bound_traces(n=n) + + if M is None: + M = self.frobenius_matrix(N=N) + + K = self.base_ring() + p = K.characteristic() + q = K.cardinality() + ppow = p**N + + t = [] + Mpow = 1 + for i in range(n): + Mpow *= M + t.append(Mpow.trace()) + + t = [x.lift() for x in t] + t = [x if 2 * x < ppow else x - ppow for x in t] + + return [q ** (i + 1) + 1 - t[i] for i in range(n)] + + def count_points_frobenius_polynomial(self, n=1, f=None): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field by computing the frobenius polynomial. + + EXAMPLES:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^19 + t + 1) + + The following computation takes a long time as the complete + characteristic polynomial of the frobenius is computed:: + + sage: H.count_points_frobenius_polynomial(3) # long time, 20s on a Corei7 (when computed before the following test of course) + [49491, 2500024375, 124992509154249] + + As the polynomial is cached, further computations of number of points + are really fast:: + + sage: H.count_points_frobenius_polynomial(19) # long time, because of the previous test + [49491, + 2500024375, + 124992509154249, + 6249500007135192947, + 312468751250758776051811, + 15623125093747382662737313867, + 781140631562281338861289572576257, + 39056250437482500417107992413002794587, + 1952773465623687539373429411200893147181079, + 97636720507718753281169963459063147221761552935, + 4881738388665429945305281187129778704058864736771824, + 244082037694882831835318764490138139735446240036293092851, + 12203857802706446708934102903106811520015567632046432103159713, + 610180686277519628999996211052002771035439565767719719151141201339, + 30508424133189703930370810556389262704405225546438978173388673620145499, + 1525390698235352006814610157008906752699329454643826047826098161898351623931, + 76268009521069364988723693240288328729528917832735078791261015331201838856825193, + 3813324208043947180071195938321176148147244128062172555558715783649006587868272993991, + 190662397077989315056379725720120486231213267083935859751911720230901597698389839098903847] + """ + if f is None: + f = self.frobenius_polynomial() + + q = self.base_ring().cardinality() + S = PowerSeriesRing(QQ, default_prec=n + 1, names="t") + frev = f.reverse() + # the coefficients() method of power series only returns + # non-zero coefficients so let us use the list() method but + # this does not work for zero which gives the empty list + flog = S(frev).log() + return [q ** (i + 1) + 1 + ZZ((i + 1) * flog[i + 1]) for i in range(n)] + + def count_points_exhaustive(self, n=1, naive=False): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field by exhaustive search if `n` if smaller than `g`, + the genus of the curve, and by computing the frobenius polynomial + after performing exhaustive search on the first `g` extensions if + `n > g` (unless ``naive == True``). + + EXAMPLES:: + + sage: K = GF(5) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.count_points_exhaustive(n=5) + [9, 27, 108, 675, 3069] + + When `n > g`, the frobenius polynomial is computed from the numbers + of points of the curve over the first `g` extension, so that computing + the number of points on extensions of degree `n > g` is not much more + expensive than for `n == g`:: + + sage: H.count_points_exhaustive(n=15) + [9, + 27, + 108, + 675, + 3069, + 16302, + 78633, + 389475, + 1954044, + 9768627, + 48814533, + 244072650, + 1220693769, + 6103414827, + 30517927308] + + This behavior can be disabled by passing ``naive=True``:: + + sage: H.count_points_exhaustive(n=6, naive=True) # long time, 7s on a Corei7 + [9, 27, 108, 675, 3069, 16302] + """ + g = self.genus() + a = [ + self.cardinality_exhaustive(extension_degree=i) + for i in range(1, min(n, g) + 1) + ] + + if n <= g: + return a + + if naive: + a.extend( + [ + self.cardinality_exhaustive(extension_degree=i) + for i in range(g + 1, n + 1) + ] + ) + + # let's not be too naive and compute the frobenius polynomial + f = self.frobenius_polynomial_cardinalities(a=a) + return self.count_points_frobenius_polynomial(n=n, f=f) + + def count_points_hypellfrob(self, n=1, N=None, algorithm=None): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field using the ``hypellfrob`` program. + + This only supports prime fields of large enough characteristic. + + EXAMPLES:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^21 + 3*t^5 + 5) + sage: H.count_points_hypellfrob() + [49804] + sage: H.count_points_hypellfrob(2) + [49804, 2499799038] + + sage: K = GF(2**7-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^11 + 3*t^5 + 5) + sage: H.count_points_hypellfrob() + [127] + sage: H.count_points_hypellfrob(n=5) + [127, 16335, 2045701, 260134299, 33038098487] + + sage: K = GF(2**7-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^13 + 3*t^5 + 5) + sage: H.count_points(n=6) + [112, 16360, 2045356, 260199160, 33038302802, 4195868633548] + + The base field should be prime:: + + sage: K. = GF(19**10) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + (z+1)*t^5 + 1) + sage: H.count_points_hypellfrob() + Traceback (most recent call last): + ... + ValueError: hypellfrob does not support non-prime fields + + and the characteristic should be large enough:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.count_points_hypellfrob() + Traceback (most recent call last): + ... + ValueError: p=7 should be greater than (2*g+1)(2*N-1)=27 + """ + K = self.base_ring() + e = K.degree() + + if e != 1: + raise ValueError("hypellfrob does not support non-prime fields") + + # K is a prime field + p = K.cardinality() + g = self.genus() + + if algorithm is None: + if n < g: + algorithm = "traces" + else: + algorithm = "charpoly" + + if N is None: + if algorithm == "traces": + N = self._frobenius_coefficient_bound_traces(n) + elif algorithm == "charpoly": + N = self._frobenius_coefficient_bound_charpoly() + else: + raise ValueError("Unknown algorithm") + + if p <= (2 * g + 1) * (2 * N - 1): + raise ValueError( + "p=%d should be greater than (2*g+1)(2*N-1)=%d" + % (p, (2 * g + 1) * (2 * N - 1)) + ) + + if algorithm == "traces": + M = self.frobenius_matrix(N=N, algorithm="hypellfrob") + return self.count_points_matrix_traces(n=n, M=M, N=N) + elif algorithm == "charpoly": + f = self.frobenius_polynomial_matrix(algorithm="hypellfrob") + return self.count_points_frobenius_polynomial(n=n, f=f) + else: + raise ValueError("Unknown algorithm") + + def count_points(self, n=1): + r""" + Count points over finite fields. + + INPUT: + + - ``n`` -- integer. + + OUTPUT: + + An integer. The number of points over `\GF{q}, \ldots, + \GF{q^n}` on a hyperelliptic curve over a finite field `\GF{q}`. + + .. WARNING:: + + This is currently using exhaustive search for hyperelliptic curves + over non-prime fields, which can be awfully slow. + + EXAMPLES:: + + sage: P. = PolynomialRing(GF(3)) + sage: C = HyperellipticCurveSmoothModel(x^3+x^2+1) + sage: C.count_points(4) + [6, 12, 18, 96] + sage: C.base_extend(GF(9,'a')).count_points(2) + [12, 96] + + sage: K = GF(2**31-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + 3*t + 5) + sage: H.count_points() # long time, 2.4 sec on a Corei7 + [2147464821] + sage: H.count_points(n=2) # long time, 30s on a Corei7 + [2147464821, 4611686018988310237] + + sage: K = GF(2**7-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^13 + 3*t^5 + 5) + sage: H.count_points(n=6) + [112, 16360, 2045356, 260199160, 33038302802, 4195868633548] + + sage: P. = PolynomialRing(GF(3)) + sage: H = HyperellipticCurveSmoothModel(x^3+x^2+1) + sage: C1 = H.count_points(4); C1 + [6, 12, 18, 96] + sage: C2 = sage.schemes.generic.scheme.Scheme.count_points(H,4); C2 # long time, 2s on a Corei7 + [6, 12, 18, 96] + sage: C1 == C2 # long time, because we need C2 to be defined + True + + sage: P. = PolynomialRing(GF(9,'a')) + sage: H = HyperellipticCurveSmoothModel(x^5+x^2+1) + sage: H.count_points(5) + [18, 78, 738, 6366, 60018] + + sage: F. = GF(4); P. = F[] + sage: H = HyperellipticCurveSmoothModel(x^5+a*x^2+1, x+a+1) + sage: H.count_points(6) + [2, 24, 74, 256, 1082, 4272] + + This example shows that :issue:`20391` is resolved:: + + sage: x = polygen(GF(4099)) + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: H.count_points(1) + [4106] + """ + K = self.base_ring() + q = K.cardinality() + e = K.degree() + g = self.genus() + f, h = self.hyperelliptic_polynomials() + + if e == 1 and h == 0 and f.degree() % 2 == 1: + N1 = self._frobenius_coefficient_bound_traces(n) + N2 = self._frobenius_coefficient_bound_charpoly() + if n < g and q > (2 * g + 1) * (2 * N1 - 1): + return self.count_points_hypellfrob(n, N=N1, algorithm="traces") + elif q > (2 * g + 1) * (2 * N2 - 1): + return self.count_points_hypellfrob(n, N=N2, algorithm="charpoly") + + # No smart method available + return self.count_points_exhaustive(n) + + def cardinality_exhaustive(self, extension_degree=1, algorithm=None): + r""" + Count points on a single extension of the base field + by enumerating over x and solving the resulting quadratic + equation for y. + + EXAMPLES:: + + sage: K. = GF(9, 'a') + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, x^2 + a) + sage: C.cardinality_exhaustive() + 7 + + sage: K = GF(next_prime(1<<10)) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 3*t^5 + 5) + sage: H.cardinality_exhaustive() + 1025 + + sage: P. = PolynomialRing(GF(9,'a')) + sage: H = HyperellipticCurveSmoothModel(x^5+x^2+1) + sage: H.count_points(5) + [18, 78, 738, 6366, 60018] + + sage: F. = GF(4); P. = F[] + sage: H = HyperellipticCurveSmoothModel(x^5+a*x^2+1, x+a+1) + sage: H.count_points(6) + [2, 24, 74, 256, 1082, 4272] + + TESTS: + + Check for :issue:`19122`:: + + sage: x = polygen(GF(19), 'x') + sage: f = 15*x^4 + 7*x^3 + 3*x^2 + 7*x + 18 + sage: HyperellipticCurveSmoothModel(f).cardinality_exhaustive(1) + 19 + + Points at infinity on general curves of genus 1 are counted + correctly (see :issue:`21195`):: + + sage: S. = PolynomialRing(QQ) + sage: C = HyperellipticCurveSmoothModel(-z^2 + z, z^2) + sage: C.base_extend(GF(2)).count_points_exhaustive() + [5] + sage: C.base_extend(GF(3)).count_points_exhaustive() + [5] + """ + K = self.base_ring() + g = self.genus() + n = extension_degree + + if g == 0: + # here is the projective line + return K.cardinality() ** n + 1 + + f, h = self.hyperelliptic_polynomials() + a = 0 + + if n == 1: + # the base field + L = K + fext = f + hext = h + else: + # extension of the base field + from sage.categories.homset import Hom + + L = GF(K.cardinality() ** n, names="z") + P = L["t"] + emb = Hom(K, L)[0] + fext = P([emb(c) for c in f]) + hext = P([emb(c) for c in h]) + + # We solve equations of the form y^2 + r*y - s == 0. + # For the points at infinity (on the smooth model), + # solve y^2 + h[g+1]*y == f[2*g+2]. + # For the affine points with given x-coordinate, + # solve y^2 + h(x)*y == f(x). + + if K.characteristic() == 2: + # points at infinity + r = h[g + 1] + if not r: + a += 1 + elif n % 2 == 0 or (f[2 * g + 2] / r**2).trace() == 0: + # Artin-Schreier equation t^2 + t = s/r^2 + # always has a solution in extensions of even degree + a += 2 + # affine points + for x in L: + r = hext(x) + if not r: + a += 1 + elif (fext(x) / r**2).trace() == 0: + a += 2 + else: + # points at infinity + d = h[g + 1] ** 2 + 4 * f[2 * g + 2] + if not d: + a += 1 + elif n % 2 == 0 or d.is_square(): + a += 2 + # affine points + for x in L: + d = hext(x) ** 2 + 4 * fext(x) + if not d: + a += 1 + elif d.is_square(): + a += 2 + + return a + + def cardinality_hypellfrob(self, extension_degree=1, algorithm=None): + r""" + Count points on a single extension of the base field + using the ``hypellfrob`` program. + + EXAMPLES:: + + sage: K = GF(next_prime(1<<10)) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 3*t^5 + 5) + sage: H.cardinality_hypellfrob() + 1025 + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 3*t^5 + 5) + sage: H.cardinality_hypellfrob() + 50162 + sage: H.cardinality_hypellfrob(3) + 124992471088310 + """ + # the following actually computes the cardinality for several extensions + # but the overhead is negligible + return self.count_points_hypellfrob(n=extension_degree, algorithm=algorithm)[-1] + + @cached_method + def cardinality(self, extension_degree=1): + r""" + Count points on a single extension of the base field. + + EXAMPLES:: + + sage: K = GF(101) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + 3*t^5 + 5) + sage: H.cardinality() + 106 + sage: H.cardinality(15) + 1160968955369992567076405831000 + sage: H.cardinality(100) + 270481382942152609326719471080753083367793838278100277689020104911710151430673927943945601434674459120495370826289654897190781715493352266982697064575800553229661690000887425442240414673923744999504000 + + sage: K = GF(37) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + 3*t^5 + 5) + sage: H.cardinality() + 40 + sage: H.cardinality(2) + 1408 + sage: H.cardinality(3) + 50116 + + The following example shows that :issue:`20391` has been resolved:: + + sage: F=GF(23) + sage: x=polygen(F) + sage: C=HyperellipticCurveSmoothModel(x^8+1) + sage: C.cardinality() + 24 + """ + K = self.base_ring() + q = K.cardinality() + e = K.degree() + g = self.genus() + f, h = self.hyperelliptic_polynomials() + n = extension_degree + + # We may: + # - check for actual field of definition of the curve (up to isomorphism) + if e == 1 and h == 0 and f.degree() % 2 == 1: + N1 = self._frobenius_coefficient_bound_traces(n) + N2 = self._frobenius_coefficient_bound_charpoly() + if n < g and q > (2 * g + 1) * (2 * N1 - 1): + return self.cardinality_hypellfrob(n, algorithm="traces") + elif q > (2 * g + 1) * (2 * N2 - 1): + return self.cardinality_hypellfrob(n, algorithm="charpoly") + + # No smart method available + return self.cardinality_exhaustive(n) + + # ------------------------------------------- + # Frobenius Computations + # ------------------------------------------- + + def zeta_function(self): + r""" + Compute the zeta function of the hyperelliptic curve. + + EXAMPLES:: + + sage: F = GF(2); R. = F[] + sage: H = HyperellipticCurveSmoothModel(t^9 + t, t^4) + sage: H.zeta_function() + (16*x^8 + 8*x^7 + 8*x^6 + 4*x^5 + 6*x^4 + 2*x^3 + 2*x^2 + x + 1)/(2*x^2 - 3*x + 1) + + sage: F. = GF(4); R. = F[] + sage: H = HyperellipticCurveSmoothModel(t^5 + t^3 + t^2 + t + 1, t^2 + t + 1) + sage: H.zeta_function() + (16*x^4 + 8*x^3 + x^2 + 2*x + 1)/(4*x^2 - 5*x + 1) + + sage: F. = GF(9); R. = F[] + sage: H = HyperellipticCurveSmoothModel(t^5 + a*t) + sage: H.zeta_function() + (81*x^4 + 72*x^3 + 32*x^2 + 8*x + 1)/(9*x^2 - 10*x + 1) + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.zeta_function() + (1369*x^4 + 37*x^3 - 52*x^2 + x + 1)/(37*x^2 - 38*x + 1) + + A quadratic twist:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.zeta_function() + (1369*x^4 - 37*x^3 - 52*x^2 - x + 1)/(37*x^2 - 38*x + 1) + """ + q = self.base_ring().cardinality() + P = self.frobenius_polynomial() + x = P.parent().gen(0) + return P.reverse() / ((1 - x) * (1 - q * x)) + + def _frobenius_coefficient_bound_charpoly(self): + r""" + Computes bound on number of `p`-adic digits needed to recover + frobenius polynomial computing the characteristic polynomial + of the frobenius matrix, i.e. returns `B` so that knowledge of + `a_1`, ..., `a_g` modulo `p^B` determine frobenius polynomial + uniquely. + + The bound used here stems from the expression of the coefficients + of the characteristic polynomial of the Frobenius as sums + of products of its eigenvalues: + + .. MATH:: + + \| a_i \| \leq \binom{2g}{i} \sqrt{q}^i + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_charpoly() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_charpoly() + 2 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_charpoly() + 3 + + sage: R. = PolynomialRing(GF(next_prime(10^9))) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_charpoly() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_charpoly() + 2 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_charpoly() + 2 + sage: HyperellipticCurveSmoothModel(t^9 + t + 1)._frobenius_coefficient_bound_charpoly() + 3 + sage: HyperellipticCurveSmoothModel(t^11 + t + 1)._frobenius_coefficient_bound_charpoly() + 3 + sage: HyperellipticCurveSmoothModel(t^13 + t + 1)._frobenius_coefficient_bound_charpoly() + 4 + """ + assert self.base_ring().is_finite() + p = self.base_ring().characteristic() + q = self.base_ring().order() + sqrtq = RR(q).sqrt() + g = self.genus() + + # note: this bound is from Kedlaya's paper, but he tells me it's not + # the best possible + M = 2 * binomial(2 * g, g) * sqrtq**g + B = ZZ(M.ceil()).exact_log(p) + if p**B < M: + B += 1 + return B + + def _frobenius_coefficient_bound_traces(self, n=1): + r""" + Computes bound on number of `p`-adic digits needed to recover + the number of rational points on `n` extensions computing + traces of the frobenius matrix powers, i.e. returns `B` so that + knowledge of `\tr(M^1)`, ..., `\tr(M^n)` modulo `p^B` determine + `N_1`, ..., `N_n` uniquely. + + The formula stems from the expression of the trace of the Frobenius + as a sum of `i`-th powers of its eigenvalues. + + .. MATH:: + + \| \tr(M^i) \| \leq 2 g \sqrt{q}^i + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_traces() + 2 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_traces() + 2 + + sage: R. = PolynomialRing(GF(next_prime(10^9))) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^9 + t + 1)._frobenius_coefficient_bound_traces(n=3) + 2 + sage: HyperellipticCurveSmoothModel(t^11 + t + 1)._frobenius_coefficient_bound_traces(n=3) + 2 + sage: HyperellipticCurveSmoothModel(t^13 + t + 1)._frobenius_coefficient_bound_traces(n=5) + 3 + + sage: R. = PolynomialRing(GF(11)) + sage: H = HyperellipticCurveSmoothModel(t^5 - t + 1) + sage: H._frobenius_coefficient_bound_traces() + 2 + """ + p = self.base_ring().characteristic() + q = self.base_ring().order() + sqrtq = RR(q).sqrt() + g = self.genus() + + M = 4 * g * sqrtq**n + B = ZZ(M.ceil()).exact_log(p) + if p**B < M: + B += 1 + return B + + def frobenius_matrix_hypellfrob(self, N=None): + r""" + Compute `p`-adic frobenius matrix to precision `p^N`. + If `N` not supplied, a default value is selected, which is the + minimum needed to recover the charpoly unambiguously. + + .. note:: + + Implemented using ``hypellfrob``, which means it only works + over the prime field `GF(p)`, and requires `p > (2g+1)(2N-1)`. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_matrix_hypellfrob() + [1258 + O(37^2) 925 + O(37^2) 132 + O(37^2) 587 + O(37^2)] + [1147 + O(37^2) 814 + O(37^2) 241 + O(37^2) 1011 + O(37^2)] + [1258 + O(37^2) 1184 + O(37^2) 1105 + O(37^2) 482 + O(37^2)] + [1073 + O(37^2) 999 + O(37^2) 772 + O(37^2) 929 + O(37^2)] + + The ``hypellfrob`` program doesn't support non-prime fields:: + + sage: K. = GF(37**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + z*t^3 + 1) + sage: H.frobenius_matrix_hypellfrob() + Traceback (most recent call last): + ... + NotImplementedError: Computation of Frobenius matrix only implemented + for hyperelliptic curves defined over prime fields. + + nor too small characteristic:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.frobenius_matrix_hypellfrob() + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 81 + """ + p = self.base_ring().characteristic() + e = self.base_ring().degree() + if e != 1: + raise NotImplementedError( + "Computation of Frobenius matrix only implemented for hyperelliptic curves defined over prime fields." + ) + + f, h = self.hyperelliptic_polynomials() + if h != 0: + # need y^2 = f(x) + raise NotImplementedError("only implemented for curves y^2 = f(x)") + + sign = 1 + if not f.is_monic(): + # at this time we need a monic f + c = f.leading_coefficient() + f = f / c + if c.is_square(): + # solutions of $y^2 = c * f(x)$ correspond naturally to + # solutions of $(sqrt(c) y)^2 = f(x)$ + pass + else: + # we'll count points on a twist and then correct by untwisting... + sign = -1 + assert f.is_monic() + + # By default, use precision enough to be able to compute the + # frobenius minimal polynomial + if N is None: + N = self._frobenius_coefficient_bound_charpoly() + + matrix_of_frobenius = hypellfrob(p, N, f) + matrix_of_frobenius = sign * matrix_of_frobenius + return matrix_of_frobenius + + def frobenius_matrix(self, N=None, algorithm="hypellfrob"): + r""" + Compute `p`-adic frobenius matrix to precision `p^N`. + If `N` not supplied, a default value is selected, which is the + minimum needed to recover the charpoly unambiguously. + + .. note:: + + Currently only implemented using ``hypellfrob``, + which means it only works over the prime field `GF(p)`, + and requires `p > (2g+1)(2N-1)`. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_matrix() + [1258 + O(37^2) 925 + O(37^2) 132 + O(37^2) 587 + O(37^2)] + [1147 + O(37^2) 814 + O(37^2) 241 + O(37^2) 1011 + O(37^2)] + [1258 + O(37^2) 1184 + O(37^2) 1105 + O(37^2) 482 + O(37^2)] + [1073 + O(37^2) 999 + O(37^2) 772 + O(37^2) 929 + O(37^2)] + + The ``hypellfrob`` program doesn't support non-prime fields:: + + sage: K. = GF(37**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + z*t^3 + 1) + sage: H.frobenius_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + NotImplementedError: Computation of Frobenius matrix only implemented + for hyperelliptic curves defined over prime fields. + + nor too small characteristic:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.frobenius_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 81 + """ + if algorithm != "hypellfrob": + raise ValueError("Unknown algorithm") + + # By default, use precision enough to be able to compute the + # frobenius minimal polynomial + if N is None: + N = self._frobenius_coefficient_bound_charpoly() + + return self.frobenius_matrix_hypellfrob(N=N) + + def frobenius_polynomial_cardinalities(self, a=None): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by computing the number of points on the curve over `g` extensions + of the base field where `g` is the genus of the curve. + + .. WARNING:: + + This is highly inefficient when the base field or the genus of the + curve are large. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial_cardinalities() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial_cardinalities() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Curve over a non-prime field:: + + sage: K. = GF(7**2) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + z*t + z^2) + sage: H.frobenius_polynomial_cardinalities() + x^4 + 8*x^3 + 70*x^2 + 392*x + 2401 + + This method may actually be useful when ``hypellfrob`` does not work:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.frobenius_polynomial_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 81 + sage: H.frobenius_polynomial_cardinalities() + x^8 - 5*x^7 + 7*x^6 + 36*x^5 - 180*x^4 + 252*x^3 + 343*x^2 - 1715*x + 2401 + """ + g = self.genus() + q = self.base_ring().cardinality() + + if a is None: + # this may actually call frobenius_polynomial() + a = self.count_points(g) + # maybe calling count_points_exhaustive() would make more sense + # but the method is currently only called with a precomputed list + # of number of points so it does not really matter + + # computation of the reciprocal polynomial + s = [ai - q ** (i + 1) - 1 for i, ai in enumerate(a)] + coeffs = [1] + for i in range(1, g + 1): + c = 0 + for j in range(i): + c += s[i - 1 - j] * coeffs[j] + coeffs.append(c / i) + coeffs = coeffs + [coeffs[g - i] * q ** (i) for i in range(1, g + 1)] + + return ZZ["x"](coeffs).reverse() + + def frobenius_polynomial_matrix(self, M=None, algorithm="hypellfrob"): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by computing the charpoly of the frobenius matrix. + + This is currently only supported when the base field is prime + and large enough using the ``hypellfrob`` library. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial_matrix() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial_matrix() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Curves defined over larger prime fields:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^5 + 1) + sage: H.frobenius_polynomial_matrix() + x^8 + 281*x^7 + 55939*x^6 + 14144175*x^5 + 3156455369*x^4 + 707194605825*x^3 + + 139841906155939*x^2 + 35122892542149719*x + 6249500014999800001 + sage: H = HyperellipticCurveSmoothModel(t^15 + t^5 + 1) + sage: H.frobenius_polynomial_matrix() # long time, 8s on a Corei7 + x^14 - 76*x^13 + 220846*x^12 - 12984372*x^11 + 24374326657*x^10 - 1203243210304*x^9 + + 1770558798515792*x^8 - 74401511415210496*x^7 + 88526169366991084208*x^6 + - 3007987702642212810304*x^5 + 3046608028331197124223343*x^4 + - 81145833008762983138584372*x^3 + 69007473838551978905211279154*x^2 + - 1187357507124810002849977200076*x + 781140631562281254374947500349999 + + This ``hypellfrob`` program doesn't support non-prime fields:: + + sage: K. = GF(37**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + z*t^3 + 1) + sage: H.frobenius_polynomial_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + NotImplementedError: Computation of Frobenius matrix only implemented + for hyperelliptic curves defined over prime fields. + """ + K = self.base_ring() + p = K.characteristic() + q = K.cardinality() + g = self.genus() + N = self._frobenius_coefficient_bound_charpoly() + # compute charpoly over ZZ and then reduce back + # (because charpoly of p-adic matrices sometimes loses precision) + M = self.frobenius_matrix(N=N, algorithm=algorithm).change_ring(ZZ) + + # get a_g, ..., a_0 in ZZ (i.e. with correct signs) + f = M.charpoly().list()[g : 2 * g + 1] + ppow = p**N + f = [x % ppow for x in f] + f = [x if 2 * x < ppow else x - ppow for x in f] + + # get a_{2g}, ..., a_{g+1} + f = [f[g - i] * q ** (g - i) for i in range(g)] + f + + return ZZ["x"](f) + + def frobenius_polynomial_pari(self): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by calling the PARI function ``hyperellcharpoly``. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial_pari() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial_pari() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Slightly larger example:: + + sage: K = GF(2003) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 487*t^5 + 9*t + 1) + sage: H.frobenius_polynomial_pari() + x^6 - 14*x^5 + 1512*x^4 - 66290*x^3 + 3028536*x^2 - 56168126*x + 8036054027 + + Curves defined over a non-prime field are supported as well:: + + sage: K. = GF(7^2) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + a*t + 1) + sage: H.frobenius_polynomial_pari() + x^4 + 4*x^3 + 84*x^2 + 196*x + 2401 + + sage: K. = GF(23**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^3 + z*t + 4) + sage: H.frobenius_polynomial_pari() + x^2 - 15*x + 12167 + + Over prime fields of odd characteristic, `h` may be non-zero:: + + sage: K = GF(101) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + 27*t + 3, t) + sage: H.frobenius_polynomial_pari() + x^4 + 2*x^3 - 58*x^2 + 202*x + 10201 + + TESTS: + + Check that :issue:`28789` is fixed:: + + sage: P. = PolynomialRing(GF(3)) + sage: u = x^10 + x^9 + x^8 + x + sage: C = HyperellipticCurveSmoothModel(u) + sage: C.frobenius_polynomial_pari() + x^8 + 2*x^7 + 6*x^6 + 9*x^5 + 18*x^4 + 27*x^3 + 54*x^2 + 54*x + 81 + """ + f, h = self.hyperelliptic_polynomials() + return ZZ["x"](pari([f, h]).hyperellcharpoly()) + + @cached_method + def frobenius_polynomial(self): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Slightly larger example:: + + sage: K = GF(2003) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 487*t^5 + 9*t + 1) + sage: H.frobenius_polynomial() + x^6 - 14*x^5 + 1512*x^4 - 66290*x^3 + 3028536*x^2 - 56168126*x + 8036054027 + + Curves defined over a non-prime field of odd characteristic, + or an odd prime field which is too small compared to the genus, + are supported via PARI:: + + sage: K. = GF(23**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^3 + z*t + 4) + sage: H.frobenius_polynomial() + x^2 - 15*x + 12167 + + sage: K. = GF(3**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + z*t + z**3) + sage: H.frobenius_polynomial() + x^4 - 3*x^3 + 10*x^2 - 81*x + 729 + + Over prime fields of odd characteristic, `h` may be non-zero:: + + sage: K = GF(101) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + 27*t + 3, t) + sage: H.frobenius_polynomial() + x^4 + 2*x^3 - 58*x^2 + 202*x + 10201 + + Over prime fields of odd characteristic, `f` may have even degree:: + + sage: H = HyperellipticCurveSmoothModel(t^6 + 27*t + 3) + sage: H.frobenius_polynomial() + x^4 + 25*x^3 + 322*x^2 + 2525*x + 10201 + + In even characteristic, the naive algorithm could cover all cases + because we can easily check for squareness in quotient rings of + polynomial rings over finite fields but these rings unfortunately + do not support iteration:: + + sage: K. = GF(2**5) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + z*t + z**3, t) + sage: H.frobenius_polynomial() + x^4 - x^3 + 16*x^2 - 32*x + 1024 + + TESTS: + + Check that :issue:`28789` is fixed:: + + sage: P. = PolynomialRing(GF(3)) + sage: u = x^10 + x^9 + x^8 + x + sage: C = HyperellipticCurveSmoothModel(u) + sage: C.frobenius_polynomial() + x^8 + 2*x^7 + 6*x^6 + 9*x^5 + 18*x^4 + 27*x^3 + 54*x^2 + 54*x + 81 + """ + K = self.base_ring() + e = K.degree() + q = K.cardinality() + + g = self.genus() + f, h = self.hyperelliptic_polynomials() + + if ( + e == 1 + and q + >= (2 * g + 1) * (2 * self._frobenius_coefficient_bound_charpoly() - 1) + and h == 0 + and f.degree() % 2 + ): + return self.frobenius_polynomial_matrix() + elif q % 2 == 1: + return self.frobenius_polynomial_pari() + else: + return self.frobenius_polynomial_cardinalities() + + # This where Cartier Matrix is actually computed. This is either called by + # E.Cartier_matrix, E.a_number, or E.Hasse_Witt. + @cached_method + def _Cartier_matrix_cached(self): + r""" + INPUT: + + - 'E' - Hyperelliptic Curve of the form `y^2 = f(x)` over a + finite field, `\GF{q}` + + OUTPUT: + + - matrix(Fq,M)' The matrix `M = (c_(pi-j)), f(x)^((p-1)/2) = \sum c_i x^i` + - 'Coeff' List of Coeffs of F, this is needed for Hasse-Witt function. + - 'g' genus of the curve self, this is needed by a-number. + - 'Fq' is the base field of self, and it is needed for Hasse-Witt + - 'p' is the char(Fq), this is needed for Hasse-Witt. + - 'E' The initial elliptic curve to check some caching conditions. + + EXAMPLES:: + + sage: K.=GF(9,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,0) + sage: C._Cartier_matrix_cached() + ( + [0 0 2] + [0 0 0] + [0 1 0], [2, 0, 0, 0, 0, 0, 0, 1, 0], 3, Finite Field in x of size 3^2, 3, Hyperelliptic Curve over Finite Field in x of size 3^2 defined by y^2 = x^7 + 2 + ) + sage: K.=GF(49,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^5+1,0) + sage: C._Cartier_matrix_cached() + ( + [0 3] + [0 0], [1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1], 2, Finite Field in x of size 7^2, 7, Hyperelliptic Curve over Finite Field in x of size 7^2 defined by y^2 = x^5 + 1 + ) + + sage: P.=GF(9,'a')[] + sage: C=HyperellipticCurveSmoothModel(x^29+1,0) + sage: C._Cartier_matrix_cached() + ( + [0 0 1 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 1 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 1 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [1 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 1 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 1 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 1 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 14, Finite Field in a of size 3^2, 3, Hyperelliptic Curve over Finite Field in a of size 3^2 defined by y^2 = x^29 + 1 + ) + + TESTS:: + + sage: K.=GF(2,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,x) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: p must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,4) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: E must be of the form y^2 = f(x) + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^8-1,0) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: In this implementation the degree of f must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^5+1,0,check_squarefree=False) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: curve is not smooth + """ + # Compute the finite field and prime p. + Fq = self.base_ring() + p = Fq.characteristic() + # checks + + if p == 2: + raise ValueError("p must be odd") + + g = self.genus() + + # retrieve the function f(x) ,where y^2=f(x) + f, h = self.hyperelliptic_polynomials() + # This implementation only deals with h=0 + if h != 0: + raise ValueError("E must be of the form y^2 = f(x)") + + d = f.degree() + # this implementation is for odd degree only, even degree will be handled later. + if d % 2 == 0: + raise ValueError("In this implementation the degree of f must be odd") + # Compute resultant to make sure no repeated roots + df = f.derivative() + R = df.resultant(f) + if R == 0: + raise ValueError("curve is not smooth") + + # computing F, since the entries of the matrix are c_i where F= \sum c_i x^i + + F = f ** ((p - 1) / 2) + + # coefficients returns a_0, ... , a_n where f(x) = a_n x^n + ... + a_0 + + Coeff = F.list() + + # inserting zeros when necessary-- that is, when deg(F) < p*g-1, (simplified if p <2g-1) + # which is the highest powered coefficient needed for our matrix + # So we don't have enough coefficients we add extra zeros to have the same poly, + # but enough coeff. + + zeros = [0 for i in range(p * g - len(Coeff))] + Coeff = Coeff + zeros + + # compute each row of matrix as list and then M=list of lists(rows) + + M = [] + for j in range(1, g + 1): + H = [Coeff[i] for i in range((p * j - 1), (p * j - g - 1), -1)] + M.append(H) + return matrix(Fq, M), Coeff, g, Fq, p, self + + # This is what is called from command line + def Cartier_matrix(self): + r""" + INPUT: + + - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``M``: The matrix `M = (c_{pi-j})`, where `c_i` are the coefficients of `f(x)^{(p-1)/2} = \sum c_i x^i` + + REFERENCES: + + N. Yui. On the Jacobian varieties of hyperelliptic curves over fields of characteristic `p > 2`. + + EXAMPLES:: + + sage: K. = GF(9,'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, 0) + sage: C.Cartier_matrix() + [0 0 2] + [0 0 0] + [0 1 0] + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1, 0) + sage: C.Cartier_matrix() + [0 3] + [0 0] + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1, 0) + sage: E.Cartier_matrix() + [0 0 1 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 1 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 1 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [1 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 1 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 1 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 1 0] + + TESTS:: + + sage: K.=GF(2,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,x) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: p must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,4) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: E must be of the form y^2 = f(x) + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^8-1,0) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: In this implementation the degree of f must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^5+1,0,check_squarefree=False) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: curve is not smooth + """ + # checking first that Cartier matrix is not already cached. Since + # it can be called by either Hasse_Witt or a_number. + # This way it does not matter which function is called first + # in the code. + # Github Issue #11115: Why shall we waste time by studying + # the cache manually? We only need to check whether the cached + # data belong to self. + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: + self._Cartier_matrix_cached.clear_cache() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + return M + + @cached_method + def _Hasse_Witt_cached(self): + r""" + This is where Hasse_Witt is actually computed. + + This is either called by E.Hasse_Witt or E.p_rank. + + INPUT: + + - ``E`` -- hyperelliptic Curve of the form `y^2 = f(x)` over + a finite field, `\GF{q}` + + OUTPUT: + + - ``N`` -- the matrix `N = M M^p \dots M^{p^{g-1}}` where + `M = c_{pi-j}, f(x)^{(p-1)/2} = \sum c_i x^i` + + - ``E`` -- the initial curve to check some caching conditions + + EXAMPLES:: + + sage: K. = GF(9,'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7-1,0) + sage: C._Hasse_Witt_cached() + ( + [0 0 0] + [0 0 0] + [0 0 0], Hyperelliptic Curve over Finite Field in x of size 3^2 defined by y^2 = x^7 + 2 + ) + + sage: K. = GF(49,'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5+1,0) + sage: C._Hasse_Witt_cached() + ( + [0 0] + [0 0], Hyperelliptic Curve over Finite Field in x of size 7^2 defined by y^2 = x^5 + 1 + ) + + sage: P. = GF(9,'a')[] + sage: C = HyperellipticCurveSmoothModel(x^29+1,0) + sage: C._Hasse_Witt_cached() + ( + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0], Hyperelliptic Curve over Finite Field in a of size 3^2 defined by y^2 = x^29 + 1 + ) + + TESTS: + + This shows that the bug at :issue:`23181` is fixed:: + + sage: K. = PolynomialRing(GF(5)) + sage: L. = GF(5).extension(z^3+3*z+3,'a') + sage: H. = L[] + sage: E = HyperellipticCurveSmoothModel(x^5+x^4+a^92*x^3+a^18*x^2+a^56*x,0) + sage: E.p_rank() + 0 + """ + # If Cartier Matrix is already cached for this curve, use that or evaluate it to get M, + # Coeffs, genus, Fq=base field of self, p=char(Fq). This is so we have one less matrix to + # compute. + + # We use caching here since Cartier matrix is needed to compute Hasse Witt. So if the Cartier + # is already computed it is stored in list A. If it was not cached (i.e. A is empty), we simply + # compute it. If it is cached then we need to make sure that we have the correct one. So check + # which curve does the matrix correspond to. Since caching stores a lot of stuff, we only check + # the last entry in A. If it does not match, clear A and compute Cartier. + # + # Since Github Issue #11115, there is a different cache for methods + # that don't accept arguments. Anyway, the easiest is to call + # the cached method and simply see whether the data belong to self. + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: + self._Cartier_matrix_cached.clear_cache() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + + # This compute the action of p^kth Frobenius on list of coefficients + def frob_mat(Coeffs, k): + a = p**k + mat = [] + Coeffs_pow = [c**a for c in Coeffs] + for i in range(1, g + 1): + H = [(Coeffs_pow[j]) for j in range((p * i - 1), (p * i - g - 1), -1)] + mat.append(H) + return matrix(Fq, mat) + + # Computes all the different possible action of frobenius on matrix M and stores in list Mall + Mall = [M] + [frob_mat(Coeffs, k) for k in range(1, g)] + Mall = reversed(Mall) + # initial N=I, so we can go through Mall and multiply all matrices with I and + # get the Hasse-Witt matrix. + N = identity_matrix(Fq, g) + for l in Mall: + N = N * l + return N, E + + # This is the function which is actually called by command line + def Hasse_Witt(self): + r""" + INPUT: + + - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``N`` : The matrix `N = M M^p \dots M^{p^{g-1}}` where `M = c_{pi-j}`, and `f(x)^{(p-1)/2} = \sum c_i x^i` + + + Reference-N. Yui. On the Jacobian varieties of hyperelliptic curves over fields of characteristic `p > 2`. + + EXAMPLES:: + + sage: K. = GF(9, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, 0) + sage: C.Hasse_Witt() + [0 0 0] + [0 0 0] + [0 0 0] + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1, 0) + sage: C.Hasse_Witt() + [0 0] + [0 0] + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1, 0) + sage: E.Hasse_Witt() + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + """ + # Since Github Issue #11115, there is a special + # type of cached for those methods that don't + # accept arguments. We want to get Hasse-Witt + # from the cache - but apparently it could be + # that the cached value does not belong to self. + # So, the easiest is: + N, E = self._Hasse_Witt_cached() + if E != self: + self._Hasse_Witt_cached.clear_cache() + N, E = self._Hasse_Witt_cached() + return N + + def a_number(self): + r""" + INPUT: + + - ``E``: Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``a`` : a-number + + + EXAMPLES:: + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1, 0) + sage: C.a_number() + 1 + + sage: K. = GF(9, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, 0) + sage: C.a_number() + 1 + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1, 0) + sage: E.a_number() + 5 + """ + # We use caching here since Cartier matrix is needed to compute a_number. So if the Cartier + # is already computed it is stored in list A. If it was not cached (i.e. A is empty), we simply + # compute it. If it is cached then we need to make sure that we have the correct one. So check + # which curve does the matrix correspond to. Since caching stores a lot of stuff, we only check + # the last entry in A. If it does not match, clear A and compute Cartier. + # + # Since Github Issue #11115, there is a special cache for methods + # that don't accept arguments. The easiest is: Call the cached + # method, and test whether the last entry is self. + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: + self._Cartier_matrix_cached.clear_cache() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + return g - rank(M) + + def p_rank(self): + r""" + INPUT: + + - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``pr`` :p-rank + + EXAMPLES:: + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1) + sage: C.p_rank() + 0 + + sage: K. = GF(9, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1) + sage: C.p_rank() + 0 + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1) + sage: E.p_rank() + 0 + """ + # We use caching here since Hasse Witt is needed to compute p_rank. So if the Hasse Witt + # is already computed it is stored in list A. If it was not cached (i.e. A is empty), we simply + # compute it. If it is cached then we need to make sure that we have the correct one. So check + # which curve does the matrix correspond to. Since caching stores a lot of stuff, we only check + # the last entry in A. If it does not match, clear A and compute Hasse Witt. + # However, it seems a waste of time to manually analyse the cache + # -- See Github Issue #11115 + N, E = self._Hasse_Witt_cached() + if E != self: + self._Hasse_Witt_cached.clear_cache() + N, E = self._Hasse_Witt_cached() + return rank(N) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py new file mode 100644 index 00000000000..eecd79b58f5 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -0,0 +1,192 @@ +from sage.misc.cachefunc import cached_method +from sage.schemes.hyperelliptic_curves_smooth_model import ( + hyperelliptic_generic, + invariants, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_finite_field import ( + HyperellipticCurveSmoothModel_finite_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_padic_field import ( + HyperellipticCurveSmoothModel_padic_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_rational_field import ( + HyperellipticCurveSmoothModel_rational_field, +) + +""" +TODO List + +### Invariants + +There seems to be massive redundancy in having these methods and the ones imported into +invariants. I think we should fix this by putting the methods themseleves into this class. +""" + + +class HyperellipticCurveSmoothModel_g2( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + def is_odd_degree(self): + """ + Return ``True`` if the curve is an odd degree model. + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = x^5 - x^4 + 3 + sage: HyperellipticCurveSmoothModel(f).is_odd_degree() + True + """ + f, h = self.hyperelliptic_polynomials() + df = f.degree() + if h.degree() < 3: + return df % 2 == 1 + elif df < 6: + return False + else: + a0 = f.leading_coefficient() + c0 = h.leading_coefficient() + return (c0**2 + 4 * a0) == 0 + + @cached_method + def jacobian(self): + from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_g2_generic import ( + HyperellipticJacobian_g2_generic, + ) + + return HyperellipticJacobian_g2_generic(self) + + # ----------------------------------- + # Genus Two invariant computations + # + # TODO: should we move logic from functions in `invariants()` + # into this file rather than import them + # ----------------------------------- + + def clebsch_invariants(self): + r""" + Return the Clebsch invariants `(A, B, C, D)` of Mestre, p 317, [Mes1991]_. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves.invariants` + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = x^5 - x^4 + 3 + sage: HyperellipticCurveSmoothModel(f).clebsch_invariants() + (0, -2048/375, -4096/25, -4881645568/84375) + sage: HyperellipticCurveSmoothModel(f(2*x)).clebsch_invariants() + (0, -8388608/375, -1073741824/25, -5241627016305836032/84375) + + sage: HyperellipticCurveSmoothModel(f, x).clebsch_invariants() + (-8/15, 17504/5625, -23162896/140625, -420832861216768/7119140625) + sage: HyperellipticCurveSmoothModel(f(2*x), 2*x).clebsch_invariants() + (-512/15, 71696384/5625, -6072014209024/140625, -451865844002031331704832/7119140625) + + TESTS:: + + sage: # optional - magma + sage: magma(HyperellipticCurveSmoothModel(f)).ClebschInvariants() + [ 0, -2048/375, -4096/25, -4881645568/84375 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x))).ClebschInvariants() + [ 0, -8388608/375, -1073741824/25, -5241627016305836032/84375 ] + sage: magma(HyperellipticCurveSmoothModel(f, x)).ClebschInvariants() + [ -8/15, 17504/5625, -23162896/140625, -420832861216768/7119140625 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x), 2*x)).ClebschInvariants() + [ -512/15, 71696384/5625, -6072014209024/140625, -451865844002031331704832/7119140625 ] + """ + f, h = self.hyperelliptic_polynomials() + return invariants.clebsch_invariants(4 * f + h**2) + + def igusa_clebsch_invariants(self): + r""" + Return the Igusa-Clebsch invariants `I_2, I_4, I_6, I_{10}` of Igusa and Clebsch [IJ1960]_. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves.invariants` + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = x^5 - x + 2 + sage: HyperellipticCurveSmoothModel(f).igusa_clebsch_invariants() + (-640, -20480, 1310720, 52160364544) + sage: HyperellipticCurveSmoothModel(f(2*x)).igusa_clebsch_invariants() + (-40960, -83886080, 343597383680, 56006764965979488256) + + sage: HyperellipticCurveSmoothModel(f, x).igusa_clebsch_invariants() + (-640, 17920, -1966656, 52409511936) + sage: HyperellipticCurveSmoothModel(f(2*x), 2*x).igusa_clebsch_invariants() + (-40960, 73400320, -515547070464, 56274284941110411264) + + TESTS:: + + sage: # optional - magma + sage: magma(HyperellipticCurveSmoothModel(f)).IgusaClebschInvariants() + [ -640, -20480, 1310720, 52160364544 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x))).IgusaClebschInvariants() + [ -40960, -83886080, 343597383680, 56006764965979488256 ] + sage: magma(HyperellipticCurveSmoothModel(f, x)).IgusaClebschInvariants() + [ -640, 17920, -1966656, 52409511936 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x), 2*x)).IgusaClebschInvariants() + [ -40960, 73400320, -515547070464, 56274284941110411264 ] + """ + f, h = self.hyperelliptic_polynomials() + return invariants.igusa_clebsch_invariants(4 * f + h**2) + + def absolute_igusa_invariants_wamelen(self): + r""" + Return the three absolute Igusa invariants used by van Wamelen [Wam1999]_. + + EXAMPLES:: + + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(x^5 - 1).absolute_igusa_invariants_wamelen() + (0, 0, 0) + sage: HyperellipticCurveSmoothModel((x^5 - 1)(x - 2), (x^2)(x - 2)).absolute_igusa_invariants_wamelen() + (0, 0, 0) + """ + f, h = self.hyperelliptic_polynomials() + return invariants.absolute_igusa_invariants_wamelen(4 * f + h**2) + + def absolute_igusa_invariants_kohel(self): + r""" + Return the three absolute Igusa invariants used by Kohel [KohECHIDNA]_. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves.invariants` + + EXAMPLES:: + + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(x^5 - 1).absolute_igusa_invariants_kohel() + (0, 0, 0) + sage: HyperellipticCurveSmoothModel(x^5 - x + 1, x^2).absolute_igusa_invariants_kohel() + (-1030567/178769, 259686400/178769, 20806400/178769) + sage: HyperellipticCurveSmoothModel((x^5 - x + 1)(3*x + 1), (x^2)(3*x + 1)).absolute_igusa_invariants_kohel() + (-1030567/178769, 259686400/178769, 20806400/178769) + """ + f, h = self.hyperelliptic_polynomials() + return invariants.absolute_igusa_invariants_kohel(4 * f + h**2) + + +class HyperellipticCurveSmoothModel_g2_padic_field( + HyperellipticCurveSmoothModel_g2, HyperellipticCurveSmoothModel_padic_field +): + pass + + +class HyperellipticCurveSmoothModel_g2_finite_field( + HyperellipticCurveSmoothModel_g2, HyperellipticCurveSmoothModel_finite_field +): + pass + + +class HyperellipticCurveSmoothModel_g2_rational_field( + HyperellipticCurveSmoothModel_g2, HyperellipticCurveSmoothModel_rational_field +): + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py new file mode 100644 index 00000000000..fcc92f59d64 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -0,0 +1,1459 @@ +from sage.functions.all import log +from sage.misc.cachefunc import cached_method +from sage.rings.big_oh import O +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.real_mpfr import RR + +# TODO: move this +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_curve import ( + WeightedProjectiveCurve, +) +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_space import ( + WeightedProjectiveSpace, +) + + +def is_HyperellipticCurveSmoothModel(C): + """ + TODO: we probably don't want this at all, this way of working + is the "old" sagemath way of doing things. + """ + return isinstance(C, HyperellipticCurveSmoothModel_generic) + + +class HyperellipticCurveSmoothModel_generic(WeightedProjectiveCurve): + def __init__(self, defining_polynomial, f, h, genus): + self._genus = genus + self._hyperelliptic_polynomials = (f, h) + + self._polynomial_ring = f.parent() + self._base_ring = f.base_ring() + + # TODO: is this simply genus + 1 + self._d = max(h.degree(), (f.degree() + 1) // 2) + + # Initalise the underlying curve + A = WeightedProjectiveSpace([1, self._genus + 1, 1], self._base_ring) + WeightedProjectiveCurve.__init__(self, A, defining_polynomial) + + def _repr_(self): + old_gen = str(self._polynomial_ring.gen()) + f, h = self._hyperelliptic_polynomials + + # TODO: + # The old class has these weird internal gens and then + # printing polynomial rings to change output. + # + # Will do something hacky here and we can talk about it. + f_str, h_str = repr(f).replace(old_gen, "x"), repr(h).replace(old_gen, "x") + + if h: + if h.is_one(): + curve = f"y^2 + y = {f_str}" + else: + curve = f"y^2 + ({h_str})*y = {f_str}" + else: + curve = f"y^2 = {f_str}" + + return f"Hyperelliptic Curve over {self.base_ring()} defined by {curve}" + + def genus(self): + """ + Return the genus of the hyperelliptic curve. + + EXAMPLES: + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: H.genus() + 3 + sage: H = HyperellipticCurveSmoothModel(x^3 + 2, x^5 + 1) + sage: H.genus() + 4 + """ + return self._genus + + def base_ring(self): + """ + Return the base ring of the hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: H.base_ring() + Rational Field + + sage: S. = FiniteField(19)[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x + 2) + sage: H.base_ring() + Finite Field of size 19 + """ + return self._base_ring + + def change_ring(self, R): + """ + Return this hyperelliptic curve over a new ring "R". + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - 10*x + 9) + sage: K = Qp(3, 5) # optional - sage.rings.padics + sage: L. = K.extension(x^30 - 3) # optional - sage.rings.padics + sage: HK = H.change_ring(K) # optional - sage.rings.padics + sage: HL = HK.change_ring(L); HL # optional - sage.rings.padics + Hyperelliptic Curve over 3-adic Eisenstein Extension Field in a defined by x^30 - 3 defined by y^2 = x^5 + (2 + 2*a^30 + a^60 + 2*a^90 + 2*a^120 + O(a^150))*x + a^60 + O(a^210) + + sage: R. = FiniteField(7)[] # optional - sage.rings.finite_rings + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 5) # optional - sage.rings.finite_rings + sage: H.base_extend(FiniteField(7^2, 'a')) # optional - sage.rings.finite_rings + Hyperelliptic Curve over Finite Field in a of size 7^2 defined by y^2 = x^8 + x + 5 + + It is also possible to compute the reduction at a prime by changing + the base ring to the residue field:: + + sage: R. = PolynomialRing(QQ); + sage: H = HyperellipticCurve(R([0, -1, 2, 0, -2]), R([0, 1, 0, 1])); #LMFDB label: 763.a.763.1 + sage: H.change_ring(FiniteField(2)) + Hyperelliptic Curve over Finite Field of size 2 defined by y^2 + (x^3 + x)*y = x + sage: H.change_ring(FiniteField(3)) + Hyperelliptic Curve over Finite Field of size 3 defined by y^2 + (x^3 + x)*y = x^4 + 2*x^2 + 2*x + + Note that this only works when the curve has good reduction at p:: + + sage: H.change_ring(FiniteField(7)) + Traceback (most recent call last): + ... + ValueError: not a hyperelliptic curve: singularity in the provided affine patch + """ + from hyperelliptic_constructor import HyperellipticCurveSmoothModel + + f, h = self._hyperelliptic_polynomials + fR = f.change_ring(R) + hR = h.change_ring(R) + return HyperellipticCurveSmoothModel(fR, hR) + + base_extend = change_ring + + def polynomial_ring(self): + """ + Returns the parent of f,h, where self is the hyperelliptic curve + defined by y^2 + h*y = f. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: H.polynomial_ring() + Univariate Polynomial Ring in x over Rational Field + + sage: # optional - sage.rings.padics + sage: K = Qp(7, 10) + sage: HK = H.change_ring(K) + sage: HK.polynomial_ring() + Univariate Polynomial Ring in x over 7-adic Field with capped relative precision 10 + """ + return self._polynomial_ring + + def hyperelliptic_polynomials(self): + """ + Return the polynomials (f, h) such that + C : y^2 + h*y = f + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(R([0, 1, 2, 0, 0, 1]), R([1, 1, 0, 1])) + sage: H.hyperelliptic_polynomials() + (x^5 + 2*x^2 + x, x^3 + x + 1) + + sage: H = HyperellipticCurveSmoothModel(x^7 + x + 2) + sage: H.hyperelliptic_polynomials() + (x^7 + x + 2, 0) + """ + return self._hyperelliptic_polynomials + + def roots_at_infinity(self): + """ + Compute the roots of: Y^2 + h[d]Y - f[2d] = 0. + When the curve is ramified, we expect one root, when + the curve is inert or split we expect zero or two roots. + """ + if hasattr(self, "_alphas"): + return self._alphas + + f, h = self._hyperelliptic_polynomials + x = f.parent().gen() + d = self._d + + if h.is_zero(): + coeff = f[2 * d] + # Handle the ramified case + if coeff.is_zero(): + return [coeff] + return f[2 * d].sqrt(all=True) + + self._alphas = (x**2 + x * h[d] - f[2 * d]).roots(multiplicities=False) + return self._alphas + + def is_split(self): + """ + Return True if the curve is split, i.e. there are two rational + points at infinity. + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^6+1, x^3+1) + sage: H.is_split() + False + + sage: HK = H.change_ring(FiniteField(19)) + sage: HK.is_split() + True + """ + return len(self.roots_at_infinity()) == 2 + + def is_ramified(self): + """ + Return True if the curve is ramified, i.e. there is one rational + point at infinity. + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^5+1) + sage: H.is_ramified() + True + + sage: H = HyperellipticCurveSmoothModel(x^5+1, x^3+1) + sage: H.is_ramified() + False + """ + return len(self.roots_at_infinity()) == 1 + + def is_inert(self): + """ + Return True if the curve is inert, i.e. there are no rational + points at infinity. + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^6+1,-x^3+1) + sage: H.is_inert() + True + + sage: K. = QQ.extension(x^2+x-1) + sage: HK = H.change_ring(K) + sage: HK.is_inert() + False + + sage: HF = H.change_ring(FiniteField(29)) + sage: HF.is_inert() + False + """ + return len(self.roots_at_infinity()) == 0 + + def infinite_polynomials(self): + """ + TODO: the name of this function could be better? + + Computes G^±(x) for curves in the split degree model + """ + if hasattr(self, "_infinite_polynomials"): + return self._infinite_polynomials + + alphas = self.roots_at_infinity() + + # This function only makes sense for the split model + if not len(alphas) == 2: + raise ValueError("hyperelliptic curve does not have the split model") + + f, h = self._hyperelliptic_polynomials + alpha_plus, alpha_minus = alphas + d = self._d + + # Construct G_plus from alpha_plus + g = [None] * (d + 1) + g[d] = alpha_plus + for i in range(d - 1, -1, -1): + # We need (g * (g + h))[x^(i + d)] to match f_{i + d} + the_rest = g[d] * h[i] + sum( + g[k] * (g[i + d - k] + h[i + d - k]) for k in range(i + 1, d) + ) + g[i] = (f[i + d] - the_rest) / (2 * g[d] + h[d]) + + G_plus = self._polynomial_ring(g) + G_minus = -G_plus - h + # Checks for the assumptions on G^± + genus = self.genus() + assert G_plus.degree() <= (genus + 1) + assert (G_plus**2 + h * G_plus - f).degree() <= genus + assert G_minus.leading_coefficient() == alpha_minus + + self._infinite_polynomials = G_plus, G_minus + return self._infinite_polynomials + + def points_at_infinity(self): + """ + Compute the points at infinity on the curve. Assumes we are using + a weighted projective model for the curve + """ + # TODO: check to False? + return [self.point([1, y, 0], check=True) for y in self.roots_at_infinity()] + + def is_x_coord(self, x): + """ + Return True if ``x`` is the `x`-coordinate of a point on this curve. + + .. SEEALSO:: + + See also :meth:`lift_x` to find the point(s) with a given + `x`-coordinate. This function may be useful in cases where + testing an element of the base field for being a square is + faster than finding its square root. + + INPUT: + + - ``x`` -- an element of the base ring of the curve + + OUTPUT: + + A bool stating whether or not `x` is a x-coordinate of a point on the curve + + EXAMPLES: + + When `x` is the `x`-coordinate of a rational point on the + curve, we can request these:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.is_x_coord(0) + True + + There are no rational points with `x`-coordinate 3:: + + sage: H.is_x_coord(3) + False + + The function also handles the case when `h(x)` is not zero:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.is_x_coord(1) + True + + We can perform these operations over finite fields too:: + + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(163)) + sage: f = x^7 + x + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.is_x_coord(13) + True + + Including the case of characteristic two:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2^4) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.is_x_coord(z4^3 + z4^2 + z4) + True + + AUTHORS: + + - Giacomo Pope (2024): adapted from :meth:`lift_x` + + TESTS: + + The `x`-coordinate must be defined over the base field of the curve:: + + sage: p = 11 + sage: F = GF(11) + sage: F_ext = GF(11^2) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.is_x_coord(F_ext.gen()) + Traceback (most recent call last): + ... + TypeError: x must be coercible into the base ring of the curve + """ + f, h = self.hyperelliptic_polynomials() + K = self.base_ring() + try: + x = K(x) + except (ValueError, TypeError): + raise TypeError("x must be coercible into the base ring of the curve") + + # When h is zero then x is a valid coordinate if y2 is square + if not h: + y2 = f(x) + return y2.is_square() + # Generic case for h != 0 + a = f(x) + b = h(x) + # Special case for char 2 + if K.characteristic() == 2: + R = f.parent() # Polynomial ring K[x] + F = R([-a, b, 1]) + return bool(F.roots()) + # Otherwise x is a point on the curve if the discriminant is a square + D = b * b + 4 * a + return D.is_square() + + def lift_x(self, x, all=False): + """ + Return one or all finite points with given `x`-coordinate. + + This method is deterministic: It returns the same data each + time when called again with the same `x`. + + INPUT: + + - ``x`` -- an element of the base ring of the curve + + - ``all`` (bool, default ``False``) -- if ``True``, return a + (possibly empty) list of all points; if ``False``, return + just one point, or raise a :class:`ValueError` if there are none. + + OUTPUT: + + A point or list of up to two points on this curve. + + .. SEEALSO:: + + :meth:`is_x_coord` + + AUTHORS: + + - Giacomo Pope (2024): Allowed for the case of characteristic two + + EXAMPLES: + + When `x` is the `x`-coordinate of a rational point on the + curve, we can request these:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.lift_x(0) + (0 : -1 : 1) + sage: H.lift_x(4, all=True) + [(4 : -33 : 1), (4 : 33 : 1)] + + There are no rational points with `x`-coordinate 3:: + + sage: H.lift_x(3) + Traceback (most recent call last): + ... + ValueError: No point with x-coordinate 3 on Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x^3 + 1 + + An empty list is returned when there are no points and ``all=True``:: + + sage: H.lift_x(3, all=True) + [] + + The function also handles the case when `h(x)` is not zero:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.lift_x(1) + (1 : -3 : 1) + + We can perform these operations over finite fields too:: + + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(163)) + sage: f = x^7 + x + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.lift_x(13) + (13 : 41 : 1) + + Including the case of characteristic two:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2^4) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.lift_x(z4^3 + z4^2 + z4, all=True) + [(z4^3 + z4^2 + z4 : z4^2 + z4 + 1 : 1), (z4^3 + z4^2 + z4 : z4^3 : 1)] + + Points at infinity are not included, as they do not have a unique x-coordinate:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: H(1, -1, 0) + (1 : -1 : 0) + sage: H.lift_x(1, all=True) + [] + + TESTS:: + + sage: # needs sage.rings.finite_rings + sage: F1 = GF(11) + sage: F2 = GF(13) + sage: R. = PolynomialRing(F1) + sage: f = x^7 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.lift_x(F2.random_element()) + Traceback (most recent call last): + ... + ValueError: x must have a common parent with the base ring + + Ensure that :issue:`37097` is fixed:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2^4) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.lift_x(z4^3 + z4^2 + z4, all=True) + [(z4^3 + z4^2 + z4 : z4^2 + z4 + 1 : 1), (z4^3 + z4^2 + z4 : z4^3 : 1)] + """ + from sage.structure.element import get_coercion_model + + cm = get_coercion_model() + + f, h = self.hyperelliptic_polynomials() + K = self.base_ring() + + # Compute the common parent between the base ring of the curve and + # the parent of the input x-coordinate. + try: + L = cm.common_parent(x.parent(), K) + x = L(x) + except (TypeError, ValueError): + raise ValueError("x must have a common parent with the base ring") + + # First we compute the y-coordinates the given x-coordinate + ys = [] + one = L.one() + + # When h is zero we find all y-coordinates with a single sqrt + if not h: + y2 = f(x) + # When y2 is not a square, ys will be an empty list + ys = y2.sqrt(all=True, extend=False) + # Otherwise we need roots of the discriminant + else: + a = f(x) + b = h(x) + # Special case for char 2 + if K.characteristic() == 2: + R = f.parent() + F = R([-a, b, 1]) + ys = F.roots(L, multiplicities=False) + else: + D = b * b + 4 * a + # When D is not a square, ys will be an empty list + ys = [(-b + d) / 2 for d in D.sqrt(all=True, extend=False)] + + if ys: + ys.sort() # Make lifting deterministic + if all: + return [self.point([x, y, one], check=False) for y in ys] + else: + return self.point([x, ys[0], one], check=False) + + if all: + return [] + else: + raise ValueError(f"No point with x-coordinate {x} on {self}") + + def affine_coordinates(self, P): + """ + Returns the affine coordinates of a point P of self. + That is for P = [X,Y,Z], the output is X/Z¸ Y/Z^(g+1). + + EXAMPLES:: + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 1) + sage: P = H.point([2,0,2]) + sage: H.affine_coordinates(P) + (1, 0) + sage: Q = H.point([1,1,0]) + sage: H.affine_coordinates(Q) + Traceback (most recent call last): + ... + ValueError: The point (1 : 1 : 0) is not an affine point of Hyperelliptic Curve over Rational Field defined by y^2 = x^6 - 1 + """ + if P[2] == 0: + raise ValueError(f"The point {P} is not an affine point of {self}") + + g = self.genus() + return P[0] / P[2], P[1] / P[2] ** (g + 1) + + def is_weierstrass_point(self, P): + """ + Return True if P is a Weierstrass point of self. + TODO: It would be better to define this function for points directly. + + EXAMPLES:: + + We consider the hyperelliptic curve `y^2 = x^6 -1` over the rational numbers. + For instance, the point P = (1,0) is a Weierstrass point, + while the points at infinity are not:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 1) + sage: P = H.point([1,0]) + sage: H.is_weierstrass_point(P) + True + sage: Q = H.point([1,1,0]) + sage: H.is_weierstrass_point(Q) + False + + This also works for hyperelliptic curves with `h(x)` nonzero. + Note that in this case the `y`-coordinate of a Weierstrass point + is not necessarily zero:: + + sage: R. = FiniteField(17)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) + sage: P = H.point([15,6,1]) + sage: H.is_weierstrass_point(P) + True + sage: Q = H.point([3,0,1]) + sage: H.is_weierstrass_point(Q) + False + + TESTS:: + + Check that the examples from the p-adic file work. + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK(0,3) + sage: HK.is_weierstrass_point(P) + False + sage: Q = HK(1,0,0) + sage: HK.is_weierstrass_point(Q) + True + sage: S = HK(1,0) + sage: HK.is_weierstrass_point(S) + True + sage: T = HK.lift_x(1+3*5^2); T + (1 + 3*5^2 + O(5^8) : 3*5 + 4*5^2 + 5^4 + 3*5^5 + 5^6 + O(5^7) : 1 + O(5^8)) + sage: HK.is_weierstrass_point(T) + False + + """ + + f, h = self.hyperelliptic_polynomials() + if P[2] == 0: + return self.is_ramified() + else: + x, y = self.affine_coordinates(P) + return y == -y - h(x) + + def rational_weierstrass_points(self): + """ + Return the rational Weierstrass points of the hyperelliptic curve. + These are the points that are fixed by the hyperelliptic involution. + + EXAMPLES:: + + When `h(x)` is zero, then the Weierstrass points are the points with + `y`-coordinate equal to zero:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x) + sage: H.rational_weierstrass_points() + [(1 : 0 : 0), (1 : 0 : 1), (0 : 0 : 1), (-1 : 0 : 1)] + + The function also handles the case with `h(x)` nonzero:: + + sage: R. = FiniteField(17)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) + sage: H.rational_weierstrass_points() + [(15 : 6 : 1), (2 : 6 : 1)] + sage: P = H.point([15,6,1]) + sage: H.is_weierstrass_point(P) + True + + + """ + f, h = self.hyperelliptic_polynomials() + + F = h**2 + 4 * f + affine_weierstrass_points = [ + self(r, -h(r) / 2) for r in F.roots(multiplicities=False) + ] + + if self.is_ramified(): # the point at infinity is Weierstrass + return self.points_at_infinity() + affine_weierstrass_points + else: + return affine_weierstrass_points + + def hyperelliptic_involution(self, P): + """ + Returns the image of P under the hyperelliptic involution. + + EXAMPLES:: + + sage: R. = FiniteField(17)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) + sage: P = H.point([8,12]) + sage: P_inv = H.hyperelliptic_involution(P); P_inv + (8 : 8 : 1) + sage: H.hyperelliptic_involution(P_inv) == P + True + sage: Q = H.point([15,6]) + sage: H.is_weierstrass_point(Q) + True + sage: H.hyperelliptic_involution(Q) == Q + True + """ + [X, Y, Z] = P._coords + f, h = self.hyperelliptic_polynomials() + if Z == 0: + if self.is_ramified(): + return P + else: + points = self.points_at_infinity() + if P == points[0]: + return points[1] + else: + return points[0] + elif Z == 1: + Y_inv = -Y - h(X) + return self.point([X, Y_inv]) + else: + raise ValueError("the point P has to be normalized") + + def distinguished_point(self): + """ + Return the distinguished point of the hyperelliptic curve. + By default, this is one of the points at infinity if possible. + """ + if hasattr(self, "_distinguished_point"): + return self._distinguished_point + + if not self.is_inert(): + # For the the split and ramified case, a point at infinity is chosen, + self._distinguished_point = self.points_at_infinity()[0] + return self._distinguished_point + else: + assert ( + self.base_ring().characteristic() > 0 + ), "in characteristic 0, a distinguished_point needs to be specified" + # in the inert case we choose a point with minimal x-coordinate + for x0 in self.base_ring(): + try: + self._distinguished_point = self.lift_x(x0) + return self._distinguished_point + except ValueError: + pass + + raise ValueError("distinguished point not found") + + + def set_distinguished_point(self, P0): + """ + Change the distinguished point of the hyperelliptic curve to P0. + """ + try: + P0 = self.point(P0) + except ValueError: + raise TypeError("P0 must be a point on the hyperelliptic curve") + self._distinguished_point = P0 + + @cached_method + def jacobian(self): + """ + Returns the Jacobian of the hyperelliptic curve. + """ + from jacobian_generic import HyperellipticJacobian_generic + + return HyperellipticJacobian_generic(self) + + @cached_method + def projective_curve(self): + """ + Returns a singular plane model of the hyperelliptic curve self. + + TODO: renaming to plane_model ? + + EXAMPLES:: + + We consider the hyperelliptic curve with affine equation + `y^2 = x^5 + x` :: + + sage: R. = FiniteField(11)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2) + sage: C = H.projective_curve(); C + Projective Plane Curve over Finite Field of size 11 defined by -x^6 + y^2*z^4 - 2*z^6 + + + Note that the projective coordinates of points on H and their images in C + are in general not the same:: + + sage: P = H.point([9,4,2]) + sage: Q = C.point([9,4,2]) + Traceback (most recent call last): + ... + TypeError: Coordinates [10, 2, 1] do not define a point on Projective Plane Curve over Finite Field of size 11 defined by -x^6 + y^2*z^4 - 2*z^6 + + + However, the affine coordinates coincide:: + + sage: H.affine_coordinates(P) + (10, 6) + sage: Q = C.point([10,6,1]) + + The model C has one singular point at infinity, while H is non-singular + and has two points at infinity. + + sage: H.points_at_infinity() + [(1 : 1 : 0), (1 : 10 : 0)] + sage: [P for P in C.rational_points() if P[2]==0] + [(0 : 1 : 0)] + """ + from sage.schemes.curves.constructor import Curve + + f, h = self._hyperelliptic_polynomials + R, (_, y, z) = PolynomialRing(self.base_ring(), 3, "x, y, z").objgens() + return Curve(R(y**2 + h * y - f).homogenize(z)) + + def rational_points(self, **kwds): + r""" + Find rational points on the hyperelliptic curve. Arguments are passed + on to :meth:`sage.schemes.generic.algebraic_scheme.rational_points`. + + ALGORITHM: + + We use :meth:`points_at_infinity` to compute the points at infinity, and + `sage.schemes.generic.algebraic_scheme.rational_points` on this curve's + :meth:`projective_curve` for the affine points. + + EXAMPLES:: + + For the LMFDB genus 2 curve `932.a.3728.1 `_:: + + sage: R. = PolynomialRing(QQ) + sage: C = HyperellipticCurveSmoothModel(R([0, -1, 1, 0, 1, -2, 1]), R([1])) + sage: C.rational_points(bound=8) + [(1 : 1 : 0), (1 : -1 : 0), (-1 : -3 : 1), (-1 : 2 : 1), (0 : -1 : 1), + (0 : 0 : 1), (1/2 : -5/8 : 1), (1/2 : -3/8 : 1), (1 : -1 : 1), (1 : 0 : 1)] + + Check that :issue:`29509` is fixed for the LMFDB genus 2 curve + `169.a.169.1 `_:: + + sage: C = HyperellipticCurveSmoothModel(R([0, 0, 0, 0, 1, 1]), R([1, 1, 0, 1])) + sage: C.rational_points(bound=10) + [(1 : 0 : 0), (1 : -1 : 0), (-1 : 0 : 1), (-1 : 1 : 1), (0 : -1 : 1), (0 : 0 : 1)] + + An example over a number field:: + + sage: R. = PolynomialRing(QuadraticField(2)) # needs sage.rings.number_field + sage: C = HyperellipticCurveSmoothModel(R([1, 0, 0, 0, 0, 1])) # needs sage.rings.number_field + sage: C.rational_points(bound=2) + [(1 : 0 : 0), (-1 : 0 : 1), (0 : -1 : 1), (0 : 1 : 1), (1 : -a : 1), (1 : a : 1)] + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 33) + sage: H.rational_points(bound=20) # long time (6s) + [(1 : 1 : 0), (1 : -1 : 0), (-2 : -17 : 1), (-2 : 17 : 1), (2 : -17 : 1), (2 : 17 : 1)] + """ + proj_pts = self.projective_curve().rational_points(**kwds) + return self.points_at_infinity() + [self(*P) for P in proj_pts if P[2] != 0] + + # ------------------------------------------- + # Hacky things + # ------------------------------------------- + + def is_singular(self, *args, **kwargs): + r""" + Returns False, because hyperelliptic curves are smooth projective + curves, as checked on construction. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1) + sage: H.is_singular() + False + """ + return False + + def is_smooth(self): + r""" + Returns True, because hyperelliptic curves are smooth projective + curves, as checked on construction. + + EXAMPLES:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: H.is_smooth() + True + """ + return True + + # ------------------------------------------- + # Odd degree model functions + # ------------------------------------------- + + def odd_degree_model(self): + r""" + Return an odd degree model of self, or raise ValueError if one does not exist over the field of definition. + The term odd degree model refers to a model of the form y^2 = f(x) with deg(f) = 2*g + 1. + + EXAMPLES:: + + sage: x = QQ['x'].gen() + sage: H = HyperellipticCurveSmoothModel((x^2 + 2)*(x^2 + 3)*(x^2 + 5)); H + Hyperelliptic Curve over Rational Field defined by y^2 = x^6 + 10*x^4 + 31*x^2 + 30 + sage: H.odd_degree_model() + Traceback (most recent call last): + ... + ValueError: No odd degree model exists over field of definition + + sage: K2 = QuadraticField(-2, 'a') # needs sage.rings.number_field + sage: Hp2 = H.change_ring(K2).odd_degree_model(); Hp2 # needs sage.rings.number_field + Hyperelliptic Curve over Number Field in a + with defining polynomial x^2 + 2 with a = 1.414213562373095?*I + defined by y^2 = 6*a*x^5 - 29*x^4 - 20*x^2 + 6*a*x + 1 + + sage: K3 = QuadraticField(-3, 'b') # needs sage.rings.number_field + sage: Hp3 = H.change_ring(QuadraticField(-3, 'b')).odd_degree_model(); Hp3 # needs sage.rings.number_field + Hyperelliptic Curve over Number Field in b + with defining polynomial x^2 + 3 with b = 1.732050807568878?*I + defined by y^2 = -4*b*x^5 - 14*x^4 - 20*b*x^3 - 35*x^2 + 6*b*x + 1 + + Of course, ``Hp2`` and ``Hp3`` are isomorphic over the composite + extension. One consequence of this is that odd degree models + reduced over "different" fields should have the same number of + points on their reductions. 43 and 67 split completely in the + compositum, so when we reduce we find: + + sage: # needs sage.rings.number_field + sage: P2 = K2.factor(43)[0][0] + sage: P3 = K3.factor(43)[0][0] + sage: Hp2.change_ring(K2.residue_field(P2)).frobenius_polynomial() + x^4 - 16*x^3 + 134*x^2 - 688*x + 1849 + sage: Hp3.change_ring(K3.residue_field(P3)).frobenius_polynomial() + x^4 - 16*x^3 + 134*x^2 - 688*x + 1849 + + sage: H.change_ring(GF(43)).odd_degree_model().frobenius_polynomial() # needs sage.rings.finite_rings + x^4 - 16*x^3 + 134*x^2 - 688*x + 1849 + + sage: # needs sage.rings.number_field + sage: P2 = K2.factor(67)[0][0] + sage: P3 = K3.factor(67)[0][0] + sage: Hp2.change_ring(K2.residue_field(P2)).frobenius_polynomial() + x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 + sage: Hp3.change_ring(K3.residue_field(P3)).frobenius_polynomial() + x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 + + sage: H.change_ring(GF(67)).odd_degree_model().frobenius_polynomial() # needs sage.rings.finite_rings + x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 + + The case where `h(x)` is nonzero is also supported:: + + sage: HyperellipticCurveSmoothModel(x^5 + 1, 1).odd_degree_model() + Hyperelliptic Curve over Rational Field defined by y^2 = 4*x^5 + 5 + + TESTS:: + + sage: # TODO this used to have names="U, V" + sage: HyperellipticCurveSmoothModel(x^5 + 1).odd_degree_model() + Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + 1 + """ + from hyperelliptic_constructor import HyperellipticCurveSmoothModel + + f, h = self._hyperelliptic_polynomials + if f.base_ring().characteristic() == 2: + raise ValueError( + "There are no odd degree models over a field with characteristic 2." + ) + if h: + f = 4 * f + h**2 # move h to the right side of the equation + if f.degree() % 2: + # already odd + return HyperellipticCurveSmoothModel(f, 0) + + rts = f.roots(multiplicities=False) + if not rts: + raise ValueError("No odd degree model exists over field of definition") + rt = rts[0] + x = f.parent().gen() + fnew = f((x * rt + 1) / x).numerator() # move rt to "infinity" + + return HyperellipticCurveSmoothModel(fnew, 0) + + def has_odd_degree_model(self): + r""" + Return True if an odd degree model of self exists over the field of definition; False otherwise. + + Use ``odd_degree_model`` to calculate an odd degree model. + + EXAMPLES:: + + sage: x = QQ['x'].0 + sage: HyperellipticCurveSmoothModel(x^5 + x).has_odd_degree_model() + True + sage: HyperellipticCurveSmoothModel(x^6 + x).has_odd_degree_model() + True + sage: HyperellipticCurveSmoothModel(x^6 + x + 1).has_odd_degree_model() + False + """ + try: + return bool(self.odd_degree_model()) + except ValueError: + return False + + # ------------------------------------------- + # Magma + # ------------------------------------------- + + def _magma_init_(self, magma): + """ + Internal function. Returns a string to initialize this elliptic + curve in the Magma subsystem. + + EXAMPLES:: + + sage: # optional - magma + sage: R. = QQ[]; C = HyperellipticCurveSmoothModel(x^3 + x - 1, x); C + Hyperelliptic Curve over Rational Field + defined by y^2 + x*y = x^3 + x - 1 + sage: magma(C) + Hyperelliptic Curve defined by y^2 + x*y = x^3 + x - 1 over Rational Field + sage: R. = GF(9,'a')[]; C = HyperellipticCurveSmoothModel(x^3 + x - 1, x^10); C # needs sage.rings.finite_rings + Hyperelliptic Curve over Finite Field in a of size 3^2 + defined by y^2 + x^10*y = x^3 + x + 2 + sage: D = magma(C); D # needs sage.rings.finite_rings + Hyperelliptic Curve defined by y^2 + (x^10)*y = x^3 + x + 2 over GF(3^2) + sage: D.sage() # needs sage.rings.finite_rings + Hyperelliptic Curve over Finite Field in a of size 3^2 + defined by y^2 + x^10*y = x^3 + x + 2 + """ + f, h = self._hyperelliptic_polynomials + return f"HyperellipticCurve({f._magma_init_(magma)}, {h._magma_init_(magma)})" + + # ------------------------------------------- + # monsky washnitzer things... + # ------------------------------------------- + + def monsky_washnitzer_gens(self): + from sage.schemes.hyperelliptic_curves import monsky_washnitzer + + S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self) + return S.gens() + + def invariant_differential(self): + """ + Returns `dx/2y`, as an element of the Monsky-Washnitzer cohomology + of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: C.invariant_differential() + 1 dx/2y + + """ + from sage.schemes.hyperelliptic_curves import monsky_washnitzer as m_w + + S = m_w.SpecialHyperellipticQuotientRing(self) + MW = m_w.MonskyWashnitzerDifferentialRing(S) + return MW.invariant_differential() + + # ------------------------------------------- + # Local coordinates + # ------------------------------------------- + + def local_coordinates_at_nonweierstrass(self, P, prec=20, name="t"): + """ + For a non-Weierstrass point `P = (a,b)` on the hyperelliptic + curve `y^2 + y*h(x) = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, + where `t = x - a` is the local parameter. + + INPUT: + + - ``P = (a, b)`` -- a non-Weierstrass point on self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- gen of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 + y(t)*h(x(t)) = f(x(t))` and `t = x - a` + is the local parameter at `P`. + + EXAMPLES:: + + We compute the local coordinates of `H : y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x` at + the point `P = (1, 6)`:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) + sage: P = H(1, 6) + sage: xt, yt = H.local_coordinates_at_nonweierstrass(P, prec=5) + sage: (xt, yt) + (1 + t + O(t^5), 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5)) + + We verify that `y(t) = f(x(t))`:: + + sage: f,_ = H.hyperelliptic_polynomials() + sage: (yt^2 - f(xt)).is_zero() + True + + We can also compute the local coordinates of points on a hyperelliptic curve + with equation `y^2 + h(x)*y = f(x)`:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3*x^5+6*x^4+7*x^3+6*x^2+3*x+1, x^2+x) # 196.a.21952.1 + sage: P = H(-1,1) + sage: xt, yt = H.local_coordinates_at_nonweierstrass(P, prec=5) + sage: (xt, yt) + (-1 + t + O(t^5), 1 - t + 3/2*t^2 - 3/4*t^3 + O(t^5)) + sage: f,h = H.hyperelliptic_polynomials() + sage: (yt^2 + h(xt)*yt -f(xt)).is_zero() + True + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + + """ + if P[2] == 0: + raise TypeError( + "P = %s is a point at infinity. Use local_coordinates_at_infinity instead!" + % P + ) + if self.is_weierstrass_point(P): + raise TypeError( + "P = %s is a Weierstrass point. Use local_coordinates_at_weierstrass instead!" + % P + ) + f, h = self.hyperelliptic_polynomials() + # pol = self.hyperelliptic_polynomials()[0] + a, b = self.affine_coordinates(P) + + L = PowerSeriesRing(self.base_ring(), name, default_prec=prec) + t = L.gen() + K = PowerSeriesRing(L, "x") + f = K(f) + h = K(h) + + ft = f(t + a) + ht = h(t + a) + for _ in range((RR(log(prec, 2))).ceil()): + b = b - (b**2 + b * ht - ft) / (2 * b + ht) + return t + a + O(t ** (prec)), b + O(t ** (prec)) + + def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): + """ + For a finite Weierstrass point P = (a,b) on the hyperelliptic + curve `y^2 + h(x)*y = f(x)`, returns `(x(t), y(t))` such that + `y(t)^2 + h(x(t))*y(t) = f(x(t))`, where `t = y - b` is the local parameter. + + INPUT: + + - ``P`` -- a finite Weierstrass point on self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- gen of the power series ring (default: `t`) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))` and `t = y - b` + is the local parameter at `P = (a,b)`. + + EXAMPLES:: + + We compute the local coordinates of the Weierstrass point `P = (4,0)` + on the hyperelliptic curve `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`. + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) + sage: P = H(4, 0) + sage: xt, yt = H.local_coordinates_at_weierstrass(P, prec=7) + sage: xt + 4 + 1/360*t^2 - 191/23328000*t^4 + 7579/188956800000*t^6 + O(t^7) + sage: yt + t + O(t^7) + + We verify that `y(t) = f(x(t))`:: + + sage: f,_ = H.hyperelliptic_polynomials() + sage: (yt^2 - f(xt)).is_zero() + True + + We compute the local coordinates at the Weierstrass point `(1,-1)` + of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2. + + sage: H = HyperellipticCurveSmoothModel(-x^2, x^3+1) + sage: P = H(1,-1) + sage: xt,yt = H.local_coordinates_at_weierstrass(P) + sage: f,h = H.hyperelliptic_polynomials() + sage: (yt^2 + h(xt)*yt - f(xt)).is_zero() + True + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Francis Clarke (2012-08-26) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + + """ + if P[2] == 0: + raise TypeError( + "P = %s is a point at infinity. Use local_coordinates_at_infinity instead!" + % P + ) + if not self.is_weierstrass_point(P): + raise TypeError( + "P = %s is not a Weierstrass point. Use local_coordinates_at_nonweierstrass instead!" + % P + ) + + L = PowerSeriesRing(self.base_ring(), name, default_prec=prec) + t = L.gen() + f, h = self.hyperelliptic_polynomials() + f_prime = f.derivative() + h_prime = h.derivative() + + a, b = self.affine_coordinates(P) + yt = (t + b).add_bigoh(prec) + yt2 = yt**2 + for _ in range(int(log(prec, 2))): + a = a - (yt2 + yt * h(a) - f(a)) / (yt * h_prime(a) - f_prime(a)) + return (a, yt) + + def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): + """ + For a hyperelliptic curve with ramified model `y^2 + h(x)*y = f(x)`, + return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where + `t = y/x^{g+1}` is the local parameter at the unique (Weierstrass) point + at infinity. + + TODO/NOTE: In the previous implementation `t = x^g/y` was used. + This is not a valid parameter on the smooth model, and the output is necessarily different. + + INPUT: + + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- generator of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y/x^{g+1}` + is the local parameter at infinity + + EXAMPLES:: + + We compute the local coordinates at the point at infinity of the + hyperelliptic curve `y^2 = x^5 - 5*x^2 + 1`. + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 5*x^2 + 1) + sage: xt, yt = H.local_coordinates_at_infinity_ramified(prec=10) + sage: (xt,yt) + (t^-2 - 5*t^4 + t^8 - 75*t^10 + O(t^12), + t^-5 - 15*t + 3*t^5 - 150*t^7 + 90*t^11 + O(t^12)) + + We verify that `y(t)^2 = f(x(t))` and `t = y(t)/x(t)^3`:: + + sage: f,_ = H.hyperelliptic_polynomials() + sage: (yt^2 - f(xt)).is_zero() + True + sage: yt/xt^3 + t + O(t^15) + + The method also works when `h` is nonzero. We compute the local + coordinates of the point at infinity of the hyperelliptic curve + `y^2 + y = x^5 - 9*x^4 + 14*x^3 - 19*x^2 + 11*x - 6`:: + + sage: H = HyperellipticCurveSmoothModel(x^5-9*x^4+14*x^3-19*x^2+11*x-6,1) + sage: f,h = H.hyperelliptic_polynomials() + sage: xt,yt = H.local_coordinates_at_infinity_ramified() + sage: (yt^2 + h(xt)*yt - f(xt)).is_zero() + True + + Note that the point at infinity has to be a Weierstrass point. + + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + if not self.is_ramified(): + raise TypeError( + "The point at infinity is not a Weierstrass point. Use local_coordinates_at_infinity_split instead!" + ) + + g = self.genus() + f, h = self.hyperelliptic_polynomials() + # if h: + # raise NotImplementedError("h = %s is nonzero" % h) + K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) + t = K.gen() + L = PolynomialRing(K, "x") + x = L.gen() + + # note that P = H(1,0,0) + yt = x ** (g + 1) * t + w = yt**2 + h * yt - f + wprime = w.derivative(x) + xt = t**-2 + for _ in range((RR(log(prec + 2) / log(2))).ceil()): + xt = xt - w(xt) / wprime(xt) + yt = xt ** (g + 1) * t + return xt + O(t ** (prec + 2)), yt + O( + t ** (prec + 2) + ) # TODO: Why the prec+2 ? Not sure if this is adapted in the correct way. + + def local_coordinates_at_infinity_split(self, P, prec=20, name="t"): + """ + For a point at infinity P = (a:b:0) on a hyperelliptic curve with + split model `y^2 + h(x)*y = f(x)`, + return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`. + Here `t = a/x` is the local parameter at P. + + INPUT: + + - ``P`` -- a point at infinity of a self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- generator of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y/x^{g+1}` + is the local parameter at infinity + + EXAMPLES:: + + We compute the local coordinates at the point at infinity of the + hyperelliptic curve ` ` + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^6+4*x^4 + 4*x^2+1) + sage: P1 = H(1,-1,0) + sage: xt1,yt1 = H.local_coordinates_at_infinity_split(P1) + + Note the similarity to the local coordinates of the other point at infinity :: + + sage: P2 = H(1,1,0) + sage: xt2,yt2 = H.local_coordinates_at_infinity_split(P2) + sage: xt1 == xt2 and yt1 == - yt2 + True + + Similarly, if `h` is nonzero, the relation between the local coordinates at the + points at infinity is obtained from the hyperelliptic involution:: + + sage: H = HyperellipticCurveSmoothModel(-x^5, x^3+x+1) + sage: f,h = H.hyperelliptic_polynomials() + sage: P1 = H(1,0,0) + sage: P2 = H(1,-1,0) + sage: xt1,yt1 = H.local_coordinates_at_infinity_split(P1) + sage: xt2,yt2 = H.local_coordinates_at_infinity_split(P2) + sage: (yt1 + yt2 + h(xt1)).is_zero() + True + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + if not self.is_split(): + raise TypeError( + "The point at infinity is a Weierstrass point. Use local_coordinates_at_infinity_ramified instead!" + ) + if not P[2] == 0: + raise TypeError( + "P = %s is not a point at infinity. Use local_coordinates_at_nonweierstrass or local_coordinates_at_weierstrass instead!" + % P + ) + + K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) + t = K.gen() + + # note that P = H(a,b,0) + xt = P[0] / t + f, h = self.hyperelliptic_polynomials() + ft = f(xt) + ht = h(xt) + yt = P[1] / t**3 + for _ in range((RR(log(prec + 2) / log(2))).ceil()): + yt = yt - (yt**2 + ht * yt - ft) / (2 * yt + ht) + return xt + O(t ** (prec + 2)), yt + O(t ** (prec + 2)) + + def local_coord(self, P, prec=20, name="t"): + """ + For point `P = (a,b)` on the hyperelliptic curve + `y^2 + y*h(x) = f(x)`, return `(x(t), y(t))` such that + `(y(t))^2 + h(x(t))*y(t) = f(x(t))`, where `t` is the local parameter at + that point. + + INPUT: + + - ``P`` -- a point on self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- generator of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))`, where `t` + is the local parameter at `P` + + EXAMPLES:: + + We compute the local coordinates of several points of the curve with + defining equation `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`. :: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) + sage: H.local_coord(H(1 ,6), prec=5) + (1 + t + O(t^5), 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5)) + sage: H.local_coord(H(4, 0), prec=7) + (4 + 1/360*t^2 - 191/23328000*t^4 + 7579/188956800000*t^6 + O(t^7), t + O(t^7)) + sage: H.local_coord(H(1, 0, 0), prec=5) + (t^-2 - 23*t^2 + 18*t^4 - 1018*t^6 + O(t^7), + t^-5 - 69*t^-1 + 54*t - 1467*t^3 + 3726*t^5 + O(t^6)) + + We compute the local coordinates of several points of the curve with + defining equation `y^2 + (x^3 + 1)*y = x^4 + 2*x^3 + x^2 - x`. :: + + sage: H = HyperellipticCurveSmoothModel(x^4+2*x^3+x^2-x,x^3+1) + sage: H.local_coord(H(1,-3,1), prec=5) + (1 + t + O(t^5), -3 - 5*t - 3*t^2 - 3/4*t^3 - 3/16*t^4 + O(t^5)) + sage: H.local_coord(H(1,-1,0), prec=5) + (t^-1 + O(t^7), -t^-3 - t^-1 - 3 + 6*t^2 + 6*t^3 - 12*t^4 - 42*t^5 + O(t^6)) + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + if P[2] == 0: + if self.is_ramified(): + return self.local_coordinates_at_infinity_ramified(prec, name) + else: + return self.local_coordinates_at_infinity_split(P, prec, name) + + elif self.is_weierstrass_point(P): + return self.local_coordinates_at_weierstrass(P, prec, name) + else: + return self.local_coordinates_at_nonweierstrass(P, prec, name) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py new file mode 100644 index 00000000000..aab4bbf961c --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -0,0 +1,1425 @@ +from sage.functions.log import log +from sage.matrix.constructor import matrix +from sage.modules.free_module import VectorSpace +from sage.modules.free_module_element import vector +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF +from sage.rings.infinity import Infinity +from sage.rings.integer_ring import ZZ +from sage.rings.padics.factory import Qp as pAdicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ, RationalField +from sage.rings.real_mpfr import RR +from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic + + +class HyperellipticCurveSmoothModel_padic_field( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + def __init__(self, projective_model, f, h, genus): + super().__init__(projective_model, f, h, genus) + + # The functions below were prototyped at the 2007 Arizona Winter School by + # Robert Bradshaw and Ralf Gerkmann, working with Miljan Brakovevic and + # Kiran Kedlaya + # All of the below is with respect to the Monsky Washnitzer cohomology. + + def local_analytic_interpolation(self, P, Q): + """ + For points `P`, `Q` in the same residue disc, + this constructs an interpolation from `P` to `Q` + (in weighted homogeneous coordinates) in a power series in + the local parameter `t`, with precision equal to + the `p`-adic precision of the underlying ring. + + INPUT: + + - P and Q points on self in the same residue disc + + OUTPUT: + + Returns a point `X(t) = ( x(t) : y(t) : z(t) )` such that: + + (1) `X(0) = P` and `X(1) = Q` if `P, Q` are not in the infinite disc + (2) `X(P[1]/P[0]^(g+1)) = P` and `X(Q[1]/Q[0]^(g+1)) = Q` if `P, Q` are in the infinite disc + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + + A non-Weierstrass disc:: + + sage: P = HK(0,3) + sage: Q = HK(5, 3 + 3*5^2 + 2*5^3 + 3*5^4 + 2*5^5 + 2*5^6 + 3*5^7 + O(5^8)) + sage: x,y,z, = HK.local_analytic_interpolation(P,Q) + sage: x(0) == P[0], x(1) == Q[0], y(0) == P[1], y.polynomial()(1) == Q[1] + (True, True, True, True) + + A finite Weierstrass disc:: + + sage: P = HK.lift_x(1 + 2*5^2) + sage: Q = HK.lift_x(1 + 3*5^2) + sage: x,y,z = HK.local_analytic_interpolation(P,Q) + sage: x(0) == P[0], x.polynomial()(1) == Q[0], y(0) == P[1], y(1) == Q[1] + (True, True, True, True) + + The infinite disc:: + + sage: g = HK.genus() + sage: P = HK.lift_x(5^-2) + sage: Q = HK.lift_x(4*5^-2) + sage: x,y,z = HK.local_analytic_interpolation(P,Q) + sage: x = x/z + sage: y = y/z^(g+1) + sage: x(P[1]/P[0]^(g+1)) == P[0] + True + sage: x(Q[1]/Q[0]^(g+1)) == Q[0] + True + sage: y(P[1]/P[0]^(g+1)) == P[1] + True + sage: y(Q[1]/Q[0]^(g+1)) == Q[1] + True + + An error if points are not in the same disc:: + + sage: x,y,z = HK.local_analytic_interpolation(P,HK(1,0)) + Traceback (most recent call last): + ... + ValueError: (5^-2 + O(5^6) : 4*5^-3 + 4*5^-2 + 4*5^-1 + 4 + 4*5 + 3*5^3 + 5^4 + O(5^5) : 1 + O(5^8)) and (1 + O(5^8) : 0 : 1 + O(5^8)) are not in the same residue disc + + TESTS: + + Check that :issue:`26005` is fixed:: + + sage: L = Qp(5, 100) + sage: HL = H.change_ring(L) + sage: P = HL.lift_x(1 + 2*5^2) + sage: Q = HL.lift_x(1 + 3*5^2) + sage: x,y,z = HL.local_analytic_interpolation(P, Q) + sage: x.polynomial().degree() + 98 + + AUTHORS: + + - Robert Bradshaw (2007-03) + - Jennifer Balakrishnan (2010-02) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + # TODO: adapt to general curve form. + prec = self.base_ring().precision_cap() + if not self.is_same_disc(P, Q): + raise ValueError(f"{P} and {Q} are not in the same residue disc") + disc = self.residue_disc(P) + t = PowerSeriesRing(self.base_ring(), "t", prec).gen(0) + if disc == self.change_ring(self.base_ring().residue_field())( + 1, 0, 0 + ): # Infinite disc + x, y = self.local_coordinates_at_infinity_ramified(2 * prec) + g = self.genus() + return (x * t**2, y * t ** (2 * g + 2), t ** (2)) + if disc[1] != 0: # non-Weierstrass disc + x = P[0] + t * (Q[0] - P[0]) + pts = self.lift_x(x, all=True) + if pts[0][1][0] == P[1]: + return pts[0] + else: + return pts[1] + else: # Weierstrass disc + S = self.find_char_zero_weierstrass_point(P) + x, y = self.local_coord(S, prec) + a = P[1] + b = Q[1] - P[1] + y = a + b * t + x = x.polynomial()(y).add_bigoh(x.prec()) + return (x, y, 1) + + def is_in_weierstrass_disc(self, P): + """ + Checks if `P` is in a Weierstrass disc. + + EXAMPLES:: + + For odd degree models, the points with `y`-coordinate equivalent to zero + are contained in a Weierstrass discs:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK(0,3) + sage: HK.is_in_weierstrass_disc(P) + False + sage: Q = HK(1,0,0) + sage: HK.is_in_weierstrass_disc(Q) + True + sage: S = HK(1,0) + sage: HK.is_in_weierstrass_disc(S) + True + sage: T = HK.lift_x(1+3*5^2); T + (1 + 3*5^2 + O(5^8) : 3*5 + 4*5^2 + 5^4 + 3*5^5 + 5^6 + O(5^7) : 1 + O(5^8)) + sage: HK.is_in_weierstrass_disc(T) + True + + The method is also implemented for general models of hyperelliptic + curves:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3, x^2+1) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1+3*5+4*5^2+4*5^3); P + (1 + 3*5 + 4*5^2 + 4*5^3 + O(5^8) : 4 + 5 + 4*5^2 + 5^3 + 3*5^4 + 5^5 + O(5^6) : 1 + O(5^8)) + sage: HK.is_in_weierstrass_disc(P) + True + + Note that `y = - y - h(x)` for Weierstrass points. We check that this relation + is satisfied for the point above (mod p):: + + sage: f,h = H.hyperelliptic_polynomials() + sage: (2*P[1] + h(P[0])).valuation() > 0 + True + + AUTHOR: + + - Jennifer Balakrishnan (2010-02) + """ + + f, h = self.hyperelliptic_polynomials() + if P[2].valuation() > P[0].valuation(): # infinite W-disc + return self.is_ramified() + else: # affine W-disc + x, y = self.affine_coordinates(P) + return (2 * y + h(x)).valuation() > 0 + + def find_char_zero_weierstrass_point(self, Q): + """ + Given `Q` a point on self in a Weierstrass disc, finds the + center of the Weierstrass disc (if defined over self.base_ring()) + + EXAMPLES:: + + Examples for a hyperelliptic curve with odd degree model:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1 + 2*5^2) + sage: Q = HK.lift_x(5^-2) + sage: S = HK(1,0) + sage: T = HK(1,0,0) + sage: HK.find_char_zero_weierstrass_point(P) + (1 + O(5^8) : 0 : 1 + O(5^8)) + sage: HK.find_char_zero_weierstrass_point(Q) + (1 + O(5^8) : 0 : 0) + sage: HK.find_char_zero_weierstrass_point(S) + (1 + O(5^8) : 0 : 1 + O(5^8)) + sage: HK.find_char_zero_weierstrass_point(T) + (1 + O(5^8) : 0 : 0) + + An example for a hyperelltiptic curve with split model:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3, x^2+1) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1+3*5+4*5^2+4*5^3) + sage: HK.find_char_zero_weierstrass_point(P) + (1 + 3*5 + 4*5^2 + 4*5^3 + 3*5^4 + 2*5^6 + 4*5^7 + O(5^8) : 4 + 5 + 3*5^2 + 4*5^3 + 2*5^5 + 4*5^6 + 4*5^7 + O(5^8) : 1 + O(5^8)) + + The input needs to be a point in a Weierstrass disc, + otherwise an error is returned:: + + sage: Q = HK.point([1,1,0]) + sage: HK.find_char_zero_weierstrass_point(Q) + Traceback (most recent call last): + ... + ValueError: (1 + O(5^8) : 1 + O(5^8) : 0) is not in a Weierstrass disc. + + AUTHOR: + + - Jennifer Balakrishnan + """ + if not self.is_in_weierstrass_disc(Q): + raise ValueError("%s is not in a Weierstrass disc." % Q) + points = self.rational_weierstrass_points() + for P in points: + if self.is_same_disc(P, Q): + return P + + def residue_disc(self, P): + """ + Gives the residue disc of `P` + + TODO: Really, this gives the reduction over the residue field. Isn't the residue disc a disc over the p-adics? + Maybe rename to residue_point or reduction ? + + EXAMPLES:: + + We compute the residue discs for diffferent points on the elliptic curve + `y^2 = x^3 = 10*x + 9` over the `5`-adics:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1 + 2*5^2) + sage: HK.residue_disc(P) + (1 : 0 : 1) + sage: Q = HK(0,3) + sage: HK.residue_disc(Q) + (0 : 3 : 1) + sage: S = HK.lift_x(5^-2) + sage: HK.residue_disc(S) + (1 : 0 : 0) + sage: T = HK(1,0,0) + sage: HK.residue_disc(T) + (1 : 0 : 0) + + We can also compute residue discs for points on curves with a split or inert model:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3, x^2+1) + sage: H.is_split() + True + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1+3*5+4*5^2+4*5^3) + sage: Pbar = HK.residue_disc(P); Pbar + (1 : 4 : 1) + + We note that `P` is in a Weierstrass disc and its reduction is indeed a Weierstrass point. + sage: HK.is_in_weierstrass_disc(P) + True + sage: HF = HK.change_ring(FiniteField(5)) + sage: HF.is_weierstrass_point(Pbar) + True + + AUTHOR: + + - Jennifer Balakrishnan + """ + + F = self.base_ring().residue_field() + HF = self.change_ring(F) + + if not P[2].is_zero(): + xP, yP = self.affine_coordinates(P) + xPv = xP.valuation() + yPv = yP.valuation() + + if yPv > 0: + if xPv > 0: + return HF(0, 0, 1) + if xPv == 0: + return HF(xP.expansion(0), 0, 1) + elif yPv == 0: + if xPv > 0: + return HF(0, yP.expansion(0), 1) + if xPv == 0: + return HF(xP.expansion(0), yP.expansion(0), 1) + + # in any other case, the point reduces to infinity. + if HF.is_ramified(): + return HF.points_at_infinity()[0] + elif HF.is_split(): + [Q1, Q2] = HF.points_at_infinity() + alpha = P[1].expansion(0) / P[0].expansion(0) ** (self.genus() + 1) + if ( + alpha == Q1[1] + ): # we assume that the points at infinity are normalized, w.r.t. x ! + return Q1 + if alpha == Q2[1]: + return Q2 + else: + raise ValueError("Unexpected behaviour.") + else: + raise ValueError( + "The reduction of the hyperelliptic curve is inert. This case should not appear." + ) + + def is_same_disc(self, P, Q): + """ + Checks if `P,Q` are in same residue disc + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1 + 2*5^2) + sage: Q = HK.lift_x(5^-2) + sage: S = HK(1,0) + sage: HK.is_same_disc(P,Q) + False + sage: HK.is_same_disc(P,S) + True + sage: HK.is_same_disc(Q,S) + False + """ + return self.residue_disc(P) == self.residue_disc(Q) + + def tiny_integrals(self, F, P, Q): + r""" + Evaluate the integrals of `f_i dx/2y` from `P` to `Q` for each `f_i` in `F` + by formally integrating a power series in a local parameter `t` + + `P` and `Q` MUST be in the same residue disc for this result to make sense. + + INPUT: + + - F a list of functions `f_i` + - P a point on self + - Q a point on self (in the same residue disc as P) + + OUTPUT: + + The integrals `\int_P^Q f_i dx/2y` + + EXAMPLES:: + + sage: K = pAdicField(17, 5) + sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a + sage: P = E(K(14/3), K(11/2)) + sage: TP = E.teichmuller(P); + sage: x,y = E.monsky_washnitzer_gens() + sage: E.tiny_integrals([1,x],P, TP) == E.tiny_integrals_on_basis(P,TP) + True + + :: + + sage: K = pAdicField(11, 5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C.lift_x(11^(-2)) + sage: Q = C.lift_x(3*11^(-2)) + sage: C.tiny_integrals([1],P,Q) + (5*11^3 + 7*11^4 + 2*11^5 + 6*11^6 + 11^7 + O(11^8)) + + Note that this fails if the points are not in the same residue disc:: + + sage: S = C(0,1/4) + sage: C.tiny_integrals([1,x,x^2,x^3],P,S) + Traceback (most recent call last): + ... + ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and + (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc + + """ + if self.hyperelliptic_polynomials()[1]: + raise NotImplementedError + if not self.is_ramified(): + raise NotImplementedError + + g = self.genus() + x, y, z = self.local_analytic_interpolation(P, Q) # homogeneous coordinates + x = x / z + y = y / z ** (g + 1) + dt = x.derivative() / (2 * y) + integrals = [] + for f in F: + try: + f_dt = f(x, y) * dt + except TypeError: # if f is a constant, not callable + f_dt = f * dt + if x.valuation() != -2: + I = sum( + f_dt[n] / (n + 1) for n in range(f_dt.degree() + 1) + ) # \int_0^1 f dt + else: + If_dt = f_dt.integral().laurent_polynomial() + I = If_dt(Q[1] / Q[0] ** (g + 1)) - If_dt(P[1] / P[0] ** (g + 1)) + integrals.append(I) + return vector(integrals) + + def tiny_integrals_on_basis(self, P, Q): + r""" + Evaluate the integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + by formally integrating a power series in a local parameter `t`. + `P` and `Q` MUST be in the same residue disc for this result to make sense. + + INPUT: + + - P a point on self + - Q a point on self (in the same residue disc as P) + + OUTPUT: + + The integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + + EXAMPLES:: + + sage: K = pAdicField(17, 5) + sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a + sage: P = E(K(14/3), K(11/2)) + sage: TP = E.teichmuller(P); + sage: E.tiny_integrals_on_basis(P, TP) + (17 + 14*17^2 + 17^3 + 8*17^4 + O(17^5), 16*17 + 5*17^2 + 8*17^3 + 14*17^4 + O(17^5)) + + :: + + sage: K = pAdicField(11, 5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C.lift_x(11^(-2)) + sage: Q = C.lift_x(3*11^(-2)) + sage: C.tiny_integrals_on_basis(P,Q) + (5*11^3 + 7*11^4 + 2*11^5 + 6*11^6 + 11^7 + O(11^8), 10*11 + 2*11^3 + 3*11^4 + 5*11^5 + O(11^6), 5*11^-1 + 8 + 4*11 + 10*11^2 + 7*11^3 + O(11^4), 2*11^-3 + 11^-2 + 11^-1 + 10 + 8*11 + O(11^2)) + + + Note that this fails if the points are not in the same residue disc:: + + sage: S = C(0,1/4) + sage: C.tiny_integrals_on_basis(P,S) + Traceback (most recent call last): + ... + ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc + + """ + if P == Q: + V = VectorSpace(self.base_ring(), 2 * self.genus()) + return V(0) + R = PolynomialRing(self.base_ring(), ["x", "y"]) + x, y = R.gens() + return self.tiny_integrals([x**i for i in range(2 * self.genus())], P, Q) + + def teichmuller(self, P): + r""" + Find a Teichm\:uller point in the same residue class of `P`. + + Because this lift of frobenius acts as `x \mapsto x^p`, + take the Teichmuller lift of `x` and then find a matching `y` + from that. + + EXAMPLES:: + + sage: K = pAdicField(7, 5) + sage: R. = K[] + sage: E = HyperellipticCurveSmoothModel(x^3 - 31/3*x - 2501/108) # 11a + sage: P = E(K(14/3), K(11/2)) + sage: E.frobenius(P) == P + False + sage: TP = E.teichmuller(P); TP + (0 : 2 + 3*7 + 3*7^2 + 3*7^4 + O(7^5) : 1 + O(7^5)) + sage: E.frobenius(TP) == TP + True + sage: (TP[0] - P[0]).valuation() > 0, (TP[1] - P[1]).valuation() > 0 + (True, True) + """ + K = P[0].parent() + x = K.teichmuller(P[0]) + pts = self.lift_x(x, all=True) + if (pts[0][1] - P[1]).valuation() > 0: + return pts[0] + else: + return pts[1] + + def coleman_integrals_on_basis(self, P, Q, algorithm=None): + r""" + Computes the Coleman integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + + INPUT: + + - P point on self + - Q point on self + - algorithm (optional) = None (uses Frobenius) or teichmuller (uses Teichmuller points) + + OUTPUT: + + the Coleman integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + + EXAMPLES:: + + sage: K = pAdicField(11, 5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C.lift_x(2) + sage: Q = C.lift_x(3) + sage: C.coleman_integrals_on_basis(P, Q) + (9*11^2 + 7*11^3 + 5*11^4 + O(11^5), 11 + 3*11^2 + 7*11^3 + 11^4 + O(11^5), 10*11 + 11^2 + 5*11^3 + 5*11^4 + O(11^5), 3 + 9*11^2 + 6*11^3 + 11^4 + O(11^5)) + sage: C.coleman_integrals_on_basis(P, Q, algorithm='teichmuller') + (9*11^2 + 7*11^3 + 5*11^4 + O(11^5), 11 + 3*11^2 + 7*11^3 + 11^4 + O(11^5), 10*11 + 11^2 + 5*11^3 + 5*11^4 + O(11^5), 3 + 9*11^2 + 6*11^3 + 11^4 + O(11^5)) + + :: + + sage: K = pAdicField(11,5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C(11^-2, 10*11^-5 + 10*11^-4 + 10*11^-3 + 2*11^-2 + 10*11^-1) + sage: Q = C(3*11^-2, 11^-5 + 11^-3 + 10*11^-2 + 7*11^-1) + sage: C.coleman_integrals_on_basis(P, Q) + (6*11^3 + 3*11^4 + 8*11^5 + 4*11^6 + 9*11^7 + O(11^8), 11 + 10*11^2 + 8*11^3 + 7*11^4 + 5*11^5 + O(11^6), 6*11^-1 + 2 + 6*11 + 3*11^3 + O(11^4), 9*11^-3 + 9*11^-2 + 9*11^-1 + 2*11 + O(11^2)) + + :: + + sage: R = C(0,1/4) + sage: a = C.coleman_integrals_on_basis(P,R) # long time (7s on sage.math, 2011) + sage: b = C.coleman_integrals_on_basis(R,Q) # long time (9s on sage.math, 2011) + sage: c = C.coleman_integrals_on_basis(P,Q) # long time + sage: a+b == c # long time + True + + :: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: S = HK(1,0) + sage: P = HK(0,3) + sage: T = HK(1,0,0) + sage: Q = HK.lift_x(5^-2) + sage: R = HK.lift_x(4*5^-2) + sage: HK.coleman_integrals_on_basis(S,P) + (2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9), 5 + 2*5^2 + 4*5^3 + 2*5^4 + 3*5^6 + 4*5^7 + 2*5^8 + O(5^9)) + sage: HK.coleman_integrals_on_basis(T,P) + (2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9), 5 + 2*5^2 + 4*5^3 + 2*5^4 + 3*5^6 + 4*5^7 + 2*5^8 + O(5^9)) + sage: HK.coleman_integrals_on_basis(P,S) == -HK.coleman_integrals_on_basis(S,P) + True + sage: HK.coleman_integrals_on_basis(S,Q) + (5 + O(5^4), 4*5^-1 + 4 + 4*5 + 4*5^2 + O(5^3)) + sage: HK.coleman_integrals_on_basis(Q,R) + (5 + 2*5^2 + 2*5^3 + 2*5^4 + 3*5^5 + 3*5^6 + 3*5^7 + 5^8 + O(5^9), 3*5^-1 + 2*5^4 + 5^5 + 2*5^6 + O(5^7)) + sage: HK.coleman_integrals_on_basis(S,R) == HK.coleman_integrals_on_basis(S,Q) + HK.coleman_integrals_on_basis(Q,R) + True + sage: HK.coleman_integrals_on_basis(T,T) + (0, 0) + sage: HK.coleman_integrals_on_basis(S,T) + (0, 0) + + AUTHORS: + + - Robert Bradshaw (2007-03): non-Weierstrass points + - Jennifer Balakrishnan and Robert Bradshaw (2010-02): Weierstrass points + """ + + if self.hyperelliptic_polynomials()[1]: + raise NotImplementedError + if not self.is_ramified(): + raise NotImplementedError + + from sage.misc.profiler import Profiler + from sage.schemes.hyperelliptic_curves import monsky_washnitzer + + prof = Profiler() + prof("setup") + K = self.base_ring() + p = K.prime() + prec = K.precision_cap() + g = self.genus() + dim = 2 * g + V = VectorSpace(K, dim) + # if P or Q is Weierstrass, use the Frobenius algorithm + if self.is_weierstrass_point(P): + if self.is_weierstrass_point(Q): + return V(0) + else: + PP = None + QQ = Q + TP = None + TQ = self.frobenius(Q) + elif self.is_weierstrass_point(Q): + PP = P + QQ = None + TQ = None + TP = self.frobenius(P) + elif self.is_same_disc(P, Q): + return self.tiny_integrals_on_basis(P, Q) + elif algorithm == "teichmuller": + prof("teichmuller") + PP = TP = self.teichmuller(P) + QQ = TQ = self.teichmuller(Q) + else: + prof("frobPQ") + TP = self.frobenius(P) + TQ = self.frobenius(Q) + PP, QQ = P, Q + prof("tiny integrals") + if TP is None: + P_to_TP = V(0) + else: + if TP is not None: + TPv = (TP[0] ** g / TP[1]).valuation() + xTPv = TP[0].valuation() + else: + xTPv = TPv = +Infinity + if TQ is not None: + TQv = (TQ[0] ** g / TQ[1]).valuation() + xTQv = TQ[0].valuation() + else: + xTQv = TQv = +Infinity + offset = (2 * g - 1) * max(TPv, TQv) + if offset == +Infinity: + offset = (2 * g - 1) * min(TPv, TQv) + if ( + offset > prec + and (xTPv < 0 or xTQv < 0) + and ( + self.residue_disc(P) == self.change_ring(GF(p))(1, 0, 0) + or self.residue_disc(Q) == self.change_ring(GF(p))(1, 0, 0) + ) + ): + newprec = offset + prec + K = pAdicField(p, newprec) + A = PolynomialRing(RationalField(), "x") + f = A(self.hyperelliptic_polynomials()[0]) + self = HyperellipticCurveSmoothModel(f).change_ring(K) + xP = P[0] + xPv = xP.valuation() + xPnew = K(sum(c * p ** (xPv + i) for i, c in enumerate(xP.expansion()))) + PP = P = self.lift_x(xPnew) + TP = self.frobenius(P) + xQ = Q[0] + xQv = xQ.valuation() + xQnew = K(sum(c * p ** (xQv + i) for i, c in enumerate(xQ.expansion()))) + QQ = Q = self.lift_x(xQnew) + TQ = self.frobenius(Q) + V = VectorSpace(K, dim) + P_to_TP = V(self.tiny_integrals_on_basis(P, TP)) + if TQ is None: + TQ_to_Q = V(0) + else: + TQ_to_Q = V(self.tiny_integrals_on_basis(TQ, Q)) + prof("mw calc") + try: + M_frob, forms = self._frob_calc + except AttributeError: + M_frob, forms = self._frob_calc = ( + monsky_washnitzer.matrix_of_frobenius_hyperelliptic(self) + ) + prof("eval f") + R = forms[0].base_ring() + try: + prof("eval f %s" % R) + if PP is None: + L = [-ff(R(QQ[0]), R(QQ[1])) for ff in forms] ##changed + elif QQ is None: + L = [ff(R(PP[0]), R(PP[1])) for ff in forms] + else: + L = [ff(R(PP[0]), R(PP[1])) - ff(R(QQ[0]), R(QQ[1])) for ff in forms] + except ValueError: + prof("changing rings") + forms = [ff.change_ring(self.base_ring()) for ff in forms] + prof("eval f %s" % self.base_ring()) + if PP is None: + L = [-ff(QQ[0], QQ[1]) for ff in forms] ##changed + elif QQ is None: + L = [ff(PP[0], PP[1]) for ff in forms] + else: + L = [ff(PP[0], PP[1]) - ff(QQ[0], QQ[1]) for ff in forms] + b = V(L) + if PP is None: + b -= TQ_to_Q + elif QQ is None: + b -= P_to_TP + elif algorithm != "teichmuller": + b -= P_to_TP + TQ_to_Q + prof("lin alg") + M_sys = matrix(K, M_frob).transpose() - 1 + TP_to_TQ = M_sys ** (-1) * b + prof("done") + if algorithm == "teichmuller": + return P_to_TP + TP_to_TQ + TQ_to_Q + else: + return TP_to_TQ + + coleman_integrals_on_basis_hyperelliptic = coleman_integrals_on_basis + + def coleman_integral(self, w, P, Q, algorithm="None"): + r""" + Return the Coleman integral `\int_P^Q w`. + + INPUT: + + - w differential (if one of P,Q is Weierstrass, w must be odd) + - P point on self + - Q point on self + - algorithm (optional) = None (uses Frobenius) or teichmuller (uses Teichmuller points) + + OUTPUT: + + the Coleman integral `\int_P^Q w` + + EXAMPLES: + + Example of Leprevost from Kiran Kedlaya + The first two should be zero as `(P-Q) = 30(P-Q)` in the Jacobian + and `dx/2y` and `x dx/2y` are holomorphic. :: + + sage: K = pAdicField(11, 6) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C(-1, 1); P1 = C(-1, -1) + sage: Q = C(0, 1/4); Q1 = C(0, -1/4) + sage: x, y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w.coleman_integral(P, Q) + O(11^6) + sage: C.coleman_integral(x*w, P, Q) + O(11^6) + sage: C.coleman_integral(x^2*w, P, Q) + 7*11 + 6*11^2 + 3*11^3 + 11^4 + 5*11^5 + O(11^6) + + :: + + sage: p = 71; m = 4 + sage: K = pAdicField(p, m) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C(-1, 1); P1 = C(-1, -1) + sage: Q = C(0, 1/4); Q1 = C(0, -1/4) + sage: x, y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w.integrate(P, Q), (x*w).integrate(P, Q) + (O(71^4), O(71^4)) + sage: R, R1 = C.lift_x(4, all=True) + sage: w.integrate(P, R) + 50*71 + 3*71^2 + 43*71^3 + O(71^4) + sage: w.integrate(P, R) + w.integrate(P1, R1) + O(71^4) + + A simple example, integrating dx:: + + sage: R. = QQ['x'] + sage: E= HyperellipticCurveSmoothModel(x^3-4*x+4) + sage: K = Qp(5,10) + sage: EK = E.change_ring(K) + sage: P = EK(2, 2) + sage: Q = EK.teichmuller(P) + sage: x, y = EK.monsky_washnitzer_gens() + sage: EK.coleman_integral(x.diff(), P, Q) + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10) + sage: Q[0] - P[0] + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10) + + Yet another example:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x*(x-1)*(x+9)) + sage: K = Qp(7,10) + sage: HK = H.change_ring(K) + sage: import monsky_washnitzer as mw + sage: M_frob, forms = mw.matrix_of_frobenius_hyperelliptic(HK) + sage: w = HK.invariant_differential() + sage: x,y = HK.monsky_washnitzer_gens() + sage: f = forms[0] + sage: S = HK(9,36) + sage: Q = HK.teichmuller(S) + sage: P = HK(-1,4) + sage: b = x*w*w._coeff.parent()(f) + sage: HK.coleman_integral(b,P,Q) + 7 + 7^2 + 4*7^3 + 5*7^4 + 3*7^5 + 7^6 + 5*7^7 + 3*7^8 + 4*7^9 + 4*7^10 + O(7^11) + + :: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3+1) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: w = HK.invariant_differential() + sage: P = HK(0,1) + sage: Q = HK(5, 1 + 3*5^3 + 2*5^4 + 2*5^5 + 3*5^7) + sage: x,y = HK.monsky_washnitzer_gens() + sage: (2*y*w).coleman_integral(P,Q) + 5 + O(5^9) + sage: xloc,yloc,zloc = HK.local_analytic_interpolation(P,Q) + sage: I2 = (xloc.derivative()/(2*yloc)).integral() + sage: I2.polynomial()(1) - I2(0) + 3*5 + 2*5^2 + 2*5^3 + 5^4 + 4*5^6 + 5^7 + O(5^9) + sage: HK.coleman_integral(w,P,Q) + 3*5 + 2*5^2 + 2*5^3 + 5^4 + 4*5^6 + 5^7 + O(5^9) + + Integrals involving Weierstrass points:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: S = HK(1,0) + sage: P = HK(0,3) + sage: negP = HK(0,-3) + sage: T = HK(1,0,0) + sage: w = HK.invariant_differential() + sage: x,y = HK.monsky_washnitzer_gens() + sage: HK.coleman_integral(w*x^3,S,T) + 0 + sage: HK.coleman_integral(w*x^3,T,S) + 0 + sage: HK.coleman_integral(w,S,P) + 2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9) + sage: HK.coleman_integral(w,T,P) + 2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9) + sage: HK.coleman_integral(w*x^3,T,P) + 5^2 + 2*5^3 + 3*5^6 + 3*5^7 + O(5^8) + sage: HK.coleman_integral(w*x^3,S,P) + 5^2 + 2*5^3 + 3*5^6 + 3*5^7 + O(5^8) + sage: HK.coleman_integral(w, P, negP, algorithm='teichmuller') + 5^2 + 4*5^3 + 2*5^4 + 2*5^5 + 3*5^6 + 2*5^7 + 4*5^8 + O(5^9) + sage: HK.coleman_integral(w, P, negP) + 5^2 + 4*5^3 + 2*5^4 + 2*5^5 + 3*5^6 + 2*5^7 + 4*5^8 + O(5^9) + + AUTHORS: + + - Robert Bradshaw (2007-03) + - Kiran Kedlaya (2008-05) + - Jennifer Balakrishnan (2010-02) + + """ + # TODO: exceptions for general curve form + # TODO: implement Jacobians and show the relationship directly + from sage.schemes.hyperelliptic_curves import monsky_washnitzer + + K = self.base_ring() + prec = K.precision_cap() + S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self, K) + MW = monsky_washnitzer.MonskyWashnitzerDifferentialRing(S) + w = MW(w) + f, vec = w.reduce_fast() + basis_values = self.coleman_integrals_on_basis(P, Q, algorithm) + dim = len(basis_values) + x, y = self.local_coordinates_at_infinity_ramified(2 * prec) + if self.is_weierstrass_point(P): + if self.is_weierstrass_point(Q): + return 0 + elif f == 0: + return sum([vec[i] * basis_values[i] for i in range(dim)]) + elif ( + w._coeff(x, -y) * x.derivative() / (-2 * y) + + w._coeff(x, y) * x.derivative() / (2 * y) + == 0 + ): + return ( + self.coleman_integral( + w, self(Q[0], -Q[1]), self(Q[0], Q[1]), algorithm + ) + / 2 + ) + else: + raise ValueError( + "The differential is not odd: use coleman_integral_from_weierstrass_via_boundary" + ) + + elif self.is_weierstrass_point(Q): + if f == 0: + return sum([vec[i] * basis_values[i] for i in range(dim)]) + elif ( + w._coeff(x, -y) * x.derivative() / (-2 * y) + + w._coeff(x, y) * x.derivative() / (2 * y) + == 0 + ): + return ( + -self.coleman_integral( + w, self(P[0], -P[1]), self(P[0], P[1]), algorithm + ) + / 2 + ) + else: + raise ValueError( + "The differential is not odd: use coleman_integral_from_weierstrass_via_boundary" + ) + else: + return ( + f(Q[0], Q[1]) + - f(P[0], P[1]) + + sum([vec[i] * basis_values[i] for i in range(dim)]) + ) # this is just a dot product... + + def frobenius(self, P=None): + """ + Returns the `p`-th power lift of Frobenius of `P` + + EXAMPLES:: + + sage: K = Qp(11, 5) + sage: R. = K[] + sage: E = HyperellipticCurveSmoothModel(x^5 - 21*x - 20) + sage: P = E.lift_x(2) + sage: E.frobenius(P) + (2 + 10*11 + 5*11^2 + 11^3 + O(11^5) : 6 + 11 + 8*11^2 + 8*11^3 + 10*11^4 + O(11^5) : 1 + O(11^5)) + sage: Q = E.teichmuller(P); Q + (2 + 10*11 + 4*11^2 + 9*11^3 + 11^4 + O(11^5) : 6 + 11 + 4*11^2 + 9*11^3 + 4*11^4 + O(11^5) : 1 + O(11^5)) + sage: E.frobenius(Q) + (2 + 10*11 + 4*11^2 + 9*11^3 + 11^4 + O(11^5) : 6 + 11 + 4*11^2 + 9*11^3 + 4*11^4 + O(11^5) : 1 + O(11^5)) + + :: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5-23*x^3+18*x^2+40*x) + sage: Q = H(0,0) + sage: u,v = H.local_coord(Q,prec=100) + sage: K = Qp(11,5) + sage: L. = K.extension(x^20-11) + sage: HL = H.change_ring(L) + sage: S = HL(u(a),v(a)) + sage: HL.frobenius(S) + (8*a^22 + 10*a^42 + 4*a^44 + 2*a^46 + 9*a^48 + 8*a^50 + a^52 + 7*a^54 + + 7*a^56 + 5*a^58 + 9*a^62 + 5*a^64 + a^66 + 6*a^68 + a^70 + 6*a^74 + + 2*a^76 + 2*a^78 + 4*a^82 + 5*a^84 + 2*a^86 + 7*a^88 + a^90 + 6*a^92 + + a^96 + 5*a^98 + 2*a^102 + 2*a^106 + 6*a^108 + 8*a^110 + 3*a^112 + + a^114 + 8*a^116 + 10*a^118 + 3*a^120 + O(a^122) : + a^11 + 7*a^33 + 7*a^35 + 4*a^37 + 6*a^39 + 9*a^41 + 8*a^43 + 8*a^45 + + a^47 + 7*a^51 + 4*a^53 + 5*a^55 + a^57 + 7*a^59 + 5*a^61 + 9*a^63 + + 4*a^65 + 10*a^69 + 3*a^71 + 2*a^73 + 9*a^75 + 10*a^77 + 6*a^79 + + 10*a^81 + 7*a^85 + a^87 + 4*a^89 + 8*a^91 + a^93 + 8*a^95 + 2*a^97 + + 7*a^99 + a^101 + 3*a^103 + 6*a^105 + 7*a^107 + 4*a^109 + O(a^111) : + 1 + O(a^100)) + + AUTHORS: + + - Robert Bradshaw and Jennifer Balakrishnan (2010-02) + """ + if self.hyperelliptic_polynomials()[1]: + raise NotImplementedError + if not self.is_ramified(): + raise NotImplementedError + + try: + _frob = self._frob + except AttributeError: + K = self.base_ring() + p = K.prime() + x = K["x"].gen(0) + + f, f2 = self.hyperelliptic_polynomials() + if f2 != 0: + raise NotImplementedError("Curve must be in weierstrass normal form.") + h = f(x**p) - f**p + + def _frob(P): + if P == self(1, 0, 0): + return P + x0 = P[0] + y0 = P[1] + try: + uN = (1 + h(x0) / y0 ** (2 * p)).sqrt() + yres = y0**p * uN + xres = x0**p + if (yres - y0).valuation() == 0: + yres = -yres + return self.point([xres, yres, K(1)]) + except (TypeError, NotImplementedError): + uN2 = 1 + h(x0) / y0 ** (2 * p) + # yfrob2 = f(x) + c = uN2.expansion(0) + v = uN2.valuation() + a = uN2.parent().gen() + uN = self.newton_sqrt( + uN2, c.sqrt() * a ** (v // 2), K.precision_cap() + ) + yres = y0**p * uN + xres = x0**p + if (yres - y0).valuation() == 0: + yres = -yres + try: + return self(xres, yres) + except ValueError: + return self._curve_over_ram_extn(xres, yres) + + self._frob = _frob + + if P is None: + return _frob + else: + return _frob(P) + + def newton_sqrt(self, f, x0, prec): + r""" + Takes the square root of the power series `f` by Newton's method + + NOTE: + + this function should eventually be moved to `p`-adic power series ring + + INPUT: + + - ``f`` -- power series with coefficients in `\QQ_p` or an extension + - ``x0`` -- seeds the Newton iteration + - ``prec`` -- precision + + OUTPUT: the square root of `f` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5-23*x^3+18*x^2+40*x) + sage: Q = H(0,0) + sage: u,v = H.local_coord(Q,prec=100) + sage: K = Qp(11,5) + sage: HK = H.change_ring(K) + sage: L. = K.extension(x^20-11) + sage: HL = H.change_ring(L) + sage: S = HL(u(a),v(a)) + sage: f = H.hyperelliptic_polynomials()[0] + sage: y = HK.newton_sqrt( f(u(a)^11), a^11,5) + sage: y^2 - f(u(a)^11) + O(a^122) + + AUTHOR: + + - Jennifer Balakrishnan + """ + z = x0 + loop_prec = log(RR(prec), 2).ceil() + for i in range(loop_prec): + z = (z + f / z) / 2 + return z + + def curve_over_ram_extn(self, deg): + r""" + Return ``self`` over `\QQ_p(p^(1/deg))`. + + INPUT: + + - deg: the degree of the ramified extension + + OUTPUT: + + ``self`` over `\QQ_p(p^(1/deg))` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5-23*x^3+18*x^2+40*x) + sage: K = Qp(11,5) + sage: HK = H.change_ring(K) + sage: HL = HK.curve_over_ram_extn(2) + sage: HL + Hyperelliptic Curve over 11-adic Eisenstein Extension Field in a defined by x^2 - 11 defined by y^2 = x^5 + (10 + 8*a^2 + 10*a^4 + 10*a^6 + 10*a^8 + O(a^10))*x^3 + (7 + a^2 + O(a^10))*x^2 + (7 + 3*a^2 + O(a^10))*x + + AUTHOR: + + - Jennifer Balakrishnan + + """ + # TODO: fix this import for sage + from hyperelliptic_constructor import HyperellipticCurveSmoothModel + + K = self.base_ring() + p = K.prime() + A = PolynomialRing(QQ, "x") + x = A.gen() + J = K.extension(x**deg - p, names="a") + pol = self.hyperelliptic_polynomials()[0] + H = HyperellipticCurveSmoothModel(A(pol)) + HJ = H.change_ring(J) + self._curve_over_ram_extn = HJ + self._curve_over_ram_extn._curve_over_Qp = self + return HJ + + def get_boundary_point(self, curve_over_extn, P): + """ + Given self over an extension field, find a point in the disc of `P` near the boundary + + INPUT: + + - curve_over_extn: self over a totally ramified extension + - P: Weierstrass point + + OUTPUT: + + a point in the disc of `P` near the boundary + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(3,6) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: J. = K.extension(x^30-3) + sage: HJ = H.change_ring(J) + sage: S = HK.get_boundary_point(HJ,P) + sage: S + (1 + 2*a^2 + 2*a^6 + 2*a^18 + a^32 + a^34 + a^36 + 2*a^38 + 2*a^40 + a^42 + 2*a^44 + a^48 + 2*a^50 + 2*a^52 + a^54 + a^56 + 2*a^60 + 2*a^62 + a^70 + 2*a^72 + a^76 + 2*a^78 + a^82 + a^88 + a^96 + 2*a^98 + 2*a^102 + a^104 + 2*a^106 + a^108 + 2*a^110 + a^112 + 2*a^116 + a^126 + 2*a^130 + 2*a^132 + a^144 + 2*a^148 + 2*a^150 + a^152 + 2*a^154 + a^162 + a^164 + a^166 + a^168 + a^170 + a^176 + a^178 + O(a^180) : a + O(a^180) : 1 + O(a^180)) + + AUTHOR: + + - Jennifer Balakrishnan + + """ + J = curve_over_extn.base_ring() + a = J.gen() + prec2 = J.precision_cap() + x, y = self.local_coord(P, prec2) + return curve_over_extn(x(a), y(a)) + + def P_to_S(self, P, S): + r""" + Given a finite Weierstrass point `P` and a point `S` + in the same disc, computes the Coleman integrals `\{\int_P^S x^i dx/2y \}_{i=0}^{2g-1}` + + INPUT: + + - P: finite Weierstrass point + - S: point in disc of P + + OUTPUT: + + Coleman integrals `\{\int_P^S x^i dx/2y \}_{i=0}^{2g-1}` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,4) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: HJ = HK.curve_over_ram_extn(10) + sage: S = HK.get_boundary_point(HJ,P) + sage: HK.P_to_S(P, S) + (2*a + 4*a^3 + 2*a^11 + 4*a^13 + 2*a^17 + 2*a^19 + a^21 + 4*a^23 + a^25 + 2*a^27 + 2*a^29 + 3*a^31 + 4*a^33 + O(a^35), a^-5 + 2*a + 2*a^3 + a^7 + 3*a^11 + a^13 + 3*a^15 + 3*a^17 + 2*a^19 + 4*a^21 + 4*a^23 + 4*a^25 + 2*a^27 + a^29 + a^31 + O(a^33)) + + AUTHOR: + + - Jennifer Balakrishnan + + """ + prec = self.base_ring().precision_cap() + deg = (S[0]).parent().defining_polynomial().degree() + prec2 = prec * deg + x, y = self.local_coord(P, prec2) + g = self.genus() + integrals = [ + ((x**k * x.derivative() / (2 * y)).integral()) for k in range(2 * g) + ] + val = [I(S[1]) for I in integrals] + return vector(val) + + def coleman_integral_P_to_S(self, w, P, S): + r""" + Given a finite Weierstrass point `P` and a point `S` + in the same disc, computes the Coleman integral `\int_P^S w` + + INPUT: + + - w: differential + - P: Weierstrass point + - S: point in the same disc of P (S is defined over an extension of `\QQ_p`; coordinates + of S are given in terms of uniformizer `a`) + + OUTPUT: + + Coleman integral `\int_P^S w` in terms of `a` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,4) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: J. = K.extension(x^10-5) + sage: HJ = H.change_ring(J) + sage: S = HK.get_boundary_point(HJ,P) + sage: x,y = HK.monsky_washnitzer_gens() + sage: S[0]-P[0] == HK.coleman_integral_P_to_S(x.diff(),P,S) + True + sage: HK.coleman_integral_P_to_S(HK.invariant_differential(),P,S) == HK.P_to_S(P,S)[0] + True + + AUTHOR: + + - Jennifer Balakrishnan + + """ + prec = self.base_ring().precision_cap() + deg = S[0].parent().defining_polynomial().degree() + prec2 = prec * deg + x, y = self.local_coord(P, prec2) + int_sing = (w.coeff()(x, y) * x.derivative() / (2 * y)).integral() + int_sing_a = int_sing(S[1]) + return int_sing_a + + def S_to_Q(self, S, Q): + r""" + Given `S` a point on self over an extension field, computes the + Coleman integrals `\{\int_S^Q x^i dx/2y \}_{i=0}^{2g-1}` + + **one should be able to feed `S,Q` into coleman_integral, + but currently that segfaults** + + INPUT: + + - S: a point with coordinates in an extension of `\QQ_p` (with unif. a) + - Q: a non-Weierstrass point defined over `\QQ_p` + + OUTPUT: + + the Coleman integrals `\{\int_S^Q x^i dx/2y \}_{i=0}^{2g-1}` in terms of `a` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,6) + sage: HK = H.change_ring(K) + sage: J. = K.extension(x^20-5) + sage: HJ = H.change_ring(J) + sage: w = HK.invariant_differential() + sage: x,y = HK.monsky_washnitzer_gens() + sage: P = HK(1,0) + sage: Q = HK(0,3) + sage: S = HK.get_boundary_point(HJ,P) + sage: P_to_S = HK.P_to_S(P,S) + sage: S_to_Q = HJ.S_to_Q(S,Q) + sage: P_to_S + S_to_Q + (2*a^40 + a^80 + a^100 + O(a^105), a^20 + 2*a^40 + 4*a^60 + 2*a^80 + O(a^103)) + sage: HK.coleman_integrals_on_basis(P,Q) + (2*5^2 + 5^4 + 5^5 + 3*5^6 + O(5^7), 5 + 2*5^2 + 4*5^3 + 2*5^4 + 5^6 + O(5^7)) + + AUTHOR: + + - Jennifer Balakrishnan + + """ + FS = self.frobenius(S) + FS = (FS[0], FS[1]) + FQ = self.frobenius(Q) + from sage.schemes.hyperelliptic_curves import monsky_washnitzer + + try: + M_frob, forms = self._frob_calc + except AttributeError: + M_frob, forms = self._frob_calc = ( + monsky_washnitzer.matrix_of_frobenius_hyperelliptic(self) + ) + try: + HJ = self._curve_over_ram_extn + K = HJ.base_ring() + except AttributeError: + HJ = S.scheme() + K = self.base_ring() + g = self.genus() + prec2 = K.precision_cap() + p = K.prime() + dim = 2 * g + V = VectorSpace(K, dim) + if S == FS: + S_to_FS = V(dim * [0]) + else: + P = self(ZZ(FS[0].expansion(0)), ZZ(FS[1].expansion(0))) + x, y = self.local_coord(P, prec2) + integrals = [ + (x**i * x.derivative() / (2 * y)).integral() for i in range(dim) + ] + S_to_FS = vector( + [I.polynomial()(FS[1]) - I.polynomial()(S[1]) for I in integrals] + ) + if HJ(Q[0], Q[1], Q[2]) == HJ(FQ[0], FQ[1], FQ[2]): + FQ_to_Q = V(dim * [0]) + else: + FQ_to_Q = V(self.tiny_integrals_on_basis(FQ, Q)) + try: + L = [f(K(S[0]), K(S[1])) - f(K(Q[0]), K(Q[1])) for f in forms] + except ValueError: + forms = [f.change_ring(K) for f in forms] + L = [f(S[0], S[1]) - f(Q[0], Q[1]) for f in forms] + b = V(L) + M_sys = matrix(K, M_frob).transpose() - 1 + B = ~M_sys + vv = min(c.valuation() for c in B.list()) + B = (p ** (-vv) * B).change_ring(K) + B = p ** (vv) * B + return B * (b - S_to_FS - FQ_to_Q) + + def coleman_integral_S_to_Q(self, w, S, Q): + r""" + Compute the Coleman integral `\int_S^Q w` + + **one should be able to feed `S,Q` into coleman_integral, + but currently that segfaults** + + INPUT: + + - w: a differential + - S: a point with coordinates in an extension of `\QQ_p` + - Q: a non-Weierstrass point defined over `\QQ_p` + + OUTPUT: + + the Coleman integral `\int_S^Q w` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,6) + sage: HK = H.change_ring(K) + sage: J. = K.extension(x^20-5) + sage: HJ = H.change_ring(J) + sage: x,y = HK.monsky_washnitzer_gens() + sage: P = HK(1,0) + sage: Q = HK(0,3) + sage: S = HK.get_boundary_point(HJ,P) + sage: P_to_S = HK.coleman_integral_P_to_S(y.diff(),P,S) + sage: S_to_Q = HJ.coleman_integral_S_to_Q(y.diff(),S,Q) + sage: P_to_S + S_to_Q + 3 + O(a^119) + sage: HK.coleman_integral(y.diff(),P,Q) + 3 + O(5^6) + + AUTHOR: + + - Jennifer Balakrishnan + """ + from sage.schemes.hyperelliptic_curves import monsky_washnitzer + + K = self.base_ring() + R = monsky_washnitzer.SpecialHyperellipticQuotientRing(self, K) + MW = monsky_washnitzer.MonskyWashnitzerDifferentialRing(R) + w = MW(w) + f, vec = w.reduce_fast() + g = self.genus() + const = f(Q[0], Q[1]) - f(S[0], S[1]) + if vec == vector(2 * g * [0]): + return const + else: + basis_values = self.S_to_Q(S, Q) + dim = len(basis_values) + dot = sum([vec[i] * basis_values[i] for i in range(dim)]) + return const + dot + + def coleman_integral_from_weierstrass_via_boundary(self, w, P, Q, d): + r""" + Computes the Coleman integral `\int_P^Q w` via a boundary point + in the disc of `P`, defined over a degree `d` extension + + INPUT: + + - w: a differential + - P: a Weierstrass point + - Q: a non-Weierstrass point + - d: degree of extension where coordinates of boundary point lie + + OUTPUT: + + the Coleman integral `\int_P^Q w`, written in terms of the uniformizer + `a` of the degree `d` extension + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,6) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: Q = HK(0,3) + sage: x,y = HK.monsky_washnitzer_gens() + sage: HK.coleman_integral_from_weierstrass_via_boundary(y.diff(),P,Q,20) + 3 + O(a^119) + sage: HK.coleman_integral(y.diff(),P,Q) + 3 + O(5^6) + sage: w = HK.invariant_differential() + sage: HK.coleman_integral_from_weierstrass_via_boundary(w,P,Q,20) + 2*a^40 + a^80 + a^100 + O(a^105) + sage: HK.coleman_integral(w,P,Q) + 2*5^2 + 5^4 + 5^5 + 3*5^6 + O(5^7) + + AUTHOR: + + - Jennifer Balakrishnan + """ + HJ = self.curve_over_ram_extn(d) + S = self.get_boundary_point(HJ, P) + P_to_S = self.coleman_integral_P_to_S(w, P, S) + S_to_Q = HJ.coleman_integral_S_to_Q(w, S, Q) + return P_to_S + S_to_Q diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py new file mode 100644 index 00000000000..0c8ea60a89d --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -0,0 +1,129 @@ +import sage.rings.abc +from sage.rings.padics.factory import Qp as pAdicField +from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic + + +class HyperellipticCurveSmoothModel_rational_field( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + def __init__(self, projective_model, f, h, genus): + super().__init__(projective_model, f, h, genus) + + def matrix_of_frobenius(self, p, prec=20): + """ + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology using + the `p`-adic field with precision ``prec``. + + This function is essentially a wrapper function of + :meth:`sage.schemes.hyperelliptic_curves.monsky_washnitzer.matrix_of_frobenius_hyperelliptic`. + + INPUT: + + - ``p`` (prime integer or pAdic ring / field ) -- if ``p`` is an integer, + constructs a ``pAdicField`` with ``p`` to compute the matrix of + Frobenius, otherwise uses the supplied pAdic ring or field. + + - ``prec`` (optional) -- if ``p`` is an prime integer, the `p`-adic + precision of the coefficient ring constructed + + EXAMPLES:: + + sage: K = pAdicField(5, prec=3) + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 2*x + 3) + sage: H.matrix_of_frobenius(K) + [ 4*5 + O(5^3) 5 + 2*5^2 + O(5^3) 2 + 3*5 + 2*5^2 + O(5^3) 2 + 5 + 5^2 + O(5^3)] + [ 3*5 + 5^2 + O(5^3) 3*5 + O(5^3) 4*5 + O(5^3) 2 + 5^2 + O(5^3)] + [ 4*5 + 4*5^2 + O(5^3) 3*5 + 2*5^2 + O(5^3) 5 + 3*5^2 + O(5^3) 2*5 + 2*5^2 + O(5^3)] + [ 5^2 + O(5^3) 5 + 4*5^2 + O(5^3) 4*5 + 3*5^2 + O(5^3) 2*5 + O(5^3)] + + You can also pass directly a prime `p` with to construct a pAdic field with precision + ``prec``:: + + sage: H.matrix_of_frobenius(3, prec=2) + [ O(3^2) 3 + O(3^2) O(3^2) O(3^2)] + [ 3 + O(3^2) O(3^2) O(3^2) 2 + 3 + O(3^2)] + [ 2*3 + O(3^2) O(3^2) O(3^2) 3^-1 + O(3)] + [ O(3^2) O(3^2) 3 + O(3^2) O(3^2)] + """ + import monsky_washnitzer # TODO fix this + + if isinstance(p, (sage.rings.abc.pAdicField, sage.rings.abc.pAdicRing)): + K = p + else: + K = pAdicField(p, prec) + frob_p, _ = monsky_washnitzer.matrix_of_frobenius_hyperelliptic( + self.change_ring(K) + ) + return frob_p + + def lseries(self, prec=53): + """ + Return the L-series of this hyperelliptic curve of genus 2. + + EXAMPLES:: + + sage: x = polygen(QQ, 'x') + sage: C = HyperellipticCurveSmoothModel(x^2+x, x^3+x^2+1) + sage: C.lseries() + PARI L-function associated to Hyperelliptic Curve + over Rational Field defined by y^2 + (x^3 + x^2 + 1)*y = x^2 + x + """ + from sage.lfunctions.pari import LFunction + + L = LFunction(lfun_genus2(self), prec=prec) + L.rename("PARI L-function associated to %s" % self) + return L + + +# +# +# TODO: this is from `sage/src/sage/lfunctions/pari.py` but it uses the old method +# so I have copy-pasted it here for now with the new curve... +# +# + + +def lfun_genus2(C): + """ + Return the L-function of a curve of genus 2. + + INPUT: + + - ``C`` -- hyperelliptic curve of genus 2 + + Currently, the model needs to be minimal at 2. + + This uses :pari:`lfungenus2`. + + EXAMPLES:: + + sage: from sage.lfunctions.pari import LFunction + sage: x = polygen(QQ, 'x') + sage: C = HyperellipticCurveSmoothModel(x^5 + x + 1) + sage: L = LFunction(lfun_genus2(C)) + ... + sage: L(3) + 0.965946926261520 + + sage: C = HyperellipticCurveSmoothModel(x^2 + x, x^3 + x^2 + 1) + sage: L = LFunction(lfun_genus2(C)) + sage: L(2) + 0.364286342944359 + + TESTS:: + + sage: x = polygen(QQ, 'x') + sage: H = HyperellipticCurveSmoothModel(x^15 + x + 1) + sage: L = LFunction(lfun_genus2(H)) + Traceback (most recent call last): + ... + ValueError: curve must be hyperelliptic of genus 2 + """ + from sage.libs.pari import pari + import hyperelliptic_g2 + + if not isinstance(C, hyperelliptic_g2.HyperellipticCurveSmoothModel_g2): + raise ValueError("curve must be hyperelliptic of genus 2") + P, Q = C.hyperelliptic_polynomials() + return pari.lfungenus2(P) if not Q else pari.lfungenus2([P, Q]) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py new file mode 100644 index 00000000000..c1c192b8565 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py @@ -0,0 +1,434 @@ +r""" +Compute invariants of quintics and sextics via 'Ueberschiebung' + +.. TODO:: + + * Implement invariants in small positive characteristic. + + * Cardona-Quer and additional invariants for classifying automorphism groups. + +AUTHOR: + +- Nick Alexander + +""" + +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + + +def diffxy(f, x, xtimes, y, ytimes): + r""" + Differentiate a polynomial ``f``, ``xtimes`` with respect to ``x``, and + ```ytimes`` with respect to ``y``. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import diffxy + sage: R. = QQ[] + sage: diffxy(u^2*v^3, u, 0, v, 0) + u^2*v^3 + sage: diffxy(u^2*v^3, u, 2, v, 1) + 6*v^2 + sage: diffxy(u^2*v^3, u, 2, v, 2) + 12*v + sage: diffxy(u^2*v^3 + u^4*v^4, u, 2, v, 2) + 144*u^2*v^2 + 12*v + """ + h = f + for i in range(xtimes): + h = h.derivative(x) + for j in range(ytimes): + h = h.derivative(y) + return h + + +def differential_operator(f, g, k): + r""" + Return the differential operator `(f g)_k` symbolically in the polynomial ring in ``dfdx, dfdy, dgdx, dgdy``. + + This is defined by Mestre on p 315 [Mes1991]_: + + .. MATH:: + + (f g)_k = \frac{(m - k)! (n - k)!}{m! n!} \left( + \frac{\partial f}{\partial x} \frac{\partial g}{\partial y} - + \frac{\partial f}{\partial y} \frac{\partial g}{\partial x} \right)^k . + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import differential_operator + sage: R. = QQ[] + sage: differential_operator(x, y, 0) + 1 + sage: differential_operator(x, y, 1) + -dfdy*dgdx + dfdx*dgdy + sage: differential_operator(x*y, x*y, 2) + 1/4*dfdy^2*dgdx^2 - 1/2*dfdx*dfdy*dgdx*dgdy + 1/4*dfdx^2*dgdy^2 + sage: differential_operator(x^2*y, x*y^2, 2) + 1/36*dfdy^2*dgdx^2 - 1/18*dfdx*dfdy*dgdx*dgdy + 1/36*dfdx^2*dgdy^2 + sage: differential_operator(x^2*y, x*y^2, 4) + 1/576*dfdy^4*dgdx^4 - 1/144*dfdx*dfdy^3*dgdx^3*dgdy + 1/96*dfdx^2*dfdy^2*dgdx^2*dgdy^2 + - 1/144*dfdx^3*dfdy*dgdx*dgdy^3 + 1/576*dfdx^4*dgdy^4 + """ + (x, y) = f.parent().gens() + n = max(ZZ(f.degree()), ZZ(k)) + m = max(ZZ(g.degree()), ZZ(k)) + R, (fx, fy, gx, gy) = PolynomialRing( + f.base_ring(), 4, "dfdx,dfdy,dgdx,dgdy" + ).objgens() + const = (m - k).factorial() * (n - k).factorial() / (m.factorial() * n.factorial()) + U = f.base_ring()(const) * (fx * gy - fy * gx) ** k + return U + + +def diffsymb(U, f, g): + r""" + Given a differential operator ``U`` in ``dfdx, dfdy, dgdx, dgdy``, + represented symbolically by ``U``, apply it to ``f, g``. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import diffsymb + sage: R. = QQ[] + sage: S. = QQ[] + sage: [ diffsymb(dd, x^2, y*0 + 1) for dd in S.gens() ] + [2*x, 0, 0, 0] + sage: [ diffsymb(dd, x*0 + 1, y^2) for dd in S.gens() ] + [0, 0, 0, 2*y] + sage: [ diffsymb(dd, x^2, y^2) for dd in S.gens() ] + [2*x*y^2, 0, 0, 2*x^2*y] + + sage: diffsymb(dfdx + dfdy*dgdy, y*x^2, y^3) + 2*x*y^4 + 3*x^2*y^2 + """ + (x, y) = f.parent().gens() + R, (fx, fy, gx, gy) = PolynomialRing( + f.base_ring(), 4, "dfdx,dfdy,dgdx,dgdy" + ).objgens() + res = 0 + for coeff, mon in list(U): + mon = R(mon) + a = diffxy(f, x, mon.degree(fx), y, mon.degree(fy)) + b = diffxy(g, x, mon.degree(gx), y, mon.degree(gy)) + temp = coeff * a * b + res = res + temp + return res + + +def Ueberschiebung(f, g, k): + r""" + Return the differential operator `(f g)_k`. + + This is defined by Mestre on page 315 [Mes1991]_: + + .. MATH:: + + (f g)_k = \frac{(m - k)! (n - k)!}{m! n!} \left( + \frac{\partial f}{\partial x} \frac{\partial g}{\partial y} - + \frac{\partial f}{\partial y} \frac{\partial g}{\partial x} \right)^k . + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import Ueberschiebung as ub + sage: R. = QQ[] + sage: ub(x, y, 0) + x*y + sage: ub(x^5 + 1, x^5 + 1, 1) + 0 + sage: ub(x^5 + 5*x + 1, x^5 + 5*x + 1, 0) + x^10 + 10*x^6 + 2*x^5 + 25*x^2 + 10*x + 1 + """ + U = differential_operator(f, g, k) + # U is the (f g)_k = ... of Mestre, p315, symbolically + return diffsymb(U, f, g) + + +def ubs(f): + r""" + Given a sextic form `f`, return a dictionary of the invariants of Mestre, p 317 [Mes1991]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import ubs + sage: x = QQ['x'].0 + sage: ubs(x^6 + 1) + {'A': 2, + 'B': 2/3, + 'C': -2/9, + 'D': 0, + 'Delta': -2/3*x^2*h^2, + 'f': x^6 + h^6, + 'i': 2*x^2*h^2, + 'y1': 0, + 'y2': 0, + 'y3': 0} + + sage: R. = QQ[] + sage: ubs(u^6 + v^6) + {'A': 2, + 'B': 2/3, + 'C': -2/9, + 'D': 0, + 'Delta': -2/3*u^2*v^2, + 'f': u^6 + v^6, + 'i': 2*u^2*v^2, + 'y1': 0, + 'y2': 0, + 'y3': 0} + + sage: R. = GF(31)[] + sage: ubs(t^6 + 2*t^5 + t^2 + 3*t + 1) + {'A': 0, + 'B': -12, + 'C': -15, + 'D': -15, + 'Delta': -10*t^4 + 12*t^3*h + 7*t^2*h^2 - 5*t*h^3 + 2*h^4, + 'f': t^6 + 2*t^5*h + t^2*h^4 + 3*t*h^5 + h^6, + 'i': -4*t^4 + 10*t^3*h + 2*t^2*h^2 - 9*t*h^3 - 7*h^4, + 'y1': 4*t^2 - 10*t*h - 13*h^2, + 'y2': 6*t^2 - 4*t*h + 2*h^2, + 'y3': 4*t^2 - 4*t*h - 9*h^2} + """ + ub = Ueberschiebung + if f.parent().ngens() == 1: + f = PolynomialRing(f.parent().base_ring(), 1, f.parent().variable_name())(f) + x1, x2 = f.homogenize().parent().gens() + f = sum([f[i] * x1**i * x2 ** (6 - i) for i in range(7)]) + U = {} + U["f"] = f + U["i"] = ub(f, f, 4) + U["Delta"] = ub(U["i"], U["i"], 2) + U["y1"] = ub(f, U["i"], 4) + U["y2"] = ub(U["i"], U["y1"], 2) + U["y3"] = ub(U["i"], U["y2"], 2) + U["A"] = ub(f, f, 6) + U["B"] = ub(U["i"], U["i"], 4) + U["C"] = ub(U["i"], U["Delta"], 4) + U["D"] = ub(U["y3"], U["y1"], 2) + return U + + +def clebsch_to_igusa(A, B, C, D): + r""" + Convert Clebsch invariants `A, B, C, D` to Igusa invariants `I_2, I_4, I_6, I_{10}`. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import clebsch_to_igusa, igusa_to_clebsch + sage: clebsch_to_igusa(2, 3, 4, 5) + (-240, 17370, 231120, -103098906) + sage: igusa_to_clebsch(*clebsch_to_igusa(2, 3, 4, 5)) + (2, 3, 4, 5) + + sage: Cs = tuple(map(GF(31), (2, 3, 4, 5))); Cs + (2, 3, 4, 5) + sage: clebsch_to_igusa(*Cs) + (8, 10, 15, 26) + sage: igusa_to_clebsch(*clebsch_to_igusa(*Cs)) + (2, 3, 4, 5) + """ + I2 = -120 * A + I4 = -720 * A**2 + 6750 * B + I6 = 8640 * A**3 - 108000 * A * B + 202500 * C + I10 = ( + -62208 * A**5 + + 972000 * A**3 * B + + 1620000 * A**2 * C + - 3037500 * A * B**2 + - 6075000 * B * C + - 4556250 * D + ) + return (I2, I4, I6, I10) + + +def igusa_to_clebsch(I2, I4, I6, I10): + r""" + Convert Igusa invariants `I_2, I_4, I_6, I_{10}` to Clebsch invariants `A, B, C, D`. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import clebsch_to_igusa, igusa_to_clebsch + sage: igusa_to_clebsch(-2400, 173700, 23112000, -10309890600) + (20, 342/5, 2512/5, 43381012/1125) + sage: clebsch_to_igusa(*igusa_to_clebsch(-2400, 173700, 23112000, -10309890600)) + (-2400, 173700, 23112000, -10309890600) + + sage: Is = tuple(map(GF(31), (-2400, 173700, 23112000, -10309890600))); Is + (18, 7, 12, 27) + sage: igusa_to_clebsch(*Is) + (20, 25, 25, 12) + sage: clebsch_to_igusa(*igusa_to_clebsch(*Is)) + (18, 7, 12, 27) + """ + A = -(+I2) / 120 + B = -(-(I2**2) - 20 * I4) / 135000 + C = -(+(I2**3) + 80 * I2 * I4 - 600 * I6) / 121500000 + D = ( + -( + +9 * I2**5 + + 700 * I2**3 * I4 + - 3600 * I2**2 * I6 + - 12400 * I2 * I4**2 + + 48000 * I4 * I6 + + 10800000 * I10 + ) + / 49207500000000 + ) + return (A, B, C, D) + + +def clebsch_invariants(f): + r""" + Given a sextic form `f`, return the Clebsch invariants `(A, B, C, D)` of + Mestre, p 317, [Mes1991]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import clebsch_invariants + sage: R. = QQ[] + sage: clebsch_invariants(x^6 + y^6) + (2, 2/3, -2/9, 0) + sage: R. = QQ[] + sage: clebsch_invariants(x^6 + x^5 + x^4 + x^2 + 2) + (62/15, 15434/5625, -236951/140625, 229930748/791015625) + + sage: magma(x^6 + 1).ClebschInvariants() # optional - magma + [ 2, 2/3, -2/9, 0 ] + sage: magma(x^6 + x^5 + x^4 + x^2 + 2).ClebschInvariants() # optional - magma + [ 62/15, 15434/5625, -236951/140625, 229930748/791015625 ] + """ + R = f.parent().base_ring() + if R.characteristic() in [2, 3, 5]: + raise NotImplementedError( + "Invariants of binary sextics/genus 2 hyperelliptic " + "curves not implemented in characteristics 2, 3, and 5" + ) + + U = ubs(f) + L = U["A"], U["B"], U["C"], U["D"] + assert all(t.is_constant() for t in L) + return tuple([t.constant_coefficient() for t in L]) + + +def igusa_clebsch_invariants(f): + r""" + Given a sextic form `f`, return the Igusa-Clebsch invariants `I_2, I_4, + I_6, I_{10}` of Igusa and Clebsch [IJ1960]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import igusa_clebsch_invariants + sage: R. = QQ[] + sage: igusa_clebsch_invariants(x^6 + y^6) + (-240, 1620, -119880, -46656) + sage: R. = QQ[] + sage: igusa_clebsch_invariants(x^6 + x^5 + x^4 + x^2 + 2) + (-496, 6220, -955932, -1111784) + + sage: magma(x^6 + 1).IgusaClebschInvariants() # optional - magma + [ -240, 1620, -119880, -46656 ] + sage: magma(x^6 + x^5 + x^4 + x^2 + 2).IgusaClebschInvariants() # optional - magma + [ -496, 6220, -955932, -1111784 ] + + TESTS: + + Let's check a symbolic example:: + + sage: R. = QQ[] + sage: S. = R[] + sage: igusa_clebsch_invariants(x^5 + a*x^4 + b*x^3 + c*x^2 + d*x + e)[0] + 6*b^2 - 16*a*c + 40*d + + sage: from sage.schemes.hyperelliptic_curves.invariants import absolute_igusa_invariants_wamelen + sage: absolute_igusa_invariants_wamelen(GF(5)['x'](x^6 - 2*x)) + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + """ + return clebsch_to_igusa(*clebsch_invariants(f)) + + +def absolute_igusa_invariants_wamelen(f): + r""" + Given a sextic form `f`, return the three absolute Igusa invariants used by van Wamelen [Wam1999]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + REFERENCES: + + - [Wam1999]_ + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import absolute_igusa_invariants_wamelen + sage: R. = QQ[] + sage: absolute_igusa_invariants_wamelen(x^5 - 1) + (0, 0, 0) + + The following example can be checked against van Wamelen's paper:: + + sage: h = -x^5 + 3*x^4 + 2*x^3 - 6*x^2 - 3*x + 1 + sage: i1, i2, i3 = absolute_igusa_invariants_wamelen(h) + sage: list(map(factor, (i1, i2, i3))) + [2^7 * 3^15, 2^5 * 3^11 * 5, 2^4 * 3^9 * 31] + + TESTS:: + + sage: absolute_igusa_invariants_wamelen(GF(3)['x'](x^5 - 2*x)) + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + """ + I2, I4, I6, I10 = igusa_clebsch_invariants(f) + i1 = I2**5 / I10 + i2 = I2**3 * I4 / I10 + i3 = I2**2 * I6 / I10 + return (i1, i2, i3) + + +def absolute_igusa_invariants_kohel(f): + r""" + Given a sextic form `f`, return the three absolute Igusa invariants used by Kohel [KohECHIDNA]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import absolute_igusa_invariants_kohel + sage: R. = QQ[] + sage: absolute_igusa_invariants_kohel(x^5 - 1) + (0, 0, 0) + sage: absolute_igusa_invariants_kohel(x^5 - x) + (100, -20000, -2000) + + The following example can be checked against Kohel's database [KohECHIDNA]_ :: + + sage: h = -x^5 + 3*x^4 + 2*x^3 - 6*x^2 - 3*x + 1 + sage: i1, i2, i3 = absolute_igusa_invariants_kohel(h) + sage: list(map(factor, (i1, i2, i3))) + [2^2 * 3^5 * 5 * 31, 2^5 * 3^11 * 5, 2^4 * 3^9 * 31] + sage: list(map(factor, (150660, 28343520, 9762768))) + [2^2 * 3^5 * 5 * 31, 2^5 * 3^11 * 5, 2^4 * 3^9 * 31] + + TESTS:: + + sage: absolute_igusa_invariants_kohel(GF(2)['x'](x^5 - x)) + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + """ + I2, I4, I6, I10 = igusa_clebsch_invariants(f) + i1 = I4 * I6 / I10 + i2 = I2**3 * I4 / I10 + i3 = I2**2 * I6 / I10 + return (i1, i2, i3) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py new file mode 100644 index 00000000000..23f79df888b --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py @@ -0,0 +1,30 @@ +from sage.schemes.hyperelliptic_curves_smooth_model import ( + jacobian_g2_homset_inert, + jacobian_g2_homset_ramified, + jacobian_g2_homset_split, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_generic import ( + HyperellipticJacobian_generic, +) + + +class HyperellipticJacobian_g2_generic(HyperellipticJacobian_generic): + """ + Special class to handle optimisations for jacobian computations + in genus two + """ + + def _point_homset(self, *args, **kwds): + # TODO: make a constructor for this?? + H = self.curve() + if H.is_ramified(): + return jacobian_g2_homset_ramified.HyperellipticJacobianHomsetRamified_g2( + *args, **kwds + ) + elif H.is_split(): + return jacobian_g2_homset_split.HyperellipticJacobianHomsetSplit_g2( + *args, **kwds + ) + return jacobian_g2_homset_inert.HyperellipticJacobianHomsetInert_g2( + *args, **kwds + ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py new file mode 100644 index 00000000000..310c9fe06d8 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_inert import ( + HyperellipticJacobianHomsetInert, +) + + +class HyperellipticJacobianHomsetInert_g2(HyperellipticJacobianHomsetInert): + """ + Special class to handle optimisations for jacobian homset computations + in genus two for hyperlliptic curves with an inert model + """ + + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py new file mode 100644 index 00000000000..c171c4bbd87 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_ramified import ( + HyperellipticJacobianHomsetRamified, +) + + +class HyperellipticJacobianHomsetRamified_g2(HyperellipticJacobianHomsetRamified): + """ + Special class to handle optimisations for jacobian homset computations + in genus two for hyperlliptic curves with an ramified model + """ + + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py new file mode 100644 index 00000000000..b0385e4b92e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_split import ( + HyperellipticJacobianHomsetSplit, +) + + +class HyperellipticJacobianHomsetSplit_g2(HyperellipticJacobianHomsetSplit): + """ + Special class to handle optimisations for jacobian homset computations + in genus two for hyperlliptic curves with an split model + """ + + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py new file mode 100644 index 00000000000..15b7ed91b08 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -0,0 +1,230 @@ +r""" + Jacobians of hyperelliptic curves + + In Sage, a hyperelliptic curve `H` of genus `g` is always + specified by an (affine) equation in Weierstrass form + + .. MATH:: + + H : y^2 + h(x) y = f(x), + + for some polynomials `h` and `f`. + + Elements of the Jacobian of such curves can be identified + with equivalence classes of divisors. In the following, we + denote by `\infty_+`, \infty_-` the points at infinity of `H`, + and we set + + ..MATH:: + D_\infty = + \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. + + Here, `\infty_- = \infty_+`, if `H` is ramified. + Unless the genus `g` is odd and `H` is inert, the divisor + `D_\infty` is rational. In these cases, any element on + the Jacobian admits a unique representative of the form + + ..MATH:: + + [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], + + with `n` and `m` non-negative integers and `P_1 + ... + P_r` + an affine and reduced divisor on `H`. + + This module implements the arithemtic for Jacobians of + hyperelliptic curves. Elements of the Jacobian are represented + by tuples of the form `(u, v : n)`, where + - (u,v) is the Mumford representative of the divisor + `P_1 + ... + P_r`, + - n is the coefficient of `\infty_+` + We note that `m = g - \deg(u) - n` and is therefore omitted in + the description. Similarly, if `H` ramified or inert, + then `n` can be deduced from `\deg(u)` and `g`. In these cases, + `n` is omitted in the description as well. + + + EXAMPLES:: + + We construct the Jacobian of a hyperelliptic curve with affine equation + `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. + This curve has two points at infinity. + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x + + The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian. + + sage: P = H.point([0, 0]) + sage: Q = H.point([-1, -1]) + sage: D1 = J(P,Q); D1 + (x^2 + x, -2*x : 0) + + Elements of the Jacobian can also be constructed by directly providing + the Mumford representation. + + sage: D1 == J(x^2 + x, -2*x, 0) + True + + We can also embed single points into the Jacobian. Below we construct + `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` + (by default one of the points at infinity). + + sage: D2 = J(P); D2 + (x, 0 : 0) + sage: P0 = H.distinguished_point(); P0 + [1 : 0 : 0] + sage: D2 == J(P, P0) + True + + We may add elements, or multiply by integers. + + sage: 2*D1 + (x, -1 : 1) + sage: D1 + D2 + (x^2 + x, -1 : 0) + sage: -D2 + (x, -1 : 1) + + Note that the neutral element is given by `[D_\infty - D_\infty]`, + in particular `n = 1`. + + sage: J.zero() + (1, 0 : 1) + + There are two more elements of the Jacobian that are only supported + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. + + sage: [P_plus, P_minus] = H.points_at_infinity() + sage: P_plus == P0 + True + sage: J(P_plus,P_minus) + (1, 0 : 2) + sage: J(P_minus, P_plus) + (1, 0 : 0) + + Now, we consider the Jacobian of a hyperelliptic curve with only one + point at infinity, defined over a finite field. + + sage: K = FiniteField(7) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 + + Elements on the Jacobian can be constructed as before. But the value + `n` is not used here, since there is only one point at infinity. + + sage: P = H.point([3, 0]) + sage: Q = H.point([5, 1]) + sage: D1 = J(P,Q); D1 + (x^2 + 6*x + 1, 3*x + 5) + sage: D2 = J(x^3 + 3*x^2 + 4*x + 3, 2*x^2 + 4*x) + sage: D1 + D2 + (x^3 + 2, 4) + + Over finite fields, we may also construct random elements and + compute the order of the Jacobian. + + sage: J.random_element() #random + (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) + sage: J.order() + 344 + + Note that arithmetic on the Jacobian is not implemented if the + underlying hyperelliptic curve is inert (i.e. has no points at + infinity) and the genus is odd. + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^4 + 1)*y = x^8 + 1 + + + TODO: + finish example for the inert case. + + AUTHORS: + + - David Kohel (2006): initial version + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# 2024 Sabrina Kunzweiler, Gareth Ma, Giacomo Pope +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** +from sage.misc.cachefunc import cached_method +from sage.rings.integer import Integer +from sage.schemes.hyperelliptic_curves_smooth_model import ( + jacobian_homset_inert, + jacobian_homset_ramified, + jacobian_homset_split, + jacobian_morphism, +) +from sage.schemes.jacobians.abstract_jacobian import Jacobian_generic + + +class HyperellipticJacobian_generic(Jacobian_generic): + """ + This is the base class for Jacobians of hyperelliptic curves. + """ + + def dimension(self): + """ + Return the dimension of this Jacobian. + """ + return Integer(self.curve().genus()) + + def point(self, *mumford, check=True, **kwargs): + try: + return self.point_homset()(*mumford, check=check) + except AttributeError: + raise ValueError("Arguments must determine a valid Mumford divisor.") + + def _point_homset(self, *args, **kwds): + # TODO: make a constructor for this?? + H = self.curve() + if H.is_ramified(): + return jacobian_homset_ramified.HyperellipticJacobianHomsetRamified( + *args, **kwds + ) + elif H.is_split(): + return jacobian_homset_split.HyperellipticJacobianHomsetSplit(*args, **kwds) + return jacobian_homset_inert.HyperellipticJacobianHomsetInert(*args, **kwds) + + def _point(self, *args, **kwds): + H = self.curve() + if H.is_ramified(): + return jacobian_morphism.MumfordDivisorClassFieldRamified(*args, **kwds) + elif H.is_split(): + return jacobian_morphism.MumfordDivisorClassFieldSplit(*args, **kwds) + return jacobian_morphism.MumfordDivisorClassFieldInert(*args, **kwds) + + @cached_method + def order(self): + return self.point_homset().order() + + def count_points(self, *args, **kwds): + return self.point_homset().count_points(*args, **kwds) + + def lift_u(self, *args, **kwds): + return self.point_homset().lift_u(*args, **kwds) + + def random_element(self, *args, **kwds): + return self.point_homset().random_element(*args, **kwds) + + def points(self, *args, **kwds): + return self.point_homset().points(*args, **kwds) + + def list(self): + return self.point_homset().points() + + def __iter__(self): + yield from self.list() + + rational_points = points diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py new file mode 100644 index 00000000000..9470d028f87 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -0,0 +1,664 @@ +import itertools + +from sage.misc.banner import SAGE_VERSION +from sage.misc.cachefunc import cached_method +from sage.misc.functional import symbolic_prod as product +from sage.misc.prandom import choice +from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic +from sage.rings.integer import Integer +from sage.rings.polynomial.polynomial_ring import polygen +from sage.schemes.generic.homset import SchemeHomset_points + +# TODO: move this +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, +) +from sage.structure.element import parent + +assert SAGE_VERSION.startswith("10."), "please update to Sage 10" +assert ( + int(SAGE_VERSION.lstrip("10.").split(".")[0]) >= 4 +), "please update to Sage 10.4+ (#37118)" + + +class HyperellipticJacobianHomset(SchemeHomset_points): + def __init__(self, Y, X, **kwds): + SchemeHomset_points.__init__(self, Y, X, **kwds) + self._morphism_element = None + + def _repr_(self) -> str: + return f"Abelian group of points on {self.codomain()}" + + def _morphism(self, *args, **kwds): + return self._morphism_element(*args, **kwds) + + def curve(self): + # It is unlike you will want to use this, as this returns the original curve H even when + return self.codomain().curve() + + def extended_curve(self): + # Code from schemes/generic/homset.py + if "_extended_curve" in self.__dict__: + return self._extended_curve + R = self.domain().coordinate_ring() + if R is not self.curve().base_ring(): + X = self.curve().base_extend(R) + else: + X = self.curve() + self._extended_curve = X + return X + + @cached_method + def order(self): + """ + TODO: currently using lazy methods by calling sage + """ + return sum(self.extended_curve().frobenius_polynomial()) + + @cached_method + def _curve_frobenius_roots(self): + r""" + Return the roots of the charpoly of frobenius on the extended curve. + + EXAMPLES:: + """ + from sage.rings.qqbar import QQbar + + roots = self.extended_curve().frobenius_polynomial().roots(QQbar) + return [r for r, e in roots for _ in range(e)] + + def cardinality(self, extension_degree=1): + r""" + Return `|Jac(C) / \F_{q^n}|`. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: J = H.jacobian() + sage: J.count_points(10) == [J.change_ring(GF((5, k))).order() for k in range(1, 11)] + True + sage: J2 = J(GF((5, 2))) + sage: J2.count_points(5) == J.count_points(10)[1::2] + True + """ + K = self.extended_curve().base_ring() + if not isinstance(K, FiniteField_generic): + raise NotImplementedError( + "cardinality is only implemented for Jacobians over finite fields" + ) + + return Integer( + product(1 - r**extension_degree for r in self._curve_frobenius_roots()) + ) + + def count_points(self, n=1): + try: + n = Integer(n) + except TypeError: + raise TypeError("n must be a positive integer") + + if n < 1: + raise ValueError("n must be a positive integer") + + if n == 1: + return self.cardinality() + + return [self.cardinality(extension_degree=i) for i in range(1, n + 1)] + + def point_to_mumford_coordinates(self, P): + """ + On input a point P, return the Mumford coordinates + of (the affine part of) the divisor [P]. + """ + R, x = self.extended_curve().polynomial_ring().objgen() + X, Y, Z = P._coords + if Z == 0: + return R.one(), R.zero() + u = x - X + v = R(Y) + return u, v + + def __call__(self, *args, check=True): + r""" + Return a rational point in the abstract Homset `J(K)`, given: + + 1. No arguments or the integer `0`; return `0 \in J`; + + 2. A point `P` on `J = Jac(C)`, return `P`; + + 3. A point `P` on the curve `H` such that `J = Jac(H)`; + return `[P - P_0]`, where `P_0` is the distinguished point of `H`. + By default, `P_0 = \infty`; + + 4. Two points `P, Q` on the curve `H` such that `J = Jac(H)`; + return `[P - Q]`; + + 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; + reutrn `[(u(x), y - v(x))]`. + + EXAMPLES: + + First consider a hyperelliptic curve with an odd-degree model, + hence a unique point at infinity:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^7 + x + 1) + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: P = H.lift_x(1) + sage: Q = H.lift_x(2) + sage: D1 = JH(P); D1 + (x + 12, 4) + sage: D2 = JH(Q); D2 + (x + 11, 1) + sage: D = JH(P,Q); D + (x^2 + 10*x + 2, 8*x + 9) + sage: D == D1 - D2 + True + sage: JH(x^2+10*x+2, 8*x+9) == D + True + + In general, a distinguished point is used to embed points of the curve + in the Jacobian. This works for general models of hyperelliptic curves:: + + sage: R. = PolynomialRing(GF(13)) + sage: H = HyperellipticCurveSmoothModel(2*x^8 + x + 1) + sage: H.is_inert() + True + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: P = H.lift_x(1) + sage: D1 = JH(P); D1 + (x^2 + 12*x, 3*x + 12 : 0) + + To understand this output, one needs to look at the distinguished + point:: + + sage: P0 = H.distinguished_point(); P0 + (0 : 1 : 1) + sage: JH(P) == JH(P,P0) + True + sage: JH(P0) + (1, 0 : 1) + + We may change the distinguished point. Of course, the divisor `[P - Q]` + does not depend on the choice of the distinguished point:: + + sage: Q = H.lift_x(3) + sage: JH(P,Q) + (x^2 + 9*x + 3, 4*x + 11 : 0) + sage: H.set_distinguished_point(H.random_point()) + sage: JH(P) # random + (x^2 + 6*x + 6, 10*x + 5 : 0) + sage: JH(P,Q) + (x^2 + 9*x + 3, 4*x + 11 : 0) + + TESTS: + + Ensure that field elements are treated as mumford coordinates:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 6*x^2 + 6 + sage: J(H.lift_x(3)) + (x + 4, 0) + sage: _ == J(x + 4, 0) == J(x + 4, R(0)) + True + + TODO: + + - Allow sending a field element corresponding to the x-coordinate of a point? + + - Use ``__classcall__`` to sanitise input? + + - Add doctest for base extension + """ + R = self.extended_curve().polynomial_ring() + + if len(args) == 0 or (len(args) == 1 and args[0] == ()): + # this returns the incorrect result because subclasses like the inert case may implement + # .zero differently + # return self._morphism_element(self, R.one(), R.zero(), check=check) + return self.zero(check=check) + + if len(args) == 1 and isinstance(args[0], (list, tuple)): + args = args[0] + + if len(args) == 1: + P1 = args[0] + if P1 == 0: + return self.zero(check=check) + elif isinstance(P1, self._morphism_element): + return P1 + elif isinstance(P1, SchemeMorphism_point_weighted_projective_ring): + args = args + ( + self.extended_curve().distinguished_point(), + ) # this case will now be handled below. + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + if len(args) == 2: + P1 = args[0] + P2 = args[1] + if isinstance(P1, SchemeMorphism_point_weighted_projective_ring) and isinstance( + P2, SchemeMorphism_point_weighted_projective_ring + ): + u1, v1 = self.point_to_mumford_coordinates(P1) + P2_inv = self.extended_curve().hyperelliptic_involution(P2) + u2, v2 = self.point_to_mumford_coordinates(P2_inv) + u, v = self.cantor_composition(u1, v1, u2, v2) + # This checks whether P1 and P2 can be interpreted as polynomials + elif R.coerce_map_from(parent(P1)) and R.coerce_map_from(parent(P2)): + u = R(P1) + v = R(P2) + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + if len(args) > 2: + raise ValueError("at most two arguments are allowed as input") + + return self._morphism_element(self, u, v, check=check) + + def zero(self, check=True): + """ + Return the zero element of this jacobian homset. + """ + H = self.extended_curve() + R = H.polynomial_ring() + return self._morphism_element(self, R.one(), R.zero(), check=check) + + def __cantor_double_generic(self, u1, v1): + """ + Efficient cantor composition for doubling an affine divisor + + Returns the Cantor composition of (u1, v1) with (u1, v1) + together with the degree of the polynomial ``s`` which is + needed for computing weights for the split and inert models + """ + f, h = self.extended_curve().hyperelliptic_polynomials() + + # New mumford coordinates + if h.is_zero(): + s, _, e2 = u1.xgcd(v1 + v1) + u3 = (u1 // s) ** 2 + v3 = v1 + e2 * (f - v1**2) // s + else: + s, _, e2 = u1.xgcd(v1 + v1 + h) + u3 = (u1 // s) ** 2 + v3 = v1 + e2 * (f - v1 * h - v1**2) // s + v3 = v3 % u3 + + return u3, v3, s.degree() + + def _cantor_composition_generic(self, u1, v1, u2, v2): + """ + Helper function for the Cantor composition algorithm. + + OUTPUT: + + The Cantor composition of ``(u1, v1)`` with ``(u2, v2)``, + together with the degree of the polynomial ``s`` which is + needed for computing the weights for the split and inert models. + """ + # Collect data from HyperellipticCurve + H = self.extended_curve() + f, h = H.hyperelliptic_polynomials() + g = H.genus() + + # Ensure D1 and D2 are semi-reduced divisors + assert ( + v1.degree() < u1.degree() and v2.degree() < u2.degree() + ), "The degree of bi must be smaller than ai" + assert ( + u1.degree() <= 2 * g + 2 and u2.degree() <= 2 * g + 2 + ), f"The degree of ai must be smaller than 2g+2, {u1.degree()}, {u2.degree()}" + + # Special case: duplication law + if u1 == u2 and v1 == v2: + return self.__cantor_double_generic(u1, v1) + + # Step One + s0, _, e2 = u1.xgcd(u2) + v1_m_v2 = v1 - v2 + + # Special case: when gcd(u0, u1) == 1 we can + # avoid many expensive steps as we have s = 1 + if s0.is_one(): + u3 = u1 * u2 + v3 = v2 + e2 * u2 * v1_m_v2 + v3 = v3 % u3 + return u3, v3, 0 + + # Step Two + w0 = v1 + v2 + h + + # Another special case, when w0 is zero we skip + # a xgcd and can return early + if w0.is_zero(): + u3 = (u1 * u2) // (s0**2) + v3 = v2 + e2 * v1_m_v2 * (u2 // s0) + v3 = v3 % u3 + return u3, v3, s0.degree() + + # Step Three + s, c1, c2 = s0.xgcd(w0) + u3 = (u1 * u2) // (s**2) + v3 = v2 + (c1 * e2 * v1_m_v2 * u2 + c2 * (f - h * v2 - v2**2)) // s + v3 = v3 % u3 + return u3, v3, s.degree() + + def _cantor_reduction_generic(self, u0, v0): + """ + Helper function for the Cantor composition algorithm. + + OUTPUT: + + The reduced divisor of ``(u0, v0)``. + + NOTE: + + The `u`-coordinate of the output is not necessarily monic. That step is + delayed to :meth:`cantor_reduction` to save time. + """ + # Collect data from HyperellipticCurve + H = self.extended_curve() + f, h = H.hyperelliptic_polynomials() + + # Compute u' and v' + u1 = (v0**2 + h * v0 - f) // u0 + v1 = (-h - v0) % u1 + + return u1, v1 + + def cantor_composition(self, u1, v1, u2, v2): + """ + Return the Cantor composition of ``(u1, v1)`` and ``(u2, v2)``. + + EXAMPLES:: + + TODO + """ + u3, v3, _ = self._cantor_composition_generic(u1, v1, u2, v2) + return u3, v3 + + def cantor_reduction(self, u0, v0): + """ + Return the Cantor reduced ``(u0, v0)``. + + EXAMPLES:: + + TODO + """ + return self._cantor_reduction_generic(u0, v0) + + def lift_u(self, u, all=False): + """ + Return one or all points with given `u`-coordinate. + + This method is deterministic: it returns the same data each time when + called with the same `u`. + + Currently only implemented for Jacobians over a finite field. + + INPUT: + + * ``u`` -- an element of the base ring of the Jacobian + + * ``all`` -- boolean (default: ``False``); if ``True``, return a + (possibly empty) list of all points with the given `u`-coordinate; if + ``False``, return just one point, or raise a ``ValueError`` if there + are none. + + OUTPUT: + + A point on this Jacobian. + + EXAMPLES:: + + sage: R. = GF(1993)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + x + 1) + sage: J = H.jacobian() + sage: P = J.lift_u(x^2 + 42*x + 270); P + (x^2 + 42*x + 270, 1837*x + 838) + """ + H = self.extended_curve() + K = H.base_ring() + g = H.genus() + + H_is_split = H.is_split() + + if u.is_zero(): + if H_is_split: + if not all: + return self._morphism_element(self, R.one(), R.zero(), 0) + return [ + self._morphism_element(self, R.one(), R.zero(), n) + for n in range(g + 1) + ] + if not all: + return self.zero() + return [self.zero()] + + if not isinstance(K, FiniteField_generic) or K.degree() != 1: + raise NotImplementedError( + "lift_u is only implemented for Jacobians over a finite field of prime order" + ) + + R = H.polynomial_ring() + f, h = H.hyperelliptic_polynomials() + u1, v1 = R.one(), R.zero() + + u = R(u).monic() + u_factors = u.factor() + vss = [] + + for x, e in u_factors: + # Solve y^2 + hy - f = 0 mod x + + # TODO: is this the most efficient method? Maybe we should write + # a helper function which computes y^2 + hy - f = 0 mod x which + # properly handles trivial cases like when x is linear? + K_ext = K.extension(modulus=x, names="a") + y_ext = polygen(K_ext, "y_ext") + h_ = K_ext(h % x) + f_ = K_ext(f % x) + vs = (y_ext**2 + h_ * y_ext - f_).roots(multiplicities=False) + if len(vs) == 0 and not all: + raise ValueError(f"no point with u-coordinate {u} on {self}") + + vss.append(vs) + + def postprocess_uv(u1, v1, n=None): + # This function converts (u, v) into the point being returned, handling split cases + if H.is_split(): + if n is None or not (0 <= n <= g - u1.degree()): + # this is an internal function + raise ValueError( + f"bug: n must be an integer between 0 and {g - u1.degree()}" + ) + return self._morphism_element(self, u1, v1, n, check=False) + + # We need to ensure the degree of u is even + if H.is_inert(): + if u1.degree() % 2: + # TODO: make composition with distinguished_point its own function? + P0 = self.extended_curve().distinguished_point() + X0, Y0, _ = P0._coords + X = R.gen() # TODO use better variable names in this function + _, h = self.extended_curve().hyperelliptic_polynomials() + u0 = X - X0 + v0 = R(-Y0 - h(X0)) + u1, v1, _ = self._cantor_composition_generic(u1, v1, u0, v0) + assert not (u1.degree() % 2), f"{u1} must have even degree" + + return self._morphism_element(self, u1, v1, check=False) + + points = [] + for vv in itertools.product(*vss): + u1, v1 = R.one(), R.zero() + try: + for (x, e), v in zip(u_factors, map(R, vv)): + for _ in range(e): + u1, v1, _ = self._cantor_composition_generic(u1, v1, x, v) + # v is not rational, so we skip it + except (ValueError, AttributeError): + pass + + if not all: + return postprocess_uv(u1, v1, n=0) + + if H_is_split: + for n in range(g - u1.degree() + 1): + points.append(postprocess_uv(u1, v1, n=n)) + else: + points.append(postprocess_uv(u1, v1)) + + return points + + def _random_element_cover(self, degree=None): + r""" + Return a random element from the Jacobian. + + Distribution is not uniformly random, but returns the entire group. + """ + H = self.extended_curve() + R = H.polynomial_ring() + g = H.genus() + + # For the inert case, the genus must be even + if H.is_inert(): + assert not (g % 2) + + if degree is None: + degree = (-1, g) + + while True: + # TODO: i think we can skip this and simply ensure u + # is even degree with composition with the distinguished + # point? + # if H.is_inert() and (u.degree() % 2) == 1: + # #TODO: better method to sample even degree polynomials + # continue + u = R.random_element(degree=degree, monic=True) + try: + return choice(self.lift_u(u, all=True)) + # TODO: better handling rather than looping with try / except? + except IndexError: + pass + + def _random_element_rational(self): + r""" + Return a random element from the Jacobian. This algorithm is faster + than :meth:`_random_element_cover` but is **NOT** surjective on the set + of points. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: f = x^5 + 2*x^4 + 4*x^3 + x^2 + 4*x + 3 + sage: H = HyperellipticCurveSmoothModel(f) + sage: J = H.jacobian() + sage: J.order() + 16 + sage: JH = J.point_homset() + sage: len(set(JH._random_element_rational() for _ in range(300))) + 8 + """ + H = self.extended_curve() + g = H.genus() + + # We randomly sample 2g + 1 points on the hyperelliptic curve + points = [H.random_point() for _ in range(2 * g + 1)] + + # We create 2g + 1 divisors of the form (P) - infty + divisors = [self(P) for P in points if P[2] != 0] + + # If we happened to only sample the point at infinity, we return this + # Otherwise we compute the sum of all divisors. + if not divisors: + return self.zero() + return sum(divisors, start=self.zero()) + + def random_element(self, fast=False, *args, **kwargs): + r""" + Returns a random element from the Jacobian. Distribution is **NOT** + uniformly random. + + INPUT: + + - ``fast`` -- (boolean, default ``True``) If set to ``True``, a fast + algorithm is used, but the output is **NOT** guaranteed to cover the + entire Jacobian. See examples below. If set to ``False``, a slower + algorithm is used, but covers the entire Jacobian. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: f = x^5 + 2*x^4 + 4*x^3 + x^2 + 4*x + 3 + sage: H = HyperellipticCurveSmoothModel(f) + sage: J = H.jacobian() + + This example demonstrates that the ``fast`` algorithm is not + necessarily surjective on the rational points of the Jacobian:: + + sage: JH = J.point_homset() + sage: len(set(JH.random_element(fast=True) for _ in range(300))) + 8 + sage: len(set(JH.random_element(fast=False) for _ in range(300))) + 16 + sage: J.order() + 16 + """ + if not isinstance(self.base_ring(), FiniteField_generic): + raise NotImplementedError( + "random element of Jacobian is only implemented over Finite Fields" + ) + + if fast: + return self._random_element_rational(*args, **kwargs) + return self._random_element_cover(*args, **kwargs) + + def points(self): + """ + Return all points on this Jacobian `J(K)`. + + .. WARNING:: + + This code is not efficient at all. + """ + H = self.extended_curve() + R = H.polynomial_ring() + g = H.genus() + + # TODO: after `monic` argument is added, use it + ss = [] + for u in R.polynomials(max_degree=g): + if u.is_monic(): + for P in self.lift_u(u, all=True): + # UGLY HACK: it keeps overcounting 0 without this... + # failing example: y^2 = x^5 + x over F_5 + if not u.is_one() and list(P)[:2] == [1, 0]: + continue + ss.append(P) + + if H.is_ramified() or H.is_split(): + assert len(ss) == self.order() + return ss + + # TODO: remove this + # failing example: y^2 = 2x^6 + 1 over F_5 + ss = sorted(set(ss)) + assert len(ss) == self.order() + return ss + + rational_points = points + + # TODO: Remove this after #38566 is merged + def __iter__(self): + yield from self.points() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py new file mode 100644 index 00000000000..e88b84d8906 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py @@ -0,0 +1,24 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( + HyperellipticJacobianHomset, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldInert, +) + + +class HyperellipticJacobianHomsetInert(HyperellipticJacobianHomset): + def __init__(self, Y, X, **kwds): + super().__init__(Y, X, **kwds) + self._morphism_element = MumfordDivisorClassFieldInert + + def zero(self, check=True): + """ + Return the zero element of the Jacobian + """ + g = self.curve().genus() + if g % 2: + raise ValueError( + "unable to perform arithmetic for inert models of odd genus" + ) + R = self.curve().polynomial_ring() + return self._morphism_element(self, R.one(), R.zero(), check=check) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py new file mode 100644 index 00000000000..8f8045a7500 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( + HyperellipticJacobianHomset, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldRamified, +) + + +class HyperellipticJacobianHomsetRamified(HyperellipticJacobianHomset): + def __init__(self, Y, X, **kwds): + super().__init__(Y, X, **kwds) + self._morphism_element = MumfordDivisorClassFieldRamified diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py new file mode 100644 index 00000000000..6944766d2b5 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -0,0 +1,269 @@ +from sage.rings.integer import Integer +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( + HyperellipticJacobianHomset, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldSplit, +) +from sage.structure.element import parent + +# TODO: move this +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, +) + + +class HyperellipticJacobianHomsetSplit(HyperellipticJacobianHomset): + def __init__(self, Y, X, **kwds): + super().__init__(Y, X, **kwds) + self._morphism_element = MumfordDivisorClassFieldSplit + + def zero(self): + """ + Return the zero element of the Jacobian + """ + g = self.curve().genus() + R = self.curve().polynomial_ring() + n = (g + 1) // 2 + return self._morphism_element(self, R.one(), R.zero(), n) + + def point_to_mumford_coordinates(self, P): + """ + On input a point P, return the Mumford coordinates + of (the affine part of) the divisor [P] and an integer n, + where + * n = 1 if P is the point oo+ + * n = 0 otherwise . + """ + + R, x = self.curve().polynomial_ring().objgen() + [X, Y, Z] = P._coords + if Z == 0: + alpha = Y / X + if alpha == self.curve().roots_at_infinity()[0]: + n = 1 + else: + n = 0 + return R.one(), R.zero(), n + u = x - X + v = R(Y) + return u, v, 0 + + def __call__(self, *args, check=True): + r""" + 3. Polynomials u,v such that `v^2 + h*v - f = 0 mod u`, + returning J(u,v,n) with n = ((g - deg(u))/2).ceil(). + + 4. Polynomials u,v and an integer n such that `v^2 + h*v - f = 0 mod u` + and 0 <= n <= g/2, + returning J(u,v,n). + + Return a rational point in the abstract Homset `J(K)`, given: + + 1. No arguments or the integer `0`; return `0 \in J`; + + 2. A point `P` on `J = Jac(C)`, return `P`; + + 3. A point `P` on the curve `H` such that `J = Jac(H)`; + return `[P - P_0]`, where `P_0` is the distinguished point of `H`. + By default, `P_0 = \infty`; + + 4. Two points `P, Q` on the curve `H` such that `J = Jac(H)`; + return `[P - Q]`; + + 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; + reutrn `[(u(x), y - v(x)) : \lceil (g - \deg(u)) / 2 \rceil]`; + + 6. Polynomials `(u, v)` and an integer `n` such that + `v^2 + hv - f \equiv 0 \pmod u` and `0 \leq n \leq g / 2`; + return `[u, v : n]`. + + EXAMPLES:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 1) + sage: H.is_split() + True + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: P = H.lift_x(1) + sage: Q = H.lift_x(2) + sage: D1 = JH(P); D1 + (x + 12, 4 : 1) + sage: D2 = JH(Q); D2 + (x + 11, 5 : 1) + sage: D = JH(P,Q); D + (x^2 + 10*x + 2, 4*x : 1) + sage: D == D1 - D2 + True + sage: JH(x^2+10*x+2, 4*x, 1) == D + True + sage: JH(x^2+10*x+2, 4*x, 0) == D + False + + The points at infinity may also be embedded into the Jacobian:: + + sage: [P0,P1] = H.points_at_infinity() + sage: JH(P0) + (1, 0 : 2) + sage: JH(P1) + (1, 0 : 1) + + TESTS:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 1) + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: J() == J(0) == J(1, 0) == J.zero() == JH(0) == 0 + True + + TODO: Merge this code with that of `HyperellipticJacobianHomset` + """ + g = self.curve().genus() + R = self.curve().polynomial_ring() + + if len(args) > 3: + raise ValueError("at most three arguments are allowed as input") + + if len(args) == 0 or (len(args) == 1 and args[0] == ()): + return self._morphism_element( + self, R.one(), R.zero(), n=(g + 1) // 2, check=check + ) + + if len(args) == 1 and isinstance(args[0], (list, tuple)): + args = args[0] + + if len(args) == 1: + P1 = args[0] + if P1 == 0: + u = R.one() + v = R.zero() + n = (g + 1) // 2 + elif isinstance(P1, self._morphism_element): + return P1 + elif isinstance(P1, SchemeMorphism_point_weighted_projective_ring): + args = args + ( + self.curve().distinguished_point(), + ) # this case will now be handled below. + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + if len(args) == 2 or len(args) == 3: + P1 = args[0] + P2 = args[1] + if isinstance(P1, SchemeMorphism_point_weighted_projective_ring) and isinstance( + P2, SchemeMorphism_point_weighted_projective_ring + ): + if len(args) == 3: + raise ValueError("the input must consist of at most two points") + u1, v1, n1 = self.point_to_mumford_coordinates(P1) + P2_inv = self.curve().hyperelliptic_involution(P2) + u2, v2, n2 = self.point_to_mumford_coordinates(P2_inv) + u, v, _ = self._cantor_composition_generic(u1, v1, u2, v2) + n = (g + 1) // 2 - 1 + n1 + n2 # this solution is a bit hacky + # This checks whether P1 and P2 can be interpreted as polynomials + elif R.coerce_map_from(parent(P1)) and R.coerce_map_from(parent(P2)): + u = R(P1) + v = R(P2) + if len(args) == 3 and isinstance(args[2], (int, Integer)): + n = args[2] + else: + n = ( + g - u.degree() + 1 + ) // 2 # TODO: do we really want to allow this input? + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + return self._morphism_element(self, u, v, n=n, check=check) + + def cantor_composition(self, u1, v1, n1, u2, v2, n2): + """ + Follows algorithm 3.4 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + + TODO: when h = 0 we can speed this up. + """ + # Collect data from HyperellipticCurve + H = self.curve() + g = H.genus() + + # Cantor composition + u3, v3, s_deg = self._cantor_composition_generic(u1, v1, u2, v2) + + # Compute new weight + n3 = n1 + n2 + s_deg - ((g + 1) // 2) + + return u3, v3, n3 + + def cantor_reduction(self, u0, v0, n0): + """ + Follows algorithm 3.5 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Collect data from HyperellipticCurve + H = self.curve() + g = H.genus() + + # Perform regular cantor reduction + u1, v1 = self._cantor_reduction_generic(u0, v0) + + # Compute the counter weights + d0 = u0.degree() + d1 = u1.degree() + a_plus, a_minus = H.roots_at_infinity() + + if v0.degree() <= g + 1: + leading_coefficient = v0[g + 1] # check coefficient of x^(g+1) + if leading_coefficient == a_plus: + n1 = n0 + d0 - g - 1 + elif leading_coefficient == a_minus: + n1 = n0 + g + 1 - d1 + else: + n1 = n0 + (d0 - d1) // 2 + else: + n1 = n0 + (d0 - d1) // 2 + return u1, v1, n1 + + def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): + """ + Follows algorithm 3.6 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Collect data from HyperellipticCurve + H = self.curve() + f, h = H.hyperelliptic_polynomials() + g = H.genus() + + # Pick either G_plus or G_minus for reduction + G_plus, G_minus = H.infinite_polynomials() + if plus: + G = G_plus + else: + G = G_minus + + v1_prime = G + ((v0 - G) % u0) + u1 = (v1_prime**2 + h * v1_prime - f) // u0 + u1 = u1.monic() + v1 = (-h - v1_prime) % u1 + + # Compute the counter weights + if plus: + n1 = n0 + u0.degree() - g - 1 + else: + n1 = n0 + g + 1 - u1.degree() + + return u1, v1, n1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py new file mode 100644 index 00000000000..0fbc9a6e5af --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -0,0 +1,368 @@ +from sage.groups.generic import order_from_multiple +from sage.misc.cachefunc import cached_method +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.schemes.generic.morphism import SchemeMorphism +from sage.structure.element import AdditiveGroupElement +from sage.structure.richcmp import richcmp + + +class MumfordDivisorClassField(AdditiveGroupElement, SchemeMorphism): + r""" + An element of a Jacobian defined over a field, i.e. in + `J(K) = \mathrm{Pic}^0_K(C)`. + """ + + def __init__(self, parent, u, v, check=True): + SchemeMorphism.__init__(self, parent) + + if not isinstance(u, Polynomial) or not isinstance(v, Polynomial): + raise TypeError(f"arguments {u = } and {v = } must be polynomials") + + # TODO: + # 1. allow elements of the base field as input + # (in particular something like (u,v) = (x-alpha, 0)) + + # Ensure the divisor is valid + if check: + f, h = parent.curve().hyperelliptic_polynomials() + assert ( + v**2 + v * h - f + ) % u == 0, f"{u = }, {v = } do not define a divisor on the Jacobian" + + # TODO: should we automatically do reduction here if the degree of u is + # too large? + + self._parent = parent + self._u = u + self._v = v + + def parent(self): + return self._parent + + def scheme(self): + r""" + Return the scheme this morphism maps to; or, where this divisor lives. + + .. WARNING:: + + Although a pointset is defined over a specific field, the + scheme returned may be over a different (usually smaller) + field. The example below demonstrates this: the pointset + is determined over a number field of absolute degree 2 but + the scheme returned is defined over the rationals. + + EXAMPLES:: + + sage: x = QQ['x'].gen() + sage: f = x^5 + x + sage: H = HyperellipticCurve(f) + sage: F. = NumberField(x^2 - 2, 'a') # needs sage.rings.number_field + sage: J = H.jacobian()(F); J # needs sage.rings.number_field + Set of rational points of Jacobian of Hyperelliptic Curve + over Number Field in a with defining polynomial x^2 - 2 + defined by y^2 = x^5 + x + + :: + + sage: P = J(H.lift_x(F(1))) # needs sage.rings.number_field + sage: P.scheme() # needs sage.rings.number_field + Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x + """ + return self.codomain() + + def _repr_(self) -> str: + return f"({self._u}, {self._v})" + + def is_zero(self): + """ + Return ``True`` if this point is zero. + + EXAMPLES:: + + sage: x = polygen(GF(5)) + sage: H = HyperellipticCurveSmoothModel(x^5 + 3 * x + 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 5 defined by y^2 = x^5 + 3*x + 1 + sage: points = list(J) + sage: [P for P in points if P.is_zero()] + [(1, 0)] + sage: [P for P in points if P == 0] + [(1, 0)] + """ + return self._u.is_one() and self._v.is_zero() + + def uv(self): + """ + Return the `u` and `v` component of this Mumford divisor. + + EXAMPLES:: + + sage: x = polygen(GF(1993)) + sage: H = HyperellipticCurveSmoothModel(x^7 + 3 * x + 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 1993 defined by y^2 = x^7 + 3*x + 1 + sage: u, v = x^3 + 1570*x^2 + 1930*x + 81, 368*x^2 + 1478*x + 256 + sage: P = J(u, v); P + (x^3 + 1570*x^2 + 1930*x + 81, 368*x^2 + 1478*x + 256) + sage: P.uv() == (u, v) + True + """ + return (self._u, self._v) + + def _richcmp_(self, other, op) -> bool: + """ + Method for rich comparison. + + TESTS:: + + sage: x = polygen(GF(23)) + sage: H = HyperellipticCurveSmoothModel(x^7 + x + 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 23 defined by y^2 = x^7 + x + 1 + sage: P = J.random_element() + sage: P == P + True + sage: P != P + False + + sage: Z = J(0); Z + (1, 0) + sage: Z == 0 + True + sage: Z != 0 + False + """ + # _richcmp_ is called after type unification/coercion + assert isinstance(other, MumfordDivisorClassField) + return richcmp(tuple(self), tuple(other), op) + + def __iter__(self): + """ + TESTS: + + Indirect tests:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x + 1) + sage: J = H.jacobian() + sage: P = J(x + 5, 12) + sage: list(P) + [x + 5, 12] + sage: tuple(P) + (x + 5, 12) + """ + yield from [self._u, self._v] + + def __getitem__(self, n): + return (self._u, self._v)[n] + + def __hash__(self): + return hash(tuple(self)) + + def __bool__(self): + return not self.is_zero() + + @cached_method + def order(self): + n = self.parent().order() + return order_from_multiple(self, n) + + def degree(self): + """ + Returns the degree of the affine part of the divisor. + """ + return self._u.degree() + + def _add_(self, other): + # `other` should be coerced automatically before reaching here + assert isinstance(other, type(self)) + + # Collect data from HyperellipticCurve + H = self.parent().curve() + g = H.genus() + + # Extract out mumford coordinates + u1, v1 = self.uv() + u2, v2 = other.uv() + + # Step one: cantor composition of the two divisors + u3, v3 = self._parent.cantor_composition(u1, v1, u2, v2) + + # Step two: cantor reduction of the above to ensure + # the degree of u is smaller than g + 1 + while u3.degree() > g: + u3, v3 = self._parent.cantor_reduction(u3, v3) + u3 = u3.monic() + v3 = v3 % u3 + + return self._parent(u3, v3, check=False) + + def _neg_(self): + _, h = self.parent().curve().hyperelliptic_polynomials() + u0, v0 = self.uv() + + if h.is_zero(): + return self._parent(u0, -v0, check=False) + + v1 = (-h - v0) % u0 + return self._parent(u0, v1, check=False) + + +# ======================================================================= +# Child classes for representation for ramified, inert and split models +# ======================================================================= + + +class MumfordDivisorClassFieldRamified(MumfordDivisorClassField): + def __init__(self, parent, u, v, check=True): + if not parent.curve().is_ramified(): + raise TypeError("hyperelliptic curve must be ramified") + + super().__init__(parent, u, v, check=check) + + +class MumfordDivisorClassFieldInert(MumfordDivisorClassField): + def __init__(self, parent, u, v, check=True): + if not parent.curve().is_inert(): + raise TypeError("hyperelliptic curve must be inert") + + if u.degree() % 2: + raise ValueError(f"mumford coordinate {u} must have even degree") + + g = parent.curve().genus() + self._n = (g - u.degree()) // 2 + super().__init__(parent, u, v, check=check) + + def __repr__(self): + return f"({self._u}, {self._v} : {self._n})" + + +class MumfordDivisorClassFieldSplit(MumfordDivisorClassField): + def __init__(self, parent, u, v, n=0, check=True): + if not parent.curve().is_split(): + raise TypeError("hyperelliptic curve must be split") + + # Ensure the weight is set correctly + g = parent.curve().genus() + assert 0 <= n <= (g - u.degree()) + self._n = n + self._m = g - u.degree() - n + + super().__init__(parent, u, v, check=check) + + def __repr__(self): + return f"({self._u}, {self._v} : {self._n})" + + def is_zero(self): + g = self._parent.curve().genus() + return self._u.is_one() and self._v.is_zero() and self._n == (g + 1) // 2 + + def __iter__(self): + """ + TESTS: + + Indirect tests:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^6 - x + 1) + sage: J = H.jacobian() + sage: P = J(x + 2, 5) + sage: list(P) + [x + 2, 5, 1] + sage: tuple(P) + (x + 2, 5, 1) + sage: Q = J(x + 2, 5, 0) + sage: list(Q) + [x + 2, 5, 0] + sage: tuple(Q) + (x + 2, 5, 0) + sage: P == Q + False + """ + yield from [self._u, self._v, self._n] + + def __hash__(self): + return hash(tuple(self)) + + def _add_(self, other): + r""" + Follows algorithm 3.7 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Ensure we are adding two divisors + assert isinstance(other, type(self)) + + # Collect data from HyperellipticCurve + H = self.parent().curve() + g = H.genus() + + # Extract out mumford coordinates + u1, v1 = self.uv() + u2, v2 = other.uv() + + # Extract out integers for weights + n1, n2 = self._n, other._n + + # Step one: cantor composition of the two divisors + u3, v3, n3 = self._parent.cantor_composition(u1, v1, n1, u2, v2, n2) + + # Step two: cantor reduction of the above to ensure + # the degree of u is smaller than g + 1 + while u3.degree() > (g + 1): + u3, v3, n3 = self._parent.cantor_reduction(u3, v3, n3) + u3 = u3.monic() + + # Step three: compose and then reduce at infinity to ensure + # unique representation of D + while n3 < 0 or n3 > g - u3.degree(): + if n3 < 0: + u3, v3, n3 = self._parent.cantor_compose_at_infinity( + u3, v3, n3, plus=False + ) + else: + u3, v3, n3 = self._parent.cantor_compose_at_infinity( + u3, v3, n3, plus=True + ) + + return self._parent(u3, v3, n3, check=False) + + def _neg_(self): + r""" + Follows algorithm 3.8 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Collect data from HyperellipticCurve + H = self.parent().curve() + _, h = H.hyperelliptic_polynomials() + g = H.genus() + + u0, v0 = self.uv() + n0, m0 = self._n, self._m + + # Case for even genus + if not (g % 2): + v1 = (-h - v0) % u0 + u1 = u0 + n1 = m0 + # Odd genus, positive n0 + elif n0 > 0: + v1 = (-h - v0) % u0 + # Note: I think this is a typo in the paper + # n1 = g - m0 - u1.degree() + 1 + u1 = u0 + n1 = m0 + 1 + else: + # Composition at infinity always with plus=True. + # want to "substract" \infty_+ - \infty_- + (u1, v1, n1) = self._parent.cantor_compose_at_infinity( + u0, -h - v0, n0, plus=True + ) + n1 = n1 - n0 + m0 + 1 + + return self._parent(u1, v1, n1, check=False) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build new file mode 100644 index 00000000000..f2b2dff0459 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build @@ -0,0 +1,31 @@ +py.install_sources( + 'all.py', + 'hyperelliptic_constructor.py', + 'hyperelliptic_finite_field.py', + 'hyperelliptic_g2.py', + 'hyperelliptic_generic.py', + 'hyperelliptic_padic_field.py', + 'hyperelliptic_rational_field.py', + 'invariants.py', + 'jacobian_g2_generic.py', + 'jacobian_g2_homset_inert.py', + 'jacobian_g2_homset_ramified.py', + 'jacobian_g2_homset_split.py', + 'jacobian_generic.py', + 'jacobian_homset_generic.py', + 'jacobian_homset_inert.py', + 'jacobian_homset_ramified.py', + 'jacobian_homset_split.py', + 'jacobian_morphism.py', + 'mestre.py', + 'monsky_washnitzer.py', + 'weighted_projective_curve.py', + 'weighted_projective_homset.py', + 'weighted_projective_point.py', + 'weighted_projective_space.py', + subdir: 'sage/schemes/hyperelliptic_curves_smooth_model', +) + +extension_data_cpp = {} + + diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py new file mode 100644 index 00000000000..b7edaddab6a --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py @@ -0,0 +1,375 @@ +r""" +Mestre's algorithm + +This file contains functions that: + +- create hyperelliptic curves from the Igusa-Clebsch invariants (over + `\QQ` and finite fields) +- create Mestre's conic from the Igusa-Clebsch invariants + +AUTHORS: + +- Florian Bouyer +- Marco Streng + +""" +# ***************************************************************************** +# Copyright (C) 2011, 2012, 2013 +# Florian Bouyer +# Marco Streng +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.matrix.constructor import Matrix +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.schemes.plane_conics.constructor import Conic + + +# TODO: +# precision is unused +def HyperellipticCurve_from_invariants( + i, reduced=True, precision=None, algorithm="default" +): + r""" + Returns a hyperelliptic curve with the given Igusa-Clebsch invariants up to + scaling. + + The output is a curve over the field in which the Igusa-Clebsch invariants + are given. The output curve is unique up to isomorphism over the algebraic + closure. If no such curve exists over the given field, then raise a + ValueError. + + INPUT: + + - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch + invariants: I2,I4,I6,I10. + - ``reduced`` - Boolean (default = True) If True, tries to reduce the + polynomial defining the hyperelliptic curve using the function + :func:`reduce_polynomial` (see the :func:`reduce_polynomial` + documentation for more details). + - ``precision`` - integer (default = None) Which precision for real and + complex numbers should the reduction use. This only affects the + reduction, not the correctness. If None, the algorithm uses the default + 53 bit precision. + - ``algorithm`` - ``'default'`` or ``'magma'``. If set to ``'magma'``, uses + Magma to parameterize Mestre's conic (needs Magma to be installed). + + OUTPUT: + + A hyperelliptic curve object. + + EXAMPLES: + + Examples over the rationals:: + + sage: HyperellipticCurve_from_invariants([3840,414720,491028480,2437709561856]) + Traceback (most recent call last): + ... + NotImplementedError: Reduction of hyperelliptic curves not yet implemented. + See issues #14755 and #14756. + + sage: HyperellipticCurve_from_invariants([3840,414720,491028480,2437709561856], reduced=False) + Hyperelliptic Curve over Rational Field defined by + y^2 = -46656*x^6 + 46656*x^5 - 19440*x^4 + 4320*x^3 - 540*x^2 + 4410*x - 1 + + sage: HyperellipticCurve_from_invariants([21, 225/64, 22941/512, 1]) + Traceback (most recent call last): + ... + NotImplementedError: Reduction of hyperelliptic curves not yet implemented. + See issues #14755 and #14756. + + An example over a finite field:: + + sage: H = HyperellipticCurve_from_invariants([GF(13)(1), 3, 7, 5]); H + Hyperelliptic Curve over Finite Field of size 13 defined by ... + sage: H.igusa_clebsch_invariants() + (4, 9, 6, 11) + + An example over a number field:: + + sage: K = QuadraticField(353, 'a') # needs sage.rings.number_field + sage: H = HyperellipticCurve_from_invariants([21, 225/64, 22941/512, 1], # needs sage.rings.number_field + ....: reduced=false) + sage: f = K['x'](H.hyperelliptic_polynomials()[0]) # needs sage.rings.number_field + + If the Mestre Conic defined by the Igusa-Clebsch invariants has no rational + points, then there exists no hyperelliptic curve over the base field with + the given invariants.:: + + sage: HyperellipticCurve_from_invariants([1,2,3,4]) + Traceback (most recent call last): + ... + ValueError: No such curve exists over Rational Field as there are + no rational points on Projective Conic Curve over Rational Field defined by + -2572155000*u^2 - 317736000*u*v + 1250755459200*v^2 + 2501510918400*u*w + + 39276887040*v*w + 2736219686912*w^2 + + Mestre's algorithm only works for generic curves of genus two, so another + algorithm is needed for those curves with extra automorphism. See also + :issue:`12199`:: + + sage: P. = QQ[] + sage: C = HyperellipticCurveSmoothModel(x^6 + 1) + sage: i = C.igusa_clebsch_invariants() + sage: HyperellipticCurve_from_invariants(i) + Traceback (most recent call last): + ... + TypeError: F (=0) must have degree 2 + + + Igusa-Clebsch invariants also only work over fields of characteristic + different from 2, 3, and 5, so another algorithm will be needed for fields + of those characteristics. See also :issue:`12200`:: + + sage: P. = GF(3)[] + sage: HyperellipticCurveSmoothModel(x^6 + x + 1).igusa_clebsch_invariants() + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + sage: HyperellipticCurve_from_invariants([GF(5)(1), 1, 0, 1]) + Traceback (most recent call last): + ... + ZeroDivisionError: inverse of Mod(0, 5) does not exist + + ALGORITHM: + + This is Mestre's algorithm [Mes1991]_. Our implementation is based on the + formulae on page 957 of [LY2001]_, cross-referenced with [Wam1999b]_ to + correct typos. + + First construct Mestre's conic using the :func:`Mestre_conic` function. + Parametrize the conic if possible. + Let `f_1, f_2, f_3` be the three coordinates of the parametrization of the + conic by the projective line, and change them into one variable by letting + `F_i = f_i(t, 1)`. Note that each `F_i` has degree at most 2. + + Then construct a sextic polynomial + `f = \sum_{0<=i,j,k<=3}{c_{ijk}*F_i*F_j*F_k}`, + where `c_{ijk}` are defined as rational functions in the invariants + (see the source code for detailed formulae for `c_{ijk}`). + The output is the hyperelliptic curve `y^2 = f`. + """ + from sage.structure.sequence import Sequence + + i = Sequence(i) + k = i.universe() + try: + k = k.fraction_field() + except (TypeError, AttributeError, NotImplementedError): + pass + + MConic, x, y, z = Mestre_conic(i, xyz=True) + if k.is_finite(): + reduced = False + + t = k["t"].gen() + + if algorithm == "magma": + from sage.interfaces.magma import magma + from sage.misc.sage_eval import sage_eval + + if MConic.has_rational_point(algorithm="magma"): + parametrization = [ + l.replace("$.1", "t").replace("$.2", "u") + for l in str(magma(MConic).Parametrization()).splitlines()[4:7] + ] + [F1, F2, F3] = [ + sage_eval(p, locals={"t": t, "u": 1, "a": k.gen()}) + for p in parametrization + ] + else: + raise ValueError( + f"No such curve exists over {k} as there are no " + f"rational points on {MConic}" + ) + else: + if MConic.has_rational_point(): + parametrization = MConic.parametrization(morphism=False)[0] + [F1, F2, F3] = [p(t, 1) for p in parametrization] + else: + raise ValueError( + f"No such curve exists over {k} as there are no " + f"rational points on {MConic}" + ) + + # setting the cijk from Mestre's algorithm + c111 = 12 * x * y - 2 * y / 3 - 4 * z + c112 = -18 * x**3 - 12 * x * y - 36 * y**2 - 2 * z + c113 = -9 * x**3 - 36 * x**2 * y - 4 * x * y - 6 * x * z - 18 * y**2 + c122 = c113 + c123 = ( + -54 * x**4 - 36 * x**2 * y - 36 * x * y**2 - 6 * x * z - 4 * y**2 - 24 * y * z + ) + c133 = ( + -27 * x**4 / 2 + - 72 * x**3 * y + - 6 * x**2 * y + - 9 * x**2 * z + - 39 * x * y**2 + - 36 * y**3 + - 2 * y * z + ) + c222 = -27 * x**4 - 18 * x**2 * y - 6 * x * y**2 - 8 * y**2 / 3 + 2 * y * z + c223 = 9 * x**3 * y - 27 * x**2 * z + 6 * x * y**2 + 18 * y**3 - 8 * y * z + c233 = ( + -81 * x**5 / 2 + - 27 * x**3 * y + - 9 * x**2 * y**2 + - 4 * x * y**2 + + 3 * x * y * z + - 6 * z**2 + ) + c333 = ( + 27 * x**4 * y / 2 + - 27 * x**3 * z / 2 + + 9 * x**2 * y**2 + + 3 * x * y**3 + - 6 * x * y * z + + 4 * y**3 / 3 + - 10 * y**2 * z + ) + + # writing out the hyperelliptic curve polynomial + f = ( + c111 * F1**3 + + c112 * F1**2 * F2 + + c113 * F1**2 * F3 + + c122 * F1 * F2**2 + + c123 * F1 * F2 * F3 + + c133 * F1 * F3**2 + + c222 * F2**3 + + c223 * F2**2 * F3 + + c233 * F2 * F3**2 + + c333 * F3**3 + ) + + try: + f = f * f.denominator() # clear the denominator + except (AttributeError, TypeError): + pass + + if reduced: + raise NotImplementedError( + "Reduction of hyperelliptic curves not " + "yet implemented. " + "See issues #14755 and #14756." + ) + + return HyperellipticCurveSmoothModel(f) + + +def Mestre_conic(i, xyz=False, names="u,v,w"): + r""" + Return the conic equation from Mestre's algorithm given the Igusa-Clebsch + invariants. + + It has a rational point if and only if a hyperelliptic curve + corresponding to the invariants exists. + + INPUT: + + - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch + invariants: I2, I4, I6, I10 + - ``xyz`` - Boolean (default: False) if True, the algorithm also + returns three invariants x,y,z used in Mestre's algorithm + - ``names`` (default: 'u,v,w') - the variable names for the conic + + OUTPUT: + + A Conic object + + EXAMPLES: + + A standard example:: + + sage: Mestre_conic([1,2,3,4]) + Projective Conic Curve over Rational Field defined by + -2572155000*u^2 - 317736000*u*v + 1250755459200*v^2 + 2501510918400*u*w + + 39276887040*v*w + 2736219686912*w^2 + + Note that the algorithm works over number fields as well:: + + sage: x = polygen(ZZ, 'x') + sage: k = NumberField(x^2 - 41, 'a') # needs sage.rings.number_field + sage: a = k.an_element() # needs sage.rings.number_field + sage: Mestre_conic([1, 2 + a, a, 4 + a]) # needs sage.rings.number_field + Projective Conic Curve over Number Field in a with defining polynomial x^2 - 41 + defined by (-801900000*a + 343845000)*u^2 + (855360000*a + 15795864000)*u*v + + (312292800000*a + 1284808579200)*v^2 + (624585600000*a + 2569617158400)*u*w + + (15799910400*a + 234573143040)*v*w + (2034199306240*a + 16429854656512)*w^2 + + And over finite fields:: + + sage: Mestre_conic([GF(7)(10), GF(7)(1), GF(7)(2), GF(7)(3)]) + Projective Conic Curve over Finite Field of size 7 + defined by -2*u*v - v^2 - 2*u*w + 2*v*w - 3*w^2 + + An example with ``xyz``:: + + sage: Mestre_conic([5,6,7,8], xyz=True) + (Projective Conic Curve over Rational Field + defined by -415125000*u^2 + 608040000*u*v + 33065136000*v^2 + + 66130272000*u*w + 240829440*v*w + 10208835584*w^2, + 232/1125, -1072/16875, 14695616/2109375) + + ALGORITHM: + + The formulas are taken from pages 956 - 957 of [LY2001]_ and based on pages + 321 and 332 of [Mes1991]_. + + See the code or [LY2001]_ for the detailed formulae defining x, y, z and L. + + """ + from sage.structure.sequence import Sequence + + k = Sequence(i).universe() + try: + k = k.fraction_field() + except (TypeError, AttributeError, NotImplementedError): + pass + + I2, I4, I6, I10 = i + + # Setting x,y,z as in Mestre's algorithm (Using Lauter and Yang's formulas) + x = 8 * (1 + 20 * I4 / (I2**2)) / 225 + y = 16 * (1 + 80 * I4 / (I2**2) - 600 * I6 / (I2**3)) / 3375 + z = ( + -64 + * ( + -10800000 * I10 / (I2**5) + - 9 + - 700 * I4 / (I2**2) + + 3600 * I6 / (I2**3) + + 12400 * I4**2 / (I2**4) + - 48000 * I4 * I6 / (I2**5) + ) + / 253125 + ) + + L = Matrix( + [ + [x + 6 * y, 6 * x**2 + 2 * y, 2 * z], + [6 * x**2 + 2 * y, 2 * z, 9 * x**3 + 4 * x * y + 6 * y**2], + [ + 2 * z, + 9 * x**3 + 4 * x * y + 6 * y**2, + 6 * x**2 * y + 2 * y**2 + 3 * x * z, + ], + ] + ) + + try: + L = L * L.denominator() # clears the denominator + except (AttributeError, TypeError): + pass + + u, v, w = PolynomialRing(k, names).gens() + MConic = Conic(k, L, names) + if xyz: + return MConic, x, y, z + return MConic diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py new file mode 100644 index 00000000000..8be6e9424da --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py @@ -0,0 +1,3999 @@ +r""" +Computation of Frobenius matrix on Monsky-Washnitzer cohomology + +The most interesting functions to be exported here are +:func:`matrix_of_frobenius` and :func:`adjusted_prec`. + +Currently this code is limited to the case `p \geq 5` (no +`GF(p^n)` for `n > 1`), and only handles the +elliptic curve case (not more general hyperelliptic curves). + +REFERENCES: + +- [Ked2001]_ + +- [Edix]_ + +AUTHORS: + +- David Harvey and Robert Bradshaw: initial code developed at the 2006 + MSRI graduate workshop, working with Jennifer Balakrishnan and Liang + Xiao + +- David Harvey (2006-08): cleaned up, rewrote some chunks, lots more + documentation, added Newton iteration method, added more complete + 'trace trick', integrated better into Sage. + +- David Harvey (2007-02): added algorithm with sqrt(p) complexity + (removed in May 2007 due to better C++ implementation) + +- Robert Bradshaw (2007-03): keep track of exact form in reduction + algorithms + +- Robert Bradshaw (2007-04): generalization to hyperelliptic curves + +- Julian Rueth (2014-05-09): improved caching +""" + +# **************************************************************************** +# Copyright (C) 2006 William Stein +# 2006 Robert Bradshaw +# 2006 David Harvey +# 2014 Julian Rueth +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.arith.misc import binomial +from sage.arith.misc import integer_ceil as ceil +from sage.categories.algebras import Algebras +from sage.categories.integral_domains import IntegralDomains +from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_import import lazy_import +from sage.misc.misc import newton_method_sizes +from sage.misc.profiler import Profiler +from sage.misc.repr import repr_lincomb +from sage.modules.free_module import FreeModule +from sage.modules.free_module_element import FreeModuleElement, vector +from sage.modules.module import Module +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing as Integers +from sage.rings.infinity import Infinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational import Rational +from sage.rings.rational_field import QQ +from sage.rings.rational_field import RationalField as Rationals +from sage.schemes.elliptic_curves.constructor import EllipticCurve +from sage.schemes.elliptic_curves.ell_generic import EllipticCurve_generic +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic import ( + HyperellipticCurveSmoothModel_generic, +) +from sage.structure.element import ModuleElement +from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp +from sage.structure.unique_representation import UniqueRepresentation + +# TODO: why are these lazy imports used? +lazy_import("sage.functions.log", "log") +lazy_import("sage.rings.lazy_series_ring", "LazyLaurentSeriesRing") +lazy_import("sage.rings.padics.factory", "Qp", as_="pAdicField") + + +class SpecialCubicQuotientRingElement(ModuleElement): + """ + An element of a :class:`SpecialCubicQuotientRing`. + """ + + def __init__(self, parent, p0, p1, p2, check=True): + """ + Construct the element `p_0 + p_1*x + p_2*x^2`, where + the `p_i` are polynomials in `T`. + + INPUT: + + - ``parent`` -- a :class:`SpecialCubicQuotientRing` + + - ``p0``, ``p1``, ``p2`` -- coefficients; must be coercible + into parent.poly_ring() + + - ``check`` -- boolean (default: ``True``); whether to carry + out coercion + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing, SpecialCubicQuotientRingElement + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: SpecialCubicQuotientRingElement(R, 2, 3, 4) + (2) + (3)*x + (4)*x^2 + + TESTS:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: TestSuite(R).run() + sage: p = R.create_element(t, t^2 - 2, 3) + sage: -p + (124*T) + (124*T^2 + 2)*x + (122)*x^2 + """ + if not isinstance(parent, SpecialCubicQuotientRing): + raise TypeError(f"parent (={parent}) must be a SpecialCubicQuotientRing") + + ModuleElement.__init__(self, parent) + + if check: + poly_ring = parent._poly_ring + p0 = poly_ring(p0) + p1 = poly_ring(p1) + p2 = poly_ring(p2) + + self._triple = (p0, p1, p2) + + def coeffs(self): + """ + Return list of three lists of coefficients, corresponding to the + `x^0`, `x^1`, `x^2` coefficients. + + The lists are zero padded to the same length. The list entries + belong to the base ring. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: p = R.create_element(t, t^2 - 2, 3) + sage: p.coeffs() + [[0, 1, 0], [123, 0, 1], [3, 0, 0]] + """ + coeffs = [column.coefficients(sparse=False) for column in self._triple] + degree = max([len(x) for x in coeffs]) + base_ring = self.parent().base_ring() + for column in coeffs: + column.extend([base_ring(0)] * (degree - len(column))) + return coeffs + + def __bool__(self) -> bool: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: not x + False + sage: not T + False + sage: not R.create_element(0, 0, 0) + True + """ + return bool(self._triple[0]) or bool(self._triple[1]) or bool(self._triple[2]) + + def _richcmp_(self, other, op) -> bool: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: x, t = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4)).gens() + sage: x == t + False + sage: x == x + True + sage: x == x + x - x + True + """ + return richcmp(self._triple, other._triple, op) + + def _repr_(self) -> str: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: x + T*x - 2*T^2 + (123*T^2) + (T + 1)*x + (0)*x^2 + """ + return "({}) + ({})*x + ({})*x^2".format(*self._triple) + + def _latex_(self) -> str: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: f = x + T*x - 2*T^2 + sage: latex(f) + (123 T^{2}) + (T + 1)x + (0)x^2 + """ + return "(%s) + (%s)x + (%s)x^2" % tuple( + column._latex_() for column in self._triple + ) + + def _add_(self, other): + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: f = R.create_element(2, t, t^2 - 3) + sage: g = R.create_element(3 + t, -t, t) + sage: f + g + (T + 5) + (0)*x + (T^2 + T + 122)*x^2 + """ + P = self.parent() + return P.element_class( + P, + self._triple[0] + other._triple[0], + self._triple[1] + other._triple[1], + self._triple[2] + other._triple[2], + check=False, + ) + + def _sub_(self, other): + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: f = R.create_element(2, t, t^2 - 3) + sage: g = R.create_element(3 + t, -t, t) + sage: f - g + (124*T + 124) + (2*T)*x + (T^2 + 124*T + 122)*x^2 + """ + P = self.parent() + return P.element_class( + P, + self._triple[0] - other._triple[0], + self._triple[1] - other._triple[1], + self._triple[2] - other._triple[2], + check=False, + ) + + def shift(self, n): + """ + Return this element multiplied by `T^n`. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: f = R.create_element(2, t, t^2 - 3) + sage: f + (2) + (T)*x + (T^2 + 122)*x^2 + sage: f.shift(1) + (2*T) + (T^2)*x + (T^3 + 122*T)*x^2 + sage: f.shift(2) + (2*T^2) + (T^3)*x + (T^4 + 122*T^2)*x^2 + """ + P = self.parent() + return P.element_class( + P, + self._triple[0].shift(n), + self._triple[1].shift(n), + self._triple[2].shift(n), + check=False, + ) + + def scalar_multiply(self, scalar): + """ + Multiply this element by a scalar, i.e. just multiply each + coefficient of `x^j` by the scalar. + + INPUT: + + - ``scalar`` -- either an element of ``base_ring``, or an + element of ``poly_ring`` + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: f = R.create_element(2, t, t^2 - 3) + sage: f + (2) + (T)*x + (T^2 + 122)*x^2 + sage: f.scalar_multiply(2) + (4) + (2*T)*x + (2*T^2 + 119)*x^2 + sage: f.scalar_multiply(t) + (2*T) + (T^2)*x + (T^3 + 122*T)*x^2 + """ + P = self.parent() + scalar = P._poly_ring(scalar) + return P.element_class( + P, + scalar * self._triple[0], + scalar * self._triple[1], + scalar * self._triple[2], + check=False, + ) + + def square(self): + """ + Return the square of the element. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + + :: + + sage: f = R.create_element(1 + 2*t + 3*t^2, 4 + 7*t + 9*t^2, 3 + 5*t + 11*t^2) + sage: f.square() + (73*T^5 + 16*T^4 + 38*T^3 + 39*T^2 + 70*T + 120) + + (121*T^5 + 113*T^4 + 73*T^3 + 8*T^2 + 51*T + 61)*x + + (18*T^4 + 60*T^3 + 22*T^2 + 108*T + 31)*x^2 + """ + return self * self + + def _mul_(self, other): + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + + :: + + sage: f = R.create_element(1 + 2*t + 3*t^2, 4 + 7*t + 9*t^2, 3 + 5*t + 11*t^2) + sage: g = R.create_element(4 + 3*t + 7*t^2, 2 + 3*t + t^2, 8 + 4*t + 6*t^2) + sage: f * g + (65*T^5 + 27*T^4 + 33*T^3 + 75*T^2 + 120*T + 57) + + (66*T^5 + T^4 + 123*T^3 + 95*T^2 + 24*T + 50)*x + + (45*T^4 + 75*T^3 + 37*T^2 + 2*T + 52)*x^2 + """ + # Here we do Toom-Cook three-way multiplication, which reduces + # the naive 9 polynomial multiplications to only 5 polynomial + # multiplications. + + a0, a1, a2 = self._triple + b0, b1, b2 = other._triple + M = self.parent()._speedup_matrix + + if self is other: + # faster method if we are squaring + p0 = a0 * a0 + temp = a0 + 2 * a1 + 4 * a2 + p1 = temp * temp + temp = a0 + a1 + a2 + p2 = temp * temp + temp = 4 * a0 + 2 * a1 + a2 + p3 = temp * temp + p4 = a2 * a2 + + else: + p0 = a0 * b0 + p1 = (a0 + 2 * a1 + 4 * a2) * (b0 + 2 * b1 + 4 * b2) + p2 = (a0 + a1 + a2) * (b0 + b1 + b2) + p3 = (4 * a0 + 2 * a1 + a2) * (4 * b0 + 2 * b1 + b2) + p4 = a2 * b2 + + q1 = p1 - p0 - 16 * p4 + q2 = p2 - p0 - p4 + q3 = p3 - 16 * p0 - p4 + + c0 = p0 + c1 = M[0] * q1 + M[1] * q2 + M[2] * q3 + c2 = M[3] * q1 + M[4] * q2 + M[5] * q3 + c3 = M[6] * q1 + M[7] * q2 + M[8] * q3 + c4 = p4 + + # Now the product is c0 + c1 x + c2 x^2 + c3 x^3 + c4 x^4. + # We need to reduce mod y = x^3 + ax + b and return result. + + parent = self.parent() + T = parent._poly_generator + b = parent._b + a = parent._a + + # todo: These lines are necessary to get binop stuff working + # for certain base rings, e.g. when we compute b*c3 in the + # final line. They shouldn't be necessary. Need to fix this + # somewhere else in Sage. + a = parent._poly_ring(a) + b = parent._poly_ring(b) + + return parent.element_class( + parent, + -b * c3 + c0 + c3 * T, + -b * c4 - a * c3 + c1 + c4 * T, + -a * c4 + c2, + check=False, + ) + + +class SpecialCubicQuotientRing(UniqueRepresentation, Parent): + r""" + Specialised class for representing the quotient ring + `R[x,T]/(T - x^3 - ax - b)`, where `R` is an + arbitrary commutative base ring (in which 2 and 3 are invertible), + `a` and `b` are elements of that ring. + + Polynomials are represented internally in the form + `p_0 + p_1 x + p_2 x^2` where the `p_i` are + polynomials in `T`. Multiplication of polynomials always + reduces high powers of `x` (i.e. beyond `x^2`) to + powers of `T`. + + Hopefully this ring is faster than a general quotient ring because + it uses the special structure of this ring to speed multiplication + (which is the dominant operation in the frobenius matrix + calculation). I haven't actually tested this theory though... + + .. TODO:: + + Eventually we will want to run this in characteristic 3, so we + need to: (a) Allow `Q(x)` to contain an `x^2` term, and (b) Remove + the requirement that 3 be invertible. Currently this is used in + the Toom-Cook algorithm to speed multiplication. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R + SpecialCubicQuotientRing over Ring of integers modulo 125 + with polynomial T = x^3 + 124*x + 94 + sage: TestSuite(R).run() + + Get generators:: + + sage: x, T = R.gens() + sage: x + (0) + (1)*x + (0)*x^2 + sage: T + (T) + (0)*x + (0)*x^2 + + Coercions:: + + sage: R(7) + (7) + (0)*x + (0)*x^2 + + Create elements directly from polynomials:: + + sage: A = R.poly_ring() + sage: A + Univariate Polynomial Ring in T over Ring of integers modulo 125 + sage: z = A.gen() + sage: R.create_element(z^2, z+1, 3) + (T^2) + (T + 1)*x + (3)*x^2 + + Some arithmetic:: + + sage: x^3 + (T + 31) + (1)*x + (0)*x^2 + sage: 3 * x**15 * T**2 + x - T + (3*T^7 + 90*T^6 + 110*T^5 + 20*T^4 + 58*T^3 + 26*T^2 + 124*T) + + (15*T^6 + 110*T^5 + 35*T^4 + 63*T^2 + 1)*x + + (30*T^5 + 40*T^4 + 8*T^3 + 38*T^2)*x^2 + + Retrieve coefficients (output is zero-padded):: + + sage: x^10 + (3*T^2 + 61*T + 8) + (T^3 + 93*T^2 + 12*T + 40)*x + (3*T^2 + 61*T + 9)*x^2 + sage: (x^10).coeffs() + [[8, 61, 3, 0], [40, 12, 93, 1], [9, 61, 3, 0]] + + .. TODO:: + + write an example checking multiplication of these polynomials + against Sage's ordinary quotient ring arithmetic. I cannot seem + to get the quotient ring stuff happening right now... + """ + + def __init__(self, Q, laurent_series=False): + """ + Constructor. + + INPUT: + + - ``Q`` -- a polynomial of the form + `Q(x) = x^3 + ax + b`, where `a`, `b` belong to a ring in which + 2, 3 are invertible. + + - ``laurent_series`` -- boolean (default: ``False``); whether or not to allow + negative powers of `T` + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R + SpecialCubicQuotientRing over Ring of integers modulo 125 + with polynomial T = x^3 + 124*x + 94 + + :: + + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 + 2*t^2 - t + B(1/4)) + Traceback (most recent call last): + ... + ValueError: Q (=t^3 + 2*t^2 + 124*t + 94) must be of the form x^3 + ax + b + + :: + + sage: B. = PolynomialRing(Integers(10)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + 1) + Traceback (most recent call last): + ... + ArithmeticError: 2 and 3 must be invertible in the coefficient ring + (=Ring of integers modulo 10) of Q + """ + if not isinstance(Q, Polynomial): + raise TypeError("Q (=%s) must be a polynomial" % Q) + + if Q.degree() != 3 or not Q[2].is_zero(): + raise ValueError("Q (=%s) must be of the form x^3 + ax + b" % Q) + + base_ring = Q.parent().base_ring() + + if not base_ring(6).is_unit(): + raise ArithmeticError( + "2 and 3 must be invertible in the " + "coefficient ring (=%s) of Q" % base_ring + ) + + self._a = Q[1] + self._b = Q[0] + if laurent_series: + self._poly_ring = LaurentSeriesRing(base_ring, "T") # R[T] + else: + self._poly_ring = PolynomialRing(base_ring, "T") # R[T] + self._poly_generator = self._poly_ring.gen(0) # the generator T + Parent.__init__( + self, base=base_ring, category=Algebras(base_ring).Commutative() + ) + + # Precompute a matrix that is used in the Toom-Cook multiplication. + # This is where we need 2 and 3 invertible. + + # a good description of Toom-Cook is online at: + # https://gmplib.org/manual/Multiplication-Algorithms + m = matrix(QQ, [[1, -12, 2], [-3, 30, -3], [2, -12, 1]]) / 6 + self._speedup_matrix = m.change_ring(base_ring).list() + + def _repr_(self) -> str: + """ + String representation. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R + SpecialCubicQuotientRing over Ring of integers modulo 125 + with polynomial T = x^3 + 124*x + 94 + """ + return "SpecialCubicQuotientRing over %s with polynomial T = %s" % ( + self.base_ring(), + PolynomialRing(self.base_ring(), "x")([self._b, self._a, 0, 1]), + ) + + def poly_ring(self): + """ + Return the underlying polynomial ring in `T`. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R.poly_ring() + Univariate Polynomial Ring in T over Ring of integers modulo 125 + """ + return self._poly_ring + + def gens(self): + """ + Return a list [x, T] where x and T are the generators of the ring + (as element *of this ring*). + + .. NOTE:: + + I have no idea if this is compatible with the usual Sage + 'gens' interface. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: x + (0) + (1)*x + (0)*x^2 + sage: T + (T) + (0)*x + (0)*x^2 + """ + zero = self._poly_ring.zero() + one = self._poly_ring.one() + return ( + self.element_class(self, zero, one, zero, check=False), + self.element_class(self, self._poly_generator, zero, zero, check=False), + ) + + def _element_constructor_(self, *args, check=True): + """ + Create the element `p_0 + p_1*x + p_2*x^2`, where the `p_i` + are polynomials in `T`. + + INPUT: + + - ``p0``, ``p1``, ``p2`` -- coefficients; must be coercible + into poly_ring() + + - ``check`` -- boolean (default: ``True``); whether to carry + out coercion + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: A, z = R.poly_ring().objgen() + sage: R.create_element(z^2, z+1, 3) # indirect doctest + (T^2) + (T + 1)*x + (3)*x^2 + """ + if len(args) == 1 and args[0] in self.base_ring(): + p0 = self._poly_ring.coerce(args[0]) + p1 = p2 = self._poly_ring.zero() + else: + p0, p1, p2 = args + return self.element_class(self, p0, p1, p2, check=check) + + create_element = _element_constructor_ + + @cached_method + def one(self): + """ + Return the unit of ``self``. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R.one() + (1) + (0)*x + (0)*x^2 + """ + R = self._poly_ring + return self.element_class(self, R.one(), R.zero(), R.zero(), check=False) + + def _coerce_map_from_(self, R): + """ + Coercion system. + + EXAMPLES:: + + sage: Z125 = Integers(125) + sage: B. = PolynomialRing(Z125) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R.has_coerce_map_from(Z125) + True + """ + return self._poly_ring.has_coerce_map_from(R) + + Element = SpecialCubicQuotientRingElement + + +def transpose_list(input) -> list[list]: + """ + INPUT: + + - ``input`` -- list of lists, each list of the same length + + OUTPUT: list of lists such that ``output[i][j] = input[j][i]`` + + EXAMPLES:: + + sage: # TODO change import for sage + sage: from monsky_washnitzer import transpose_list + sage: L = [[1, 2], [3, 4], [5, 6]] + sage: transpose_list(L) + [[1, 3, 5], [2, 4, 6]] + """ + h = len(input) + w = len(input[0]) + + output = [] + for i in range(w): + row = [] + for j in range(h): + row.append(input[j][i]) + output.append(row) + return output + + +def helper_matrix(Q): + r""" + Compute the (constant) matrix used to calculate the linear + combinations of the `d(x^i y^j)` needed to eliminate the + negative powers of `y` in the cohomology (i.e., in + :func:`reduce_negative`). + + INPUT: + + - ``Q`` -- cubic polynomial + + EXAMPLES:: + + sage: t = polygen(QQ,'t') + sage: # TODO change import for sage + sage: from monsky_washnitzer import helper_matrix + sage: helper_matrix(t**3-4*t-691) + [ 64/12891731 -16584/12891731 4297329/12891731] + [ 6219/12891731 -32/12891731 8292/12891731] + [ -24/12891731 6219/12891731 -32/12891731] + """ + a = Q[1] + b = Q[0] + + # Discriminant (should be invertible for a curve of good reduction) + D = 4 * a**3 + 27 * b**2 + Dinv = D ** (-1) # NB do not use 1/D + + # This is the inverse of the matrix + # [ a, -3b, 0 ] + # [ 0, -2a, -3b ] + # [ 3, 0, -2a ] + + return Dinv * matrix( + [ + [4 * a**2, -6 * b * a, 9 * b**2], + [-9 * b, -2 * a**2, 3 * b * a], + [6 * a, -9 * b, -2 * a**2], + ] + ) + + +def lift(x): + r""" + Try to call ``x.lift()``, presumably from the `p`-adics to `\ZZ`. + + If this fails, it assumes the input is a power series, and tries to + lift it to a power series over `\QQ`. + + This function is just a very kludgy solution to the problem of + trying to make the reduction code (below) work over both `\ZZ_p` and + `\ZZ_p[[t]]`. + + EXAMPLES:: + + sage: # needs sage.rings.padics + sage: # TODO change import for sage + sage: from monsky_washnitzer import lift + sage: l = lift(Qp(13)(131)); l + 131 + sage: l.parent() + Integer Ring + sage: x = PowerSeriesRing(Qp(17),'x').gen() + sage: l = lift(4 + 5*x + 17*x**6); l + 4 + 5*t + 17*t^6 + sage: l.parent() + Power Series Ring in t over Rational Field + """ + try: + return x.lift() + except AttributeError: + return PowerSeriesRing(Rationals(), "t")(x.list(), x.prec()) + + +def reduce_negative(Q, p, coeffs, offset, exact_form=None): + """ + Apply cohomology relations to incorporate negative powers of + `y` into the `y^0` term. + + INPUT: + + - ``p`` -- prime + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list ``[a, b, c]`` represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + The reduction is performed in-place. The output is placed + in coeffs[offset]. Note that coeffs[i] will be meaningless for i + offset after this function is finished. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[10, 15, 20], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_negative(Q, 5, coeffs, 3) + sage: coeffs[3] + [28, 52, 9] + + :: + + sage: R. = Integers(7^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[7, 14, 21], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_negative(Q, 7, coeffs, 3) + sage: coeffs[3] + [245, 332, 9] + """ + + m = helper_matrix(Q).list() + base_ring = Q.base_ring() + next_a = coeffs[0] + + if exact_form is not None: + x = exact_form.parent().gen(0) + y = exact_form.parent()(exact_form.parent().base_ring().gen(0)) + + try: + three_j_plus_5 = 5 - base_ring(6 * offset) + three_j_plus_7 = 7 - base_ring(6 * offset) + six = base_ring(6) + + for i in range(offset): + j = 2 * (i - offset) + a = next_a + next_a = coeffs[i + 1] + + # todo: the following divisions will sometimes involve + # a division by (a power of) p. In all cases, we know (from + # Kedlaya's estimates) that the answer should be p-integral. + # However, since we're working over $Z/p^k Z$, we're not allowed + # to "divide by p". So currently we lift to Q, divide, and coerce + # back. Eventually, when pAdicInteger is implemented, and plays + # nicely with pAdicField, we should reimplement this stuff + # using pAdicInteger. + + if p.divides(j + 1): + # need to lift here to perform the division + a[0] = base_ring(lift(a[0]) / (j + 1)) + a[1] = base_ring(lift(a[1]) / (j + 1)) + a[2] = base_ring(lift(a[2]) / (j + 1)) + else: + j_plus_1_inv = ~base_ring(j + 1) + a[0] = a[0] * j_plus_1_inv + a[1] = a[1] * j_plus_1_inv + a[2] = a[2] * j_plus_1_inv + + c1 = m[3] * a[0] + m[4] * a[1] + m[5] * a[2] + c2 = m[6] * a[0] + m[7] * a[1] + m[8] * a[2] + next_a[0] = next_a[0] - three_j_plus_5 * c1 + next_a[1] = next_a[1] - three_j_plus_7 * c2 + + three_j_plus_7 = three_j_plus_7 + six + three_j_plus_5 = three_j_plus_5 + six + + if exact_form is not None: + c0 = m[0] * a[0] + m[1] * a[1] + m[2] * a[2] + exact_form += (c0 + c1 * x + c2 * x**2) * y ** (j + 1) + + except NotImplementedError: + raise NotImplementedError( + "It looks like you've found a " + "non-integral matrix of Frobenius! " + f"(Q={Q}, p={p})\nTime to write a paper." + ) + + coeffs[int(offset)] = next_a + + return exact_form + + +def reduce_positive(Q, p, coeffs, offset, exact_form=None): + """ + Apply cohomology relations to incorporate positive powers of + `y` into the `y^0` term. + + INPUT: + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list [a, b, c] represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + The reduction is performed in-place. The output is placed + in coeffs[offset]. Note that coeffs[i] will be meaningless for i + offset after this function is finished. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + + :: + + sage: coeffs = [[1, 2, 3], [10, 15, 20]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_positive(Q, 5, coeffs, 0) + sage: coeffs[0] + [16, 102, 88] + + :: + + sage: coeffs = [[9, 8, 7], [10, 15, 20]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_positive(Q, 5, coeffs, 0) + sage: coeffs[0] + [24, 108, 92] + """ + + base_ring = Q.base_ring() + next_a = coeffs[len(coeffs) - 1] + + Qa = Q[1] + Qb = Q[0] + + A = 2 * Qa + B = 3 * Qb + + offset = Integer(offset) + + if exact_form is not None: + x = exact_form.parent().gen(0) + y = exact_form.parent().base_ring().gen(0) + # y = exact_form.parent()(exact_form.parent().base_ring().gen(0)) + + for i in range(len(coeffs) - 1, offset, -1): + j = 2 * (i - offset) - 2 + a = next_a + next_a = coeffs[i - 1] + + a[0] = a[0] - Qa * a[2] / 3 # subtract d(y^j + 3) + if exact_form is not None: + exact_form += Q.base_ring()(a[2].lift() / (3 * j + 9)) * y ** (j + 3) + + # todo: see comments about pAdicInteger in reduceNegative() + + # subtract off c1 of d(x y^j + 1), and + if p.divides(3 * j + 5): + c1 = base_ring(lift(a[0]) / (3 * j + 5)) + else: + c1 = a[0] / (3 * j + 5) + + # subtract off c2 of d(x^2 y^j + 1) + if p.divides(3 * j + 7): + c2 = base_ring(lift(a[1]) / (3 * j + 7)) + else: + c2 = a[1] / (3 * j + 7) + + next_a[0] = next_a[0] + B * c1 * (j + 1) + next_a[1] = next_a[1] + A * c1 * (j + 1) + B * c2 * (j + 1) + next_a[2] = next_a[2] + A * c2 * (j + 1) + + if exact_form is not None: + exact_form += (c1 * x + c2 * x**2) * y ** (j + 1) + + coeffs[int(offset)] = next_a + + return exact_form + + +def reduce_zero(Q, coeffs, offset, exact_form=None): + """ + Apply cohomology relation to incorporate `x^2 y^0` term + into `x^0 y^0` and `x^1 y^0` terms. + + INPUT: + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list [a, b, c] represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + The reduction is performed in-place. The output is placed + in coeffs[offset]. This method completely ignores coeffs[i] for i + != offset. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_zero(Q, coeffs, 1) + sage: coeffs[1] + [6, 5, 0] + """ + + a = coeffs[int(offset)] + if a[2] == 0: + return exact_form + + Qa = Q[1] + + a[0] = a[0] - a[2] * Qa / 3 # $3x^2 dx/y = -a dx/y$ + + coeffs[int(offset)] = a + + if exact_form is not None: + y = exact_form.parent()(exact_form.parent().base_ring().gen(0)) + exact_form += Q.base_ring()(a[2] / 3) * y + + a[2] = 0 + + coeffs[int(offset)] = a + return exact_form + + +def reduce_all(Q, p, coeffs, offset, compute_exact_form=False): + """ + Apply cohomology relations to reduce all terms to a linear + combination of `dx/y` and `x dx/y`. + + INPUT: + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list [a, b, c] represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + - ``A``, ``B`` -- pair such that the input differential is + cohomologous to (A + Bx) dx/y + + .. NOTE:: + + The algorithm operates in-place, so the data in coeffs is + destroyed. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_all(Q, 5, coeffs, 1) + (21, 106) + """ + + R = Q.base_ring() + + if compute_exact_form: + # exact_form = SpecialCubicQuotientRing(Q, laurent_series=True)(0) + exact_form = PolynomialRing(LaurentSeriesRing(Q.base_ring(), "y"), "x").zero() + # t = (Q.base_ring().order().factor())[0] + # from sage.rings.padics.qp import pAdicField + # exact_form = PolynomialRing(LaurentSeriesRing(pAdicField(p, t[1]), 'y'), 'x')(0) + else: + exact_form = None + + while len(coeffs) <= offset: + coeffs.append([R(0), R(0), R(0)]) + + exact_form = reduce_negative(Q, p, coeffs, offset, exact_form) + exact_form = reduce_positive(Q, p, coeffs, offset, exact_form) + exact_form = reduce_zero(Q, coeffs, offset, exact_form) + + if exact_form is None: + return coeffs[int(offset)][0], coeffs[int(offset)][1] + else: + return (coeffs[int(offset)][0], coeffs[int(offset)][1]), exact_form + + +def frobenius_expansion_by_newton(Q, p, M): + r""" + Compute the action of Frobenius on `dx/y` and on + `x dx/y`, using Newton's method (as suggested in Kedlaya's + paper [Ked2001]_). + + (This function does *not* yet use the cohomology relations - that + happens afterwards in the "reduction" step.) + + More specifically, it finds `F_0` and `F_1` in + the quotient ring `R[x, T]/(T - Q(x))`, such that + + .. MATH:: + + F( dx/y) = T^{-r} F0 dx/y, \text{\ and\ } F(x dx/y) = T^{-r} F1 dx/y + + where + + .. MATH:: + + r = ( (2M-3)p - 1 )/2. + + + (Here `T` is `y^2 = z^{-2}`, and `R` is the + coefficient ring of `Q`.) + + `F_0` and `F_1` are computed in the + :class:`SpecialCubicQuotientRing` associated to `Q`, so all powers + of `x^j` for `j \geq 3` are reduced to powers of + `T`. + + INPUT: + + - ``Q`` -- cubic polynomial of the form + `Q(x) = x^3 + ax + b`, whose coefficient ring is a + `Z/(p^M)Z`-algebra + + - ``p`` -- residue characteristic of the `p`-adic field + + - ``M`` -- `p`-adic precision of the coefficient ring + (this will be used to determine the number of Newton iterations) + + OUTPUT: + + - ``F0``, ``F1`` -- elements of + ``SpecialCubicQuotientRing(Q)``, as described above + + - ``r`` -- nonnegative integer, as described above + + EXAMPLES:: + + sage: # TODO change import for sage + sage: from monsky_washnitzer import frobenius_expansion_by_newton + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: frobenius_expansion_by_newton(Q,5,3) + ((25*T^5 + 75*T^3 + 100*T^2 + 100*T + 100) + (5*T^6 + 80*T^5 + 100*T^3 + + 25*T + 50)*x + (55*T^5 + 50*T^4 + 75*T^3 + 25*T^2 + 25*T + 25)*x^2, + (5*T^8 + 15*T^7 + 95*T^6 + 10*T^5 + 25*T^4 + 25*T^3 + 100*T^2 + 50) + + (65*T^7 + 55*T^6 + 70*T^5 + 100*T^4 + 25*T^2 + 100*T)*x + + (15*T^6 + 115*T^5 + 75*T^4 + 100*T^3 + 50*T^2 + 75*T + 75)*x^2, 7) + """ + + S = SpecialCubicQuotientRing(Q) + x, _ = S.gens() # T = y^2 + base_ring = S.base_ring() + + # When we compute Frob(1/y) we actually only need precision M-1, since + # we're going to multiply by p at the end anyway. + M = float(M - 1) + + # Kedlaya sets s = Q(x^p)/T^p = 1 + p T^{-p} E, where + # E = (Q(x^p) - Q(x)^p) / p (has integral coefficients). + # Then he computes s^{-1/2} in S, using Newton's method to find + # successive approximations. We follow this plan, but we normalise our + # approximations so that we only ever need positive powers of T. + + # Start by setting r = Q(x^p)/2 = 1/2 T^p s. + # (The 1/2 is for convenience later on.) + x_to_p_less_one = x ** (p - 1) + x_to_p = x_to_p_less_one * x + x_to_p_cubed = x_to_p.square() * x_to_p + r = (base_ring(1) / base_ring(2)) * (x_to_p_cubed + Q[1] * x_to_p + S(Q[0])) + + # todo: this next loop would be clearer if it used the newton_method_sizes() + # function + + # We will start with a hard-coded initial approximation, which we provide + # up to precision 3. First work out what precision is best to start with. + if M <= 3: + initial_precision = M + elif ceil(log(M / 2, 2)) == ceil(log(M / 3, 2)): + # In this case there is no advantage to starting with precision three, + # because we'll overshoot at the end. E.g. suppose the final precision + # is 8. If we start with precision 2, we need two iterations to get us + # to 8. If we start at precision 3, we will still need two iterations, + # but we do more work along the way. So may as well start with only 2. + initial_precision = 2 + else: + initial_precision = 3 + + # Now compute the first approximation. In the main loop below, X is the + # normalised approximation, and k is the precision. More specifically, + # X = T^{p(k-1)} x_i, where x_i is an approximation to s^{-1/2}, and the + # approximation is correct mod p^k. + if initial_precision == 1: + k = 1 + X = S(1) + elif initial_precision == 2: + # approximation is 3/2 - 1/2 s + k = 2 + X = S(base_ring(3) / base_ring(2)).shift(p) - r + elif initial_precision == 3: + # approximation is (15 - 10 s + 3 s^2) / 8 + k = 3 + X = (base_ring(1) / base_ring(8)) * ( + S(15).shift(2 * p) + - (base_ring(20) * r).shift(p) + + (base_ring(12) * r.square()) + ) + # The key to the following calculation is that the T^{-m} coefficient + # of every x_i is divisible by p^(ceil(m/p)) (for m >= 0). Therefore if + # we are only expecting an answer correct mod p^k, we can truncate + # beyond the T^{-(k-1)p} term without any problems. + + # todo: what would be really nice is to be able to work in a lower + # precision *coefficient ring* when we start the iteration, and move up to + # higher precision rings as the iteration proceeds. This would be feasible + # over Integers(p**n), but quite complicated (maybe impossible) over a more + # general base ring. This might give a decent constant factor speedup; + # or it might not, depending on how much the last iteration dominates the + # whole runtime. My guess is that it isn't worth the effort. + + three_halves = base_ring(3) / base_ring(2) + + # Newton iteration loop + while k < M: + # target_k = k' = precision we want our answer to be after this iteration + target_k = 2 * k + + # This prevents us overshooting. For example if the current precision + # is 3 and we want to get to 10, we're better off going up to 5 + # instead of 6, because it is less work to get from 5 to 10 than it + # is to get from 6 to 10. + if ceil(log(M / target_k, 2)) == ceil(log(M / (target_k - 1), 2)): + target_k -= 1 + + # temp = T^{p(3k-2)} 1/2 s x_i^3 + temp = X.square() * (X * r) + + # We know that the final result is only going to be correct mod + # p^(target_k), so we might as well truncate the extraneous terms now. + # temp = T^{p(k'-1)} 1/2 s x_i^3 + temp = temp.shift(-p * (3 * k - target_k - 1)) + + # X = T^{p(k'-1)} (3/2 x_i - 1/2 s x_i^3) + # = T^{p(k'-1)} x_{i+1} + X = (three_halves * X).shift(p * (target_k - k)) - temp + + k = target_k + + # Now k should equal M, since we're up to the correct precision + assert k == M, "Oops, something went wrong in the iteration" + + # We should have s^{-1/2} correct to precision M. + # The following line can be uncommented to verify this. + # (It is a slow verification though, can double the whole computation time.) + + # assert (p * X.square() * r * base_ring(2)).coeffs() == \ + # R(p).shift(p*(2*M - 1)).coeffs() + + # Finally incorporate frobenius of dx and x dx, and choose offset that + # compensates for our normalisations by powers of T. + F0 = base_ring(p) * x_to_p_less_one * X + F1 = F0 * x_to_p + offset = ((2 * k - 1) * p - 1) / 2 + + return F0, F1, offset + + +def frobenius_expansion_by_series(Q, p, M): + r""" + Compute the action of Frobenius on `dx/y` and on `x dx/y`, using a + series expansion. + + (This function computes the same thing as + frobenius_expansion_by_newton(), using a different method. + Theoretically the Newton method should be asymptotically faster, + when the precision gets large. However, in practice, this functions + seems to be marginally faster for moderate precision, so I'm + keeping it here until I figure out exactly why it is faster.) + + (This function does *not* yet use the cohomology relations - that + happens afterwards in the "reduction" step.) + + More specifically, it finds F0 and F1 in the quotient ring + `R[x, T]/(T - Q(x))`, such that + `F( dx/y) = T^{-r} F0 dx/y`, and + `F(x dx/y) = T^{-r} F1 dx/y` where + `r = ( (2M-3)p - 1 )/2`. (Here `T` is `y^2 = z^{-2}`, + and `R` is the coefficient ring of `Q`.) + + `F_0` and `F_1` are computed in the + :class:`SpecialCubicQuotientRing` associated to `Q`, so all powers + of `x^j` for `j \geq 3` are reduced to powers of + `T`. + + It uses the sum + + .. MATH:: + + F0 = \sum_{k=0}^{M-2} \binom{-1/2}{k} p x^{p-1} E^k T^{(M-2-k)p} + + and + + .. MATH:: + + F1 = x^p F0, + + where `E = Q(x^p) - Q(x)^p`. + + INPUT: + + - ``Q`` -- cubic polynomial of the form + `Q(x) = x^3 + ax + b`, whose coefficient ring is a + `\ZZ/(p^M)\ZZ` -algebra + + - ``p`` -- residue characteristic of the `p`-adic field + + - ``M`` -- `p`-adic precision of the coefficient ring + (this will be used to determine the number of terms in the + series) + + OUTPUT: + + - ``F0``, ``F1`` -- elements of + ``SpecialCubicQuotientRing(Q)``, as described above + + - ``r`` -- nonnegative integer, as described above + + EXAMPLES:: + + sage: # TODO change import for sage + sage: from monsky_washnitzer import frobenius_expansion_by_series + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: frobenius_expansion_by_series(Q,5,3) # needs sage.libs.pari + ((25*T^5 + 75*T^3 + 100*T^2 + 100*T + 100) + (5*T^6 + 80*T^5 + 100*T^3 + + 25*T + 50)*x + (55*T^5 + 50*T^4 + 75*T^3 + 25*T^2 + 25*T + 25)*x^2, + (5*T^8 + 15*T^7 + 95*T^6 + 10*T^5 + 25*T^4 + 25*T^3 + 100*T^2 + 50) + + (65*T^7 + 55*T^6 + 70*T^5 + 100*T^4 + 25*T^2 + 100*T)*x + + (15*T^6 + 115*T^5 + 75*T^4 + 100*T^3 + 50*T^2 + 75*T + 75)*x^2, 7) + """ + + S = SpecialCubicQuotientRing(Q) + x, _ = S.gens() + base_ring = S.base_ring() + + x_to_p_less_1 = x ** (p - 1) + x_to_p = x_to_p_less_1 * x + + # compute frobQ = Q(x^p) + x_to_p_squared = x_to_p * x_to_p + x_to_p_cubed = x_to_p_squared * x_to_p + frobQ = x_to_p_cubed + Q[1] * x_to_p + Q[0] * S.one() + # anticipating the day when p = 3 is supported: + # frobQ = x_to_p_cubed + Q[2]*x_to_p_squared + Q[1]*x_to_p + Q[0]*S(1) + + E = frobQ - S.one().shift(p) # E = Q(x^p) - Q(x)^p + + offset = int(((2 * M - 3) * p - 1) / 2) + term = p * x_to_p_less_1 + F0 = term.shift((M - 2) * p) + + # todo: Possible speedup idea, perhaps by a factor of 2, but + # it requires a lot of work: + # Note that p divides E, so p^k divides E^k. So when we are + # working with high powers of E, we're doing a lot more work + # in the multiplications than we need to. To take advantage of + # this we would need some protocol for "lowering the precision" + # of a SpecialCubicQuotientRing. This would be quite messy to + # do properly over an arbitrary base ring. Perhaps it is + # feasible to do for the most common case (i.e. Z/p^nZ). + # (but it probably won't save much time unless p^n is very + # large, because the machine word size is probably pretty + # big anyway.) + + for k in range(1, int(M - 1)): + term = term * E + c = base_ring(binomial(QQ((-1, 2)), k)) + F0 += (term * c).shift((M - k - 2) * p) + + return F0, F0 * x_to_p, offset + + +def adjusted_prec(p, prec): + r""" + Compute how much precision is required in ``matrix_of_frobenius`` to + get an answer correct to ``prec`` `p`-adic digits. + + The issue is that the algorithm used in + :func:`matrix_of_frobenius` sometimes performs divisions by `p`, + so precision is lost during the algorithm. + + The estimate returned by this function is based on Kedlaya's result + (Lemmas 2 and 3 of [Ked2001]_), + which implies that if we start with `M` `p`-adic + digits, the total precision loss is at most + `1 + \lfloor \log_p(2M-3) \rfloor` `p`-adic + digits. (This estimate is somewhat less than the amount you would + expect by naively counting the number of divisions by + `p`.) + + INPUT: + + - ``p`` -- a prime ``p >= 5`` + + - ``prec`` -- integer; desired output precision, ``prec >= 1`` + + OUTPUT: adjusted precision (usually slightly more than ``prec``) + + EXAMPLES:: + + sage: # TODO change import for sage + sage: from monsky_washnitzer import adjusted_prec + sage: adjusted_prec(5,2) + 3 + """ + # initial estimate: + if prec <= 2: + defect = 0 + adjusted = 2 + else: + defect = Integer(2 * prec - 3).exact_log(p) + adjusted = prec + defect - 1 + + # increase it until we have enough + while adjusted - defect - 1 < prec: + adjusted += 1 + + return adjusted + + +def matrix_of_frobenius(Q, p, M, trace=None, compute_exact_forms=False): + r""" + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology, + with respect to the basis `(dx/y, x dx/y)`. + + INPUT: + + - ``Q`` -- cubic polynomial `Q(x) = x^3 + ax + b` + defining an elliptic curve `E` by + `y^2 = Q(x)`. The coefficient ring of `Q` should be a + `\ZZ/(p^M)\ZZ`-algebra in which the matrix of + frobenius will be constructed. + + - ``p`` -- prime >= 5 for which E has good reduction + + - ``M`` -- integer >= 2; `p` -adic precision of the coefficient ring + + - ``trace`` -- (optional) the trace of the matrix, if + known in advance. This is easy to compute because it is just the + `a_p` of the curve. If the trace is supplied, + matrix_of_frobenius will use it to speed the computation (i.e. we + know the determinant is `p`, so we have two conditions, so + really only column of the matrix needs to be computed. it is + actually a little more complicated than that, but that's the basic + idea.) If trace=None, then both columns will be computed + independently, and you can get a strong indication of correctness + by verifying the trace afterwards. + + .. WARNING:: + + THE RESULT WILL NOT NECESSARILY BE CORRECT TO M p-ADIC + DIGITS. If you want prec digits of precision, you need to use + the function adjusted_prec(), and then you need to reduce the + answer mod `p^{\mathrm{prec}}` at the end. + + OUTPUT: + + `2 \times 2` matrix of Frobenius acting on Monsky-Washnitzer cohomology, + with entries in the coefficient ring of ``Q``. + + EXAMPLES: + + A simple example:: + + sage: p = 5 + sage: prec = 3 + sage: M = monsky_washnitzer.adjusted_prec(p, prec); M + 4 + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: A + [340 62] + [ 70 533] + + But the result is only accurate to ``prec`` digits:: + + sage: B = A.change_ring(Integers(p**prec)) + sage: B + [90 62] + [70 33] + + Check trace (123 = -2 mod 125) and determinant:: + + sage: B.det() + 5 + sage: B.trace() + 123 + sage: EllipticCurve([-1, 1/4]).ap(5) + -2 + + Try using the trace to speed up the calculation:: + + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), + ....: p, M, -2) + sage: A + [ 90 62] + [320 533] + + Hmmm... it looks different, but that's because the trace of our + first answer was only -2 modulo `5^3`, not -2 modulo + `5^5`. So the right answer is:: + + sage: A.change_ring(Integers(p**prec)) + [90 62] + [70 33] + + Check it works with only one digit of precision:: + + sage: p = 5 + sage: prec = 1 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: A.change_ring(Integers(p)) + [0 2] + [0 3] + + Here is an example that is particularly badly conditioned for + using the trace trick:: + + sage: # needs sage.libs.pari + sage: p = 11 + sage: prec = 3 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 + 7*x + 8, p, M) + sage: A.change_ring(Integers(p**prec)) + [1144 176] + [ 847 185] + + The problem here is that the top-right entry is divisible by 11, + and the bottom-left entry is divisible by `11^2`. So when + you apply the trace trick, neither `F(dx/y)` nor + `F(x dx/y)` is enough to compute the whole matrix to the + desired precision, even if you try increasing the target precision + by one. Nevertheless, ``matrix_of_frobenius`` knows + how to get the right answer by evaluating `F((x+1) dx/y)` + instead:: + + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 + 7*x + 8, p, M, -2) + sage: A.change_ring(Integers(p**prec)) + [1144 176] + [ 847 185] + + The running time is about ``O(p*prec**2)`` (times some logarithmic + factors), so it is feasible to run on fairly large primes, or + precision (or both?!?!):: + + sage: # long time, needs sage.libs.pari + sage: p = 10007 + sage: prec = 2 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: B = A.change_ring(Integers(p**prec)); B + [74311982 57996908] + [95877067 25828133] + sage: B.det() + 10007 + sage: B.trace() + 66 + sage: EllipticCurve([-1, 1/4]).ap(10007) + 66 + + :: + + sage: # long time, needs sage.libs.pari + sage: p = 5 + sage: prec = 300 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: B = A.change_ring(Integers(p**prec)) + sage: B.det() + 5 + sage: -B.trace() + 2 + sage: EllipticCurve([-1, 1/4]).ap(5) + -2 + + Let us check consistency of the results for a range of precisions:: + + sage: # long time, needs sage.libs.pari + sage: p = 5 + sage: max_prec = 60 + sage: M = monsky_washnitzer.adjusted_prec(p, max_prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: A = A.change_ring(Integers(p**max_prec)) + sage: result = [] + sage: for prec in range(1, max_prec): + ....: M = monsky_washnitzer.adjusted_prec(p, prec) + ....: R. = PolynomialRing(Integers(p^M),'x') + ....: B = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + ....: B = B.change_ring(Integers(p**prec)) + ....: result.append(B == A.change_ring(Integers(p**prec))) + sage: result == [True] * (max_prec - 1) + True + + The remaining examples discuss what happens when you take the + coefficient ring to be a power series ring; i.e. in effect you're + looking at a family of curves. + + The code does in fact work... + + :: + + sage: # needs sage.libs.pari + sage: p = 11 + sage: prec = 3 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: S. = PowerSeriesRing(Integers(p**M), default_prec=4) + sage: a = 7 + t + 3*t^2 + sage: b = 8 - 6*t + 17*t^2 + sage: R. = PolynomialRing(S) + sage: Q = x**3 + a*x + b + sage: A = monsky_washnitzer.matrix_of_frobenius(Q, p, M) # long time + sage: B = A.change_ring(PowerSeriesRing(Integers(p**prec), 't', # long time + ....: default_prec=4)); B + [1144 + 264*t + 841*t^2 + 1025*t^3 + O(t^4) 176 + 1052*t + 216*t^2 + 523*t^3 + O(t^4)] + [ 847 + 668*t + 81*t^2 + 424*t^3 + O(t^4) 185 + 341*t + 171*t^2 + 642*t^3 + O(t^4)] + + The trace trick should work for power series rings too, even in the + badly-conditioned case. Unfortunately I do not know how to compute + the trace in advance, so I am not sure exactly how this would help. + Also, I suspect the running time will be dominated by the + expansion, so the trace trick will not really speed things up anyway. + Another problem is that the determinant is not always p:: + + sage: B.det() # long time + 11 + 484*t^2 + 451*t^3 + O(t^4) + + However, it appears that the determinant always has the property + that if you substitute t - 11t, you do get the constant series p + (mod p\*\*prec). Similarly for the trace. And since the parameter + only really makes sense when it is divisible by p anyway, perhaps + this is not a problem after all. + """ + M = int(M) + if M < 2: + raise ValueError("M (=%s) must be at least 2" % M) + + base_ring = Q.base_ring() + + # Expand out frobenius of dx/y and x dx/y. + # (You can substitute frobenius_expansion_by_series here, that will work + # as well. See its docstring for some performance notes.) + F0, F1, offset = frobenius_expansion_by_newton(Q, p, M) + # F0, F1, offset = frobenius_expansion_by_series(Q, p, M) + + if compute_exact_forms: + # we need to do all the work to get the exact expressions f such that F(x^i dx/y) = df + \sum a_i x^i dx/y + F0_coeffs = transpose_list(F0.coeffs()) + F0_reduced, f_0 = reduce_all(Q, p, F0_coeffs, offset, True) + + F1_coeffs = transpose_list(F1.coeffs()) + F1_reduced, f_1 = reduce_all(Q, p, F1_coeffs, offset, True) + + elif M == 2: + # This implies that only one digit of precision is valid, so we only need + # to reduce the second column. Also, the trace doesn't help at all. + + F0_reduced = [base_ring(0), base_ring(0)] + + F1_coeffs = transpose_list(F1.coeffs()) + F1_reduced = reduce_all(Q, p, F1_coeffs, offset) + + elif trace is None: + # No trace provided, just reduce F(dx/y) and F(x dx/y) separately. + + F0_coeffs = transpose_list(F0.coeffs()) + F0_reduced = reduce_all(Q, p, F0_coeffs, offset) + + F1_coeffs = transpose_list(F1.coeffs()) + F1_reduced = reduce_all(Q, p, F1_coeffs, offset) + + else: + # Trace has been provided. + + # In most cases this can be used to quickly compute F(dx/y) from + # F(x dx/y). However, if we're unlucky, the (dx/y)-component of + # F(x dx/y) (i.e. the top-right corner of the matrix) may be divisible + # by p, in which case there isn't enough information to get the + # (x dx/y)-component of F(dx/y) to the desired precision. When this + # happens, it turns out that F((x+1) dx/y) always *does* give enough + # information (together with the trace) to get both columns to the + # desired precision. + + # First however we need a quick way of telling whether the top-right + # corner is divisible by p, i.e. we want to compute the second column + # of the matrix mod p. We could do this by just running the entire + # algorithm with M = 2 (which assures precision 1). Luckily, we've + # already done most of the work by computing F1 to high precision; so + # all we need to do is extract the coefficients that would correspond + # to the first term of the series, and run the reduction on them. + + # todo: actually we only need to do this reduction step mod p^2, not + # mod p^M, which is what the code currently does. If the base ring + # is Integers(p^M), then it is easy. Otherwise it is tricky to construct + # the right ring, I don't know how to do it. + + F1_coeffs = transpose_list(F1.coeffs()) + F1_modp_coeffs = F1_coeffs[int((M - 2) * p) :] + # make a copy, because reduce_all will destroy the coefficients: + F1_modp_coeffs = [list(row) for row in F1_modp_coeffs] + F1_modp_offset = offset - (M - 2) * p + F1_modp_reduced = reduce_all(Q, p, F1_modp_coeffs, F1_modp_offset) + + if F1_modp_reduced[0].is_unit(): + # If the first entry is invertible mod p, then F(x dx/y) is sufficient + # to get the whole matrix. + + F1_reduced = reduce_all(Q, p, F1_coeffs, offset) + + F0_reduced = [base_ring(trace) - F1_reduced[1], None] + # using that the determinant is p: + F0_reduced[1] = (F0_reduced[0] * F1_reduced[1] - base_ring(p)) / F1_reduced[ + 0 + ] + + else: + # If the first entry is zero mod p, then F((x+1) dx/y) will be sufficient + # to get the whole matrix. (Here we are using the fact that the second + # entry *cannot* be zero mod p. This is guaranteed by some results in + # section 3.2 of ``Computation of p-adic Heights and Log Convergence'' + # by Mazur, Stein, Tate. But let's quickly check it anyway :-)) + msg = "The second entry in the second column " + msg += "should be invertible mod p!" + assert F1_modp_reduced[1].is_unit(), msg + + G0_coeffs = transpose_list((F0 + F1).coeffs()) + G0_reduced = reduce_all(Q, p, G0_coeffs, offset) + + # Now G0_reduced expresses F((x+1) dx/y) in terms of dx/y and x dx/y. + # Re-express this in terms of (x+1) dx/y and x dx/y. + H0_reduced = [G0_reduced[0], G0_reduced[1] - G0_reduced[0]] + + # The thing we're about to divide by better be a unit. + msg = "The second entry in this column " + msg += "should be invertible mod p!" + assert H0_reduced[1].is_unit(), msg + + # Figure out the second column using the trace... + H1_reduced = [None, base_ring(trace) - H0_reduced[0]] + # ... and using that the determinant is p: + H1_reduced[0] = (H0_reduced[0] * H1_reduced[1] - base_ring(p)) / H0_reduced[ + 1 + ] + + # Finally, change back to the usual basis (dx/y, x dx/y) + F1_reduced = [H1_reduced[0], H1_reduced[0] + H1_reduced[1]] + F0_reduced = [ + H0_reduced[0] - F1_reduced[0], + H0_reduced[0] + H0_reduced[1] - F1_reduced[1], + ] + + # One more sanity check: our final result should be congruent mod p + # to the approximation we used earlier. + msg = "The output matrix is not congruent mod p " + msg += "to the approximation found earlier!" + assert not ( + (F1_reduced[0] - F1_modp_reduced[0]).is_unit() + or (F1_reduced[1] - F1_modp_reduced[1]).is_unit() + or F0_reduced[0].is_unit() + or F0_reduced[1].is_unit() + ), msg + + if compute_exact_forms: + return ( + matrix( + base_ring, + 2, + 2, + [F0_reduced[0], F1_reduced[0], F0_reduced[1], F1_reduced[1]], + ), + f_0, + f_1, + ) + else: + return matrix( + base_ring, + 2, + 2, + [F0_reduced[0], F1_reduced[0], F0_reduced[1], F1_reduced[1]], + ) + + +# **************************************************************************** +# This is a generalization of the above functionality for hyperelliptic curves. +# +# THIS IS A WORK IN PROGRESS. +# +# I tried to embed must stuff into the rings themselves rather than +# just extract and manipulate lists of coefficients. Hence the implementations +# below are much less optimized, so are much slower, but should hopefully be +# easier to follow. (E.g. one can print/make sense of intermediate results.) +# +# AUTHOR: +# -- Robert Bradshaw (2007-04) +# +# **************************************************************************** + + +def matrix_of_frobenius_hyperelliptic(Q, p=None, prec=None, M=None): + r""" + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology, + with respect to the basis `(dx/2y, x dx/2y, ...x^{d-2} dx/2y)`, where + `d` is the degree of `Q`. + + INPUT: + + - ``Q`` -- monic polynomial `Q(x)` + + - ``p`` -- prime `\geq 5` for which `E` has good reduction + + - ``prec`` -- (optional) `p`-adic precision of the coefficient ring + + - ``M`` -- (optional) adjusted `p`-adic precision of the coefficient ring + + OUTPUT: + + `(d-1)` x `(d-1)` matrix `M` of Frobenius on Monsky-Washnitzer cohomology, + and list of differentials \{f_i \} such that + + .. MATH:: + + \phi^* (x^i dx/2y) = df_i + M[i]*vec(dx/2y, ..., x^{d-2} dx/2y) + + EXAMPLES:: + + sage: # needs sage.rings.padics + sage: p = 5 + sage: prec = 3 + sage: R. = QQ['x'] + sage: A,f = monsky_washnitzer.matrix_of_frobenius_hyperelliptic(x^5 - 2*x + 3, p, prec) + sage: A + [ 4*5 + O(5^3) 5 + 2*5^2 + O(5^3) 2 + 3*5 + 2*5^2 + O(5^3) 2 + 5 + 5^2 + O(5^3)] + [ 3*5 + 5^2 + O(5^3) 3*5 + O(5^3) 4*5 + O(5^3) 2 + 5^2 + O(5^3)] + [ 4*5 + 4*5^2 + O(5^3) 3*5 + 2*5^2 + O(5^3) 5 + 3*5^2 + O(5^3) 2*5 + 2*5^2 + O(5^3)] + [ 5^2 + O(5^3) 5 + 4*5^2 + O(5^3) 4*5 + 3*5^2 + O(5^3) 2*5 + O(5^3)] + """ + prof = Profiler() + prof("setup") + if p is None: + try: + K = Q.base_ring() + p = K.prime() + prec = K.precision_cap() + except AttributeError: + raise ValueError( + "p and prec must be specified if Q is not " "defined over a p-adic ring" + ) + if M is None: + M = adjusted_prec(p, prec) + extra_prec_ring = Integers(p**M) + # extra_prec_ring = pAdicField(p, M) # SLOW! + + real_prec_ring = pAdicField(p, prec) # pAdicField(p, prec) # To capped absolute? + S = SpecialHyperellipticQuotientRing(Q, extra_prec_ring, True) + MW = S.monsky_washnitzer() + prof("frob basis elements") + F = MW.frob_basis_elements(M, p) + + prof("rationalize") + # do reduction over Q in case we have non-integral entries (and it is so much faster than padics) + rational_S = S.change_ring(QQ) + # this is a hack until pAdics are fast + # (They are in the latest development bundle, but its not standard and I'd need to merge. + # (it will periodically cast into this ring to reduce coefficient size) + rational_S._prec_cap = p**M + rational_S._p = p + # S._p = p + # rational_S(F[0]).reduce_fast() + # prof("reduce others") + + # rational_S = S.change_ring(pAdicField(p, M)) + F = [rational_S(F_i) for F_i in F] + + prof("reduce") + reduced = [F_i.reduce_fast(True) for F_i in F] + + # but the coeffs are WAY more precision than they need to be + + prof("make matrix") + # now take care of precision capping + M = matrix(real_prec_ring, [a for f, a in reduced]) + for i in range(M.ncols()): + for j in range(M.nrows()): + M[i, j] = M[i, j].add_bigoh(prec) + return M.transpose(), [f for f, a in reduced] + + +class SpecialHyperellipticQuotientElement(ModuleElement): + r""" + Element in the Hyperelliptic quotient ring. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: MW(x + x**2 + y - 77) + -(77-y)*1 + x + x^2 + """ + + def __init__(self, parent, val=0, offset=0, check=True): + """ + Elements in the Hyperelliptic quotient ring. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: elt = MW(x + x**2 + y - 77) + sage: TestSuite(elt).run() + """ + ModuleElement.__init__(self, parent) + if not check: + self._f = parent._poly_ring(val, check=False) + return + if isinstance(val, SpecialHyperellipticQuotientElement): + R = parent.base_ring() + self._f = parent._poly_ring([a.change_ring(R) for a in val._f]) + return + if isinstance(val, tuple): + val, offset = val + if isinstance(val, list) and val and isinstance(val[0], FreeModuleElement): + val = transpose_list(val) + self._f = parent._poly_ring(val) + if offset != 0: + self._f = self._f.parent()([a << offset for a in self._f], check=False) + + def _richcmp_(self, other, op) -> bool: + """ + Compare the elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x == x + True + sage: x > y + True + """ + return richcmp(self._f, other._f, op) + + def change_ring(self, R): + """ + Return the same element after changing the base ring to `R`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: z = MW(x + x**2 + y - 77) + sage: z.change_ring(AA).parent() # needs sage.rings.number_field + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 36*x + 1) + over Algebraic Real Field + """ + return self.parent().change_ring(R)(self) + + def __call__(self, *x): + """ + Evaluate ``self`` at given arguments. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: z = MW(x + x**2 + y - 77); z + -(77-y)*1 + x + x^2 + sage: z(66) + 4345 + y + sage: z(5,4) + -43 + """ + return self._f(*x) + + def __invert__(self): + """ + Return the inverse of ``self``. + + The general element in our ring is not invertible, but `y` may + be. We do not want to pass to the fraction field. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: z = y**(-1) # indirect doctest + sage: z.parent() + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 36*x + 1) + over Rational Field + + sage: z = (x+y)**(-1) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: element not invertible + """ + if self._f.degree() == 0 and self._f[0].is_unit(): + P = self.parent() + return P.element_class(P, ~self._f[0]) + else: + raise ZeroDivisionError("element not invertible") + + def __bool__(self): + """ + Return ``True`` iff ``self`` is not zero. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: bool(x) + True + """ + return bool(self._f) + + def __eq__(self, other): + """ + Return ``True`` iff ``self`` is equal to ``other``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x == y # indirect doctest + False + """ + if not isinstance(other, SpecialHyperellipticQuotientElement): + other = self.parent()(other) + return self._f == other._f + + def _add_(self, other): + """ + Return the sum of two elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x + y + y*1 + x + """ + P = self.parent() + return P.element_class(P, self._f + other._f) + + def _sub_(self, other): + """ + Return the difference of two elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: y - x + y*1 - x + """ + P = self.parent() + return P.element_class(P, self._f - other._f) + + def _mul_(self, other): + """ + Return the product of two elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: y*x + y*x + """ + # over Laurent series, addition and subtraction can be + # expensive, and the degree of this poly is small enough that + # Karatsuba actually hurts significantly in some cases + if self._f[0].valuation() + other._f[0].valuation() > -200: + prod = self._f._mul_generic(other._f) + else: + prod = self._f * other._f + v = prod.list() + parent = self.parent() + Q_coeffs = parent._Q_coeffs + n = len(Q_coeffs) - 1 + y2 = self.parent()._series_ring_y << 1 + for i in range(len(v) - 1, n - 1, -1): + for j in range(n): + v[i - n + j] -= Q_coeffs[j] * v[i] + v[i - n] += y2 * v[i] + P = self.parent() + return P.element_class(P, v[0:n]) + + def _rmul_(self, c): + """ + Return the product of ``self`` with `c` on the left. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x._rmul_(y) # needs sage.rings.real_interval_field + y*1*x + """ + P = self.parent() + if not c: + return P.zero() + ret = [c * a for a in self._f.list(copy=False)] + while ret and not ret[-1]: # strip off trailing 0s + ret.pop() + return P.element_class(P, ret, check=False) + + def _lmul_(self, c): + """ + Return the product of ``self`` with `c` on the right. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5-3*x+1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x._lmul_(y) # needs sage.rings.real_interval_field + y*1*x + """ + P = self.parent() + if not c: + return P.zero() + ret = [a * c for a in self._f.list(copy=False)] + while ret and not ret[-1]: # strip off trailing 0s + ret.pop() + return P.element_class(P, ret, check=False) + + def __lshift__(self, k): + """ + Return the left shift of ``self`` by `k`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.__lshift__(3) + y^3*x + """ + coeffs = self._f.list(copy=False) + P = self.parent() + return P.element_class(P, [a << k for a in coeffs], check=False) + + def __rshift__(self, k): + """ + Return the right shift of ``self`` by `k`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: y.__rshift__(3) + (y^-2)*1 + """ + coeffs = self._f.list(copy=False) + P = self.parent() + return P.element_class(P, [a >> k for a in coeffs], check=False) + + def truncate_neg(self, n): + """ + Return ``self`` minus its terms of degree less than `n` wrt `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y + 7*x*2*y**4).truncate_neg(1) + 3*y*1 + 14*y^4*x + """ + coeffs = self._f.list(copy=False) + P = self.parent() + return P.element_class(P, [a.truncate_neg(n) for a in coeffs], check=False) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y)._repr_() + '3*y*1 + x' + """ + x = PolynomialRing(QQ, "x").gen(0) + coeffs = self._f.list() + return repr_lincomb([(x**i, coeffs[i]) for i in range(len(coeffs))]) + + def _latex_(self): + r""" + Return a LateX string for ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y)._latex_() + '3y 1 + x' + """ + x = PolynomialRing(QQ, "x").gen(0) + coeffs = self._f.list() + return repr_lincomb( + [(x**i, coeffs[i]) for i in range(len(coeffs))], is_latex=True + ) + + def diff(self): + """ + Return the differential of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y).diff() + (-(9-2*y)*1 + 15*x^4) dx/2y + """ + # try: + # return self._diff_x + # except AttributeError: + # pass + + # d(self) = A dx + B dy + # = (2y A + BQ') dx/2y + P = self.parent() + R = P.base_ring() + v = self._f.list() + n = len(v) + A = P([R(i) * v[i] for i in range(1, n)]) + B = P([a.derivative() for a in v]) + dQ = P._dQ + return P._monsky_washnitzer((R(2) * A << 1) + dQ * B) + + # self._diff = self.parent()._monsky_washnitzer(two_y * A + dQ * B) + # return self._diff + + def extract_pow_y(self, k): + r""" + Return the coefficients of `y^k` in ``self`` as a list. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y + 9*x*y).extract_pow_y(1) + [3, 9, 0, 0, 0] + """ + v = [a[k] for a in self._f.list()] + v += [ZZ.zero()] * (self.parent()._n - len(v)) + return v + + def min_pow_y(self): + r""" + Return the minimal degree of ``self`` with respect to `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y).min_pow_y() + 0 + """ + if self._f.degree() == -1: + return ZZ.zero() + return min([a.valuation() for a in self._f.list()]) + + def max_pow_y(self): + r""" + Return the maximal degree of ``self`` with respect to `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y).max_pow_y() + 1 + """ + if self._f.degree() == -1: + return ZZ.zero() + return max([a.degree() for a in self._f.list()]) + + def coeffs(self, R=None): + r""" + Return the raw coefficients of this element. + + INPUT: + + - ``R`` -- an (optional) base-ring in which to cast the coefficients + + OUTPUT: + + - ``coeffs`` -- list of coefficients of powers of `x` for each power + of `y` + + - ``n`` -- an offset indicating the power of `y` of the first list + element + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.coeffs() + ([(0, 1, 0, 0, 0)], 0) + sage: y.coeffs() + ([(0, 0, 0, 0, 0), (1, 0, 0, 0, 0)], 0) + + sage: a = sum(n*x^n for n in range(5)); a + x + 2*x^2 + 3*x^3 + 4*x^4 + sage: a.coeffs() + ([(0, 1, 2, 3, 4)], 0) + sage: a.coeffs(Qp(7)) # needs sage.rings.padics + ([(0, 1 + O(7^20), 2 + O(7^20), 3 + O(7^20), 4 + O(7^20))], 0) + sage: (a*y).coeffs() + ([(0, 0, 0, 0, 0), (0, 1, 2, 3, 4)], 0) + sage: (a*y^-2).coeffs() + ([(0, 1, 2, 3, 4), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)], -2) + + Note that the coefficient list is transposed compared to how they + are stored and printed:: + + sage: a*y^-2 + (y^-2)*x + (2*y^-2)*x^2 + (3*y^-2)*x^3 + (4*y^-2)*x^4 + + A more complicated example:: + + sage: a = x^20*y^-3 - x^11*y^2; a + (y^-3-4*y^-1+6*y-4*y^3+y^5)*1 - (12*y^-3-36*y^-1+36*y+y^2-12*y^3-2*y^4+y^6)*x + + (54*y^-3-108*y^-1+54*y+6*y^2-6*y^4)*x^2 - (108*y^-3-108*y^-1+9*y^2)*x^3 + + (81*y^-3)*x^4 + sage: raw, offset = a.coeffs() + sage: a.min_pow_y() + -3 + sage: offset + -3 + sage: raw + [(1, -12, 54, -108, 81), + (0, 0, 0, 0, 0), + (-4, 36, -108, 108, 0), + (0, 0, 0, 0, 0), + (6, -36, 54, 0, 0), + (0, -1, 6, -9, 0), + (-4, 12, 0, 0, 0), + (0, 2, -6, 0, 0), + (1, 0, 0, 0, 0), + (0, -1, 0, 0, 0)] + sage: sum(c * x^i * y^(j+offset) + ....: for j, L in enumerate(raw) for i, c in enumerate(L)) == a + True + + Can also be used to construct elements:: + + sage: a.parent()(raw, offset) == a + True + """ + zero = self.base_ring()(0) if R is None else R(0) + y_offset = min(self.min_pow_y(), 0) + y_degree = max(self.max_pow_y(), 0) + coeffs = [] + n = y_degree - y_offset + 1 + for a in self._f.list(): + k = a.valuation() + if k is Infinity: + k = 0 + k -= y_offset + z = a.list() + coeffs.append([zero] * k + z + [zero] * (n - len(z) - k)) + while len(coeffs) < self.parent().degree(): + coeffs.append([zero] * n) + V = FreeModule(self.base_ring() if R is None else R, self.parent().degree()) + coeffs = transpose_list(coeffs) + return [V(a) for a in coeffs], y_offset + + +class SpecialHyperellipticQuotientRing(UniqueRepresentation, Parent): + """ + The special hyperelliptic quotient ring. + """ + + _p = None + + def __init__(self, Q, R=None, invert_y=True): + r""" + Initialize ``self``. + + TESTS:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialHyperellipticQuotientRing + sage: HQR = SpecialHyperellipticQuotientRing(E) + sage: TestSuite(HQR).run() # needs sage.rings.real_interval_field + + Check that caching works:: + + sage: HQR is SpecialHyperellipticQuotientRing(E) + True + """ + if R is None: + R = Q.base_ring() + + x = PolynomialRing(R, "xx").gen() + if isinstance(Q, EllipticCurve_generic): + E = Q + if E.a1() != 0 or E.a2() != 0: + raise NotImplementedError("curve must be in Weierstrass normal form") + Q = -E.change_ring(R).defining_polynomial()(x, 0, 1) + self._curve = E + + elif isinstance(Q, HyperellipticCurveSmoothModel_generic): + C = Q + if C.hyperelliptic_polynomials()[1] != 0: + raise NotImplementedError("curve must be of form y^2 = Q(x)") + Q = C.hyperelliptic_polynomials()[0].change_ring(R) + self._curve = C + + if isinstance(Q, Polynomial): + self._Q = Q.change_ring(R) + self._coeffs = self._Q.coefficients(sparse=False) + if self._coeffs.pop() != 1: + raise NotImplementedError("polynomial must be monic") + if not hasattr(self, "_curve"): + if self._Q.degree() == 3: + ainvs = [0, self._Q[2], 0, self._Q[1], self._Q[0]] + self._curve = EllipticCurve(ainvs, check_squarefree=R.is_field()) + else: + self._curve = HyperellipticCurveSmoothModel( + self._Q, check_squarefree=R.is_field() + ) + + else: + raise NotImplementedError( + "must be an elliptic curve or polynomial " + "Q for y^2 = Q(x)\n(Got element of %s)" % Q.parent() + ) + + self._n = int(Q.degree()) + self._series_ring = (LaurentSeriesRing if invert_y else PolynomialRing)(R, "y") + self._series_ring_y = self._series_ring.gen(0) + self._series_ring_0 = self._series_ring.zero() + + Parent.__init__(self, base=R, category=Algebras(R).Commutative()) + + self._poly_ring = PolynomialRing(self._series_ring, "x") + + self._x = self.element_class(self, self._poly_ring.gen(0)) + self._y = self.element_class(self, self._series_ring.gen(0)) + + self._Q_coeffs = Q.change_ring(self._series_ring).list() + self._dQ = Q.derivative().change_ring(self)(self._x) + self._monsky_washnitzer = MonskyWashnitzerDifferentialRing(self) + + self._monomial_diffs = {} + self._monomial_diff_coeffs = {} + + def _repr_(self) -> str: + r""" + String representation. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent() # indirect doctest + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 3*x + 1) over Rational Field + """ + y_inverse = ( + ",y^-1" + if isinstance(self._series_ring, (LaurentSeriesRing, LazyLaurentSeriesRing)) + else "" + ) + return "SpecialHyperellipticQuotientRing K[x,y%s] / (y^2 = %s) over %s" % ( + y_inverse, + self._Q, + self.base_ring(), + ) + + def base_extend(self, R): + r""" + Return the base extension of ``self`` to the ring ``R`` if possible. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().base_extend(UniversalCyclotomicField()) # needs sage.libs.gap + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 3*x + 1) + over Universal Cyclotomic Field + sage: x.parent().base_extend(ZZ) + Traceback (most recent call last): + ... + TypeError: no such base extension + """ + if R.has_coerce_map_from(self.base_ring()): + return self.change_ring(R) + raise TypeError("no such base extension") + + def change_ring(self, R): + r""" + Return the analog of ``self`` over the ring ``R``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().change_ring(ZZ) + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 3*x + 1) over Integer Ring + """ + return SpecialHyperellipticQuotientRing( + self._Q.change_ring(R), + R, + isinstance(self._series_ring, (LaurentSeriesRing, LazyLaurentSeriesRing)), + ) + + def _element_constructor_(self, val, offset=0, check=True): + r""" + Construct an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent()(x^6) + -(1-y^2)*x + 3*x^2 + """ + if ( + isinstance(val, SpecialHyperellipticQuotientElement) + and val.parent() is self + ): + if offset == 0: + return val + else: + return val << offset + elif isinstance(val, MonskyWashnitzerDifferential): + return self._monsky_washnitzer(val) + return self.element_class(self, val, offset, check) + + @cached_method + def one(self): + """ + Return the unit of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().one() + 1 + """ + return self.element_class(self, self._poly_ring.one(), check=False) + + @cached_method + def zero(self): + """ + Return the zero of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().zero() + 0 + """ + return self.element_class(self, self._poly_ring.zero(), check=False) + + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().gens() + (x, y*1) + """ + return (self._x, self._y) + + def x(self): + r""" + Return the generator `x` of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().x() + x + """ + return self._x + + def y(self): + r""" + Return the generator `y` of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().y() + y*1 + """ + return self._y + + def monomial(self, i, j, b=None): + """ + Return `b y^j x^i`, computed quickly. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().monomial(4,5) + y^5*x^4 + """ + i = int(i) + j = int(j) + + if 0 < i and i < self._n: + if b is None: + by_to_j = self._series_ring_y << (j - 1) + else: + by_to_j = self._series_ring(b) << j + v = [self._series_ring_0] * self._n + v[i] = by_to_j + return self.element_class(self, v) + + if b is not None: + b = self.base_ring()(b) + return (self._x**i) << j if b is None else b * (self._x**i) << j + + def monomial_diff_coeffs(self, i, j): + r""" + Compute coefficients of the basis representation of `d(x^iy^j)`. + + The key here is that the formula for `d(x^iy^j)` is messy + in terms of `i`, but varies nicely with `j`. + + .. MATH:: + + d(x^iy^j) = y^{j-1} (2ix^{i-1}y^2 + j (A_i(x) + B_i(x)y^2)) \frac{dx}{2y}, + + where `A,B` have degree at most `n-1` for each + `i`. Pre-compute `A_i, B_i` for each `i` + the "hard" way, and the rest are easy. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().monomial_diff_coeffs(2,3) + ((0, -15, 36, 0, 0), (0, 19, 0, 0, 0)) + """ + try: + return self._monomial_diff_coeffs[i, j] + except KeyError: + pass + if i < self._n: + try: + A, B, two_i_x_to_i = self._precomputed_diff_coeffs[i] + except AttributeError: + self._precomputed_diff_coeffs = self._precompute_monomial_diffs() + A, B, two_i_x_to_i = self._precomputed_diff_coeffs[i] + if i == 0: + return j * A, j * B + else: + return j * A, j * B + two_i_x_to_i + else: + dg = self.monomial(i, j).diff() + coeffs = [dg.extract_pow_y(j - 1), dg.extract_pow_y(j + 1)] + self._monomial_diff_coeffs[i, j] = coeffs + return coeffs + + def monomial_diff_coeffs_matrices(self): + r""" + Compute tables of coefficients of the basis representation + of `d(x^iy^j)` for small `i`, `j`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().monomial_diff_coeffs_matrices() + ( + [0 5 0 0 0] [0 2 0 0 0] + [0 0 5 0 0] [0 0 4 0 0] + [0 0 0 5 0] [0 0 0 6 0] + [0 0 0 0 5] [0 0 0 0 8] + [0 0 0 0 0], [0 0 0 0 0] + ) + """ + self.monomial_diff_coeffs(0, 0) # precompute stuff + R = self.base_ring() + mat_1 = matrix(R, self._n, self._n) + mat_2 = matrix(R, self._n, self._n) + for i in range(self._n): + mat_1[i] = self._precomputed_diff_coeffs[i][1] + mat_2[i] = self._precomputed_diff_coeffs[i][2] + return mat_1.transpose(), mat_2.transpose() + + def _precompute_monomial_diffs(self): + r""" + Precompute coefficients of the basis representation of `d(x^iy^j)` + for small `i`, `j`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent()._precompute_monomial_diffs() + [((-3, 0, 0, 0, 5), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)), + ((-5, 12, 0, 0, 0), (5, 0, 0, 0, 0), (2, 0, 0, 0, 0)), + ((0, -5, 12, 0, 0), (0, 5, 0, 0, 0), (0, 4, 0, 0, 0)), + ((0, 0, -5, 12, 0), (0, 0, 5, 0, 0), (0, 0, 6, 0, 0)), + ((0, 0, 0, -5, 12), (0, 0, 0, 5, 0), (0, 0, 0, 8, 0))] + """ + x, y = self.gens() + R = self.base_ring() + V = FreeModule(R, self.degree()) + As = [] + for i in range(self.degree()): + dg = self.monomial(i, 1).diff() + two_i_x_to_i = R(2 * i) * x ** (i - 1) * y * y if i > 0 else self(0) + A = dg - self._monsky_washnitzer(two_i_x_to_i) + As.append( + ( + V(A.extract_pow_y(0)), + V(A.extract_pow_y(2)), + V(two_i_x_to_i.extract_pow_y(2)), + ) + ) + return As + + def Q(self): + """ + Return the defining polynomial of the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5-2*x+1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().Q() + x^5 - 2*x + 1 + """ + return self._Q + + def curve(self): + """ + Return the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().curve() + Hyperelliptic Curve over Rational Field defined by y^2 = x^5 - 3*x + 1 + """ + return self._curve + + def degree(self): + """ + Return the degree of the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().degree() + 5 + """ + return Integer(self._n) + + def prime(self): + """ + Return the stored prime number `p`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().prime() is None + True + """ + return self._p + + def monsky_washnitzer(self): + """ + Return the stored Monsky-Washnitzer differential ring. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: # TODO fix me when in sage + sage: type(x.parent().monsky_washnitzer()) + + """ + return self._monsky_washnitzer + + def is_field(self, proof=True) -> bool: + """ + Return ``False`` as ``self`` is not a field. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().is_field() + False + """ + return False + + Element = SpecialHyperellipticQuotientElement + + +SpecialHyperellipticQuotientRing_class = SpecialHyperellipticQuotientRing + + +class MonskyWashnitzerDifferential(ModuleElement): + r""" + An element of the Monsky-Washnitzer ring of differentials, of + the form `F dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: MW = C.invariant_differential().parent() + sage: MW(x) + x dx/2y + sage: MW(y) + y*1 dx/2y + sage: MW(x, 10) + y^10*x dx/2y + """ + + def __init__(self, parent, val, offset=0): + r""" + Initialize ``self``. + + INPUT: + + - ``parent`` -- Monsky-Washnitzer differential ring (instance of class + :class:`~MonskyWashnitzerDifferentialRing` + + - ``val`` -- element of the base ring, or list of coefficients + + - ``offset`` -- (default: 0) if nonzero, shift val by `y^\text{offset}` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: MW = C.invariant_differential().parent() + sage: elt = MW(x) + sage: TestSuite(elt).run() + """ + ModuleElement.__init__(self, parent) + R = parent.base_ring() + self._coeff = R._element_constructor_(val, offset) + + def _add_(self, other): + r""" + Return the sum of ``self`` and ``other``, both elements of the + Monsky-Washnitzer ring of differentials. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w + w + 2*1 dx/2y + sage: x*w + w + (1 + x) dx/2y + sage: x*w + y*w + (y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, self._coeff + other._coeff) + + def _sub_(self, other): + r""" + Return the difference of ``self`` and ``other``, both elements of the + Monsky-Washnitzer ring of differentials. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w-w + 0 dx/2y + sage: x*w-w + (-1 + x) dx/2y + sage: w - x*w - y*w + ((1-y)*1 - x) dx/2y + """ + P = self.parent() + return P.element_class(P, self._coeff - other._coeff) + + def __neg__(self): + r""" + Return the additive inverse of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: -w + -1 dx/2y + sage: -((y-x)*w) + (-y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, -self._coeff) + + def _lmul_(self, a): + r""" + Return `self * a`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w*x + x dx/2y + sage: (w*x)*2 + 2*x dx/2y + sage: w*y + y*1 dx/2y + sage: w*(x+y) + (y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, self._coeff * a) + + def _rmul_(self, a): + r""" + Return `a * self`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: x*w + x dx/2y + sage: 2*(x*w) + 2*x dx/2y + sage: y*w + y*1 dx/2y + sage: (x+y)*w + (y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, a * self._coeff) + + def coeff(self): + r""" + Return `A`, where this element is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w + 1 dx/2y + sage: w.coeff() + 1 + sage: (x*y*w).coeff() + y*x + """ + return self._coeff + + def __bool__(self): + r""" + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: not w + False + sage: not 0*w + True + sage: not x*y*w + False + """ + return bool(self._coeff) + + def _repr_(self): + r""" + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w + 1 dx/2y + sage: (2*x+y)*w + (y*1 + 2*x) dx/2y + """ + s = self._coeff._repr_() + if s.find("+") != -1 or s.find("-") > 0: + s = "(%s)" % s + return s + " dx/2y" + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: latex(w) + 1 \frac{dx}{2y} + sage: latex(x*w) + x \frac{dx}{2y} + """ + s = self._coeff._latex_() + if s.find("+") != -1 or s.find("-") > 0: + s = "\\left(%s\\right)" % s + return s + " \\frac{dx}{2y}" + + def _richcmp_(self, other, op): + """ + Rich comparison of ``self`` to ``other``. + + .. TODO:: + + This does not compare elements by any reduction; + it only compares the coefficients. The comparison + should be done against a normal form or possibly + after some reduction steps. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^-1).diff() == (y^-1).diff() + True + + This element is the zero differential in the ring, but it does + not compare as equal because the representative is different:: + + sage: MW = C.invariant_differential().parent() + sage: (y^-1).diff().reduce_neg_y() + ((y^-1)*1, 0 dx/2y) + sage: (y^-1).diff() == MW.zero() + False + """ + return richcmp(self._coeff, other._coeff, op) + + def extract_pow_y(self, k): + r""" + Return the power of `y` in `A` where ``self`` is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: A = y^5 - x*y^3 + sage: A.extract_pow_y(5) + [1, 0, 0, 0, 0] + sage: (A * C.invariant_differential()).extract_pow_y(5) + [1, 0, 0, 0, 0] + """ + return self._coeff.extract_pow_y(k) + + def min_pow_y(self): + r""" + Return the minimum power of `y` in `A` where ``self`` is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = y^5 * C.invariant_differential() + sage: w.min_pow_y() + 5 + sage: w = (x^2*y^4 + y^5) * C.invariant_differential() + sage: w.min_pow_y() + 4 + """ + return self._coeff.min_pow_y() + + def max_pow_y(self): + r""" + Return the maximum power of `y` in `A` where ``self`` is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = y^5 * C.invariant_differential() + sage: w.max_pow_y() + 5 + sage: w = (x^2*y^4 + y^5) * C.invariant_differential() + sage: w.max_pow_y() + 5 + """ + return self._coeff.max_pow_y() + + def reduce_neg_y(self): + r""" + Use homology relations to eliminate negative powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^-1).diff().reduce_neg_y() + ((y^-1)*1, 0 dx/2y) + sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y() + ((y^-1)*x + (y^-5)*x^2, 0 dx/2y) + """ + S = self.parent().base_ring() + R = S.base_ring() + M = self.parent().helper_matrix() + p = S._p + f = S.zero() + reduced = self + for j in range(self.min_pow_y() + 1, 0): + if p is not None and p.divides(j): + cs = [a / j for a in reduced.extract_pow_y(j - 1)] + else: + j_inverse = ~R(j) + cs = [a * j_inverse for a in reduced.extract_pow_y(j - 1)] + lin_comb = M * vector(M.base_ring(), cs) + if lin_comb.is_zero(): + continue + g = S.sum(S.monomial(i, j, val) for i, val in enumerate(lin_comb) if val) + if not g.is_zero(): + f += g + reduced -= g.diff() + + return f, reduced + + def reduce_neg_y_fast(self, even_degree_only=False): + r""" + Use homology relations to eliminate negative powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x, y = E.monsky_washnitzer_gens() + sage: (y^-1).diff().reduce_neg_y_fast() + ((y^-1)*1, 0 dx/2y) + sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y_fast() + ((y^-1)*x + (y^-5)*x^2, 0 dx/2y) + + It leaves nonnegative powers of `y` alone:: + + sage: y.diff() + (-3*1 + 5*x^4) dx/2y + sage: y.diff().reduce_neg_y_fast() + (0, (-3*1 + 5*x^4) dx/2y) + """ + # prof = Profiler() + # prof("reduce setup") + S = self.parent().base_ring() + R = S.base_ring() + M = self.parent().helper_matrix() + + # prof("extract coeffs") + coeffs, offset = self.coeffs(R) + V = coeffs[0].parent() + + if offset == 0: + return S(0), self + + # prof("loop %s"%self.min_pow_y()) + forms = [] + p = S._p + for j in range(self.min_pow_y() + 1, 0): + if (even_degree_only and j % 2 == 0) or coeffs[j - offset - 1].is_zero(): + forms.append(V(0)) + else: + # this is a total hack to deal with the fact that we're using + # rational numbers to approximate fixed precision p-adics + if p is not None and j % 3 == 1: + try: + v = coeffs[j - offset - 1] + for kk in range(len(v)): + a = v[kk] + ppow = p ** max(-a.valuation(S._p), 0) + v[kk] = ((a * ppow) % S._prec_cap) / ppow + except AttributeError: + pass + lin_comb = ~R(j) * (M * coeffs[j - offset - 1]) + forms.append(lin_comb) + for i in lin_comb.nonzero_positions(): + # g = lin_comb[i] x^i y^j + # self -= dg + coeffs[j - offset + 1] -= ( + lin_comb[i] * S.monomial_diff_coeffs(i, j)[1] + ) + + # prof("recreate forms") + f = S(forms, offset + 1) + reduced = S._monsky_washnitzer(coeffs[-1 - offset :], -1) + return f, reduced + + def reduce_neg_y_faster(self, even_degree_only=False): + r""" + Use homology relations to eliminate negative powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^-1).diff().reduce_neg_y() + ((y^-1)*1, 0 dx/2y) + sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y_faster() + ((y^-1)*x + (y^-5)*x^2, 0 dx/2y) + """ + # Timings indicate that this is not any faster after all... + + S = self.parent().base_ring() + R = S.base_ring() + M = self.parent().helper_matrix() + + coeffs, offset = self.coeffs(R) + V = coeffs[0].parent() + zeroV = V(0) + + if offset == 0: + return S.zero(), self + + # See monomial_diff_coeffs + # this is the B_i and x_to_i contributions respectively for all i + d_mat_1, d_mat_2 = S.monomial_diff_coeffs_matrices() + + forms = [] + for j in range(self.min_pow_y() + 1, 0): + if coeffs[j - offset - 1].is_zero(): + forms.append(zeroV) + else: + # this is a total hack to deal with the fact that we're using + # rational numbers to approximate fixed precision p-adics + if j % 3 == 0: + try: + v = coeffs[j - offset - 1] + for kk in range(len(v)): + a = v[kk] + ppow = S._p ** max(-a.valuation(S._p), 0) + v[kk] = ((a * ppow) % S._prec_cap) / ppow + except AttributeError: + pass + j_inverse = ~R(j) + lin_comb = M * coeffs[j - offset - 1] + forms.append(j_inverse * lin_comb) + coeffs[j - offset + 1] -= (d_mat_1 + j_inverse * d_mat_2) * lin_comb + + f = S(forms, offset + 1) + reduced = S._monsky_washnitzer(coeffs[-1 - offset :], -1) + # reduced = self - f.diff() + return f, reduced + + def reduce_pos_y(self): + r""" + Use homology relations to eliminate positive powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^3-4*x+4) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^2).diff().reduce_pos_y() + (y^2*1, 0 dx/2y) + sage: (y^2*x).diff().reduce_pos_y() + (y^2*x, 0 dx/2y) + sage: (y^92*x).diff().reduce_pos_y() + (y^92*x, 0 dx/2y) + sage: w = (y^3 + x).diff() + sage: w += w.parent()(x) + sage: w.reduce_pos_y_fast() + (y^3*1 + x, x dx/2y) + """ + S = self.parent().base_ring() + n = S.Q().degree() + f = S.zero() + reduced = self + for j in range(self.max_pow_y(), 0, -1): + for i in range(n - 1, -1, -1): + c = reduced.extract_pow_y(j)[i] + if c: + g = S.monomial(0, j + 1) if i == n - 1 else S.monomial(i + 1, j - 1) + dg = g.diff() + denom = dg.extract_pow_y(j)[i] + c /= denom + c = g.parent()(c) + f += c * g + reduced -= c * dg + + return f, reduced + + def reduce_pos_y_fast(self, even_degree_only=False): + r""" + Use homology relations to eliminate positive powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^3 - 4*x + 4) + sage: x, y = E.monsky_washnitzer_gens() + sage: y.diff().reduce_pos_y_fast() + (y*1, 0 dx/2y) + sage: (y^2).diff().reduce_pos_y_fast() + (y^2*1, 0 dx/2y) + sage: (y^2*x).diff().reduce_pos_y_fast() + (y^2*x, 0 dx/2y) + sage: (y^92*x).diff().reduce_pos_y_fast() + (y^92*x, 0 dx/2y) + sage: w = (y^3 + x).diff() + sage: w += w.parent()(x) + sage: w.reduce_pos_y_fast() + (y^3*1 + x, x dx/2y) + """ + S = self.parent().base_ring() + R = S.base_ring() + n = S.Q().degree() + + coeffs, offset = self.coeffs(R) + V = coeffs[0].parent() + zeroV = V(0) + forms = [V(0), V(0)] + + for j in range(self.max_pow_y(), -1, -1): + if (even_degree_only and j % 2) or (j > 0 and coeffs[j - offset].is_zero()): + forms.append(zeroV) + continue + + form = V(0) + i = n - 1 + c = coeffs[j - offset][i] + if c: + dg_coeffs = S.monomial_diff_coeffs(0, j + 1)[0] + c /= dg_coeffs[i] + forms[len(forms) - 2][0] = c + # self -= c d(y^{j+1}) + coeffs[j - offset] -= c * dg_coeffs + + if j == 0: + # the others are basis elements + break + + for i in range(n - 2, -1, -1): + c = coeffs[j - offset][i] + if c: + dg_coeffs = S.monomial_diff_coeffs(i + 1, j - 1) + denom = dg_coeffs[1][i] + c /= denom + form[i + 1] = c + # self -= c d(x^{i+1} y^{j-1}) + coeffs[j - offset] -= c * dg_coeffs[1] + coeffs[j - offset - 2] -= c * dg_coeffs[0] + forms.append(form) + + forms.reverse() + f = S(forms) + reduced = self.parent()(coeffs[: 1 - offset], offset) + return f, reduced + + def reduce(self): + r""" + Use homology relations to find `a` and `f` such that this element is + equal to `a + df`, where `a` is given in terms of the `x^i dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = (y*x).diff() + sage: w.reduce() + (y*x, 0 dx/2y) + + sage: w = x^4 * C.invariant_differential() + sage: w.reduce() + (1/5*y*1, 4/5*1 dx/2y) + + sage: w = sum(QQ.random_element() * x^i * y^j + ....: for i in [0..4] for j in [-3..3]) * C.invariant_differential() + sage: f, a = w.reduce() + sage: f.diff() + a - w + 0 dx/2y + """ + n = self.parent().base_ring().Q().degree() + f1, a = self.reduce_neg_y() + f2, a = a.reduce_pos_y() + f = f1 + f2 + + c = a.extract_pow_y(0)[n - 1] + if c: + x, y = self.parent().base_ring().gens() + g = y + dg = g.diff() + c = g.parent()(c / dg.extract_pow_y(0)[n - 1]) + f += c * g + a -= c * dg + + return f, a + + def reduce_fast(self, even_degree_only=False): + r""" + Use homology relations to find `a` and `f` such that this element is + equal to `a + df`, where `a` is given in terms of the `x^i dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^3 - 4*x + 4) + sage: x, y = E.monsky_washnitzer_gens() + sage: x.diff().reduce_fast() + (x, (0, 0)) + sage: y.diff().reduce_fast() + (y*1, (0, 0)) + sage: (y^-1).diff().reduce_fast() + ((y^-1)*1, (0, 0)) + sage: (y^-11).diff().reduce_fast() + ((y^-11)*1, (0, 0)) + sage: (x*y^2).diff().reduce_fast() + (y^2*x, (0, 0)) + """ + f1, reduced = self.reduce_neg_y_fast(even_degree_only) + f2, reduced = reduced.reduce_pos_y_fast(even_degree_only) + # f1, reduced = self.reduce_neg_y() + # f2, reduced = reduced.reduce_pos_y() + v = reduced.extract_pow_y(0) + v.pop() + V = FreeModule(self.base_ring().base_ring(), len(v)) + return f1 + f2, V(v) + + def coeffs(self, R=None): + """ + Used to obtain the raw coefficients of a differential, see + :meth:`SpecialHyperellipticQuotientElement.coeffs` + + INPUT: + + - ``R`` -- an (optional) base ring in which to cast the coefficients + + OUTPUT: the raw coefficients of `A` where ``self`` is `A dx/2y` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w.coeffs() + ([(1, 0, 0, 0, 0)], 0) + sage: (x*w).coeffs() + ([(0, 1, 0, 0, 0)], 0) + sage: (y*w).coeffs() + ([(0, 0, 0, 0, 0), (1, 0, 0, 0, 0)], 0) + sage: (y^-2*w).coeffs() + ([(1, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)], -2) + """ + return self._coeff.coeffs(R) + + def coleman_integral(self, P, Q): + r""" + Compute the definite integral of ``self`` from `P` to `Q`. + + INPUT: + + - `P`, `Q` -- two points on the underlying curve + + OUTPUT: `\int_P^Q \text{self}` + + EXAMPLES:: + + sage: K = pAdicField(5,7) + sage: E = EllipticCurve(K,[-31/3,-2501/108]) #11a + sage: P = E(K(14/3), K(11/2)) + sage: w = E.invariant_differential() + sage: w.coleman_integral(P, 2*P) + O(5^6) + + sage: Q = E([3,58332]) + sage: w.coleman_integral(P,Q) + 2*5 + 4*5^2 + 3*5^3 + 4*5^4 + 3*5^5 + O(5^6) + sage: w.coleman_integral(2*P,Q) + 2*5 + 4*5^2 + 3*5^3 + 4*5^4 + 3*5^5 + O(5^6) + sage: (2*w).coleman_integral(P, Q) == 2*(w.coleman_integral(P, Q)) + True + """ + return self.parent().base_ring().curve().coleman_integral(self, P, Q) + + integrate = coleman_integral + + +class MonskyWashnitzerDifferentialRing(UniqueRepresentation, Module): + r""" + A ring of Monsky--Washnitzer differentials over ``base_ring``. + """ + + def __init__(self, base_ring): + r""" + Initialize ``self``. + + TESTS:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: # TODO change import for sage + sage: from monsky_washnitzer import SpecialHyperellipticQuotientRing, MonskyWashnitzerDifferentialRing + sage: S = SpecialHyperellipticQuotientRing(E) + sage: DR = MonskyWashnitzerDifferentialRing(S) + sage: TestSuite(DR).run() # needs sage.rings.real_interval_field + + Check that caching works:: + + sage: DR is MonskyWashnitzerDifferentialRing(S) + True + """ + Module.__init__(self, base_ring) + + def invariant_differential(self): + r""" + Return `dx/2y` as an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.invariant_differential() + 1 dx/2y + """ + return self.element_class(self, 1) + + def base_extend(self, R): + """ + Return a new differential ring which is ``self`` base-extended to `R`. + + INPUT: + + - ``R`` -- ring + + OUTPUT: + + Self, base-extended to `R`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.base_ring() + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 4*x + 4) + over Rational Field + sage: MW.base_extend(Qp(5,5)).base_ring() # needs sage.rings.padics + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = (1 + O(5^5))*x^5 + + (1 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + O(5^5))*x + 4 + O(5^5)) + over 5-adic Field with capped relative precision 5 + """ + return MonskyWashnitzerDifferentialRing(self.base_ring().base_extend(R)) + + def change_ring(self, R): + """ + Return a new differential ring which is ``self`` with the coefficient + ring changed to `R`. + + INPUT: + + - ``R`` -- ring of coefficients + + OUTPUT: ``self`` with the coefficient ring changed to `R` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.base_ring() + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 4*x + 4) + over Rational Field + sage: MW.change_ring(Qp(5,5)).base_ring() # needs sage.rings.padics + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = (1 + O(5^5))*x^5 + + (1 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + O(5^5))*x + 4 + O(5^5)) + over 5-adic Field with capped relative precision 5 + """ + return MonskyWashnitzerDifferentialRing(self.base_ring().change_ring(R)) + + def degree(self): + """ + Return the degree of `Q(x)`, where the model of the underlying + hyperelliptic curve of ``self`` is given by `y^2 = Q(x)`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.Q() + x^5 - 4*x + 4 + sage: MW.degree() + 5 + """ + return self.base_ring().degree() + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: # needs sage.rings.padics + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: K = Qp(7,5) + sage: CK = C.change_ring(K) + sage: MW = CK.invariant_differential().parent() + sage: MW.dimension() + 4 + """ + return self.base_ring().degree() - 1 + + def Q(self): + """ + Return `Q(x)` where the model of the underlying hyperelliptic curve + of ``self`` is given by `y^2 = Q(x)`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.Q() + x^5 - 4*x + 4 + """ + return self.base_ring().Q() + + @cached_method + def x_to_p(self, p): + """ + Return and cache `x^p`, reduced via the relations coming from the + defining polynomial of the hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.x_to_p(3) + x^3 + sage: MW.x_to_p(5) + -(4-y^2)*1 + 4*x + sage: MW.x_to_p(101) is MW.x_to_p(101) + True + """ + return self.base_ring().x() ** p + + @cached_method + def frob_Q(self, p): + r""" + Return and cache `Q(x^p)`, which is used in computing the image of + `y` under a `p`-power lift of Frobenius to `A^{\dagger}`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.frob_Q(3) + -(60-48*y^2+12*y^4-y^6)*1 + (192-96*y^2+12*y^4)*x - (192-48*y^2)*x^2 + 60*x^3 + sage: MW.Q()(MW.x_to_p(3)) # needs sage.rings.real_interval_field + -(60-48*y^2+12*y^4-y^6)*1 + (192-96*y^2+12*y^4)*x - (192-48*y^2)*x^2 + 60*x^3 + sage: MW.frob_Q(11) is MW.frob_Q(11) + True + """ + return self.base_ring()._Q.change_ring(self.base_ring())(self.x_to_p(p)) + + def frob_invariant_differential(self, prec, p): + r""" + Kedlaya's algorithm allows us to calculate the action of Frobenius on + the Monsky-Washnitzer cohomology. First we lift `\phi` to `A^{\dagger}` + by setting + + .. MATH:: + + \phi(x) = x^p, + \qquad\qquad + \phi(y) = y^p \sqrt{1 + \frac{Q(x^p) - Q(x)^p}{Q(x)^p}}. + + Pulling back the differential `dx/2y`, we get + + .. MATH:: + + \phi^*(dx/2y) = px^{p-1} y(\phi(y))^{-1} dx/2y + = px^{p-1} y^{1-p} \sqrt{1+ \frac{Q(x^p) - Q(x)^p}{Q(x)^p}} dx/2y. + + Use Newton's method to calculate the square root. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: prec = 2 + sage: p = 7 + sage: MW = C.invariant_differential().parent() + sage: MW.frob_invariant_differential(prec, p) + ((67894400*y^-20-81198880*y^-18+40140800*y^-16-10035200*y^-14+1254400*y^-12-62720*y^-10)*1 + - (119503944*y^-20-116064242*y^-18+43753472*y^-16-7426048*y^-14+514304*y^-12-12544*y^-10+1568*y^-8-70*y^-6-7*y^-4)*x + + (78905288*y^-20-61014016*y^-18+16859136*y^-16-2207744*y^-14+250880*y^-12-37632*y^-10+3136*y^-8-70*y^-6)*x^2 + - (39452448*y^-20-26148752*y^-18+8085490*y^-16-2007040*y^-14+376320*y^-12-37632*y^-10+1568*y^-8)*x^3 + + (21102144*y^-20-18120592*y^-18+8028160*y^-16-2007040*y^-14+250880*y^-12-12544*y^-10)*x^4) dx/2y + """ + prof = Profiler() + prof("setup") + # TODO, would it be useful to be able to take Frobenius of any element? Less efficient? + x, y = self.base_ring().gens() + prof("x_to_p") + x_to_p_less_1 = x ** (p - 1) + x_to_p = x * x_to_p_less_1 + + # cache for future use + self.x_to_p.set_cache(p, x_to_p) + + prof("frob_Q") + a = self.frob_Q(p) >> 2 * p # frobQ * y^{-2p} + + prof("sqrt") + + # Q = self.base_ring()._Q + # three_halves = Q.parent().base_ring()(Rational((3,2))) + # one_half = Q.parent().base_ring()(Rational((1,2))) + three_halves = self.base_ring()._series_ring.base_ring()(Rational((3, 2))) + one_half = self.base_ring()._series_ring.base_ring()(Rational((1, 2))) + half_a = a._rmul_(one_half) + + # We are solving for t = a^{-1/2} = (F_pQ y^{-p})^{-1/2} + # Newton's method converges because we know the root is in the same residue class as 1. + + # t = self.base_ring()(1) + t = self.base_ring()(three_halves) - half_a + # first iteration trivial, start with prec 2 + + for cur_prec in newton_method_sizes(prec)[2:]: + # newton_method_sizes = [1, 2, ...] + y_prec = -(2 * cur_prec - 1) * p + 1 + # binomial expansion is $\sum p^{k+1} y^{-(2k+1)p+1} f(x)$ + # so if we are only correct mod p^prec, + # can ignore y powers less than y_prec + t_cube = (t * t * t).truncate_neg(y_prec) + t = t._rmul_(three_halves) - (half_a * t_cube).truncate_neg(y_prec) + # t = (3/2) t - (1/2) a t^3 + + prof("compose") + F_dx_y = (p * x_to_p_less_1 * t) >> (p - 1) # px^{p-1} sqrt(a) * y^{-p+1} + + prof("done") + return MonskyWashnitzerDifferential(self, F_dx_y) + + def frob_basis_elements(self, prec, p): + r""" + Return the action of a `p`-power lift of Frobenius on the basis. + + .. MATH:: + + \{ dx/2y, x dx/2y, ..., x^{d-2} dx/2y \}, + + where `d` is the degree of the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: prec = 1 + sage: p = 5 + sage: MW = C.invariant_differential().parent() + sage: MW.frob_basis_elements(prec, p) + [((92000*y^-14-74200*y^-12+32000*y^-10-8000*y^-8+1000*y^-6-50*y^-4)*1 + - (194400*y^-14-153600*y^-12+57600*y^-10-9600*y^-8+600*y^-6)*x + + (204800*y^-14-153600*y^-12+38400*y^-10-3200*y^-8)*x^2 + - (153600*y^-14-76800*y^-12+9600*y^-10)*x^3 + + (63950*y^-14-18550*y^-12+1600*y^-10-400*y^-8+50*y^-6+5*y^-4)*x^4) dx/2y, + (-(1391200*y^-14-941400*y^-12+302000*y^-10-76800*y^-8+14400*y^-6-1320*y^-4+30*y^-2)*1 + + (2168800*y^-14-1402400*y^-12+537600*y^-10-134400*y^-8+16800*y^-6-720*y^-4)*x + - (1596800*y^-14-1433600*y^-12+537600*y^-10-89600*y^-8+5600*y^-6)*x^2 + + (1433600*y^-14-1075200*y^-12+268800*y^-10-22400*y^-8)*x^3 + - (870200*y^-14-445350*y^-12+63350*y^-10-3200*y^-8+600*y^-6-30*y^-4-5*y^-2)*x^4) dx/2y, + ((19488000*y^-14-15763200*y^-12+4944400*y^-10-913800*y^-8+156800*y^-6-22560*y^-4+1480*y^-2-10)*1 + - (28163200*y^-14-18669600*y^-12+5774400*y^-10-1433600*y^-8+268800*y^-6-25440*y^-4+760*y^-2)*x + + (15062400*y^-14-12940800*y^-12+5734400*y^-10-1433600*y^-8+179200*y^-6-8480*y^-4)*x^2 + - (12121600*y^-14-11468800*y^-12+4300800*y^-10-716800*y^-8+44800*y^-6)*x^3 + + (9215200*y^-14-6952400*y^-12+1773950*y^-10-165750*y^-8+5600*y^-6-720*y^-4+10*y^-2+5)*x^4) dx/2y, + (-(225395200*y^-14-230640000*y^-12+91733600*y^-10-18347400*y^-8+2293600*y^-6-280960*y^-4+31520*y^-2-1480-10*y^2)*1 + + (338048000*y^-14-277132800*y^-12+89928000*y^-10-17816000*y^-8+3225600*y^-6-472320*y^-4+34560*y^-2-720)*x + - (172902400*y^-14-141504000*y^-12+58976000*y^-10-17203200*y^-8+3225600*y^-6-314880*y^-4+11520*y^-2)*x^2 + + (108736000*y^-14-109760000*y^-12+51609600*y^-10-12902400*y^-8+1612800*y^-6-78720*y^-4)*x^3 + - (85347200*y^-14-82900000*y^-12+31251400*y^-10-5304150*y^-8+367350*y^-6-8480*y^-4+760*y^-2+10-5*y^2)*x^4) dx/2y] + """ + F_i = self.frob_invariant_differential(prec, p) + x_to_p = self.x_to_p(p) + F = [F_i] + for i in range(1, self.degree() - 1): + F_i *= x_to_p + F.append(F_i) + return F + + def helper_matrix(self): + r""" + We use this to solve for the linear combination of + `x^i y^j` needed to clear all terms with `y^{j-1}`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.helper_matrix() + [ 256/2101 320/2101 400/2101 500/2101 625/2101] + [-625/8404 -64/2101 -80/2101 -100/2101 -125/2101] + [-125/2101 -625/8404 -64/2101 -80/2101 -100/2101] + [-100/2101 -125/2101 -625/8404 -64/2101 -80/2101] + [ -80/2101 -100/2101 -125/2101 -625/8404 -64/2101] + """ + try: + return self._helper_matrix + except AttributeError: + pass + + # The smallest y term of (1/j) d(x^i y^j) is constant for all j. + x, y = self.base_ring().gens() + n = self.degree() + L = [(y * x**i).diff().extract_pow_y(0) for i in range(n)] + A = matrix(L).transpose() + if A.base_ring() not in IntegralDomains(): + # must be using integer_mod or something to approximate + self._helper_matrix = (~A.change_ring(QQ)).change_ring(A.base_ring()) + else: + self._helper_matrix = ~A + return self._helper_matrix + + def _element_constructor_(self, val=0, offset=0): + r""" + Construct an element of ``self``. + + INPUT: + + - ``parent`` -- Monsky-Washnitzer differential ring (instance of class + :class:`~MonskyWashnitzerDifferentialRing` + - ``val`` -- element of the base ring, or list of coefficients + - ``offset`` -- (default: 0) if nonzero, shift val by `y^\text{offset}` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW(3) + 3*1 dx/2y + """ + if isinstance(val, MonskyWashnitzerDifferential): + val = val._coeff + return self.element_class(self, val, offset) + + Element = MonskyWashnitzerDifferential + + +MonskyWashnitzerDifferentialRing_class = MonskyWashnitzerDifferentialRing diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py new file mode 100644 index 00000000000..ea34d940f6b --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py @@ -0,0 +1,12 @@ +from sage.schemes.curves.curve import Curve_generic + + +class WeightedProjectiveCurve(Curve_generic): + def __init__(self, A, X, *kwargs): + # TODO ensure that A is the right type? + # Something like a `is_WeightProjectiveSpace` which means making a + # WeightProjectiveSpace class? + super().__init__(A, X, *kwargs) + + def curve(self): + return diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py new file mode 100644 index 00000000000..84cdf7d028d --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py @@ -0,0 +1,46 @@ +""" +Hom-sets of weighted projective schemes + +AUTHORS: + +- Gareth Ma (2024): initial version, based on unweighted version. +""" + +# ***************************************************************************** +# Copyright (C) 2024 Gareth Ma +# Copyright (C) 2011 Volker Braun +# Copyright (C) 2006 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.schemes.generic.homset import SchemeHomset_points + + +class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): + """ + Set of rational points of a weighted projective variety over a ring. + + INPUT: + + See :class:`SchemeHomset_generic`. + + EXAMPLES:: + + sage: # TODO + """ + + def points(self, **__): + """ + Return some or all rational points of this weighted projective scheme. + + For dimension 0 subschemes points are determined through a groebner + basis calculation. For schemes or subschemes with dimension greater than 1 + points are determined through enumeration up to the specified bound. + + TODO: modify implementation from projective space + """ + raise NotImplementedError("enumerating points on weighted projective scheme is not implemented") diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py new file mode 100644 index 00000000000..dcb014416a3 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py @@ -0,0 +1,389 @@ +r""" +Points on projective schemes + +This module implements scheme morphism for points on weighted projective schemes. + +AUTHORS: + +- Gareth Ma (2024): initial version, based on unweighted version. +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# Copyright (C) 2006 William Stein +# Copyright (C) 2011 Volker Braun +# Copyright (C) 2024 Gareth Ma +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from copy import copy + +from sage.categories.integral_domains import IntegralDomains +from sage.rings.fraction_field import FractionField +from sage.schemes.generic.morphism import ( + SchemeMorphism, + SchemeMorphism_point, + is_SchemeMorphism, +) +from sage.structure.richcmp import op_EQ, op_NE, richcmp +from sage.structure.sequence import Sequence + +# -------------------- +# Projective varieties +# -------------------- + +class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): + """ + A rational point of projective space over a ring. + + INPUT: + + - ``X`` -- a homset of a subscheme of an ambient projective space over a ring `K`. + + - ``v`` -- a list or tuple of coordinates in `K`. + + - ``check`` -- boolean (default:``True``). Whether to check the input for consistency. + + EXAMPLES:: + + sage: P = ProjectiveSpace(2, ZZ) + sage: P(2,3,4) + (2 : 3 : 4) + """ + + def __init__(self, X, v, check=True): + """ + The Python constructor. + + EXAMPLES:: + + TODO + """ + SchemeMorphism.__init__(self, X) + + if check: + # check parent + from weighted_projective_homset import SchemeHomset_points_weighted_projective_ring + if not isinstance(X, SchemeHomset_points_weighted_projective_ring): + raise TypeError(f"ambient space {X} must be a weighted projective space") + + from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field + from sage.rings.ring import CommutativeRing + d = X.codomain().ambient_space().ngens() + if isinstance(v, SchemeMorphism) or isinstance(v, EllipticCurvePoint_field): + v = list(v) + else: + try: + if isinstance(v.parent(), CommutativeRing): + v = [v] + except AttributeError: + pass + if not isinstance(v, (list, tuple)): + raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) + if len(v) != d and len(v) != d-1: + raise TypeError("v (=%s) must have %s components" % (v, d)) + + R = X.value_ring() + v = Sequence(v, R) + if len(v) == d-1: # very common special case + v.append(R.one()) + + if R in IntegralDomains(): + # Over integral domains, any tuple with at least one + # non-zero coordinate is a valid projective point. + if not any(v): + raise ValueError(f"{v} does not define a valid projective " + "point since all entries are zero") + else: + # Over rings with zero divisors, a more careful check + # is required: We test whether the coordinates of the + # point generate the unit ideal. See #31576. + if 1 not in R.ideal(v): + raise ValueError(f"{v} does not define a valid projective point " + "since it is a multiple of a zero divisor") + + X.extended_codomain()._check_satisfies_equations(v) + + self._coords = tuple(v) + self._normalized = False + + def _repr_(self): + return "({})".format(" : ".join(map(repr, self._coords))) + + def _richcmp_(self, other, op): + """ + Test the projective equality of two points. + + INPUT: + + - ``right`` -- a point on projective space + + OUTPUT: + + Boolean + + EXAMPLES:: + + TODO + """ + assert isinstance(other, SchemeMorphism_point) + + if self.codomain() != other.codomain(): + return op == op_NE + + n = len(self._coords) + if op in [op_EQ, op_NE]: + b = all(self[i] * other[j] == self[j] * other[i] + for i in range(n) for j in range(i + 1, n)) + return b == (op == op_EQ) + return richcmp(self._coords, other._coords, op) + + def __hash__(self): + """ + Compute the hash value of this point. + + If the base ring has a fraction field, normalize the point in + the fraction field and then hash so that equal points have + equal hash values. If the base ring is not an integral domain, + return the hash of the parent. + + OUTPUT: Integer. + + EXAMPLES:: + + sage: P. = ProjectiveSpace(ZZ, 1) + sage: hash(P([1, 1])) == hash(P.point([2, 2], False)) + True + + :: + + sage: # needs sage.rings.number_field + sage: R. = PolynomialRing(QQ) + sage: K. = NumberField(x^2 + 3) + sage: O = K.maximal_order() + sage: P. = ProjectiveSpace(O, 1) + sage: hash(P([1 + w, 2])) == hash(P([2, 1 - w])) + True + + TESTS:: + + sage: P. = ProjectiveSpace(Zmod(10), 1) + sage: Q. = ProjectiveSpace(Zmod(10), 1) + sage: hash(P([2, 5])) == hash(Q([2, 5])) + True + sage: hash(P([2, 5])) == hash(P([2, 5])) + True + sage: hash(P([3, 7])) == hash(P([2, 5])) + True + """ + R = self.codomain().base_ring() + #if there is a fraction field normalize the point so that + #equal points have equal hash values + if R in IntegralDomains(): + P = self.change_ring(FractionField(R)) + P.normalize_coordinates() + return hash(tuple(P)) + #if there is no good way to normalize return + #a constant value + return hash(self.codomain()) + +class SchemeMorphism_point_weighted_projective_field(SchemeMorphism_point_weighted_projective_ring): + """ + A rational point of projective space over a field. + + INPUT: + + - ``X`` -- a homset of a subscheme of an ambient projective space + over a field `K`. + + - ``v`` -- a list or tuple of coordinates in `K`. + + - ``check`` -- boolean (default:``True``). Whether to + check the input for consistency. + + EXAMPLES:: + + sage: # needs sage.rings.real_mpfr + sage: P = ProjectiveSpace(3, RR) + sage: P(2, 3, 4, 5) + (0.400000000000000 : 0.600000000000000 : 0.800000000000000 : 1.00000000000000) + """ + + def __init__(self, X, v, check=True): + """ + The Python constructor. + + See :class:`SchemeMorphism_point_projective_ring` for details. + + This function still normalizes points so that the rightmost non-zero coordinate is 1. + This is to maintain functionality with current + implementations of curves in projectives space (plane, conic, elliptic, etc). + The :class:`SchemeMorphism_point_projective_ring` is for general use. + + EXAMPLES:: + + sage: P = ProjectiveSpace(2, QQ) + sage: P(2, 3/5, 4) + (1/2 : 3/20 : 1) + + :: + + sage: P = ProjectiveSpace(3, QQ) + sage: P(0, 0, 0, 0) + Traceback (most recent call last): + ... + ValueError: [0, 0, 0, 0] does not define a valid projective point since all entries are zero + + :: + + sage: P. = ProjectiveSpace(2, QQ) + sage: X = P.subscheme([x^2 - y*z]) + sage: X([2, 2, 2]) + (1 : 1 : 1) + + :: + + sage: P = ProjectiveSpace(1, GF(7)) + sage: Q = P([2, 1]) + sage: Q[0].parent() + Finite Field of size 7 + + :: + + sage: P = ProjectiveSpace(QQ, 1) + sage: P.point(Infinity) + (1 : 0) + sage: P(infinity) + (1 : 0) + + :: + + sage: P = ProjectiveSpace(QQ, 2) + sage: P(infinity) + Traceback (most recent call last): + ... + ValueError: +Infinity not well defined in dimension > 1 + sage: P.point(infinity) + Traceback (most recent call last): + ... + ValueError: +Infinity not well defined in dimension > 1 + """ + SchemeMorphism.__init__(self, X) + + self._normalized = False + + if check: + from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field + from sage.rings.ring import CommutativeRing + d = X.codomain().ambient_space().ngens() + if is_SchemeMorphism(v) or isinstance(v, EllipticCurvePoint_field): + v = list(v) + else: + try: + if isinstance(v.parent(), CommutativeRing): + v = [v] + except AttributeError: + pass + if not isinstance(v, (list,tuple)): + raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) + if len(v) != d and len(v) != d-1: + raise TypeError("v (=%s) must have %s components" % (v, d)) + + R = X.value_ring() + v = Sequence(v, R) + if len(v) == d-1: # very common special case + v.append(R.one()) + + for last in reversed(range(len(v))): + c = v[last] + if c.is_one(): + break + if c: + for j in range(last): + v[j] /= c + v[last] = R.one() + break + else: + raise ValueError(f"{v} does not define a valid projective " + "point since all entries are zero") + self._normalized = True + + X.extended_codomain()._check_satisfies_equations(v) + + self._coords = tuple(v) + + def __hash__(self): + """ + Computes the hash value of this point. + + OUTPUT: Integer. + + EXAMPLES:: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: hash(P([1/2, 1])) == hash(P.point([1, 2], False)) + True + """ + P = copy(self) + P.normalize_coordinates() + return hash(tuple(P)) + + def normalize_coordinates(self): + r""" + Normalizes the point so that the last non-zero coordinate is `1`. + + OUTPUT: None. + + EXAMPLES:: + + sage: P. = ProjectiveSpace(GF(5), 2) + sage: Q = P.point([GF(5)(1), GF(5)(3), GF(5)(0)], False); Q + (1 : 3 : 0) + sage: Q.normalize_coordinates(); Q + (2 : 1 : 0) + + :: + + sage: P. = ProjectiveSpace(QQ, 2) + sage: X = P.subscheme(x^2 - y^2); + sage: Q = X.point([23, 23, 46], False); Q + (23 : 23 : 46) + sage: Q.normalize_coordinates(); Q + (1/2 : 1/2 : 1) + """ + if self._normalized: + return + for index in reversed(range(len(self._coords))): + c = self._coords[index] + if c.is_one(): + break + if c: + inv = c.inverse() + new_coords = [d * inv for d in self._coords[:index]] + new_coords.append(self.base_ring().one()) + new_coords.extend(self._coords[index+1:]) + self._coords = tuple(new_coords) + break + else: + assert False, 'bug: invalid projective point' + self._normalized = True + + +class SchemeMorphism_point_weighted_projective_finite_field(SchemeMorphism_point_weighted_projective_field): + + def __hash__(self): + r""" + Returns the integer hash of this point. + + OUTPUT: Integer. + + EXAMPLES: TODO + """ + p = self.codomain().base_ring().order() + N = self.codomain().ambient_space().dimension_relative() + return hash(sum(hash(self[i]) * p**i for i in range(N + 1))) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py new file mode 100644 index 00000000000..d7499073f36 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py @@ -0,0 +1,369 @@ +from sage.misc.latex import latex +from sage.misc.prandom import shuffle +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.term_order import TermOrder +from sage.schemes.generic.ambient_space import AmbientSpace +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_homset import ( + SchemeHomset_points_weighted_projective_ring, +) +from sage.schemes.projective.projective_space import ProjectiveSpace, _CommRings +from sage.structure.all import UniqueRepresentation +from sage.structure.category_object import normalize_names + + +def WeightedProjectiveSpace(weights, R=None, names=None): + r""" + Return a weighted projective space with the given ``weights`` over the ring ``R``. + + EXAMPLES:: + + sage: # TODO: add example of point on this space (it doesn't work right now) + sage: WP = WeightedProjectiveSpace([1, 3, 1]); WP + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + """ + if ( + isinstance(weights, (MPolynomialRing_base, PolynomialRing_general)) + and R is None + ): + if names is not None: + # Check for the case that the user provided a variable name + # That does not match what we wanted to use from R + names = normalize_names(weights.ngens(), names) + if weights.variable_names() != names: + # The provided name doesn't match the name of R's variables + raise NameError( + "variable names passed to ProjectiveSpace conflict with names in ring" + ) + A = WeightedProjectiveSpace( + weights.ngens() - 1, weights.base_ring(), names=weights.variable_names() + ) + A._coordinate_ring = weights + return A + + if isinstance(R, (int, Integer, list, tuple)): + weights, R = R, weights + elif R is None: + R = ZZ + + # WeightedProjectiveSpace(5) -> just return unweighted version + if isinstance(weights, (int, Integer)): + return ProjectiveSpace(weights, R=R, names=names) + elif isinstance(weights, (list, tuple)): + # Make it hashable + weights = tuple(map(Integer, weights)) + if any(w <= 0 for w in weights): + raise TypeError( + f"weights(={weights}) should only consist of positive integers" + ) + else: + raise TypeError(f"weights={weights} must be an integer, a list or a tuple") + + if names is None: + names = "x" + + # TODO: Specialise implementation to projective spaces over non-rings. But + # since we don't really implement extra functionalities, I don't think we + # care. + if R in _CommRings: + return WeightedProjectiveSpace_ring(weights, R=R, names=names) + + raise TypeError(f"R (={R}) must be a commutative ring") + + +class WeightedProjectiveSpace_ring(UniqueRepresentation, AmbientSpace): + @staticmethod + def __classcall__(cls, weights: tuple[Integer], R=ZZ, names=None): + # __classcall_ is the "preprocessing" step for UniqueRepresentation + # see docs of CachedRepresentation + # weights should be a tuple, also because it should be hashable + if not isinstance(weights, tuple): + raise TypeError( + f"weights(={weights}) is not a tuple. Please use the `WeightedProjectiveSpace`" + " constructor" + ) + + # TODO: Do we normalise the weights to make it coprime? + normalized_names = normalize_names(len(weights), names) + return super().__classcall__(cls, weights, R, normalized_names) + + def __init__(self, weights: tuple[Integer], R=ZZ, names=None): + """ + Initialization function. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(Zp(5), [1, 3, 1], 'y') # needs sage.rings.padics + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over 5-adic Ring with + capped relative precision 20 + sage: WeightedProjectiveSpace(QQ, 5, 'y') + Projective Space of dimension 5 over Rational Field + sage: _ is ProjectiveSpace(QQ, 5, 'y') + True + """ + AmbientSpace.__init__(self, len(weights) - 1, R) + self._weights = weights + self._assign_names(names) + + def weights(self) -> tuple[Integer]: + """ + Return the tuple of weights of this weighted projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(QQ, [1, 3, 1]).weights() + (1, 3, 1) + """ + return self._weights + + def ngens(self) -> Integer: + """ + Return the number of generators of this weighted projective space. + + This is the number of variables in the coordinate ring of ``self``. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(QQ, [1, 3, 1]).ngens() + 3 + sage: WeightedProjectiveSpace(ZZ, 5).ngens() + 6 + """ + return self.dimension_relative() + 1 + + def _check_satisfies_equations(self, v: list[Integer] | tuple[Integer]) -> bool: + """ + Return ``True`` if ``v`` defines a point on the weighted projective + plane; raise a :class:`TypeError` otherwise. + + EXAMPLES:: + + sage: P = WeightedProjectiveSpace(ZZ, [1, 3, 1]) + sage: P._check_satisfies_equations([1, 1, 0]) + True + + sage: P._check_satisfies_equations([1, 0]) + Traceback (most recent call last): + ... + TypeError: the list v=[1, 0] must have 3 components + + sage: P._check_satisfies_equations([1/2, 0, 1]) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + """ + if not isinstance(v, (list, tuple)): + raise TypeError(f"the argument v={v} must be a list or tuple") + + n = self.ngens() + if len(v) != n: + raise TypeError(f"the list v={v} must have {n} components") + + v = list(map(self.base_ring(), v)) + if all(vi.is_zero() for vi in v): + raise TypeError("the zero vector is not a point in projective space") + + return True + + def coordinate_ring(self) -> PolynomialRing_general: + """ + Return the coordinate ring of this weighted projective space. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(GF(19^2, 'α'), [1, 3, 4, 1], 'abcd') + sage: # needs sage.rings.finite_rings + sage: R = WP.coordinate_ring(); R + Multivariate Polynomial Ring in a, b, c, d over Finite Field in α of size 19^2 + sage: R.term_order() + Weighted degree reverse lexicographic term order with weights (1, 3, 4, 1) + + :: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 1, 1], ['alpha', 'beta', 'gamma']) + sage: R = WP.coordinate_ring(); R + Multivariate Polynomial Ring in alpha, beta, gamma over Rational Field + sage: R.term_order() + Weighted degree reverse lexicographic term order with weights (1, 1, 1) + """ + if not hasattr(self, "_coordinate_ring"): + term_order = TermOrder("wdegrevlex", self.weights()) + self._coordinate_ring = PolynomialRing( + self.base_ring(), + self.dimension_relative() + 1, + names=self.variable_names(), + order=term_order, + ) + return self._coordinate_ring + + def _validate(self, polynomials): + """ + If ``polynomials`` is a tuple of valid polynomial functions on ``self``, + return ``polynomials``, otherwise raise ``TypeError``. + + Since this is a weighted projective space, polynomials must be + homogeneous with respect to the grading of this space. + + INPUT: + + - ``polynomials`` -- tuple of polynomials in the coordinate ring of + this space. + + OUTPUT: + + - tuple of polynomials in the coordinate ring of this space. + + EXAMPLES:: + + sage: P. = WeightedProjectiveSpace(QQ, [1, 3, 1]) + sage: P._validate([x*y - z^4, x]) + [x*y - z^4, x] + sage: P._validate([x*y - z^2, x]) + Traceback (most recent call last): + ... + TypeError: x*y - z^2 is not homogeneous with weights (1, 3, 1) + sage: P._validate(x*y - z) + Traceback (most recent call last): + ... + TypeError: the argument polynomials=x*y - z must be a list or tuple + """ + if not isinstance(polynomials, (list, tuple)): + raise TypeError( + f"the argument polynomials={polynomials} must be a list or tuple" + ) + + R = self.coordinate_ring() + for f in map(R, polynomials): + if not f.is_homogeneous(): + raise TypeError(f"{f} is not homogeneous with weights {self.weights()}") + + return polynomials + + def _latex_(self): + r""" + Return a LaTeX representation of this projective space. + + EXAMPLES:: + + sage: print(latex(WeightedProjectiveSpace(ZZ, [1, 3, 1], 'x'))) + {\mathbf P}_{\Bold{Z}}^{[1, 3, 1]} + + TESTS:: + + sage: WeightedProjectiveSpace(Zp(5), [2, 1, 3], 'y')._latex_() # needs sage.rings.padics + '{\\mathbf P}_{\\Bold{Z}_{5}}^{[2, 1, 3]}' + """ + return ( + f"{{\\mathbf P}}_{{{latex(self.base_ring())}}}^{{{list(self.weights())}}}" + ) + + def _morphism(self, *_, **__): + """ + Construct a morphism. + + For internal use only. See :mod:`morphism` for details. + """ + raise NotImplementedError( + "_morphism not implemented for weighted projective space" + ) + + def _homset(self, *_, **__): + """ + Construct the Hom-set. + """ + raise NotImplementedError( + "_homset not implemented for weighted projective space" + ) + + def _point_homset(self, *args, **kwds): + """ + Construct a point Hom-set. + + For internal use only. See :mod:`morphism` for details. + """ + # raise NotImplementedError("_point_homset not implemented for weighted projective space") + return SchemeHomset_points_weighted_projective_ring(*args, **kwds) + + def point(self, v, check=True): + """ + Create a point on this weighted projective space. + + INPUT: + + INPUT: + + - ``v`` -- anything that defines a point + + - ``check`` -- boolean (default: ``True``); whether + to check the defining data for consistency + + OUTPUT: A point of this weighted projective space. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 3, 1]) + sage: WP.point([2, 3, 1]) + (2 : 3 : 1) + """ + from sage.rings.infinity import infinity + + if v is infinity or ( + isinstance(v, (list, tuple)) and len(v) == 1 and v[0] is infinity + ): + if self.dimension_relative() > 1: + raise ValueError("%s not well defined in dimension > 1" % v) + v = [1, 0] + + return self.point_homset()(v, check=check) + + def _point(self, *args, **kwds): + """ + Construct a point. + + For internal use only. See :mod:`morphism` for details. + """ + from weighted_projective_point import SchemeMorphism_point_weighted_projective_ring + return SchemeMorphism_point_weighted_projective_ring(*args, **kwds) + + def _repr_(self) -> str: + """ + Return a string representation of this projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(Qp(5), [1, 3, 1], 'x') # needs sage.rings.padics + Weighted Projective Space of dimension 2 with weights (1, 3, 1) + over 5-adic Field with capped relative precision 20 + """ + return ( + f"Weighted Projective Space of dimension {self.dimension_relative()} with weights" + f" {self.weights()} over {self.base_ring()}" + ) + + def _an_element_(self): + r""" + Return a (preferably typical) element of this space. + + This is used both for illustration and testing purposes. + + OUTPUT: a point in this projective space. + + EXAMPLES:: + + sage: # TODO: Enable this + sage: WeightedProjectiveSpace(ZZ, [1, 3, 1], 'x').an_element() # random + (7 : 6 : 5 : 1) + sage: WeightedProjectiveSpace(ZZ["y"], [2, 3, 1], 'x').an_element() # random + (7*y : 6*y : 5*y : 1) + """ + n = self.dimension_relative() + R = self.base_ring() + coords = [(n + 1 - i) * R.an_element() for i in range(n)] + [R.one()] + shuffle(coords) + return self(coords) + + def subscheme(self, *_, **__): + raise NotImplementedError("subscheme of weighted projective space has not been implemented") diff --git a/src/sage/schemes/meson.build b/src/sage/schemes/meson.build index c74c532b930..24807ecf335 100644 --- a/src/sage/schemes/meson.build +++ b/src/sage/schemes/meson.build @@ -7,6 +7,7 @@ install_subdir('cyclic_covers', install_dir: sage_install_dir / 'schemes') subdir('elliptic_curves') install_subdir('generic', install_dir: sage_install_dir / 'schemes') subdir('hyperelliptic_curves') +subdir('hyperelliptic_curves_smooth_model') install_subdir('jacobians', install_dir: sage_install_dir / 'schemes') install_subdir('plane_conics', install_dir: sage_install_dir / 'schemes') install_subdir('plane_quartics', install_dir: sage_install_dir / 'schemes') From 93019cf6aafae54e34df7035a8f9153ebd7e8cff Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 4 Dec 2024 15:18:40 +0000 Subject: [PATCH 02/56] fix import lines --- src/sage/schemes/hyperelliptic_curves_smooth_model/all.py | 4 +++- .../jacobian_homset_split.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py index 4e197e56905..92453c6b48e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -1,2 +1,4 @@ -from sage.schemes.hyperelliptic_curves_smooth_model.constructor import HyperellipticCurveSmoothModel +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, +) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index 6944766d2b5..ddb7c246c55 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -5,12 +5,12 @@ from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( MumfordDivisorClassFieldSplit, ) -from sage.structure.element import parent # TODO: move this from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( SchemeMorphism_point_weighted_projective_ring, ) +from sage.structure.element import parent class HyperellipticJacobianHomsetSplit(HyperellipticJacobianHomset): From 50154ed538f241dff3f2f3f8eccd7161632404c0 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 4 Dec 2024 15:25:37 +0000 Subject: [PATCH 03/56] fix function imports --- .../weighted_projective_point.py | 2 +- .../weighted_projective_space.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py index dcb014416a3..7081ba5ccdf 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py @@ -68,7 +68,7 @@ def __init__(self, X, v, check=True): if check: # check parent - from weighted_projective_homset import SchemeHomset_points_weighted_projective_ring + from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_homset import SchemeHomset_points_weighted_projective_ring if not isinstance(X, SchemeHomset_points_weighted_projective_ring): raise TypeError(f"ambient space {X} must be a weighted projective space") diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py index d7499073f36..810adc6e7d4 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py @@ -325,7 +325,9 @@ def _point(self, *args, **kwds): For internal use only. See :mod:`morphism` for details. """ - from weighted_projective_point import SchemeMorphism_point_weighted_projective_ring + from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, + ) return SchemeMorphism_point_weighted_projective_ring(*args, **kwds) def _repr_(self) -> str: From 7044145886b5bfbc7871d1819c817e3bc25469c3 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 4 Dec 2024 15:31:35 +0000 Subject: [PATCH 04/56] fix a bunch of old import statements --- .../hyperelliptic_generic.py | 12 ++- .../hyperelliptic_padic_field.py | 5 +- .../hyperelliptic_rational_field.py | 2 +- .../monsky_washnitzer.py | 93 +++++++------------ 4 files changed, 44 insertions(+), 68 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index fcc92f59d64..0b6ea1591c9 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -128,7 +128,9 @@ def change_ring(self, R): ... ValueError: not a hyperelliptic curve: singularity in the provided affine patch """ - from hyperelliptic_constructor import HyperellipticCurveSmoothModel + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) f, h = self._hyperelliptic_polynomials fR = f.change_ring(R) @@ -779,7 +781,9 @@ def jacobian(self): """ Returns the Jacobian of the hyperelliptic curve. """ - from jacobian_generic import HyperellipticJacobian_generic + from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_generic import ( + HyperellipticJacobian_generic, + ) return HyperellipticJacobian_generic(self) @@ -976,7 +980,9 @@ def odd_degree_model(self): sage: HyperellipticCurveSmoothModel(x^5 + 1).odd_degree_model() Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + 1 """ - from hyperelliptic_constructor import HyperellipticCurveSmoothModel + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) f, h = self._hyperelliptic_polynomials if f.base_ring().characteristic() == 2: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index aab4bbf961c..103a0bdf4cc 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -1090,8 +1090,9 @@ def curve_over_ram_extn(self, deg): - Jennifer Balakrishnan """ - # TODO: fix this import for sage - from hyperelliptic_constructor import HyperellipticCurveSmoothModel + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) K = self.base_ring() p = K.prime() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py index 0c8ea60a89d..790355662c6 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -121,7 +121,7 @@ def lfun_genus2(C): ValueError: curve must be hyperelliptic of genus 2 """ from sage.libs.pari import pari - import hyperelliptic_g2 + from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_g2 if not isinstance(C, hyperelliptic_g2.HyperellipticCurveSmoothModel_g2): raise ValueError("curve must be hyperelliptic of genus 2") diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py index 8be6e9424da..3c4a3d9da13 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py @@ -111,8 +111,7 @@ def __init__(self, parent, p0, p1, p2, check=True): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing, SpecialCubicQuotientRingElement + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing, SpecialCubicQuotientRingElement sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: SpecialCubicQuotientRingElement(R, 2, 3, 4) (2) + (3)*x + (4)*x^2 @@ -120,8 +119,7 @@ def __init__(self, parent, p0, p1, p2, check=True): TESTS:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: TestSuite(R).run() sage: p = R.create_element(t, t^2 - 2, 3) @@ -152,8 +150,7 @@ def coeffs(self): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: p = R.create_element(t, t^2 - 2, 3) sage: p.coeffs() @@ -171,8 +168,7 @@ def __bool__(self) -> bool: EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() sage: not x @@ -204,8 +200,7 @@ def _repr_(self) -> str: EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() sage: x + T*x - 2*T^2 @@ -218,8 +213,7 @@ def _latex_(self) -> str: EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() sage: f = x + T*x - 2*T^2 @@ -235,8 +229,7 @@ def _add_(self, other): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: f = R.create_element(2, t, t^2 - 3) sage: g = R.create_element(3 + t, -t, t) @@ -257,8 +250,7 @@ def _sub_(self, other): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: f = R.create_element(2, t, t^2 - 3) sage: g = R.create_element(3 + t, -t, t) @@ -281,8 +273,7 @@ def shift(self, n): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: f = R.create_element(2, t, t^2 - 3) sage: f @@ -314,8 +305,7 @@ def scalar_multiply(self, scalar): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() sage: f = R.create_element(2, t, t^2 - 3) @@ -343,8 +333,7 @@ def square(self): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() @@ -363,8 +352,7 @@ def _mul_(self, other): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() @@ -465,8 +453,7 @@ class SpecialCubicQuotientRing(UniqueRepresentation, Parent): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: R SpecialCubicQuotientRing over Ring of integers modulo 125 @@ -534,8 +521,7 @@ def __init__(self, Q, laurent_series=False): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: R SpecialCubicQuotientRing over Ring of integers modulo 125 @@ -543,8 +529,7 @@ def __init__(self, Q, laurent_series=False): :: - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 + 2*t^2 - t + B(1/4)) Traceback (most recent call last): ... @@ -553,8 +538,7 @@ def __init__(self, Q, laurent_series=False): :: sage: B. = PolynomialRing(Integers(10)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + 1) Traceback (most recent call last): ... @@ -601,8 +585,7 @@ def _repr_(self) -> str: EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: R SpecialCubicQuotientRing over Ring of integers modulo 125 @@ -620,8 +603,7 @@ def poly_ring(self): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: R.poly_ring() Univariate Polynomial Ring in T over Ring of integers modulo 125 @@ -641,8 +623,7 @@ def gens(self): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: x, T = R.gens() sage: x @@ -673,8 +654,7 @@ def _element_constructor_(self, *args, check=True): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: A, z = R.poly_ring().objgen() sage: R.create_element(z^2, z+1, 3) # indirect doctest @@ -697,8 +677,7 @@ def one(self): EXAMPLES:: sage: B. = PolynomialRing(Integers(125)) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: R.one() (1) + (0)*x + (0)*x^2 @@ -714,8 +693,7 @@ def _coerce_map_from_(self, R): sage: Z125 = Integers(125) sage: B. = PolynomialRing(Z125) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialCubicQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) sage: R.has_coerce_map_from(Z125) True @@ -735,8 +713,7 @@ def transpose_list(input) -> list[list]: EXAMPLES:: - sage: # TODO change import for sage - sage: from monsky_washnitzer import transpose_list + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import transpose_list sage: L = [[1, 2], [3, 4], [5, 6]] sage: transpose_list(L) [[1, 3, 5], [2, 4, 6]] @@ -767,8 +744,7 @@ def helper_matrix(Q): EXAMPLES:: sage: t = polygen(QQ,'t') - sage: # TODO change import for sage - sage: from monsky_washnitzer import helper_matrix + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import helper_matrix sage: helper_matrix(t**3-4*t-691) [ 64/12891731 -16584/12891731 4297329/12891731] [ 6219/12891731 -32/12891731 8292/12891731] @@ -809,8 +785,7 @@ def lift(x): EXAMPLES:: sage: # needs sage.rings.padics - sage: # TODO change import for sage - sage: from monsky_washnitzer import lift + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import lift sage: l = lift(Qp(13)(131)); l 131 sage: l.parent() @@ -1189,8 +1164,7 @@ def frobenius_expansion_by_newton(Q, p, M): EXAMPLES:: - sage: # TODO change import for sage - sage: from monsky_washnitzer import frobenius_expansion_by_newton + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import frobenius_expansion_by_newton sage: R. = Integers(5^3)['x'] sage: Q = x^3 - x + R(1/4) sage: frobenius_expansion_by_newton(Q,5,3) @@ -1380,8 +1354,7 @@ def frobenius_expansion_by_series(Q, p, M): EXAMPLES:: - sage: # TODO change import for sage - sage: from monsky_washnitzer import frobenius_expansion_by_series + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import frobenius_expansion_by_series sage: R. = Integers(5^3)['x'] sage: Q = x^3 - x + R(1/4) sage: frobenius_expansion_by_series(Q,5,3) # needs sage.libs.pari @@ -1461,8 +1434,7 @@ def adjusted_prec(p, prec): EXAMPLES:: - sage: # TODO change import for sage - sage: from monsky_washnitzer import adjusted_prec + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import adjusted_prec sage: adjusted_prec(5,2) 3 """ @@ -2477,8 +2449,7 @@ def __init__(self, Q, R=None, invert_y=True): sage: R. = QQ['x'] sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialHyperellipticQuotientRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialHyperellipticQuotientRing sage: HQR = SpecialHyperellipticQuotientRing(E) sage: TestSuite(HQR).run() # needs sage.rings.real_interval_field @@ -2900,9 +2871,8 @@ def monsky_washnitzer(self): sage: R. = QQ['x'] sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) sage: x,y = E.monsky_washnitzer_gens() - sage: # TODO fix me when in sage sage: type(x.parent().monsky_washnitzer()) - + """ return self._monsky_washnitzer @@ -3639,8 +3609,7 @@ def __init__(self, base_ring): sage: R. = QQ['x'] sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) - sage: # TODO change import for sage - sage: from monsky_washnitzer import SpecialHyperellipticQuotientRing, MonskyWashnitzerDifferentialRing + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialHyperellipticQuotientRing, MonskyWashnitzerDifferentialRing sage: S = SpecialHyperellipticQuotientRing(E) sage: DR = MonskyWashnitzerDifferentialRing(S) sage: TestSuite(DR).run() # needs sage.rings.real_interval_field From 1f2e5ff41ee84ce774b4a410ee8bd57a0ddd2cb8 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 4 Dec 2024 16:03:31 +0000 Subject: [PATCH 05/56] fix some monsky_washnitzer imports --- .../hyperelliptic_generic.py | 4 ++-- .../hyperelliptic_padic_field.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 0b6ea1591c9..e84d6b2d1ab 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -1059,7 +1059,7 @@ def _magma_init_(self, magma): # ------------------------------------------- def monsky_washnitzer_gens(self): - from sage.schemes.hyperelliptic_curves import monsky_washnitzer + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self) return S.gens() @@ -1077,7 +1077,7 @@ def invariant_differential(self): 1 dx/2y """ - from sage.schemes.hyperelliptic_curves import monsky_washnitzer as m_w + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer as m_w S = m_w.SpecialHyperellipticQuotientRing(self) MW = m_w.MonskyWashnitzerDifferentialRing(S) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 103a0bdf4cc..0e73666f5a1 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -598,7 +598,7 @@ def coleman_integrals_on_basis(self, P, Q, algorithm=None): raise NotImplementedError from sage.misc.profiler import Profiler - from sage.schemes.hyperelliptic_curves import monsky_washnitzer + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer prof = Profiler() prof("setup") @@ -866,7 +866,7 @@ def coleman_integral(self, w, P, Q, algorithm="None"): """ # TODO: exceptions for general curve form # TODO: implement Jacobians and show the relationship directly - from sage.schemes.hyperelliptic_curves import monsky_washnitzer + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer K = self.base_ring() prec = K.precision_cap() @@ -1275,7 +1275,7 @@ def S_to_Q(self, S, Q): FS = self.frobenius(S) FS = (FS[0], FS[1]) FQ = self.frobenius(Q) - from sage.schemes.hyperelliptic_curves import monsky_washnitzer + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer try: M_frob, forms = self._frob_calc @@ -1362,7 +1362,7 @@ def coleman_integral_S_to_Q(self, w, S, Q): - Jennifer Balakrishnan """ - from sage.schemes.hyperelliptic_curves import monsky_washnitzer + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer K = self.base_ring() R = monsky_washnitzer.SpecialHyperellipticQuotientRing(self, K) From c5e077b4ba84de093f1421c6a3804de24cf18a1e Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 4 Dec 2024 16:32:47 +0000 Subject: [PATCH 06/56] fix all doctest errors --- .../hyperelliptic_curves_smooth_model/all.py | 3 +++ .../hyperelliptic_constructor.py | 8 ++++---- .../hyperelliptic_finite_field.py | 20 +++++++++++-------- .../hyperelliptic_padic_field.py | 2 +- .../hyperelliptic_rational_field.py | 3 ++- .../jacobian_generic.py | 2 +- .../monsky_washnitzer.py | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py index 92453c6b48e..9a42062fe7c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -2,3 +2,6 @@ HyperellipticCurveSmoothModel, ) +from sage.misc.lazy_import import lazy_import +lazy_import('sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_space', 'WeightedProjectiveSpace') +lazy_import('sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_curve', 'WeightedProjectiveCurve') diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 7c25c8aa5f9..18a6375212e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -42,7 +42,7 @@ sage: K. = QQ.extension(x^2+x-1) sage: HK = H.change_ring(K) sage: HK.points_at_infinity() - [[1 : alpha : 0], [1 : -alpha - 1 : 0]] + [(1 : alpha : 0), (1 : -alpha - 1 : 0)] sage: HK.is_split() True @@ -59,7 +59,7 @@ sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) sage: type(H5) - + The input polynomials need not be monic:: sage: R. = QQ[] @@ -183,7 +183,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): sage: K. = QQ.extension(x^2+x-1) sage: HK = H.change_ring(K) sage: HK.points_at_infinity() - [[1 : alpha : 0], [1 : -alpha - 1 : 0]] + [(1 : alpha : 0), (1 : -alpha - 1 : 0)] sage: HK.is_split() True @@ -200,7 +200,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) sage: type(H5) - + The input polynomials need not be monic:: sage: R. = QQ[] diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index 78308a8dacf..46153a2a0a7 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -35,7 +35,7 @@ def random_point(self): sage: C.random_point() # random (4 : 1 : 1) sage: type(C.random_point()) - + """ k = self.base_ring() n = 2 * k.order() + 1 @@ -101,15 +101,19 @@ def points(self): sage: C.points() [(1 : 1 : 0), (1 : 6 : 0), (1 : 0 : 1), (2 : 0 : 1), (3 : 0 : 1), (4 : 0 : 1), (5 : 0 : 1), (6 : 0 : 1)] + sage: C.points_at_infinity() + [(1 : 1 : 0), (1 : 6 : 0)] - Conics are allowed (the issue reported at :issue:`11800` - has been resolved):: + This method works even for hyperelliptic curves with no rational points + at infinity:: - sage: R. = GF(7)[] - sage: H = HyperellipticCurveSmoothModel(3*x^2 + 5*x + 1) - sage: H.points() - [(0 : 1 : 1), (0 : 6 : 1), (1 : 3 : 1), (1 : 4 : 1), (2 : 3 : 1), - (2 : 4 : 1), (3 : 1 : 1), (3 : 6 : 1)] + sage: C = HyperellipticCurveSmoothModel(3 * x^6 - 1) + sage: C.points() + [(1 : 3 : 1), (1 : 4 : 1), (2 : 3 : 1), (2 : 4 : 1), (3 : 3 : 1), + (3 : 4 : 1), (4 : 3 : 1), (4 : 4 : 1), (5 : 3 : 1), (5 : 4 : 1), + (6 : 3 : 1), (6 : 4 : 1)] + sage: C.points_at_infinity() + [] .. SEEALSO:: :meth:`rational_points_iterator` """ diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 0e73666f5a1..68e79bb604e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -797,7 +797,7 @@ def coleman_integral(self, w, P, Q, algorithm="None"): sage: H = HyperellipticCurveSmoothModel(x*(x-1)*(x+9)) sage: K = Qp(7,10) sage: HK = H.change_ring(K) - sage: import monsky_washnitzer as mw + sage: from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer as mw sage: M_frob, forms = mw.matrix_of_frobenius_hyperelliptic(HK) sage: w = HK.invariant_differential() sage: x,y = HK.monsky_washnitzer_gens() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py index 790355662c6..fb8eea5a43e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -46,7 +46,7 @@ def matrix_of_frobenius(self, p, prec=20): [ 2*3 + O(3^2) O(3^2) O(3^2) 3^-1 + O(3)] [ O(3^2) O(3^2) 3 + O(3^2) O(3^2)] """ - import monsky_washnitzer # TODO fix this + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer if isinstance(p, (sage.rings.abc.pAdicField, sage.rings.abc.pAdicRing)): K = p @@ -99,6 +99,7 @@ def lfun_genus2(C): EXAMPLES:: sage: from sage.lfunctions.pari import LFunction + sage: from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_rational_field import lfun_genus2 sage: x = polygen(QQ, 'x') sage: C = HyperellipticCurveSmoothModel(x^5 + x + 1) sage: L = LFunction(lfun_genus2(C)) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index 15b7ed91b08..1fcae854cac 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -75,7 +75,7 @@ sage: D2 = J(P); D2 (x, 0 : 0) sage: P0 = H.distinguished_point(); P0 - [1 : 0 : 0] + (1 : 0 : 0) sage: D2 == J(P, P0) True diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py index 3c4a3d9da13..cfffcf048eb 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py @@ -2484,7 +2484,7 @@ def __init__(self, Q, R=None, invert_y=True): if not hasattr(self, "_curve"): if self._Q.degree() == 3: ainvs = [0, self._Q[2], 0, self._Q[1], self._Q[0]] - self._curve = EllipticCurve(ainvs, check_squarefree=R.is_field()) + self._curve = EllipticCurve(ainvs) else: self._curve = HyperellipticCurveSmoothModel( self._Q, check_squarefree=R.is_field() From e16e9f522a418a62f902c9ba2fe3e8e9defeba88 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 5 Dec 2024 11:15:26 +0000 Subject: [PATCH 07/56] add long time warning to doctest --- .../hyperelliptic_curves_smooth_model/hyperelliptic_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index e84d6b2d1ab..5ff626e9830 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -860,7 +860,7 @@ def rational_points(self, **kwds): `169.a.169.1 `_:: sage: C = HyperellipticCurveSmoothModel(R([0, 0, 0, 0, 1, 1]), R([1, 1, 0, 1])) - sage: C.rational_points(bound=10) + sage: C.rational_points(bound=10) # long time (6s) [(1 : 0 : 0), (1 : -1 : 0), (-1 : 0 : 1), (-1 : 1 : 1), (0 : -1 : 1), (0 : 0 : 1)] An example over a number field:: From 79e3692b6e6cb07ddfbb4dbe70e6d6841795873c Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 5 Dec 2024 11:21:07 +0000 Subject: [PATCH 08/56] run linting checks --- .../hyperelliptic_curves_smooth_model/hyperelliptic_generic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 5ff626e9830..94fb16b9326 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -765,7 +765,6 @@ def distinguished_point(self): raise ValueError("distinguished point not found") - def set_distinguished_point(self, P0): """ Change the distinguished point of the hyperelliptic curve to P0. From 54619c433eab0c51fe94ce204d114eb74b274af9 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 5 Dec 2024 11:28:29 +0000 Subject: [PATCH 09/56] remove is_HyperellipticCurveSmoothModel as it's not really a modern solution --- .../hyperelliptic_generic.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 94fb16b9326..588a591462b 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -15,14 +15,6 @@ ) -def is_HyperellipticCurveSmoothModel(C): - """ - TODO: we probably don't want this at all, this way of working - is the "old" sagemath way of doing things. - """ - return isinstance(C, HyperellipticCurveSmoothModel_generic) - - class HyperellipticCurveSmoothModel_generic(WeightedProjectiveCurve): def __init__(self, defining_polynomial, f, h, genus): self._genus = genus From 5a8daf4e29c0124bcfc4cd0758de4a81f47b1fc6 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Mon, 2 Dec 2024 21:53:33 +0000 Subject: [PATCH 10/56] start porting code ready to be integrated into sagemath Co-Authored-By: grhkm21 <83517584+grhkm21@users.noreply.github.com> Co-Authored-By: sabrinakunzweiler <22136626+sabrinakunzweiler@users.noreply.github.com> --- src/sage/schemes/all.py | 2 + .../hyperelliptic_curves_smooth_model/all.py | 7 + .../hyperelliptic_constructor.py | 341 ++ .../hyperelliptic_finite_field.py | 1706 +++++++ .../hyperelliptic_g2.py | 192 + .../hyperelliptic_generic.py | 1458 ++++++ .../hyperelliptic_padic_field.py | 1426 ++++++ .../hyperelliptic_rational_field.py | 130 + .../invariants.py | 434 ++ .../jacobian_g2_generic.py | 30 + .../jacobian_g2_homset_inert.py | 12 + .../jacobian_g2_homset_ramified.py | 12 + .../jacobian_g2_homset_split.py | 12 + .../jacobian_generic.py | 230 + .../jacobian_homset_generic.py | 686 +++ .../jacobian_homset_inert.py | 24 + .../jacobian_homset_ramified.py | 12 + .../jacobian_homset_split.py | 269 ++ .../jacobian_morphism.py | 368 ++ .../meson.build | 31 + .../mestre.py | 377 ++ .../monsky_washnitzer.py | 3968 +++++++++++++++++ .../weighted_projective_curve.py | 12 + .../weighted_projective_homset.py | 46 + .../weighted_projective_point.py | 390 ++ .../weighted_projective_space.py | 371 ++ src/sage/schemes/meson.build | 1 + 27 files changed, 12547 insertions(+) create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/all.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py create mode 100644 src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py diff --git a/src/sage/schemes/all.py b/src/sage/schemes/all.py index f5126a0dee1..90ca2a53bd3 100644 --- a/src/sage/schemes/all.py +++ b/src/sage/schemes/all.py @@ -45,3 +45,5 @@ from sage.schemes.cyclic_covers.all import * from sage.schemes.berkovich.all import * + +from sage.schemes.hyperelliptic_curves_smooth_model.all import * diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py new file mode 100644 index 00000000000..189a6c54a3f --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -0,0 +1,7 @@ +from sage.misc.lazy_import import lazy_import +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, +) + +lazy_import('sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_space', 'WeightedProjectiveSpace') +lazy_import('sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_curve', 'WeightedProjectiveCurve') diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py new file mode 100644 index 00000000000..18a6375212e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -0,0 +1,341 @@ +r""" + Constructor for hyperelliptic curves using the smooth model + + In Sage, a hyperelliptic curve of genus `g` is always + specified by an (affine) equation in Weierstrass form + + .. MATH:: + + y^2 + h(x) y = f(x), + + for some polynomials `h` and `f`. This defines a smooth + model in weighted projective space `\PP(1 : g + 1 : 1)` + + .. MATH:: + Y^2 + H(X,Z) Y = F(X,Z), + + where `H` is the degree `g + 1` homogenization of `h`, + and `F` is the degree `2 g + 2` homogenization of `f`. + + There are either 0, 1 or 2 points at infinity (`Z=0`), + in which case we say that the hyperelliptic curve is + inert, ramified or split, respectively. + + + EXAMPLES: + + We create a hyperelliptic curve over the rationals:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 + + This hyperelliptic curve has no points at infinity, + i.e. `H` is inert:: + sage: H.points_at_infinity() + [] + sage: H.is_inert() + True + + We can extend the base field to obtain a hyperelliptic curve + with two points at infinity:: + sage: K. = QQ.extension(x^2+x-1) + sage: HK = H.change_ring(K) + sage: HK.points_at_infinity() + [(1 : alpha : 0), (1 : -alpha - 1 : 0)] + sage: HK.is_split() + True + + The construction of hyperelliptic curves is supported over different fields. + The correct class is chosen automatically:: + sage: F = FiniteField(13) + sage: S. = PolynomialRing(F) + sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF + Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^3 + x)*y = x^5 + x^4 + x^3 + x^2 + x + 1 + sage: type(HF) + + sage: Q5 = Qp(5,10) + sage: T. = Q5[] + sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 + Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) + sage: type(H5) + + + The input polynomials need not be monic:: + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(3*x^5+1) + Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 + + The polynomials f and h need to define a smooth curve of genus at + least one. In particular polynomials defining elliptic curves are + allowed as input. + sage: E = HyperellipticCurveSmoothModel(x^3+1) + sage: E.genus() + 1 + sage: HyperellipticCurveSmoothModel(x) + Traceback (most recent call last): + ... + ValueError: arguments f = x and h = 0 must define a curve of genus at least one. + + The following polynomials define a singular curve and are + not allowed as input:: + sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) + Traceback (most recent call last): + ... + ValueError: not a hyperelliptic curve: singularity in the provided affine patch + +Adapted from /hyperelliptic/constructor.py + +AUTHORS: + +- David Kohel (2006): initial version +- Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# 2019 Anna Somoza +# 2024 Sabrina Kunzweiler, Gareth Ma, Giacomo Pope +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.categories.finite_fields import FiniteFields +from sage.rings.abc import pAdicField +from sage.rings.integer import Integer +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import RationalField +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_finite_field import ( + HyperellipticCurveSmoothModel_finite_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_g2 import ( + HyperellipticCurveSmoothModel_g2, + HyperellipticCurveSmoothModel_g2_finite_field, + HyperellipticCurveSmoothModel_g2_padic_field, + HyperellipticCurveSmoothModel_g2_rational_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic import ( + HyperellipticCurveSmoothModel_generic, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_padic_field import ( + HyperellipticCurveSmoothModel_padic_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_rational_field import ( + HyperellipticCurveSmoothModel_rational_field, +) + +""" +TODO: + +- We currently cannot support the construction of curves over rings + +""" + + +def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): + r""" + Constructor function for creating a hyperelliptic curve with + smooth model with polynomials f, h. + + In Sage, a hyperelliptic curve of genus `g` is always + specified by an (affine) equation in Weierstrass form + + .. MATH:: + + y^2 + h(x) y = f(x), + + for some polynomials `h` and `f`. This defines a smooth + model in weighted projective space `\PP(1 : g + 1 : 1)` + + .. MATH:: + Y^2 + H(X,Z) Y = F(X,Z), + + where `H` is the degree `g + 1` homogenization of `h`, + and `F` is the degree `2 g + 2` homogenization of `f`. + + INPUT: + + - ``f`` -- polynomial + + - ``h`` (default: ``0``) -- polynomial + + - ``check_squarefree`` (default: ``True``) -- test if + the input defines a hyperelliptic curve + + EXAMPLES: + + We create a hyperelliptic curve over the rationals:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 + + This hyperelliptic curve has no points at infinity, + i.e. `H` is inert:: + sage: H.points_at_infinity() + [] + sage: H.is_inert() + True + + We can extend the base field to obtain a hyperelliptic curve + with two points at infinity:: + sage: K. = QQ.extension(x^2+x-1) + sage: HK = H.change_ring(K) + sage: HK.points_at_infinity() + [(1 : alpha : 0), (1 : -alpha - 1 : 0)] + sage: HK.is_split() + True + + The construction of hyperelliptic curves is supported over different fields. + The correct class is chosen automatically:: + sage: F = FiniteField(13) + sage: S. = PolynomialRing(F) + sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF + Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^3 + x)*y = x^5 + x^4 + x^3 + x^2 + x + 1 + sage: type(HF) + + sage: Q5 = Qp(5,10) + sage: T. = Q5[] + sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 + Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) + sage: type(H5) + + + The input polynomials need not be monic:: + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(3*x^5+1) + Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 + + The polynomials f and h need to define a smooth curve of genus at + least one. In particular polynomials defining elliptic curves are + allowed as input. + sage: E = HyperellipticCurveSmoothModel(x^3+1) + sage: E.genus() + 1 + sage: HyperellipticCurveSmoothModel(x) + Traceback (most recent call last): + ... + ValueError: arguments f = x and h = 0 must define a curve of genus at least one. + + The following polynomials define a singular curve and are + not allowed as input:: + sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) + Traceback (most recent call last): + ... + ValueError: not a hyperelliptic curve: singularity in the provided affine patch + + """ + + # --------------------------- + # Internal Helper functions + # --------------------------- + + def __genus(f, h): + """ + Helper function to compute the genus of a hyperelliptic curve + defined by `y^2 + h(x)y = f(x)`. + """ + # Some classes still have issues with degrees returning `int` + # rather than Sage Integer types + df = Integer(f.degree()) + dh_2 = 2 * Integer(h.degree()) + if dh_2 < df: + return (df - 1) // 2 + return (dh_2 - 1) // 2 + + def __check_no_affine_singularities(f, h): + """ + Helper function which determines whether there are any + affine singularities in the curve `y^2 + h(x)y = f(x)`. + """ + if f.base_ring().characteristic() == 2: + if h.is_zero(): + return False + elif h.is_constant(): + return True + return h.gcd(f.derivative() ** 2 - f * h.derivative() ** 2).is_one() + + if h.is_zero(): + return f.gcd(f.derivative()).is_one() + + g1 = h**2 + 4 * f + g2 = 2 * f.derivative() + h * h.derivative() + return g1.gcd(g2).is_one() + + def __defining_polynomial(f, h): + """ + Compute the (homogenised weighted projective) defining polynomial of + the hyperelliptic curve. + """ + X, Y, Z = PolynomialRing(f.base_ring(), names="X, Y, Z").gens() + + # Some classes still have issues with degrees returning `int` + d = max(Integer(h.degree()), (Integer(f.degree()) + 1) // 2) + F = sum(f[i] * X**i * Z ** (2 * d - i) for i in range(2 * d + 1)) + + if h.is_zero(): + G = Y**2 - F + else: + H = sum(h[i] * X**i * Z ** (d - i) for i in range(d + 1)) + G = Y**2 + H * Y - F + + return G + + # ------------------------------------------- + # Typechecking and projective model creation + # ------------------------------------------- + + # Check the polynomials are of the right type + F = h**2 + 4 * f + if not isinstance(F, Polynomial): + raise TypeError(f"arguments {f = } and {h = } must be polynomials") + + # Store the hyperelliptic polynomials as the correct type + polynomial_ring = F.parent() + base_ring = F.base_ring() + f = polynomial_ring(f) + h = polynomial_ring(h) + + # Ensure that there are no affine singular points + if check_squarefree and not __check_no_affine_singularities(f, h): + raise ValueError("singularity in the provided affine patch") + + # Compute the genus of the curve from f, h + genus = __genus(f, h) + if genus == 0: + raise ValueError(f"arguments {f = } and {h = } must define a curve of genus at least one.") + + # Compute the smooth model for the hyperelliptic curve + # using a weighted projective space (via Toric Variety) + defining_polynomial = __defining_polynomial(f, h) + + # ----------------------- + # Class selection + # ----------------------- + + # Special class for finite fields + if base_ring in FiniteFields(): + if genus == 2: + cls = HyperellipticCurveSmoothModel_g2_finite_field + else: + cls = HyperellipticCurveSmoothModel_finite_field + # Special class for pAdic fields + elif isinstance(base_ring, pAdicField): + if genus == 2: + cls = HyperellipticCurveSmoothModel_g2_padic_field + else: + cls = HyperellipticCurveSmoothModel_padic_field + # Special class for rational fields + elif isinstance(base_ring, RationalField): + if genus == 2: + cls = HyperellipticCurveSmoothModel_g2_rational_field + else: + cls = HyperellipticCurveSmoothModel_rational_field + # Default class for all other fields + elif genus == 2: + cls = HyperellipticCurveSmoothModel_g2 + else: + cls = HyperellipticCurveSmoothModel_generic + + return cls(defining_polynomial, f, h, genus) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py new file mode 100644 index 00000000000..46153a2a0a7 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -0,0 +1,1706 @@ +from sage.arith.misc import binomial +from sage.libs.pari.all import pari +from sage.matrix.constructor import identity_matrix, matrix +from sage.misc.cachefunc import cached_method +from sage.misc.functional import rank +from sage.misc.prandom import choice +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF +from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ +from sage.rings.real_mpfr import RR +from sage.schemes.hyperelliptic_curves.hypellfrob import hypellfrob +from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic + + +class HyperellipticCurveSmoothModel_finite_field( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + """ + TODO: write some examples of this class + """ + + def __init__(self, projective_model, f, h, genus): + super().__init__(projective_model, f, h, genus) + + def random_point(self): + """ + Return a random point on this hyperelliptic curve, uniformly chosen + among all rational points. + + EXAMPLES:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: C.random_point() # random + (4 : 1 : 1) + sage: type(C.random_point()) + + """ + k = self.base_ring() + 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 and not self.is_inert(): + # Deal with that there is more than one point at infinity + return choice(self.points_at_infinity()) + v = self.lift_x(k.random_element(), all=True) + try: + return v[i % 2] + except IndexError: + pass + + def rational_points_iterator(self): + """ + Return all the points on this hyperelliptic curve as an iterator. + + EXAMPLES:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: list(C.rational_points_iterator()) + [(1 : 0 : 0), (2 : 2 : 1), (2 : 5 : 1), (3 : 0 : 1), (4 : 1 : 1), + (4 : 6 : 1), (5 : 0 : 1), (6 : 2 : 1), (6 : 5 : 1)] + sage: _ == C.points() + True + + .. SEEALSO:: :meth:`points` + """ + # TODO: this is very silly but works + yield from self.points_at_infinity() + for x in self.base_ring(): + yield from self.lift_x(x, all=True) + + @cached_method + def points(self): + """ + Return all the points on this hyperelliptic curve. + + EXAMPLES:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: C.points() + [(1 : 0 : 0), (2 : 2 : 1), (2 : 5 : 1), (3 : 0 : 1), (4 : 1 : 1), + (4 : 6 : 1), (5 : 0 : 1), (6 : 2 : 1), (6 : 5 : 1)] + + :: + + sage: x = polygen(GF(121, 'a')) + sage: C = HyperellipticCurveSmoothModel(x^5 + x - 1, x^2 + 2) + sage: len(C.points()) + 122 + + As we use the smooth model we also can work with the case + of an even degree model:: + + sage: x = polygen(GF(7)) + sage: C = HyperellipticCurveSmoothModel(x^6 - 1) + sage: C.points() + [(1 : 1 : 0), (1 : 6 : 0), (1 : 0 : 1), (2 : 0 : 1), (3 : 0 : 1), + (4 : 0 : 1), (5 : 0 : 1), (6 : 0 : 1)] + sage: C.points_at_infinity() + [(1 : 1 : 0), (1 : 6 : 0)] + + This method works even for hyperelliptic curves with no rational points + at infinity:: + + sage: C = HyperellipticCurveSmoothModel(3 * x^6 - 1) + sage: C.points() + [(1 : 3 : 1), (1 : 4 : 1), (2 : 3 : 1), (2 : 4 : 1), (3 : 3 : 1), + (3 : 4 : 1), (4 : 3 : 1), (4 : 4 : 1), (5 : 3 : 1), (5 : 4 : 1), + (6 : 3 : 1), (6 : 4 : 1)] + sage: C.points_at_infinity() + [] + + .. SEEALSO:: :meth:`rational_points_iterator` + """ + return list(self.rational_points_iterator()) + + rational_points = points + + def count_points_matrix_traces(self, n=1, M=None, N=None): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field by computing traces of powers of the frobenius + matrix. + This requires less `p`-adic precision than computing the charpoly + of the matrix when `n < g` where `g` is the genus of the curve. + + EXAMPLES:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^19 + t + 1) + sage: H.count_points_matrix_traces(3) + [49491, 2500024375, 124992509154249] + + TESTS: + + Check that :issue:`18831` is fixed:: + + sage: R. = PolynomialRing(GF(11)) + sage: H = HyperellipticCurveSmoothModel(t^5 - t + 1) + sage: H.count_points_matrix_traces() + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 15 + """ + if N is None: + N = self._frobenius_coefficient_bound_traces(n=n) + + if M is None: + M = self.frobenius_matrix(N=N) + + K = self.base_ring() + p = K.characteristic() + q = K.cardinality() + ppow = p**N + + t = [] + Mpow = 1 + for i in range(n): + Mpow *= M + t.append(Mpow.trace()) + + t = [x.lift() for x in t] + t = [x if 2 * x < ppow else x - ppow for x in t] + + return [q ** (i + 1) + 1 - t[i] for i in range(n)] + + def count_points_frobenius_polynomial(self, n=1, f=None): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field by computing the frobenius polynomial. + + EXAMPLES:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^19 + t + 1) + + The following computation takes a long time as the complete + characteristic polynomial of the frobenius is computed:: + + sage: H.count_points_frobenius_polynomial(3) # long time, 20s on a Corei7 (when computed before the following test of course) + [49491, 2500024375, 124992509154249] + + As the polynomial is cached, further computations of number of points + are really fast:: + + sage: H.count_points_frobenius_polynomial(19) # long time, because of the previous test + [49491, + 2500024375, + 124992509154249, + 6249500007135192947, + 312468751250758776051811, + 15623125093747382662737313867, + 781140631562281338861289572576257, + 39056250437482500417107992413002794587, + 1952773465623687539373429411200893147181079, + 97636720507718753281169963459063147221761552935, + 4881738388665429945305281187129778704058864736771824, + 244082037694882831835318764490138139735446240036293092851, + 12203857802706446708934102903106811520015567632046432103159713, + 610180686277519628999996211052002771035439565767719719151141201339, + 30508424133189703930370810556389262704405225546438978173388673620145499, + 1525390698235352006814610157008906752699329454643826047826098161898351623931, + 76268009521069364988723693240288328729528917832735078791261015331201838856825193, + 3813324208043947180071195938321176148147244128062172555558715783649006587868272993991, + 190662397077989315056379725720120486231213267083935859751911720230901597698389839098903847] + """ + if f is None: + f = self.frobenius_polynomial() + + q = self.base_ring().cardinality() + S = PowerSeriesRing(QQ, default_prec=n + 1, names="t") + frev = f.reverse() + # the coefficients() method of power series only returns + # non-zero coefficients so let us use the list() method but + # this does not work for zero which gives the empty list + flog = S(frev).log() + return [q ** (i + 1) + 1 + ZZ((i + 1) * flog[i + 1]) for i in range(n)] + + def count_points_exhaustive(self, n=1, naive=False): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field by exhaustive search if `n` if smaller than `g`, + the genus of the curve, and by computing the frobenius polynomial + after performing exhaustive search on the first `g` extensions if + `n > g` (unless ``naive == True``). + + EXAMPLES:: + + sage: K = GF(5) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.count_points_exhaustive(n=5) + [9, 27, 108, 675, 3069] + + When `n > g`, the frobenius polynomial is computed from the numbers + of points of the curve over the first `g` extension, so that computing + the number of points on extensions of degree `n > g` is not much more + expensive than for `n == g`:: + + sage: H.count_points_exhaustive(n=15) + [9, + 27, + 108, + 675, + 3069, + 16302, + 78633, + 389475, + 1954044, + 9768627, + 48814533, + 244072650, + 1220693769, + 6103414827, + 30517927308] + + This behavior can be disabled by passing ``naive=True``:: + + sage: H.count_points_exhaustive(n=6, naive=True) # long time, 7s on a Corei7 + [9, 27, 108, 675, 3069, 16302] + """ + g = self.genus() + a = [ + self.cardinality_exhaustive(extension_degree=i) + for i in range(1, min(n, g) + 1) + ] + + if n <= g: + return a + + if naive: + a.extend( + [ + self.cardinality_exhaustive(extension_degree=i) + for i in range(g + 1, n + 1) + ] + ) + + # let's not be too naive and compute the frobenius polynomial + f = self.frobenius_polynomial_cardinalities(a=a) + return self.count_points_frobenius_polynomial(n=n, f=f) + + def count_points_hypellfrob(self, n=1, N=None, algorithm=None): + r""" + Count the number of points on the curve over the first `n` extensions + of the base field using the ``hypellfrob`` program. + + This only supports prime fields of large enough characteristic. + + EXAMPLES:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^21 + 3*t^5 + 5) + sage: H.count_points_hypellfrob() + [49804] + sage: H.count_points_hypellfrob(2) + [49804, 2499799038] + + sage: K = GF(2**7-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^11 + 3*t^5 + 5) + sage: H.count_points_hypellfrob() + [127] + sage: H.count_points_hypellfrob(n=5) + [127, 16335, 2045701, 260134299, 33038098487] + + sage: K = GF(2**7-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^13 + 3*t^5 + 5) + sage: H.count_points(n=6) + [112, 16360, 2045356, 260199160, 33038302802, 4195868633548] + + The base field should be prime:: + + sage: K. = GF(19**10) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + (z+1)*t^5 + 1) + sage: H.count_points_hypellfrob() + Traceback (most recent call last): + ... + ValueError: hypellfrob does not support non-prime fields + + and the characteristic should be large enough:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.count_points_hypellfrob() + Traceback (most recent call last): + ... + ValueError: p=7 should be greater than (2*g+1)(2*N-1)=27 + """ + K = self.base_ring() + e = K.degree() + + if e != 1: + raise ValueError("hypellfrob does not support non-prime fields") + + # K is a prime field + p = K.cardinality() + g = self.genus() + + if algorithm is None: + if n < g: + algorithm = "traces" + else: + algorithm = "charpoly" + + if N is None: + if algorithm == "traces": + N = self._frobenius_coefficient_bound_traces(n) + elif algorithm == "charpoly": + N = self._frobenius_coefficient_bound_charpoly() + else: + raise ValueError("Unknown algorithm") + + if p <= (2 * g + 1) * (2 * N - 1): + raise ValueError( + "p=%d should be greater than (2*g+1)(2*N-1)=%d" + % (p, (2 * g + 1) * (2 * N - 1)) + ) + + if algorithm == "traces": + M = self.frobenius_matrix(N=N, algorithm="hypellfrob") + return self.count_points_matrix_traces(n=n, M=M, N=N) + elif algorithm == "charpoly": + f = self.frobenius_polynomial_matrix(algorithm="hypellfrob") + return self.count_points_frobenius_polynomial(n=n, f=f) + else: + raise ValueError("Unknown algorithm") + + def count_points(self, n=1): + r""" + Count points over finite fields. + + INPUT: + + - ``n`` -- integer. + + OUTPUT: + + An integer. The number of points over `\GF{q}, \ldots, + \GF{q^n}` on a hyperelliptic curve over a finite field `\GF{q}`. + + .. WARNING:: + + This is currently using exhaustive search for hyperelliptic curves + over non-prime fields, which can be awfully slow. + + EXAMPLES:: + + sage: P. = PolynomialRing(GF(3)) + sage: C = HyperellipticCurveSmoothModel(x^3+x^2+1) + sage: C.count_points(4) + [6, 12, 18, 96] + sage: C.base_extend(GF(9,'a')).count_points(2) + [12, 96] + + sage: K = GF(2**31-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + 3*t + 5) + sage: H.count_points() # long time, 2.4 sec on a Corei7 + [2147464821] + sage: H.count_points(n=2) # long time, 30s on a Corei7 + [2147464821, 4611686018988310237] + + sage: K = GF(2**7-1) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^13 + 3*t^5 + 5) + sage: H.count_points(n=6) + [112, 16360, 2045356, 260199160, 33038302802, 4195868633548] + + sage: P. = PolynomialRing(GF(3)) + sage: H = HyperellipticCurveSmoothModel(x^3+x^2+1) + sage: C1 = H.count_points(4); C1 + [6, 12, 18, 96] + sage: C2 = sage.schemes.generic.scheme.Scheme.count_points(H,4); C2 # long time, 2s on a Corei7 + [6, 12, 18, 96] + sage: C1 == C2 # long time, because we need C2 to be defined + True + + sage: P. = PolynomialRing(GF(9,'a')) + sage: H = HyperellipticCurveSmoothModel(x^5+x^2+1) + sage: H.count_points(5) + [18, 78, 738, 6366, 60018] + + sage: F. = GF(4); P. = F[] + sage: H = HyperellipticCurveSmoothModel(x^5+a*x^2+1, x+a+1) + sage: H.count_points(6) + [2, 24, 74, 256, 1082, 4272] + + This example shows that :issue:`20391` is resolved:: + + sage: x = polygen(GF(4099)) + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: H.count_points(1) + [4106] + """ + K = self.base_ring() + q = K.cardinality() + e = K.degree() + g = self.genus() + f, h = self.hyperelliptic_polynomials() + + if e == 1 and h == 0 and f.degree() % 2 == 1: + N1 = self._frobenius_coefficient_bound_traces(n) + N2 = self._frobenius_coefficient_bound_charpoly() + if n < g and q > (2 * g + 1) * (2 * N1 - 1): + return self.count_points_hypellfrob(n, N=N1, algorithm="traces") + elif q > (2 * g + 1) * (2 * N2 - 1): + return self.count_points_hypellfrob(n, N=N2, algorithm="charpoly") + + # No smart method available + return self.count_points_exhaustive(n) + + def cardinality_exhaustive(self, extension_degree=1, algorithm=None): + r""" + Count points on a single extension of the base field + by enumerating over x and solving the resulting quadratic + equation for y. + + EXAMPLES:: + + sage: K. = GF(9, 'a') + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, x^2 + a) + sage: C.cardinality_exhaustive() + 7 + + sage: K = GF(next_prime(1<<10)) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 3*t^5 + 5) + sage: H.cardinality_exhaustive() + 1025 + + sage: P. = PolynomialRing(GF(9,'a')) + sage: H = HyperellipticCurveSmoothModel(x^5+x^2+1) + sage: H.count_points(5) + [18, 78, 738, 6366, 60018] + + sage: F. = GF(4); P. = F[] + sage: H = HyperellipticCurveSmoothModel(x^5+a*x^2+1, x+a+1) + sage: H.count_points(6) + [2, 24, 74, 256, 1082, 4272] + + TESTS: + + Check for :issue:`19122`:: + + sage: x = polygen(GF(19), 'x') + sage: f = 15*x^4 + 7*x^3 + 3*x^2 + 7*x + 18 + sage: HyperellipticCurveSmoothModel(f).cardinality_exhaustive(1) + 19 + + Points at infinity on general curves of genus 1 are counted + correctly (see :issue:`21195`):: + + sage: S. = PolynomialRing(QQ) + sage: C = HyperellipticCurveSmoothModel(-z^2 + z, z^2) + sage: C.base_extend(GF(2)).count_points_exhaustive() + [5] + sage: C.base_extend(GF(3)).count_points_exhaustive() + [5] + """ + K = self.base_ring() + g = self.genus() + n = extension_degree + + if g == 0: + # here is the projective line + return K.cardinality() ** n + 1 + + f, h = self.hyperelliptic_polynomials() + a = 0 + + if n == 1: + # the base field + L = K + fext = f + hext = h + else: + # extension of the base field + from sage.categories.homset import Hom + + L = GF(K.cardinality() ** n, names="z") + P = L["t"] + emb = Hom(K, L)[0] + fext = P([emb(c) for c in f]) + hext = P([emb(c) for c in h]) + + # We solve equations of the form y^2 + r*y - s == 0. + # For the points at infinity (on the smooth model), + # solve y^2 + h[g+1]*y == f[2*g+2]. + # For the affine points with given x-coordinate, + # solve y^2 + h(x)*y == f(x). + + if K.characteristic() == 2: + # points at infinity + r = h[g + 1] + if not r: + a += 1 + elif n % 2 == 0 or (f[2 * g + 2] / r**2).trace() == 0: + # Artin-Schreier equation t^2 + t = s/r^2 + # always has a solution in extensions of even degree + a += 2 + # affine points + for x in L: + r = hext(x) + if not r: + a += 1 + elif (fext(x) / r**2).trace() == 0: + a += 2 + else: + # points at infinity + d = h[g + 1] ** 2 + 4 * f[2 * g + 2] + if not d: + a += 1 + elif n % 2 == 0 or d.is_square(): + a += 2 + # affine points + for x in L: + d = hext(x) ** 2 + 4 * fext(x) + if not d: + a += 1 + elif d.is_square(): + a += 2 + + return a + + def cardinality_hypellfrob(self, extension_degree=1, algorithm=None): + r""" + Count points on a single extension of the base field + using the ``hypellfrob`` program. + + EXAMPLES:: + + sage: K = GF(next_prime(1<<10)) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 3*t^5 + 5) + sage: H.cardinality_hypellfrob() + 1025 + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 3*t^5 + 5) + sage: H.cardinality_hypellfrob() + 50162 + sage: H.cardinality_hypellfrob(3) + 124992471088310 + """ + # the following actually computes the cardinality for several extensions + # but the overhead is negligible + return self.count_points_hypellfrob(n=extension_degree, algorithm=algorithm)[-1] + + @cached_method + def cardinality(self, extension_degree=1): + r""" + Count points on a single extension of the base field. + + EXAMPLES:: + + sage: K = GF(101) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + 3*t^5 + 5) + sage: H.cardinality() + 106 + sage: H.cardinality(15) + 1160968955369992567076405831000 + sage: H.cardinality(100) + 270481382942152609326719471080753083367793838278100277689020104911710151430673927943945601434674459120495370826289654897190781715493352266982697064575800553229661690000887425442240414673923744999504000 + + sage: K = GF(37) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + 3*t^5 + 5) + sage: H.cardinality() + 40 + sage: H.cardinality(2) + 1408 + sage: H.cardinality(3) + 50116 + + The following example shows that :issue:`20391` has been resolved:: + + sage: F=GF(23) + sage: x=polygen(F) + sage: C=HyperellipticCurveSmoothModel(x^8+1) + sage: C.cardinality() + 24 + """ + K = self.base_ring() + q = K.cardinality() + e = K.degree() + g = self.genus() + f, h = self.hyperelliptic_polynomials() + n = extension_degree + + # We may: + # - check for actual field of definition of the curve (up to isomorphism) + if e == 1 and h == 0 and f.degree() % 2 == 1: + N1 = self._frobenius_coefficient_bound_traces(n) + N2 = self._frobenius_coefficient_bound_charpoly() + if n < g and q > (2 * g + 1) * (2 * N1 - 1): + return self.cardinality_hypellfrob(n, algorithm="traces") + elif q > (2 * g + 1) * (2 * N2 - 1): + return self.cardinality_hypellfrob(n, algorithm="charpoly") + + # No smart method available + return self.cardinality_exhaustive(n) + + # ------------------------------------------- + # Frobenius Computations + # ------------------------------------------- + + def zeta_function(self): + r""" + Compute the zeta function of the hyperelliptic curve. + + EXAMPLES:: + + sage: F = GF(2); R. = F[] + sage: H = HyperellipticCurveSmoothModel(t^9 + t, t^4) + sage: H.zeta_function() + (16*x^8 + 8*x^7 + 8*x^6 + 4*x^5 + 6*x^4 + 2*x^3 + 2*x^2 + x + 1)/(2*x^2 - 3*x + 1) + + sage: F. = GF(4); R. = F[] + sage: H = HyperellipticCurveSmoothModel(t^5 + t^3 + t^2 + t + 1, t^2 + t + 1) + sage: H.zeta_function() + (16*x^4 + 8*x^3 + x^2 + 2*x + 1)/(4*x^2 - 5*x + 1) + + sage: F. = GF(9); R. = F[] + sage: H = HyperellipticCurveSmoothModel(t^5 + a*t) + sage: H.zeta_function() + (81*x^4 + 72*x^3 + 32*x^2 + 8*x + 1)/(9*x^2 - 10*x + 1) + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.zeta_function() + (1369*x^4 + 37*x^3 - 52*x^2 + x + 1)/(37*x^2 - 38*x + 1) + + A quadratic twist:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.zeta_function() + (1369*x^4 - 37*x^3 - 52*x^2 - x + 1)/(37*x^2 - 38*x + 1) + """ + q = self.base_ring().cardinality() + P = self.frobenius_polynomial() + x = P.parent().gen(0) + return P.reverse() / ((1 - x) * (1 - q * x)) + + def _frobenius_coefficient_bound_charpoly(self): + r""" + Computes bound on number of `p`-adic digits needed to recover + frobenius polynomial computing the characteristic polynomial + of the frobenius matrix, i.e. returns `B` so that knowledge of + `a_1`, ..., `a_g` modulo `p^B` determine frobenius polynomial + uniquely. + + The bound used here stems from the expression of the coefficients + of the characteristic polynomial of the Frobenius as sums + of products of its eigenvalues: + + .. MATH:: + + \| a_i \| \leq \binom{2g}{i} \sqrt{q}^i + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_charpoly() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_charpoly() + 2 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_charpoly() + 3 + + sage: R. = PolynomialRing(GF(next_prime(10^9))) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_charpoly() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_charpoly() + 2 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_charpoly() + 2 + sage: HyperellipticCurveSmoothModel(t^9 + t + 1)._frobenius_coefficient_bound_charpoly() + 3 + sage: HyperellipticCurveSmoothModel(t^11 + t + 1)._frobenius_coefficient_bound_charpoly() + 3 + sage: HyperellipticCurveSmoothModel(t^13 + t + 1)._frobenius_coefficient_bound_charpoly() + 4 + """ + assert self.base_ring().is_finite() + p = self.base_ring().characteristic() + q = self.base_ring().order() + sqrtq = RR(q).sqrt() + g = self.genus() + + # note: this bound is from Kedlaya's paper, but he tells me it's not + # the best possible + M = 2 * binomial(2 * g, g) * sqrtq**g + B = ZZ(M.ceil()).exact_log(p) + if p**B < M: + B += 1 + return B + + def _frobenius_coefficient_bound_traces(self, n=1): + r""" + Computes bound on number of `p`-adic digits needed to recover + the number of rational points on `n` extensions computing + traces of the frobenius matrix powers, i.e. returns `B` so that + knowledge of `\tr(M^1)`, ..., `\tr(M^n)` modulo `p^B` determine + `N_1`, ..., `N_n` uniquely. + + The formula stems from the expression of the trace of the Frobenius + as a sum of `i`-th powers of its eigenvalues. + + .. MATH:: + + \| \tr(M^i) \| \leq 2 g \sqrt{q}^i + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_traces() + 2 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_traces() + 2 + + sage: R. = PolynomialRing(GF(next_prime(10^9))) + sage: HyperellipticCurveSmoothModel(t^3 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^5 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^7 + t + 1)._frobenius_coefficient_bound_traces() + 1 + sage: HyperellipticCurveSmoothModel(t^9 + t + 1)._frobenius_coefficient_bound_traces(n=3) + 2 + sage: HyperellipticCurveSmoothModel(t^11 + t + 1)._frobenius_coefficient_bound_traces(n=3) + 2 + sage: HyperellipticCurveSmoothModel(t^13 + t + 1)._frobenius_coefficient_bound_traces(n=5) + 3 + + sage: R. = PolynomialRing(GF(11)) + sage: H = HyperellipticCurveSmoothModel(t^5 - t + 1) + sage: H._frobenius_coefficient_bound_traces() + 2 + """ + p = self.base_ring().characteristic() + q = self.base_ring().order() + sqrtq = RR(q).sqrt() + g = self.genus() + + M = 4 * g * sqrtq**n + B = ZZ(M.ceil()).exact_log(p) + if p**B < M: + B += 1 + return B + + def frobenius_matrix_hypellfrob(self, N=None): + r""" + Compute `p`-adic frobenius matrix to precision `p^N`. + If `N` not supplied, a default value is selected, which is the + minimum needed to recover the charpoly unambiguously. + + .. note:: + + Implemented using ``hypellfrob``, which means it only works + over the prime field `GF(p)`, and requires `p > (2g+1)(2N-1)`. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_matrix_hypellfrob() + [1258 + O(37^2) 925 + O(37^2) 132 + O(37^2) 587 + O(37^2)] + [1147 + O(37^2) 814 + O(37^2) 241 + O(37^2) 1011 + O(37^2)] + [1258 + O(37^2) 1184 + O(37^2) 1105 + O(37^2) 482 + O(37^2)] + [1073 + O(37^2) 999 + O(37^2) 772 + O(37^2) 929 + O(37^2)] + + The ``hypellfrob`` program doesn't support non-prime fields:: + + sage: K. = GF(37**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + z*t^3 + 1) + sage: H.frobenius_matrix_hypellfrob() + Traceback (most recent call last): + ... + NotImplementedError: Computation of Frobenius matrix only implemented + for hyperelliptic curves defined over prime fields. + + nor too small characteristic:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.frobenius_matrix_hypellfrob() + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 81 + """ + p = self.base_ring().characteristic() + e = self.base_ring().degree() + if e != 1: + raise NotImplementedError( + "Computation of Frobenius matrix only implemented for hyperelliptic curves defined over prime fields." + ) + + f, h = self.hyperelliptic_polynomials() + if h != 0: + # need y^2 = f(x) + raise NotImplementedError("only implemented for curves y^2 = f(x)") + + sign = 1 + if not f.is_monic(): + # at this time we need a monic f + c = f.leading_coefficient() + f = f / c + if c.is_square(): + # solutions of $y^2 = c * f(x)$ correspond naturally to + # solutions of $(sqrt(c) y)^2 = f(x)$ + pass + else: + # we'll count points on a twist and then correct by untwisting... + sign = -1 + assert f.is_monic() + + # By default, use precision enough to be able to compute the + # frobenius minimal polynomial + if N is None: + N = self._frobenius_coefficient_bound_charpoly() + + matrix_of_frobenius = hypellfrob(p, N, f) + matrix_of_frobenius = sign * matrix_of_frobenius + return matrix_of_frobenius + + def frobenius_matrix(self, N=None, algorithm="hypellfrob"): + r""" + Compute `p`-adic frobenius matrix to precision `p^N`. + If `N` not supplied, a default value is selected, which is the + minimum needed to recover the charpoly unambiguously. + + .. note:: + + Currently only implemented using ``hypellfrob``, + which means it only works over the prime field `GF(p)`, + and requires `p > (2g+1)(2N-1)`. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_matrix() + [1258 + O(37^2) 925 + O(37^2) 132 + O(37^2) 587 + O(37^2)] + [1147 + O(37^2) 814 + O(37^2) 241 + O(37^2) 1011 + O(37^2)] + [1258 + O(37^2) 1184 + O(37^2) 1105 + O(37^2) 482 + O(37^2)] + [1073 + O(37^2) 999 + O(37^2) 772 + O(37^2) 929 + O(37^2)] + + The ``hypellfrob`` program doesn't support non-prime fields:: + + sage: K. = GF(37**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + z*t^3 + 1) + sage: H.frobenius_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + NotImplementedError: Computation of Frobenius matrix only implemented + for hyperelliptic curves defined over prime fields. + + nor too small characteristic:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.frobenius_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 81 + """ + if algorithm != "hypellfrob": + raise ValueError("Unknown algorithm") + + # By default, use precision enough to be able to compute the + # frobenius minimal polynomial + if N is None: + N = self._frobenius_coefficient_bound_charpoly() + + return self.frobenius_matrix_hypellfrob(N=N) + + def frobenius_polynomial_cardinalities(self, a=None): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by computing the number of points on the curve over `g` extensions + of the base field where `g` is the genus of the curve. + + .. WARNING:: + + This is highly inefficient when the base field or the genus of the + curve are large. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial_cardinalities() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial_cardinalities() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Curve over a non-prime field:: + + sage: K. = GF(7**2) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + z*t + z^2) + sage: H.frobenius_polynomial_cardinalities() + x^4 + 8*x^3 + 70*x^2 + 392*x + 2401 + + This method may actually be useful when ``hypellfrob`` does not work:: + + sage: K = GF(7) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^3 + 1) + sage: H.frobenius_polynomial_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + ValueError: In the current implementation, p must be greater than (2g+1)(2N-1) = 81 + sage: H.frobenius_polynomial_cardinalities() + x^8 - 5*x^7 + 7*x^6 + 36*x^5 - 180*x^4 + 252*x^3 + 343*x^2 - 1715*x + 2401 + """ + g = self.genus() + q = self.base_ring().cardinality() + + if a is None: + # this may actually call frobenius_polynomial() + a = self.count_points(g) + # maybe calling count_points_exhaustive() would make more sense + # but the method is currently only called with a precomputed list + # of number of points so it does not really matter + + # computation of the reciprocal polynomial + s = [ai - q ** (i + 1) - 1 for i, ai in enumerate(a)] + coeffs = [1] + for i in range(1, g + 1): + c = 0 + for j in range(i): + c += s[i - 1 - j] * coeffs[j] + coeffs.append(c / i) + coeffs = coeffs + [coeffs[g - i] * q ** (i) for i in range(1, g + 1)] + + return ZZ["x"](coeffs).reverse() + + def frobenius_polynomial_matrix(self, M=None, algorithm="hypellfrob"): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by computing the charpoly of the frobenius matrix. + + This is currently only supported when the base field is prime + and large enough using the ``hypellfrob`` library. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial_matrix() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial_matrix() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Curves defined over larger prime fields:: + + sage: K = GF(49999) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + t^5 + 1) + sage: H.frobenius_polynomial_matrix() + x^8 + 281*x^7 + 55939*x^6 + 14144175*x^5 + 3156455369*x^4 + 707194605825*x^3 + + 139841906155939*x^2 + 35122892542149719*x + 6249500014999800001 + sage: H = HyperellipticCurveSmoothModel(t^15 + t^5 + 1) + sage: H.frobenius_polynomial_matrix() # long time, 8s on a Corei7 + x^14 - 76*x^13 + 220846*x^12 - 12984372*x^11 + 24374326657*x^10 - 1203243210304*x^9 + + 1770558798515792*x^8 - 74401511415210496*x^7 + 88526169366991084208*x^6 + - 3007987702642212810304*x^5 + 3046608028331197124223343*x^4 + - 81145833008762983138584372*x^3 + 69007473838551978905211279154*x^2 + - 1187357507124810002849977200076*x + 781140631562281254374947500349999 + + This ``hypellfrob`` program doesn't support non-prime fields:: + + sage: K. = GF(37**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^9 + z*t^3 + 1) + sage: H.frobenius_polynomial_matrix(algorithm='hypellfrob') + Traceback (most recent call last): + ... + NotImplementedError: Computation of Frobenius matrix only implemented + for hyperelliptic curves defined over prime fields. + """ + K = self.base_ring() + p = K.characteristic() + q = K.cardinality() + g = self.genus() + N = self._frobenius_coefficient_bound_charpoly() + # compute charpoly over ZZ and then reduce back + # (because charpoly of p-adic matrices sometimes loses precision) + M = self.frobenius_matrix(N=N, algorithm=algorithm).change_ring(ZZ) + + # get a_g, ..., a_0 in ZZ (i.e. with correct signs) + f = M.charpoly().list()[g : 2 * g + 1] + ppow = p**N + f = [x % ppow for x in f] + f = [x if 2 * x < ppow else x - ppow for x in f] + + # get a_{2g}, ..., a_{g+1} + f = [f[g - i] * q ** (g - i) for i in range(g)] + f + + return ZZ["x"](f) + + def frobenius_polynomial_pari(self): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`, + by calling the PARI function ``hyperellcharpoly``. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial_pari() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial_pari() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Slightly larger example:: + + sage: K = GF(2003) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 487*t^5 + 9*t + 1) + sage: H.frobenius_polynomial_pari() + x^6 - 14*x^5 + 1512*x^4 - 66290*x^3 + 3028536*x^2 - 56168126*x + 8036054027 + + Curves defined over a non-prime field are supported as well:: + + sage: K. = GF(7^2) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + a*t + 1) + sage: H.frobenius_polynomial_pari() + x^4 + 4*x^3 + 84*x^2 + 196*x + 2401 + + sage: K. = GF(23**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^3 + z*t + 4) + sage: H.frobenius_polynomial_pari() + x^2 - 15*x + 12167 + + Over prime fields of odd characteristic, `h` may be non-zero:: + + sage: K = GF(101) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + 27*t + 3, t) + sage: H.frobenius_polynomial_pari() + x^4 + 2*x^3 - 58*x^2 + 202*x + 10201 + + TESTS: + + Check that :issue:`28789` is fixed:: + + sage: P. = PolynomialRing(GF(3)) + sage: u = x^10 + x^9 + x^8 + x + sage: C = HyperellipticCurveSmoothModel(u) + sage: C.frobenius_polynomial_pari() + x^8 + 2*x^7 + 6*x^6 + 9*x^5 + 18*x^4 + 27*x^3 + 54*x^2 + 54*x + 81 + """ + f, h = self.hyperelliptic_polynomials() + return ZZ["x"](pari([f, h]).hyperellcharpoly()) + + @cached_method + def frobenius_polynomial(self): + r""" + Compute the charpoly of frobenius, as an element of `\ZZ[x]`. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(37)) + sage: H = HyperellipticCurveSmoothModel(t^5 + t + 2) + sage: H.frobenius_polynomial() + x^4 + x^3 - 52*x^2 + 37*x + 1369 + + A quadratic twist:: + + sage: H = HyperellipticCurveSmoothModel(2*t^5 + 2*t + 4) + sage: H.frobenius_polynomial() + x^4 - x^3 - 52*x^2 - 37*x + 1369 + + Slightly larger example:: + + sage: K = GF(2003) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^7 + 487*t^5 + 9*t + 1) + sage: H.frobenius_polynomial() + x^6 - 14*x^5 + 1512*x^4 - 66290*x^3 + 3028536*x^2 - 56168126*x + 8036054027 + + Curves defined over a non-prime field of odd characteristic, + or an odd prime field which is too small compared to the genus, + are supported via PARI:: + + sage: K. = GF(23**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^3 + z*t + 4) + sage: H.frobenius_polynomial() + x^2 - 15*x + 12167 + + sage: K. = GF(3**3) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + z*t + z**3) + sage: H.frobenius_polynomial() + x^4 - 3*x^3 + 10*x^2 - 81*x + 729 + + Over prime fields of odd characteristic, `h` may be non-zero:: + + sage: K = GF(101) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + 27*t + 3, t) + sage: H.frobenius_polynomial() + x^4 + 2*x^3 - 58*x^2 + 202*x + 10201 + + Over prime fields of odd characteristic, `f` may have even degree:: + + sage: H = HyperellipticCurveSmoothModel(t^6 + 27*t + 3) + sage: H.frobenius_polynomial() + x^4 + 25*x^3 + 322*x^2 + 2525*x + 10201 + + In even characteristic, the naive algorithm could cover all cases + because we can easily check for squareness in quotient rings of + polynomial rings over finite fields but these rings unfortunately + do not support iteration:: + + sage: K. = GF(2**5) + sage: R. = PolynomialRing(K) + sage: H = HyperellipticCurveSmoothModel(t^5 + z*t + z**3, t) + sage: H.frobenius_polynomial() + x^4 - x^3 + 16*x^2 - 32*x + 1024 + + TESTS: + + Check that :issue:`28789` is fixed:: + + sage: P. = PolynomialRing(GF(3)) + sage: u = x^10 + x^9 + x^8 + x + sage: C = HyperellipticCurveSmoothModel(u) + sage: C.frobenius_polynomial() + x^8 + 2*x^7 + 6*x^6 + 9*x^5 + 18*x^4 + 27*x^3 + 54*x^2 + 54*x + 81 + """ + K = self.base_ring() + e = K.degree() + q = K.cardinality() + + g = self.genus() + f, h = self.hyperelliptic_polynomials() + + if ( + e == 1 + and q + >= (2 * g + 1) * (2 * self._frobenius_coefficient_bound_charpoly() - 1) + and h == 0 + and f.degree() % 2 + ): + return self.frobenius_polynomial_matrix() + elif q % 2 == 1: + return self.frobenius_polynomial_pari() + else: + return self.frobenius_polynomial_cardinalities() + + # This where Cartier Matrix is actually computed. This is either called by + # E.Cartier_matrix, E.a_number, or E.Hasse_Witt. + @cached_method + def _Cartier_matrix_cached(self): + r""" + INPUT: + + - 'E' - Hyperelliptic Curve of the form `y^2 = f(x)` over a + finite field, `\GF{q}` + + OUTPUT: + + - matrix(Fq,M)' The matrix `M = (c_(pi-j)), f(x)^((p-1)/2) = \sum c_i x^i` + - 'Coeff' List of Coeffs of F, this is needed for Hasse-Witt function. + - 'g' genus of the curve self, this is needed by a-number. + - 'Fq' is the base field of self, and it is needed for Hasse-Witt + - 'p' is the char(Fq), this is needed for Hasse-Witt. + - 'E' The initial elliptic curve to check some caching conditions. + + EXAMPLES:: + + sage: K.=GF(9,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,0) + sage: C._Cartier_matrix_cached() + ( + [0 0 2] + [0 0 0] + [0 1 0], [2, 0, 0, 0, 0, 0, 0, 1, 0], 3, Finite Field in x of size 3^2, 3, Hyperelliptic Curve over Finite Field in x of size 3^2 defined by y^2 = x^7 + 2 + ) + sage: K.=GF(49,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^5+1,0) + sage: C._Cartier_matrix_cached() + ( + [0 3] + [0 0], [1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1], 2, Finite Field in x of size 7^2, 7, Hyperelliptic Curve over Finite Field in x of size 7^2 defined by y^2 = x^5 + 1 + ) + + sage: P.=GF(9,'a')[] + sage: C=HyperellipticCurveSmoothModel(x^29+1,0) + sage: C._Cartier_matrix_cached() + ( + [0 0 1 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 1 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 1 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [1 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 1 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 1 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 1 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 14, Finite Field in a of size 3^2, 3, Hyperelliptic Curve over Finite Field in a of size 3^2 defined by y^2 = x^29 + 1 + ) + + TESTS:: + + sage: K.=GF(2,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,x) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: p must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,4) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: E must be of the form y^2 = f(x) + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^8-1,0) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: In this implementation the degree of f must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^5+1,0,check_squarefree=False) + sage: C._Cartier_matrix_cached() + Traceback (most recent call last): + ... + ValueError: curve is not smooth + """ + # Compute the finite field and prime p. + Fq = self.base_ring() + p = Fq.characteristic() + # checks + + if p == 2: + raise ValueError("p must be odd") + + g = self.genus() + + # retrieve the function f(x) ,where y^2=f(x) + f, h = self.hyperelliptic_polynomials() + # This implementation only deals with h=0 + if h != 0: + raise ValueError("E must be of the form y^2 = f(x)") + + d = f.degree() + # this implementation is for odd degree only, even degree will be handled later. + if d % 2 == 0: + raise ValueError("In this implementation the degree of f must be odd") + # Compute resultant to make sure no repeated roots + df = f.derivative() + R = df.resultant(f) + if R == 0: + raise ValueError("curve is not smooth") + + # computing F, since the entries of the matrix are c_i where F= \sum c_i x^i + + F = f ** ((p - 1) / 2) + + # coefficients returns a_0, ... , a_n where f(x) = a_n x^n + ... + a_0 + + Coeff = F.list() + + # inserting zeros when necessary-- that is, when deg(F) < p*g-1, (simplified if p <2g-1) + # which is the highest powered coefficient needed for our matrix + # So we don't have enough coefficients we add extra zeros to have the same poly, + # but enough coeff. + + zeros = [0 for i in range(p * g - len(Coeff))] + Coeff = Coeff + zeros + + # compute each row of matrix as list and then M=list of lists(rows) + + M = [] + for j in range(1, g + 1): + H = [Coeff[i] for i in range((p * j - 1), (p * j - g - 1), -1)] + M.append(H) + return matrix(Fq, M), Coeff, g, Fq, p, self + + # This is what is called from command line + def Cartier_matrix(self): + r""" + INPUT: + + - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``M``: The matrix `M = (c_{pi-j})`, where `c_i` are the coefficients of `f(x)^{(p-1)/2} = \sum c_i x^i` + + REFERENCES: + + N. Yui. On the Jacobian varieties of hyperelliptic curves over fields of characteristic `p > 2`. + + EXAMPLES:: + + sage: K. = GF(9,'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, 0) + sage: C.Cartier_matrix() + [0 0 2] + [0 0 0] + [0 1 0] + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1, 0) + sage: C.Cartier_matrix() + [0 3] + [0 0] + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1, 0) + sage: E.Cartier_matrix() + [0 0 1 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 1 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 1 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [1 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 1 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 1 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 1 0] + + TESTS:: + + sage: K.=GF(2,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,x) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: p must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^7-1,4) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: E must be of the form y^2 = f(x) + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^8-1,0) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: In this implementation the degree of f must be odd + + sage: K.=GF(5,'x')[] + sage: C=HyperellipticCurveSmoothModel(x^5+1,0,check_squarefree=False) + sage: C.Cartier_matrix() + Traceback (most recent call last): + ... + ValueError: curve is not smooth + """ + # checking first that Cartier matrix is not already cached. Since + # it can be called by either Hasse_Witt or a_number. + # This way it does not matter which function is called first + # in the code. + # Github Issue #11115: Why shall we waste time by studying + # the cache manually? We only need to check whether the cached + # data belong to self. + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: + self._Cartier_matrix_cached.clear_cache() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + return M + + @cached_method + def _Hasse_Witt_cached(self): + r""" + This is where Hasse_Witt is actually computed. + + This is either called by E.Hasse_Witt or E.p_rank. + + INPUT: + + - ``E`` -- hyperelliptic Curve of the form `y^2 = f(x)` over + a finite field, `\GF{q}` + + OUTPUT: + + - ``N`` -- the matrix `N = M M^p \dots M^{p^{g-1}}` where + `M = c_{pi-j}, f(x)^{(p-1)/2} = \sum c_i x^i` + + - ``E`` -- the initial curve to check some caching conditions + + EXAMPLES:: + + sage: K. = GF(9,'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7-1,0) + sage: C._Hasse_Witt_cached() + ( + [0 0 0] + [0 0 0] + [0 0 0], Hyperelliptic Curve over Finite Field in x of size 3^2 defined by y^2 = x^7 + 2 + ) + + sage: K. = GF(49,'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5+1,0) + sage: C._Hasse_Witt_cached() + ( + [0 0] + [0 0], Hyperelliptic Curve over Finite Field in x of size 7^2 defined by y^2 = x^5 + 1 + ) + + sage: P. = GF(9,'a')[] + sage: C = HyperellipticCurveSmoothModel(x^29+1,0) + sage: C._Hasse_Witt_cached() + ( + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0], Hyperelliptic Curve over Finite Field in a of size 3^2 defined by y^2 = x^29 + 1 + ) + + TESTS: + + This shows that the bug at :issue:`23181` is fixed:: + + sage: K. = PolynomialRing(GF(5)) + sage: L. = GF(5).extension(z^3+3*z+3,'a') + sage: H. = L[] + sage: E = HyperellipticCurveSmoothModel(x^5+x^4+a^92*x^3+a^18*x^2+a^56*x,0) + sage: E.p_rank() + 0 + """ + # If Cartier Matrix is already cached for this curve, use that or evaluate it to get M, + # Coeffs, genus, Fq=base field of self, p=char(Fq). This is so we have one less matrix to + # compute. + + # We use caching here since Cartier matrix is needed to compute Hasse Witt. So if the Cartier + # is already computed it is stored in list A. If it was not cached (i.e. A is empty), we simply + # compute it. If it is cached then we need to make sure that we have the correct one. So check + # which curve does the matrix correspond to. Since caching stores a lot of stuff, we only check + # the last entry in A. If it does not match, clear A and compute Cartier. + # + # Since Github Issue #11115, there is a different cache for methods + # that don't accept arguments. Anyway, the easiest is to call + # the cached method and simply see whether the data belong to self. + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: + self._Cartier_matrix_cached.clear_cache() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + + # This compute the action of p^kth Frobenius on list of coefficients + def frob_mat(Coeffs, k): + a = p**k + mat = [] + Coeffs_pow = [c**a for c in Coeffs] + for i in range(1, g + 1): + H = [(Coeffs_pow[j]) for j in range((p * i - 1), (p * i - g - 1), -1)] + mat.append(H) + return matrix(Fq, mat) + + # Computes all the different possible action of frobenius on matrix M and stores in list Mall + Mall = [M] + [frob_mat(Coeffs, k) for k in range(1, g)] + Mall = reversed(Mall) + # initial N=I, so we can go through Mall and multiply all matrices with I and + # get the Hasse-Witt matrix. + N = identity_matrix(Fq, g) + for l in Mall: + N = N * l + return N, E + + # This is the function which is actually called by command line + def Hasse_Witt(self): + r""" + INPUT: + + - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``N`` : The matrix `N = M M^p \dots M^{p^{g-1}}` where `M = c_{pi-j}`, and `f(x)^{(p-1)/2} = \sum c_i x^i` + + + Reference-N. Yui. On the Jacobian varieties of hyperelliptic curves over fields of characteristic `p > 2`. + + EXAMPLES:: + + sage: K. = GF(9, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, 0) + sage: C.Hasse_Witt() + [0 0 0] + [0 0 0] + [0 0 0] + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1, 0) + sage: C.Hasse_Witt() + [0 0] + [0 0] + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1, 0) + sage: E.Hasse_Witt() + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0 0 0 0 0 0 0] + """ + # Since Github Issue #11115, there is a special + # type of cached for those methods that don't + # accept arguments. We want to get Hasse-Witt + # from the cache - but apparently it could be + # that the cached value does not belong to self. + # So, the easiest is: + N, E = self._Hasse_Witt_cached() + if E != self: + self._Hasse_Witt_cached.clear_cache() + N, E = self._Hasse_Witt_cached() + return N + + def a_number(self): + r""" + INPUT: + + - ``E``: Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``a`` : a-number + + + EXAMPLES:: + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1, 0) + sage: C.a_number() + 1 + + sage: K. = GF(9, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1, 0) + sage: C.a_number() + 1 + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1, 0) + sage: E.a_number() + 5 + """ + # We use caching here since Cartier matrix is needed to compute a_number. So if the Cartier + # is already computed it is stored in list A. If it was not cached (i.e. A is empty), we simply + # compute it. If it is cached then we need to make sure that we have the correct one. So check + # which curve does the matrix correspond to. Since caching stores a lot of stuff, we only check + # the last entry in A. If it does not match, clear A and compute Cartier. + # + # Since Github Issue #11115, there is a special cache for methods + # that don't accept arguments. The easiest is: Call the cached + # method, and test whether the last entry is self. + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + if E != self: + self._Cartier_matrix_cached.clear_cache() + M, Coeffs, g, Fq, p, E = self._Cartier_matrix_cached() + return g - rank(M) + + def p_rank(self): + r""" + INPUT: + + - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + + OUTPUT: + + - ``pr`` :p-rank + + EXAMPLES:: + + sage: K. = GF(49, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^5 + 1) + sage: C.p_rank() + 0 + + sage: K. = GF(9, 'x')[] + sage: C = HyperellipticCurveSmoothModel(x^7 - 1) + sage: C.p_rank() + 0 + + sage: P. = GF(9, 'a')[] + sage: E = HyperellipticCurveSmoothModel(x^29 + 1) + sage: E.p_rank() + 0 + """ + # We use caching here since Hasse Witt is needed to compute p_rank. So if the Hasse Witt + # is already computed it is stored in list A. If it was not cached (i.e. A is empty), we simply + # compute it. If it is cached then we need to make sure that we have the correct one. So check + # which curve does the matrix correspond to. Since caching stores a lot of stuff, we only check + # the last entry in A. If it does not match, clear A and compute Hasse Witt. + # However, it seems a waste of time to manually analyse the cache + # -- See Github Issue #11115 + N, E = self._Hasse_Witt_cached() + if E != self: + self._Hasse_Witt_cached.clear_cache() + N, E = self._Hasse_Witt_cached() + return rank(N) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py new file mode 100644 index 00000000000..eecd79b58f5 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -0,0 +1,192 @@ +from sage.misc.cachefunc import cached_method +from sage.schemes.hyperelliptic_curves_smooth_model import ( + hyperelliptic_generic, + invariants, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_finite_field import ( + HyperellipticCurveSmoothModel_finite_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_padic_field import ( + HyperellipticCurveSmoothModel_padic_field, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_rational_field import ( + HyperellipticCurveSmoothModel_rational_field, +) + +""" +TODO List + +### Invariants + +There seems to be massive redundancy in having these methods and the ones imported into +invariants. I think we should fix this by putting the methods themseleves into this class. +""" + + +class HyperellipticCurveSmoothModel_g2( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + def is_odd_degree(self): + """ + Return ``True`` if the curve is an odd degree model. + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = x^5 - x^4 + 3 + sage: HyperellipticCurveSmoothModel(f).is_odd_degree() + True + """ + f, h = self.hyperelliptic_polynomials() + df = f.degree() + if h.degree() < 3: + return df % 2 == 1 + elif df < 6: + return False + else: + a0 = f.leading_coefficient() + c0 = h.leading_coefficient() + return (c0**2 + 4 * a0) == 0 + + @cached_method + def jacobian(self): + from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_g2_generic import ( + HyperellipticJacobian_g2_generic, + ) + + return HyperellipticJacobian_g2_generic(self) + + # ----------------------------------- + # Genus Two invariant computations + # + # TODO: should we move logic from functions in `invariants()` + # into this file rather than import them + # ----------------------------------- + + def clebsch_invariants(self): + r""" + Return the Clebsch invariants `(A, B, C, D)` of Mestre, p 317, [Mes1991]_. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves.invariants` + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = x^5 - x^4 + 3 + sage: HyperellipticCurveSmoothModel(f).clebsch_invariants() + (0, -2048/375, -4096/25, -4881645568/84375) + sage: HyperellipticCurveSmoothModel(f(2*x)).clebsch_invariants() + (0, -8388608/375, -1073741824/25, -5241627016305836032/84375) + + sage: HyperellipticCurveSmoothModel(f, x).clebsch_invariants() + (-8/15, 17504/5625, -23162896/140625, -420832861216768/7119140625) + sage: HyperellipticCurveSmoothModel(f(2*x), 2*x).clebsch_invariants() + (-512/15, 71696384/5625, -6072014209024/140625, -451865844002031331704832/7119140625) + + TESTS:: + + sage: # optional - magma + sage: magma(HyperellipticCurveSmoothModel(f)).ClebschInvariants() + [ 0, -2048/375, -4096/25, -4881645568/84375 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x))).ClebschInvariants() + [ 0, -8388608/375, -1073741824/25, -5241627016305836032/84375 ] + sage: magma(HyperellipticCurveSmoothModel(f, x)).ClebschInvariants() + [ -8/15, 17504/5625, -23162896/140625, -420832861216768/7119140625 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x), 2*x)).ClebschInvariants() + [ -512/15, 71696384/5625, -6072014209024/140625, -451865844002031331704832/7119140625 ] + """ + f, h = self.hyperelliptic_polynomials() + return invariants.clebsch_invariants(4 * f + h**2) + + def igusa_clebsch_invariants(self): + r""" + Return the Igusa-Clebsch invariants `I_2, I_4, I_6, I_{10}` of Igusa and Clebsch [IJ1960]_. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves.invariants` + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = x^5 - x + 2 + sage: HyperellipticCurveSmoothModel(f).igusa_clebsch_invariants() + (-640, -20480, 1310720, 52160364544) + sage: HyperellipticCurveSmoothModel(f(2*x)).igusa_clebsch_invariants() + (-40960, -83886080, 343597383680, 56006764965979488256) + + sage: HyperellipticCurveSmoothModel(f, x).igusa_clebsch_invariants() + (-640, 17920, -1966656, 52409511936) + sage: HyperellipticCurveSmoothModel(f(2*x), 2*x).igusa_clebsch_invariants() + (-40960, 73400320, -515547070464, 56274284941110411264) + + TESTS:: + + sage: # optional - magma + sage: magma(HyperellipticCurveSmoothModel(f)).IgusaClebschInvariants() + [ -640, -20480, 1310720, 52160364544 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x))).IgusaClebschInvariants() + [ -40960, -83886080, 343597383680, 56006764965979488256 ] + sage: magma(HyperellipticCurveSmoothModel(f, x)).IgusaClebschInvariants() + [ -640, 17920, -1966656, 52409511936 ] + sage: magma(HyperellipticCurveSmoothModel(f(2*x), 2*x)).IgusaClebschInvariants() + [ -40960, 73400320, -515547070464, 56274284941110411264 ] + """ + f, h = self.hyperelliptic_polynomials() + return invariants.igusa_clebsch_invariants(4 * f + h**2) + + def absolute_igusa_invariants_wamelen(self): + r""" + Return the three absolute Igusa invariants used by van Wamelen [Wam1999]_. + + EXAMPLES:: + + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(x^5 - 1).absolute_igusa_invariants_wamelen() + (0, 0, 0) + sage: HyperellipticCurveSmoothModel((x^5 - 1)(x - 2), (x^2)(x - 2)).absolute_igusa_invariants_wamelen() + (0, 0, 0) + """ + f, h = self.hyperelliptic_polynomials() + return invariants.absolute_igusa_invariants_wamelen(4 * f + h**2) + + def absolute_igusa_invariants_kohel(self): + r""" + Return the three absolute Igusa invariants used by Kohel [KohECHIDNA]_. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves.invariants` + + EXAMPLES:: + + sage: R. = QQ[] + sage: HyperellipticCurveSmoothModel(x^5 - 1).absolute_igusa_invariants_kohel() + (0, 0, 0) + sage: HyperellipticCurveSmoothModel(x^5 - x + 1, x^2).absolute_igusa_invariants_kohel() + (-1030567/178769, 259686400/178769, 20806400/178769) + sage: HyperellipticCurveSmoothModel((x^5 - x + 1)(3*x + 1), (x^2)(3*x + 1)).absolute_igusa_invariants_kohel() + (-1030567/178769, 259686400/178769, 20806400/178769) + """ + f, h = self.hyperelliptic_polynomials() + return invariants.absolute_igusa_invariants_kohel(4 * f + h**2) + + +class HyperellipticCurveSmoothModel_g2_padic_field( + HyperellipticCurveSmoothModel_g2, HyperellipticCurveSmoothModel_padic_field +): + pass + + +class HyperellipticCurveSmoothModel_g2_finite_field( + HyperellipticCurveSmoothModel_g2, HyperellipticCurveSmoothModel_finite_field +): + pass + + +class HyperellipticCurveSmoothModel_g2_rational_field( + HyperellipticCurveSmoothModel_g2, HyperellipticCurveSmoothModel_rational_field +): + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py new file mode 100644 index 00000000000..47a4de64469 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -0,0 +1,1458 @@ +from sage.functions.all import log +from sage.misc.cachefunc import cached_method +from sage.rings.big_oh import O +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.real_mpfr import RR + +# TODO: move this +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_curve import ( + WeightedProjectiveCurve, +) +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_space import ( + WeightedProjectiveSpace, +) + + +class HyperellipticCurveSmoothModel_generic(WeightedProjectiveCurve): + def __init__(self, defining_polynomial, f, h, genus): + self._genus = genus + self._hyperelliptic_polynomials = (f, h) + + self._polynomial_ring = f.parent() + self._base_ring = f.base_ring() + + # TODO: is this simply genus + 1 + self._d = max(h.degree(), (f.degree() + 1) // 2) + + # Initalise the underlying curve + A = WeightedProjectiveSpace([1, self._genus + 1, 1], self._base_ring) + WeightedProjectiveCurve.__init__(self, A, defining_polynomial) + + def _repr_(self): + old_gen = str(self._polynomial_ring.gen()) + f, h = self._hyperelliptic_polynomials + + # TODO: + # The old class has these weird internal gens and then + # printing polynomial rings to change output. + # + # Will do something hacky here and we can talk about it. + f_str, h_str = repr(f).replace(old_gen, "x"), repr(h).replace(old_gen, "x") + + if h: + if h.is_one(): + curve = f"y^2 + y = {f_str}" + else: + curve = f"y^2 + ({h_str})*y = {f_str}" + else: + curve = f"y^2 = {f_str}" + + return f"Hyperelliptic Curve over {self.base_ring()} defined by {curve}" + + def genus(self): + """ + Return the genus of the hyperelliptic curve. + + EXAMPLES: + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: H.genus() + 3 + sage: H = HyperellipticCurveSmoothModel(x^3 + 2, x^5 + 1) + sage: H.genus() + 4 + """ + return self._genus + + def base_ring(self): + """ + Return the base ring of the hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: H.base_ring() + Rational Field + + sage: S. = FiniteField(19)[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x + 2) + sage: H.base_ring() + Finite Field of size 19 + """ + return self._base_ring + + def change_ring(self, R): + """ + Return this hyperelliptic curve over a new ring "R". + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - 10*x + 9) + sage: K = Qp(3, 5) # optional - sage.rings.padics + sage: L. = K.extension(x^30 - 3) # optional - sage.rings.padics + sage: HK = H.change_ring(K) # optional - sage.rings.padics + sage: HL = HK.change_ring(L); HL # optional - sage.rings.padics + Hyperelliptic Curve over 3-adic Eisenstein Extension Field in a defined by x^30 - 3 defined by y^2 = x^5 + (2 + 2*a^30 + a^60 + 2*a^90 + 2*a^120 + O(a^150))*x + a^60 + O(a^210) + + sage: R. = FiniteField(7)[] # optional - sage.rings.finite_rings + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 5) # optional - sage.rings.finite_rings + sage: H.base_extend(FiniteField(7^2, 'a')) # optional - sage.rings.finite_rings + Hyperelliptic Curve over Finite Field in a of size 7^2 defined by y^2 = x^8 + x + 5 + + It is also possible to compute the reduction at a prime by changing + the base ring to the residue field:: + + sage: R. = PolynomialRing(QQ); + sage: H = HyperellipticCurve(R([0, -1, 2, 0, -2]), R([0, 1, 0, 1])); #LMFDB label: 763.a.763.1 + sage: H.change_ring(FiniteField(2)) + Hyperelliptic Curve over Finite Field of size 2 defined by y^2 + (x^3 + x)*y = x + sage: H.change_ring(FiniteField(3)) + Hyperelliptic Curve over Finite Field of size 3 defined by y^2 + (x^3 + x)*y = x^4 + 2*x^2 + 2*x + + Note that this only works when the curve has good reduction at p:: + + sage: H.change_ring(FiniteField(7)) + Traceback (most recent call last): + ... + ValueError: not a hyperelliptic curve: singularity in the provided affine patch + """ + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) + + f, h = self._hyperelliptic_polynomials + fR = f.change_ring(R) + hR = h.change_ring(R) + return HyperellipticCurveSmoothModel(fR, hR) + + base_extend = change_ring + + def polynomial_ring(self): + """ + Returns the parent of f,h, where self is the hyperelliptic curve + defined by y^2 + h*y = f. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: H.polynomial_ring() + Univariate Polynomial Ring in x over Rational Field + + sage: # optional - sage.rings.padics + sage: K = Qp(7, 10) + sage: HK = H.change_ring(K) + sage: HK.polynomial_ring() + Univariate Polynomial Ring in x over 7-adic Field with capped relative precision 10 + """ + return self._polynomial_ring + + def hyperelliptic_polynomials(self): + """ + Return the polynomials (f, h) such that + C : y^2 + h*y = f + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(R([0, 1, 2, 0, 0, 1]), R([1, 1, 0, 1])) + sage: H.hyperelliptic_polynomials() + (x^5 + 2*x^2 + x, x^3 + x + 1) + + sage: H = HyperellipticCurveSmoothModel(x^7 + x + 2) + sage: H.hyperelliptic_polynomials() + (x^7 + x + 2, 0) + """ + return self._hyperelliptic_polynomials + + def roots_at_infinity(self): + """ + Compute the roots of: Y^2 + h[d]Y - f[2d] = 0. + When the curve is ramified, we expect one root, when + the curve is inert or split we expect zero or two roots. + """ + if hasattr(self, "_alphas"): + return self._alphas + + f, h = self._hyperelliptic_polynomials + x = f.parent().gen() + d = self._d + + if h.is_zero(): + coeff = f[2 * d] + # Handle the ramified case + if coeff.is_zero(): + return [coeff] + return f[2 * d].sqrt(all=True) + + self._alphas = (x**2 + x * h[d] - f[2 * d]).roots(multiplicities=False) + return self._alphas + + def is_split(self): + """ + Return True if the curve is split, i.e. there are two rational + points at infinity. + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^6+1, x^3+1) + sage: H.is_split() + False + + sage: HK = H.change_ring(FiniteField(19)) + sage: HK.is_split() + True + """ + return len(self.roots_at_infinity()) == 2 + + def is_ramified(self): + """ + Return True if the curve is ramified, i.e. there is one rational + point at infinity. + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^5+1) + sage: H.is_ramified() + True + + sage: H = HyperellipticCurveSmoothModel(x^5+1, x^3+1) + sage: H.is_ramified() + False + """ + return len(self.roots_at_infinity()) == 1 + + def is_inert(self): + """ + Return True if the curve is inert, i.e. there are no rational + points at infinity. + + EXAMPLES: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^6+1,-x^3+1) + sage: H.is_inert() + True + + sage: K. = QQ.extension(x^2+x-1) + sage: HK = H.change_ring(K) + sage: HK.is_inert() + False + + sage: HF = H.change_ring(FiniteField(29)) + sage: HF.is_inert() + False + """ + return len(self.roots_at_infinity()) == 0 + + def infinite_polynomials(self): + """ + TODO: the name of this function could be better? + + Computes G^±(x) for curves in the split degree model + """ + if hasattr(self, "_infinite_polynomials"): + return self._infinite_polynomials + + alphas = self.roots_at_infinity() + + # This function only makes sense for the split model + if not len(alphas) == 2: + raise ValueError("hyperelliptic curve does not have the split model") + + f, h = self._hyperelliptic_polynomials + alpha_plus, alpha_minus = alphas + d = self._d + + # Construct G_plus from alpha_plus + g = [None] * (d + 1) + g[d] = alpha_plus + for i in range(d - 1, -1, -1): + # We need (g * (g + h))[x^(i + d)] to match f_{i + d} + the_rest = g[d] * h[i] + sum( + g[k] * (g[i + d - k] + h[i + d - k]) for k in range(i + 1, d) + ) + g[i] = (f[i + d] - the_rest) / (2 * g[d] + h[d]) + + G_plus = self._polynomial_ring(g) + G_minus = -G_plus - h + # Checks for the assumptions on G^± + genus = self.genus() + assert G_plus.degree() <= (genus + 1) + assert (G_plus**2 + h * G_plus - f).degree() <= genus + assert G_minus.leading_coefficient() == alpha_minus + + self._infinite_polynomials = G_plus, G_minus + return self._infinite_polynomials + + def points_at_infinity(self): + """ + Compute the points at infinity on the curve. Assumes we are using + a weighted projective model for the curve + """ + # TODO: check to False? + return [self.point([1, y, 0], check=True) for y in self.roots_at_infinity()] + + def is_x_coord(self, x): + """ + Return True if ``x`` is the `x`-coordinate of a point on this curve. + + .. SEEALSO:: + + See also :meth:`lift_x` to find the point(s) with a given + `x`-coordinate. This function may be useful in cases where + testing an element of the base field for being a square is + faster than finding its square root. + + INPUT: + + - ``x`` -- an element of the base ring of the curve + + OUTPUT: + + A bool stating whether or not `x` is a x-coordinate of a point on the curve + + EXAMPLES: + + When `x` is the `x`-coordinate of a rational point on the + curve, we can request these:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.is_x_coord(0) + True + + There are no rational points with `x`-coordinate 3:: + + sage: H.is_x_coord(3) + False + + The function also handles the case when `h(x)` is not zero:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.is_x_coord(1) + True + + We can perform these operations over finite fields too:: + + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(163)) + sage: f = x^7 + x + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.is_x_coord(13) + True + + Including the case of characteristic two:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2^4) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.is_x_coord(z4^3 + z4^2 + z4) + True + + AUTHORS: + + - Giacomo Pope (2024): adapted from :meth:`lift_x` + + TESTS: + + The `x`-coordinate must be defined over the base field of the curve:: + + sage: p = 11 + sage: F = GF(11) + sage: F_ext = GF(11^2) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.is_x_coord(F_ext.gen()) + Traceback (most recent call last): + ... + TypeError: x must be coercible into the base ring of the curve + """ + f, h = self.hyperelliptic_polynomials() + K = self.base_ring() + try: + x = K(x) + except (ValueError, TypeError): + raise TypeError("x must be coercible into the base ring of the curve") + + # When h is zero then x is a valid coordinate if y2 is square + if not h: + y2 = f(x) + return y2.is_square() + # Generic case for h != 0 + a = f(x) + b = h(x) + # Special case for char 2 + if K.characteristic() == 2: + R = f.parent() # Polynomial ring K[x] + F = R([-a, b, 1]) + return bool(F.roots()) + # Otherwise x is a point on the curve if the discriminant is a square + D = b * b + 4 * a + return D.is_square() + + def lift_x(self, x, all=False): + """ + Return one or all finite points with given `x`-coordinate. + + This method is deterministic: It returns the same data each + time when called again with the same `x`. + + INPUT: + + - ``x`` -- an element of the base ring of the curve + + - ``all`` (bool, default ``False``) -- if ``True``, return a + (possibly empty) list of all points; if ``False``, return + just one point, or raise a :class:`ValueError` if there are none. + + OUTPUT: + + A point or list of up to two points on this curve. + + .. SEEALSO:: + + :meth:`is_x_coord` + + AUTHORS: + + - Giacomo Pope (2024): Allowed for the case of characteristic two + + EXAMPLES: + + When `x` is the `x`-coordinate of a rational point on the + curve, we can request these:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.lift_x(0) + (0 : -1 : 1) + sage: H.lift_x(4, all=True) + [(4 : -33 : 1), (4 : 33 : 1)] + + There are no rational points with `x`-coordinate 3:: + + sage: H.lift_x(3) + Traceback (most recent call last): + ... + ValueError: No point with x-coordinate 3 on Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x^3 + 1 + + An empty list is returned when there are no points and ``all=True``:: + + sage: H.lift_x(3, all=True) + [] + + The function also handles the case when `h(x)` is not zero:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^5 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.lift_x(1) + (1 : -3 : 1) + + We can perform these operations over finite fields too:: + + sage: # needs sage.rings.finite_rings + sage: R. = PolynomialRing(GF(163)) + sage: f = x^7 + x + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.lift_x(13) + (13 : 41 : 1) + + Including the case of characteristic two:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2^4) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.lift_x(z4^3 + z4^2 + z4, all=True) + [(z4^3 + z4^2 + z4 : z4^2 + z4 + 1 : 1), (z4^3 + z4^2 + z4 : z4^3 : 1)] + + Points at infinity are not included, as they do not have a unique x-coordinate:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: H(1, -1, 0) + (1 : -1 : 0) + sage: H.lift_x(1, all=True) + [] + + TESTS:: + + sage: # needs sage.rings.finite_rings + sage: F1 = GF(11) + sage: F2 = GF(13) + sage: R. = PolynomialRing(F1) + sage: f = x^7 + x^3 + 1 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.lift_x(F2.random_element()) + Traceback (most recent call last): + ... + ValueError: x must have a common parent with the base ring + + Ensure that :issue:`37097` is fixed:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2^4) + sage: R. = PolynomialRing(F) + sage: f = x^7 + x^3 + 1 + sage: h = x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.lift_x(z4^3 + z4^2 + z4, all=True) + [(z4^3 + z4^2 + z4 : z4^2 + z4 + 1 : 1), (z4^3 + z4^2 + z4 : z4^3 : 1)] + """ + from sage.structure.element import get_coercion_model + + cm = get_coercion_model() + + f, h = self.hyperelliptic_polynomials() + K = self.base_ring() + + # Compute the common parent between the base ring of the curve and + # the parent of the input x-coordinate. + try: + L = cm.common_parent(x.parent(), K) + x = L(x) + except (TypeError, ValueError): + raise ValueError("x must have a common parent with the base ring") + + # First we compute the y-coordinates the given x-coordinate + ys = [] + one = L.one() + + # When h is zero we find all y-coordinates with a single sqrt + if not h: + y2 = f(x) + # When y2 is not a square, ys will be an empty list + ys = y2.sqrt(all=True, extend=False) + # Otherwise we need roots of the discriminant + else: + a = f(x) + b = h(x) + # Special case for char 2 + if K.characteristic() == 2: + R = f.parent() + F = R([-a, b, 1]) + ys = F.roots(L, multiplicities=False) + else: + D = b * b + 4 * a + # When D is not a square, ys will be an empty list + ys = [(-b + d) / 2 for d in D.sqrt(all=True, extend=False)] + + if ys: + ys.sort() # Make lifting deterministic + if all: + return [self.point([x, y, one], check=False) for y in ys] + else: + return self.point([x, ys[0], one], check=False) + + if all: + return [] + else: + raise ValueError(f"No point with x-coordinate {x} on {self}") + + def affine_coordinates(self, P): + """ + Returns the affine coordinates of a point P of self. + That is for P = [X,Y,Z], the output is X/Z¸ Y/Z^(g+1). + + EXAMPLES:: + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 1) + sage: P = H.point([2,0,2]) + sage: H.affine_coordinates(P) + (1, 0) + sage: Q = H.point([1,1,0]) + sage: H.affine_coordinates(Q) + Traceback (most recent call last): + ... + ValueError: The point (1 : 1 : 0) is not an affine point of Hyperelliptic Curve over Rational Field defined by y^2 = x^6 - 1 + """ + if P[2] == 0: + raise ValueError(f"The point {P} is not an affine point of {self}") + + g = self.genus() + return P[0] / P[2], P[1] / P[2] ** (g + 1) + + def is_weierstrass_point(self, P): + """ + Return True if P is a Weierstrass point of self. + TODO: It would be better to define this function for points directly. + + EXAMPLES:: + + We consider the hyperelliptic curve `y^2 = x^6 -1` over the rational numbers. + For instance, the point P = (1,0) is a Weierstrass point, + while the points at infinity are not:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 1) + sage: P = H.point([1,0]) + sage: H.is_weierstrass_point(P) + True + sage: Q = H.point([1,1,0]) + sage: H.is_weierstrass_point(Q) + False + + This also works for hyperelliptic curves with `h(x)` nonzero. + Note that in this case the `y`-coordinate of a Weierstrass point + is not necessarily zero:: + + sage: R. = FiniteField(17)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) + sage: P = H.point([15,6,1]) + sage: H.is_weierstrass_point(P) + True + sage: Q = H.point([3,0,1]) + sage: H.is_weierstrass_point(Q) + False + + TESTS:: + + Check that the examples from the p-adic file work. + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK(0,3) + sage: HK.is_weierstrass_point(P) + False + sage: Q = HK(1,0,0) + sage: HK.is_weierstrass_point(Q) + True + sage: S = HK(1,0) + sage: HK.is_weierstrass_point(S) + True + sage: T = HK.lift_x(1+3*5^2); T + (1 + 3*5^2 + O(5^8) : 3*5 + 4*5^2 + 5^4 + 3*5^5 + 5^6 + O(5^7) : 1 + O(5^8)) + sage: HK.is_weierstrass_point(T) + False + + """ + + f, h = self.hyperelliptic_polynomials() + if P[2] == 0: + return self.is_ramified() + else: + x, y = self.affine_coordinates(P) + return y == -y - h(x) + + def rational_weierstrass_points(self): + """ + Return the rational Weierstrass points of the hyperelliptic curve. + These are the points that are fixed by the hyperelliptic involution. + + EXAMPLES:: + + When `h(x)` is zero, then the Weierstrass points are the points with + `y`-coordinate equal to zero:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x) + sage: H.rational_weierstrass_points() + [(1 : 0 : 0), (1 : 0 : 1), (0 : 0 : 1), (-1 : 0 : 1)] + + The function also handles the case with `h(x)` nonzero:: + + sage: R. = FiniteField(17)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) + sage: H.rational_weierstrass_points() + [(15 : 6 : 1), (2 : 6 : 1)] + sage: P = H.point([15,6,1]) + sage: H.is_weierstrass_point(P) + True + + + """ + f, h = self.hyperelliptic_polynomials() + + F = h**2 + 4 * f + affine_weierstrass_points = [ + self(r, -h(r) / 2) for r in F.roots(multiplicities=False) + ] + + if self.is_ramified(): # the point at infinity is Weierstrass + return self.points_at_infinity() + affine_weierstrass_points + else: + return affine_weierstrass_points + + def hyperelliptic_involution(self, P): + """ + Returns the image of P under the hyperelliptic involution. + + EXAMPLES:: + + sage: R. = FiniteField(17)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) + sage: P = H.point([8,12]) + sage: P_inv = H.hyperelliptic_involution(P); P_inv + (8 : 8 : 1) + sage: H.hyperelliptic_involution(P_inv) == P + True + sage: Q = H.point([15,6]) + sage: H.is_weierstrass_point(Q) + True + sage: H.hyperelliptic_involution(Q) == Q + True + """ + [X, Y, Z] = P._coords + f, h = self.hyperelliptic_polynomials() + if Z == 0: + if self.is_ramified(): + return P + else: + points = self.points_at_infinity() + if P == points[0]: + return points[1] + else: + return points[0] + elif Z == 1: + Y_inv = -Y - h(X) + return self.point([X, Y_inv]) + else: + raise ValueError("the point P has to be normalized") + + def distinguished_point(self): + """ + Return the distinguished point of the hyperelliptic curve. + By default, this is one of the points at infinity if possible. + """ + if hasattr(self, "_distinguished_point"): + return self._distinguished_point + + if not self.is_inert(): + # For the the split and ramified case, a point at infinity is chosen, + self._distinguished_point = self.points_at_infinity()[0] + return self._distinguished_point + else: + assert ( + self.base_ring().characteristic() > 0 + ), "in characteristic 0, a distinguished_point needs to be specified" + # in the inert case we choose a point with minimal x-coordinate + for x0 in self.base_ring(): + try: + self._distinguished_point = self.lift_x(x0) + return self._distinguished_point + except ValueError: + pass + + raise ValueError("distinguished point not found") + + def set_distinguished_point(self, P0): + """ + Change the distinguished point of the hyperelliptic curve to P0. + """ + try: + P0 = self.point(P0) + except ValueError: + raise TypeError("P0 must be a point on the hyperelliptic curve") + self._distinguished_point = P0 + + @cached_method + def jacobian(self): + """ + Returns the Jacobian of the hyperelliptic curve. + """ + from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_generic import ( + HyperellipticJacobian_generic, + ) + + return HyperellipticJacobian_generic(self) + + @cached_method + def projective_curve(self): + """ + Returns a singular plane model of the hyperelliptic curve self. + + TODO: renaming to plane_model ? + + EXAMPLES:: + + We consider the hyperelliptic curve with affine equation + `y^2 = x^5 + x` :: + + sage: R. = FiniteField(11)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2) + sage: C = H.projective_curve(); C + Projective Plane Curve over Finite Field of size 11 defined by -x^6 + y^2*z^4 - 2*z^6 + + + Note that the projective coordinates of points on H and their images in C + are in general not the same:: + + sage: P = H.point([9,4,2]) + sage: Q = C.point([9,4,2]) + Traceback (most recent call last): + ... + TypeError: Coordinates [10, 2, 1] do not define a point on Projective Plane Curve over Finite Field of size 11 defined by -x^6 + y^2*z^4 - 2*z^6 + + + However, the affine coordinates coincide:: + + sage: H.affine_coordinates(P) + (10, 6) + sage: Q = C.point([10,6,1]) + + The model C has one singular point at infinity, while H is non-singular + and has two points at infinity. + + sage: H.points_at_infinity() + [(1 : 1 : 0), (1 : 10 : 0)] + sage: [P for P in C.rational_points() if P[2]==0] + [(0 : 1 : 0)] + """ + from sage.schemes.curves.constructor import Curve + + f, h = self._hyperelliptic_polynomials + R, (_, y, z) = PolynomialRing(self.base_ring(), 3, "x, y, z").objgens() + return Curve(R(y**2 + h * y - f).homogenize(z)) + + def rational_points(self, **kwds): + r""" + Find rational points on the hyperelliptic curve. Arguments are passed + on to :meth:`sage.schemes.generic.algebraic_scheme.rational_points`. + + ALGORITHM: + + We use :meth:`points_at_infinity` to compute the points at infinity, and + `sage.schemes.generic.algebraic_scheme.rational_points` on this curve's + :meth:`projective_curve` for the affine points. + + EXAMPLES:: + + For the LMFDB genus 2 curve `932.a.3728.1 `_:: + + sage: R. = PolynomialRing(QQ) + sage: C = HyperellipticCurveSmoothModel(R([0, -1, 1, 0, 1, -2, 1]), R([1])) + sage: C.rational_points(bound=8) + [(1 : 1 : 0), (1 : -1 : 0), (-1 : -3 : 1), (-1 : 2 : 1), (0 : -1 : 1), + (0 : 0 : 1), (1/2 : -5/8 : 1), (1/2 : -3/8 : 1), (1 : -1 : 1), (1 : 0 : 1)] + + Check that :issue:`29509` is fixed for the LMFDB genus 2 curve + `169.a.169.1 `_:: + + sage: C = HyperellipticCurveSmoothModel(R([0, 0, 0, 0, 1, 1]), R([1, 1, 0, 1])) + sage: C.rational_points(bound=10) # long time (6s) + [(1 : 0 : 0), (1 : -1 : 0), (-1 : 0 : 1), (-1 : 1 : 1), (0 : -1 : 1), (0 : 0 : 1)] + + An example over a number field:: + + sage: R. = PolynomialRing(QuadraticField(2)) # needs sage.rings.number_field + sage: C = HyperellipticCurveSmoothModel(R([1, 0, 0, 0, 0, 1])) # needs sage.rings.number_field + sage: C.rational_points(bound=2) + [(1 : 0 : 0), (-1 : 0 : 1), (0 : -1 : 1), (0 : 1 : 1), (1 : -a : 1), (1 : a : 1)] + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 33) + sage: H.rational_points(bound=20) # long time (6s) + [(1 : 1 : 0), (1 : -1 : 0), (-2 : -17 : 1), (-2 : 17 : 1), (2 : -17 : 1), (2 : 17 : 1)] + """ + proj_pts = self.projective_curve().rational_points(**kwds) + return self.points_at_infinity() + [self(*P) for P in proj_pts if P[2] != 0] + + # ------------------------------------------- + # Hacky things + # ------------------------------------------- + + def is_singular(self, *args, **kwargs): + r""" + Returns False, because hyperelliptic curves are smooth projective + curves, as checked on construction. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1) + sage: H.is_singular() + False + """ + return False + + def is_smooth(self): + r""" + Returns True, because hyperelliptic curves are smooth projective + curves, as checked on construction. + + EXAMPLES:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: H.is_smooth() + True + """ + return True + + # ------------------------------------------- + # Odd degree model functions + # ------------------------------------------- + + def odd_degree_model(self): + r""" + Return an odd degree model of self, or raise ValueError if one does not exist over the field of definition. + The term odd degree model refers to a model of the form y^2 = f(x) with deg(f) = 2*g + 1. + + EXAMPLES:: + + sage: x = QQ['x'].gen() + sage: H = HyperellipticCurveSmoothModel((x^2 + 2)*(x^2 + 3)*(x^2 + 5)); H + Hyperelliptic Curve over Rational Field defined by y^2 = x^6 + 10*x^4 + 31*x^2 + 30 + sage: H.odd_degree_model() + Traceback (most recent call last): + ... + ValueError: No odd degree model exists over field of definition + + sage: K2 = QuadraticField(-2, 'a') # needs sage.rings.number_field + sage: Hp2 = H.change_ring(K2).odd_degree_model(); Hp2 # needs sage.rings.number_field + Hyperelliptic Curve over Number Field in a + with defining polynomial x^2 + 2 with a = 1.414213562373095?*I + defined by y^2 = 6*a*x^5 - 29*x^4 - 20*x^2 + 6*a*x + 1 + + sage: K3 = QuadraticField(-3, 'b') # needs sage.rings.number_field + sage: Hp3 = H.change_ring(QuadraticField(-3, 'b')).odd_degree_model(); Hp3 # needs sage.rings.number_field + Hyperelliptic Curve over Number Field in b + with defining polynomial x^2 + 3 with b = 1.732050807568878?*I + defined by y^2 = -4*b*x^5 - 14*x^4 - 20*b*x^3 - 35*x^2 + 6*b*x + 1 + + Of course, ``Hp2`` and ``Hp3`` are isomorphic over the composite + extension. One consequence of this is that odd degree models + reduced over "different" fields should have the same number of + points on their reductions. 43 and 67 split completely in the + compositum, so when we reduce we find: + + sage: # needs sage.rings.number_field + sage: P2 = K2.factor(43)[0][0] + sage: P3 = K3.factor(43)[0][0] + sage: Hp2.change_ring(K2.residue_field(P2)).frobenius_polynomial() + x^4 - 16*x^3 + 134*x^2 - 688*x + 1849 + sage: Hp3.change_ring(K3.residue_field(P3)).frobenius_polynomial() + x^4 - 16*x^3 + 134*x^2 - 688*x + 1849 + + sage: H.change_ring(GF(43)).odd_degree_model().frobenius_polynomial() # needs sage.rings.finite_rings + x^4 - 16*x^3 + 134*x^2 - 688*x + 1849 + + sage: # needs sage.rings.number_field + sage: P2 = K2.factor(67)[0][0] + sage: P3 = K3.factor(67)[0][0] + sage: Hp2.change_ring(K2.residue_field(P2)).frobenius_polynomial() + x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 + sage: Hp3.change_ring(K3.residue_field(P3)).frobenius_polynomial() + x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 + + sage: H.change_ring(GF(67)).odd_degree_model().frobenius_polynomial() # needs sage.rings.finite_rings + x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 + + The case where `h(x)` is nonzero is also supported:: + + sage: HyperellipticCurveSmoothModel(x^5 + 1, 1).odd_degree_model() + Hyperelliptic Curve over Rational Field defined by y^2 = 4*x^5 + 5 + + TESTS:: + + sage: # TODO this used to have names="U, V" + sage: HyperellipticCurveSmoothModel(x^5 + 1).odd_degree_model() + Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + 1 + """ + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) + + f, h = self._hyperelliptic_polynomials + if f.base_ring().characteristic() == 2: + raise ValueError( + "There are no odd degree models over a field with characteristic 2." + ) + if h: + f = 4 * f + h**2 # move h to the right side of the equation + if f.degree() % 2: + # already odd + return HyperellipticCurveSmoothModel(f, 0) + + rts = f.roots(multiplicities=False) + if not rts: + raise ValueError("No odd degree model exists over field of definition") + rt = rts[0] + x = f.parent().gen() + fnew = f((x * rt + 1) / x).numerator() # move rt to "infinity" + + return HyperellipticCurveSmoothModel(fnew, 0) + + def has_odd_degree_model(self): + r""" + Return True if an odd degree model of self exists over the field of definition; False otherwise. + + Use ``odd_degree_model`` to calculate an odd degree model. + + EXAMPLES:: + + sage: x = QQ['x'].0 + sage: HyperellipticCurveSmoothModel(x^5 + x).has_odd_degree_model() + True + sage: HyperellipticCurveSmoothModel(x^6 + x).has_odd_degree_model() + True + sage: HyperellipticCurveSmoothModel(x^6 + x + 1).has_odd_degree_model() + False + """ + try: + return bool(self.odd_degree_model()) + except ValueError: + return False + + # ------------------------------------------- + # Magma + # ------------------------------------------- + + def _magma_init_(self, magma): + """ + Internal function. Returns a string to initialize this elliptic + curve in the Magma subsystem. + + EXAMPLES:: + + sage: # optional - magma + sage: R. = QQ[]; C = HyperellipticCurveSmoothModel(x^3 + x - 1, x); C + Hyperelliptic Curve over Rational Field + defined by y^2 + x*y = x^3 + x - 1 + sage: magma(C) + Hyperelliptic Curve defined by y^2 + x*y = x^3 + x - 1 over Rational Field + sage: R. = GF(9,'a')[]; C = HyperellipticCurveSmoothModel(x^3 + x - 1, x^10); C # needs sage.rings.finite_rings + Hyperelliptic Curve over Finite Field in a of size 3^2 + defined by y^2 + x^10*y = x^3 + x + 2 + sage: D = magma(C); D # needs sage.rings.finite_rings + Hyperelliptic Curve defined by y^2 + (x^10)*y = x^3 + x + 2 over GF(3^2) + sage: D.sage() # needs sage.rings.finite_rings + Hyperelliptic Curve over Finite Field in a of size 3^2 + defined by y^2 + x^10*y = x^3 + x + 2 + """ + f, h = self._hyperelliptic_polynomials + return f"HyperellipticCurve({f._magma_init_(magma)}, {h._magma_init_(magma)})" + + # ------------------------------------------- + # monsky washnitzer things... + # ------------------------------------------- + + def monsky_washnitzer_gens(self): + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + + S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self) + return S.gens() + + def invariant_differential(self): + """ + Returns `dx/2y`, as an element of the Monsky-Washnitzer cohomology + of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: C.invariant_differential() + 1 dx/2y + + """ + from sage.schemes.hyperelliptic_curves_smooth_model import ( + monsky_washnitzer as m_w, + ) + + S = m_w.SpecialHyperellipticQuotientRing(self) + MW = m_w.MonskyWashnitzerDifferentialRing(S) + return MW.invariant_differential() + + # ------------------------------------------- + # Local coordinates + # ------------------------------------------- + + def local_coordinates_at_nonweierstrass(self, P, prec=20, name="t"): + """ + For a non-Weierstrass point `P = (a,b)` on the hyperelliptic + curve `y^2 + y*h(x) = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, + where `t = x - a` is the local parameter. + + INPUT: + + - ``P = (a, b)`` -- a non-Weierstrass point on self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- gen of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 + y(t)*h(x(t)) = f(x(t))` and `t = x - a` + is the local parameter at `P`. + + EXAMPLES:: + + We compute the local coordinates of `H : y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x` at + the point `P = (1, 6)`:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) + sage: P = H(1, 6) + sage: xt, yt = H.local_coordinates_at_nonweierstrass(P, prec=5) + sage: (xt, yt) + (1 + t + O(t^5), 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5)) + + We verify that `y(t) = f(x(t))`:: + + sage: f,_ = H.hyperelliptic_polynomials() + sage: (yt^2 - f(xt)).is_zero() + True + + We can also compute the local coordinates of points on a hyperelliptic curve + with equation `y^2 + h(x)*y = f(x)`:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3*x^5+6*x^4+7*x^3+6*x^2+3*x+1, x^2+x) # 196.a.21952.1 + sage: P = H(-1,1) + sage: xt, yt = H.local_coordinates_at_nonweierstrass(P, prec=5) + sage: (xt, yt) + (-1 + t + O(t^5), 1 - t + 3/2*t^2 - 3/4*t^3 + O(t^5)) + sage: f,h = H.hyperelliptic_polynomials() + sage: (yt^2 + h(xt)*yt -f(xt)).is_zero() + True + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + + """ + if P[2] == 0: + raise TypeError( + "P = %s is a point at infinity. Use local_coordinates_at_infinity instead!" + % P + ) + if self.is_weierstrass_point(P): + raise TypeError( + "P = %s is a Weierstrass point. Use local_coordinates_at_weierstrass instead!" + % P + ) + f, h = self.hyperelliptic_polynomials() + # pol = self.hyperelliptic_polynomials()[0] + a, b = self.affine_coordinates(P) + + L = PowerSeriesRing(self.base_ring(), name, default_prec=prec) + t = L.gen() + K = PowerSeriesRing(L, "x") + f = K(f) + h = K(h) + + ft = f(t + a) + ht = h(t + a) + for _ in range((RR(log(prec, 2))).ceil()): + b = b - (b**2 + b * ht - ft) / (2 * b + ht) + return t + a + O(t ** (prec)), b + O(t ** (prec)) + + def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): + """ + For a finite Weierstrass point P = (a,b) on the hyperelliptic + curve `y^2 + h(x)*y = f(x)`, returns `(x(t), y(t))` such that + `y(t)^2 + h(x(t))*y(t) = f(x(t))`, where `t = y - b` is the local parameter. + + INPUT: + + - ``P`` -- a finite Weierstrass point on self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- gen of the power series ring (default: `t`) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))` and `t = y - b` + is the local parameter at `P = (a,b)`. + + EXAMPLES:: + + We compute the local coordinates of the Weierstrass point `P = (4,0)` + on the hyperelliptic curve `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`. + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) + sage: P = H(4, 0) + sage: xt, yt = H.local_coordinates_at_weierstrass(P, prec=7) + sage: xt + 4 + 1/360*t^2 - 191/23328000*t^4 + 7579/188956800000*t^6 + O(t^7) + sage: yt + t + O(t^7) + + We verify that `y(t) = f(x(t))`:: + + sage: f,_ = H.hyperelliptic_polynomials() + sage: (yt^2 - f(xt)).is_zero() + True + + We compute the local coordinates at the Weierstrass point `(1,-1)` + of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2. + + sage: H = HyperellipticCurveSmoothModel(-x^2, x^3+1) + sage: P = H(1,-1) + sage: xt,yt = H.local_coordinates_at_weierstrass(P) + sage: f,h = H.hyperelliptic_polynomials() + sage: (yt^2 + h(xt)*yt - f(xt)).is_zero() + True + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Francis Clarke (2012-08-26) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + + """ + if P[2] == 0: + raise TypeError( + "P = %s is a point at infinity. Use local_coordinates_at_infinity instead!" + % P + ) + if not self.is_weierstrass_point(P): + raise TypeError( + "P = %s is not a Weierstrass point. Use local_coordinates_at_nonweierstrass instead!" + % P + ) + + L = PowerSeriesRing(self.base_ring(), name, default_prec=prec) + t = L.gen() + f, h = self.hyperelliptic_polynomials() + f_prime = f.derivative() + h_prime = h.derivative() + + a, b = self.affine_coordinates(P) + yt = (t + b).add_bigoh(prec) + yt2 = yt**2 + for _ in range(int(log(prec, 2))): + a = a - (yt2 + yt * h(a) - f(a)) / (yt * h_prime(a) - f_prime(a)) + return (a, yt) + + def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): + """ + For a hyperelliptic curve with ramified model `y^2 + h(x)*y = f(x)`, + return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where + `t = y/x^{g+1}` is the local parameter at the unique (Weierstrass) point + at infinity. + + TODO/NOTE: In the previous implementation `t = x^g/y` was used. + This is not a valid parameter on the smooth model, and the output is necessarily different. + + INPUT: + + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- generator of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y/x^{g+1}` + is the local parameter at infinity + + EXAMPLES:: + + We compute the local coordinates at the point at infinity of the + hyperelliptic curve `y^2 = x^5 - 5*x^2 + 1`. + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 5*x^2 + 1) + sage: xt, yt = H.local_coordinates_at_infinity_ramified(prec=10) + sage: (xt,yt) + (t^-2 - 5*t^4 + t^8 - 75*t^10 + O(t^12), + t^-5 - 15*t + 3*t^5 - 150*t^7 + 90*t^11 + O(t^12)) + + We verify that `y(t)^2 = f(x(t))` and `t = y(t)/x(t)^3`:: + + sage: f,_ = H.hyperelliptic_polynomials() + sage: (yt^2 - f(xt)).is_zero() + True + sage: yt/xt^3 + t + O(t^15) + + The method also works when `h` is nonzero. We compute the local + coordinates of the point at infinity of the hyperelliptic curve + `y^2 + y = x^5 - 9*x^4 + 14*x^3 - 19*x^2 + 11*x - 6`:: + + sage: H = HyperellipticCurveSmoothModel(x^5-9*x^4+14*x^3-19*x^2+11*x-6,1) + sage: f,h = H.hyperelliptic_polynomials() + sage: xt,yt = H.local_coordinates_at_infinity_ramified() + sage: (yt^2 + h(xt)*yt - f(xt)).is_zero() + True + + Note that the point at infinity has to be a Weierstrass point. + + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + if not self.is_ramified(): + raise TypeError( + "The point at infinity is not a Weierstrass point. Use local_coordinates_at_infinity_split instead!" + ) + + g = self.genus() + f, h = self.hyperelliptic_polynomials() + # if h: + # raise NotImplementedError("h = %s is nonzero" % h) + K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) + t = K.gen() + L = PolynomialRing(K, "x") + x = L.gen() + + # note that P = H(1,0,0) + yt = x ** (g + 1) * t + w = yt**2 + h * yt - f + wprime = w.derivative(x) + xt = t**-2 + for _ in range((RR(log(prec + 2) / log(2))).ceil()): + xt = xt - w(xt) / wprime(xt) + yt = xt ** (g + 1) * t + return xt + O(t ** (prec + 2)), yt + O( + t ** (prec + 2) + ) # TODO: Why the prec+2 ? Not sure if this is adapted in the correct way. + + def local_coordinates_at_infinity_split(self, P, prec=20, name="t"): + """ + For a point at infinity P = (a:b:0) on a hyperelliptic curve with + split model `y^2 + h(x)*y = f(x)`, + return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`. + Here `t = a/x` is the local parameter at P. + + INPUT: + + - ``P`` -- a point at infinity of a self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- generator of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y/x^{g+1}` + is the local parameter at infinity + + EXAMPLES:: + + We compute the local coordinates at the point at infinity of the + hyperelliptic curve ` ` + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^6+4*x^4 + 4*x^2+1) + sage: P1 = H(1,-1,0) + sage: xt1,yt1 = H.local_coordinates_at_infinity_split(P1) + + Note the similarity to the local coordinates of the other point at infinity :: + + sage: P2 = H(1,1,0) + sage: xt2,yt2 = H.local_coordinates_at_infinity_split(P2) + sage: xt1 == xt2 and yt1 == - yt2 + True + + Similarly, if `h` is nonzero, the relation between the local coordinates at the + points at infinity is obtained from the hyperelliptic involution:: + + sage: H = HyperellipticCurveSmoothModel(-x^5, x^3+x+1) + sage: f,h = H.hyperelliptic_polynomials() + sage: P1 = H(1,0,0) + sage: P2 = H(1,-1,0) + sage: xt1,yt1 = H.local_coordinates_at_infinity_split(P1) + sage: xt2,yt2 = H.local_coordinates_at_infinity_split(P2) + sage: (yt1 + yt2 + h(xt1)).is_zero() + True + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + if not self.is_split(): + raise TypeError( + "The point at infinity is a Weierstrass point. Use local_coordinates_at_infinity_ramified instead!" + ) + if not P[2] == 0: + raise TypeError( + "P = %s is not a point at infinity. Use local_coordinates_at_nonweierstrass or local_coordinates_at_weierstrass instead!" + % P + ) + + K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) + t = K.gen() + + # note that P = H(a,b,0) + xt = P[0] / t + f, h = self.hyperelliptic_polynomials() + ft = f(xt) + ht = h(xt) + yt = P[1] / t**3 + for _ in range((RR(log(prec + 2) / log(2))).ceil()): + yt = yt - (yt**2 + ht * yt - ft) / (2 * yt + ht) + return xt + O(t ** (prec + 2)), yt + O(t ** (prec + 2)) + + def local_coord(self, P, prec=20, name="t"): + """ + For point `P = (a,b)` on the hyperelliptic curve + `y^2 + y*h(x) = f(x)`, return `(x(t), y(t))` such that + `(y(t))^2 + h(x(t))*y(t) = f(x(t))`, where `t` is the local parameter at + that point. + + INPUT: + + - ``P`` -- a point on self + - ``prec`` -- desired precision of the local coordinates + - ``name`` -- generator of the power series ring (default: ``t``) + + OUTPUT: + + `(x(t),y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))`, where `t` + is the local parameter at `P` + + EXAMPLES:: + + We compute the local coordinates of several points of the curve with + defining equation `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`. :: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) + sage: H.local_coord(H(1 ,6), prec=5) + (1 + t + O(t^5), 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5)) + sage: H.local_coord(H(4, 0), prec=7) + (4 + 1/360*t^2 - 191/23328000*t^4 + 7579/188956800000*t^6 + O(t^7), t + O(t^7)) + sage: H.local_coord(H(1, 0, 0), prec=5) + (t^-2 - 23*t^2 + 18*t^4 - 1018*t^6 + O(t^7), + t^-5 - 69*t^-1 + 54*t - 1467*t^3 + 3726*t^5 + O(t^6)) + + We compute the local coordinates of several points of the curve with + defining equation `y^2 + (x^3 + 1)*y = x^4 + 2*x^3 + x^2 - x`. :: + + sage: H = HyperellipticCurveSmoothModel(x^4+2*x^3+x^2-x,x^3+1) + sage: H.local_coord(H(1,-3,1), prec=5) + (1 + t + O(t^5), -3 - 5*t - 3*t^2 - 3/4*t^3 - 3/16*t^4 + O(t^5)) + sage: H.local_coord(H(1,-1,0), prec=5) + (t^-1 + O(t^7), -t^-3 - t^-1 - 3 + 6*t^2 + 6*t^3 - 12*t^4 - 42*t^5 + O(t^6)) + + AUTHOR: + + - Jennifer Balakrishnan (2007-12) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + if P[2] == 0: + if self.is_ramified(): + return self.local_coordinates_at_infinity_ramified(prec, name) + else: + return self.local_coordinates_at_infinity_split(P, prec, name) + + elif self.is_weierstrass_point(P): + return self.local_coordinates_at_weierstrass(P, prec, name) + else: + return self.local_coordinates_at_nonweierstrass(P, prec, name) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py new file mode 100644 index 00000000000..68e79bb604e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -0,0 +1,1426 @@ +from sage.functions.log import log +from sage.matrix.constructor import matrix +from sage.modules.free_module import VectorSpace +from sage.modules.free_module_element import vector +from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF +from sage.rings.infinity import Infinity +from sage.rings.integer_ring import ZZ +from sage.rings.padics.factory import Qp as pAdicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ, RationalField +from sage.rings.real_mpfr import RR +from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic + + +class HyperellipticCurveSmoothModel_padic_field( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + def __init__(self, projective_model, f, h, genus): + super().__init__(projective_model, f, h, genus) + + # The functions below were prototyped at the 2007 Arizona Winter School by + # Robert Bradshaw and Ralf Gerkmann, working with Miljan Brakovevic and + # Kiran Kedlaya + # All of the below is with respect to the Monsky Washnitzer cohomology. + + def local_analytic_interpolation(self, P, Q): + """ + For points `P`, `Q` in the same residue disc, + this constructs an interpolation from `P` to `Q` + (in weighted homogeneous coordinates) in a power series in + the local parameter `t`, with precision equal to + the `p`-adic precision of the underlying ring. + + INPUT: + + - P and Q points on self in the same residue disc + + OUTPUT: + + Returns a point `X(t) = ( x(t) : y(t) : z(t) )` such that: + + (1) `X(0) = P` and `X(1) = Q` if `P, Q` are not in the infinite disc + (2) `X(P[1]/P[0]^(g+1)) = P` and `X(Q[1]/Q[0]^(g+1)) = Q` if `P, Q` are in the infinite disc + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + + A non-Weierstrass disc:: + + sage: P = HK(0,3) + sage: Q = HK(5, 3 + 3*5^2 + 2*5^3 + 3*5^4 + 2*5^5 + 2*5^6 + 3*5^7 + O(5^8)) + sage: x,y,z, = HK.local_analytic_interpolation(P,Q) + sage: x(0) == P[0], x(1) == Q[0], y(0) == P[1], y.polynomial()(1) == Q[1] + (True, True, True, True) + + A finite Weierstrass disc:: + + sage: P = HK.lift_x(1 + 2*5^2) + sage: Q = HK.lift_x(1 + 3*5^2) + sage: x,y,z = HK.local_analytic_interpolation(P,Q) + sage: x(0) == P[0], x.polynomial()(1) == Q[0], y(0) == P[1], y(1) == Q[1] + (True, True, True, True) + + The infinite disc:: + + sage: g = HK.genus() + sage: P = HK.lift_x(5^-2) + sage: Q = HK.lift_x(4*5^-2) + sage: x,y,z = HK.local_analytic_interpolation(P,Q) + sage: x = x/z + sage: y = y/z^(g+1) + sage: x(P[1]/P[0]^(g+1)) == P[0] + True + sage: x(Q[1]/Q[0]^(g+1)) == Q[0] + True + sage: y(P[1]/P[0]^(g+1)) == P[1] + True + sage: y(Q[1]/Q[0]^(g+1)) == Q[1] + True + + An error if points are not in the same disc:: + + sage: x,y,z = HK.local_analytic_interpolation(P,HK(1,0)) + Traceback (most recent call last): + ... + ValueError: (5^-2 + O(5^6) : 4*5^-3 + 4*5^-2 + 4*5^-1 + 4 + 4*5 + 3*5^3 + 5^4 + O(5^5) : 1 + O(5^8)) and (1 + O(5^8) : 0 : 1 + O(5^8)) are not in the same residue disc + + TESTS: + + Check that :issue:`26005` is fixed:: + + sage: L = Qp(5, 100) + sage: HL = H.change_ring(L) + sage: P = HL.lift_x(1 + 2*5^2) + sage: Q = HL.lift_x(1 + 3*5^2) + sage: x,y,z = HL.local_analytic_interpolation(P, Q) + sage: x.polynomial().degree() + 98 + + AUTHORS: + + - Robert Bradshaw (2007-03) + - Jennifer Balakrishnan (2010-02) + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model + """ + + # TODO: adapt to general curve form. + prec = self.base_ring().precision_cap() + if not self.is_same_disc(P, Q): + raise ValueError(f"{P} and {Q} are not in the same residue disc") + disc = self.residue_disc(P) + t = PowerSeriesRing(self.base_ring(), "t", prec).gen(0) + if disc == self.change_ring(self.base_ring().residue_field())( + 1, 0, 0 + ): # Infinite disc + x, y = self.local_coordinates_at_infinity_ramified(2 * prec) + g = self.genus() + return (x * t**2, y * t ** (2 * g + 2), t ** (2)) + if disc[1] != 0: # non-Weierstrass disc + x = P[0] + t * (Q[0] - P[0]) + pts = self.lift_x(x, all=True) + if pts[0][1][0] == P[1]: + return pts[0] + else: + return pts[1] + else: # Weierstrass disc + S = self.find_char_zero_weierstrass_point(P) + x, y = self.local_coord(S, prec) + a = P[1] + b = Q[1] - P[1] + y = a + b * t + x = x.polynomial()(y).add_bigoh(x.prec()) + return (x, y, 1) + + def is_in_weierstrass_disc(self, P): + """ + Checks if `P` is in a Weierstrass disc. + + EXAMPLES:: + + For odd degree models, the points with `y`-coordinate equivalent to zero + are contained in a Weierstrass discs:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK(0,3) + sage: HK.is_in_weierstrass_disc(P) + False + sage: Q = HK(1,0,0) + sage: HK.is_in_weierstrass_disc(Q) + True + sage: S = HK(1,0) + sage: HK.is_in_weierstrass_disc(S) + True + sage: T = HK.lift_x(1+3*5^2); T + (1 + 3*5^2 + O(5^8) : 3*5 + 4*5^2 + 5^4 + 3*5^5 + 5^6 + O(5^7) : 1 + O(5^8)) + sage: HK.is_in_weierstrass_disc(T) + True + + The method is also implemented for general models of hyperelliptic + curves:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3, x^2+1) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1+3*5+4*5^2+4*5^3); P + (1 + 3*5 + 4*5^2 + 4*5^3 + O(5^8) : 4 + 5 + 4*5^2 + 5^3 + 3*5^4 + 5^5 + O(5^6) : 1 + O(5^8)) + sage: HK.is_in_weierstrass_disc(P) + True + + Note that `y = - y - h(x)` for Weierstrass points. We check that this relation + is satisfied for the point above (mod p):: + + sage: f,h = H.hyperelliptic_polynomials() + sage: (2*P[1] + h(P[0])).valuation() > 0 + True + + AUTHOR: + + - Jennifer Balakrishnan (2010-02) + """ + + f, h = self.hyperelliptic_polynomials() + if P[2].valuation() > P[0].valuation(): # infinite W-disc + return self.is_ramified() + else: # affine W-disc + x, y = self.affine_coordinates(P) + return (2 * y + h(x)).valuation() > 0 + + def find_char_zero_weierstrass_point(self, Q): + """ + Given `Q` a point on self in a Weierstrass disc, finds the + center of the Weierstrass disc (if defined over self.base_ring()) + + EXAMPLES:: + + Examples for a hyperelliptic curve with odd degree model:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1 + 2*5^2) + sage: Q = HK.lift_x(5^-2) + sage: S = HK(1,0) + sage: T = HK(1,0,0) + sage: HK.find_char_zero_weierstrass_point(P) + (1 + O(5^8) : 0 : 1 + O(5^8)) + sage: HK.find_char_zero_weierstrass_point(Q) + (1 + O(5^8) : 0 : 0) + sage: HK.find_char_zero_weierstrass_point(S) + (1 + O(5^8) : 0 : 1 + O(5^8)) + sage: HK.find_char_zero_weierstrass_point(T) + (1 + O(5^8) : 0 : 0) + + An example for a hyperelltiptic curve with split model:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3, x^2+1) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1+3*5+4*5^2+4*5^3) + sage: HK.find_char_zero_weierstrass_point(P) + (1 + 3*5 + 4*5^2 + 4*5^3 + 3*5^4 + 2*5^6 + 4*5^7 + O(5^8) : 4 + 5 + 3*5^2 + 4*5^3 + 2*5^5 + 4*5^6 + 4*5^7 + O(5^8) : 1 + O(5^8)) + + The input needs to be a point in a Weierstrass disc, + otherwise an error is returned:: + + sage: Q = HK.point([1,1,0]) + sage: HK.find_char_zero_weierstrass_point(Q) + Traceback (most recent call last): + ... + ValueError: (1 + O(5^8) : 1 + O(5^8) : 0) is not in a Weierstrass disc. + + AUTHOR: + + - Jennifer Balakrishnan + """ + if not self.is_in_weierstrass_disc(Q): + raise ValueError("%s is not in a Weierstrass disc." % Q) + points = self.rational_weierstrass_points() + for P in points: + if self.is_same_disc(P, Q): + return P + + def residue_disc(self, P): + """ + Gives the residue disc of `P` + + TODO: Really, this gives the reduction over the residue field. Isn't the residue disc a disc over the p-adics? + Maybe rename to residue_point or reduction ? + + EXAMPLES:: + + We compute the residue discs for diffferent points on the elliptic curve + `y^2 = x^3 = 10*x + 9` over the `5`-adics:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1 + 2*5^2) + sage: HK.residue_disc(P) + (1 : 0 : 1) + sage: Q = HK(0,3) + sage: HK.residue_disc(Q) + (0 : 3 : 1) + sage: S = HK.lift_x(5^-2) + sage: HK.residue_disc(S) + (1 : 0 : 0) + sage: T = HK(1,0,0) + sage: HK.residue_disc(T) + (1 : 0 : 0) + + We can also compute residue discs for points on curves with a split or inert model:: + + sage: H = HyperellipticCurveSmoothModel(x^6+3, x^2+1) + sage: H.is_split() + True + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1+3*5+4*5^2+4*5^3) + sage: Pbar = HK.residue_disc(P); Pbar + (1 : 4 : 1) + + We note that `P` is in a Weierstrass disc and its reduction is indeed a Weierstrass point. + sage: HK.is_in_weierstrass_disc(P) + True + sage: HF = HK.change_ring(FiniteField(5)) + sage: HF.is_weierstrass_point(Pbar) + True + + AUTHOR: + + - Jennifer Balakrishnan + """ + + F = self.base_ring().residue_field() + HF = self.change_ring(F) + + if not P[2].is_zero(): + xP, yP = self.affine_coordinates(P) + xPv = xP.valuation() + yPv = yP.valuation() + + if yPv > 0: + if xPv > 0: + return HF(0, 0, 1) + if xPv == 0: + return HF(xP.expansion(0), 0, 1) + elif yPv == 0: + if xPv > 0: + return HF(0, yP.expansion(0), 1) + if xPv == 0: + return HF(xP.expansion(0), yP.expansion(0), 1) + + # in any other case, the point reduces to infinity. + if HF.is_ramified(): + return HF.points_at_infinity()[0] + elif HF.is_split(): + [Q1, Q2] = HF.points_at_infinity() + alpha = P[1].expansion(0) / P[0].expansion(0) ** (self.genus() + 1) + if ( + alpha == Q1[1] + ): # we assume that the points at infinity are normalized, w.r.t. x ! + return Q1 + if alpha == Q2[1]: + return Q2 + else: + raise ValueError("Unexpected behaviour.") + else: + raise ValueError( + "The reduction of the hyperelliptic curve is inert. This case should not appear." + ) + + def is_same_disc(self, P, Q): + """ + Checks if `P,Q` are in same residue disc + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: P = HK.lift_x(1 + 2*5^2) + sage: Q = HK.lift_x(5^-2) + sage: S = HK(1,0) + sage: HK.is_same_disc(P,Q) + False + sage: HK.is_same_disc(P,S) + True + sage: HK.is_same_disc(Q,S) + False + """ + return self.residue_disc(P) == self.residue_disc(Q) + + def tiny_integrals(self, F, P, Q): + r""" + Evaluate the integrals of `f_i dx/2y` from `P` to `Q` for each `f_i` in `F` + by formally integrating a power series in a local parameter `t` + + `P` and `Q` MUST be in the same residue disc for this result to make sense. + + INPUT: + + - F a list of functions `f_i` + - P a point on self + - Q a point on self (in the same residue disc as P) + + OUTPUT: + + The integrals `\int_P^Q f_i dx/2y` + + EXAMPLES:: + + sage: K = pAdicField(17, 5) + sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a + sage: P = E(K(14/3), K(11/2)) + sage: TP = E.teichmuller(P); + sage: x,y = E.monsky_washnitzer_gens() + sage: E.tiny_integrals([1,x],P, TP) == E.tiny_integrals_on_basis(P,TP) + True + + :: + + sage: K = pAdicField(11, 5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C.lift_x(11^(-2)) + sage: Q = C.lift_x(3*11^(-2)) + sage: C.tiny_integrals([1],P,Q) + (5*11^3 + 7*11^4 + 2*11^5 + 6*11^6 + 11^7 + O(11^8)) + + Note that this fails if the points are not in the same residue disc:: + + sage: S = C(0,1/4) + sage: C.tiny_integrals([1,x,x^2,x^3],P,S) + Traceback (most recent call last): + ... + ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and + (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc + + """ + if self.hyperelliptic_polynomials()[1]: + raise NotImplementedError + if not self.is_ramified(): + raise NotImplementedError + + g = self.genus() + x, y, z = self.local_analytic_interpolation(P, Q) # homogeneous coordinates + x = x / z + y = y / z ** (g + 1) + dt = x.derivative() / (2 * y) + integrals = [] + for f in F: + try: + f_dt = f(x, y) * dt + except TypeError: # if f is a constant, not callable + f_dt = f * dt + if x.valuation() != -2: + I = sum( + f_dt[n] / (n + 1) for n in range(f_dt.degree() + 1) + ) # \int_0^1 f dt + else: + If_dt = f_dt.integral().laurent_polynomial() + I = If_dt(Q[1] / Q[0] ** (g + 1)) - If_dt(P[1] / P[0] ** (g + 1)) + integrals.append(I) + return vector(integrals) + + def tiny_integrals_on_basis(self, P, Q): + r""" + Evaluate the integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + by formally integrating a power series in a local parameter `t`. + `P` and `Q` MUST be in the same residue disc for this result to make sense. + + INPUT: + + - P a point on self + - Q a point on self (in the same residue disc as P) + + OUTPUT: + + The integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + + EXAMPLES:: + + sage: K = pAdicField(17, 5) + sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a + sage: P = E(K(14/3), K(11/2)) + sage: TP = E.teichmuller(P); + sage: E.tiny_integrals_on_basis(P, TP) + (17 + 14*17^2 + 17^3 + 8*17^4 + O(17^5), 16*17 + 5*17^2 + 8*17^3 + 14*17^4 + O(17^5)) + + :: + + sage: K = pAdicField(11, 5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C.lift_x(11^(-2)) + sage: Q = C.lift_x(3*11^(-2)) + sage: C.tiny_integrals_on_basis(P,Q) + (5*11^3 + 7*11^4 + 2*11^5 + 6*11^6 + 11^7 + O(11^8), 10*11 + 2*11^3 + 3*11^4 + 5*11^5 + O(11^6), 5*11^-1 + 8 + 4*11 + 10*11^2 + 7*11^3 + O(11^4), 2*11^-3 + 11^-2 + 11^-1 + 10 + 8*11 + O(11^2)) + + + Note that this fails if the points are not in the same residue disc:: + + sage: S = C(0,1/4) + sage: C.tiny_integrals_on_basis(P,S) + Traceback (most recent call last): + ... + ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc + + """ + if P == Q: + V = VectorSpace(self.base_ring(), 2 * self.genus()) + return V(0) + R = PolynomialRing(self.base_ring(), ["x", "y"]) + x, y = R.gens() + return self.tiny_integrals([x**i for i in range(2 * self.genus())], P, Q) + + def teichmuller(self, P): + r""" + Find a Teichm\:uller point in the same residue class of `P`. + + Because this lift of frobenius acts as `x \mapsto x^p`, + take the Teichmuller lift of `x` and then find a matching `y` + from that. + + EXAMPLES:: + + sage: K = pAdicField(7, 5) + sage: R. = K[] + sage: E = HyperellipticCurveSmoothModel(x^3 - 31/3*x - 2501/108) # 11a + sage: P = E(K(14/3), K(11/2)) + sage: E.frobenius(P) == P + False + sage: TP = E.teichmuller(P); TP + (0 : 2 + 3*7 + 3*7^2 + 3*7^4 + O(7^5) : 1 + O(7^5)) + sage: E.frobenius(TP) == TP + True + sage: (TP[0] - P[0]).valuation() > 0, (TP[1] - P[1]).valuation() > 0 + (True, True) + """ + K = P[0].parent() + x = K.teichmuller(P[0]) + pts = self.lift_x(x, all=True) + if (pts[0][1] - P[1]).valuation() > 0: + return pts[0] + else: + return pts[1] + + def coleman_integrals_on_basis(self, P, Q, algorithm=None): + r""" + Computes the Coleman integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + + INPUT: + + - P point on self + - Q point on self + - algorithm (optional) = None (uses Frobenius) or teichmuller (uses Teichmuller points) + + OUTPUT: + + the Coleman integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` + + EXAMPLES:: + + sage: K = pAdicField(11, 5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C.lift_x(2) + sage: Q = C.lift_x(3) + sage: C.coleman_integrals_on_basis(P, Q) + (9*11^2 + 7*11^3 + 5*11^4 + O(11^5), 11 + 3*11^2 + 7*11^3 + 11^4 + O(11^5), 10*11 + 11^2 + 5*11^3 + 5*11^4 + O(11^5), 3 + 9*11^2 + 6*11^3 + 11^4 + O(11^5)) + sage: C.coleman_integrals_on_basis(P, Q, algorithm='teichmuller') + (9*11^2 + 7*11^3 + 5*11^4 + O(11^5), 11 + 3*11^2 + 7*11^3 + 11^4 + O(11^5), 10*11 + 11^2 + 5*11^3 + 5*11^4 + O(11^5), 3 + 9*11^2 + 6*11^3 + 11^4 + O(11^5)) + + :: + + sage: K = pAdicField(11,5) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C(11^-2, 10*11^-5 + 10*11^-4 + 10*11^-3 + 2*11^-2 + 10*11^-1) + sage: Q = C(3*11^-2, 11^-5 + 11^-3 + 10*11^-2 + 7*11^-1) + sage: C.coleman_integrals_on_basis(P, Q) + (6*11^3 + 3*11^4 + 8*11^5 + 4*11^6 + 9*11^7 + O(11^8), 11 + 10*11^2 + 8*11^3 + 7*11^4 + 5*11^5 + O(11^6), 6*11^-1 + 2 + 6*11 + 3*11^3 + O(11^4), 9*11^-3 + 9*11^-2 + 9*11^-1 + 2*11 + O(11^2)) + + :: + + sage: R = C(0,1/4) + sage: a = C.coleman_integrals_on_basis(P,R) # long time (7s on sage.math, 2011) + sage: b = C.coleman_integrals_on_basis(R,Q) # long time (9s on sage.math, 2011) + sage: c = C.coleman_integrals_on_basis(P,Q) # long time + sage: a+b == c # long time + True + + :: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: S = HK(1,0) + sage: P = HK(0,3) + sage: T = HK(1,0,0) + sage: Q = HK.lift_x(5^-2) + sage: R = HK.lift_x(4*5^-2) + sage: HK.coleman_integrals_on_basis(S,P) + (2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9), 5 + 2*5^2 + 4*5^3 + 2*5^4 + 3*5^6 + 4*5^7 + 2*5^8 + O(5^9)) + sage: HK.coleman_integrals_on_basis(T,P) + (2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9), 5 + 2*5^2 + 4*5^3 + 2*5^4 + 3*5^6 + 4*5^7 + 2*5^8 + O(5^9)) + sage: HK.coleman_integrals_on_basis(P,S) == -HK.coleman_integrals_on_basis(S,P) + True + sage: HK.coleman_integrals_on_basis(S,Q) + (5 + O(5^4), 4*5^-1 + 4 + 4*5 + 4*5^2 + O(5^3)) + sage: HK.coleman_integrals_on_basis(Q,R) + (5 + 2*5^2 + 2*5^3 + 2*5^4 + 3*5^5 + 3*5^6 + 3*5^7 + 5^8 + O(5^9), 3*5^-1 + 2*5^4 + 5^5 + 2*5^6 + O(5^7)) + sage: HK.coleman_integrals_on_basis(S,R) == HK.coleman_integrals_on_basis(S,Q) + HK.coleman_integrals_on_basis(Q,R) + True + sage: HK.coleman_integrals_on_basis(T,T) + (0, 0) + sage: HK.coleman_integrals_on_basis(S,T) + (0, 0) + + AUTHORS: + + - Robert Bradshaw (2007-03): non-Weierstrass points + - Jennifer Balakrishnan and Robert Bradshaw (2010-02): Weierstrass points + """ + + if self.hyperelliptic_polynomials()[1]: + raise NotImplementedError + if not self.is_ramified(): + raise NotImplementedError + + from sage.misc.profiler import Profiler + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + + prof = Profiler() + prof("setup") + K = self.base_ring() + p = K.prime() + prec = K.precision_cap() + g = self.genus() + dim = 2 * g + V = VectorSpace(K, dim) + # if P or Q is Weierstrass, use the Frobenius algorithm + if self.is_weierstrass_point(P): + if self.is_weierstrass_point(Q): + return V(0) + else: + PP = None + QQ = Q + TP = None + TQ = self.frobenius(Q) + elif self.is_weierstrass_point(Q): + PP = P + QQ = None + TQ = None + TP = self.frobenius(P) + elif self.is_same_disc(P, Q): + return self.tiny_integrals_on_basis(P, Q) + elif algorithm == "teichmuller": + prof("teichmuller") + PP = TP = self.teichmuller(P) + QQ = TQ = self.teichmuller(Q) + else: + prof("frobPQ") + TP = self.frobenius(P) + TQ = self.frobenius(Q) + PP, QQ = P, Q + prof("tiny integrals") + if TP is None: + P_to_TP = V(0) + else: + if TP is not None: + TPv = (TP[0] ** g / TP[1]).valuation() + xTPv = TP[0].valuation() + else: + xTPv = TPv = +Infinity + if TQ is not None: + TQv = (TQ[0] ** g / TQ[1]).valuation() + xTQv = TQ[0].valuation() + else: + xTQv = TQv = +Infinity + offset = (2 * g - 1) * max(TPv, TQv) + if offset == +Infinity: + offset = (2 * g - 1) * min(TPv, TQv) + if ( + offset > prec + and (xTPv < 0 or xTQv < 0) + and ( + self.residue_disc(P) == self.change_ring(GF(p))(1, 0, 0) + or self.residue_disc(Q) == self.change_ring(GF(p))(1, 0, 0) + ) + ): + newprec = offset + prec + K = pAdicField(p, newprec) + A = PolynomialRing(RationalField(), "x") + f = A(self.hyperelliptic_polynomials()[0]) + self = HyperellipticCurveSmoothModel(f).change_ring(K) + xP = P[0] + xPv = xP.valuation() + xPnew = K(sum(c * p ** (xPv + i) for i, c in enumerate(xP.expansion()))) + PP = P = self.lift_x(xPnew) + TP = self.frobenius(P) + xQ = Q[0] + xQv = xQ.valuation() + xQnew = K(sum(c * p ** (xQv + i) for i, c in enumerate(xQ.expansion()))) + QQ = Q = self.lift_x(xQnew) + TQ = self.frobenius(Q) + V = VectorSpace(K, dim) + P_to_TP = V(self.tiny_integrals_on_basis(P, TP)) + if TQ is None: + TQ_to_Q = V(0) + else: + TQ_to_Q = V(self.tiny_integrals_on_basis(TQ, Q)) + prof("mw calc") + try: + M_frob, forms = self._frob_calc + except AttributeError: + M_frob, forms = self._frob_calc = ( + monsky_washnitzer.matrix_of_frobenius_hyperelliptic(self) + ) + prof("eval f") + R = forms[0].base_ring() + try: + prof("eval f %s" % R) + if PP is None: + L = [-ff(R(QQ[0]), R(QQ[1])) for ff in forms] ##changed + elif QQ is None: + L = [ff(R(PP[0]), R(PP[1])) for ff in forms] + else: + L = [ff(R(PP[0]), R(PP[1])) - ff(R(QQ[0]), R(QQ[1])) for ff in forms] + except ValueError: + prof("changing rings") + forms = [ff.change_ring(self.base_ring()) for ff in forms] + prof("eval f %s" % self.base_ring()) + if PP is None: + L = [-ff(QQ[0], QQ[1]) for ff in forms] ##changed + elif QQ is None: + L = [ff(PP[0], PP[1]) for ff in forms] + else: + L = [ff(PP[0], PP[1]) - ff(QQ[0], QQ[1]) for ff in forms] + b = V(L) + if PP is None: + b -= TQ_to_Q + elif QQ is None: + b -= P_to_TP + elif algorithm != "teichmuller": + b -= P_to_TP + TQ_to_Q + prof("lin alg") + M_sys = matrix(K, M_frob).transpose() - 1 + TP_to_TQ = M_sys ** (-1) * b + prof("done") + if algorithm == "teichmuller": + return P_to_TP + TP_to_TQ + TQ_to_Q + else: + return TP_to_TQ + + coleman_integrals_on_basis_hyperelliptic = coleman_integrals_on_basis + + def coleman_integral(self, w, P, Q, algorithm="None"): + r""" + Return the Coleman integral `\int_P^Q w`. + + INPUT: + + - w differential (if one of P,Q is Weierstrass, w must be odd) + - P point on self + - Q point on self + - algorithm (optional) = None (uses Frobenius) or teichmuller (uses Teichmuller points) + + OUTPUT: + + the Coleman integral `\int_P^Q w` + + EXAMPLES: + + Example of Leprevost from Kiran Kedlaya + The first two should be zero as `(P-Q) = 30(P-Q)` in the Jacobian + and `dx/2y` and `x dx/2y` are holomorphic. :: + + sage: K = pAdicField(11, 6) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C(-1, 1); P1 = C(-1, -1) + sage: Q = C(0, 1/4); Q1 = C(0, -1/4) + sage: x, y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w.coleman_integral(P, Q) + O(11^6) + sage: C.coleman_integral(x*w, P, Q) + O(11^6) + sage: C.coleman_integral(x^2*w, P, Q) + 7*11 + 6*11^2 + 3*11^3 + 11^4 + 5*11^5 + O(11^6) + + :: + + sage: p = 71; m = 4 + sage: K = pAdicField(p, m) + sage: x = polygen(K) + sage: C = HyperellipticCurveSmoothModel(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) + sage: P = C(-1, 1); P1 = C(-1, -1) + sage: Q = C(0, 1/4); Q1 = C(0, -1/4) + sage: x, y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w.integrate(P, Q), (x*w).integrate(P, Q) + (O(71^4), O(71^4)) + sage: R, R1 = C.lift_x(4, all=True) + sage: w.integrate(P, R) + 50*71 + 3*71^2 + 43*71^3 + O(71^4) + sage: w.integrate(P, R) + w.integrate(P1, R1) + O(71^4) + + A simple example, integrating dx:: + + sage: R. = QQ['x'] + sage: E= HyperellipticCurveSmoothModel(x^3-4*x+4) + sage: K = Qp(5,10) + sage: EK = E.change_ring(K) + sage: P = EK(2, 2) + sage: Q = EK.teichmuller(P) + sage: x, y = EK.monsky_washnitzer_gens() + sage: EK.coleman_integral(x.diff(), P, Q) + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10) + sage: Q[0] - P[0] + 5 + 2*5^2 + 5^3 + 3*5^4 + 4*5^5 + 2*5^6 + 3*5^7 + 3*5^9 + O(5^10) + + Yet another example:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x*(x-1)*(x+9)) + sage: K = Qp(7,10) + sage: HK = H.change_ring(K) + sage: from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer as mw + sage: M_frob, forms = mw.matrix_of_frobenius_hyperelliptic(HK) + sage: w = HK.invariant_differential() + sage: x,y = HK.monsky_washnitzer_gens() + sage: f = forms[0] + sage: S = HK(9,36) + sage: Q = HK.teichmuller(S) + sage: P = HK(-1,4) + sage: b = x*w*w._coeff.parent()(f) + sage: HK.coleman_integral(b,P,Q) + 7 + 7^2 + 4*7^3 + 5*7^4 + 3*7^5 + 7^6 + 5*7^7 + 3*7^8 + 4*7^9 + 4*7^10 + O(7^11) + + :: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3+1) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: w = HK.invariant_differential() + sage: P = HK(0,1) + sage: Q = HK(5, 1 + 3*5^3 + 2*5^4 + 2*5^5 + 3*5^7) + sage: x,y = HK.monsky_washnitzer_gens() + sage: (2*y*w).coleman_integral(P,Q) + 5 + O(5^9) + sage: xloc,yloc,zloc = HK.local_analytic_interpolation(P,Q) + sage: I2 = (xloc.derivative()/(2*yloc)).integral() + sage: I2.polynomial()(1) - I2(0) + 3*5 + 2*5^2 + 2*5^3 + 5^4 + 4*5^6 + 5^7 + O(5^9) + sage: HK.coleman_integral(w,P,Q) + 3*5 + 2*5^2 + 2*5^3 + 5^4 + 4*5^6 + 5^7 + O(5^9) + + Integrals involving Weierstrass points:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,8) + sage: HK = H.change_ring(K) + sage: S = HK(1,0) + sage: P = HK(0,3) + sage: negP = HK(0,-3) + sage: T = HK(1,0,0) + sage: w = HK.invariant_differential() + sage: x,y = HK.monsky_washnitzer_gens() + sage: HK.coleman_integral(w*x^3,S,T) + 0 + sage: HK.coleman_integral(w*x^3,T,S) + 0 + sage: HK.coleman_integral(w,S,P) + 2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9) + sage: HK.coleman_integral(w,T,P) + 2*5^2 + 5^4 + 5^5 + 3*5^6 + 3*5^7 + 2*5^8 + O(5^9) + sage: HK.coleman_integral(w*x^3,T,P) + 5^2 + 2*5^3 + 3*5^6 + 3*5^7 + O(5^8) + sage: HK.coleman_integral(w*x^3,S,P) + 5^2 + 2*5^3 + 3*5^6 + 3*5^7 + O(5^8) + sage: HK.coleman_integral(w, P, negP, algorithm='teichmuller') + 5^2 + 4*5^3 + 2*5^4 + 2*5^5 + 3*5^6 + 2*5^7 + 4*5^8 + O(5^9) + sage: HK.coleman_integral(w, P, negP) + 5^2 + 4*5^3 + 2*5^4 + 2*5^5 + 3*5^6 + 2*5^7 + 4*5^8 + O(5^9) + + AUTHORS: + + - Robert Bradshaw (2007-03) + - Kiran Kedlaya (2008-05) + - Jennifer Balakrishnan (2010-02) + + """ + # TODO: exceptions for general curve form + # TODO: implement Jacobians and show the relationship directly + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + + K = self.base_ring() + prec = K.precision_cap() + S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self, K) + MW = monsky_washnitzer.MonskyWashnitzerDifferentialRing(S) + w = MW(w) + f, vec = w.reduce_fast() + basis_values = self.coleman_integrals_on_basis(P, Q, algorithm) + dim = len(basis_values) + x, y = self.local_coordinates_at_infinity_ramified(2 * prec) + if self.is_weierstrass_point(P): + if self.is_weierstrass_point(Q): + return 0 + elif f == 0: + return sum([vec[i] * basis_values[i] for i in range(dim)]) + elif ( + w._coeff(x, -y) * x.derivative() / (-2 * y) + + w._coeff(x, y) * x.derivative() / (2 * y) + == 0 + ): + return ( + self.coleman_integral( + w, self(Q[0], -Q[1]), self(Q[0], Q[1]), algorithm + ) + / 2 + ) + else: + raise ValueError( + "The differential is not odd: use coleman_integral_from_weierstrass_via_boundary" + ) + + elif self.is_weierstrass_point(Q): + if f == 0: + return sum([vec[i] * basis_values[i] for i in range(dim)]) + elif ( + w._coeff(x, -y) * x.derivative() / (-2 * y) + + w._coeff(x, y) * x.derivative() / (2 * y) + == 0 + ): + return ( + -self.coleman_integral( + w, self(P[0], -P[1]), self(P[0], P[1]), algorithm + ) + / 2 + ) + else: + raise ValueError( + "The differential is not odd: use coleman_integral_from_weierstrass_via_boundary" + ) + else: + return ( + f(Q[0], Q[1]) + - f(P[0], P[1]) + + sum([vec[i] * basis_values[i] for i in range(dim)]) + ) # this is just a dot product... + + def frobenius(self, P=None): + """ + Returns the `p`-th power lift of Frobenius of `P` + + EXAMPLES:: + + sage: K = Qp(11, 5) + sage: R. = K[] + sage: E = HyperellipticCurveSmoothModel(x^5 - 21*x - 20) + sage: P = E.lift_x(2) + sage: E.frobenius(P) + (2 + 10*11 + 5*11^2 + 11^3 + O(11^5) : 6 + 11 + 8*11^2 + 8*11^3 + 10*11^4 + O(11^5) : 1 + O(11^5)) + sage: Q = E.teichmuller(P); Q + (2 + 10*11 + 4*11^2 + 9*11^3 + 11^4 + O(11^5) : 6 + 11 + 4*11^2 + 9*11^3 + 4*11^4 + O(11^5) : 1 + O(11^5)) + sage: E.frobenius(Q) + (2 + 10*11 + 4*11^2 + 9*11^3 + 11^4 + O(11^5) : 6 + 11 + 4*11^2 + 9*11^3 + 4*11^4 + O(11^5) : 1 + O(11^5)) + + :: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5-23*x^3+18*x^2+40*x) + sage: Q = H(0,0) + sage: u,v = H.local_coord(Q,prec=100) + sage: K = Qp(11,5) + sage: L. = K.extension(x^20-11) + sage: HL = H.change_ring(L) + sage: S = HL(u(a),v(a)) + sage: HL.frobenius(S) + (8*a^22 + 10*a^42 + 4*a^44 + 2*a^46 + 9*a^48 + 8*a^50 + a^52 + 7*a^54 + + 7*a^56 + 5*a^58 + 9*a^62 + 5*a^64 + a^66 + 6*a^68 + a^70 + 6*a^74 + + 2*a^76 + 2*a^78 + 4*a^82 + 5*a^84 + 2*a^86 + 7*a^88 + a^90 + 6*a^92 + + a^96 + 5*a^98 + 2*a^102 + 2*a^106 + 6*a^108 + 8*a^110 + 3*a^112 + + a^114 + 8*a^116 + 10*a^118 + 3*a^120 + O(a^122) : + a^11 + 7*a^33 + 7*a^35 + 4*a^37 + 6*a^39 + 9*a^41 + 8*a^43 + 8*a^45 + + a^47 + 7*a^51 + 4*a^53 + 5*a^55 + a^57 + 7*a^59 + 5*a^61 + 9*a^63 + + 4*a^65 + 10*a^69 + 3*a^71 + 2*a^73 + 9*a^75 + 10*a^77 + 6*a^79 + + 10*a^81 + 7*a^85 + a^87 + 4*a^89 + 8*a^91 + a^93 + 8*a^95 + 2*a^97 + + 7*a^99 + a^101 + 3*a^103 + 6*a^105 + 7*a^107 + 4*a^109 + O(a^111) : + 1 + O(a^100)) + + AUTHORS: + + - Robert Bradshaw and Jennifer Balakrishnan (2010-02) + """ + if self.hyperelliptic_polynomials()[1]: + raise NotImplementedError + if not self.is_ramified(): + raise NotImplementedError + + try: + _frob = self._frob + except AttributeError: + K = self.base_ring() + p = K.prime() + x = K["x"].gen(0) + + f, f2 = self.hyperelliptic_polynomials() + if f2 != 0: + raise NotImplementedError("Curve must be in weierstrass normal form.") + h = f(x**p) - f**p + + def _frob(P): + if P == self(1, 0, 0): + return P + x0 = P[0] + y0 = P[1] + try: + uN = (1 + h(x0) / y0 ** (2 * p)).sqrt() + yres = y0**p * uN + xres = x0**p + if (yres - y0).valuation() == 0: + yres = -yres + return self.point([xres, yres, K(1)]) + except (TypeError, NotImplementedError): + uN2 = 1 + h(x0) / y0 ** (2 * p) + # yfrob2 = f(x) + c = uN2.expansion(0) + v = uN2.valuation() + a = uN2.parent().gen() + uN = self.newton_sqrt( + uN2, c.sqrt() * a ** (v // 2), K.precision_cap() + ) + yres = y0**p * uN + xres = x0**p + if (yres - y0).valuation() == 0: + yres = -yres + try: + return self(xres, yres) + except ValueError: + return self._curve_over_ram_extn(xres, yres) + + self._frob = _frob + + if P is None: + return _frob + else: + return _frob(P) + + def newton_sqrt(self, f, x0, prec): + r""" + Takes the square root of the power series `f` by Newton's method + + NOTE: + + this function should eventually be moved to `p`-adic power series ring + + INPUT: + + - ``f`` -- power series with coefficients in `\QQ_p` or an extension + - ``x0`` -- seeds the Newton iteration + - ``prec`` -- precision + + OUTPUT: the square root of `f` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5-23*x^3+18*x^2+40*x) + sage: Q = H(0,0) + sage: u,v = H.local_coord(Q,prec=100) + sage: K = Qp(11,5) + sage: HK = H.change_ring(K) + sage: L. = K.extension(x^20-11) + sage: HL = H.change_ring(L) + sage: S = HL(u(a),v(a)) + sage: f = H.hyperelliptic_polynomials()[0] + sage: y = HK.newton_sqrt( f(u(a)^11), a^11,5) + sage: y^2 - f(u(a)^11) + O(a^122) + + AUTHOR: + + - Jennifer Balakrishnan + """ + z = x0 + loop_prec = log(RR(prec), 2).ceil() + for i in range(loop_prec): + z = (z + f / z) / 2 + return z + + def curve_over_ram_extn(self, deg): + r""" + Return ``self`` over `\QQ_p(p^(1/deg))`. + + INPUT: + + - deg: the degree of the ramified extension + + OUTPUT: + + ``self`` over `\QQ_p(p^(1/deg))` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5-23*x^3+18*x^2+40*x) + sage: K = Qp(11,5) + sage: HK = H.change_ring(K) + sage: HL = HK.curve_over_ram_extn(2) + sage: HL + Hyperelliptic Curve over 11-adic Eisenstein Extension Field in a defined by x^2 - 11 defined by y^2 = x^5 + (10 + 8*a^2 + 10*a^4 + 10*a^6 + 10*a^8 + O(a^10))*x^3 + (7 + a^2 + O(a^10))*x^2 + (7 + 3*a^2 + O(a^10))*x + + AUTHOR: + + - Jennifer Balakrishnan + + """ + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) + + K = self.base_ring() + p = K.prime() + A = PolynomialRing(QQ, "x") + x = A.gen() + J = K.extension(x**deg - p, names="a") + pol = self.hyperelliptic_polynomials()[0] + H = HyperellipticCurveSmoothModel(A(pol)) + HJ = H.change_ring(J) + self._curve_over_ram_extn = HJ + self._curve_over_ram_extn._curve_over_Qp = self + return HJ + + def get_boundary_point(self, curve_over_extn, P): + """ + Given self over an extension field, find a point in the disc of `P` near the boundary + + INPUT: + + - curve_over_extn: self over a totally ramified extension + - P: Weierstrass point + + OUTPUT: + + a point in the disc of `P` near the boundary + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(3,6) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: J. = K.extension(x^30-3) + sage: HJ = H.change_ring(J) + sage: S = HK.get_boundary_point(HJ,P) + sage: S + (1 + 2*a^2 + 2*a^6 + 2*a^18 + a^32 + a^34 + a^36 + 2*a^38 + 2*a^40 + a^42 + 2*a^44 + a^48 + 2*a^50 + 2*a^52 + a^54 + a^56 + 2*a^60 + 2*a^62 + a^70 + 2*a^72 + a^76 + 2*a^78 + a^82 + a^88 + a^96 + 2*a^98 + 2*a^102 + a^104 + 2*a^106 + a^108 + 2*a^110 + a^112 + 2*a^116 + a^126 + 2*a^130 + 2*a^132 + a^144 + 2*a^148 + 2*a^150 + a^152 + 2*a^154 + a^162 + a^164 + a^166 + a^168 + a^170 + a^176 + a^178 + O(a^180) : a + O(a^180) : 1 + O(a^180)) + + AUTHOR: + + - Jennifer Balakrishnan + + """ + J = curve_over_extn.base_ring() + a = J.gen() + prec2 = J.precision_cap() + x, y = self.local_coord(P, prec2) + return curve_over_extn(x(a), y(a)) + + def P_to_S(self, P, S): + r""" + Given a finite Weierstrass point `P` and a point `S` + in the same disc, computes the Coleman integrals `\{\int_P^S x^i dx/2y \}_{i=0}^{2g-1}` + + INPUT: + + - P: finite Weierstrass point + - S: point in disc of P + + OUTPUT: + + Coleman integrals `\{\int_P^S x^i dx/2y \}_{i=0}^{2g-1}` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,4) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: HJ = HK.curve_over_ram_extn(10) + sage: S = HK.get_boundary_point(HJ,P) + sage: HK.P_to_S(P, S) + (2*a + 4*a^3 + 2*a^11 + 4*a^13 + 2*a^17 + 2*a^19 + a^21 + 4*a^23 + a^25 + 2*a^27 + 2*a^29 + 3*a^31 + 4*a^33 + O(a^35), a^-5 + 2*a + 2*a^3 + a^7 + 3*a^11 + a^13 + 3*a^15 + 3*a^17 + 2*a^19 + 4*a^21 + 4*a^23 + 4*a^25 + 2*a^27 + a^29 + a^31 + O(a^33)) + + AUTHOR: + + - Jennifer Balakrishnan + + """ + prec = self.base_ring().precision_cap() + deg = (S[0]).parent().defining_polynomial().degree() + prec2 = prec * deg + x, y = self.local_coord(P, prec2) + g = self.genus() + integrals = [ + ((x**k * x.derivative() / (2 * y)).integral()) for k in range(2 * g) + ] + val = [I(S[1]) for I in integrals] + return vector(val) + + def coleman_integral_P_to_S(self, w, P, S): + r""" + Given a finite Weierstrass point `P` and a point `S` + in the same disc, computes the Coleman integral `\int_P^S w` + + INPUT: + + - w: differential + - P: Weierstrass point + - S: point in the same disc of P (S is defined over an extension of `\QQ_p`; coordinates + of S are given in terms of uniformizer `a`) + + OUTPUT: + + Coleman integral `\int_P^S w` in terms of `a` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,4) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: J. = K.extension(x^10-5) + sage: HJ = H.change_ring(J) + sage: S = HK.get_boundary_point(HJ,P) + sage: x,y = HK.monsky_washnitzer_gens() + sage: S[0]-P[0] == HK.coleman_integral_P_to_S(x.diff(),P,S) + True + sage: HK.coleman_integral_P_to_S(HK.invariant_differential(),P,S) == HK.P_to_S(P,S)[0] + True + + AUTHOR: + + - Jennifer Balakrishnan + + """ + prec = self.base_ring().precision_cap() + deg = S[0].parent().defining_polynomial().degree() + prec2 = prec * deg + x, y = self.local_coord(P, prec2) + int_sing = (w.coeff()(x, y) * x.derivative() / (2 * y)).integral() + int_sing_a = int_sing(S[1]) + return int_sing_a + + def S_to_Q(self, S, Q): + r""" + Given `S` a point on self over an extension field, computes the + Coleman integrals `\{\int_S^Q x^i dx/2y \}_{i=0}^{2g-1}` + + **one should be able to feed `S,Q` into coleman_integral, + but currently that segfaults** + + INPUT: + + - S: a point with coordinates in an extension of `\QQ_p` (with unif. a) + - Q: a non-Weierstrass point defined over `\QQ_p` + + OUTPUT: + + the Coleman integrals `\{\int_S^Q x^i dx/2y \}_{i=0}^{2g-1}` in terms of `a` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,6) + sage: HK = H.change_ring(K) + sage: J. = K.extension(x^20-5) + sage: HJ = H.change_ring(J) + sage: w = HK.invariant_differential() + sage: x,y = HK.monsky_washnitzer_gens() + sage: P = HK(1,0) + sage: Q = HK(0,3) + sage: S = HK.get_boundary_point(HJ,P) + sage: P_to_S = HK.P_to_S(P,S) + sage: S_to_Q = HJ.S_to_Q(S,Q) + sage: P_to_S + S_to_Q + (2*a^40 + a^80 + a^100 + O(a^105), a^20 + 2*a^40 + 4*a^60 + 2*a^80 + O(a^103)) + sage: HK.coleman_integrals_on_basis(P,Q) + (2*5^2 + 5^4 + 5^5 + 3*5^6 + O(5^7), 5 + 2*5^2 + 4*5^3 + 2*5^4 + 5^6 + O(5^7)) + + AUTHOR: + + - Jennifer Balakrishnan + + """ + FS = self.frobenius(S) + FS = (FS[0], FS[1]) + FQ = self.frobenius(Q) + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + + try: + M_frob, forms = self._frob_calc + except AttributeError: + M_frob, forms = self._frob_calc = ( + monsky_washnitzer.matrix_of_frobenius_hyperelliptic(self) + ) + try: + HJ = self._curve_over_ram_extn + K = HJ.base_ring() + except AttributeError: + HJ = S.scheme() + K = self.base_ring() + g = self.genus() + prec2 = K.precision_cap() + p = K.prime() + dim = 2 * g + V = VectorSpace(K, dim) + if S == FS: + S_to_FS = V(dim * [0]) + else: + P = self(ZZ(FS[0].expansion(0)), ZZ(FS[1].expansion(0))) + x, y = self.local_coord(P, prec2) + integrals = [ + (x**i * x.derivative() / (2 * y)).integral() for i in range(dim) + ] + S_to_FS = vector( + [I.polynomial()(FS[1]) - I.polynomial()(S[1]) for I in integrals] + ) + if HJ(Q[0], Q[1], Q[2]) == HJ(FQ[0], FQ[1], FQ[2]): + FQ_to_Q = V(dim * [0]) + else: + FQ_to_Q = V(self.tiny_integrals_on_basis(FQ, Q)) + try: + L = [f(K(S[0]), K(S[1])) - f(K(Q[0]), K(Q[1])) for f in forms] + except ValueError: + forms = [f.change_ring(K) for f in forms] + L = [f(S[0], S[1]) - f(Q[0], Q[1]) for f in forms] + b = V(L) + M_sys = matrix(K, M_frob).transpose() - 1 + B = ~M_sys + vv = min(c.valuation() for c in B.list()) + B = (p ** (-vv) * B).change_ring(K) + B = p ** (vv) * B + return B * (b - S_to_FS - FQ_to_Q) + + def coleman_integral_S_to_Q(self, w, S, Q): + r""" + Compute the Coleman integral `\int_S^Q w` + + **one should be able to feed `S,Q` into coleman_integral, + but currently that segfaults** + + INPUT: + + - w: a differential + - S: a point with coordinates in an extension of `\QQ_p` + - Q: a non-Weierstrass point defined over `\QQ_p` + + OUTPUT: + + the Coleman integral `\int_S^Q w` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,6) + sage: HK = H.change_ring(K) + sage: J. = K.extension(x^20-5) + sage: HJ = H.change_ring(J) + sage: x,y = HK.monsky_washnitzer_gens() + sage: P = HK(1,0) + sage: Q = HK(0,3) + sage: S = HK.get_boundary_point(HJ,P) + sage: P_to_S = HK.coleman_integral_P_to_S(y.diff(),P,S) + sage: S_to_Q = HJ.coleman_integral_S_to_Q(y.diff(),S,Q) + sage: P_to_S + S_to_Q + 3 + O(a^119) + sage: HK.coleman_integral(y.diff(),P,Q) + 3 + O(5^6) + + AUTHOR: + + - Jennifer Balakrishnan + """ + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + + K = self.base_ring() + R = monsky_washnitzer.SpecialHyperellipticQuotientRing(self, K) + MW = monsky_washnitzer.MonskyWashnitzerDifferentialRing(R) + w = MW(w) + f, vec = w.reduce_fast() + g = self.genus() + const = f(Q[0], Q[1]) - f(S[0], S[1]) + if vec == vector(2 * g * [0]): + return const + else: + basis_values = self.S_to_Q(S, Q) + dim = len(basis_values) + dot = sum([vec[i] * basis_values[i] for i in range(dim)]) + return const + dot + + def coleman_integral_from_weierstrass_via_boundary(self, w, P, Q, d): + r""" + Computes the Coleman integral `\int_P^Q w` via a boundary point + in the disc of `P`, defined over a degree `d` extension + + INPUT: + + - w: a differential + - P: a Weierstrass point + - Q: a non-Weierstrass point + - d: degree of extension where coordinates of boundary point lie + + OUTPUT: + + the Coleman integral `\int_P^Q w`, written in terms of the uniformizer + `a` of the degree `d` extension + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) + sage: K = Qp(5,6) + sage: HK = H.change_ring(K) + sage: P = HK(1,0) + sage: Q = HK(0,3) + sage: x,y = HK.monsky_washnitzer_gens() + sage: HK.coleman_integral_from_weierstrass_via_boundary(y.diff(),P,Q,20) + 3 + O(a^119) + sage: HK.coleman_integral(y.diff(),P,Q) + 3 + O(5^6) + sage: w = HK.invariant_differential() + sage: HK.coleman_integral_from_weierstrass_via_boundary(w,P,Q,20) + 2*a^40 + a^80 + a^100 + O(a^105) + sage: HK.coleman_integral(w,P,Q) + 2*5^2 + 5^4 + 5^5 + 3*5^6 + O(5^7) + + AUTHOR: + + - Jennifer Balakrishnan + """ + HJ = self.curve_over_ram_extn(d) + S = self.get_boundary_point(HJ, P) + P_to_S = self.coleman_integral_P_to_S(w, P, S) + S_to_Q = HJ.coleman_integral_S_to_Q(w, S, Q) + return P_to_S + S_to_Q diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py new file mode 100644 index 00000000000..fb8eea5a43e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -0,0 +1,130 @@ +import sage.rings.abc +from sage.rings.padics.factory import Qp as pAdicField +from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic + + +class HyperellipticCurveSmoothModel_rational_field( + hyperelliptic_generic.HyperellipticCurveSmoothModel_generic +): + def __init__(self, projective_model, f, h, genus): + super().__init__(projective_model, f, h, genus) + + def matrix_of_frobenius(self, p, prec=20): + """ + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology using + the `p`-adic field with precision ``prec``. + + This function is essentially a wrapper function of + :meth:`sage.schemes.hyperelliptic_curves.monsky_washnitzer.matrix_of_frobenius_hyperelliptic`. + + INPUT: + + - ``p`` (prime integer or pAdic ring / field ) -- if ``p`` is an integer, + constructs a ``pAdicField`` with ``p`` to compute the matrix of + Frobenius, otherwise uses the supplied pAdic ring or field. + + - ``prec`` (optional) -- if ``p`` is an prime integer, the `p`-adic + precision of the coefficient ring constructed + + EXAMPLES:: + + sage: K = pAdicField(5, prec=3) + sage: R. = QQ['x'] + sage: H = HyperellipticCurveSmoothModel(x^5 - 2*x + 3) + sage: H.matrix_of_frobenius(K) + [ 4*5 + O(5^3) 5 + 2*5^2 + O(5^3) 2 + 3*5 + 2*5^2 + O(5^3) 2 + 5 + 5^2 + O(5^3)] + [ 3*5 + 5^2 + O(5^3) 3*5 + O(5^3) 4*5 + O(5^3) 2 + 5^2 + O(5^3)] + [ 4*5 + 4*5^2 + O(5^3) 3*5 + 2*5^2 + O(5^3) 5 + 3*5^2 + O(5^3) 2*5 + 2*5^2 + O(5^3)] + [ 5^2 + O(5^3) 5 + 4*5^2 + O(5^3) 4*5 + 3*5^2 + O(5^3) 2*5 + O(5^3)] + + You can also pass directly a prime `p` with to construct a pAdic field with precision + ``prec``:: + + sage: H.matrix_of_frobenius(3, prec=2) + [ O(3^2) 3 + O(3^2) O(3^2) O(3^2)] + [ 3 + O(3^2) O(3^2) O(3^2) 2 + 3 + O(3^2)] + [ 2*3 + O(3^2) O(3^2) O(3^2) 3^-1 + O(3)] + [ O(3^2) O(3^2) 3 + O(3^2) O(3^2)] + """ + from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + + if isinstance(p, (sage.rings.abc.pAdicField, sage.rings.abc.pAdicRing)): + K = p + else: + K = pAdicField(p, prec) + frob_p, _ = monsky_washnitzer.matrix_of_frobenius_hyperelliptic( + self.change_ring(K) + ) + return frob_p + + def lseries(self, prec=53): + """ + Return the L-series of this hyperelliptic curve of genus 2. + + EXAMPLES:: + + sage: x = polygen(QQ, 'x') + sage: C = HyperellipticCurveSmoothModel(x^2+x, x^3+x^2+1) + sage: C.lseries() + PARI L-function associated to Hyperelliptic Curve + over Rational Field defined by y^2 + (x^3 + x^2 + 1)*y = x^2 + x + """ + from sage.lfunctions.pari import LFunction + + L = LFunction(lfun_genus2(self), prec=prec) + L.rename("PARI L-function associated to %s" % self) + return L + + +# +# +# TODO: this is from `sage/src/sage/lfunctions/pari.py` but it uses the old method +# so I have copy-pasted it here for now with the new curve... +# +# + + +def lfun_genus2(C): + """ + Return the L-function of a curve of genus 2. + + INPUT: + + - ``C`` -- hyperelliptic curve of genus 2 + + Currently, the model needs to be minimal at 2. + + This uses :pari:`lfungenus2`. + + EXAMPLES:: + + sage: from sage.lfunctions.pari import LFunction + sage: from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_rational_field import lfun_genus2 + sage: x = polygen(QQ, 'x') + sage: C = HyperellipticCurveSmoothModel(x^5 + x + 1) + sage: L = LFunction(lfun_genus2(C)) + ... + sage: L(3) + 0.965946926261520 + + sage: C = HyperellipticCurveSmoothModel(x^2 + x, x^3 + x^2 + 1) + sage: L = LFunction(lfun_genus2(C)) + sage: L(2) + 0.364286342944359 + + TESTS:: + + sage: x = polygen(QQ, 'x') + sage: H = HyperellipticCurveSmoothModel(x^15 + x + 1) + sage: L = LFunction(lfun_genus2(H)) + Traceback (most recent call last): + ... + ValueError: curve must be hyperelliptic of genus 2 + """ + from sage.libs.pari import pari + from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_g2 + + if not isinstance(C, hyperelliptic_g2.HyperellipticCurveSmoothModel_g2): + raise ValueError("curve must be hyperelliptic of genus 2") + P, Q = C.hyperelliptic_polynomials() + return pari.lfungenus2(P) if not Q else pari.lfungenus2([P, Q]) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py new file mode 100644 index 00000000000..c1c192b8565 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/invariants.py @@ -0,0 +1,434 @@ +r""" +Compute invariants of quintics and sextics via 'Ueberschiebung' + +.. TODO:: + + * Implement invariants in small positive characteristic. + + * Cardona-Quer and additional invariants for classifying automorphism groups. + +AUTHOR: + +- Nick Alexander + +""" + +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + + +def diffxy(f, x, xtimes, y, ytimes): + r""" + Differentiate a polynomial ``f``, ``xtimes`` with respect to ``x``, and + ```ytimes`` with respect to ``y``. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import diffxy + sage: R. = QQ[] + sage: diffxy(u^2*v^3, u, 0, v, 0) + u^2*v^3 + sage: diffxy(u^2*v^3, u, 2, v, 1) + 6*v^2 + sage: diffxy(u^2*v^3, u, 2, v, 2) + 12*v + sage: diffxy(u^2*v^3 + u^4*v^4, u, 2, v, 2) + 144*u^2*v^2 + 12*v + """ + h = f + for i in range(xtimes): + h = h.derivative(x) + for j in range(ytimes): + h = h.derivative(y) + return h + + +def differential_operator(f, g, k): + r""" + Return the differential operator `(f g)_k` symbolically in the polynomial ring in ``dfdx, dfdy, dgdx, dgdy``. + + This is defined by Mestre on p 315 [Mes1991]_: + + .. MATH:: + + (f g)_k = \frac{(m - k)! (n - k)!}{m! n!} \left( + \frac{\partial f}{\partial x} \frac{\partial g}{\partial y} - + \frac{\partial f}{\partial y} \frac{\partial g}{\partial x} \right)^k . + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import differential_operator + sage: R. = QQ[] + sage: differential_operator(x, y, 0) + 1 + sage: differential_operator(x, y, 1) + -dfdy*dgdx + dfdx*dgdy + sage: differential_operator(x*y, x*y, 2) + 1/4*dfdy^2*dgdx^2 - 1/2*dfdx*dfdy*dgdx*dgdy + 1/4*dfdx^2*dgdy^2 + sage: differential_operator(x^2*y, x*y^2, 2) + 1/36*dfdy^2*dgdx^2 - 1/18*dfdx*dfdy*dgdx*dgdy + 1/36*dfdx^2*dgdy^2 + sage: differential_operator(x^2*y, x*y^2, 4) + 1/576*dfdy^4*dgdx^4 - 1/144*dfdx*dfdy^3*dgdx^3*dgdy + 1/96*dfdx^2*dfdy^2*dgdx^2*dgdy^2 + - 1/144*dfdx^3*dfdy*dgdx*dgdy^3 + 1/576*dfdx^4*dgdy^4 + """ + (x, y) = f.parent().gens() + n = max(ZZ(f.degree()), ZZ(k)) + m = max(ZZ(g.degree()), ZZ(k)) + R, (fx, fy, gx, gy) = PolynomialRing( + f.base_ring(), 4, "dfdx,dfdy,dgdx,dgdy" + ).objgens() + const = (m - k).factorial() * (n - k).factorial() / (m.factorial() * n.factorial()) + U = f.base_ring()(const) * (fx * gy - fy * gx) ** k + return U + + +def diffsymb(U, f, g): + r""" + Given a differential operator ``U`` in ``dfdx, dfdy, dgdx, dgdy``, + represented symbolically by ``U``, apply it to ``f, g``. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import diffsymb + sage: R. = QQ[] + sage: S. = QQ[] + sage: [ diffsymb(dd, x^2, y*0 + 1) for dd in S.gens() ] + [2*x, 0, 0, 0] + sage: [ diffsymb(dd, x*0 + 1, y^2) for dd in S.gens() ] + [0, 0, 0, 2*y] + sage: [ diffsymb(dd, x^2, y^2) for dd in S.gens() ] + [2*x*y^2, 0, 0, 2*x^2*y] + + sage: diffsymb(dfdx + dfdy*dgdy, y*x^2, y^3) + 2*x*y^4 + 3*x^2*y^2 + """ + (x, y) = f.parent().gens() + R, (fx, fy, gx, gy) = PolynomialRing( + f.base_ring(), 4, "dfdx,dfdy,dgdx,dgdy" + ).objgens() + res = 0 + for coeff, mon in list(U): + mon = R(mon) + a = diffxy(f, x, mon.degree(fx), y, mon.degree(fy)) + b = diffxy(g, x, mon.degree(gx), y, mon.degree(gy)) + temp = coeff * a * b + res = res + temp + return res + + +def Ueberschiebung(f, g, k): + r""" + Return the differential operator `(f g)_k`. + + This is defined by Mestre on page 315 [Mes1991]_: + + .. MATH:: + + (f g)_k = \frac{(m - k)! (n - k)!}{m! n!} \left( + \frac{\partial f}{\partial x} \frac{\partial g}{\partial y} - + \frac{\partial f}{\partial y} \frac{\partial g}{\partial x} \right)^k . + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import Ueberschiebung as ub + sage: R. = QQ[] + sage: ub(x, y, 0) + x*y + sage: ub(x^5 + 1, x^5 + 1, 1) + 0 + sage: ub(x^5 + 5*x + 1, x^5 + 5*x + 1, 0) + x^10 + 10*x^6 + 2*x^5 + 25*x^2 + 10*x + 1 + """ + U = differential_operator(f, g, k) + # U is the (f g)_k = ... of Mestre, p315, symbolically + return diffsymb(U, f, g) + + +def ubs(f): + r""" + Given a sextic form `f`, return a dictionary of the invariants of Mestre, p 317 [Mes1991]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import ubs + sage: x = QQ['x'].0 + sage: ubs(x^6 + 1) + {'A': 2, + 'B': 2/3, + 'C': -2/9, + 'D': 0, + 'Delta': -2/3*x^2*h^2, + 'f': x^6 + h^6, + 'i': 2*x^2*h^2, + 'y1': 0, + 'y2': 0, + 'y3': 0} + + sage: R. = QQ[] + sage: ubs(u^6 + v^6) + {'A': 2, + 'B': 2/3, + 'C': -2/9, + 'D': 0, + 'Delta': -2/3*u^2*v^2, + 'f': u^6 + v^6, + 'i': 2*u^2*v^2, + 'y1': 0, + 'y2': 0, + 'y3': 0} + + sage: R. = GF(31)[] + sage: ubs(t^6 + 2*t^5 + t^2 + 3*t + 1) + {'A': 0, + 'B': -12, + 'C': -15, + 'D': -15, + 'Delta': -10*t^4 + 12*t^3*h + 7*t^2*h^2 - 5*t*h^3 + 2*h^4, + 'f': t^6 + 2*t^5*h + t^2*h^4 + 3*t*h^5 + h^6, + 'i': -4*t^4 + 10*t^3*h + 2*t^2*h^2 - 9*t*h^3 - 7*h^4, + 'y1': 4*t^2 - 10*t*h - 13*h^2, + 'y2': 6*t^2 - 4*t*h + 2*h^2, + 'y3': 4*t^2 - 4*t*h - 9*h^2} + """ + ub = Ueberschiebung + if f.parent().ngens() == 1: + f = PolynomialRing(f.parent().base_ring(), 1, f.parent().variable_name())(f) + x1, x2 = f.homogenize().parent().gens() + f = sum([f[i] * x1**i * x2 ** (6 - i) for i in range(7)]) + U = {} + U["f"] = f + U["i"] = ub(f, f, 4) + U["Delta"] = ub(U["i"], U["i"], 2) + U["y1"] = ub(f, U["i"], 4) + U["y2"] = ub(U["i"], U["y1"], 2) + U["y3"] = ub(U["i"], U["y2"], 2) + U["A"] = ub(f, f, 6) + U["B"] = ub(U["i"], U["i"], 4) + U["C"] = ub(U["i"], U["Delta"], 4) + U["D"] = ub(U["y3"], U["y1"], 2) + return U + + +def clebsch_to_igusa(A, B, C, D): + r""" + Convert Clebsch invariants `A, B, C, D` to Igusa invariants `I_2, I_4, I_6, I_{10}`. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import clebsch_to_igusa, igusa_to_clebsch + sage: clebsch_to_igusa(2, 3, 4, 5) + (-240, 17370, 231120, -103098906) + sage: igusa_to_clebsch(*clebsch_to_igusa(2, 3, 4, 5)) + (2, 3, 4, 5) + + sage: Cs = tuple(map(GF(31), (2, 3, 4, 5))); Cs + (2, 3, 4, 5) + sage: clebsch_to_igusa(*Cs) + (8, 10, 15, 26) + sage: igusa_to_clebsch(*clebsch_to_igusa(*Cs)) + (2, 3, 4, 5) + """ + I2 = -120 * A + I4 = -720 * A**2 + 6750 * B + I6 = 8640 * A**3 - 108000 * A * B + 202500 * C + I10 = ( + -62208 * A**5 + + 972000 * A**3 * B + + 1620000 * A**2 * C + - 3037500 * A * B**2 + - 6075000 * B * C + - 4556250 * D + ) + return (I2, I4, I6, I10) + + +def igusa_to_clebsch(I2, I4, I6, I10): + r""" + Convert Igusa invariants `I_2, I_4, I_6, I_{10}` to Clebsch invariants `A, B, C, D`. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import clebsch_to_igusa, igusa_to_clebsch + sage: igusa_to_clebsch(-2400, 173700, 23112000, -10309890600) + (20, 342/5, 2512/5, 43381012/1125) + sage: clebsch_to_igusa(*igusa_to_clebsch(-2400, 173700, 23112000, -10309890600)) + (-2400, 173700, 23112000, -10309890600) + + sage: Is = tuple(map(GF(31), (-2400, 173700, 23112000, -10309890600))); Is + (18, 7, 12, 27) + sage: igusa_to_clebsch(*Is) + (20, 25, 25, 12) + sage: clebsch_to_igusa(*igusa_to_clebsch(*Is)) + (18, 7, 12, 27) + """ + A = -(+I2) / 120 + B = -(-(I2**2) - 20 * I4) / 135000 + C = -(+(I2**3) + 80 * I2 * I4 - 600 * I6) / 121500000 + D = ( + -( + +9 * I2**5 + + 700 * I2**3 * I4 + - 3600 * I2**2 * I6 + - 12400 * I2 * I4**2 + + 48000 * I4 * I6 + + 10800000 * I10 + ) + / 49207500000000 + ) + return (A, B, C, D) + + +def clebsch_invariants(f): + r""" + Given a sextic form `f`, return the Clebsch invariants `(A, B, C, D)` of + Mestre, p 317, [Mes1991]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import clebsch_invariants + sage: R. = QQ[] + sage: clebsch_invariants(x^6 + y^6) + (2, 2/3, -2/9, 0) + sage: R. = QQ[] + sage: clebsch_invariants(x^6 + x^5 + x^4 + x^2 + 2) + (62/15, 15434/5625, -236951/140625, 229930748/791015625) + + sage: magma(x^6 + 1).ClebschInvariants() # optional - magma + [ 2, 2/3, -2/9, 0 ] + sage: magma(x^6 + x^5 + x^4 + x^2 + 2).ClebschInvariants() # optional - magma + [ 62/15, 15434/5625, -236951/140625, 229930748/791015625 ] + """ + R = f.parent().base_ring() + if R.characteristic() in [2, 3, 5]: + raise NotImplementedError( + "Invariants of binary sextics/genus 2 hyperelliptic " + "curves not implemented in characteristics 2, 3, and 5" + ) + + U = ubs(f) + L = U["A"], U["B"], U["C"], U["D"] + assert all(t.is_constant() for t in L) + return tuple([t.constant_coefficient() for t in L]) + + +def igusa_clebsch_invariants(f): + r""" + Given a sextic form `f`, return the Igusa-Clebsch invariants `I_2, I_4, + I_6, I_{10}` of Igusa and Clebsch [IJ1960]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import igusa_clebsch_invariants + sage: R. = QQ[] + sage: igusa_clebsch_invariants(x^6 + y^6) + (-240, 1620, -119880, -46656) + sage: R. = QQ[] + sage: igusa_clebsch_invariants(x^6 + x^5 + x^4 + x^2 + 2) + (-496, 6220, -955932, -1111784) + + sage: magma(x^6 + 1).IgusaClebschInvariants() # optional - magma + [ -240, 1620, -119880, -46656 ] + sage: magma(x^6 + x^5 + x^4 + x^2 + 2).IgusaClebschInvariants() # optional - magma + [ -496, 6220, -955932, -1111784 ] + + TESTS: + + Let's check a symbolic example:: + + sage: R. = QQ[] + sage: S. = R[] + sage: igusa_clebsch_invariants(x^5 + a*x^4 + b*x^3 + c*x^2 + d*x + e)[0] + 6*b^2 - 16*a*c + 40*d + + sage: from sage.schemes.hyperelliptic_curves.invariants import absolute_igusa_invariants_wamelen + sage: absolute_igusa_invariants_wamelen(GF(5)['x'](x^6 - 2*x)) + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + """ + return clebsch_to_igusa(*clebsch_invariants(f)) + + +def absolute_igusa_invariants_wamelen(f): + r""" + Given a sextic form `f`, return the three absolute Igusa invariants used by van Wamelen [Wam1999]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + REFERENCES: + + - [Wam1999]_ + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import absolute_igusa_invariants_wamelen + sage: R. = QQ[] + sage: absolute_igusa_invariants_wamelen(x^5 - 1) + (0, 0, 0) + + The following example can be checked against van Wamelen's paper:: + + sage: h = -x^5 + 3*x^4 + 2*x^3 - 6*x^2 - 3*x + 1 + sage: i1, i2, i3 = absolute_igusa_invariants_wamelen(h) + sage: list(map(factor, (i1, i2, i3))) + [2^7 * 3^15, 2^5 * 3^11 * 5, 2^4 * 3^9 * 31] + + TESTS:: + + sage: absolute_igusa_invariants_wamelen(GF(3)['x'](x^5 - 2*x)) + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + """ + I2, I4, I6, I10 = igusa_clebsch_invariants(f) + i1 = I2**5 / I10 + i2 = I2**3 * I4 / I10 + i3 = I2**2 * I6 / I10 + return (i1, i2, i3) + + +def absolute_igusa_invariants_kohel(f): + r""" + Given a sextic form `f`, return the three absolute Igusa invariants used by Kohel [KohECHIDNA]_. + + `f` may be homogeneous in two variables or inhomogeneous in one. + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves.invariants import absolute_igusa_invariants_kohel + sage: R. = QQ[] + sage: absolute_igusa_invariants_kohel(x^5 - 1) + (0, 0, 0) + sage: absolute_igusa_invariants_kohel(x^5 - x) + (100, -20000, -2000) + + The following example can be checked against Kohel's database [KohECHIDNA]_ :: + + sage: h = -x^5 + 3*x^4 + 2*x^3 - 6*x^2 - 3*x + 1 + sage: i1, i2, i3 = absolute_igusa_invariants_kohel(h) + sage: list(map(factor, (i1, i2, i3))) + [2^2 * 3^5 * 5 * 31, 2^5 * 3^11 * 5, 2^4 * 3^9 * 31] + sage: list(map(factor, (150660, 28343520, 9762768))) + [2^2 * 3^5 * 5 * 31, 2^5 * 3^11 * 5, 2^4 * 3^9 * 31] + + TESTS:: + + sage: absolute_igusa_invariants_kohel(GF(2)['x'](x^5 - x)) + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + """ + I2, I4, I6, I10 = igusa_clebsch_invariants(f) + i1 = I4 * I6 / I10 + i2 = I2**3 * I4 / I10 + i3 = I2**2 * I6 / I10 + return (i1, i2, i3) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py new file mode 100644 index 00000000000..23f79df888b --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py @@ -0,0 +1,30 @@ +from sage.schemes.hyperelliptic_curves_smooth_model import ( + jacobian_g2_homset_inert, + jacobian_g2_homset_ramified, + jacobian_g2_homset_split, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_generic import ( + HyperellipticJacobian_generic, +) + + +class HyperellipticJacobian_g2_generic(HyperellipticJacobian_generic): + """ + Special class to handle optimisations for jacobian computations + in genus two + """ + + def _point_homset(self, *args, **kwds): + # TODO: make a constructor for this?? + H = self.curve() + if H.is_ramified(): + return jacobian_g2_homset_ramified.HyperellipticJacobianHomsetRamified_g2( + *args, **kwds + ) + elif H.is_split(): + return jacobian_g2_homset_split.HyperellipticJacobianHomsetSplit_g2( + *args, **kwds + ) + return jacobian_g2_homset_inert.HyperellipticJacobianHomsetInert_g2( + *args, **kwds + ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py new file mode 100644 index 00000000000..310c9fe06d8 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_inert import ( + HyperellipticJacobianHomsetInert, +) + + +class HyperellipticJacobianHomsetInert_g2(HyperellipticJacobianHomsetInert): + """ + Special class to handle optimisations for jacobian homset computations + in genus two for hyperlliptic curves with an inert model + """ + + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py new file mode 100644 index 00000000000..c171c4bbd87 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_ramified import ( + HyperellipticJacobianHomsetRamified, +) + + +class HyperellipticJacobianHomsetRamified_g2(HyperellipticJacobianHomsetRamified): + """ + Special class to handle optimisations for jacobian homset computations + in genus two for hyperlliptic curves with an ramified model + """ + + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py new file mode 100644 index 00000000000..b0385e4b92e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_split import ( + HyperellipticJacobianHomsetSplit, +) + + +class HyperellipticJacobianHomsetSplit_g2(HyperellipticJacobianHomsetSplit): + """ + Special class to handle optimisations for jacobian homset computations + in genus two for hyperlliptic curves with an split model + """ + + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py new file mode 100644 index 00000000000..1fcae854cac --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -0,0 +1,230 @@ +r""" + Jacobians of hyperelliptic curves + + In Sage, a hyperelliptic curve `H` of genus `g` is always + specified by an (affine) equation in Weierstrass form + + .. MATH:: + + H : y^2 + h(x) y = f(x), + + for some polynomials `h` and `f`. + + Elements of the Jacobian of such curves can be identified + with equivalence classes of divisors. In the following, we + denote by `\infty_+`, \infty_-` the points at infinity of `H`, + and we set + + ..MATH:: + D_\infty = + \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. + + Here, `\infty_- = \infty_+`, if `H` is ramified. + Unless the genus `g` is odd and `H` is inert, the divisor + `D_\infty` is rational. In these cases, any element on + the Jacobian admits a unique representative of the form + + ..MATH:: + + [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], + + with `n` and `m` non-negative integers and `P_1 + ... + P_r` + an affine and reduced divisor on `H`. + + This module implements the arithemtic for Jacobians of + hyperelliptic curves. Elements of the Jacobian are represented + by tuples of the form `(u, v : n)`, where + - (u,v) is the Mumford representative of the divisor + `P_1 + ... + P_r`, + - n is the coefficient of `\infty_+` + We note that `m = g - \deg(u) - n` and is therefore omitted in + the description. Similarly, if `H` ramified or inert, + then `n` can be deduced from `\deg(u)` and `g`. In these cases, + `n` is omitted in the description as well. + + + EXAMPLES:: + + We construct the Jacobian of a hyperelliptic curve with affine equation + `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. + This curve has two points at infinity. + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x + + The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian. + + sage: P = H.point([0, 0]) + sage: Q = H.point([-1, -1]) + sage: D1 = J(P,Q); D1 + (x^2 + x, -2*x : 0) + + Elements of the Jacobian can also be constructed by directly providing + the Mumford representation. + + sage: D1 == J(x^2 + x, -2*x, 0) + True + + We can also embed single points into the Jacobian. Below we construct + `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` + (by default one of the points at infinity). + + sage: D2 = J(P); D2 + (x, 0 : 0) + sage: P0 = H.distinguished_point(); P0 + (1 : 0 : 0) + sage: D2 == J(P, P0) + True + + We may add elements, or multiply by integers. + + sage: 2*D1 + (x, -1 : 1) + sage: D1 + D2 + (x^2 + x, -1 : 0) + sage: -D2 + (x, -1 : 1) + + Note that the neutral element is given by `[D_\infty - D_\infty]`, + in particular `n = 1`. + + sage: J.zero() + (1, 0 : 1) + + There are two more elements of the Jacobian that are only supported + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. + + sage: [P_plus, P_minus] = H.points_at_infinity() + sage: P_plus == P0 + True + sage: J(P_plus,P_minus) + (1, 0 : 2) + sage: J(P_minus, P_plus) + (1, 0 : 0) + + Now, we consider the Jacobian of a hyperelliptic curve with only one + point at infinity, defined over a finite field. + + sage: K = FiniteField(7) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 + + Elements on the Jacobian can be constructed as before. But the value + `n` is not used here, since there is only one point at infinity. + + sage: P = H.point([3, 0]) + sage: Q = H.point([5, 1]) + sage: D1 = J(P,Q); D1 + (x^2 + 6*x + 1, 3*x + 5) + sage: D2 = J(x^3 + 3*x^2 + 4*x + 3, 2*x^2 + 4*x) + sage: D1 + D2 + (x^3 + 2, 4) + + Over finite fields, we may also construct random elements and + compute the order of the Jacobian. + + sage: J.random_element() #random + (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) + sage: J.order() + 344 + + Note that arithmetic on the Jacobian is not implemented if the + underlying hyperelliptic curve is inert (i.e. has no points at + infinity) and the genus is odd. + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^4 + 1)*y = x^8 + 1 + + + TODO: + finish example for the inert case. + + AUTHORS: + + - David Kohel (2006): initial version + - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# 2024 Sabrina Kunzweiler, Gareth Ma, Giacomo Pope +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** +from sage.misc.cachefunc import cached_method +from sage.rings.integer import Integer +from sage.schemes.hyperelliptic_curves_smooth_model import ( + jacobian_homset_inert, + jacobian_homset_ramified, + jacobian_homset_split, + jacobian_morphism, +) +from sage.schemes.jacobians.abstract_jacobian import Jacobian_generic + + +class HyperellipticJacobian_generic(Jacobian_generic): + """ + This is the base class for Jacobians of hyperelliptic curves. + """ + + def dimension(self): + """ + Return the dimension of this Jacobian. + """ + return Integer(self.curve().genus()) + + def point(self, *mumford, check=True, **kwargs): + try: + return self.point_homset()(*mumford, check=check) + except AttributeError: + raise ValueError("Arguments must determine a valid Mumford divisor.") + + def _point_homset(self, *args, **kwds): + # TODO: make a constructor for this?? + H = self.curve() + if H.is_ramified(): + return jacobian_homset_ramified.HyperellipticJacobianHomsetRamified( + *args, **kwds + ) + elif H.is_split(): + return jacobian_homset_split.HyperellipticJacobianHomsetSplit(*args, **kwds) + return jacobian_homset_inert.HyperellipticJacobianHomsetInert(*args, **kwds) + + def _point(self, *args, **kwds): + H = self.curve() + if H.is_ramified(): + return jacobian_morphism.MumfordDivisorClassFieldRamified(*args, **kwds) + elif H.is_split(): + return jacobian_morphism.MumfordDivisorClassFieldSplit(*args, **kwds) + return jacobian_morphism.MumfordDivisorClassFieldInert(*args, **kwds) + + @cached_method + def order(self): + return self.point_homset().order() + + def count_points(self, *args, **kwds): + return self.point_homset().count_points(*args, **kwds) + + def lift_u(self, *args, **kwds): + return self.point_homset().lift_u(*args, **kwds) + + def random_element(self, *args, **kwds): + return self.point_homset().random_element(*args, **kwds) + + def points(self, *args, **kwds): + return self.point_homset().points(*args, **kwds) + + def list(self): + return self.point_homset().points() + + def __iter__(self): + yield from self.list() + + rational_points = points diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py new file mode 100644 index 00000000000..50ec44e95bf --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -0,0 +1,686 @@ +import itertools + +from sage.misc.banner import SAGE_VERSION +from sage.misc.cachefunc import cached_method +from sage.misc.functional import symbolic_prod as product +from sage.misc.prandom import choice +from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic +from sage.rings.integer import Integer +from sage.rings.polynomial.polynomial_ring import polygen +from sage.schemes.generic.homset import SchemeHomset_points + +# TODO: move this +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, +) +from sage.structure.element import parent + +assert SAGE_VERSION.startswith("10."), "please update to Sage 10" +assert ( + int(SAGE_VERSION.lstrip("10.").split(".")[0]) >= 4 +), "please update to Sage 10.4+ (#37118)" + + +class HyperellipticJacobianHomset(SchemeHomset_points): + def __init__(self, Y, X, **kwds): + SchemeHomset_points.__init__(self, Y, X, **kwds) + self._morphism_element = None + + def _repr_(self) -> str: + return f"Abelian group of points on {self.codomain()}" + + def _morphism(self, *args, **kwds): + return self._morphism_element(*args, **kwds) + + def curve(self): + # It is unlike you will want to use this, as this returns the original curve H even when + return self.codomain().curve() + + def extended_curve(self): + """ + TODO: + + EXAMPLES:: + + sage: # TODO + """ + # Code from schemes/generic/homset.py + if "_extended_curve" in self.__dict__: + return self._extended_curve + R = self.domain().coordinate_ring() + if R is not self.curve().base_ring(): + X = self.curve().base_extend(R) + else: + X = self.curve() + self._extended_curve = X + return X + + @cached_method + def order(self): + """ + Compute the order of the Jacobian + + TODO: currently using lazy methods by calling sage + + EXAMPLES:: + + sage: # TODO + """ + return sum(self.extended_curve().frobenius_polynomial()) + + @cached_method + def _curve_frobenius_roots(self): + r""" + Return the roots of the charpoly of frobenius on the extended curve. + + EXAMPLES:: + + sage: # TODO + """ + from sage.rings.qqbar import QQbar + + roots = self.extended_curve().frobenius_polynomial().roots(QQbar) + return [r for r, e in roots for _ in range(e)] + + def cardinality(self, extension_degree=1): + r""" + Return `|Jac(C) / \F_{q^n}|`. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: J = H.jacobian() + sage: J.count_points(10) == [J.change_ring(GF((5, k))).order() for k in range(1, 11)] + True + sage: J2 = J(GF((5, 2))) + sage: J2.count_points(5) == J.count_points(10)[1::2] + True + """ + K = self.extended_curve().base_ring() + if not isinstance(K, FiniteField_generic): + raise NotImplementedError( + "cardinality is only implemented for Jacobians over finite fields" + ) + + return Integer( + product(1 - r**extension_degree for r in self._curve_frobenius_roots()) + ) + + def count_points(self, n=1): + """ + TODO + + EXAMPLES:: + + sage: # TODO + """ + try: + n = Integer(n) + except TypeError: + raise TypeError("n must be a positive integer") + + if n < 1: + raise ValueError("n must be a positive integer") + + if n == 1: + return self.cardinality() + + return [self.cardinality(extension_degree=i) for i in range(1, n + 1)] + + def point_to_mumford_coordinates(self, P): + """ + On input a point P, return the Mumford coordinates + of (the affine part of) the divisor [P]. + + EXAMPLES:: + + sage: # TODO + """ + R, x = self.extended_curve().polynomial_ring().objgen() + X, Y, Z = P._coords + if Z == 0: + return R.one(), R.zero() + u = x - X + v = R(Y) + return u, v + + def __call__(self, *args, check=True): + r""" + Return a rational point in the abstract Homset `J(K)`, given: + + 1. No arguments or the integer `0`; return `0 \in J`; + + 2. A point `P` on `J = Jac(C)`, return `P`; + + 3. A point `P` on the curve `H` such that `J = Jac(H)`; + return `[P - P_0]`, where `P_0` is the distinguished point of `H`. + By default, `P_0 = \infty`; + + 4. Two points `P, Q` on the curve `H` such that `J = Jac(H)`; + return `[P - Q]`; + + 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; + reutrn `[(u(x), y - v(x))]`. + + EXAMPLES: + + First consider a hyperelliptic curve with an odd-degree model, + hence a unique point at infinity:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^7 + x + 1) + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: P = H.lift_x(1) + sage: Q = H.lift_x(2) + sage: D1 = JH(P); D1 + (x + 12, 4) + sage: D2 = JH(Q); D2 + (x + 11, 1) + sage: D = JH(P,Q); D + (x^2 + 10*x + 2, 8*x + 9) + sage: D == D1 - D2 + True + sage: JH(x^2+10*x+2, 8*x+9) == D + True + + In general, a distinguished point is used to embed points of the curve + in the Jacobian. This works for general models of hyperelliptic curves:: + + sage: R. = PolynomialRing(GF(13)) + sage: H = HyperellipticCurveSmoothModel(2*x^8 + x + 1) + sage: H.is_inert() + True + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: P = H.lift_x(1) + sage: D1 = JH(P); D1 + (x^2 + 12*x, 3*x + 12 : 0) + + To understand this output, one needs to look at the distinguished + point:: + + sage: P0 = H.distinguished_point(); P0 + (0 : 1 : 1) + sage: JH(P) == JH(P,P0) + True + sage: JH(P0) + (1, 0 : 1) + + We may change the distinguished point. Of course, the divisor `[P - Q]` + does not depend on the choice of the distinguished point:: + + sage: Q = H.lift_x(3) + sage: JH(P,Q) + (x^2 + 9*x + 3, 4*x + 11 : 0) + sage: H.set_distinguished_point(H.random_point()) + sage: JH(P) # random + (x^2 + 6*x + 6, 10*x + 5 : 0) + sage: JH(P,Q) + (x^2 + 9*x + 3, 4*x + 11 : 0) + + TESTS: + + Ensure that field elements are treated as mumford coordinates:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^7 - x^2 - 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 6*x^2 + 6 + sage: J(H.lift_x(3)) + (x + 4, 0) + sage: _ == J(x + 4, 0) == J(x + 4, R(0)) + True + + TODO: + + - Allow sending a field element corresponding to the x-coordinate of a point? + + - Use ``__classcall__`` to sanitise input? + + - Add doctest for base extension + """ + R = self.extended_curve().polynomial_ring() + + if len(args) == 0 or (len(args) == 1 and args[0] == ()): + # this returns the incorrect result because subclasses like the inert case may implement + # .zero differently + # return self._morphism_element(self, R.one(), R.zero(), check=check) + return self.zero(check=check) + + if len(args) == 1 and isinstance(args[0], (list, tuple)): + args = args[0] + + if len(args) == 1: + P1 = args[0] + if P1 == 0: + return self.zero(check=check) + elif isinstance(P1, self._morphism_element): + return P1 + elif isinstance(P1, SchemeMorphism_point_weighted_projective_ring): + args = args + ( + self.extended_curve().distinguished_point(), + ) # this case will now be handled below. + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + if len(args) == 2: + P1 = args[0] + P2 = args[1] + if isinstance(P1, SchemeMorphism_point_weighted_projective_ring) and isinstance( + P2, SchemeMorphism_point_weighted_projective_ring + ): + u1, v1 = self.point_to_mumford_coordinates(P1) + P2_inv = self.extended_curve().hyperelliptic_involution(P2) + u2, v2 = self.point_to_mumford_coordinates(P2_inv) + u, v = self.cantor_composition(u1, v1, u2, v2) + # This checks whether P1 and P2 can be interpreted as polynomials + elif R.coerce_map_from(parent(P1)) and R.coerce_map_from(parent(P2)): + u = R(P1) + v = R(P2) + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + if len(args) > 2: + raise ValueError("at most two arguments are allowed as input") + + return self._morphism_element(self, u, v, check=check) + + def zero(self, check=True): + """ + Return the zero element of this jacobian homset. + """ + H = self.extended_curve() + R = H.polynomial_ring() + return self._morphism_element(self, R.one(), R.zero(), check=check) + + def __cantor_double_generic(self, u1, v1): + """ + Efficient cantor composition for doubling an affine divisor + + Returns the Cantor composition of (u1, v1) with (u1, v1) + together with the degree of the polynomial ``s`` which is + needed for computing weights for the split and inert models + """ + f, h = self.extended_curve().hyperelliptic_polynomials() + + # New mumford coordinates + if h.is_zero(): + s, _, e2 = u1.xgcd(v1 + v1) + u3 = (u1 // s) ** 2 + v3 = v1 + e2 * (f - v1**2) // s + else: + s, _, e2 = u1.xgcd(v1 + v1 + h) + u3 = (u1 // s) ** 2 + v3 = v1 + e2 * (f - v1 * h - v1**2) // s + v3 = v3 % u3 + + return u3, v3, s.degree() + + def _cantor_composition_generic(self, u1, v1, u2, v2): + """ + Helper function for the Cantor composition algorithm. + + OUTPUT: + + The Cantor composition of ``(u1, v1)`` with ``(u2, v2)``, + together with the degree of the polynomial ``s`` which is + needed for computing the weights for the split and inert models. + """ + # Collect data from HyperellipticCurve + H = self.extended_curve() + f, h = H.hyperelliptic_polynomials() + g = H.genus() + + # Ensure D1 and D2 are semi-reduced divisors + assert ( + v1.degree() < u1.degree() and v2.degree() < u2.degree() + ), "The degree of bi must be smaller than ai" + assert ( + u1.degree() <= 2 * g + 2 and u2.degree() <= 2 * g + 2 + ), f"The degree of ai must be smaller than 2g+2, {u1.degree()}, {u2.degree()}" + + # Special case: duplication law + if u1 == u2 and v1 == v2: + return self.__cantor_double_generic(u1, v1) + + # Step One + s0, _, e2 = u1.xgcd(u2) + v1_m_v2 = v1 - v2 + + # Special case: when gcd(u0, u1) == 1 we can + # avoid many expensive steps as we have s = 1 + if s0.is_one(): + u3 = u1 * u2 + v3 = v2 + e2 * u2 * v1_m_v2 + v3 = v3 % u3 + return u3, v3, 0 + + # Step Two + w0 = v1 + v2 + h + + # Another special case, when w0 is zero we skip + # a xgcd and can return early + if w0.is_zero(): + u3 = (u1 * u2) // (s0**2) + v3 = v2 + e2 * v1_m_v2 * (u2 // s0) + v3 = v3 % u3 + return u3, v3, s0.degree() + + # Step Three + s, c1, c2 = s0.xgcd(w0) + u3 = (u1 * u2) // (s**2) + v3 = v2 + (c1 * e2 * v1_m_v2 * u2 + c2 * (f - h * v2 - v2**2)) // s + v3 = v3 % u3 + return u3, v3, s.degree() + + def _cantor_reduction_generic(self, u0, v0): + """ + Helper function for the Cantor composition algorithm. + + OUTPUT: + + The reduced divisor of ``(u0, v0)``. + + NOTE: + + The `u`-coordinate of the output is not necessarily monic. That step is + delayed to :meth:`cantor_reduction` to save time. + """ + # Collect data from HyperellipticCurve + H = self.extended_curve() + f, h = H.hyperelliptic_polynomials() + + # Compute u' and v' + u1 = (v0**2 + h * v0 - f) // u0 + v1 = (-h - v0) % u1 + + return u1, v1 + + def cantor_composition(self, u1, v1, u2, v2): + """ + Return the Cantor composition of ``(u1, v1)`` and ``(u2, v2)``. + + EXAMPLES:: + + TODO + """ + u3, v3, _ = self._cantor_composition_generic(u1, v1, u2, v2) + return u3, v3 + + def cantor_reduction(self, u0, v0): + """ + Return the Cantor reduced ``(u0, v0)``. + + EXAMPLES:: + + TODO + """ + return self._cantor_reduction_generic(u0, v0) + + def lift_u(self, u, all=False): + """ + Return one or all points with given `u`-coordinate. + + This method is deterministic: it returns the same data each time when + called with the same `u`. + + Currently only implemented for Jacobians over a finite field. + + INPUT: + + * ``u`` -- an element of the base ring of the Jacobian + + * ``all`` -- boolean (default: ``False``); if ``True``, return a + (possibly empty) list of all points with the given `u`-coordinate; if + ``False``, return just one point, or raise a ``ValueError`` if there + are none. + + OUTPUT: + + A point on this Jacobian. + + EXAMPLES:: + + sage: R. = GF(1993)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + x + 1) + sage: J = H.jacobian() + sage: P = J.lift_u(x^2 + 42*x + 270); P + (x^2 + 42*x + 270, 1837*x + 838) + """ + H = self.extended_curve() + K = H.base_ring() + g = H.genus() + + H_is_split = H.is_split() + + if u.is_zero(): + if H_is_split: + if not all: + return self._morphism_element(self, R.one(), R.zero(), 0) + return [ + self._morphism_element(self, R.one(), R.zero(), n) + for n in range(g + 1) + ] + if not all: + return self.zero() + return [self.zero()] + + if not isinstance(K, FiniteField_generic) or K.degree() != 1: + raise NotImplementedError( + "lift_u is only implemented for Jacobians over a finite field of prime order" + ) + + R = H.polynomial_ring() + f, h = H.hyperelliptic_polynomials() + u1, v1 = R.one(), R.zero() + + u = R(u).monic() + u_factors = u.factor() + vss = [] + + for x, e in u_factors: + # Solve y^2 + hy - f = 0 mod x + + # TODO: is this the most efficient method? Maybe we should write + # a helper function which computes y^2 + hy - f = 0 mod x which + # properly handles trivial cases like when x is linear? + K_ext = K.extension(modulus=x, names="a") + y_ext = polygen(K_ext, "y_ext") + h_ = K_ext(h % x) + f_ = K_ext(f % x) + vs = (y_ext**2 + h_ * y_ext - f_).roots(multiplicities=False) + if len(vs) == 0 and not all: + raise ValueError(f"no point with u-coordinate {u} on {self}") + + vss.append(vs) + + def postprocess_uv(u1, v1, n=None): + # This function converts (u, v) into the point being returned, handling split cases + if H.is_split(): + if n is None or not (0 <= n <= g - u1.degree()): + # this is an internal function + raise ValueError( + f"bug: n must be an integer between 0 and {g - u1.degree()}" + ) + return self._morphism_element(self, u1, v1, n, check=False) + + # We need to ensure the degree of u is even + if H.is_inert(): + if u1.degree() % 2: + # TODO: make composition with distinguished_point its own function? + P0 = self.extended_curve().distinguished_point() + X0, Y0, _ = P0._coords + X = R.gen() # TODO use better variable names in this function + _, h = self.extended_curve().hyperelliptic_polynomials() + u0 = X - X0 + v0 = R(-Y0 - h(X0)) + u1, v1, _ = self._cantor_composition_generic(u1, v1, u0, v0) + assert not (u1.degree() % 2), f"{u1} must have even degree" + + return self._morphism_element(self, u1, v1, check=False) + + points = [] + for vv in itertools.product(*vss): + u1, v1 = R.one(), R.zero() + try: + for (x, e), v in zip(u_factors, map(R, vv)): + for _ in range(e): + u1, v1, _ = self._cantor_composition_generic(u1, v1, x, v) + # v is not rational, so we skip it + except (ValueError, AttributeError): + pass + + if not all: + return postprocess_uv(u1, v1, n=0) + + if H_is_split: + for n in range(g - u1.degree() + 1): + points.append(postprocess_uv(u1, v1, n=n)) + else: + points.append(postprocess_uv(u1, v1)) + + return points + + def _random_element_cover(self, degree=None): + r""" + Return a random element from the Jacobian. + + Distribution is not uniformly random, but returns the entire group. + """ + H = self.extended_curve() + R = H.polynomial_ring() + g = H.genus() + + # For the inert case, the genus must be even + if H.is_inert(): + assert not (g % 2) + + if degree is None: + degree = (-1, g) + + while True: + # TODO: i think we can skip this and simply ensure u + # is even degree with composition with the distinguished + # point? + # if H.is_inert() and (u.degree() % 2) == 1: + # #TODO: better method to sample even degree polynomials + # continue + u = R.random_element(degree=degree, monic=True) + try: + return choice(self.lift_u(u, all=True)) + # TODO: better handling rather than looping with try / except? + except IndexError: + pass + + def _random_element_rational(self): + r""" + Return a random element from the Jacobian. This algorithm is faster + than :meth:`_random_element_cover` but is **NOT** surjective on the set + of points. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: f = x^5 + 2*x^4 + 4*x^3 + x^2 + 4*x + 3 + sage: H = HyperellipticCurveSmoothModel(f) + sage: J = H.jacobian() + sage: J.order() + 16 + sage: JH = J.point_homset() + sage: len(set(JH._random_element_rational() for _ in range(300))) + 8 + """ + H = self.extended_curve() + g = H.genus() + + # We randomly sample 2g + 1 points on the hyperelliptic curve + points = [H.random_point() for _ in range(2 * g + 1)] + + # We create 2g + 1 divisors of the form (P) - infty + divisors = [self(P) for P in points if P[2] != 0] + + # If we happened to only sample the point at infinity, we return this + # Otherwise we compute the sum of all divisors. + if not divisors: + return self.zero() + return sum(divisors, start=self.zero()) + + def random_element(self, fast=False, *args, **kwargs): + r""" + Returns a random element from the Jacobian. Distribution is **NOT** + uniformly random. + + INPUT: + + - ``fast`` -- (boolean, default ``True``) If set to ``True``, a fast + algorithm is used, but the output is **NOT** guaranteed to cover the + entire Jacobian. See examples below. If set to ``False``, a slower + algorithm is used, but covers the entire Jacobian. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: f = x^5 + 2*x^4 + 4*x^3 + x^2 + 4*x + 3 + sage: H = HyperellipticCurveSmoothModel(f) + sage: J = H.jacobian() + + This example demonstrates that the ``fast`` algorithm is not + necessarily surjective on the rational points of the Jacobian:: + + sage: JH = J.point_homset() + sage: len(set(JH.random_element(fast=True) for _ in range(300))) + 8 + sage: len(set(JH.random_element(fast=False) for _ in range(300))) + 16 + sage: J.order() + 16 + """ + if not isinstance(self.base_ring(), FiniteField_generic): + raise NotImplementedError( + "random element of Jacobian is only implemented over Finite Fields" + ) + + if fast: + return self._random_element_rational(*args, **kwargs) + return self._random_element_cover(*args, **kwargs) + + def points(self): + """ + Return all points on this Jacobian `J(K)`. + + .. WARNING:: + + This code is not efficient at all. + """ + H = self.extended_curve() + R = H.polynomial_ring() + g = H.genus() + + # TODO: after `monic` argument is added, use it + ss = [] + for u in R.polynomials(max_degree=g): + if u.is_monic(): + for P in self.lift_u(u, all=True): + # UGLY HACK: it keeps overcounting 0 without this... + # failing example: y^2 = x^5 + x over F_5 + if not u.is_one() and list(P)[:2] == [1, 0]: + continue + ss.append(P) + + if H.is_ramified() or H.is_split(): + assert len(ss) == self.order() + return ss + + # TODO: remove this + # failing example: y^2 = 2x^6 + 1 over F_5 + ss = sorted(set(ss)) + assert len(ss) == self.order() + return ss + + rational_points = points diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py new file mode 100644 index 00000000000..e88b84d8906 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py @@ -0,0 +1,24 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( + HyperellipticJacobianHomset, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldInert, +) + + +class HyperellipticJacobianHomsetInert(HyperellipticJacobianHomset): + def __init__(self, Y, X, **kwds): + super().__init__(Y, X, **kwds) + self._morphism_element = MumfordDivisorClassFieldInert + + def zero(self, check=True): + """ + Return the zero element of the Jacobian + """ + g = self.curve().genus() + if g % 2: + raise ValueError( + "unable to perform arithmetic for inert models of odd genus" + ) + R = self.curve().polynomial_ring() + return self._morphism_element(self, R.one(), R.zero(), check=check) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py new file mode 100644 index 00000000000..8f8045a7500 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py @@ -0,0 +1,12 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( + HyperellipticJacobianHomset, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldRamified, +) + + +class HyperellipticJacobianHomsetRamified(HyperellipticJacobianHomset): + def __init__(self, Y, X, **kwds): + super().__init__(Y, X, **kwds) + self._morphism_element = MumfordDivisorClassFieldRamified diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py new file mode 100644 index 00000000000..ddb7c246c55 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -0,0 +1,269 @@ +from sage.rings.integer import Integer +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( + HyperellipticJacobianHomset, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldSplit, +) + +# TODO: move this +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, +) +from sage.structure.element import parent + + +class HyperellipticJacobianHomsetSplit(HyperellipticJacobianHomset): + def __init__(self, Y, X, **kwds): + super().__init__(Y, X, **kwds) + self._morphism_element = MumfordDivisorClassFieldSplit + + def zero(self): + """ + Return the zero element of the Jacobian + """ + g = self.curve().genus() + R = self.curve().polynomial_ring() + n = (g + 1) // 2 + return self._morphism_element(self, R.one(), R.zero(), n) + + def point_to_mumford_coordinates(self, P): + """ + On input a point P, return the Mumford coordinates + of (the affine part of) the divisor [P] and an integer n, + where + * n = 1 if P is the point oo+ + * n = 0 otherwise . + """ + + R, x = self.curve().polynomial_ring().objgen() + [X, Y, Z] = P._coords + if Z == 0: + alpha = Y / X + if alpha == self.curve().roots_at_infinity()[0]: + n = 1 + else: + n = 0 + return R.one(), R.zero(), n + u = x - X + v = R(Y) + return u, v, 0 + + def __call__(self, *args, check=True): + r""" + 3. Polynomials u,v such that `v^2 + h*v - f = 0 mod u`, + returning J(u,v,n) with n = ((g - deg(u))/2).ceil(). + + 4. Polynomials u,v and an integer n such that `v^2 + h*v - f = 0 mod u` + and 0 <= n <= g/2, + returning J(u,v,n). + + Return a rational point in the abstract Homset `J(K)`, given: + + 1. No arguments or the integer `0`; return `0 \in J`; + + 2. A point `P` on `J = Jac(C)`, return `P`; + + 3. A point `P` on the curve `H` such that `J = Jac(H)`; + return `[P - P_0]`, where `P_0` is the distinguished point of `H`. + By default, `P_0 = \infty`; + + 4. Two points `P, Q` on the curve `H` such that `J = Jac(H)`; + return `[P - Q]`; + + 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; + reutrn `[(u(x), y - v(x)) : \lceil (g - \deg(u)) / 2 \rceil]`; + + 6. Polynomials `(u, v)` and an integer `n` such that + `v^2 + hv - f \equiv 0 \pmod u` and `0 \leq n \leq g / 2`; + return `[u, v : n]`. + + EXAMPLES:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 1) + sage: H.is_split() + True + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: P = H.lift_x(1) + sage: Q = H.lift_x(2) + sage: D1 = JH(P); D1 + (x + 12, 4 : 1) + sage: D2 = JH(Q); D2 + (x + 11, 5 : 1) + sage: D = JH(P,Q); D + (x^2 + 10*x + 2, 4*x : 1) + sage: D == D1 - D2 + True + sage: JH(x^2+10*x+2, 4*x, 1) == D + True + sage: JH(x^2+10*x+2, 4*x, 0) == D + False + + The points at infinity may also be embedded into the Jacobian:: + + sage: [P0,P1] = H.points_at_infinity() + sage: JH(P0) + (1, 0 : 2) + sage: JH(P1) + (1, 0 : 1) + + TESTS:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 1) + sage: J = Jacobian(H) + sage: JH = J.point_homset() + sage: J() == J(0) == J(1, 0) == J.zero() == JH(0) == 0 + True + + TODO: Merge this code with that of `HyperellipticJacobianHomset` + """ + g = self.curve().genus() + R = self.curve().polynomial_ring() + + if len(args) > 3: + raise ValueError("at most three arguments are allowed as input") + + if len(args) == 0 or (len(args) == 1 and args[0] == ()): + return self._morphism_element( + self, R.one(), R.zero(), n=(g + 1) // 2, check=check + ) + + if len(args) == 1 and isinstance(args[0], (list, tuple)): + args = args[0] + + if len(args) == 1: + P1 = args[0] + if P1 == 0: + u = R.one() + v = R.zero() + n = (g + 1) // 2 + elif isinstance(P1, self._morphism_element): + return P1 + elif isinstance(P1, SchemeMorphism_point_weighted_projective_ring): + args = args + ( + self.curve().distinguished_point(), + ) # this case will now be handled below. + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + if len(args) == 2 or len(args) == 3: + P1 = args[0] + P2 = args[1] + if isinstance(P1, SchemeMorphism_point_weighted_projective_ring) and isinstance( + P2, SchemeMorphism_point_weighted_projective_ring + ): + if len(args) == 3: + raise ValueError("the input must consist of at most two points") + u1, v1, n1 = self.point_to_mumford_coordinates(P1) + P2_inv = self.curve().hyperelliptic_involution(P2) + u2, v2, n2 = self.point_to_mumford_coordinates(P2_inv) + u, v, _ = self._cantor_composition_generic(u1, v1, u2, v2) + n = (g + 1) // 2 - 1 + n1 + n2 # this solution is a bit hacky + # This checks whether P1 and P2 can be interpreted as polynomials + elif R.coerce_map_from(parent(P1)) and R.coerce_map_from(parent(P2)): + u = R(P1) + v = R(P2) + if len(args) == 3 and isinstance(args[2], (int, Integer)): + n = args[2] + else: + n = ( + g - u.degree() + 1 + ) // 2 # TODO: do we really want to allow this input? + else: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) + + return self._morphism_element(self, u, v, n=n, check=check) + + def cantor_composition(self, u1, v1, n1, u2, v2, n2): + """ + Follows algorithm 3.4 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + + TODO: when h = 0 we can speed this up. + """ + # Collect data from HyperellipticCurve + H = self.curve() + g = H.genus() + + # Cantor composition + u3, v3, s_deg = self._cantor_composition_generic(u1, v1, u2, v2) + + # Compute new weight + n3 = n1 + n2 + s_deg - ((g + 1) // 2) + + return u3, v3, n3 + + def cantor_reduction(self, u0, v0, n0): + """ + Follows algorithm 3.5 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Collect data from HyperellipticCurve + H = self.curve() + g = H.genus() + + # Perform regular cantor reduction + u1, v1 = self._cantor_reduction_generic(u0, v0) + + # Compute the counter weights + d0 = u0.degree() + d1 = u1.degree() + a_plus, a_minus = H.roots_at_infinity() + + if v0.degree() <= g + 1: + leading_coefficient = v0[g + 1] # check coefficient of x^(g+1) + if leading_coefficient == a_plus: + n1 = n0 + d0 - g - 1 + elif leading_coefficient == a_minus: + n1 = n0 + g + 1 - d1 + else: + n1 = n0 + (d0 - d1) // 2 + else: + n1 = n0 + (d0 - d1) // 2 + return u1, v1, n1 + + def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): + """ + Follows algorithm 3.6 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Collect data from HyperellipticCurve + H = self.curve() + f, h = H.hyperelliptic_polynomials() + g = H.genus() + + # Pick either G_plus or G_minus for reduction + G_plus, G_minus = H.infinite_polynomials() + if plus: + G = G_plus + else: + G = G_minus + + v1_prime = G + ((v0 - G) % u0) + u1 = (v1_prime**2 + h * v1_prime - f) // u0 + u1 = u1.monic() + v1 = (-h - v1_prime) % u1 + + # Compute the counter weights + if plus: + n1 = n0 + u0.degree() - g - 1 + else: + n1 = n0 + g + 1 - u1.degree() + + return u1, v1, n1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py new file mode 100644 index 00000000000..0fbc9a6e5af --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -0,0 +1,368 @@ +from sage.groups.generic import order_from_multiple +from sage.misc.cachefunc import cached_method +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.schemes.generic.morphism import SchemeMorphism +from sage.structure.element import AdditiveGroupElement +from sage.structure.richcmp import richcmp + + +class MumfordDivisorClassField(AdditiveGroupElement, SchemeMorphism): + r""" + An element of a Jacobian defined over a field, i.e. in + `J(K) = \mathrm{Pic}^0_K(C)`. + """ + + def __init__(self, parent, u, v, check=True): + SchemeMorphism.__init__(self, parent) + + if not isinstance(u, Polynomial) or not isinstance(v, Polynomial): + raise TypeError(f"arguments {u = } and {v = } must be polynomials") + + # TODO: + # 1. allow elements of the base field as input + # (in particular something like (u,v) = (x-alpha, 0)) + + # Ensure the divisor is valid + if check: + f, h = parent.curve().hyperelliptic_polynomials() + assert ( + v**2 + v * h - f + ) % u == 0, f"{u = }, {v = } do not define a divisor on the Jacobian" + + # TODO: should we automatically do reduction here if the degree of u is + # too large? + + self._parent = parent + self._u = u + self._v = v + + def parent(self): + return self._parent + + def scheme(self): + r""" + Return the scheme this morphism maps to; or, where this divisor lives. + + .. WARNING:: + + Although a pointset is defined over a specific field, the + scheme returned may be over a different (usually smaller) + field. The example below demonstrates this: the pointset + is determined over a number field of absolute degree 2 but + the scheme returned is defined over the rationals. + + EXAMPLES:: + + sage: x = QQ['x'].gen() + sage: f = x^5 + x + sage: H = HyperellipticCurve(f) + sage: F. = NumberField(x^2 - 2, 'a') # needs sage.rings.number_field + sage: J = H.jacobian()(F); J # needs sage.rings.number_field + Set of rational points of Jacobian of Hyperelliptic Curve + over Number Field in a with defining polynomial x^2 - 2 + defined by y^2 = x^5 + x + + :: + + sage: P = J(H.lift_x(F(1))) # needs sage.rings.number_field + sage: P.scheme() # needs sage.rings.number_field + Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x + """ + return self.codomain() + + def _repr_(self) -> str: + return f"({self._u}, {self._v})" + + def is_zero(self): + """ + Return ``True`` if this point is zero. + + EXAMPLES:: + + sage: x = polygen(GF(5)) + sage: H = HyperellipticCurveSmoothModel(x^5 + 3 * x + 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 5 defined by y^2 = x^5 + 3*x + 1 + sage: points = list(J) + sage: [P for P in points if P.is_zero()] + [(1, 0)] + sage: [P for P in points if P == 0] + [(1, 0)] + """ + return self._u.is_one() and self._v.is_zero() + + def uv(self): + """ + Return the `u` and `v` component of this Mumford divisor. + + EXAMPLES:: + + sage: x = polygen(GF(1993)) + sage: H = HyperellipticCurveSmoothModel(x^7 + 3 * x + 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 1993 defined by y^2 = x^7 + 3*x + 1 + sage: u, v = x^3 + 1570*x^2 + 1930*x + 81, 368*x^2 + 1478*x + 256 + sage: P = J(u, v); P + (x^3 + 1570*x^2 + 1930*x + 81, 368*x^2 + 1478*x + 256) + sage: P.uv() == (u, v) + True + """ + return (self._u, self._v) + + def _richcmp_(self, other, op) -> bool: + """ + Method for rich comparison. + + TESTS:: + + sage: x = polygen(GF(23)) + sage: H = HyperellipticCurveSmoothModel(x^7 + x + 1) + sage: J = H.jacobian(); J + Jacobian of Hyperelliptic Curve over Finite Field of size 23 defined by y^2 = x^7 + x + 1 + sage: P = J.random_element() + sage: P == P + True + sage: P != P + False + + sage: Z = J(0); Z + (1, 0) + sage: Z == 0 + True + sage: Z != 0 + False + """ + # _richcmp_ is called after type unification/coercion + assert isinstance(other, MumfordDivisorClassField) + return richcmp(tuple(self), tuple(other), op) + + def __iter__(self): + """ + TESTS: + + Indirect tests:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x + 1) + sage: J = H.jacobian() + sage: P = J(x + 5, 12) + sage: list(P) + [x + 5, 12] + sage: tuple(P) + (x + 5, 12) + """ + yield from [self._u, self._v] + + def __getitem__(self, n): + return (self._u, self._v)[n] + + def __hash__(self): + return hash(tuple(self)) + + def __bool__(self): + return not self.is_zero() + + @cached_method + def order(self): + n = self.parent().order() + return order_from_multiple(self, n) + + def degree(self): + """ + Returns the degree of the affine part of the divisor. + """ + return self._u.degree() + + def _add_(self, other): + # `other` should be coerced automatically before reaching here + assert isinstance(other, type(self)) + + # Collect data from HyperellipticCurve + H = self.parent().curve() + g = H.genus() + + # Extract out mumford coordinates + u1, v1 = self.uv() + u2, v2 = other.uv() + + # Step one: cantor composition of the two divisors + u3, v3 = self._parent.cantor_composition(u1, v1, u2, v2) + + # Step two: cantor reduction of the above to ensure + # the degree of u is smaller than g + 1 + while u3.degree() > g: + u3, v3 = self._parent.cantor_reduction(u3, v3) + u3 = u3.monic() + v3 = v3 % u3 + + return self._parent(u3, v3, check=False) + + def _neg_(self): + _, h = self.parent().curve().hyperelliptic_polynomials() + u0, v0 = self.uv() + + if h.is_zero(): + return self._parent(u0, -v0, check=False) + + v1 = (-h - v0) % u0 + return self._parent(u0, v1, check=False) + + +# ======================================================================= +# Child classes for representation for ramified, inert and split models +# ======================================================================= + + +class MumfordDivisorClassFieldRamified(MumfordDivisorClassField): + def __init__(self, parent, u, v, check=True): + if not parent.curve().is_ramified(): + raise TypeError("hyperelliptic curve must be ramified") + + super().__init__(parent, u, v, check=check) + + +class MumfordDivisorClassFieldInert(MumfordDivisorClassField): + def __init__(self, parent, u, v, check=True): + if not parent.curve().is_inert(): + raise TypeError("hyperelliptic curve must be inert") + + if u.degree() % 2: + raise ValueError(f"mumford coordinate {u} must have even degree") + + g = parent.curve().genus() + self._n = (g - u.degree()) // 2 + super().__init__(parent, u, v, check=check) + + def __repr__(self): + return f"({self._u}, {self._v} : {self._n})" + + +class MumfordDivisorClassFieldSplit(MumfordDivisorClassField): + def __init__(self, parent, u, v, n=0, check=True): + if not parent.curve().is_split(): + raise TypeError("hyperelliptic curve must be split") + + # Ensure the weight is set correctly + g = parent.curve().genus() + assert 0 <= n <= (g - u.degree()) + self._n = n + self._m = g - u.degree() - n + + super().__init__(parent, u, v, check=check) + + def __repr__(self): + return f"({self._u}, {self._v} : {self._n})" + + def is_zero(self): + g = self._parent.curve().genus() + return self._u.is_one() and self._v.is_zero() and self._n == (g + 1) // 2 + + def __iter__(self): + """ + TESTS: + + Indirect tests:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^6 - x + 1) + sage: J = H.jacobian() + sage: P = J(x + 2, 5) + sage: list(P) + [x + 2, 5, 1] + sage: tuple(P) + (x + 2, 5, 1) + sage: Q = J(x + 2, 5, 0) + sage: list(Q) + [x + 2, 5, 0] + sage: tuple(Q) + (x + 2, 5, 0) + sage: P == Q + False + """ + yield from [self._u, self._v, self._n] + + def __hash__(self): + return hash(tuple(self)) + + def _add_(self, other): + r""" + Follows algorithm 3.7 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Ensure we are adding two divisors + assert isinstance(other, type(self)) + + # Collect data from HyperellipticCurve + H = self.parent().curve() + g = H.genus() + + # Extract out mumford coordinates + u1, v1 = self.uv() + u2, v2 = other.uv() + + # Extract out integers for weights + n1, n2 = self._n, other._n + + # Step one: cantor composition of the two divisors + u3, v3, n3 = self._parent.cantor_composition(u1, v1, n1, u2, v2, n2) + + # Step two: cantor reduction of the above to ensure + # the degree of u is smaller than g + 1 + while u3.degree() > (g + 1): + u3, v3, n3 = self._parent.cantor_reduction(u3, v3, n3) + u3 = u3.monic() + + # Step three: compose and then reduce at infinity to ensure + # unique representation of D + while n3 < 0 or n3 > g - u3.degree(): + if n3 < 0: + u3, v3, n3 = self._parent.cantor_compose_at_infinity( + u3, v3, n3, plus=False + ) + else: + u3, v3, n3 = self._parent.cantor_compose_at_infinity( + u3, v3, n3, plus=True + ) + + return self._parent(u3, v3, n3, check=False) + + def _neg_(self): + r""" + Follows algorithm 3.8 of + + Efficient Arithmetic on Hyperelliptic Curves With Real Representation + David J. Mireles Morales (2008) + https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + """ + # Collect data from HyperellipticCurve + H = self.parent().curve() + _, h = H.hyperelliptic_polynomials() + g = H.genus() + + u0, v0 = self.uv() + n0, m0 = self._n, self._m + + # Case for even genus + if not (g % 2): + v1 = (-h - v0) % u0 + u1 = u0 + n1 = m0 + # Odd genus, positive n0 + elif n0 > 0: + v1 = (-h - v0) % u0 + # Note: I think this is a typo in the paper + # n1 = g - m0 - u1.degree() + 1 + u1 = u0 + n1 = m0 + 1 + else: + # Composition at infinity always with plus=True. + # want to "substract" \infty_+ - \infty_- + (u1, v1, n1) = self._parent.cantor_compose_at_infinity( + u0, -h - v0, n0, plus=True + ) + n1 = n1 - n0 + m0 + 1 + + return self._parent(u1, v1, n1, check=False) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build new file mode 100644 index 00000000000..f2b2dff0459 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build @@ -0,0 +1,31 @@ +py.install_sources( + 'all.py', + 'hyperelliptic_constructor.py', + 'hyperelliptic_finite_field.py', + 'hyperelliptic_g2.py', + 'hyperelliptic_generic.py', + 'hyperelliptic_padic_field.py', + 'hyperelliptic_rational_field.py', + 'invariants.py', + 'jacobian_g2_generic.py', + 'jacobian_g2_homset_inert.py', + 'jacobian_g2_homset_ramified.py', + 'jacobian_g2_homset_split.py', + 'jacobian_generic.py', + 'jacobian_homset_generic.py', + 'jacobian_homset_inert.py', + 'jacobian_homset_ramified.py', + 'jacobian_homset_split.py', + 'jacobian_morphism.py', + 'mestre.py', + 'monsky_washnitzer.py', + 'weighted_projective_curve.py', + 'weighted_projective_homset.py', + 'weighted_projective_point.py', + 'weighted_projective_space.py', + subdir: 'sage/schemes/hyperelliptic_curves_smooth_model', +) + +extension_data_cpp = {} + + diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py new file mode 100644 index 00000000000..9ca63591e49 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/mestre.py @@ -0,0 +1,377 @@ +r""" +Mestre's algorithm + +This file contains functions that: + +- create hyperelliptic curves from the Igusa-Clebsch invariants (over + `\QQ` and finite fields) +- create Mestre's conic from the Igusa-Clebsch invariants + +AUTHORS: + +- Florian Bouyer +- Marco Streng + +""" +# ***************************************************************************** +# Copyright (C) 2011, 2012, 2013 +# Florian Bouyer +# Marco Streng +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.matrix.constructor import Matrix +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, +) +from sage.schemes.plane_conics.constructor import Conic + + +# TODO: +# precision is unused +def HyperellipticCurve_from_invariants( + i, reduced=True, precision=None, algorithm="default" +): + r""" + Returns a hyperelliptic curve with the given Igusa-Clebsch invariants up to + scaling. + + The output is a curve over the field in which the Igusa-Clebsch invariants + are given. The output curve is unique up to isomorphism over the algebraic + closure. If no such curve exists over the given field, then raise a + ValueError. + + INPUT: + + - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch + invariants: I2,I4,I6,I10. + - ``reduced`` - Boolean (default = True) If True, tries to reduce the + polynomial defining the hyperelliptic curve using the function + :func:`reduce_polynomial` (see the :func:`reduce_polynomial` + documentation for more details). + - ``precision`` - integer (default = None) Which precision for real and + complex numbers should the reduction use. This only affects the + reduction, not the correctness. If None, the algorithm uses the default + 53 bit precision. + - ``algorithm`` - ``'default'`` or ``'magma'``. If set to ``'magma'``, uses + Magma to parameterize Mestre's conic (needs Magma to be installed). + + OUTPUT: + + A hyperelliptic curve object. + + EXAMPLES: + + Examples over the rationals:: + + sage: HyperellipticCurve_from_invariants([3840,414720,491028480,2437709561856]) + Traceback (most recent call last): + ... + NotImplementedError: Reduction of hyperelliptic curves not yet implemented. + See issues #14755 and #14756. + + sage: HyperellipticCurve_from_invariants([3840,414720,491028480,2437709561856], reduced=False) + Hyperelliptic Curve over Rational Field defined by + y^2 = -46656*x^6 + 46656*x^5 - 19440*x^4 + 4320*x^3 - 540*x^2 + 4410*x - 1 + + sage: HyperellipticCurve_from_invariants([21, 225/64, 22941/512, 1]) + Traceback (most recent call last): + ... + NotImplementedError: Reduction of hyperelliptic curves not yet implemented. + See issues #14755 and #14756. + + An example over a finite field:: + + sage: H = HyperellipticCurve_from_invariants([GF(13)(1), 3, 7, 5]); H + Hyperelliptic Curve over Finite Field of size 13 defined by ... + sage: H.igusa_clebsch_invariants() + (4, 9, 6, 11) + + An example over a number field:: + + sage: K = QuadraticField(353, 'a') # needs sage.rings.number_field + sage: H = HyperellipticCurve_from_invariants([21, 225/64, 22941/512, 1], # needs sage.rings.number_field + ....: reduced=false) + sage: f = K['x'](H.hyperelliptic_polynomials()[0]) # needs sage.rings.number_field + + If the Mestre Conic defined by the Igusa-Clebsch invariants has no rational + points, then there exists no hyperelliptic curve over the base field with + the given invariants.:: + + sage: HyperellipticCurve_from_invariants([1,2,3,4]) + Traceback (most recent call last): + ... + ValueError: No such curve exists over Rational Field as there are + no rational points on Projective Conic Curve over Rational Field defined by + -2572155000*u^2 - 317736000*u*v + 1250755459200*v^2 + 2501510918400*u*w + + 39276887040*v*w + 2736219686912*w^2 + + Mestre's algorithm only works for generic curves of genus two, so another + algorithm is needed for those curves with extra automorphism. See also + :issue:`12199`:: + + sage: P. = QQ[] + sage: C = HyperellipticCurveSmoothModel(x^6 + 1) + sage: i = C.igusa_clebsch_invariants() + sage: HyperellipticCurve_from_invariants(i) + Traceback (most recent call last): + ... + TypeError: F (=0) must have degree 2 + + + Igusa-Clebsch invariants also only work over fields of characteristic + different from 2, 3, and 5, so another algorithm will be needed for fields + of those characteristics. See also :issue:`12200`:: + + sage: P. = GF(3)[] + sage: HyperellipticCurveSmoothModel(x^6 + x + 1).igusa_clebsch_invariants() + Traceback (most recent call last): + ... + NotImplementedError: Invariants of binary sextics/genus 2 hyperelliptic curves + not implemented in characteristics 2, 3, and 5 + sage: HyperellipticCurve_from_invariants([GF(5)(1), 1, 0, 1]) + Traceback (most recent call last): + ... + ZeroDivisionError: inverse of Mod(0, 5) does not exist + + ALGORITHM: + + This is Mestre's algorithm [Mes1991]_. Our implementation is based on the + formulae on page 957 of [LY2001]_, cross-referenced with [Wam1999b]_ to + correct typos. + + First construct Mestre's conic using the :func:`Mestre_conic` function. + Parametrize the conic if possible. + Let `f_1, f_2, f_3` be the three coordinates of the parametrization of the + conic by the projective line, and change them into one variable by letting + `F_i = f_i(t, 1)`. Note that each `F_i` has degree at most 2. + + Then construct a sextic polynomial + `f = \sum_{0<=i,j,k<=3}{c_{ijk}*F_i*F_j*F_k}`, + where `c_{ijk}` are defined as rational functions in the invariants + (see the source code for detailed formulae for `c_{ijk}`). + The output is the hyperelliptic curve `y^2 = f`. + """ + from sage.structure.sequence import Sequence + + i = Sequence(i) + k = i.universe() + try: + k = k.fraction_field() + except (TypeError, AttributeError, NotImplementedError): + pass + + MConic, x, y, z = Mestre_conic(i, xyz=True) + if k.is_finite(): + reduced = False + + t = k["t"].gen() + + if algorithm == "magma": + from sage.interfaces.magma import magma + from sage.misc.sage_eval import sage_eval + + if MConic.has_rational_point(algorithm="magma"): + parametrization = [ + l.replace("$.1", "t").replace("$.2", "u") + for l in str(magma(MConic).Parametrization()).splitlines()[4:7] + ] + [F1, F2, F3] = [ + sage_eval(p, locals={"t": t, "u": 1, "a": k.gen()}) + for p in parametrization + ] + else: + raise ValueError( + f"No such curve exists over {k} as there are no " + f"rational points on {MConic}" + ) + elif MConic.has_rational_point(): + parametrization = MConic.parametrization(morphism=False)[0] + [F1, F2, F3] = [p(t, 1) for p in parametrization] + else: + raise ValueError( + f"No such curve exists over {k} as there are no " + f"rational points on {MConic}" + ) + + # setting the cijk from Mestre's algorithm + c111 = 12 * x * y - 2 * y / 3 - 4 * z + c112 = -18 * x**3 - 12 * x * y - 36 * y**2 - 2 * z + c113 = -9 * x**3 - 36 * x**2 * y - 4 * x * y - 6 * x * z - 18 * y**2 + c122 = c113 + c123 = ( + -54 * x**4 - 36 * x**2 * y - 36 * x * y**2 - 6 * x * z - 4 * y**2 - 24 * y * z + ) + c133 = ( + -27 * x**4 / 2 + - 72 * x**3 * y + - 6 * x**2 * y + - 9 * x**2 * z + - 39 * x * y**2 + - 36 * y**3 + - 2 * y * z + ) + c222 = -27 * x**4 - 18 * x**2 * y - 6 * x * y**2 - 8 * y**2 / 3 + 2 * y * z + c223 = 9 * x**3 * y - 27 * x**2 * z + 6 * x * y**2 + 18 * y**3 - 8 * y * z + c233 = ( + -81 * x**5 / 2 + - 27 * x**3 * y + - 9 * x**2 * y**2 + - 4 * x * y**2 + + 3 * x * y * z + - 6 * z**2 + ) + c333 = ( + 27 * x**4 * y / 2 + - 27 * x**3 * z / 2 + + 9 * x**2 * y**2 + + 3 * x * y**3 + - 6 * x * y * z + + 4 * y**3 / 3 + - 10 * y**2 * z + ) + + # writing out the hyperelliptic curve polynomial + f = ( + c111 * F1**3 + + c112 * F1**2 * F2 + + c113 * F1**2 * F3 + + c122 * F1 * F2**2 + + c123 * F1 * F2 * F3 + + c133 * F1 * F3**2 + + c222 * F2**3 + + c223 * F2**2 * F3 + + c233 * F2 * F3**2 + + c333 * F3**3 + ) + + try: + f = f * f.denominator() # clear the denominator + except (AttributeError, TypeError): + pass + + if reduced: + raise NotImplementedError( + "Reduction of hyperelliptic curves not " + "yet implemented. " + "See issues #14755 and #14756." + ) + + return HyperellipticCurveSmoothModel(f) + + +def Mestre_conic(i, xyz=False, names="u,v,w"): + r""" + Return the conic equation from Mestre's algorithm given the Igusa-Clebsch + invariants. + + It has a rational point if and only if a hyperelliptic curve + corresponding to the invariants exists. + + INPUT: + + - ``i`` - list or tuple of length 4 containing the four Igusa-Clebsch + invariants: I2, I4, I6, I10 + - ``xyz`` - Boolean (default: False) if True, the algorithm also + returns three invariants x,y,z used in Mestre's algorithm + - ``names`` (default: 'u,v,w') - the variable names for the conic + + OUTPUT: + + A Conic object + + EXAMPLES: + + A standard example:: + + sage: Mestre_conic([1,2,3,4]) + Projective Conic Curve over Rational Field defined by + -2572155000*u^2 - 317736000*u*v + 1250755459200*v^2 + 2501510918400*u*w + + 39276887040*v*w + 2736219686912*w^2 + + Note that the algorithm works over number fields as well:: + + sage: x = polygen(ZZ, 'x') + sage: k = NumberField(x^2 - 41, 'a') # needs sage.rings.number_field + sage: a = k.an_element() # needs sage.rings.number_field + sage: Mestre_conic([1, 2 + a, a, 4 + a]) # needs sage.rings.number_field + Projective Conic Curve over Number Field in a with defining polynomial x^2 - 41 + defined by (-801900000*a + 343845000)*u^2 + (855360000*a + 15795864000)*u*v + + (312292800000*a + 1284808579200)*v^2 + (624585600000*a + 2569617158400)*u*w + + (15799910400*a + 234573143040)*v*w + (2034199306240*a + 16429854656512)*w^2 + + And over finite fields:: + + sage: Mestre_conic([GF(7)(10), GF(7)(1), GF(7)(2), GF(7)(3)]) + Projective Conic Curve over Finite Field of size 7 + defined by -2*u*v - v^2 - 2*u*w + 2*v*w - 3*w^2 + + An example with ``xyz``:: + + sage: Mestre_conic([5,6,7,8], xyz=True) + (Projective Conic Curve over Rational Field + defined by -415125000*u^2 + 608040000*u*v + 33065136000*v^2 + + 66130272000*u*w + 240829440*v*w + 10208835584*w^2, + 232/1125, -1072/16875, 14695616/2109375) + + ALGORITHM: + + The formulas are taken from pages 956 - 957 of [LY2001]_ and based on pages + 321 and 332 of [Mes1991]_. + + See the code or [LY2001]_ for the detailed formulae defining x, y, z and L. + + """ + from sage.structure.sequence import Sequence + + k = Sequence(i).universe() + try: + k = k.fraction_field() + except (TypeError, AttributeError, NotImplementedError): + pass + + I2, I4, I6, I10 = i + + # Setting x,y,z as in Mestre's algorithm (Using Lauter and Yang's formulas) + x = 8 * (1 + 20 * I4 / (I2**2)) / 225 + y = 16 * (1 + 80 * I4 / (I2**2) - 600 * I6 / (I2**3)) / 3375 + z = ( + -64 + * ( + -10800000 * I10 / (I2**5) + - 9 + - 700 * I4 / (I2**2) + + 3600 * I6 / (I2**3) + + 12400 * I4**2 / (I2**4) + - 48000 * I4 * I6 / (I2**5) + ) + / 253125 + ) + + L = Matrix( + [ + [x + 6 * y, 6 * x**2 + 2 * y, 2 * z], + [6 * x**2 + 2 * y, 2 * z, 9 * x**3 + 4 * x * y + 6 * y**2], + [ + 2 * z, + 9 * x**3 + 4 * x * y + 6 * y**2, + 6 * x**2 * y + 2 * y**2 + 3 * x * z, + ], + ] + ) + + try: + L = L * L.denominator() # clears the denominator + except (AttributeError, TypeError): + pass + + u, v, w = PolynomialRing(k, names).gens() + MConic = Conic(k, L, names) + if xyz: + return MConic, x, y, z + return MConic diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py new file mode 100644 index 00000000000..cfffcf048eb --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer.py @@ -0,0 +1,3968 @@ +r""" +Computation of Frobenius matrix on Monsky-Washnitzer cohomology + +The most interesting functions to be exported here are +:func:`matrix_of_frobenius` and :func:`adjusted_prec`. + +Currently this code is limited to the case `p \geq 5` (no +`GF(p^n)` for `n > 1`), and only handles the +elliptic curve case (not more general hyperelliptic curves). + +REFERENCES: + +- [Ked2001]_ + +- [Edix]_ + +AUTHORS: + +- David Harvey and Robert Bradshaw: initial code developed at the 2006 + MSRI graduate workshop, working with Jennifer Balakrishnan and Liang + Xiao + +- David Harvey (2006-08): cleaned up, rewrote some chunks, lots more + documentation, added Newton iteration method, added more complete + 'trace trick', integrated better into Sage. + +- David Harvey (2007-02): added algorithm with sqrt(p) complexity + (removed in May 2007 due to better C++ implementation) + +- Robert Bradshaw (2007-03): keep track of exact form in reduction + algorithms + +- Robert Bradshaw (2007-04): generalization to hyperelliptic curves + +- Julian Rueth (2014-05-09): improved caching +""" + +# **************************************************************************** +# Copyright (C) 2006 William Stein +# 2006 Robert Bradshaw +# 2006 David Harvey +# 2014 Julian Rueth +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.arith.misc import binomial +from sage.arith.misc import integer_ceil as ceil +from sage.categories.algebras import Algebras +from sage.categories.integral_domains import IntegralDomains +from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_import import lazy_import +from sage.misc.misc import newton_method_sizes +from sage.misc.profiler import Profiler +from sage.misc.repr import repr_lincomb +from sage.modules.free_module import FreeModule +from sage.modules.free_module_element import FreeModuleElement, vector +from sage.modules.module import Module +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing as Integers +from sage.rings.infinity import Infinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational import Rational +from sage.rings.rational_field import QQ +from sage.rings.rational_field import RationalField as Rationals +from sage.schemes.elliptic_curves.constructor import EllipticCurve +from sage.schemes.elliptic_curves.ell_generic import EllipticCurve_generic +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, +) +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic import ( + HyperellipticCurveSmoothModel_generic, +) +from sage.structure.element import ModuleElement +from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp +from sage.structure.unique_representation import UniqueRepresentation + +# TODO: why are these lazy imports used? +lazy_import("sage.functions.log", "log") +lazy_import("sage.rings.lazy_series_ring", "LazyLaurentSeriesRing") +lazy_import("sage.rings.padics.factory", "Qp", as_="pAdicField") + + +class SpecialCubicQuotientRingElement(ModuleElement): + """ + An element of a :class:`SpecialCubicQuotientRing`. + """ + + def __init__(self, parent, p0, p1, p2, check=True): + """ + Construct the element `p_0 + p_1*x + p_2*x^2`, where + the `p_i` are polynomials in `T`. + + INPUT: + + - ``parent`` -- a :class:`SpecialCubicQuotientRing` + + - ``p0``, ``p1``, ``p2`` -- coefficients; must be coercible + into parent.poly_ring() + + - ``check`` -- boolean (default: ``True``); whether to carry + out coercion + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing, SpecialCubicQuotientRingElement + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: SpecialCubicQuotientRingElement(R, 2, 3, 4) + (2) + (3)*x + (4)*x^2 + + TESTS:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: TestSuite(R).run() + sage: p = R.create_element(t, t^2 - 2, 3) + sage: -p + (124*T) + (124*T^2 + 2)*x + (122)*x^2 + """ + if not isinstance(parent, SpecialCubicQuotientRing): + raise TypeError(f"parent (={parent}) must be a SpecialCubicQuotientRing") + + ModuleElement.__init__(self, parent) + + if check: + poly_ring = parent._poly_ring + p0 = poly_ring(p0) + p1 = poly_ring(p1) + p2 = poly_ring(p2) + + self._triple = (p0, p1, p2) + + def coeffs(self): + """ + Return list of three lists of coefficients, corresponding to the + `x^0`, `x^1`, `x^2` coefficients. + + The lists are zero padded to the same length. The list entries + belong to the base ring. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: p = R.create_element(t, t^2 - 2, 3) + sage: p.coeffs() + [[0, 1, 0], [123, 0, 1], [3, 0, 0]] + """ + coeffs = [column.coefficients(sparse=False) for column in self._triple] + degree = max([len(x) for x in coeffs]) + base_ring = self.parent().base_ring() + for column in coeffs: + column.extend([base_ring(0)] * (degree - len(column))) + return coeffs + + def __bool__(self) -> bool: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: not x + False + sage: not T + False + sage: not R.create_element(0, 0, 0) + True + """ + return bool(self._triple[0]) or bool(self._triple[1]) or bool(self._triple[2]) + + def _richcmp_(self, other, op) -> bool: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: x, t = monsky_washnitzer.SpecialCubicQuotientRing(t^3 - t + B(1/4)).gens() + sage: x == t + False + sage: x == x + True + sage: x == x + x - x + True + """ + return richcmp(self._triple, other._triple, op) + + def _repr_(self) -> str: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: x + T*x - 2*T^2 + (123*T^2) + (T + 1)*x + (0)*x^2 + """ + return "({}) + ({})*x + ({})*x^2".format(*self._triple) + + def _latex_(self) -> str: + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: f = x + T*x - 2*T^2 + sage: latex(f) + (123 T^{2}) + (T + 1)x + (0)x^2 + """ + return "(%s) + (%s)x + (%s)x^2" % tuple( + column._latex_() for column in self._triple + ) + + def _add_(self, other): + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: f = R.create_element(2, t, t^2 - 3) + sage: g = R.create_element(3 + t, -t, t) + sage: f + g + (T + 5) + (0)*x + (T^2 + T + 122)*x^2 + """ + P = self.parent() + return P.element_class( + P, + self._triple[0] + other._triple[0], + self._triple[1] + other._triple[1], + self._triple[2] + other._triple[2], + check=False, + ) + + def _sub_(self, other): + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: f = R.create_element(2, t, t^2 - 3) + sage: g = R.create_element(3 + t, -t, t) + sage: f - g + (124*T + 124) + (2*T)*x + (T^2 + 124*T + 122)*x^2 + """ + P = self.parent() + return P.element_class( + P, + self._triple[0] - other._triple[0], + self._triple[1] - other._triple[1], + self._triple[2] - other._triple[2], + check=False, + ) + + def shift(self, n): + """ + Return this element multiplied by `T^n`. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: f = R.create_element(2, t, t^2 - 3) + sage: f + (2) + (T)*x + (T^2 + 122)*x^2 + sage: f.shift(1) + (2*T) + (T^2)*x + (T^3 + 122*T)*x^2 + sage: f.shift(2) + (2*T^2) + (T^3)*x + (T^4 + 122*T^2)*x^2 + """ + P = self.parent() + return P.element_class( + P, + self._triple[0].shift(n), + self._triple[1].shift(n), + self._triple[2].shift(n), + check=False, + ) + + def scalar_multiply(self, scalar): + """ + Multiply this element by a scalar, i.e. just multiply each + coefficient of `x^j` by the scalar. + + INPUT: + + - ``scalar`` -- either an element of ``base_ring``, or an + element of ``poly_ring`` + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: f = R.create_element(2, t, t^2 - 3) + sage: f + (2) + (T)*x + (T^2 + 122)*x^2 + sage: f.scalar_multiply(2) + (4) + (2*T)*x + (2*T^2 + 119)*x^2 + sage: f.scalar_multiply(t) + (2*T) + (T^2)*x + (T^3 + 122*T)*x^2 + """ + P = self.parent() + scalar = P._poly_ring(scalar) + return P.element_class( + P, + scalar * self._triple[0], + scalar * self._triple[1], + scalar * self._triple[2], + check=False, + ) + + def square(self): + """ + Return the square of the element. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + + :: + + sage: f = R.create_element(1 + 2*t + 3*t^2, 4 + 7*t + 9*t^2, 3 + 5*t + 11*t^2) + sage: f.square() + (73*T^5 + 16*T^4 + 38*T^3 + 39*T^2 + 70*T + 120) + + (121*T^5 + 113*T^4 + 73*T^3 + 8*T^2 + 51*T + 61)*x + + (18*T^4 + 60*T^3 + 22*T^2 + 108*T + 31)*x^2 + """ + return self * self + + def _mul_(self, other): + """ + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + + :: + + sage: f = R.create_element(1 + 2*t + 3*t^2, 4 + 7*t + 9*t^2, 3 + 5*t + 11*t^2) + sage: g = R.create_element(4 + 3*t + 7*t^2, 2 + 3*t + t^2, 8 + 4*t + 6*t^2) + sage: f * g + (65*T^5 + 27*T^4 + 33*T^3 + 75*T^2 + 120*T + 57) + + (66*T^5 + T^4 + 123*T^3 + 95*T^2 + 24*T + 50)*x + + (45*T^4 + 75*T^3 + 37*T^2 + 2*T + 52)*x^2 + """ + # Here we do Toom-Cook three-way multiplication, which reduces + # the naive 9 polynomial multiplications to only 5 polynomial + # multiplications. + + a0, a1, a2 = self._triple + b0, b1, b2 = other._triple + M = self.parent()._speedup_matrix + + if self is other: + # faster method if we are squaring + p0 = a0 * a0 + temp = a0 + 2 * a1 + 4 * a2 + p1 = temp * temp + temp = a0 + a1 + a2 + p2 = temp * temp + temp = 4 * a0 + 2 * a1 + a2 + p3 = temp * temp + p4 = a2 * a2 + + else: + p0 = a0 * b0 + p1 = (a0 + 2 * a1 + 4 * a2) * (b0 + 2 * b1 + 4 * b2) + p2 = (a0 + a1 + a2) * (b0 + b1 + b2) + p3 = (4 * a0 + 2 * a1 + a2) * (4 * b0 + 2 * b1 + b2) + p4 = a2 * b2 + + q1 = p1 - p0 - 16 * p4 + q2 = p2 - p0 - p4 + q3 = p3 - 16 * p0 - p4 + + c0 = p0 + c1 = M[0] * q1 + M[1] * q2 + M[2] * q3 + c2 = M[3] * q1 + M[4] * q2 + M[5] * q3 + c3 = M[6] * q1 + M[7] * q2 + M[8] * q3 + c4 = p4 + + # Now the product is c0 + c1 x + c2 x^2 + c3 x^3 + c4 x^4. + # We need to reduce mod y = x^3 + ax + b and return result. + + parent = self.parent() + T = parent._poly_generator + b = parent._b + a = parent._a + + # todo: These lines are necessary to get binop stuff working + # for certain base rings, e.g. when we compute b*c3 in the + # final line. They shouldn't be necessary. Need to fix this + # somewhere else in Sage. + a = parent._poly_ring(a) + b = parent._poly_ring(b) + + return parent.element_class( + parent, + -b * c3 + c0 + c3 * T, + -b * c4 - a * c3 + c1 + c4 * T, + -a * c4 + c2, + check=False, + ) + + +class SpecialCubicQuotientRing(UniqueRepresentation, Parent): + r""" + Specialised class for representing the quotient ring + `R[x,T]/(T - x^3 - ax - b)`, where `R` is an + arbitrary commutative base ring (in which 2 and 3 are invertible), + `a` and `b` are elements of that ring. + + Polynomials are represented internally in the form + `p_0 + p_1 x + p_2 x^2` where the `p_i` are + polynomials in `T`. Multiplication of polynomials always + reduces high powers of `x` (i.e. beyond `x^2`) to + powers of `T`. + + Hopefully this ring is faster than a general quotient ring because + it uses the special structure of this ring to speed multiplication + (which is the dominant operation in the frobenius matrix + calculation). I haven't actually tested this theory though... + + .. TODO:: + + Eventually we will want to run this in characteristic 3, so we + need to: (a) Allow `Q(x)` to contain an `x^2` term, and (b) Remove + the requirement that 3 be invertible. Currently this is used in + the Toom-Cook algorithm to speed multiplication. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R + SpecialCubicQuotientRing over Ring of integers modulo 125 + with polynomial T = x^3 + 124*x + 94 + sage: TestSuite(R).run() + + Get generators:: + + sage: x, T = R.gens() + sage: x + (0) + (1)*x + (0)*x^2 + sage: T + (T) + (0)*x + (0)*x^2 + + Coercions:: + + sage: R(7) + (7) + (0)*x + (0)*x^2 + + Create elements directly from polynomials:: + + sage: A = R.poly_ring() + sage: A + Univariate Polynomial Ring in T over Ring of integers modulo 125 + sage: z = A.gen() + sage: R.create_element(z^2, z+1, 3) + (T^2) + (T + 1)*x + (3)*x^2 + + Some arithmetic:: + + sage: x^3 + (T + 31) + (1)*x + (0)*x^2 + sage: 3 * x**15 * T**2 + x - T + (3*T^7 + 90*T^6 + 110*T^5 + 20*T^4 + 58*T^3 + 26*T^2 + 124*T) + + (15*T^6 + 110*T^5 + 35*T^4 + 63*T^2 + 1)*x + + (30*T^5 + 40*T^4 + 8*T^3 + 38*T^2)*x^2 + + Retrieve coefficients (output is zero-padded):: + + sage: x^10 + (3*T^2 + 61*T + 8) + (T^3 + 93*T^2 + 12*T + 40)*x + (3*T^2 + 61*T + 9)*x^2 + sage: (x^10).coeffs() + [[8, 61, 3, 0], [40, 12, 93, 1], [9, 61, 3, 0]] + + .. TODO:: + + write an example checking multiplication of these polynomials + against Sage's ordinary quotient ring arithmetic. I cannot seem + to get the quotient ring stuff happening right now... + """ + + def __init__(self, Q, laurent_series=False): + """ + Constructor. + + INPUT: + + - ``Q`` -- a polynomial of the form + `Q(x) = x^3 + ax + b`, where `a`, `b` belong to a ring in which + 2, 3 are invertible. + + - ``laurent_series`` -- boolean (default: ``False``); whether or not to allow + negative powers of `T` + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R + SpecialCubicQuotientRing over Ring of integers modulo 125 + with polynomial T = x^3 + 124*x + 94 + + :: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 + 2*t^2 - t + B(1/4)) + Traceback (most recent call last): + ... + ValueError: Q (=t^3 + 2*t^2 + 124*t + 94) must be of the form x^3 + ax + b + + :: + + sage: B. = PolynomialRing(Integers(10)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + 1) + Traceback (most recent call last): + ... + ArithmeticError: 2 and 3 must be invertible in the coefficient ring + (=Ring of integers modulo 10) of Q + """ + if not isinstance(Q, Polynomial): + raise TypeError("Q (=%s) must be a polynomial" % Q) + + if Q.degree() != 3 or not Q[2].is_zero(): + raise ValueError("Q (=%s) must be of the form x^3 + ax + b" % Q) + + base_ring = Q.parent().base_ring() + + if not base_ring(6).is_unit(): + raise ArithmeticError( + "2 and 3 must be invertible in the " + "coefficient ring (=%s) of Q" % base_ring + ) + + self._a = Q[1] + self._b = Q[0] + if laurent_series: + self._poly_ring = LaurentSeriesRing(base_ring, "T") # R[T] + else: + self._poly_ring = PolynomialRing(base_ring, "T") # R[T] + self._poly_generator = self._poly_ring.gen(0) # the generator T + Parent.__init__( + self, base=base_ring, category=Algebras(base_ring).Commutative() + ) + + # Precompute a matrix that is used in the Toom-Cook multiplication. + # This is where we need 2 and 3 invertible. + + # a good description of Toom-Cook is online at: + # https://gmplib.org/manual/Multiplication-Algorithms + m = matrix(QQ, [[1, -12, 2], [-3, 30, -3], [2, -12, 1]]) / 6 + self._speedup_matrix = m.change_ring(base_ring).list() + + def _repr_(self) -> str: + """ + String representation. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R + SpecialCubicQuotientRing over Ring of integers modulo 125 + with polynomial T = x^3 + 124*x + 94 + """ + return "SpecialCubicQuotientRing over %s with polynomial T = %s" % ( + self.base_ring(), + PolynomialRing(self.base_ring(), "x")([self._b, self._a, 0, 1]), + ) + + def poly_ring(self): + """ + Return the underlying polynomial ring in `T`. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R.poly_ring() + Univariate Polynomial Ring in T over Ring of integers modulo 125 + """ + return self._poly_ring + + def gens(self): + """ + Return a list [x, T] where x and T are the generators of the ring + (as element *of this ring*). + + .. NOTE:: + + I have no idea if this is compatible with the usual Sage + 'gens' interface. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: x, T = R.gens() + sage: x + (0) + (1)*x + (0)*x^2 + sage: T + (T) + (0)*x + (0)*x^2 + """ + zero = self._poly_ring.zero() + one = self._poly_ring.one() + return ( + self.element_class(self, zero, one, zero, check=False), + self.element_class(self, self._poly_generator, zero, zero, check=False), + ) + + def _element_constructor_(self, *args, check=True): + """ + Create the element `p_0 + p_1*x + p_2*x^2`, where the `p_i` + are polynomials in `T`. + + INPUT: + + - ``p0``, ``p1``, ``p2`` -- coefficients; must be coercible + into poly_ring() + + - ``check`` -- boolean (default: ``True``); whether to carry + out coercion + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: A, z = R.poly_ring().objgen() + sage: R.create_element(z^2, z+1, 3) # indirect doctest + (T^2) + (T + 1)*x + (3)*x^2 + """ + if len(args) == 1 and args[0] in self.base_ring(): + p0 = self._poly_ring.coerce(args[0]) + p1 = p2 = self._poly_ring.zero() + else: + p0, p1, p2 = args + return self.element_class(self, p0, p1, p2, check=check) + + create_element = _element_constructor_ + + @cached_method + def one(self): + """ + Return the unit of ``self``. + + EXAMPLES:: + + sage: B. = PolynomialRing(Integers(125)) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R.one() + (1) + (0)*x + (0)*x^2 + """ + R = self._poly_ring + return self.element_class(self, R.one(), R.zero(), R.zero(), check=False) + + def _coerce_map_from_(self, R): + """ + Coercion system. + + EXAMPLES:: + + sage: Z125 = Integers(125) + sage: B. = PolynomialRing(Z125) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialCubicQuotientRing + sage: R = SpecialCubicQuotientRing(t^3 - t + B(1/4)) + sage: R.has_coerce_map_from(Z125) + True + """ + return self._poly_ring.has_coerce_map_from(R) + + Element = SpecialCubicQuotientRingElement + + +def transpose_list(input) -> list[list]: + """ + INPUT: + + - ``input`` -- list of lists, each list of the same length + + OUTPUT: list of lists such that ``output[i][j] = input[j][i]`` + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import transpose_list + sage: L = [[1, 2], [3, 4], [5, 6]] + sage: transpose_list(L) + [[1, 3, 5], [2, 4, 6]] + """ + h = len(input) + w = len(input[0]) + + output = [] + for i in range(w): + row = [] + for j in range(h): + row.append(input[j][i]) + output.append(row) + return output + + +def helper_matrix(Q): + r""" + Compute the (constant) matrix used to calculate the linear + combinations of the `d(x^i y^j)` needed to eliminate the + negative powers of `y` in the cohomology (i.e., in + :func:`reduce_negative`). + + INPUT: + + - ``Q`` -- cubic polynomial + + EXAMPLES:: + + sage: t = polygen(QQ,'t') + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import helper_matrix + sage: helper_matrix(t**3-4*t-691) + [ 64/12891731 -16584/12891731 4297329/12891731] + [ 6219/12891731 -32/12891731 8292/12891731] + [ -24/12891731 6219/12891731 -32/12891731] + """ + a = Q[1] + b = Q[0] + + # Discriminant (should be invertible for a curve of good reduction) + D = 4 * a**3 + 27 * b**2 + Dinv = D ** (-1) # NB do not use 1/D + + # This is the inverse of the matrix + # [ a, -3b, 0 ] + # [ 0, -2a, -3b ] + # [ 3, 0, -2a ] + + return Dinv * matrix( + [ + [4 * a**2, -6 * b * a, 9 * b**2], + [-9 * b, -2 * a**2, 3 * b * a], + [6 * a, -9 * b, -2 * a**2], + ] + ) + + +def lift(x): + r""" + Try to call ``x.lift()``, presumably from the `p`-adics to `\ZZ`. + + If this fails, it assumes the input is a power series, and tries to + lift it to a power series over `\QQ`. + + This function is just a very kludgy solution to the problem of + trying to make the reduction code (below) work over both `\ZZ_p` and + `\ZZ_p[[t]]`. + + EXAMPLES:: + + sage: # needs sage.rings.padics + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import lift + sage: l = lift(Qp(13)(131)); l + 131 + sage: l.parent() + Integer Ring + sage: x = PowerSeriesRing(Qp(17),'x').gen() + sage: l = lift(4 + 5*x + 17*x**6); l + 4 + 5*t + 17*t^6 + sage: l.parent() + Power Series Ring in t over Rational Field + """ + try: + return x.lift() + except AttributeError: + return PowerSeriesRing(Rationals(), "t")(x.list(), x.prec()) + + +def reduce_negative(Q, p, coeffs, offset, exact_form=None): + """ + Apply cohomology relations to incorporate negative powers of + `y` into the `y^0` term. + + INPUT: + + - ``p`` -- prime + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list ``[a, b, c]`` represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + The reduction is performed in-place. The output is placed + in coeffs[offset]. Note that coeffs[i] will be meaningless for i + offset after this function is finished. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[10, 15, 20], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_negative(Q, 5, coeffs, 3) + sage: coeffs[3] + [28, 52, 9] + + :: + + sage: R. = Integers(7^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[7, 14, 21], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_negative(Q, 7, coeffs, 3) + sage: coeffs[3] + [245, 332, 9] + """ + + m = helper_matrix(Q).list() + base_ring = Q.base_ring() + next_a = coeffs[0] + + if exact_form is not None: + x = exact_form.parent().gen(0) + y = exact_form.parent()(exact_form.parent().base_ring().gen(0)) + + try: + three_j_plus_5 = 5 - base_ring(6 * offset) + three_j_plus_7 = 7 - base_ring(6 * offset) + six = base_ring(6) + + for i in range(offset): + j = 2 * (i - offset) + a = next_a + next_a = coeffs[i + 1] + + # todo: the following divisions will sometimes involve + # a division by (a power of) p. In all cases, we know (from + # Kedlaya's estimates) that the answer should be p-integral. + # However, since we're working over $Z/p^k Z$, we're not allowed + # to "divide by p". So currently we lift to Q, divide, and coerce + # back. Eventually, when pAdicInteger is implemented, and plays + # nicely with pAdicField, we should reimplement this stuff + # using pAdicInteger. + + if p.divides(j + 1): + # need to lift here to perform the division + a[0] = base_ring(lift(a[0]) / (j + 1)) + a[1] = base_ring(lift(a[1]) / (j + 1)) + a[2] = base_ring(lift(a[2]) / (j + 1)) + else: + j_plus_1_inv = ~base_ring(j + 1) + a[0] = a[0] * j_plus_1_inv + a[1] = a[1] * j_plus_1_inv + a[2] = a[2] * j_plus_1_inv + + c1 = m[3] * a[0] + m[4] * a[1] + m[5] * a[2] + c2 = m[6] * a[0] + m[7] * a[1] + m[8] * a[2] + next_a[0] = next_a[0] - three_j_plus_5 * c1 + next_a[1] = next_a[1] - three_j_plus_7 * c2 + + three_j_plus_7 = three_j_plus_7 + six + three_j_plus_5 = three_j_plus_5 + six + + if exact_form is not None: + c0 = m[0] * a[0] + m[1] * a[1] + m[2] * a[2] + exact_form += (c0 + c1 * x + c2 * x**2) * y ** (j + 1) + + except NotImplementedError: + raise NotImplementedError( + "It looks like you've found a " + "non-integral matrix of Frobenius! " + f"(Q={Q}, p={p})\nTime to write a paper." + ) + + coeffs[int(offset)] = next_a + + return exact_form + + +def reduce_positive(Q, p, coeffs, offset, exact_form=None): + """ + Apply cohomology relations to incorporate positive powers of + `y` into the `y^0` term. + + INPUT: + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list [a, b, c] represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + The reduction is performed in-place. The output is placed + in coeffs[offset]. Note that coeffs[i] will be meaningless for i + offset after this function is finished. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + + :: + + sage: coeffs = [[1, 2, 3], [10, 15, 20]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_positive(Q, 5, coeffs, 0) + sage: coeffs[0] + [16, 102, 88] + + :: + + sage: coeffs = [[9, 8, 7], [10, 15, 20]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_positive(Q, 5, coeffs, 0) + sage: coeffs[0] + [24, 108, 92] + """ + + base_ring = Q.base_ring() + next_a = coeffs[len(coeffs) - 1] + + Qa = Q[1] + Qb = Q[0] + + A = 2 * Qa + B = 3 * Qb + + offset = Integer(offset) + + if exact_form is not None: + x = exact_form.parent().gen(0) + y = exact_form.parent().base_ring().gen(0) + # y = exact_form.parent()(exact_form.parent().base_ring().gen(0)) + + for i in range(len(coeffs) - 1, offset, -1): + j = 2 * (i - offset) - 2 + a = next_a + next_a = coeffs[i - 1] + + a[0] = a[0] - Qa * a[2] / 3 # subtract d(y^j + 3) + if exact_form is not None: + exact_form += Q.base_ring()(a[2].lift() / (3 * j + 9)) * y ** (j + 3) + + # todo: see comments about pAdicInteger in reduceNegative() + + # subtract off c1 of d(x y^j + 1), and + if p.divides(3 * j + 5): + c1 = base_ring(lift(a[0]) / (3 * j + 5)) + else: + c1 = a[0] / (3 * j + 5) + + # subtract off c2 of d(x^2 y^j + 1) + if p.divides(3 * j + 7): + c2 = base_ring(lift(a[1]) / (3 * j + 7)) + else: + c2 = a[1] / (3 * j + 7) + + next_a[0] = next_a[0] + B * c1 * (j + 1) + next_a[1] = next_a[1] + A * c1 * (j + 1) + B * c2 * (j + 1) + next_a[2] = next_a[2] + A * c2 * (j + 1) + + if exact_form is not None: + exact_form += (c1 * x + c2 * x**2) * y ** (j + 1) + + coeffs[int(offset)] = next_a + + return exact_form + + +def reduce_zero(Q, coeffs, offset, exact_form=None): + """ + Apply cohomology relation to incorporate `x^2 y^0` term + into `x^0 y^0` and `x^1 y^0` terms. + + INPUT: + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list [a, b, c] represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + The reduction is performed in-place. The output is placed + in coeffs[offset]. This method completely ignores coeffs[i] for i + != offset. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_zero(Q, coeffs, 1) + sage: coeffs[1] + [6, 5, 0] + """ + + a = coeffs[int(offset)] + if a[2] == 0: + return exact_form + + Qa = Q[1] + + a[0] = a[0] - a[2] * Qa / 3 # $3x^2 dx/y = -a dx/y$ + + coeffs[int(offset)] = a + + if exact_form is not None: + y = exact_form.parent()(exact_form.parent().base_ring().gen(0)) + exact_form += Q.base_ring()(a[2] / 3) * y + + a[2] = 0 + + coeffs[int(offset)] = a + return exact_form + + +def reduce_all(Q, p, coeffs, offset, compute_exact_form=False): + """ + Apply cohomology relations to reduce all terms to a linear + combination of `dx/y` and `x dx/y`. + + INPUT: + + - ``Q`` -- cubic polynomial + + - ``coeffs`` -- list of length 3 lists. The + `i`-th list [a, b, c] represents + `y^{2(i - offset)} (a + bx + cx^2) dx/y`. + + - ``offset`` -- nonnegative integer + + OUTPUT: + + - ``A``, ``B`` -- pair such that the input differential is + cohomologous to (A + Bx) dx/y + + .. NOTE:: + + The algorithm operates in-place, so the data in coeffs is + destroyed. + + EXAMPLES:: + + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: coeffs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: coeffs = [[R.base_ring()(a) for a in row] for row in coeffs] + sage: monsky_washnitzer.reduce_all(Q, 5, coeffs, 1) + (21, 106) + """ + + R = Q.base_ring() + + if compute_exact_form: + # exact_form = SpecialCubicQuotientRing(Q, laurent_series=True)(0) + exact_form = PolynomialRing(LaurentSeriesRing(Q.base_ring(), "y"), "x").zero() + # t = (Q.base_ring().order().factor())[0] + # from sage.rings.padics.qp import pAdicField + # exact_form = PolynomialRing(LaurentSeriesRing(pAdicField(p, t[1]), 'y'), 'x')(0) + else: + exact_form = None + + while len(coeffs) <= offset: + coeffs.append([R(0), R(0), R(0)]) + + exact_form = reduce_negative(Q, p, coeffs, offset, exact_form) + exact_form = reduce_positive(Q, p, coeffs, offset, exact_form) + exact_form = reduce_zero(Q, coeffs, offset, exact_form) + + if exact_form is None: + return coeffs[int(offset)][0], coeffs[int(offset)][1] + else: + return (coeffs[int(offset)][0], coeffs[int(offset)][1]), exact_form + + +def frobenius_expansion_by_newton(Q, p, M): + r""" + Compute the action of Frobenius on `dx/y` and on + `x dx/y`, using Newton's method (as suggested in Kedlaya's + paper [Ked2001]_). + + (This function does *not* yet use the cohomology relations - that + happens afterwards in the "reduction" step.) + + More specifically, it finds `F_0` and `F_1` in + the quotient ring `R[x, T]/(T - Q(x))`, such that + + .. MATH:: + + F( dx/y) = T^{-r} F0 dx/y, \text{\ and\ } F(x dx/y) = T^{-r} F1 dx/y + + where + + .. MATH:: + + r = ( (2M-3)p - 1 )/2. + + + (Here `T` is `y^2 = z^{-2}`, and `R` is the + coefficient ring of `Q`.) + + `F_0` and `F_1` are computed in the + :class:`SpecialCubicQuotientRing` associated to `Q`, so all powers + of `x^j` for `j \geq 3` are reduced to powers of + `T`. + + INPUT: + + - ``Q`` -- cubic polynomial of the form + `Q(x) = x^3 + ax + b`, whose coefficient ring is a + `Z/(p^M)Z`-algebra + + - ``p`` -- residue characteristic of the `p`-adic field + + - ``M`` -- `p`-adic precision of the coefficient ring + (this will be used to determine the number of Newton iterations) + + OUTPUT: + + - ``F0``, ``F1`` -- elements of + ``SpecialCubicQuotientRing(Q)``, as described above + + - ``r`` -- nonnegative integer, as described above + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import frobenius_expansion_by_newton + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: frobenius_expansion_by_newton(Q,5,3) + ((25*T^5 + 75*T^3 + 100*T^2 + 100*T + 100) + (5*T^6 + 80*T^5 + 100*T^3 + + 25*T + 50)*x + (55*T^5 + 50*T^4 + 75*T^3 + 25*T^2 + 25*T + 25)*x^2, + (5*T^8 + 15*T^7 + 95*T^6 + 10*T^5 + 25*T^4 + 25*T^3 + 100*T^2 + 50) + + (65*T^7 + 55*T^6 + 70*T^5 + 100*T^4 + 25*T^2 + 100*T)*x + + (15*T^6 + 115*T^5 + 75*T^4 + 100*T^3 + 50*T^2 + 75*T + 75)*x^2, 7) + """ + + S = SpecialCubicQuotientRing(Q) + x, _ = S.gens() # T = y^2 + base_ring = S.base_ring() + + # When we compute Frob(1/y) we actually only need precision M-1, since + # we're going to multiply by p at the end anyway. + M = float(M - 1) + + # Kedlaya sets s = Q(x^p)/T^p = 1 + p T^{-p} E, where + # E = (Q(x^p) - Q(x)^p) / p (has integral coefficients). + # Then he computes s^{-1/2} in S, using Newton's method to find + # successive approximations. We follow this plan, but we normalise our + # approximations so that we only ever need positive powers of T. + + # Start by setting r = Q(x^p)/2 = 1/2 T^p s. + # (The 1/2 is for convenience later on.) + x_to_p_less_one = x ** (p - 1) + x_to_p = x_to_p_less_one * x + x_to_p_cubed = x_to_p.square() * x_to_p + r = (base_ring(1) / base_ring(2)) * (x_to_p_cubed + Q[1] * x_to_p + S(Q[0])) + + # todo: this next loop would be clearer if it used the newton_method_sizes() + # function + + # We will start with a hard-coded initial approximation, which we provide + # up to precision 3. First work out what precision is best to start with. + if M <= 3: + initial_precision = M + elif ceil(log(M / 2, 2)) == ceil(log(M / 3, 2)): + # In this case there is no advantage to starting with precision three, + # because we'll overshoot at the end. E.g. suppose the final precision + # is 8. If we start with precision 2, we need two iterations to get us + # to 8. If we start at precision 3, we will still need two iterations, + # but we do more work along the way. So may as well start with only 2. + initial_precision = 2 + else: + initial_precision = 3 + + # Now compute the first approximation. In the main loop below, X is the + # normalised approximation, and k is the precision. More specifically, + # X = T^{p(k-1)} x_i, where x_i is an approximation to s^{-1/2}, and the + # approximation is correct mod p^k. + if initial_precision == 1: + k = 1 + X = S(1) + elif initial_precision == 2: + # approximation is 3/2 - 1/2 s + k = 2 + X = S(base_ring(3) / base_ring(2)).shift(p) - r + elif initial_precision == 3: + # approximation is (15 - 10 s + 3 s^2) / 8 + k = 3 + X = (base_ring(1) / base_ring(8)) * ( + S(15).shift(2 * p) + - (base_ring(20) * r).shift(p) + + (base_ring(12) * r.square()) + ) + # The key to the following calculation is that the T^{-m} coefficient + # of every x_i is divisible by p^(ceil(m/p)) (for m >= 0). Therefore if + # we are only expecting an answer correct mod p^k, we can truncate + # beyond the T^{-(k-1)p} term without any problems. + + # todo: what would be really nice is to be able to work in a lower + # precision *coefficient ring* when we start the iteration, and move up to + # higher precision rings as the iteration proceeds. This would be feasible + # over Integers(p**n), but quite complicated (maybe impossible) over a more + # general base ring. This might give a decent constant factor speedup; + # or it might not, depending on how much the last iteration dominates the + # whole runtime. My guess is that it isn't worth the effort. + + three_halves = base_ring(3) / base_ring(2) + + # Newton iteration loop + while k < M: + # target_k = k' = precision we want our answer to be after this iteration + target_k = 2 * k + + # This prevents us overshooting. For example if the current precision + # is 3 and we want to get to 10, we're better off going up to 5 + # instead of 6, because it is less work to get from 5 to 10 than it + # is to get from 6 to 10. + if ceil(log(M / target_k, 2)) == ceil(log(M / (target_k - 1), 2)): + target_k -= 1 + + # temp = T^{p(3k-2)} 1/2 s x_i^3 + temp = X.square() * (X * r) + + # We know that the final result is only going to be correct mod + # p^(target_k), so we might as well truncate the extraneous terms now. + # temp = T^{p(k'-1)} 1/2 s x_i^3 + temp = temp.shift(-p * (3 * k - target_k - 1)) + + # X = T^{p(k'-1)} (3/2 x_i - 1/2 s x_i^3) + # = T^{p(k'-1)} x_{i+1} + X = (three_halves * X).shift(p * (target_k - k)) - temp + + k = target_k + + # Now k should equal M, since we're up to the correct precision + assert k == M, "Oops, something went wrong in the iteration" + + # We should have s^{-1/2} correct to precision M. + # The following line can be uncommented to verify this. + # (It is a slow verification though, can double the whole computation time.) + + # assert (p * X.square() * r * base_ring(2)).coeffs() == \ + # R(p).shift(p*(2*M - 1)).coeffs() + + # Finally incorporate frobenius of dx and x dx, and choose offset that + # compensates for our normalisations by powers of T. + F0 = base_ring(p) * x_to_p_less_one * X + F1 = F0 * x_to_p + offset = ((2 * k - 1) * p - 1) / 2 + + return F0, F1, offset + + +def frobenius_expansion_by_series(Q, p, M): + r""" + Compute the action of Frobenius on `dx/y` and on `x dx/y`, using a + series expansion. + + (This function computes the same thing as + frobenius_expansion_by_newton(), using a different method. + Theoretically the Newton method should be asymptotically faster, + when the precision gets large. However, in practice, this functions + seems to be marginally faster for moderate precision, so I'm + keeping it here until I figure out exactly why it is faster.) + + (This function does *not* yet use the cohomology relations - that + happens afterwards in the "reduction" step.) + + More specifically, it finds F0 and F1 in the quotient ring + `R[x, T]/(T - Q(x))`, such that + `F( dx/y) = T^{-r} F0 dx/y`, and + `F(x dx/y) = T^{-r} F1 dx/y` where + `r = ( (2M-3)p - 1 )/2`. (Here `T` is `y^2 = z^{-2}`, + and `R` is the coefficient ring of `Q`.) + + `F_0` and `F_1` are computed in the + :class:`SpecialCubicQuotientRing` associated to `Q`, so all powers + of `x^j` for `j \geq 3` are reduced to powers of + `T`. + + It uses the sum + + .. MATH:: + + F0 = \sum_{k=0}^{M-2} \binom{-1/2}{k} p x^{p-1} E^k T^{(M-2-k)p} + + and + + .. MATH:: + + F1 = x^p F0, + + where `E = Q(x^p) - Q(x)^p`. + + INPUT: + + - ``Q`` -- cubic polynomial of the form + `Q(x) = x^3 + ax + b`, whose coefficient ring is a + `\ZZ/(p^M)\ZZ` -algebra + + - ``p`` -- residue characteristic of the `p`-adic field + + - ``M`` -- `p`-adic precision of the coefficient ring + (this will be used to determine the number of terms in the + series) + + OUTPUT: + + - ``F0``, ``F1`` -- elements of + ``SpecialCubicQuotientRing(Q)``, as described above + + - ``r`` -- nonnegative integer, as described above + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import frobenius_expansion_by_series + sage: R. = Integers(5^3)['x'] + sage: Q = x^3 - x + R(1/4) + sage: frobenius_expansion_by_series(Q,5,3) # needs sage.libs.pari + ((25*T^5 + 75*T^3 + 100*T^2 + 100*T + 100) + (5*T^6 + 80*T^5 + 100*T^3 + + 25*T + 50)*x + (55*T^5 + 50*T^4 + 75*T^3 + 25*T^2 + 25*T + 25)*x^2, + (5*T^8 + 15*T^7 + 95*T^6 + 10*T^5 + 25*T^4 + 25*T^3 + 100*T^2 + 50) + + (65*T^7 + 55*T^6 + 70*T^5 + 100*T^4 + 25*T^2 + 100*T)*x + + (15*T^6 + 115*T^5 + 75*T^4 + 100*T^3 + 50*T^2 + 75*T + 75)*x^2, 7) + """ + + S = SpecialCubicQuotientRing(Q) + x, _ = S.gens() + base_ring = S.base_ring() + + x_to_p_less_1 = x ** (p - 1) + x_to_p = x_to_p_less_1 * x + + # compute frobQ = Q(x^p) + x_to_p_squared = x_to_p * x_to_p + x_to_p_cubed = x_to_p_squared * x_to_p + frobQ = x_to_p_cubed + Q[1] * x_to_p + Q[0] * S.one() + # anticipating the day when p = 3 is supported: + # frobQ = x_to_p_cubed + Q[2]*x_to_p_squared + Q[1]*x_to_p + Q[0]*S(1) + + E = frobQ - S.one().shift(p) # E = Q(x^p) - Q(x)^p + + offset = int(((2 * M - 3) * p - 1) / 2) + term = p * x_to_p_less_1 + F0 = term.shift((M - 2) * p) + + # todo: Possible speedup idea, perhaps by a factor of 2, but + # it requires a lot of work: + # Note that p divides E, so p^k divides E^k. So when we are + # working with high powers of E, we're doing a lot more work + # in the multiplications than we need to. To take advantage of + # this we would need some protocol for "lowering the precision" + # of a SpecialCubicQuotientRing. This would be quite messy to + # do properly over an arbitrary base ring. Perhaps it is + # feasible to do for the most common case (i.e. Z/p^nZ). + # (but it probably won't save much time unless p^n is very + # large, because the machine word size is probably pretty + # big anyway.) + + for k in range(1, int(M - 1)): + term = term * E + c = base_ring(binomial(QQ((-1, 2)), k)) + F0 += (term * c).shift((M - k - 2) * p) + + return F0, F0 * x_to_p, offset + + +def adjusted_prec(p, prec): + r""" + Compute how much precision is required in ``matrix_of_frobenius`` to + get an answer correct to ``prec`` `p`-adic digits. + + The issue is that the algorithm used in + :func:`matrix_of_frobenius` sometimes performs divisions by `p`, + so precision is lost during the algorithm. + + The estimate returned by this function is based on Kedlaya's result + (Lemmas 2 and 3 of [Ked2001]_), + which implies that if we start with `M` `p`-adic + digits, the total precision loss is at most + `1 + \lfloor \log_p(2M-3) \rfloor` `p`-adic + digits. (This estimate is somewhat less than the amount you would + expect by naively counting the number of divisions by + `p`.) + + INPUT: + + - ``p`` -- a prime ``p >= 5`` + + - ``prec`` -- integer; desired output precision, ``prec >= 1`` + + OUTPUT: adjusted precision (usually slightly more than ``prec``) + + EXAMPLES:: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import adjusted_prec + sage: adjusted_prec(5,2) + 3 + """ + # initial estimate: + if prec <= 2: + defect = 0 + adjusted = 2 + else: + defect = Integer(2 * prec - 3).exact_log(p) + adjusted = prec + defect - 1 + + # increase it until we have enough + while adjusted - defect - 1 < prec: + adjusted += 1 + + return adjusted + + +def matrix_of_frobenius(Q, p, M, trace=None, compute_exact_forms=False): + r""" + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology, + with respect to the basis `(dx/y, x dx/y)`. + + INPUT: + + - ``Q`` -- cubic polynomial `Q(x) = x^3 + ax + b` + defining an elliptic curve `E` by + `y^2 = Q(x)`. The coefficient ring of `Q` should be a + `\ZZ/(p^M)\ZZ`-algebra in which the matrix of + frobenius will be constructed. + + - ``p`` -- prime >= 5 for which E has good reduction + + - ``M`` -- integer >= 2; `p` -adic precision of the coefficient ring + + - ``trace`` -- (optional) the trace of the matrix, if + known in advance. This is easy to compute because it is just the + `a_p` of the curve. If the trace is supplied, + matrix_of_frobenius will use it to speed the computation (i.e. we + know the determinant is `p`, so we have two conditions, so + really only column of the matrix needs to be computed. it is + actually a little more complicated than that, but that's the basic + idea.) If trace=None, then both columns will be computed + independently, and you can get a strong indication of correctness + by verifying the trace afterwards. + + .. WARNING:: + + THE RESULT WILL NOT NECESSARILY BE CORRECT TO M p-ADIC + DIGITS. If you want prec digits of precision, you need to use + the function adjusted_prec(), and then you need to reduce the + answer mod `p^{\mathrm{prec}}` at the end. + + OUTPUT: + + `2 \times 2` matrix of Frobenius acting on Monsky-Washnitzer cohomology, + with entries in the coefficient ring of ``Q``. + + EXAMPLES: + + A simple example:: + + sage: p = 5 + sage: prec = 3 + sage: M = monsky_washnitzer.adjusted_prec(p, prec); M + 4 + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: A + [340 62] + [ 70 533] + + But the result is only accurate to ``prec`` digits:: + + sage: B = A.change_ring(Integers(p**prec)) + sage: B + [90 62] + [70 33] + + Check trace (123 = -2 mod 125) and determinant:: + + sage: B.det() + 5 + sage: B.trace() + 123 + sage: EllipticCurve([-1, 1/4]).ap(5) + -2 + + Try using the trace to speed up the calculation:: + + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), + ....: p, M, -2) + sage: A + [ 90 62] + [320 533] + + Hmmm... it looks different, but that's because the trace of our + first answer was only -2 modulo `5^3`, not -2 modulo + `5^5`. So the right answer is:: + + sage: A.change_ring(Integers(p**prec)) + [90 62] + [70 33] + + Check it works with only one digit of precision:: + + sage: p = 5 + sage: prec = 1 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: A.change_ring(Integers(p)) + [0 2] + [0 3] + + Here is an example that is particularly badly conditioned for + using the trace trick:: + + sage: # needs sage.libs.pari + sage: p = 11 + sage: prec = 3 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 + 7*x + 8, p, M) + sage: A.change_ring(Integers(p**prec)) + [1144 176] + [ 847 185] + + The problem here is that the top-right entry is divisible by 11, + and the bottom-left entry is divisible by `11^2`. So when + you apply the trace trick, neither `F(dx/y)` nor + `F(x dx/y)` is enough to compute the whole matrix to the + desired precision, even if you try increasing the target precision + by one. Nevertheless, ``matrix_of_frobenius`` knows + how to get the right answer by evaluating `F((x+1) dx/y)` + instead:: + + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 + 7*x + 8, p, M, -2) + sage: A.change_ring(Integers(p**prec)) + [1144 176] + [ 847 185] + + The running time is about ``O(p*prec**2)`` (times some logarithmic + factors), so it is feasible to run on fairly large primes, or + precision (or both?!?!):: + + sage: # long time, needs sage.libs.pari + sage: p = 10007 + sage: prec = 2 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: B = A.change_ring(Integers(p**prec)); B + [74311982 57996908] + [95877067 25828133] + sage: B.det() + 10007 + sage: B.trace() + 66 + sage: EllipticCurve([-1, 1/4]).ap(10007) + 66 + + :: + + sage: # long time, needs sage.libs.pari + sage: p = 5 + sage: prec = 300 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: B = A.change_ring(Integers(p**prec)) + sage: B.det() + 5 + sage: -B.trace() + 2 + sage: EllipticCurve([-1, 1/4]).ap(5) + -2 + + Let us check consistency of the results for a range of precisions:: + + sage: # long time, needs sage.libs.pari + sage: p = 5 + sage: max_prec = 60 + sage: M = monsky_washnitzer.adjusted_prec(p, max_prec) + sage: R. = PolynomialRing(Integers(p**M)) + sage: A = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + sage: A = A.change_ring(Integers(p**max_prec)) + sage: result = [] + sage: for prec in range(1, max_prec): + ....: M = monsky_washnitzer.adjusted_prec(p, prec) + ....: R. = PolynomialRing(Integers(p^M),'x') + ....: B = monsky_washnitzer.matrix_of_frobenius(x^3 - x + R(1/4), p, M) + ....: B = B.change_ring(Integers(p**prec)) + ....: result.append(B == A.change_ring(Integers(p**prec))) + sage: result == [True] * (max_prec - 1) + True + + The remaining examples discuss what happens when you take the + coefficient ring to be a power series ring; i.e. in effect you're + looking at a family of curves. + + The code does in fact work... + + :: + + sage: # needs sage.libs.pari + sage: p = 11 + sage: prec = 3 + sage: M = monsky_washnitzer.adjusted_prec(p, prec) + sage: S. = PowerSeriesRing(Integers(p**M), default_prec=4) + sage: a = 7 + t + 3*t^2 + sage: b = 8 - 6*t + 17*t^2 + sage: R. = PolynomialRing(S) + sage: Q = x**3 + a*x + b + sage: A = monsky_washnitzer.matrix_of_frobenius(Q, p, M) # long time + sage: B = A.change_ring(PowerSeriesRing(Integers(p**prec), 't', # long time + ....: default_prec=4)); B + [1144 + 264*t + 841*t^2 + 1025*t^3 + O(t^4) 176 + 1052*t + 216*t^2 + 523*t^3 + O(t^4)] + [ 847 + 668*t + 81*t^2 + 424*t^3 + O(t^4) 185 + 341*t + 171*t^2 + 642*t^3 + O(t^4)] + + The trace trick should work for power series rings too, even in the + badly-conditioned case. Unfortunately I do not know how to compute + the trace in advance, so I am not sure exactly how this would help. + Also, I suspect the running time will be dominated by the + expansion, so the trace trick will not really speed things up anyway. + Another problem is that the determinant is not always p:: + + sage: B.det() # long time + 11 + 484*t^2 + 451*t^3 + O(t^4) + + However, it appears that the determinant always has the property + that if you substitute t - 11t, you do get the constant series p + (mod p\*\*prec). Similarly for the trace. And since the parameter + only really makes sense when it is divisible by p anyway, perhaps + this is not a problem after all. + """ + M = int(M) + if M < 2: + raise ValueError("M (=%s) must be at least 2" % M) + + base_ring = Q.base_ring() + + # Expand out frobenius of dx/y and x dx/y. + # (You can substitute frobenius_expansion_by_series here, that will work + # as well. See its docstring for some performance notes.) + F0, F1, offset = frobenius_expansion_by_newton(Q, p, M) + # F0, F1, offset = frobenius_expansion_by_series(Q, p, M) + + if compute_exact_forms: + # we need to do all the work to get the exact expressions f such that F(x^i dx/y) = df + \sum a_i x^i dx/y + F0_coeffs = transpose_list(F0.coeffs()) + F0_reduced, f_0 = reduce_all(Q, p, F0_coeffs, offset, True) + + F1_coeffs = transpose_list(F1.coeffs()) + F1_reduced, f_1 = reduce_all(Q, p, F1_coeffs, offset, True) + + elif M == 2: + # This implies that only one digit of precision is valid, so we only need + # to reduce the second column. Also, the trace doesn't help at all. + + F0_reduced = [base_ring(0), base_ring(0)] + + F1_coeffs = transpose_list(F1.coeffs()) + F1_reduced = reduce_all(Q, p, F1_coeffs, offset) + + elif trace is None: + # No trace provided, just reduce F(dx/y) and F(x dx/y) separately. + + F0_coeffs = transpose_list(F0.coeffs()) + F0_reduced = reduce_all(Q, p, F0_coeffs, offset) + + F1_coeffs = transpose_list(F1.coeffs()) + F1_reduced = reduce_all(Q, p, F1_coeffs, offset) + + else: + # Trace has been provided. + + # In most cases this can be used to quickly compute F(dx/y) from + # F(x dx/y). However, if we're unlucky, the (dx/y)-component of + # F(x dx/y) (i.e. the top-right corner of the matrix) may be divisible + # by p, in which case there isn't enough information to get the + # (x dx/y)-component of F(dx/y) to the desired precision. When this + # happens, it turns out that F((x+1) dx/y) always *does* give enough + # information (together with the trace) to get both columns to the + # desired precision. + + # First however we need a quick way of telling whether the top-right + # corner is divisible by p, i.e. we want to compute the second column + # of the matrix mod p. We could do this by just running the entire + # algorithm with M = 2 (which assures precision 1). Luckily, we've + # already done most of the work by computing F1 to high precision; so + # all we need to do is extract the coefficients that would correspond + # to the first term of the series, and run the reduction on them. + + # todo: actually we only need to do this reduction step mod p^2, not + # mod p^M, which is what the code currently does. If the base ring + # is Integers(p^M), then it is easy. Otherwise it is tricky to construct + # the right ring, I don't know how to do it. + + F1_coeffs = transpose_list(F1.coeffs()) + F1_modp_coeffs = F1_coeffs[int((M - 2) * p) :] + # make a copy, because reduce_all will destroy the coefficients: + F1_modp_coeffs = [list(row) for row in F1_modp_coeffs] + F1_modp_offset = offset - (M - 2) * p + F1_modp_reduced = reduce_all(Q, p, F1_modp_coeffs, F1_modp_offset) + + if F1_modp_reduced[0].is_unit(): + # If the first entry is invertible mod p, then F(x dx/y) is sufficient + # to get the whole matrix. + + F1_reduced = reduce_all(Q, p, F1_coeffs, offset) + + F0_reduced = [base_ring(trace) - F1_reduced[1], None] + # using that the determinant is p: + F0_reduced[1] = (F0_reduced[0] * F1_reduced[1] - base_ring(p)) / F1_reduced[ + 0 + ] + + else: + # If the first entry is zero mod p, then F((x+1) dx/y) will be sufficient + # to get the whole matrix. (Here we are using the fact that the second + # entry *cannot* be zero mod p. This is guaranteed by some results in + # section 3.2 of ``Computation of p-adic Heights and Log Convergence'' + # by Mazur, Stein, Tate. But let's quickly check it anyway :-)) + msg = "The second entry in the second column " + msg += "should be invertible mod p!" + assert F1_modp_reduced[1].is_unit(), msg + + G0_coeffs = transpose_list((F0 + F1).coeffs()) + G0_reduced = reduce_all(Q, p, G0_coeffs, offset) + + # Now G0_reduced expresses F((x+1) dx/y) in terms of dx/y and x dx/y. + # Re-express this in terms of (x+1) dx/y and x dx/y. + H0_reduced = [G0_reduced[0], G0_reduced[1] - G0_reduced[0]] + + # The thing we're about to divide by better be a unit. + msg = "The second entry in this column " + msg += "should be invertible mod p!" + assert H0_reduced[1].is_unit(), msg + + # Figure out the second column using the trace... + H1_reduced = [None, base_ring(trace) - H0_reduced[0]] + # ... and using that the determinant is p: + H1_reduced[0] = (H0_reduced[0] * H1_reduced[1] - base_ring(p)) / H0_reduced[ + 1 + ] + + # Finally, change back to the usual basis (dx/y, x dx/y) + F1_reduced = [H1_reduced[0], H1_reduced[0] + H1_reduced[1]] + F0_reduced = [ + H0_reduced[0] - F1_reduced[0], + H0_reduced[0] + H0_reduced[1] - F1_reduced[1], + ] + + # One more sanity check: our final result should be congruent mod p + # to the approximation we used earlier. + msg = "The output matrix is not congruent mod p " + msg += "to the approximation found earlier!" + assert not ( + (F1_reduced[0] - F1_modp_reduced[0]).is_unit() + or (F1_reduced[1] - F1_modp_reduced[1]).is_unit() + or F0_reduced[0].is_unit() + or F0_reduced[1].is_unit() + ), msg + + if compute_exact_forms: + return ( + matrix( + base_ring, + 2, + 2, + [F0_reduced[0], F1_reduced[0], F0_reduced[1], F1_reduced[1]], + ), + f_0, + f_1, + ) + else: + return matrix( + base_ring, + 2, + 2, + [F0_reduced[0], F1_reduced[0], F0_reduced[1], F1_reduced[1]], + ) + + +# **************************************************************************** +# This is a generalization of the above functionality for hyperelliptic curves. +# +# THIS IS A WORK IN PROGRESS. +# +# I tried to embed must stuff into the rings themselves rather than +# just extract and manipulate lists of coefficients. Hence the implementations +# below are much less optimized, so are much slower, but should hopefully be +# easier to follow. (E.g. one can print/make sense of intermediate results.) +# +# AUTHOR: +# -- Robert Bradshaw (2007-04) +# +# **************************************************************************** + + +def matrix_of_frobenius_hyperelliptic(Q, p=None, prec=None, M=None): + r""" + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology, + with respect to the basis `(dx/2y, x dx/2y, ...x^{d-2} dx/2y)`, where + `d` is the degree of `Q`. + + INPUT: + + - ``Q`` -- monic polynomial `Q(x)` + + - ``p`` -- prime `\geq 5` for which `E` has good reduction + + - ``prec`` -- (optional) `p`-adic precision of the coefficient ring + + - ``M`` -- (optional) adjusted `p`-adic precision of the coefficient ring + + OUTPUT: + + `(d-1)` x `(d-1)` matrix `M` of Frobenius on Monsky-Washnitzer cohomology, + and list of differentials \{f_i \} such that + + .. MATH:: + + \phi^* (x^i dx/2y) = df_i + M[i]*vec(dx/2y, ..., x^{d-2} dx/2y) + + EXAMPLES:: + + sage: # needs sage.rings.padics + sage: p = 5 + sage: prec = 3 + sage: R. = QQ['x'] + sage: A,f = monsky_washnitzer.matrix_of_frobenius_hyperelliptic(x^5 - 2*x + 3, p, prec) + sage: A + [ 4*5 + O(5^3) 5 + 2*5^2 + O(5^3) 2 + 3*5 + 2*5^2 + O(5^3) 2 + 5 + 5^2 + O(5^3)] + [ 3*5 + 5^2 + O(5^3) 3*5 + O(5^3) 4*5 + O(5^3) 2 + 5^2 + O(5^3)] + [ 4*5 + 4*5^2 + O(5^3) 3*5 + 2*5^2 + O(5^3) 5 + 3*5^2 + O(5^3) 2*5 + 2*5^2 + O(5^3)] + [ 5^2 + O(5^3) 5 + 4*5^2 + O(5^3) 4*5 + 3*5^2 + O(5^3) 2*5 + O(5^3)] + """ + prof = Profiler() + prof("setup") + if p is None: + try: + K = Q.base_ring() + p = K.prime() + prec = K.precision_cap() + except AttributeError: + raise ValueError( + "p and prec must be specified if Q is not " "defined over a p-adic ring" + ) + if M is None: + M = adjusted_prec(p, prec) + extra_prec_ring = Integers(p**M) + # extra_prec_ring = pAdicField(p, M) # SLOW! + + real_prec_ring = pAdicField(p, prec) # pAdicField(p, prec) # To capped absolute? + S = SpecialHyperellipticQuotientRing(Q, extra_prec_ring, True) + MW = S.monsky_washnitzer() + prof("frob basis elements") + F = MW.frob_basis_elements(M, p) + + prof("rationalize") + # do reduction over Q in case we have non-integral entries (and it is so much faster than padics) + rational_S = S.change_ring(QQ) + # this is a hack until pAdics are fast + # (They are in the latest development bundle, but its not standard and I'd need to merge. + # (it will periodically cast into this ring to reduce coefficient size) + rational_S._prec_cap = p**M + rational_S._p = p + # S._p = p + # rational_S(F[0]).reduce_fast() + # prof("reduce others") + + # rational_S = S.change_ring(pAdicField(p, M)) + F = [rational_S(F_i) for F_i in F] + + prof("reduce") + reduced = [F_i.reduce_fast(True) for F_i in F] + + # but the coeffs are WAY more precision than they need to be + + prof("make matrix") + # now take care of precision capping + M = matrix(real_prec_ring, [a for f, a in reduced]) + for i in range(M.ncols()): + for j in range(M.nrows()): + M[i, j] = M[i, j].add_bigoh(prec) + return M.transpose(), [f for f, a in reduced] + + +class SpecialHyperellipticQuotientElement(ModuleElement): + r""" + Element in the Hyperelliptic quotient ring. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: MW(x + x**2 + y - 77) + -(77-y)*1 + x + x^2 + """ + + def __init__(self, parent, val=0, offset=0, check=True): + """ + Elements in the Hyperelliptic quotient ring. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: elt = MW(x + x**2 + y - 77) + sage: TestSuite(elt).run() + """ + ModuleElement.__init__(self, parent) + if not check: + self._f = parent._poly_ring(val, check=False) + return + if isinstance(val, SpecialHyperellipticQuotientElement): + R = parent.base_ring() + self._f = parent._poly_ring([a.change_ring(R) for a in val._f]) + return + if isinstance(val, tuple): + val, offset = val + if isinstance(val, list) and val and isinstance(val[0], FreeModuleElement): + val = transpose_list(val) + self._f = parent._poly_ring(val) + if offset != 0: + self._f = self._f.parent()([a << offset for a in self._f], check=False) + + def _richcmp_(self, other, op) -> bool: + """ + Compare the elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x == x + True + sage: x > y + True + """ + return richcmp(self._f, other._f, op) + + def change_ring(self, R): + """ + Return the same element after changing the base ring to `R`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: z = MW(x + x**2 + y - 77) + sage: z.change_ring(AA).parent() # needs sage.rings.number_field + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 36*x + 1) + over Algebraic Real Field + """ + return self.parent().change_ring(R)(self) + + def __call__(self, *x): + """ + Evaluate ``self`` at given arguments. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: z = MW(x + x**2 + y - 77); z + -(77-y)*1 + x + x^2 + sage: z(66) + 4345 + y + sage: z(5,4) + -43 + """ + return self._f(*x) + + def __invert__(self): + """ + Return the inverse of ``self``. + + The general element in our ring is not invertible, but `y` may + be. We do not want to pass to the fraction field. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: MW = x.parent() + sage: z = y**(-1) # indirect doctest + sage: z.parent() + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 36*x + 1) + over Rational Field + + sage: z = (x+y)**(-1) # indirect doctest + Traceback (most recent call last): + ... + ZeroDivisionError: element not invertible + """ + if self._f.degree() == 0 and self._f[0].is_unit(): + P = self.parent() + return P.element_class(P, ~self._f[0]) + else: + raise ZeroDivisionError("element not invertible") + + def __bool__(self): + """ + Return ``True`` iff ``self`` is not zero. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: bool(x) + True + """ + return bool(self._f) + + def __eq__(self, other): + """ + Return ``True`` iff ``self`` is equal to ``other``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x == y # indirect doctest + False + """ + if not isinstance(other, SpecialHyperellipticQuotientElement): + other = self.parent()(other) + return self._f == other._f + + def _add_(self, other): + """ + Return the sum of two elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x + y + y*1 + x + """ + P = self.parent() + return P.element_class(P, self._f + other._f) + + def _sub_(self, other): + """ + Return the difference of two elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: y - x + y*1 - x + """ + P = self.parent() + return P.element_class(P, self._f - other._f) + + def _mul_(self, other): + """ + Return the product of two elements. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 36*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: y*x + y*x + """ + # over Laurent series, addition and subtraction can be + # expensive, and the degree of this poly is small enough that + # Karatsuba actually hurts significantly in some cases + if self._f[0].valuation() + other._f[0].valuation() > -200: + prod = self._f._mul_generic(other._f) + else: + prod = self._f * other._f + v = prod.list() + parent = self.parent() + Q_coeffs = parent._Q_coeffs + n = len(Q_coeffs) - 1 + y2 = self.parent()._series_ring_y << 1 + for i in range(len(v) - 1, n - 1, -1): + for j in range(n): + v[i - n + j] -= Q_coeffs[j] * v[i] + v[i - n] += y2 * v[i] + P = self.parent() + return P.element_class(P, v[0:n]) + + def _rmul_(self, c): + """ + Return the product of ``self`` with `c` on the left. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x._rmul_(y) # needs sage.rings.real_interval_field + y*1*x + """ + P = self.parent() + if not c: + return P.zero() + ret = [c * a for a in self._f.list(copy=False)] + while ret and not ret[-1]: # strip off trailing 0s + ret.pop() + return P.element_class(P, ret, check=False) + + def _lmul_(self, c): + """ + Return the product of ``self`` with `c` on the right. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5-3*x+1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x._lmul_(y) # needs sage.rings.real_interval_field + y*1*x + """ + P = self.parent() + if not c: + return P.zero() + ret = [a * c for a in self._f.list(copy=False)] + while ret and not ret[-1]: # strip off trailing 0s + ret.pop() + return P.element_class(P, ret, check=False) + + def __lshift__(self, k): + """ + Return the left shift of ``self`` by `k`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.__lshift__(3) + y^3*x + """ + coeffs = self._f.list(copy=False) + P = self.parent() + return P.element_class(P, [a << k for a in coeffs], check=False) + + def __rshift__(self, k): + """ + Return the right shift of ``self`` by `k`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: y.__rshift__(3) + (y^-2)*1 + """ + coeffs = self._f.list(copy=False) + P = self.parent() + return P.element_class(P, [a >> k for a in coeffs], check=False) + + def truncate_neg(self, n): + """ + Return ``self`` minus its terms of degree less than `n` wrt `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y + 7*x*2*y**4).truncate_neg(1) + 3*y*1 + 14*y^4*x + """ + coeffs = self._f.list(copy=False) + P = self.parent() + return P.element_class(P, [a.truncate_neg(n) for a in coeffs], check=False) + + def _repr_(self): + """ + Return a string representation of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y)._repr_() + '3*y*1 + x' + """ + x = PolynomialRing(QQ, "x").gen(0) + coeffs = self._f.list() + return repr_lincomb([(x**i, coeffs[i]) for i in range(len(coeffs))]) + + def _latex_(self): + r""" + Return a LateX string for ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y)._latex_() + '3y 1 + x' + """ + x = PolynomialRing(QQ, "x").gen(0) + coeffs = self._f.list() + return repr_lincomb( + [(x**i, coeffs[i]) for i in range(len(coeffs))], is_latex=True + ) + + def diff(self): + """ + Return the differential of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y).diff() + (-(9-2*y)*1 + 15*x^4) dx/2y + """ + # try: + # return self._diff_x + # except AttributeError: + # pass + + # d(self) = A dx + B dy + # = (2y A + BQ') dx/2y + P = self.parent() + R = P.base_ring() + v = self._f.list() + n = len(v) + A = P([R(i) * v[i] for i in range(1, n)]) + B = P([a.derivative() for a in v]) + dQ = P._dQ + return P._monsky_washnitzer((R(2) * A << 1) + dQ * B) + + # self._diff = self.parent()._monsky_washnitzer(two_y * A + dQ * B) + # return self._diff + + def extract_pow_y(self, k): + r""" + Return the coefficients of `y^k` in ``self`` as a list. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y + 9*x*y).extract_pow_y(1) + [3, 9, 0, 0, 0] + """ + v = [a[k] for a in self._f.list()] + v += [ZZ.zero()] * (self.parent()._n - len(v)) + return v + + def min_pow_y(self): + r""" + Return the minimal degree of ``self`` with respect to `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y).min_pow_y() + 0 + """ + if self._f.degree() == -1: + return ZZ.zero() + return min([a.valuation() for a in self._f.list()]) + + def max_pow_y(self): + r""" + Return the maximal degree of ``self`` with respect to `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: (x + 3*y).max_pow_y() + 1 + """ + if self._f.degree() == -1: + return ZZ.zero() + return max([a.degree() for a in self._f.list()]) + + def coeffs(self, R=None): + r""" + Return the raw coefficients of this element. + + INPUT: + + - ``R`` -- an (optional) base-ring in which to cast the coefficients + + OUTPUT: + + - ``coeffs`` -- list of coefficients of powers of `x` for each power + of `y` + + - ``n`` -- an offset indicating the power of `y` of the first list + element + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.coeffs() + ([(0, 1, 0, 0, 0)], 0) + sage: y.coeffs() + ([(0, 0, 0, 0, 0), (1, 0, 0, 0, 0)], 0) + + sage: a = sum(n*x^n for n in range(5)); a + x + 2*x^2 + 3*x^3 + 4*x^4 + sage: a.coeffs() + ([(0, 1, 2, 3, 4)], 0) + sage: a.coeffs(Qp(7)) # needs sage.rings.padics + ([(0, 1 + O(7^20), 2 + O(7^20), 3 + O(7^20), 4 + O(7^20))], 0) + sage: (a*y).coeffs() + ([(0, 0, 0, 0, 0), (0, 1, 2, 3, 4)], 0) + sage: (a*y^-2).coeffs() + ([(0, 1, 2, 3, 4), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)], -2) + + Note that the coefficient list is transposed compared to how they + are stored and printed:: + + sage: a*y^-2 + (y^-2)*x + (2*y^-2)*x^2 + (3*y^-2)*x^3 + (4*y^-2)*x^4 + + A more complicated example:: + + sage: a = x^20*y^-3 - x^11*y^2; a + (y^-3-4*y^-1+6*y-4*y^3+y^5)*1 - (12*y^-3-36*y^-1+36*y+y^2-12*y^3-2*y^4+y^6)*x + + (54*y^-3-108*y^-1+54*y+6*y^2-6*y^4)*x^2 - (108*y^-3-108*y^-1+9*y^2)*x^3 + + (81*y^-3)*x^4 + sage: raw, offset = a.coeffs() + sage: a.min_pow_y() + -3 + sage: offset + -3 + sage: raw + [(1, -12, 54, -108, 81), + (0, 0, 0, 0, 0), + (-4, 36, -108, 108, 0), + (0, 0, 0, 0, 0), + (6, -36, 54, 0, 0), + (0, -1, 6, -9, 0), + (-4, 12, 0, 0, 0), + (0, 2, -6, 0, 0), + (1, 0, 0, 0, 0), + (0, -1, 0, 0, 0)] + sage: sum(c * x^i * y^(j+offset) + ....: for j, L in enumerate(raw) for i, c in enumerate(L)) == a + True + + Can also be used to construct elements:: + + sage: a.parent()(raw, offset) == a + True + """ + zero = self.base_ring()(0) if R is None else R(0) + y_offset = min(self.min_pow_y(), 0) + y_degree = max(self.max_pow_y(), 0) + coeffs = [] + n = y_degree - y_offset + 1 + for a in self._f.list(): + k = a.valuation() + if k is Infinity: + k = 0 + k -= y_offset + z = a.list() + coeffs.append([zero] * k + z + [zero] * (n - len(z) - k)) + while len(coeffs) < self.parent().degree(): + coeffs.append([zero] * n) + V = FreeModule(self.base_ring() if R is None else R, self.parent().degree()) + coeffs = transpose_list(coeffs) + return [V(a) for a in coeffs], y_offset + + +class SpecialHyperellipticQuotientRing(UniqueRepresentation, Parent): + """ + The special hyperelliptic quotient ring. + """ + + _p = None + + def __init__(self, Q, R=None, invert_y=True): + r""" + Initialize ``self``. + + TESTS:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialHyperellipticQuotientRing + sage: HQR = SpecialHyperellipticQuotientRing(E) + sage: TestSuite(HQR).run() # needs sage.rings.real_interval_field + + Check that caching works:: + + sage: HQR is SpecialHyperellipticQuotientRing(E) + True + """ + if R is None: + R = Q.base_ring() + + x = PolynomialRing(R, "xx").gen() + if isinstance(Q, EllipticCurve_generic): + E = Q + if E.a1() != 0 or E.a2() != 0: + raise NotImplementedError("curve must be in Weierstrass normal form") + Q = -E.change_ring(R).defining_polynomial()(x, 0, 1) + self._curve = E + + elif isinstance(Q, HyperellipticCurveSmoothModel_generic): + C = Q + if C.hyperelliptic_polynomials()[1] != 0: + raise NotImplementedError("curve must be of form y^2 = Q(x)") + Q = C.hyperelliptic_polynomials()[0].change_ring(R) + self._curve = C + + if isinstance(Q, Polynomial): + self._Q = Q.change_ring(R) + self._coeffs = self._Q.coefficients(sparse=False) + if self._coeffs.pop() != 1: + raise NotImplementedError("polynomial must be monic") + if not hasattr(self, "_curve"): + if self._Q.degree() == 3: + ainvs = [0, self._Q[2], 0, self._Q[1], self._Q[0]] + self._curve = EllipticCurve(ainvs) + else: + self._curve = HyperellipticCurveSmoothModel( + self._Q, check_squarefree=R.is_field() + ) + + else: + raise NotImplementedError( + "must be an elliptic curve or polynomial " + "Q for y^2 = Q(x)\n(Got element of %s)" % Q.parent() + ) + + self._n = int(Q.degree()) + self._series_ring = (LaurentSeriesRing if invert_y else PolynomialRing)(R, "y") + self._series_ring_y = self._series_ring.gen(0) + self._series_ring_0 = self._series_ring.zero() + + Parent.__init__(self, base=R, category=Algebras(R).Commutative()) + + self._poly_ring = PolynomialRing(self._series_ring, "x") + + self._x = self.element_class(self, self._poly_ring.gen(0)) + self._y = self.element_class(self, self._series_ring.gen(0)) + + self._Q_coeffs = Q.change_ring(self._series_ring).list() + self._dQ = Q.derivative().change_ring(self)(self._x) + self._monsky_washnitzer = MonskyWashnitzerDifferentialRing(self) + + self._monomial_diffs = {} + self._monomial_diff_coeffs = {} + + def _repr_(self) -> str: + r""" + String representation. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent() # indirect doctest + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 3*x + 1) over Rational Field + """ + y_inverse = ( + ",y^-1" + if isinstance(self._series_ring, (LaurentSeriesRing, LazyLaurentSeriesRing)) + else "" + ) + return "SpecialHyperellipticQuotientRing K[x,y%s] / (y^2 = %s) over %s" % ( + y_inverse, + self._Q, + self.base_ring(), + ) + + def base_extend(self, R): + r""" + Return the base extension of ``self`` to the ring ``R`` if possible. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().base_extend(UniversalCyclotomicField()) # needs sage.libs.gap + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 3*x + 1) + over Universal Cyclotomic Field + sage: x.parent().base_extend(ZZ) + Traceback (most recent call last): + ... + TypeError: no such base extension + """ + if R.has_coerce_map_from(self.base_ring()): + return self.change_ring(R) + raise TypeError("no such base extension") + + def change_ring(self, R): + r""" + Return the analog of ``self`` over the ring ``R``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().change_ring(ZZ) + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 3*x + 1) over Integer Ring + """ + return SpecialHyperellipticQuotientRing( + self._Q.change_ring(R), + R, + isinstance(self._series_ring, (LaurentSeriesRing, LazyLaurentSeriesRing)), + ) + + def _element_constructor_(self, val, offset=0, check=True): + r""" + Construct an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent()(x^6) + -(1-y^2)*x + 3*x^2 + """ + if ( + isinstance(val, SpecialHyperellipticQuotientElement) + and val.parent() is self + ): + if offset == 0: + return val + else: + return val << offset + elif isinstance(val, MonskyWashnitzerDifferential): + return self._monsky_washnitzer(val) + return self.element_class(self, val, offset, check) + + @cached_method + def one(self): + """ + Return the unit of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().one() + 1 + """ + return self.element_class(self, self._poly_ring.one(), check=False) + + @cached_method + def zero(self): + """ + Return the zero of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().zero() + 0 + """ + return self.element_class(self, self._poly_ring.zero(), check=False) + + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().gens() + (x, y*1) + """ + return (self._x, self._y) + + def x(self): + r""" + Return the generator `x` of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().x() + x + """ + return self._x + + def y(self): + r""" + Return the generator `y` of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().y() + y*1 + """ + return self._y + + def monomial(self, i, j, b=None): + """ + Return `b y^j x^i`, computed quickly. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().monomial(4,5) + y^5*x^4 + """ + i = int(i) + j = int(j) + + if 0 < i and i < self._n: + if b is None: + by_to_j = self._series_ring_y << (j - 1) + else: + by_to_j = self._series_ring(b) << j + v = [self._series_ring_0] * self._n + v[i] = by_to_j + return self.element_class(self, v) + + if b is not None: + b = self.base_ring()(b) + return (self._x**i) << j if b is None else b * (self._x**i) << j + + def monomial_diff_coeffs(self, i, j): + r""" + Compute coefficients of the basis representation of `d(x^iy^j)`. + + The key here is that the formula for `d(x^iy^j)` is messy + in terms of `i`, but varies nicely with `j`. + + .. MATH:: + + d(x^iy^j) = y^{j-1} (2ix^{i-1}y^2 + j (A_i(x) + B_i(x)y^2)) \frac{dx}{2y}, + + where `A,B` have degree at most `n-1` for each + `i`. Pre-compute `A_i, B_i` for each `i` + the "hard" way, and the rest are easy. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().monomial_diff_coeffs(2,3) + ((0, -15, 36, 0, 0), (0, 19, 0, 0, 0)) + """ + try: + return self._monomial_diff_coeffs[i, j] + except KeyError: + pass + if i < self._n: + try: + A, B, two_i_x_to_i = self._precomputed_diff_coeffs[i] + except AttributeError: + self._precomputed_diff_coeffs = self._precompute_monomial_diffs() + A, B, two_i_x_to_i = self._precomputed_diff_coeffs[i] + if i == 0: + return j * A, j * B + else: + return j * A, j * B + two_i_x_to_i + else: + dg = self.monomial(i, j).diff() + coeffs = [dg.extract_pow_y(j - 1), dg.extract_pow_y(j + 1)] + self._monomial_diff_coeffs[i, j] = coeffs + return coeffs + + def monomial_diff_coeffs_matrices(self): + r""" + Compute tables of coefficients of the basis representation + of `d(x^iy^j)` for small `i`, `j`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().monomial_diff_coeffs_matrices() + ( + [0 5 0 0 0] [0 2 0 0 0] + [0 0 5 0 0] [0 0 4 0 0] + [0 0 0 5 0] [0 0 0 6 0] + [0 0 0 0 5] [0 0 0 0 8] + [0 0 0 0 0], [0 0 0 0 0] + ) + """ + self.monomial_diff_coeffs(0, 0) # precompute stuff + R = self.base_ring() + mat_1 = matrix(R, self._n, self._n) + mat_2 = matrix(R, self._n, self._n) + for i in range(self._n): + mat_1[i] = self._precomputed_diff_coeffs[i][1] + mat_2[i] = self._precomputed_diff_coeffs[i][2] + return mat_1.transpose(), mat_2.transpose() + + def _precompute_monomial_diffs(self): + r""" + Precompute coefficients of the basis representation of `d(x^iy^j)` + for small `i`, `j`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent()._precompute_monomial_diffs() + [((-3, 0, 0, 0, 5), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)), + ((-5, 12, 0, 0, 0), (5, 0, 0, 0, 0), (2, 0, 0, 0, 0)), + ((0, -5, 12, 0, 0), (0, 5, 0, 0, 0), (0, 4, 0, 0, 0)), + ((0, 0, -5, 12, 0), (0, 0, 5, 0, 0), (0, 0, 6, 0, 0)), + ((0, 0, 0, -5, 12), (0, 0, 0, 5, 0), (0, 0, 0, 8, 0))] + """ + x, y = self.gens() + R = self.base_ring() + V = FreeModule(R, self.degree()) + As = [] + for i in range(self.degree()): + dg = self.monomial(i, 1).diff() + two_i_x_to_i = R(2 * i) * x ** (i - 1) * y * y if i > 0 else self(0) + A = dg - self._monsky_washnitzer(two_i_x_to_i) + As.append( + ( + V(A.extract_pow_y(0)), + V(A.extract_pow_y(2)), + V(two_i_x_to_i.extract_pow_y(2)), + ) + ) + return As + + def Q(self): + """ + Return the defining polynomial of the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5-2*x+1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().Q() + x^5 - 2*x + 1 + """ + return self._Q + + def curve(self): + """ + Return the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().curve() + Hyperelliptic Curve over Rational Field defined by y^2 = x^5 - 3*x + 1 + """ + return self._curve + + def degree(self): + """ + Return the degree of the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().degree() + 5 + """ + return Integer(self._n) + + def prime(self): + """ + Return the stored prime number `p`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().prime() is None + True + """ + return self._p + + def monsky_washnitzer(self): + """ + Return the stored Monsky-Washnitzer differential ring. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: type(x.parent().monsky_washnitzer()) + + """ + return self._monsky_washnitzer + + def is_field(self, proof=True) -> bool: + """ + Return ``False`` as ``self`` is not a field. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = E.monsky_washnitzer_gens() + sage: x.parent().is_field() + False + """ + return False + + Element = SpecialHyperellipticQuotientElement + + +SpecialHyperellipticQuotientRing_class = SpecialHyperellipticQuotientRing + + +class MonskyWashnitzerDifferential(ModuleElement): + r""" + An element of the Monsky-Washnitzer ring of differentials, of + the form `F dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: MW = C.invariant_differential().parent() + sage: MW(x) + x dx/2y + sage: MW(y) + y*1 dx/2y + sage: MW(x, 10) + y^10*x dx/2y + """ + + def __init__(self, parent, val, offset=0): + r""" + Initialize ``self``. + + INPUT: + + - ``parent`` -- Monsky-Washnitzer differential ring (instance of class + :class:`~MonskyWashnitzerDifferentialRing` + + - ``val`` -- element of the base ring, or list of coefficients + + - ``offset`` -- (default: 0) if nonzero, shift val by `y^\text{offset}` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: MW = C.invariant_differential().parent() + sage: elt = MW(x) + sage: TestSuite(elt).run() + """ + ModuleElement.__init__(self, parent) + R = parent.base_ring() + self._coeff = R._element_constructor_(val, offset) + + def _add_(self, other): + r""" + Return the sum of ``self`` and ``other``, both elements of the + Monsky-Washnitzer ring of differentials. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w + w + 2*1 dx/2y + sage: x*w + w + (1 + x) dx/2y + sage: x*w + y*w + (y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, self._coeff + other._coeff) + + def _sub_(self, other): + r""" + Return the difference of ``self`` and ``other``, both elements of the + Monsky-Washnitzer ring of differentials. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w-w + 0 dx/2y + sage: x*w-w + (-1 + x) dx/2y + sage: w - x*w - y*w + ((1-y)*1 - x) dx/2y + """ + P = self.parent() + return P.element_class(P, self._coeff - other._coeff) + + def __neg__(self): + r""" + Return the additive inverse of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: -w + -1 dx/2y + sage: -((y-x)*w) + (-y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, -self._coeff) + + def _lmul_(self, a): + r""" + Return `self * a`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w*x + x dx/2y + sage: (w*x)*2 + 2*x dx/2y + sage: w*y + y*1 dx/2y + sage: w*(x+y) + (y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, self._coeff * a) + + def _rmul_(self, a): + r""" + Return `a * self`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: x*w + x dx/2y + sage: 2*(x*w) + 2*x dx/2y + sage: y*w + y*1 dx/2y + sage: (x+y)*w + (y*1 + x) dx/2y + """ + P = self.parent() + return P.element_class(P, a * self._coeff) + + def coeff(self): + r""" + Return `A`, where this element is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w + 1 dx/2y + sage: w.coeff() + 1 + sage: (x*y*w).coeff() + y*x + """ + return self._coeff + + def __bool__(self): + r""" + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: not w + False + sage: not 0*w + True + sage: not x*y*w + False + """ + return bool(self._coeff) + + def _repr_(self): + r""" + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w + 1 dx/2y + sage: (2*x+y)*w + (y*1 + 2*x) dx/2y + """ + s = self._coeff._repr_() + if s.find("+") != -1 or s.find("-") > 0: + s = "(%s)" % s + return s + " dx/2y" + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: latex(w) + 1 \frac{dx}{2y} + sage: latex(x*w) + x \frac{dx}{2y} + """ + s = self._coeff._latex_() + if s.find("+") != -1 or s.find("-") > 0: + s = "\\left(%s\\right)" % s + return s + " \\frac{dx}{2y}" + + def _richcmp_(self, other, op): + """ + Rich comparison of ``self`` to ``other``. + + .. TODO:: + + This does not compare elements by any reduction; + it only compares the coefficients. The comparison + should be done against a normal form or possibly + after some reduction steps. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^-1).diff() == (y^-1).diff() + True + + This element is the zero differential in the ring, but it does + not compare as equal because the representative is different:: + + sage: MW = C.invariant_differential().parent() + sage: (y^-1).diff().reduce_neg_y() + ((y^-1)*1, 0 dx/2y) + sage: (y^-1).diff() == MW.zero() + False + """ + return richcmp(self._coeff, other._coeff, op) + + def extract_pow_y(self, k): + r""" + Return the power of `y` in `A` where ``self`` is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: A = y^5 - x*y^3 + sage: A.extract_pow_y(5) + [1, 0, 0, 0, 0] + sage: (A * C.invariant_differential()).extract_pow_y(5) + [1, 0, 0, 0, 0] + """ + return self._coeff.extract_pow_y(k) + + def min_pow_y(self): + r""" + Return the minimum power of `y` in `A` where ``self`` is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = y^5 * C.invariant_differential() + sage: w.min_pow_y() + 5 + sage: w = (x^2*y^4 + y^5) * C.invariant_differential() + sage: w.min_pow_y() + 4 + """ + return self._coeff.min_pow_y() + + def max_pow_y(self): + r""" + Return the maximum power of `y` in `A` where ``self`` is `A dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = y^5 * C.invariant_differential() + sage: w.max_pow_y() + 5 + sage: w = (x^2*y^4 + y^5) * C.invariant_differential() + sage: w.max_pow_y() + 5 + """ + return self._coeff.max_pow_y() + + def reduce_neg_y(self): + r""" + Use homology relations to eliminate negative powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^-1).diff().reduce_neg_y() + ((y^-1)*1, 0 dx/2y) + sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y() + ((y^-1)*x + (y^-5)*x^2, 0 dx/2y) + """ + S = self.parent().base_ring() + R = S.base_ring() + M = self.parent().helper_matrix() + p = S._p + f = S.zero() + reduced = self + for j in range(self.min_pow_y() + 1, 0): + if p is not None and p.divides(j): + cs = [a / j for a in reduced.extract_pow_y(j - 1)] + else: + j_inverse = ~R(j) + cs = [a * j_inverse for a in reduced.extract_pow_y(j - 1)] + lin_comb = M * vector(M.base_ring(), cs) + if lin_comb.is_zero(): + continue + g = S.sum(S.monomial(i, j, val) for i, val in enumerate(lin_comb) if val) + if not g.is_zero(): + f += g + reduced -= g.diff() + + return f, reduced + + def reduce_neg_y_fast(self, even_degree_only=False): + r""" + Use homology relations to eliminate negative powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x, y = E.monsky_washnitzer_gens() + sage: (y^-1).diff().reduce_neg_y_fast() + ((y^-1)*1, 0 dx/2y) + sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y_fast() + ((y^-1)*x + (y^-5)*x^2, 0 dx/2y) + + It leaves nonnegative powers of `y` alone:: + + sage: y.diff() + (-3*1 + 5*x^4) dx/2y + sage: y.diff().reduce_neg_y_fast() + (0, (-3*1 + 5*x^4) dx/2y) + """ + # prof = Profiler() + # prof("reduce setup") + S = self.parent().base_ring() + R = S.base_ring() + M = self.parent().helper_matrix() + + # prof("extract coeffs") + coeffs, offset = self.coeffs(R) + V = coeffs[0].parent() + + if offset == 0: + return S(0), self + + # prof("loop %s"%self.min_pow_y()) + forms = [] + p = S._p + for j in range(self.min_pow_y() + 1, 0): + if (even_degree_only and j % 2 == 0) or coeffs[j - offset - 1].is_zero(): + forms.append(V(0)) + else: + # this is a total hack to deal with the fact that we're using + # rational numbers to approximate fixed precision p-adics + if p is not None and j % 3 == 1: + try: + v = coeffs[j - offset - 1] + for kk in range(len(v)): + a = v[kk] + ppow = p ** max(-a.valuation(S._p), 0) + v[kk] = ((a * ppow) % S._prec_cap) / ppow + except AttributeError: + pass + lin_comb = ~R(j) * (M * coeffs[j - offset - 1]) + forms.append(lin_comb) + for i in lin_comb.nonzero_positions(): + # g = lin_comb[i] x^i y^j + # self -= dg + coeffs[j - offset + 1] -= ( + lin_comb[i] * S.monomial_diff_coeffs(i, j)[1] + ) + + # prof("recreate forms") + f = S(forms, offset + 1) + reduced = S._monsky_washnitzer(coeffs[-1 - offset :], -1) + return f, reduced + + def reduce_neg_y_faster(self, even_degree_only=False): + r""" + Use homology relations to eliminate negative powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^-1).diff().reduce_neg_y() + ((y^-1)*1, 0 dx/2y) + sage: (y^-5*x^2+y^-1*x).diff().reduce_neg_y_faster() + ((y^-1)*x + (y^-5)*x^2, 0 dx/2y) + """ + # Timings indicate that this is not any faster after all... + + S = self.parent().base_ring() + R = S.base_ring() + M = self.parent().helper_matrix() + + coeffs, offset = self.coeffs(R) + V = coeffs[0].parent() + zeroV = V(0) + + if offset == 0: + return S.zero(), self + + # See monomial_diff_coeffs + # this is the B_i and x_to_i contributions respectively for all i + d_mat_1, d_mat_2 = S.monomial_diff_coeffs_matrices() + + forms = [] + for j in range(self.min_pow_y() + 1, 0): + if coeffs[j - offset - 1].is_zero(): + forms.append(zeroV) + else: + # this is a total hack to deal with the fact that we're using + # rational numbers to approximate fixed precision p-adics + if j % 3 == 0: + try: + v = coeffs[j - offset - 1] + for kk in range(len(v)): + a = v[kk] + ppow = S._p ** max(-a.valuation(S._p), 0) + v[kk] = ((a * ppow) % S._prec_cap) / ppow + except AttributeError: + pass + j_inverse = ~R(j) + lin_comb = M * coeffs[j - offset - 1] + forms.append(j_inverse * lin_comb) + coeffs[j - offset + 1] -= (d_mat_1 + j_inverse * d_mat_2) * lin_comb + + f = S(forms, offset + 1) + reduced = S._monsky_washnitzer(coeffs[-1 - offset :], -1) + # reduced = self - f.diff() + return f, reduced + + def reduce_pos_y(self): + r""" + Use homology relations to eliminate positive powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^3-4*x+4) + sage: x,y = C.monsky_washnitzer_gens() + sage: (y^2).diff().reduce_pos_y() + (y^2*1, 0 dx/2y) + sage: (y^2*x).diff().reduce_pos_y() + (y^2*x, 0 dx/2y) + sage: (y^92*x).diff().reduce_pos_y() + (y^92*x, 0 dx/2y) + sage: w = (y^3 + x).diff() + sage: w += w.parent()(x) + sage: w.reduce_pos_y_fast() + (y^3*1 + x, x dx/2y) + """ + S = self.parent().base_ring() + n = S.Q().degree() + f = S.zero() + reduced = self + for j in range(self.max_pow_y(), 0, -1): + for i in range(n - 1, -1, -1): + c = reduced.extract_pow_y(j)[i] + if c: + g = S.monomial(0, j + 1) if i == n - 1 else S.monomial(i + 1, j - 1) + dg = g.diff() + denom = dg.extract_pow_y(j)[i] + c /= denom + c = g.parent()(c) + f += c * g + reduced -= c * dg + + return f, reduced + + def reduce_pos_y_fast(self, even_degree_only=False): + r""" + Use homology relations to eliminate positive powers of `y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^3 - 4*x + 4) + sage: x, y = E.monsky_washnitzer_gens() + sage: y.diff().reduce_pos_y_fast() + (y*1, 0 dx/2y) + sage: (y^2).diff().reduce_pos_y_fast() + (y^2*1, 0 dx/2y) + sage: (y^2*x).diff().reduce_pos_y_fast() + (y^2*x, 0 dx/2y) + sage: (y^92*x).diff().reduce_pos_y_fast() + (y^92*x, 0 dx/2y) + sage: w = (y^3 + x).diff() + sage: w += w.parent()(x) + sage: w.reduce_pos_y_fast() + (y^3*1 + x, x dx/2y) + """ + S = self.parent().base_ring() + R = S.base_ring() + n = S.Q().degree() + + coeffs, offset = self.coeffs(R) + V = coeffs[0].parent() + zeroV = V(0) + forms = [V(0), V(0)] + + for j in range(self.max_pow_y(), -1, -1): + if (even_degree_only and j % 2) or (j > 0 and coeffs[j - offset].is_zero()): + forms.append(zeroV) + continue + + form = V(0) + i = n - 1 + c = coeffs[j - offset][i] + if c: + dg_coeffs = S.monomial_diff_coeffs(0, j + 1)[0] + c /= dg_coeffs[i] + forms[len(forms) - 2][0] = c + # self -= c d(y^{j+1}) + coeffs[j - offset] -= c * dg_coeffs + + if j == 0: + # the others are basis elements + break + + for i in range(n - 2, -1, -1): + c = coeffs[j - offset][i] + if c: + dg_coeffs = S.monomial_diff_coeffs(i + 1, j - 1) + denom = dg_coeffs[1][i] + c /= denom + form[i + 1] = c + # self -= c d(x^{i+1} y^{j-1}) + coeffs[j - offset] -= c * dg_coeffs[1] + coeffs[j - offset - 2] -= c * dg_coeffs[0] + forms.append(form) + + forms.reverse() + f = S(forms) + reduced = self.parent()(coeffs[: 1 - offset], offset) + return f, reduced + + def reduce(self): + r""" + Use homology relations to find `a` and `f` such that this element is + equal to `a + df`, where `a` is given in terms of the `x^i dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = (y*x).diff() + sage: w.reduce() + (y*x, 0 dx/2y) + + sage: w = x^4 * C.invariant_differential() + sage: w.reduce() + (1/5*y*1, 4/5*1 dx/2y) + + sage: w = sum(QQ.random_element() * x^i * y^j + ....: for i in [0..4] for j in [-3..3]) * C.invariant_differential() + sage: f, a = w.reduce() + sage: f.diff() + a - w + 0 dx/2y + """ + n = self.parent().base_ring().Q().degree() + f1, a = self.reduce_neg_y() + f2, a = a.reduce_pos_y() + f = f1 + f2 + + c = a.extract_pow_y(0)[n - 1] + if c: + x, y = self.parent().base_ring().gens() + g = y + dg = g.diff() + c = g.parent()(c / dg.extract_pow_y(0)[n - 1]) + f += c * g + a -= c * dg + + return f, a + + def reduce_fast(self, even_degree_only=False): + r""" + Use homology relations to find `a` and `f` such that this element is + equal to `a + df`, where `a` is given in terms of the `x^i dx/2y`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^3 - 4*x + 4) + sage: x, y = E.monsky_washnitzer_gens() + sage: x.diff().reduce_fast() + (x, (0, 0)) + sage: y.diff().reduce_fast() + (y*1, (0, 0)) + sage: (y^-1).diff().reduce_fast() + ((y^-1)*1, (0, 0)) + sage: (y^-11).diff().reduce_fast() + ((y^-11)*1, (0, 0)) + sage: (x*y^2).diff().reduce_fast() + (y^2*x, (0, 0)) + """ + f1, reduced = self.reduce_neg_y_fast(even_degree_only) + f2, reduced = reduced.reduce_pos_y_fast(even_degree_only) + # f1, reduced = self.reduce_neg_y() + # f2, reduced = reduced.reduce_pos_y() + v = reduced.extract_pow_y(0) + v.pop() + V = FreeModule(self.base_ring().base_ring(), len(v)) + return f1 + f2, V(v) + + def coeffs(self, R=None): + """ + Used to obtain the raw coefficients of a differential, see + :meth:`SpecialHyperellipticQuotientElement.coeffs` + + INPUT: + + - ``R`` -- an (optional) base ring in which to cast the coefficients + + OUTPUT: the raw coefficients of `A` where ``self`` is `A dx/2y` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: x,y = C.monsky_washnitzer_gens() + sage: w = C.invariant_differential() + sage: w.coeffs() + ([(1, 0, 0, 0, 0)], 0) + sage: (x*w).coeffs() + ([(0, 1, 0, 0, 0)], 0) + sage: (y*w).coeffs() + ([(0, 0, 0, 0, 0), (1, 0, 0, 0, 0)], 0) + sage: (y^-2*w).coeffs() + ([(1, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0)], -2) + """ + return self._coeff.coeffs(R) + + def coleman_integral(self, P, Q): + r""" + Compute the definite integral of ``self`` from `P` to `Q`. + + INPUT: + + - `P`, `Q` -- two points on the underlying curve + + OUTPUT: `\int_P^Q \text{self}` + + EXAMPLES:: + + sage: K = pAdicField(5,7) + sage: E = EllipticCurve(K,[-31/3,-2501/108]) #11a + sage: P = E(K(14/3), K(11/2)) + sage: w = E.invariant_differential() + sage: w.coleman_integral(P, 2*P) + O(5^6) + + sage: Q = E([3,58332]) + sage: w.coleman_integral(P,Q) + 2*5 + 4*5^2 + 3*5^3 + 4*5^4 + 3*5^5 + O(5^6) + sage: w.coleman_integral(2*P,Q) + 2*5 + 4*5^2 + 3*5^3 + 4*5^4 + 3*5^5 + O(5^6) + sage: (2*w).coleman_integral(P, Q) == 2*(w.coleman_integral(P, Q)) + True + """ + return self.parent().base_ring().curve().coleman_integral(self, P, Q) + + integrate = coleman_integral + + +class MonskyWashnitzerDifferentialRing(UniqueRepresentation, Module): + r""" + A ring of Monsky--Washnitzer differentials over ``base_ring``. + """ + + def __init__(self, base_ring): + r""" + Initialize ``self``. + + TESTS:: + + sage: R. = QQ['x'] + sage: E = HyperellipticCurveSmoothModel(x^5 - 3*x + 1) + sage: from sage.schemes.hyperelliptic_curves_smooth_model.monsky_washnitzer import SpecialHyperellipticQuotientRing, MonskyWashnitzerDifferentialRing + sage: S = SpecialHyperellipticQuotientRing(E) + sage: DR = MonskyWashnitzerDifferentialRing(S) + sage: TestSuite(DR).run() # needs sage.rings.real_interval_field + + Check that caching works:: + + sage: DR is MonskyWashnitzerDifferentialRing(S) + True + """ + Module.__init__(self, base_ring) + + def invariant_differential(self): + r""" + Return `dx/2y` as an element of ``self``. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.invariant_differential() + 1 dx/2y + """ + return self.element_class(self, 1) + + def base_extend(self, R): + """ + Return a new differential ring which is ``self`` base-extended to `R`. + + INPUT: + + - ``R`` -- ring + + OUTPUT: + + Self, base-extended to `R`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.base_ring() + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 4*x + 4) + over Rational Field + sage: MW.base_extend(Qp(5,5)).base_ring() # needs sage.rings.padics + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = (1 + O(5^5))*x^5 + + (1 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + O(5^5))*x + 4 + O(5^5)) + over 5-adic Field with capped relative precision 5 + """ + return MonskyWashnitzerDifferentialRing(self.base_ring().base_extend(R)) + + def change_ring(self, R): + """ + Return a new differential ring which is ``self`` with the coefficient + ring changed to `R`. + + INPUT: + + - ``R`` -- ring of coefficients + + OUTPUT: ``self`` with the coefficient ring changed to `R` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.base_ring() + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = x^5 - 4*x + 4) + over Rational Field + sage: MW.change_ring(Qp(5,5)).base_ring() # needs sage.rings.padics + SpecialHyperellipticQuotientRing K[x,y,y^-1] / (y^2 = (1 + O(5^5))*x^5 + + (1 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + O(5^5))*x + 4 + O(5^5)) + over 5-adic Field with capped relative precision 5 + """ + return MonskyWashnitzerDifferentialRing(self.base_ring().change_ring(R)) + + def degree(self): + """ + Return the degree of `Q(x)`, where the model of the underlying + hyperelliptic curve of ``self`` is given by `y^2 = Q(x)`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.Q() + x^5 - 4*x + 4 + sage: MW.degree() + 5 + """ + return self.base_ring().degree() + + def dimension(self): + """ + Return the dimension of ``self``. + + EXAMPLES:: + + sage: # needs sage.rings.padics + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: K = Qp(7,5) + sage: CK = C.change_ring(K) + sage: MW = CK.invariant_differential().parent() + sage: MW.dimension() + 4 + """ + return self.base_ring().degree() - 1 + + def Q(self): + """ + Return `Q(x)` where the model of the underlying hyperelliptic curve + of ``self`` is given by `y^2 = Q(x)`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.Q() + x^5 - 4*x + 4 + """ + return self.base_ring().Q() + + @cached_method + def x_to_p(self, p): + """ + Return and cache `x^p`, reduced via the relations coming from the + defining polynomial of the hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.x_to_p(3) + x^3 + sage: MW.x_to_p(5) + -(4-y^2)*1 + 4*x + sage: MW.x_to_p(101) is MW.x_to_p(101) + True + """ + return self.base_ring().x() ** p + + @cached_method + def frob_Q(self, p): + r""" + Return and cache `Q(x^p)`, which is used in computing the image of + `y` under a `p`-power lift of Frobenius to `A^{\dagger}`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.frob_Q(3) + -(60-48*y^2+12*y^4-y^6)*1 + (192-96*y^2+12*y^4)*x - (192-48*y^2)*x^2 + 60*x^3 + sage: MW.Q()(MW.x_to_p(3)) # needs sage.rings.real_interval_field + -(60-48*y^2+12*y^4-y^6)*1 + (192-96*y^2+12*y^4)*x - (192-48*y^2)*x^2 + 60*x^3 + sage: MW.frob_Q(11) is MW.frob_Q(11) + True + """ + return self.base_ring()._Q.change_ring(self.base_ring())(self.x_to_p(p)) + + def frob_invariant_differential(self, prec, p): + r""" + Kedlaya's algorithm allows us to calculate the action of Frobenius on + the Monsky-Washnitzer cohomology. First we lift `\phi` to `A^{\dagger}` + by setting + + .. MATH:: + + \phi(x) = x^p, + \qquad\qquad + \phi(y) = y^p \sqrt{1 + \frac{Q(x^p) - Q(x)^p}{Q(x)^p}}. + + Pulling back the differential `dx/2y`, we get + + .. MATH:: + + \phi^*(dx/2y) = px^{p-1} y(\phi(y))^{-1} dx/2y + = px^{p-1} y^{1-p} \sqrt{1+ \frac{Q(x^p) - Q(x)^p}{Q(x)^p}} dx/2y. + + Use Newton's method to calculate the square root. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: prec = 2 + sage: p = 7 + sage: MW = C.invariant_differential().parent() + sage: MW.frob_invariant_differential(prec, p) + ((67894400*y^-20-81198880*y^-18+40140800*y^-16-10035200*y^-14+1254400*y^-12-62720*y^-10)*1 + - (119503944*y^-20-116064242*y^-18+43753472*y^-16-7426048*y^-14+514304*y^-12-12544*y^-10+1568*y^-8-70*y^-6-7*y^-4)*x + + (78905288*y^-20-61014016*y^-18+16859136*y^-16-2207744*y^-14+250880*y^-12-37632*y^-10+3136*y^-8-70*y^-6)*x^2 + - (39452448*y^-20-26148752*y^-18+8085490*y^-16-2007040*y^-14+376320*y^-12-37632*y^-10+1568*y^-8)*x^3 + + (21102144*y^-20-18120592*y^-18+8028160*y^-16-2007040*y^-14+250880*y^-12-12544*y^-10)*x^4) dx/2y + """ + prof = Profiler() + prof("setup") + # TODO, would it be useful to be able to take Frobenius of any element? Less efficient? + x, y = self.base_ring().gens() + prof("x_to_p") + x_to_p_less_1 = x ** (p - 1) + x_to_p = x * x_to_p_less_1 + + # cache for future use + self.x_to_p.set_cache(p, x_to_p) + + prof("frob_Q") + a = self.frob_Q(p) >> 2 * p # frobQ * y^{-2p} + + prof("sqrt") + + # Q = self.base_ring()._Q + # three_halves = Q.parent().base_ring()(Rational((3,2))) + # one_half = Q.parent().base_ring()(Rational((1,2))) + three_halves = self.base_ring()._series_ring.base_ring()(Rational((3, 2))) + one_half = self.base_ring()._series_ring.base_ring()(Rational((1, 2))) + half_a = a._rmul_(one_half) + + # We are solving for t = a^{-1/2} = (F_pQ y^{-p})^{-1/2} + # Newton's method converges because we know the root is in the same residue class as 1. + + # t = self.base_ring()(1) + t = self.base_ring()(three_halves) - half_a + # first iteration trivial, start with prec 2 + + for cur_prec in newton_method_sizes(prec)[2:]: + # newton_method_sizes = [1, 2, ...] + y_prec = -(2 * cur_prec - 1) * p + 1 + # binomial expansion is $\sum p^{k+1} y^{-(2k+1)p+1} f(x)$ + # so if we are only correct mod p^prec, + # can ignore y powers less than y_prec + t_cube = (t * t * t).truncate_neg(y_prec) + t = t._rmul_(three_halves) - (half_a * t_cube).truncate_neg(y_prec) + # t = (3/2) t - (1/2) a t^3 + + prof("compose") + F_dx_y = (p * x_to_p_less_1 * t) >> (p - 1) # px^{p-1} sqrt(a) * y^{-p+1} + + prof("done") + return MonskyWashnitzerDifferential(self, F_dx_y) + + def frob_basis_elements(self, prec, p): + r""" + Return the action of a `p`-power lift of Frobenius on the basis. + + .. MATH:: + + \{ dx/2y, x dx/2y, ..., x^{d-2} dx/2y \}, + + where `d` is the degree of the underlying hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: prec = 1 + sage: p = 5 + sage: MW = C.invariant_differential().parent() + sage: MW.frob_basis_elements(prec, p) + [((92000*y^-14-74200*y^-12+32000*y^-10-8000*y^-8+1000*y^-6-50*y^-4)*1 + - (194400*y^-14-153600*y^-12+57600*y^-10-9600*y^-8+600*y^-6)*x + + (204800*y^-14-153600*y^-12+38400*y^-10-3200*y^-8)*x^2 + - (153600*y^-14-76800*y^-12+9600*y^-10)*x^3 + + (63950*y^-14-18550*y^-12+1600*y^-10-400*y^-8+50*y^-6+5*y^-4)*x^4) dx/2y, + (-(1391200*y^-14-941400*y^-12+302000*y^-10-76800*y^-8+14400*y^-6-1320*y^-4+30*y^-2)*1 + + (2168800*y^-14-1402400*y^-12+537600*y^-10-134400*y^-8+16800*y^-6-720*y^-4)*x + - (1596800*y^-14-1433600*y^-12+537600*y^-10-89600*y^-8+5600*y^-6)*x^2 + + (1433600*y^-14-1075200*y^-12+268800*y^-10-22400*y^-8)*x^3 + - (870200*y^-14-445350*y^-12+63350*y^-10-3200*y^-8+600*y^-6-30*y^-4-5*y^-2)*x^4) dx/2y, + ((19488000*y^-14-15763200*y^-12+4944400*y^-10-913800*y^-8+156800*y^-6-22560*y^-4+1480*y^-2-10)*1 + - (28163200*y^-14-18669600*y^-12+5774400*y^-10-1433600*y^-8+268800*y^-6-25440*y^-4+760*y^-2)*x + + (15062400*y^-14-12940800*y^-12+5734400*y^-10-1433600*y^-8+179200*y^-6-8480*y^-4)*x^2 + - (12121600*y^-14-11468800*y^-12+4300800*y^-10-716800*y^-8+44800*y^-6)*x^3 + + (9215200*y^-14-6952400*y^-12+1773950*y^-10-165750*y^-8+5600*y^-6-720*y^-4+10*y^-2+5)*x^4) dx/2y, + (-(225395200*y^-14-230640000*y^-12+91733600*y^-10-18347400*y^-8+2293600*y^-6-280960*y^-4+31520*y^-2-1480-10*y^2)*1 + + (338048000*y^-14-277132800*y^-12+89928000*y^-10-17816000*y^-8+3225600*y^-6-472320*y^-4+34560*y^-2-720)*x + - (172902400*y^-14-141504000*y^-12+58976000*y^-10-17203200*y^-8+3225600*y^-6-314880*y^-4+11520*y^-2)*x^2 + + (108736000*y^-14-109760000*y^-12+51609600*y^-10-12902400*y^-8+1612800*y^-6-78720*y^-4)*x^3 + - (85347200*y^-14-82900000*y^-12+31251400*y^-10-5304150*y^-8+367350*y^-6-8480*y^-4+760*y^-2+10-5*y^2)*x^4) dx/2y] + """ + F_i = self.frob_invariant_differential(prec, p) + x_to_p = self.x_to_p(p) + F = [F_i] + for i in range(1, self.degree() - 1): + F_i *= x_to_p + F.append(F_i) + return F + + def helper_matrix(self): + r""" + We use this to solve for the linear combination of + `x^i y^j` needed to clear all terms with `y^{j-1}`. + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW.helper_matrix() + [ 256/2101 320/2101 400/2101 500/2101 625/2101] + [-625/8404 -64/2101 -80/2101 -100/2101 -125/2101] + [-125/2101 -625/8404 -64/2101 -80/2101 -100/2101] + [-100/2101 -125/2101 -625/8404 -64/2101 -80/2101] + [ -80/2101 -100/2101 -125/2101 -625/8404 -64/2101] + """ + try: + return self._helper_matrix + except AttributeError: + pass + + # The smallest y term of (1/j) d(x^i y^j) is constant for all j. + x, y = self.base_ring().gens() + n = self.degree() + L = [(y * x**i).diff().extract_pow_y(0) for i in range(n)] + A = matrix(L).transpose() + if A.base_ring() not in IntegralDomains(): + # must be using integer_mod or something to approximate + self._helper_matrix = (~A.change_ring(QQ)).change_ring(A.base_ring()) + else: + self._helper_matrix = ~A + return self._helper_matrix + + def _element_constructor_(self, val=0, offset=0): + r""" + Construct an element of ``self``. + + INPUT: + + - ``parent`` -- Monsky-Washnitzer differential ring (instance of class + :class:`~MonskyWashnitzerDifferentialRing` + - ``val`` -- element of the base ring, or list of coefficients + - ``offset`` -- (default: 0) if nonzero, shift val by `y^\text{offset}` + + EXAMPLES:: + + sage: R. = QQ['x'] + sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) + sage: MW = C.invariant_differential().parent() + sage: MW(3) + 3*1 dx/2y + """ + if isinstance(val, MonskyWashnitzerDifferential): + val = val._coeff + return self.element_class(self, val, offset) + + Element = MonskyWashnitzerDifferential + + +MonskyWashnitzerDifferentialRing_class = MonskyWashnitzerDifferentialRing diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py new file mode 100644 index 00000000000..ea34d940f6b --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py @@ -0,0 +1,12 @@ +from sage.schemes.curves.curve import Curve_generic + + +class WeightedProjectiveCurve(Curve_generic): + def __init__(self, A, X, *kwargs): + # TODO ensure that A is the right type? + # Something like a `is_WeightProjectiveSpace` which means making a + # WeightProjectiveSpace class? + super().__init__(A, X, *kwargs) + + def curve(self): + return diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py new file mode 100644 index 00000000000..84cdf7d028d --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py @@ -0,0 +1,46 @@ +""" +Hom-sets of weighted projective schemes + +AUTHORS: + +- Gareth Ma (2024): initial version, based on unweighted version. +""" + +# ***************************************************************************** +# Copyright (C) 2024 Gareth Ma +# Copyright (C) 2011 Volker Braun +# Copyright (C) 2006 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.schemes.generic.homset import SchemeHomset_points + + +class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): + """ + Set of rational points of a weighted projective variety over a ring. + + INPUT: + + See :class:`SchemeHomset_generic`. + + EXAMPLES:: + + sage: # TODO + """ + + def points(self, **__): + """ + Return some or all rational points of this weighted projective scheme. + + For dimension 0 subschemes points are determined through a groebner + basis calculation. For schemes or subschemes with dimension greater than 1 + points are determined through enumeration up to the specified bound. + + TODO: modify implementation from projective space + """ + raise NotImplementedError("enumerating points on weighted projective scheme is not implemented") diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py new file mode 100644 index 00000000000..baab433e8cf --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py @@ -0,0 +1,390 @@ +r""" +Points on projective schemes + +This module implements scheme morphism for points on weighted projective schemes. + +AUTHORS: + +- Gareth Ma (2024): initial version, based on unweighted version. +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# Copyright (C) 2006 William Stein +# Copyright (C) 2011 Volker Braun +# Copyright (C) 2024 Gareth Ma +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from copy import copy + +from sage.categories.integral_domains import IntegralDomains +from sage.rings.fraction_field import FractionField +from sage.schemes.generic.morphism import ( + SchemeMorphism, + SchemeMorphism_point, + is_SchemeMorphism, +) +from sage.structure.richcmp import op_EQ, op_NE, richcmp +from sage.structure.sequence import Sequence + +# -------------------- +# Projective varieties +# -------------------- + +class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): + """ + A rational point of projective space over a ring. + + INPUT: + + - ``X`` -- a homset of a subscheme of an ambient projective space over a ring `K`. + + - ``v`` -- a list or tuple of coordinates in `K`. + + - ``check`` -- boolean (default:``True``). Whether to check the input for consistency. + + EXAMPLES:: + + sage: P = ProjectiveSpace(2, ZZ) + sage: P(2,3,4) + (2 : 3 : 4) + """ + + def __init__(self, X, v, check=True): + """ + The Python constructor. + + EXAMPLES:: + + TODO + """ + SchemeMorphism.__init__(self, X) + + if check: + # check parent + from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_homset import ( + SchemeHomset_points_weighted_projective_ring, + ) + if not isinstance(X, SchemeHomset_points_weighted_projective_ring): + raise TypeError(f"ambient space {X} must be a weighted projective space") + + from sage.rings.ring import CommutativeRing + from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field + d = X.codomain().ambient_space().ngens() + if isinstance(v, SchemeMorphism) or isinstance(v, EllipticCurvePoint_field): + v = list(v) + else: + try: + if isinstance(v.parent(), CommutativeRing): + v = [v] + except AttributeError: + pass + if not isinstance(v, (list, tuple)): + raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) + if len(v) != d and len(v) != d-1: + raise TypeError("v (=%s) must have %s components" % (v, d)) + + R = X.value_ring() + v = Sequence(v, R) + if len(v) == d-1: # very common special case + v.append(R.one()) + + if R in IntegralDomains(): + # Over integral domains, any tuple with at least one + # non-zero coordinate is a valid projective point. + if not any(v): + raise ValueError(f"{v} does not define a valid projective " + "point since all entries are zero") + # Over rings with zero divisors, a more careful check + # is required: We test whether the coordinates of the + # point generate the unit ideal. See #31576. + elif 1 not in R.ideal(v): + raise ValueError(f"{v} does not define a valid projective point " + "since it is a multiple of a zero divisor") + + X.extended_codomain()._check_satisfies_equations(v) + + self._coords = tuple(v) + self._normalized = False + + def _repr_(self): + return "({})".format(" : ".join(map(repr, self._coords))) + + def _richcmp_(self, other, op): + """ + Test the projective equality of two points. + + INPUT: + + - ``right`` -- a point on projective space + + OUTPUT: + + Boolean + + EXAMPLES:: + + TODO + """ + assert isinstance(other, SchemeMorphism_point) + + if self.codomain() != other.codomain(): + return op == op_NE + + n = len(self._coords) + if op in [op_EQ, op_NE]: + b = all(self[i] * other[j] == self[j] * other[i] + for i in range(n) for j in range(i + 1, n)) + return b == (op == op_EQ) + return richcmp(self._coords, other._coords, op) + + def __hash__(self): + """ + Compute the hash value of this point. + + If the base ring has a fraction field, normalize the point in + the fraction field and then hash so that equal points have + equal hash values. If the base ring is not an integral domain, + return the hash of the parent. + + OUTPUT: Integer. + + EXAMPLES:: + + sage: P. = ProjectiveSpace(ZZ, 1) + sage: hash(P([1, 1])) == hash(P.point([2, 2], False)) + True + + :: + + sage: # needs sage.rings.number_field + sage: R. = PolynomialRing(QQ) + sage: K. = NumberField(x^2 + 3) + sage: O = K.maximal_order() + sage: P. = ProjectiveSpace(O, 1) + sage: hash(P([1 + w, 2])) == hash(P([2, 1 - w])) + True + + TESTS:: + + sage: P. = ProjectiveSpace(Zmod(10), 1) + sage: Q. = ProjectiveSpace(Zmod(10), 1) + sage: hash(P([2, 5])) == hash(Q([2, 5])) + True + sage: hash(P([2, 5])) == hash(P([2, 5])) + True + sage: hash(P([3, 7])) == hash(P([2, 5])) + True + """ + R = self.codomain().base_ring() + #if there is a fraction field normalize the point so that + #equal points have equal hash values + if R in IntegralDomains(): + P = self.change_ring(FractionField(R)) + P.normalize_coordinates() + return hash(tuple(P)) + #if there is no good way to normalize return + #a constant value + return hash(self.codomain()) + +class SchemeMorphism_point_weighted_projective_field(SchemeMorphism_point_weighted_projective_ring): + """ + A rational point of projective space over a field. + + INPUT: + + - ``X`` -- a homset of a subscheme of an ambient projective space + over a field `K`. + + - ``v`` -- a list or tuple of coordinates in `K`. + + - ``check`` -- boolean (default:``True``). Whether to + check the input for consistency. + + EXAMPLES:: + + sage: # needs sage.rings.real_mpfr + sage: P = ProjectiveSpace(3, RR) + sage: P(2, 3, 4, 5) + (0.400000000000000 : 0.600000000000000 : 0.800000000000000 : 1.00000000000000) + """ + + def __init__(self, X, v, check=True): + """ + The Python constructor. + + See :class:`SchemeMorphism_point_projective_ring` for details. + + This function still normalizes points so that the rightmost non-zero coordinate is 1. + This is to maintain functionality with current + implementations of curves in projectives space (plane, conic, elliptic, etc). + The :class:`SchemeMorphism_point_projective_ring` is for general use. + + EXAMPLES:: + + sage: P = ProjectiveSpace(2, QQ) + sage: P(2, 3/5, 4) + (1/2 : 3/20 : 1) + + :: + + sage: P = ProjectiveSpace(3, QQ) + sage: P(0, 0, 0, 0) + Traceback (most recent call last): + ... + ValueError: [0, 0, 0, 0] does not define a valid projective point since all entries are zero + + :: + + sage: P. = ProjectiveSpace(2, QQ) + sage: X = P.subscheme([x^2 - y*z]) + sage: X([2, 2, 2]) + (1 : 1 : 1) + + :: + + sage: P = ProjectiveSpace(1, GF(7)) + sage: Q = P([2, 1]) + sage: Q[0].parent() + Finite Field of size 7 + + :: + + sage: P = ProjectiveSpace(QQ, 1) + sage: P.point(Infinity) + (1 : 0) + sage: P(infinity) + (1 : 0) + + :: + + sage: P = ProjectiveSpace(QQ, 2) + sage: P(infinity) + Traceback (most recent call last): + ... + ValueError: +Infinity not well defined in dimension > 1 + sage: P.point(infinity) + Traceback (most recent call last): + ... + ValueError: +Infinity not well defined in dimension > 1 + """ + SchemeMorphism.__init__(self, X) + + self._normalized = False + + if check: + from sage.rings.ring import CommutativeRing + from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field + d = X.codomain().ambient_space().ngens() + if is_SchemeMorphism(v) or isinstance(v, EllipticCurvePoint_field): + v = list(v) + else: + try: + if isinstance(v.parent(), CommutativeRing): + v = [v] + except AttributeError: + pass + if not isinstance(v, (list,tuple)): + raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) + if len(v) != d and len(v) != d-1: + raise TypeError("v (=%s) must have %s components" % (v, d)) + + R = X.value_ring() + v = Sequence(v, R) + if len(v) == d-1: # very common special case + v.append(R.one()) + + for last in reversed(range(len(v))): + c = v[last] + if c.is_one(): + break + if c: + for j in range(last): + v[j] /= c + v[last] = R.one() + break + else: + raise ValueError(f"{v} does not define a valid projective " + "point since all entries are zero") + self._normalized = True + + X.extended_codomain()._check_satisfies_equations(v) + + self._coords = tuple(v) + + def __hash__(self): + """ + Computes the hash value of this point. + + OUTPUT: Integer. + + EXAMPLES:: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: hash(P([1/2, 1])) == hash(P.point([1, 2], False)) + True + """ + P = copy(self) + P.normalize_coordinates() + return hash(tuple(P)) + + def normalize_coordinates(self): + r""" + Normalizes the point so that the last non-zero coordinate is `1`. + + OUTPUT: None. + + EXAMPLES:: + + sage: P. = ProjectiveSpace(GF(5), 2) + sage: Q = P.point([GF(5)(1), GF(5)(3), GF(5)(0)], False); Q + (1 : 3 : 0) + sage: Q.normalize_coordinates(); Q + (2 : 1 : 0) + + :: + + sage: P. = ProjectiveSpace(QQ, 2) + sage: X = P.subscheme(x^2 - y^2); + sage: Q = X.point([23, 23, 46], False); Q + (23 : 23 : 46) + sage: Q.normalize_coordinates(); Q + (1/2 : 1/2 : 1) + """ + if self._normalized: + return + for index in reversed(range(len(self._coords))): + c = self._coords[index] + if c.is_one(): + break + if c: + inv = c.inverse() + new_coords = [d * inv for d in self._coords[:index]] + new_coords.append(self.base_ring().one()) + new_coords.extend(self._coords[index+1:]) + self._coords = tuple(new_coords) + break + else: + assert False, 'bug: invalid projective point' + self._normalized = True + + +class SchemeMorphism_point_weighted_projective_finite_field(SchemeMorphism_point_weighted_projective_field): + + def __hash__(self): + r""" + Returns the integer hash of this point. + + OUTPUT: Integer. + + EXAMPLES: TODO + """ + p = self.codomain().base_ring().order() + N = self.codomain().ambient_space().dimension_relative() + return hash(sum(hash(self[i]) * p**i for i in range(N + 1))) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py new file mode 100644 index 00000000000..810adc6e7d4 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py @@ -0,0 +1,371 @@ +from sage.misc.latex import latex +from sage.misc.prandom import shuffle +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.term_order import TermOrder +from sage.schemes.generic.ambient_space import AmbientSpace +from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_homset import ( + SchemeHomset_points_weighted_projective_ring, +) +from sage.schemes.projective.projective_space import ProjectiveSpace, _CommRings +from sage.structure.all import UniqueRepresentation +from sage.structure.category_object import normalize_names + + +def WeightedProjectiveSpace(weights, R=None, names=None): + r""" + Return a weighted projective space with the given ``weights`` over the ring ``R``. + + EXAMPLES:: + + sage: # TODO: add example of point on this space (it doesn't work right now) + sage: WP = WeightedProjectiveSpace([1, 3, 1]); WP + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + """ + if ( + isinstance(weights, (MPolynomialRing_base, PolynomialRing_general)) + and R is None + ): + if names is not None: + # Check for the case that the user provided a variable name + # That does not match what we wanted to use from R + names = normalize_names(weights.ngens(), names) + if weights.variable_names() != names: + # The provided name doesn't match the name of R's variables + raise NameError( + "variable names passed to ProjectiveSpace conflict with names in ring" + ) + A = WeightedProjectiveSpace( + weights.ngens() - 1, weights.base_ring(), names=weights.variable_names() + ) + A._coordinate_ring = weights + return A + + if isinstance(R, (int, Integer, list, tuple)): + weights, R = R, weights + elif R is None: + R = ZZ + + # WeightedProjectiveSpace(5) -> just return unweighted version + if isinstance(weights, (int, Integer)): + return ProjectiveSpace(weights, R=R, names=names) + elif isinstance(weights, (list, tuple)): + # Make it hashable + weights = tuple(map(Integer, weights)) + if any(w <= 0 for w in weights): + raise TypeError( + f"weights(={weights}) should only consist of positive integers" + ) + else: + raise TypeError(f"weights={weights} must be an integer, a list or a tuple") + + if names is None: + names = "x" + + # TODO: Specialise implementation to projective spaces over non-rings. But + # since we don't really implement extra functionalities, I don't think we + # care. + if R in _CommRings: + return WeightedProjectiveSpace_ring(weights, R=R, names=names) + + raise TypeError(f"R (={R}) must be a commutative ring") + + +class WeightedProjectiveSpace_ring(UniqueRepresentation, AmbientSpace): + @staticmethod + def __classcall__(cls, weights: tuple[Integer], R=ZZ, names=None): + # __classcall_ is the "preprocessing" step for UniqueRepresentation + # see docs of CachedRepresentation + # weights should be a tuple, also because it should be hashable + if not isinstance(weights, tuple): + raise TypeError( + f"weights(={weights}) is not a tuple. Please use the `WeightedProjectiveSpace`" + " constructor" + ) + + # TODO: Do we normalise the weights to make it coprime? + normalized_names = normalize_names(len(weights), names) + return super().__classcall__(cls, weights, R, normalized_names) + + def __init__(self, weights: tuple[Integer], R=ZZ, names=None): + """ + Initialization function. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(Zp(5), [1, 3, 1], 'y') # needs sage.rings.padics + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over 5-adic Ring with + capped relative precision 20 + sage: WeightedProjectiveSpace(QQ, 5, 'y') + Projective Space of dimension 5 over Rational Field + sage: _ is ProjectiveSpace(QQ, 5, 'y') + True + """ + AmbientSpace.__init__(self, len(weights) - 1, R) + self._weights = weights + self._assign_names(names) + + def weights(self) -> tuple[Integer]: + """ + Return the tuple of weights of this weighted projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(QQ, [1, 3, 1]).weights() + (1, 3, 1) + """ + return self._weights + + def ngens(self) -> Integer: + """ + Return the number of generators of this weighted projective space. + + This is the number of variables in the coordinate ring of ``self``. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(QQ, [1, 3, 1]).ngens() + 3 + sage: WeightedProjectiveSpace(ZZ, 5).ngens() + 6 + """ + return self.dimension_relative() + 1 + + def _check_satisfies_equations(self, v: list[Integer] | tuple[Integer]) -> bool: + """ + Return ``True`` if ``v`` defines a point on the weighted projective + plane; raise a :class:`TypeError` otherwise. + + EXAMPLES:: + + sage: P = WeightedProjectiveSpace(ZZ, [1, 3, 1]) + sage: P._check_satisfies_equations([1, 1, 0]) + True + + sage: P._check_satisfies_equations([1, 0]) + Traceback (most recent call last): + ... + TypeError: the list v=[1, 0] must have 3 components + + sage: P._check_satisfies_equations([1/2, 0, 1]) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + """ + if not isinstance(v, (list, tuple)): + raise TypeError(f"the argument v={v} must be a list or tuple") + + n = self.ngens() + if len(v) != n: + raise TypeError(f"the list v={v} must have {n} components") + + v = list(map(self.base_ring(), v)) + if all(vi.is_zero() for vi in v): + raise TypeError("the zero vector is not a point in projective space") + + return True + + def coordinate_ring(self) -> PolynomialRing_general: + """ + Return the coordinate ring of this weighted projective space. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(GF(19^2, 'α'), [1, 3, 4, 1], 'abcd') + sage: # needs sage.rings.finite_rings + sage: R = WP.coordinate_ring(); R + Multivariate Polynomial Ring in a, b, c, d over Finite Field in α of size 19^2 + sage: R.term_order() + Weighted degree reverse lexicographic term order with weights (1, 3, 4, 1) + + :: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 1, 1], ['alpha', 'beta', 'gamma']) + sage: R = WP.coordinate_ring(); R + Multivariate Polynomial Ring in alpha, beta, gamma over Rational Field + sage: R.term_order() + Weighted degree reverse lexicographic term order with weights (1, 1, 1) + """ + if not hasattr(self, "_coordinate_ring"): + term_order = TermOrder("wdegrevlex", self.weights()) + self._coordinate_ring = PolynomialRing( + self.base_ring(), + self.dimension_relative() + 1, + names=self.variable_names(), + order=term_order, + ) + return self._coordinate_ring + + def _validate(self, polynomials): + """ + If ``polynomials`` is a tuple of valid polynomial functions on ``self``, + return ``polynomials``, otherwise raise ``TypeError``. + + Since this is a weighted projective space, polynomials must be + homogeneous with respect to the grading of this space. + + INPUT: + + - ``polynomials`` -- tuple of polynomials in the coordinate ring of + this space. + + OUTPUT: + + - tuple of polynomials in the coordinate ring of this space. + + EXAMPLES:: + + sage: P. = WeightedProjectiveSpace(QQ, [1, 3, 1]) + sage: P._validate([x*y - z^4, x]) + [x*y - z^4, x] + sage: P._validate([x*y - z^2, x]) + Traceback (most recent call last): + ... + TypeError: x*y - z^2 is not homogeneous with weights (1, 3, 1) + sage: P._validate(x*y - z) + Traceback (most recent call last): + ... + TypeError: the argument polynomials=x*y - z must be a list or tuple + """ + if not isinstance(polynomials, (list, tuple)): + raise TypeError( + f"the argument polynomials={polynomials} must be a list or tuple" + ) + + R = self.coordinate_ring() + for f in map(R, polynomials): + if not f.is_homogeneous(): + raise TypeError(f"{f} is not homogeneous with weights {self.weights()}") + + return polynomials + + def _latex_(self): + r""" + Return a LaTeX representation of this projective space. + + EXAMPLES:: + + sage: print(latex(WeightedProjectiveSpace(ZZ, [1, 3, 1], 'x'))) + {\mathbf P}_{\Bold{Z}}^{[1, 3, 1]} + + TESTS:: + + sage: WeightedProjectiveSpace(Zp(5), [2, 1, 3], 'y')._latex_() # needs sage.rings.padics + '{\\mathbf P}_{\\Bold{Z}_{5}}^{[2, 1, 3]}' + """ + return ( + f"{{\\mathbf P}}_{{{latex(self.base_ring())}}}^{{{list(self.weights())}}}" + ) + + def _morphism(self, *_, **__): + """ + Construct a morphism. + + For internal use only. See :mod:`morphism` for details. + """ + raise NotImplementedError( + "_morphism not implemented for weighted projective space" + ) + + def _homset(self, *_, **__): + """ + Construct the Hom-set. + """ + raise NotImplementedError( + "_homset not implemented for weighted projective space" + ) + + def _point_homset(self, *args, **kwds): + """ + Construct a point Hom-set. + + For internal use only. See :mod:`morphism` for details. + """ + # raise NotImplementedError("_point_homset not implemented for weighted projective space") + return SchemeHomset_points_weighted_projective_ring(*args, **kwds) + + def point(self, v, check=True): + """ + Create a point on this weighted projective space. + + INPUT: + + INPUT: + + - ``v`` -- anything that defines a point + + - ``check`` -- boolean (default: ``True``); whether + to check the defining data for consistency + + OUTPUT: A point of this weighted projective space. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 3, 1]) + sage: WP.point([2, 3, 1]) + (2 : 3 : 1) + """ + from sage.rings.infinity import infinity + + if v is infinity or ( + isinstance(v, (list, tuple)) and len(v) == 1 and v[0] is infinity + ): + if self.dimension_relative() > 1: + raise ValueError("%s not well defined in dimension > 1" % v) + v = [1, 0] + + return self.point_homset()(v, check=check) + + def _point(self, *args, **kwds): + """ + Construct a point. + + For internal use only. See :mod:`morphism` for details. + """ + from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, + ) + return SchemeMorphism_point_weighted_projective_ring(*args, **kwds) + + def _repr_(self) -> str: + """ + Return a string representation of this projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(Qp(5), [1, 3, 1], 'x') # needs sage.rings.padics + Weighted Projective Space of dimension 2 with weights (1, 3, 1) + over 5-adic Field with capped relative precision 20 + """ + return ( + f"Weighted Projective Space of dimension {self.dimension_relative()} with weights" + f" {self.weights()} over {self.base_ring()}" + ) + + def _an_element_(self): + r""" + Return a (preferably typical) element of this space. + + This is used both for illustration and testing purposes. + + OUTPUT: a point in this projective space. + + EXAMPLES:: + + sage: # TODO: Enable this + sage: WeightedProjectiveSpace(ZZ, [1, 3, 1], 'x').an_element() # random + (7 : 6 : 5 : 1) + sage: WeightedProjectiveSpace(ZZ["y"], [2, 3, 1], 'x').an_element() # random + (7*y : 6*y : 5*y : 1) + """ + n = self.dimension_relative() + R = self.base_ring() + coords = [(n + 1 - i) * R.an_element() for i in range(n)] + [R.one()] + shuffle(coords) + return self(coords) + + def subscheme(self, *_, **__): + raise NotImplementedError("subscheme of weighted projective space has not been implemented") diff --git a/src/sage/schemes/meson.build b/src/sage/schemes/meson.build index c74c532b930..24807ecf335 100644 --- a/src/sage/schemes/meson.build +++ b/src/sage/schemes/meson.build @@ -7,6 +7,7 @@ install_subdir('cyclic_covers', install_dir: sage_install_dir / 'schemes') subdir('elliptic_curves') install_subdir('generic', install_dir: sage_install_dir / 'schemes') subdir('hyperelliptic_curves') +subdir('hyperelliptic_curves_smooth_model') install_subdir('jacobians', install_dir: sage_install_dir / 'schemes') install_subdir('plane_conics', install_dir: sage_install_dir / 'schemes') install_subdir('plane_quartics', install_dir: sage_install_dir / 'schemes') From 7383da5a9aa4b4657083e7fb001cfd8a5d8676e6 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 5 Dec 2024 16:25:34 +0000 Subject: [PATCH 11/56] some proof reading --- .../hyperelliptic_finite_field.py | 47 +++++----- .../hyperelliptic_generic.py | 86 +++++++++---------- 2 files changed, 64 insertions(+), 69 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index 46153a2a0a7..7f1e9034a5c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -68,7 +68,7 @@ def rational_points_iterator(self): .. SEEALSO:: :meth:`points` """ - # TODO: this is very silly but works + # NOTE: this is a very naive implementation yield from self.points_at_infinity() for x in self.base_ring(): yield from self.lift_x(x, all=True) @@ -161,7 +161,7 @@ def count_points_matrix_traces(self, n=1, M=None, N=None): t = [] Mpow = 1 - for i in range(n): + for _ in range(n): Mpow *= M t.append(Mpow.trace()) @@ -362,10 +362,10 @@ def count_points_hypellfrob(self, n=1, N=None, algorithm=None): else: raise ValueError("Unknown algorithm") - if p <= (2 * g + 1) * (2 * N - 1): + lower_bound = (2 * g + 1) * (2 * N - 1) + if p <= lower_bound: raise ValueError( - "p=%d should be greater than (2*g+1)(2*N-1)=%d" - % (p, (2 * g + 1) * (2 * N - 1)) + f"p={p} should be greater than (2*g+1)(2*N-1)={lower_bound}" ) if algorithm == "traces": @@ -461,7 +461,7 @@ def count_points(self, n=1): # No smart method available return self.count_points_exhaustive(n) - def cardinality_exhaustive(self, extension_degree=1, algorithm=None): + def cardinality_exhaustive(self, extension_degree=1): r""" Count points on a single extension of the base field by enumerating over x and solving the resulting quadratic @@ -514,10 +514,6 @@ def cardinality_exhaustive(self, extension_degree=1, algorithm=None): g = self.genus() n = extension_degree - if g == 0: - # here is the projective line - return K.cardinality() ** n + 1 - f, h = self.hyperelliptic_polynomials() a = 0 @@ -542,6 +538,7 @@ def cardinality_exhaustive(self, extension_degree=1, algorithm=None): # For the affine points with given x-coordinate, # solve y^2 + h(x)*y == f(x). + # Handle the special case for char 2 if K.characteristic() == 2: # points at infinity r = h[g + 1] @@ -558,21 +555,21 @@ def cardinality_exhaustive(self, extension_degree=1, algorithm=None): a += 1 elif (fext(x) / r**2).trace() == 0: a += 2 - else: - # points at infinity - d = h[g + 1] ** 2 + 4 * f[2 * g + 2] + return a + + # points at infinity + d = h[g + 1] ** 2 + 4 * f[2 * g + 2] + if not d: + a += 1 + elif n % 2 == 0 or d.is_square(): + a += 2 + # affine points + for x in L: + d = hext(x) ** 2 + 4 * fext(x) if not d: a += 1 - elif n % 2 == 0 or d.is_square(): + elif d.is_square(): a += 2 - # affine points - for x in L: - d = hext(x) ** 2 + 4 * fext(x) - if not d: - a += 1 - elif d.is_square(): - a += 2 - return a def cardinality_hypellfrob(self, extension_degree=1, algorithm=None): @@ -743,8 +740,8 @@ def _frobenius_coefficient_bound_charpoly(self): sqrtq = RR(q).sqrt() g = self.genus() - # note: this bound is from Kedlaya's paper, but he tells me it's not - # the best possible + # NOTE: this bound is from Kedlaya's paper, but he tells me it's not + # the best possible -- David Harvey M = 2 * binomial(2 * g, g) * sqrtq**g B = ZZ(M.ceil()).exact_log(p) if p**B < M: @@ -1318,8 +1315,8 @@ def _Cartier_matrix_cached(self): # Compute the finite field and prime p. Fq = self.base_ring() p = Fq.characteristic() + # checks - if p == 2: raise ValueError("p must be odd") diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 47a4de64469..abaea2adbd7 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -255,7 +255,13 @@ def infinite_polynomials(self): """ TODO: the name of this function could be better? - Computes G^±(x) for curves in the split degree model + Computes G^±(x) for curves in the split degree model used for + Cantor composition with points at infinity when performing + arithmetic with the Jac(H). + + .. SEEALSO:: + + :func:`~sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_split.cantor_compose_at_infinity` """ if hasattr(self, "_infinite_polynomials"): return self._infinite_polynomials @@ -282,6 +288,7 @@ def infinite_polynomials(self): G_plus = self._polynomial_ring(g) G_minus = -G_plus - h + # Checks for the assumptions on G^± genus = self.genus() assert G_plus.degree() <= (genus + 1) @@ -296,8 +303,7 @@ def points_at_infinity(self): Compute the points at infinity on the curve. Assumes we are using a weighted projective model for the curve """ - # TODO: check to False? - return [self.point([1, y, 0], check=True) for y in self.roots_at_infinity()] + return [self.point([1, y, 0], check=False) for y in self.roots_at_infinity()] def is_x_coord(self, x): """ @@ -393,14 +399,17 @@ def is_x_coord(self, x): if not h: y2 = f(x) return y2.is_square() + # Generic case for h != 0 a = f(x) b = h(x) + # Special case for char 2 if K.characteristic() == 2: R = f.parent() # Polynomial ring K[x] F = R([-a, b, 1]) return bool(F.roots()) + # Otherwise x is a point on the curve if the discriminant is a square D = b * b + 4 * a return D.is_square() @@ -646,7 +655,6 @@ def is_weierstrass_point(self, P): (1 + 3*5^2 + O(5^8) : 3*5 + 4*5^2 + 5^4 + 3*5^5 + 5^6 + O(5^7) : 1 + O(5^8)) sage: HK.is_weierstrass_point(T) False - """ f, h = self.hyperelliptic_polynomials() @@ -680,8 +688,6 @@ def rational_weierstrass_points(self): sage: P = H.point([15,6,1]) sage: H.is_weierstrass_point(P) True - - """ f, h = self.hyperelliptic_polynomials() @@ -743,17 +749,18 @@ def distinguished_point(self): # For the the split and ramified case, a point at infinity is chosen, self._distinguished_point = self.points_at_infinity()[0] return self._distinguished_point - else: - assert ( - self.base_ring().characteristic() > 0 - ), "in characteristic 0, a distinguished_point needs to be specified" - # in the inert case we choose a point with minimal x-coordinate - for x0 in self.base_ring(): - try: - self._distinguished_point = self.lift_x(x0) - return self._distinguished_point - except ValueError: - pass + + assert ( + self.base_ring().characteristic() > 0 + ), "in characteristic 0, a distinguished_point needs to be specified" + + # in the inert case we choose a point with minimal x-coordinate + for x0 in self.base_ring(): + try: + self._distinguished_point = self.lift_x(x0) + return self._distinguished_point + except ValueError: + pass raise ValueError("distinguished point not found") @@ -870,7 +877,7 @@ def rational_points(self, **kwds): return self.points_at_infinity() + [self(*P) for P in proj_pts if P[2] != 0] # ------------------------------------------- - # Hacky things + # Hacky functions from old implementation. # ------------------------------------------- def is_singular(self, *args, **kwargs): @@ -907,8 +914,9 @@ def is_smooth(self): def odd_degree_model(self): r""" - Return an odd degree model of self, or raise ValueError if one does not exist over the field of definition. - The term odd degree model refers to a model of the form y^2 = f(x) with deg(f) = 2*g + 1. + Return an odd degree model of self, or raise ValueError if one does not + exist over the field of definition. The term odd degree model refers to + a model of the form y^2 = f(x) with deg(f) = 2*g + 1. EXAMPLES:: @@ -964,12 +972,6 @@ def odd_degree_model(self): sage: HyperellipticCurveSmoothModel(x^5 + 1, 1).odd_degree_model() Hyperelliptic Curve over Rational Field defined by y^2 = 4*x^5 + 5 - - TESTS:: - - sage: # TODO this used to have names="U, V" - sage: HyperellipticCurveSmoothModel(x^5 + 1).odd_degree_model() - Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + 1 """ from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( HyperellipticCurveSmoothModel, @@ -991,9 +993,9 @@ def odd_degree_model(self): raise ValueError("No odd degree model exists over field of definition") rt = rts[0] x = f.parent().gen() - fnew = f((x * rt + 1) / x).numerator() # move rt to "infinity" + f_new = f((x * rt + 1) / x).numerator() # move rt to "infinity" - return HyperellipticCurveSmoothModel(fnew, 0) + return HyperellipticCurveSmoothModel(f_new, 0) def has_odd_degree_model(self): r""" @@ -1050,6 +1052,13 @@ def _magma_init_(self, magma): # ------------------------------------------- def monsky_washnitzer_gens(self): + """ + Compute the generators of the special hyperelliptic quotient ring + + EXAMPLES:: + + TODO + """ from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self) @@ -1066,7 +1075,6 @@ def invariant_differential(self): sage: C = HyperellipticCurveSmoothModel(x^5 - 4*x + 4) sage: C.invariant_differential() 1 dx/2y - """ from sage.schemes.hyperelliptic_curves_smooth_model import ( monsky_washnitzer as m_w, @@ -1131,20 +1139,16 @@ def local_coordinates_at_nonweierstrass(self, P, prec=20, name="t"): - Jennifer Balakrishnan (2007-12) - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model - """ if P[2] == 0: raise TypeError( - "P = %s is a point at infinity. Use local_coordinates_at_infinity instead!" - % P + f"P = {P} is a point at infinity. Use local_coordinates_at_infinity instead" ) if self.is_weierstrass_point(P): raise TypeError( - "P = %s is a Weierstrass point. Use local_coordinates_at_weierstrass instead!" - % P + f"P = {P} is a Weierstrass point. Use local_coordinates_at_weierstrass instead" ) f, h = self.hyperelliptic_polynomials() - # pol = self.hyperelliptic_polynomials()[0] a, b = self.affine_coordinates(P) L = PowerSeriesRing(self.base_ring(), name, default_prec=prec) @@ -1211,17 +1215,14 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): - Jennifer Balakrishnan (2007-12) - Francis Clarke (2012-08-26) - Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024): adapt to smooth model - """ if P[2] == 0: raise TypeError( - "P = %s is a point at infinity. Use local_coordinates_at_infinity instead!" - % P + f"P = {P} is a point at infinity. Use local_coordinates_at_infinity instead" ) if not self.is_weierstrass_point(P): raise TypeError( - "P = %s is not a Weierstrass point. Use local_coordinates_at_nonweierstrass instead!" - % P + f"P = {P} is not a Weierstrass point. Use local_coordinates_at_nonweierstrass instead" ) L = PowerSeriesRing(self.base_ring(), name, default_prec=prec) @@ -1303,8 +1304,6 @@ def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): g = self.genus() f, h = self.hyperelliptic_polynomials() - # if h: - # raise NotImplementedError("h = %s is nonzero" % h) K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) t = K.gen() L = PolynomialRing(K, "x") @@ -1381,8 +1380,7 @@ def local_coordinates_at_infinity_split(self, P, prec=20, name="t"): ) if not P[2] == 0: raise TypeError( - "P = %s is not a point at infinity. Use local_coordinates_at_nonweierstrass or local_coordinates_at_weierstrass instead!" - % P + f"P = {P} is not a point at infinity. Use local_coordinates_at_nonweierstrass or local_coordinates_at_weierstrass instead" ) K = LaurentSeriesRing(self.base_ring(), name, default_prec=prec + 2) From 55bdd3a8d67b5fcf61a8a2382ec0b4be50a7da0c Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Thu, 5 Dec 2024 17:15:56 +0000 Subject: [PATCH 12/56] fix stupid bug --- .../hyperelliptic_curves_smooth_model/hyperelliptic_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index abaea2adbd7..12fbcc6cb86 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -303,7 +303,7 @@ def points_at_infinity(self): Compute the points at infinity on the curve. Assumes we are using a weighted projective model for the curve """ - return [self.point([1, y, 0], check=False) for y in self.roots_at_infinity()] + return [self.point([1, y, 0], check=True) for y in self.roots_at_infinity()] def is_x_coord(self, x): """ From a1a44c32437c30af9f666d32f3f52dff04e692c7 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 18 Dec 2024 17:50:38 +0000 Subject: [PATCH 13/56] pycodestyle fix --- .../hyperelliptic_constructor.py | 4 ++-- .../hyperelliptic_finite_field.py | 2 +- .../hyperelliptic_curves_smooth_model/jacobian_morphism.py | 4 ++-- .../weighted_projective_point.py | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 18a6375212e..bc35f689e30 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -289,7 +289,7 @@ def __defining_polynomial(f, h): # Check the polynomials are of the right type F = h**2 + 4 * f if not isinstance(F, Polynomial): - raise TypeError(f"arguments {f = } and {h = } must be polynomials") + raise TypeError(f"arguments f={f} and h={h} must be polynomials") # Store the hyperelliptic polynomials as the correct type polynomial_ring = F.parent() @@ -304,7 +304,7 @@ def __defining_polynomial(f, h): # Compute the genus of the curve from f, h genus = __genus(f, h) if genus == 0: - raise ValueError(f"arguments {f = } and {h = } must define a curve of genus at least one.") + raise ValueError(f"arguments f={f} and h={h} must define a curve of genus at least one.") # Compute the smooth model for the hyperelliptic curve # using a weighted projective space (via Toric Variety) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index 7f1e9034a5c..76e282bdd45 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -1315,7 +1315,7 @@ def _Cartier_matrix_cached(self): # Compute the finite field and prime p. Fq = self.base_ring() p = Fq.characteristic() - + # checks if p == 2: raise ValueError("p must be odd") diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py index 0fbc9a6e5af..b5d5abe1229 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -16,7 +16,7 @@ def __init__(self, parent, u, v, check=True): SchemeMorphism.__init__(self, parent) if not isinstance(u, Polynomial) or not isinstance(v, Polynomial): - raise TypeError(f"arguments {u = } and {v = } must be polynomials") + raise TypeError(f"arguments u={u} and v={v} must be polynomials") # TODO: # 1. allow elements of the base field as input @@ -27,7 +27,7 @@ def __init__(self, parent, u, v, check=True): f, h = parent.curve().hyperelliptic_polynomials() assert ( v**2 + v * h - f - ) % u == 0, f"{u = }, {v = } do not define a divisor on the Jacobian" + ) % u == 0, f"u={u}, v={v} do not define a divisor on the Jacobian" # TODO: should we automatically do reduction here if the degree of u is # too large? diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py index baab433e8cf..46aa723e1c3 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py @@ -37,6 +37,7 @@ # Projective varieties # -------------------- + class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): """ A rational point of projective space over a ring. @@ -193,6 +194,7 @@ def __hash__(self): #a constant value return hash(self.codomain()) + class SchemeMorphism_point_weighted_projective_field(SchemeMorphism_point_weighted_projective_ring): """ A rational point of projective space over a field. From 9a0c4d7a8c7b1ed0094358abda010491b4770ce0 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 18 Dec 2024 17:53:28 +0000 Subject: [PATCH 14/56] fix some rst issues, some remain... --- .../hyperelliptic_constructor.py | 25 ++++++----------- .../hyperelliptic_generic.py | 28 +++++++++---------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index bc35f689e30..9fcd5453637 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -30,15 +30,13 @@ sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 - This hyperelliptic curve has no points at infinity, - i.e. `H` is inert:: + This hyperelliptic curve has no points at infinity, i.e. `H` is inert:: sage: H.points_at_infinity() [] sage: H.is_inert() True - We can extend the base field to obtain a hyperelliptic curve - with two points at infinity:: + We can extend the base field to obtain a hyperelliptic curve with two points at infinity:: sage: K. = QQ.extension(x^2+x-1) sage: HK = H.change_ring(K) sage: HK.points_at_infinity() @@ -46,8 +44,7 @@ sage: HK.is_split() True - The construction of hyperelliptic curves is supported over different fields. - The correct class is chosen automatically:: + The construction of hyperelliptic curves is supported over different fields. The correct class is chosen automatically:: sage: F = FiniteField(13) sage: S. = PolynomialRing(F) sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF @@ -66,9 +63,7 @@ sage: HyperellipticCurveSmoothModel(3*x^5+1) Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 - The polynomials f and h need to define a smooth curve of genus at - least one. In particular polynomials defining elliptic curves are - allowed as input. + The polynomials f and h need to define a smooth curve of genus at least one. In particular polynomials defining elliptic curves are allowed as input. sage: E = HyperellipticCurveSmoothModel(x^3+1) sage: E.genus() 1 @@ -77,8 +72,7 @@ ... ValueError: arguments f = x and h = 0 must define a curve of genus at least one. - The following polynomials define a singular curve and are - not allowed as input:: + The following polynomials define a singular curve and are not allowed as input:: sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) Traceback (most recent call last): ... @@ -171,15 +165,13 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 - This hyperelliptic curve has no points at infinity, - i.e. `H` is inert:: + This hyperelliptic curve has no points at infinity, i.e. `H` is inert:: sage: H.points_at_infinity() [] sage: H.is_inert() True - We can extend the base field to obtain a hyperelliptic curve - with two points at infinity:: + We can extend the base field to obtain a hyperelliptic curve with two points at infinity:: sage: K. = QQ.extension(x^2+x-1) sage: HK = H.change_ring(K) sage: HK.points_at_infinity() @@ -187,8 +179,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): sage: HK.is_split() True - The construction of hyperelliptic curves is supported over different fields. - The correct class is chosen automatically:: + The construction of hyperelliptic curves is supported over different fields. The correct class is chosen automatically:: sage: F = FiniteField(13) sage: S. = PolynomialRing(F) sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 0d9c0cd6edc..7537d71c24e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -608,18 +608,18 @@ def is_weierstrass_point(self, P): EXAMPLES:: - We consider the hyperelliptic curve `y^2 = x^6 -1` over the rational numbers. - For instance, the point P = (1,0) is a Weierstrass point, - while the points at infinity are not:: - - sage: R. = QQ[] - sage: H = HyperellipticCurveSmoothModel(x^6 - 1) - sage: P = H.point([1,0]) - sage: H.is_weierstrass_point(P) - True - sage: Q = H.point([1,1,0]) - sage: H.is_weierstrass_point(Q) - False + We consider the hyperelliptic curve `y^2 = x^6 -1` over the rational numbers. + For instance, the point P = (1,0) is a Weierstrass point, + while the points at infinity are not:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 1) + sage: P = H.point([1,0]) + sage: H.is_weierstrass_point(P) + True + sage: Q = H.point([1,1,0]) + sage: H.is_weierstrass_point(Q) + False This also works for hyperelliptic curves with `h(x)` nonzero. Note that in this case the `y`-coordinate of a Weierstrass point @@ -634,9 +634,9 @@ def is_weierstrass_point(self, P): sage: H.is_weierstrass_point(Q) False - TESTS:: + TESTS: - Check that the examples from the p-adic file work. + Check that the examples from the p-adic file work:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) From e83a326476cedda440b5d95153400721c3d4720e Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 18 Dec 2024 18:08:09 +0000 Subject: [PATCH 15/56] try and fix more RST issues --- .../hyperelliptic_constructor.py | 5 +-- .../hyperelliptic_generic.py | 31 +++++++++---------- .../hyperelliptic_padic_field.py | 6 ++-- .../jacobian_generic.py | 30 +++++++++--------- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 9fcd5453637..3bae9cefa48 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -200,7 +200,8 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The polynomials f and h need to define a smooth curve of genus at least one. In particular polynomials defining elliptic curves are - allowed as input. + allowed as input:: + sage: E = HyperellipticCurveSmoothModel(x^3+1) sage: E.genus() 1 @@ -211,11 +212,11 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The following polynomials define a singular curve and are not allowed as input:: + sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) Traceback (most recent call last): ... ValueError: not a hyperelliptic curve: singularity in the provided affine patch - """ # --------------------------- diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 7537d71c24e..9c5059b30db 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -669,7 +669,7 @@ def rational_weierstrass_points(self): Return the rational Weierstrass points of the hyperelliptic curve. These are the points that are fixed by the hyperelliptic involution. - EXAMPLES:: + EXAMPLES: When `h(x)` is zero, then the Weierstrass points are the points with `y`-coordinate equal to zero:: @@ -792,10 +792,9 @@ def projective_curve(self): TODO: renaming to plane_model ? - EXAMPLES:: + EXAMPLES: - We consider the hyperelliptic curve with affine equation - `y^2 = x^5 + x` :: + We consider the hyperelliptic curve with affine equation `y^2 = x^5 + x`:: sage: R. = FiniteField(11)[] sage: H = HyperellipticCurveSmoothModel(x^6 + 2) @@ -844,7 +843,7 @@ def rational_points(self, **kwds): `sage.schemes.generic.algebraic_scheme.rational_points` on this curve's :meth:`projective_curve` for the affine points. - EXAMPLES:: + EXAMPLES: For the LMFDB genus 2 curve `932.a.3728.1 `_:: @@ -1103,7 +1102,7 @@ def local_coordinates_at_nonweierstrass(self, P, prec=20, name="t"): `(x(t),y(t))` such that `y(t)^2 + y(t)*h(x(t)) = f(x(t))` and `t = x - a` is the local parameter at `P`. - EXAMPLES:: + EXAMPLES: We compute the local coordinates of `H : y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x` at the point `P = (1, 6)`:: @@ -1178,10 +1177,10 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): `(x(t),y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))` and `t = y - b` is the local parameter at `P = (a,b)`. - EXAMPLES:: + EXAMPLES: We compute the local coordinates of the Weierstrass point `P = (4,0)` - on the hyperelliptic curve `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`. + on the hyperelliptic curve `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) @@ -1199,7 +1198,7 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): True We compute the local coordinates at the Weierstrass point `(1,-1)` - of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2. + of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2:: sage: H = HyperellipticCurveSmoothModel(-x^2, x^3+1) sage: P = H(1,-1) @@ -1256,10 +1255,10 @@ def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y/x^{g+1}` is the local parameter at infinity - EXAMPLES:: + EXAMPLES: We compute the local coordinates at the point at infinity of the - hyperelliptic curve `y^2 = x^5 - 5*x^2 + 1`. + hyperelliptic curve `y^2 = x^5 - 5*x^2 + 1`:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^5 - 5*x^2 + 1) @@ -1337,17 +1336,17 @@ def local_coordinates_at_infinity_split(self, P, prec=20, name="t"): `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y/x^{g+1}` is the local parameter at infinity - EXAMPLES:: + EXAMPLES: We compute the local coordinates at the point at infinity of the - hyperelliptic curve ` ` + hyperelliptic curve:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^6+4*x^4 + 4*x^2+1) sage: P1 = H(1,-1,0) sage: xt1,yt1 = H.local_coordinates_at_infinity_split(P1) - Note the similarity to the local coordinates of the other point at infinity :: + Note the similarity to the local coordinates of the other point at infinity:: sage: P2 = H(1,1,0) sage: xt2,yt2 = H.local_coordinates_at_infinity_split(P2) @@ -1412,10 +1411,10 @@ def local_coord(self, P, prec=20, name="t"): `(x(t),y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))`, where `t` is the local parameter at `P` - EXAMPLES:: + EXAMPLES: We compute the local coordinates of several points of the curve with - defining equation `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`. :: + defining equation `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 68e79bb604e..e550b0c69b2 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -141,7 +141,7 @@ def is_in_weierstrass_disc(self, P): """ Checks if `P` is in a Weierstrass disc. - EXAMPLES:: + EXAMPLES: For odd degree models, the points with `y`-coordinate equivalent to zero are contained in a Weierstrass discs:: @@ -198,7 +198,7 @@ def find_char_zero_weierstrass_point(self, Q): Given `Q` a point on self in a Weierstrass disc, finds the center of the Weierstrass disc (if defined over self.base_ring()) - EXAMPLES:: + EXAMPLES: Examples for a hyperelliptic curve with odd degree model:: @@ -254,7 +254,7 @@ def residue_disc(self, P): TODO: Really, this gives the reduction over the residue field. Isn't the residue disc a disc over the p-adics? Maybe rename to residue_point or reduction ? - EXAMPLES:: + EXAMPLES: We compute the residue discs for diffferent points on the elliptic curve `y^2 = x^3 = 10*x + 9` over the `5`-adics:: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index 1fcae854cac..e9b79cd17a6 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -34,20 +34,20 @@ This module implements the arithemtic for Jacobians of hyperelliptic curves. Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where - - (u,v) is the Mumford representative of the divisor - `P_1 + ... + P_r`, + - (u,v) is the Mumford representative of the divisor `P_1 + ... + P_r`, - n is the coefficient of `\infty_+` + We note that `m = g - \deg(u) - n` and is therefore omitted in the description. Similarly, if `H` ramified or inert, then `n` can be deduced from `\deg(u)` and `g`. In these cases, `n` is omitted in the description as well. - EXAMPLES:: + EXAMPLES: We construct the Jacobian of a hyperelliptic curve with affine equation `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. - This curve has two points at infinity. + This curve has two points at infinity:: sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) @@ -55,7 +55,7 @@ Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the - element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian. + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: sage: P = H.point([0, 0]) sage: Q = H.point([-1, -1]) @@ -63,14 +63,14 @@ (x^2 + x, -2*x : 0) Elements of the Jacobian can also be constructed by directly providing - the Mumford representation. + the Mumford representation:: sage: D1 == J(x^2 + x, -2*x, 0) True We can also embed single points into the Jacobian. Below we construct `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` - (by default one of the points at infinity). + (by default one of the points at infinity):: sage: D2 = J(P); D2 (x, 0 : 0) @@ -79,7 +79,7 @@ sage: D2 == J(P, P0) True - We may add elements, or multiply by integers. + We may add elements, or multiply by integers:: sage: 2*D1 (x, -1 : 1) @@ -89,13 +89,13 @@ (x, -1 : 1) Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`. + in particular `n = 1`:: sage: J.zero() (1, 0 : 1) There are two more elements of the Jacobian that are only supported - at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: sage: [P_plus, P_minus] = H.points_at_infinity() sage: P_plus == P0 @@ -106,7 +106,7 @@ (1, 0 : 0) Now, we consider the Jacobian of a hyperelliptic curve with only one - point at infinity, defined over a finite field. + point at infinity, defined over a finite field:: sage: K = FiniteField(7) sage: R. = K[] @@ -115,7 +115,7 @@ Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 Elements on the Jacobian can be constructed as before. But the value - `n` is not used here, since there is only one point at infinity. + `n` is not used here, since there is only one point at infinity:: sage: P = H.point([3, 0]) sage: Q = H.point([5, 1]) @@ -126,7 +126,7 @@ (x^3 + 2, 4) Over finite fields, we may also construct random elements and - compute the order of the Jacobian. + compute the order of the Jacobian:: sage: J.random_element() #random (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) @@ -135,7 +135,7 @@ Note that arithmetic on the Jacobian is not implemented if the underlying hyperelliptic curve is inert (i.e. has no points at - infinity) and the genus is odd. + infinity) and the genus is odd:: sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) @@ -144,7 +144,7 @@ TODO: - finish example for the inert case. + - finish example for the inert case. AUTHORS: From b56a767efe8e6d0ada2056dd1f3b7efb0d503591 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 18 Dec 2024 18:14:14 +0000 Subject: [PATCH 16/56] Moved weighted files and changed imports --- src/sage/schemes/all.py | 2 + .../hyperelliptic_curves_smooth_model/all.py | 4 +- .../hyperelliptic_finite_field.py | 2 +- .../hyperelliptic_generic.py | 9 +- .../jacobian_homset_generic.py | 12 +- .../jacobian_homset_split.py | 6 +- .../schemes/projective/projective_homset.py | 1 + .../schemes/projective/projective_point.py | 4 +- src/sage/schemes/weighted_projective/a.py | 10 ++ src/sage/schemes/weighted_projective/all.py | 2 + .../weighted_projective_curve.py | 0 .../weighted_projective_homset.py | 18 ++ .../weighted_projective_point.py | 167 ++++++++++++++---- .../weighted_projective_space.py | 25 ++- 14 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 src/sage/schemes/weighted_projective/a.py create mode 100644 src/sage/schemes/weighted_projective/all.py rename src/sage/schemes/{hyperelliptic_curves_smooth_model => weighted_projective}/weighted_projective_curve.py (100%) rename src/sage/schemes/{hyperelliptic_curves_smooth_model => weighted_projective}/weighted_projective_homset.py (69%) rename src/sage/schemes/{hyperelliptic_curves_smooth_model => weighted_projective}/weighted_projective_point.py (66%) rename src/sage/schemes/{hyperelliptic_curves_smooth_model => weighted_projective}/weighted_projective_space.py (95%) diff --git a/src/sage/schemes/all.py b/src/sage/schemes/all.py index 90ca2a53bd3..81811758e84 100644 --- a/src/sage/schemes/all.py +++ b/src/sage/schemes/all.py @@ -47,3 +47,5 @@ from sage.schemes.berkovich.all import * from sage.schemes.hyperelliptic_curves_smooth_model.all import * + +from sage.schemes.weighted_projective.all import * diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py index 189a6c54a3f..7cd734335f5 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -3,5 +3,5 @@ HyperellipticCurveSmoothModel, ) -lazy_import('sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_space', 'WeightedProjectiveSpace') -lazy_import('sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_curve', 'WeightedProjectiveCurve') +lazy_import('sage.schemes.weighted_projective.weighted_projective_space', 'WeightedProjectiveSpace') +lazy_import('sage.schemes.weighted_projective.weighted_projective_curve', 'WeightedProjectiveCurve') diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index 76e282bdd45..c102107e304 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -35,7 +35,7 @@ def random_point(self): sage: C.random_point() # random (4 : 1 : 1) sage: type(C.random_point()) - + """ k = self.base_ring() n = 2 * k.order() + 1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 9c5059b30db..fbbdf6346d6 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -6,13 +6,8 @@ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.real_mpfr import RR -# TODO: move this -from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_curve import ( - WeightedProjectiveCurve, -) -from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_space import ( - WeightedProjectiveSpace, -) +from sage.schemes.weighted_projective.weighted_projective_curve import WeightedProjectiveCurve +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace class HyperellipticCurveSmoothModel_generic(WeightedProjectiveCurve): diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 50ec44e95bf..21711a94356 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -1,6 +1,3 @@ -import itertools - -from sage.misc.banner import SAGE_VERSION from sage.misc.cachefunc import cached_method from sage.misc.functional import symbolic_prod as product from sage.misc.prandom import choice @@ -9,17 +6,11 @@ from sage.rings.polynomial.polynomial_ring import polygen from sage.schemes.generic.homset import SchemeHomset_points -# TODO: move this -from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( +from sage.schemes.weighted_projective.weighted_projective_point import ( SchemeMorphism_point_weighted_projective_ring, ) from sage.structure.element import parent -assert SAGE_VERSION.startswith("10."), "please update to Sage 10" -assert ( - int(SAGE_VERSION.lstrip("10.").split(".")[0]) >= 4 -), "please update to Sage 10.4+ (#37118)" - class HyperellipticJacobianHomset(SchemeHomset_points): def __init__(self, Y, X, **kwds): @@ -525,6 +516,7 @@ def postprocess_uv(u1, v1, n=None): return self._morphism_element(self, u1, v1, check=False) + import itertools points = [] for vv in itertools.product(*vss): u1, v1 = R.one(), R.zero() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index ddb7c246c55..dc25510b970 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -6,8 +6,7 @@ MumfordDivisorClassFieldSplit, ) -# TODO: move this -from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( +from sage.schemes.weighted_projective.weighted_projective_point import ( SchemeMorphism_point_weighted_projective_ring, ) from sage.structure.element import parent @@ -18,7 +17,7 @@ def __init__(self, Y, X, **kwds): super().__init__(Y, X, **kwds) self._morphism_element = MumfordDivisorClassFieldSplit - def zero(self): + def zero(self, check=True): """ Return the zero element of the Jacobian """ @@ -143,6 +142,7 @@ def __call__(self, *args, check=True): elif isinstance(P1, self._morphism_element): return P1 elif isinstance(P1, SchemeMorphism_point_weighted_projective_ring): + # TODO: Test this path when args is a tuple args = args + ( self.curve().distinguished_point(), ) # this case will now be handled below. diff --git a/src/sage/schemes/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index da3f5b502cd..fd6a2d5e846 100755 --- a/src/sage/schemes/projective/projective_homset.py +++ b/src/sage/schemes/projective/projective_homset.py @@ -462,6 +462,7 @@ def numerical_points(self, F=None, **kwds): raise NotImplementedError('numerical approximation of points only for dimension 0 subschemes') +# TODO: Should this inherit from the weighted projective class? class SchemeHomset_points_projective_ring(SchemeHomset_points): """ Set of rational points of a projective variety over a commutative ring. diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 88ab4eadcfc..b4adce9ce4f 100755 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -196,7 +196,9 @@ def __init__(self, X, v, check=True): raise ValueError(f"{v} does not define a valid projective point " "since it is a multiple of a zero divisor") + print("v:", v) X.extended_codomain()._check_satisfies_equations(v) + print("v:", v) self._coords = tuple(v) self._normalized = False @@ -1081,7 +1083,7 @@ def __init__(self, X, v, check=True): This function still normalizes points so that the rightmost nonzero coordinate is 1. This is to maintain functionality with current - implementations of curves in projectives space (plane, conic, elliptic, etc). + implementations of curves in projective spaces (plane, conic, elliptic, etc). The :class:`SchemeMorphism_point_projective_ring` is for general use. EXAMPLES:: diff --git a/src/sage/schemes/weighted_projective/a.py b/src/sage/schemes/weighted_projective/a.py new file mode 100644 index 00000000000..5f6c7d3bd97 --- /dev/null +++ b/src/sage/schemes/weighted_projective/a.py @@ -0,0 +1,10 @@ +def main(): + + return \ + 1234 + + +def main2(): + + return \ + "don't refor diff --git a/src/sage/schemes/weighted_projective/all.py b/src/sage/schemes/weighted_projective/all.py new file mode 100644 index 00000000000..50271df0a1b --- /dev/null +++ b/src/sage/schemes/weighted_projective/all.py @@ -0,0 +1,2 @@ +from sage.schemes.weighted_projective.weighted_projective_curve import WeightedProjectiveCurve +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py b/src/sage/schemes/weighted_projective/weighted_projective_curve.py similarity index 100% rename from src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_curve.py rename to src/sage/schemes/weighted_projective/weighted_projective_curve.py diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py similarity index 69% rename from src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py rename to src/sage/schemes/weighted_projective/weighted_projective_homset.py index 84cdf7d028d..262839d7203 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_homset.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -44,3 +44,21 @@ def points(self, **__): TODO: modify implementation from projective space """ raise NotImplementedError("enumerating points on weighted projective scheme is not implemented") + + +class SchemeHomset_points_weighted_projective_field(SchemeHomset_points): + """ + Set of rational points of a weighted projective variety over a field. + + Placeholder class. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 3, 1]); WP + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field + sage: WP.point_homset() + Set of rational points of Weighted Projective Space of dimension 3 with weights (1, 3, 1, 1) over Rational Field + sage: type(WP.point_homset()) + + """ + pass diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py similarity index 66% rename from src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py rename to src/sage/schemes/weighted_projective/weighted_projective_point.py index 46aa723e1c3..ea4a6d299e5 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -40,11 +40,11 @@ class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): """ - A rational point of projective space over a ring. + A rational point of weighted projective space over a ring. INPUT: - - ``X`` -- a homset of a subscheme of an ambient projective space over a ring `K`. + - ``X`` -- a homset of a subscheme of an ambient weighted projective space over a ring `K`. - ``v`` -- a list or tuple of coordinates in `K`. @@ -52,9 +52,7 @@ class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): EXAMPLES:: - sage: P = ProjectiveSpace(2, ZZ) - sage: P(2,3,4) - (2 : 3 : 4) + TODO """ def __init__(self, X, v, check=True): @@ -69,16 +67,18 @@ def __init__(self, X, v, check=True): if check: # check parent - from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_homset import ( + from sage.schemes.weighted_projective.weighted_projective_homset import ( SchemeHomset_points_weighted_projective_ring, ) + if not isinstance(X, SchemeHomset_points_weighted_projective_ring): raise TypeError(f"ambient space {X} must be a weighted projective space") from sage.rings.ring import CommutativeRing - from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field + d = X.codomain().ambient_space().ngens() - if isinstance(v, SchemeMorphism) or isinstance(v, EllipticCurvePoint_field): + # TODO: Should we also do a special case when v (argument) is a hyperelliptic curve pt + if isinstance(v, SchemeMorphism): v = list(v) else: try: @@ -88,26 +88,30 @@ def __init__(self, X, v, check=True): pass if not isinstance(v, (list, tuple)): raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) - if len(v) != d and len(v) != d-1: + if len(v) != d and len(v) != d - 1: raise TypeError("v (=%s) must have %s components" % (v, d)) R = X.value_ring() v = Sequence(v, R) - if len(v) == d-1: # very common special case + if len(v) == d - 1: # very common special case v.append(R.one()) if R in IntegralDomains(): # Over integral domains, any tuple with at least one # non-zero coordinate is a valid projective point. if not any(v): - raise ValueError(f"{v} does not define a valid projective " - "point since all entries are zero") + raise ValueError( + f"{v} does not define a valid projective " + "point since all entries are zero" + ) # Over rings with zero divisors, a more careful check # is required: We test whether the coordinates of the # point generate the unit ideal. See #31576. elif 1 not in R.ideal(v): - raise ValueError(f"{v} does not define a valid projective point " - "since it is a multiple of a zero divisor") + raise ValueError( + f"{v} does not define a valid projective point " + "since it is a multiple of a zero divisor" + ) X.extended_codomain()._check_satisfies_equations(v) @@ -140,8 +144,9 @@ def _richcmp_(self, other, op): n = len(self._coords) if op in [op_EQ, op_NE]: - b = all(self[i] * other[j] == self[j] * other[i] - for i in range(n) for j in range(i + 1, n)) + b = all( + self[i] * other[j] == self[j] * other[i] for i in range(n) for j in range(i + 1, n) + ) return b == (op == op_EQ) return richcmp(self._coords, other._coords, op) @@ -184,24 +189,126 @@ def __hash__(self): True """ R = self.codomain().base_ring() - #if there is a fraction field normalize the point so that - #equal points have equal hash values + # if there is a fraction field normalize the point so that + # equal points have equal hash values if R in IntegralDomains(): P = self.change_ring(FractionField(R)) P.normalize_coordinates() return hash(tuple(P)) - #if there is no good way to normalize return - #a constant value + # if there is no good way to normalize return + # a constant value return hash(self.codomain()) + def normalize_coordinates(self): + """ + Removes the gcd from the coordinates of this point (including `-1`) + and rescales everything so that the last nonzero entry is as "simple" + as possible. The notion of "simple" here depends on the base ring; + concretely, the last nonzero coordinate will be `1` in a field and + positive over an ordered ring. + + .. WARNING:: The gcd will depend on the base ring. + + OUTPUT: none + + EXAMPLES:: + + sage: P = ProjectiveSpace(ZZ, 2, 'x') + sage: p = P([-5, -15, -20]) + sage: p.normalize_coordinates(); p + (1 : 3 : 4) + + :: + + sage: # needs sage.rings.padics + sage: P = ProjectiveSpace(Zp(7), 2, 'x') + sage: p = P([-5, -15, -2]) + sage: p.normalize_coordinates(); p + (6 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : 4 + 4*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : 1 + O(7^20)) + + :: + + sage: R. = PolynomialRing(QQ) + sage: P = ProjectiveSpace(R, 2, 'x') + sage: p = P([3/5*t^3, 6*t, t]) + sage: p.normalize_coordinates(); p + (3/5*t^2 : 6 : 1) + + :: + + sage: P. = ProjectiveSpace(Zmod(20), 1) + sage: Q = P(3, 6) + sage: Q.normalize_coordinates() + sage: Q + (1 : 2) + + Since the base ring is a polynomial ring over a field, only the + gcd `c` is removed. :: + + sage: R. = PolynomialRing(QQ) + sage: P = ProjectiveSpace(R, 1) + sage: Q = P(2*c, 4*c) + sage: Q.normalize_coordinates(); Q + (1/2 : 1) + + A polynomial ring over a ring gives the more intuitive result. :: + + sage: R. = PolynomialRing(ZZ) + sage: P = ProjectiveSpace(R, 1) + sage: Q = P(2*c, 4*c) + sage: Q.normalize_coordinates();Q + (1 : 2) + + :: + + sage: # needs sage.libs.singular + sage: R. = QQ[] + sage: S = R.quotient_ring(R.ideal(t^3)) + sage: P. = ProjectiveSpace(S, 1) + sage: Q = P(t + 1, t^2 + t) + sage: Q.normalize_coordinates() + sage: Q + (1 : tbar) + """ + if self._normalized: + return + R = self.codomain().base_ring() + if isinstance(R, QuotientRing_generic): + index = len(self._coords) - 1 + while not self._coords[index]: + index -= 1 + last = self._coords[index].lift() + mod, = R.defining_ideal().gens() + unit = last + while not (zdiv := mod.gcd(unit)).is_unit(): + unit //= zdiv + self.scale_by(unit.inverse_mod(mod)) + else: + GCD = R(gcd(self._coords[0], self._coords[1])) + index = 2 + while not GCD.is_unit() and index < len(self._coords): + GCD = R(gcd(GCD, self._coords[index])) + index += 1 + if not GCD.is_unit(): + self.scale_by(~GCD) + index = len(self._coords) - 1 + while not self._coords[index]: + index -= 1 + if self._coords[index].is_unit(): + if not self._coords[index].is_one(): + self.scale_by(~self._coords[index]) + elif self._coords[index] < 0: + self.scale_by(-R.one()) + self._normalized = True + class SchemeMorphism_point_weighted_projective_field(SchemeMorphism_point_weighted_projective_ring): """ - A rational point of projective space over a field. + A rational point of weighted projective space over a field. INPUT: - - ``X`` -- a homset of a subscheme of an ambient projective space + - ``X`` -- a homset of a subscheme of an ambient weighted projective space over a field `K`. - ``v`` -- a list or tuple of coordinates in `K`. @@ -225,8 +332,8 @@ def __init__(self, X, v, check=True): This function still normalizes points so that the rightmost non-zero coordinate is 1. This is to maintain functionality with current - implementations of curves in projectives space (plane, conic, elliptic, etc). - The :class:`SchemeMorphism_point_projective_ring` is for general use. + implementations of curves in projective spaces (plane, conic, elliptic, etc). + The :class:`SchemeMorphism_point_weighted_projective_ring` is for general use. EXAMPLES:: @@ -281,10 +388,8 @@ def __init__(self, X, v, check=True): self._normalized = False if check: - from sage.rings.ring import CommutativeRing - from sage.schemes.elliptic_curves.ell_point import EllipticCurvePoint_field d = X.codomain().ambient_space().ngens() - if is_SchemeMorphism(v) or isinstance(v, EllipticCurvePoint_field): + if isinstance(v, SchemeMorphism): v = list(v) else: try: @@ -369,15 +474,17 @@ def normalize_coordinates(self): inv = c.inverse() new_coords = [d * inv for d in self._coords[:index]] new_coords.append(self.base_ring().one()) - new_coords.extend(self._coords[index+1:]) + new_coords.extend(self._coords[index + 1 :]) self._coords = tuple(new_coords) break else: - assert False, 'bug: invalid projective point' + assert False, "bug: invalid projective point" self._normalized = True -class SchemeMorphism_point_weighted_projective_finite_field(SchemeMorphism_point_weighted_projective_field): +class SchemeMorphism_point_weighted_projective_finite_field( + SchemeMorphism_point_weighted_projective_field +): def __hash__(self): r""" diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py similarity index 95% rename from src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py rename to src/sage/schemes/weighted_projective/weighted_projective_space.py index 810adc6e7d4..febae7ede15 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -1,3 +1,5 @@ +from sage.categories.fields import Fields +from sage.categories.rings import Rings from sage.misc.latex import latex from sage.misc.prandom import shuffle from sage.rings.integer import Integer @@ -7,13 +9,17 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.term_order import TermOrder from sage.schemes.generic.ambient_space import AmbientSpace -from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_homset import ( +from sage.schemes.weighted_projective.weighted_projective_homset import ( SchemeHomset_points_weighted_projective_ring, ) -from sage.schemes.projective.projective_space import ProjectiveSpace, _CommRings +from sage.schemes.projective.projective_space import ProjectiveSpace from sage.structure.all import UniqueRepresentation from sage.structure.category_object import normalize_names +_Fields = Fields() +_Rings = Rings() +_CommRings = _Rings.Commutative() + def WeightedProjectiveSpace(weights, R=None, names=None): r""" @@ -68,6 +74,8 @@ def WeightedProjectiveSpace(weights, R=None, names=None): # TODO: Specialise implementation to projective spaces over non-rings. But # since we don't really implement extra functionalities, I don't think we # care. + if R in _Fields: + return WeightedProjectiveSpace_field(weights, R=R, names=names) if R in _CommRings: return WeightedProjectiveSpace_ring(weights, R=R, names=names) @@ -75,6 +83,9 @@ def WeightedProjectiveSpace(weights, R=None, names=None): class WeightedProjectiveSpace_ring(UniqueRepresentation, AmbientSpace): + """ + TODO: Documentation + """ @staticmethod def __classcall__(cls, weights: tuple[Integer], R=ZZ, names=None): # __classcall_ is the "preprocessing" step for UniqueRepresentation @@ -284,7 +295,6 @@ def _point_homset(self, *args, **kwds): For internal use only. See :mod:`morphism` for details. """ - # raise NotImplementedError("_point_homset not implemented for weighted projective space") return SchemeHomset_points_weighted_projective_ring(*args, **kwds) def point(self, v, check=True): @@ -325,7 +335,7 @@ def _point(self, *args, **kwds): For internal use only. See :mod:`morphism` for details. """ - from sage.schemes.hyperelliptic_curves_smooth_model.weighted_projective_point import ( + from sage.schemes.weighted_projective.weighted_projective_point import ( SchemeMorphism_point_weighted_projective_ring, ) return SchemeMorphism_point_weighted_projective_ring(*args, **kwds) @@ -369,3 +379,10 @@ def _an_element_(self): def subscheme(self, *_, **__): raise NotImplementedError("subscheme of weighted projective space has not been implemented") + + +class WeightedProjectiveSpace_field(WeightedProjectiveSpace_ring): + """ + TODO: Documentation + """ + pass From 6c783450af2bfc9a979cc22617c7b71deca5d160 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 18 Dec 2024 18:17:37 +0000 Subject: [PATCH 17/56] remove random file... --- src/sage/schemes/weighted_projective/a.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/sage/schemes/weighted_projective/a.py diff --git a/src/sage/schemes/weighted_projective/a.py b/src/sage/schemes/weighted_projective/a.py deleted file mode 100644 index 5f6c7d3bd97..00000000000 --- a/src/sage/schemes/weighted_projective/a.py +++ /dev/null @@ -1,10 +0,0 @@ -def main(): - - return \ - 1234 - - -def main2(): - - return \ - "don't refor From c6e9f14e2561d9f62f15f202f17f38bb367847df Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Wed, 18 Dec 2024 18:22:30 +0000 Subject: [PATCH 18/56] remove more debugs... --- src/sage/schemes/projective/projective_point.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index b4adce9ce4f..ef2ab238fd0 100755 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -196,9 +196,7 @@ def __init__(self, X, v, check=True): raise ValueError(f"{v} does not define a valid projective point " "since it is a multiple of a zero divisor") - print("v:", v) X.extended_codomain()._check_satisfies_equations(v) - print("v:", v) self._coords = tuple(v) self._normalized = False From 4bc05aabcf3ff67a729dbb16a0de0e1e27f6db0c Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 18 Dec 2024 18:31:55 +0000 Subject: [PATCH 19/56] more RST --- .../hyperelliptic_generic.py | 2 +- .../jacobian_homset_generic.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index fbbdf6346d6..d546a5733e5 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -1193,7 +1193,7 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): True We compute the local coordinates at the Weierstrass point `(1,-1)` - of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2:: + of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2`:: sage: H = HyperellipticCurveSmoothModel(-x^2, x^3+1) sage: P = H(1,-1) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 21711a94356..b4a42058e44 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -399,7 +399,7 @@ def cantor_composition(self, u1, v1, u2, v2): EXAMPLES:: - TODO + sage: # TODO """ u3, v3, _ = self._cantor_composition_generic(u1, v1, u2, v2) return u3, v3 @@ -410,7 +410,7 @@ def cantor_reduction(self, u0, v0): EXAMPLES:: - TODO + sage: # TODO """ return self._cantor_reduction_generic(u0, v0) From 6c7c4368d7afee0d255cc17c4b2b2dea3e1fe7b6 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 18 Dec 2024 22:44:05 +0000 Subject: [PATCH 20/56] fix failing doctests --- .../hyperelliptic_constructor.py | 4 ++-- .../hyperelliptic_padic_field.py | 3 +++ .../schemes/weighted_projective/weighted_projective_homset.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 3bae9cefa48..fe80f88d25d 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -281,7 +281,7 @@ def __defining_polynomial(f, h): # Check the polynomials are of the right type F = h**2 + 4 * f if not isinstance(F, Polynomial): - raise TypeError(f"arguments f={f} and h={h} must be polynomials") + raise TypeError(f"arguments f = {f} and h = {h} must be polynomials") # Store the hyperelliptic polynomials as the correct type polynomial_ring = F.parent() @@ -296,7 +296,7 @@ def __defining_polynomial(f, h): # Compute the genus of the curve from f, h genus = __genus(f, h) if genus == 0: - raise ValueError(f"arguments f={f} and h={h} must define a curve of genus at least one.") + raise ValueError(f"arguments f = {f} and h = {h} must define a curve of genus at least one.") # Compute the smooth model for the hyperelliptic curve # using a weighted projective space (via Toric Variety) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index e550b0c69b2..62d87dcb479 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -599,6 +599,9 @@ def coleman_integrals_on_basis(self, P, Q, algorithm=None): from sage.misc.profiler import Profiler from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) prof = Profiler() prof("setup") diff --git a/src/sage/schemes/weighted_projective/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py index 262839d7203..e28de64093f 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_homset.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -57,7 +57,7 @@ class SchemeHomset_points_weighted_projective_field(SchemeHomset_points): sage: WP = WeightedProjectiveSpace(QQ, [1, 3, 1]); WP Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field sage: WP.point_homset() - Set of rational points of Weighted Projective Space of dimension 3 with weights (1, 3, 1, 1) over Rational Field + Set of rational points of Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field sage: type(WP.point_homset()) """ From ab94c8f353bb6ba95d3e4f645faf7d47715014e8 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Wed, 18 Dec 2024 23:34:05 +0000 Subject: [PATCH 21/56] fix typing error --- .../weighted_projective/weighted_projective_point.py | 6 +----- .../weighted_projective/weighted_projective_space.py | 6 ++++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index ea4a6d299e5..28434166924 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -25,11 +25,7 @@ from sage.categories.integral_domains import IntegralDomains from sage.rings.fraction_field import FractionField -from sage.schemes.generic.morphism import ( - SchemeMorphism, - SchemeMorphism_point, - is_SchemeMorphism, -) +from sage.schemes.generic.morphism import SchemeMorphism, SchemeMorphism_point from sage.structure.richcmp import op_EQ, op_NE, richcmp from sage.structure.sequence import Sequence diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py index febae7ede15..5a534c78977 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -1,3 +1,5 @@ +from typing import Union + from sage.categories.fields import Fields from sage.categories.rings import Rings from sage.misc.latex import latex @@ -9,10 +11,10 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.term_order import TermOrder from sage.schemes.generic.ambient_space import AmbientSpace +from sage.schemes.projective.projective_space import ProjectiveSpace from sage.schemes.weighted_projective.weighted_projective_homset import ( SchemeHomset_points_weighted_projective_ring, ) -from sage.schemes.projective.projective_space import ProjectiveSpace from sage.structure.all import UniqueRepresentation from sage.structure.category_object import normalize_names @@ -145,7 +147,7 @@ def ngens(self) -> Integer: """ return self.dimension_relative() + 1 - def _check_satisfies_equations(self, v: list[Integer] | tuple[Integer]) -> bool: + def _check_satisfies_equations(self, v: Union[list[Integer], tuple[Integer]]) -> bool: """ Return ``True`` if ``v`` defines a point on the weighted projective plane; raise a :class:`TypeError` otherwise. From dddb0ee1d80fca38a5ddda493d901fa8c6a0cf7b Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 01:59:31 +0000 Subject: [PATCH 22/56] implement comparison between weighted projective points --- .../weighted_projective_curve.py | 1 + .../weighted_projective_homset.py | 17 +++++- .../weighted_projective_point.py | 59 ++++++++++++++++--- .../weighted_projective_space.py | 20 ++++++- 4 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_curve.py b/src/sage/schemes/weighted_projective/weighted_projective_curve.py index ea34d940f6b..bbb5221f931 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_curve.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_curve.py @@ -1,6 +1,7 @@ from sage.schemes.curves.curve import Curve_generic +# TODO: Implement (some) embedding into straight projective space class WeightedProjectiveCurve(Curve_generic): def __init__(self, A, X, *kwargs): # TODO ensure that A is the right type? diff --git a/src/sage/schemes/weighted_projective/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py index e28de64093f..5f0368f7b5a 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_homset.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -30,7 +30,16 @@ class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): EXAMPLES:: - sage: # TODO + sage: WP = WeightedProjectiveSpace(ZZ, [1, 3, 1]); WP + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + sage: WP.point_homset() + Set of rational points of Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + sage: type(WP.point_homset()) + + sage: WP(2, 24, 2) + (2 : 24 : 2) + sage: WP(2, 24, 2) == WP(1, 3, 1) + True """ def points(self, **__): @@ -59,6 +68,10 @@ class SchemeHomset_points_weighted_projective_field(SchemeHomset_points): sage: WP.point_homset() Set of rational points of Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field sage: type(WP.point_homset()) - + + sage: WP(2, 24, 2) + (1 : 3 : 1) + sage: WP(2, 24, 2) == WP(1, 3, 1) + True """ pass diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index 28434166924..2e42df75fb9 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -23,8 +23,11 @@ from copy import copy +from sage.arith.misc import gcd from sage.categories.integral_domains import IntegralDomains from sage.rings.fraction_field import FractionField +from sage.rings.quotient_ring import QuotientRing_generic +from sage.rings.ring import CommutativeRing from sage.schemes.generic.morphism import SchemeMorphism, SchemeMorphism_point from sage.structure.richcmp import op_EQ, op_NE, richcmp from sage.structure.sequence import Sequence @@ -103,6 +106,7 @@ def __init__(self, X, v, check=True): # Over rings with zero divisors, a more careful check # is required: We test whether the coordinates of the # point generate the unit ideal. See #31576. + # TODO: Does this require modification? (Ring theory exam question) elif 1 not in R.ideal(v): raise ValueError( f"{v} does not define a valid projective point " @@ -119,11 +123,11 @@ def _repr_(self): def _richcmp_(self, other, op): """ - Test the projective equality of two points. + Test the weighted projective equality of two points. INPUT: - - ``right`` -- a point on projective space + - ``right`` -- a point on weighted projective space OUTPUT: @@ -131,7 +135,25 @@ def _richcmp_(self, other, op): EXAMPLES:: - TODO + sage: WP = WeightedProjectiveSpace(ZZ, [1, 3, 1]) + sage: WP(2, 8, 2) + (2 : 8 : 2) + sage: _ == WP(1, 1, 1) + True + sage: WP(5, 0, 5) == WP(1, 0, 1) + True + + :: + + sage: weights = [randint(1, 10) for _ in range(5)] + sage: WP = WeightedProjectiveSpace(ZZ, weights) + sage: P = WP([randint(1, 100) * choice([-1, 1]) for _ in range(5)]) + sage: λ = randint(2, 100) * choice([-1, 1]) + sage: Q = WP([c * λ**w for c, w in zip(P._coords, weights)]) + sage: P._coords == Q._coords + False + sage: P == Q + True """ assert isinstance(other, SchemeMorphism_point) @@ -140,8 +162,12 @@ def _richcmp_(self, other, op): n = len(self._coords) if op in [op_EQ, op_NE]: + weights = self.codomain().weights() b = all( - self[i] * other[j] == self[j] * other[i] for i in range(n) for j in range(i + 1, n) + other[i]**weights[j] * self[j]**weights[i] + == self[i]**weights[j] * other[j]**weights[i] + for i in range(n) + for j in range(i + 1, n) ) return b == (op == op_EQ) return richcmp(self._coords, other._coords, op) @@ -220,7 +246,9 @@ def normalize_coordinates(self): sage: P = ProjectiveSpace(Zp(7), 2, 'x') sage: p = P([-5, -15, -2]) sage: p.normalize_coordinates(); p - (6 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : 4 + 4*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : 1 + O(7^20)) + (6 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : + 4 + 4*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : + 1 + O(7^20)) :: @@ -297,6 +325,12 @@ def normalize_coordinates(self): self.scale_by(-R.one()) self._normalized = True + def scale_by(self, t): + """ + TODO: Implement + """ + raise NotImplementedError + class SchemeMorphism_point_weighted_projective_field(SchemeMorphism_point_weighted_projective_ring): """ @@ -384,6 +418,14 @@ def __init__(self, X, v, check=True): self._normalized = False if check: + # check parent + from sage.schemes.weighted_projective.weighted_projective_homset import ( + SchemeHomset_points_weighted_projective_field, + ) + + if not isinstance(X, SchemeHomset_points_weighted_projective_field): + raise TypeError(f"ambient space {X} must be a weighted projective space over a ring") + d = X.codomain().ambient_space().ngens() if isinstance(v, SchemeMorphism): v = list(v) @@ -393,7 +435,7 @@ def __init__(self, X, v, check=True): v = [v] except AttributeError: pass - if not isinstance(v, (list,tuple)): + if not isinstance(v, (list, tuple)): raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) if len(v) != d and len(v) != d-1: raise TypeError("v (=%s) must have %s components" % (v, d)) @@ -408,8 +450,9 @@ def __init__(self, X, v, check=True): if c.is_one(): break if c: + weights = X.codomain().weights() for j in range(last): - v[j] /= c + v[j] /= c ** weights[j] v[last] = R.one() break else: @@ -470,7 +513,7 @@ def normalize_coordinates(self): inv = c.inverse() new_coords = [d * inv for d in self._coords[:index]] new_coords.append(self.base_ring().one()) - new_coords.extend(self._coords[index + 1 :]) + new_coords.extend(self._coords[index + 1:]) self._coords = tuple(new_coords) break else: diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py index 5a534c78977..5fe178da2b1 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -14,6 +14,7 @@ from sage.schemes.projective.projective_space import ProjectiveSpace from sage.schemes.weighted_projective.weighted_projective_homset import ( SchemeHomset_points_weighted_projective_ring, + SchemeHomset_points_weighted_projective_field, ) from sage.structure.all import UniqueRepresentation from sage.structure.category_object import normalize_names @@ -387,4 +388,21 @@ class WeightedProjectiveSpace_field(WeightedProjectiveSpace_ring): """ TODO: Documentation """ - pass + def _point_homset(self, *args, **kwds): + """ + Construct a point Hom-set. + + For internal use only. See :mod:`morphism` for details. + """ + return SchemeHomset_points_weighted_projective_field(*args, **kwds) + + def _point(self, *args, **kwds): + """ + Construct a point. + + For internal use only. See :mod:`morphism` for details. + """ + from sage.schemes.weighted_projective.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_field, + ) + return SchemeMorphism_point_weighted_projective_field(*args, **kwds) From 228be07b714492f0b15a014b920b340913fd091c Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 04:12:58 +0000 Subject: [PATCH 23/56] implement zero divisor check for constructor of points in weighted projective space --- .../weighted_projective_point.py | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index 2e42df75fb9..6b9c42e4df1 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -30,6 +30,7 @@ from sage.rings.ring import CommutativeRing from sage.schemes.generic.morphism import SchemeMorphism, SchemeMorphism_point from sage.structure.richcmp import op_EQ, op_NE, richcmp +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing_generic from sage.structure.sequence import Sequence # -------------------- @@ -55,12 +56,29 @@ class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): """ def __init__(self, X, v, check=True): - """ + r""" The Python constructor. EXAMPLES:: - TODO + sage: WeightedProjectiveSpace(ZZ, [1, 3, 1])(2, 8, 2) + (2 : 8 : 2) + sage: WeightedProjectiveSpace(Zmod(20), [1, 3, 1])(1, 9, 2) + (1 : 9 : 2) + sage: WeightedProjectiveSpace(Zmod(20), [1, 3, 1])(2, 8, 2) + Traceback (most recent call last): + ... + ValueError: [2, 8, 2] does not define a valid weighted projective point since it is a multiple of a zero divisor + sage: WeightedProjectiveSpace(Zmod(6), [2, 3])(2, 3) + (2 : 3) + + The following example is a valid projective point in `\mathbb{P}^1`, but is equivalent to + `(0 : 0)` in `\mathbb{P}^{[2, 3]}` and hence invalid:: + + sage: WeightedProjectiveSpace(Zmod(2^5), [2, 3])(1, 1) + Traceback (most recent call last): + ... + ValueError: [1, 1] does not define a valid weighted projective point since it is a multiple of a zero divisor """ SchemeMorphism.__init__(self, X) @@ -103,15 +121,23 @@ def __init__(self, X, v, check=True): f"{v} does not define a valid projective " "point since all entries are zero" ) - # Over rings with zero divisors, a more careful check - # is required: We test whether the coordinates of the - # point generate the unit ideal. See #31576. - # TODO: Does this require modification? (Ring theory exam question) - elif 1 not in R.ideal(v): - raise ValueError( - f"{v} does not define a valid projective point " - "since it is a multiple of a zero divisor" - ) + + # Over rings with zero divisors, we want to check if there exists λ ≠ 0 such that + # v[i] * λ^w[i] = 0. But this is a hard problem in general. For example, consider + # R = Z/nZ, v[i] = 1, w[i] = 2, then λ exists iff n is powerful i.e. all exponents in + # prime factorisation of n is at least 2. Also this relates to finding nilpotents in a + # general ring etc. + # So instead we implement this only for cyclic rings by factorisation. + elif isinstance(R, IntegerModRing_generic): + from sage.arith.misc import valuation + weights = X.extended_codomain().weights() + for p, e in R.factored_order(): + # v[i] * λ^w[i] = 0 mod p^e + # only possible if v_p(v[i]) + w[i] * (e - 1) >= e + # after simplifying: + if all(w >= 2 and e >= 2 or valuation(v, p) >= 1 for v, w in zip(v, weights)): + raise ValueError(f"{v} does not define a valid weighted projective point " + "since it is a multiple of a zero divisor") X.extended_codomain()._check_satisfies_equations(v) @@ -450,7 +476,7 @@ def __init__(self, X, v, check=True): if c.is_one(): break if c: - weights = X.codomain().weights() + weights = X.extended_codomain().weights() for j in range(last): v[j] /= c ** weights[j] v[last] = R.one() From 1392729c36999d51110a65ce1a9f19c994fb8aa6 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 04:14:09 +0000 Subject: [PATCH 24/56] reimplement check more directly --- .../schemes/weighted_projective/weighted_projective_point.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index 6b9c42e4df1..1d3789aebf9 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -134,8 +134,7 @@ def __init__(self, X, v, check=True): for p, e in R.factored_order(): # v[i] * λ^w[i] = 0 mod p^e # only possible if v_p(v[i]) + w[i] * (e - 1) >= e - # after simplifying: - if all(w >= 2 and e >= 2 or valuation(v, p) >= 1 for v, w in zip(v, weights)): + if all(valuation(v, p) + w * (e - 1) >= e for v, w in zip(v, weights)): raise ValueError(f"{v} does not define a valid weighted projective point " "since it is a multiple of a zero divisor") From afd568aa2441d8ba1842d2c868edb758afb51a0f Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 04:18:33 +0000 Subject: [PATCH 25/56] remove extra import --- src/sage/schemes/hyperelliptic_curves_smooth_model/all.py | 4 ---- src/sage/schemes/weighted_projective/all.py | 1 - 2 files changed, 5 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py index 7cd734335f5..6d9736c739c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -1,7 +1,3 @@ -from sage.misc.lazy_import import lazy_import from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( HyperellipticCurveSmoothModel, ) - -lazy_import('sage.schemes.weighted_projective.weighted_projective_space', 'WeightedProjectiveSpace') -lazy_import('sage.schemes.weighted_projective.weighted_projective_curve', 'WeightedProjectiveCurve') diff --git a/src/sage/schemes/weighted_projective/all.py b/src/sage/schemes/weighted_projective/all.py index 50271df0a1b..7be4ce119b1 100644 --- a/src/sage/schemes/weighted_projective/all.py +++ b/src/sage/schemes/weighted_projective/all.py @@ -1,2 +1 @@ -from sage.schemes.weighted_projective.weighted_projective_curve import WeightedProjectiveCurve from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace From 0418f7c3bec3aaea6f430716a15f9cee02eeb6a6 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 07:47:47 +0000 Subject: [PATCH 26/56] move weighted_projective_curve to schemes/curves --- .../weighted_projective_curve.py | 0 .../hyperelliptic_curves_smooth_model/hyperelliptic_generic.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/sage/schemes/{weighted_projective => curves}/weighted_projective_curve.py (100%) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_curve.py b/src/sage/schemes/curves/weighted_projective_curve.py similarity index 100% rename from src/sage/schemes/weighted_projective/weighted_projective_curve.py rename to src/sage/schemes/curves/weighted_projective_curve.py diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index d546a5733e5..40ce5aab0ea 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -6,7 +6,7 @@ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.real_mpfr import RR -from sage.schemes.weighted_projective.weighted_projective_curve import WeightedProjectiveCurve +from sage.schemes.curves.weighted_projective_curve import WeightedProjectiveCurve from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace From 2467ced8dadad1590ec83cad818a523a126b73fe Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 07:48:26 +0000 Subject: [PATCH 27/56] update meson.build --- .../schemes/hyperelliptic_curves_smooth_model/meson.build | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build index f2b2dff0459..da68694587d 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build @@ -19,10 +19,6 @@ py.install_sources( 'jacobian_morphism.py', 'mestre.py', 'monsky_washnitzer.py', - 'weighted_projective_curve.py', - 'weighted_projective_homset.py', - 'weighted_projective_point.py', - 'weighted_projective_space.py', subdir: 'sage/schemes/hyperelliptic_curves_smooth_model', ) From fe6cdf4f99ee4626a8fe6c713bccc4a9abf33e0c Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 07:48:57 +0000 Subject: [PATCH 28/56] sort imports and remove extra export --- src/sage/schemes/weighted_projective/all.py | 1 - .../schemes/weighted_projective/weighted_projective_point.py | 2 +- .../schemes/weighted_projective/weighted_projective_space.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/schemes/weighted_projective/all.py b/src/sage/schemes/weighted_projective/all.py index 7be4ce119b1..e69de29bb2d 100644 --- a/src/sage/schemes/weighted_projective/all.py +++ b/src/sage/schemes/weighted_projective/all.py @@ -1 +0,0 @@ -from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index 1d3789aebf9..16d583cc978 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -25,12 +25,12 @@ from sage.arith.misc import gcd from sage.categories.integral_domains import IntegralDomains +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing_generic from sage.rings.fraction_field import FractionField from sage.rings.quotient_ring import QuotientRing_generic from sage.rings.ring import CommutativeRing from sage.schemes.generic.morphism import SchemeMorphism, SchemeMorphism_point from sage.structure.richcmp import op_EQ, op_NE, richcmp -from sage.rings.finite_rings.integer_mod_ring import IntegerModRing_generic from sage.structure.sequence import Sequence # -------------------- diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py index 5fe178da2b1..7b06650c8e1 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -13,8 +13,8 @@ from sage.schemes.generic.ambient_space import AmbientSpace from sage.schemes.projective.projective_space import ProjectiveSpace from sage.schemes.weighted_projective.weighted_projective_homset import ( - SchemeHomset_points_weighted_projective_ring, SchemeHomset_points_weighted_projective_field, + SchemeHomset_points_weighted_projective_ring, ) from sage.structure.all import UniqueRepresentation from sage.structure.category_object import normalize_names From 456c0c6a5057ae6d8aeef01509f9431e5420c0b0 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 13:18:31 +0000 Subject: [PATCH 29/56] add back missing import for WeightedProjectiveSpace --- src/sage/schemes/weighted_projective/all.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/schemes/weighted_projective/all.py b/src/sage/schemes/weighted_projective/all.py index e69de29bb2d..7be4ce119b1 100644 --- a/src/sage/schemes/weighted_projective/all.py +++ b/src/sage/schemes/weighted_projective/all.py @@ -0,0 +1 @@ +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace From 490f774b86b392cf6614469cae9281887d6e29aa Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 13:20:44 +0000 Subject: [PATCH 30/56] run tools/update_meson.py (also done in #39159) --- src/sage/modules/meson.build | 1 + src/sage/schemes/elliptic_curves/meson.build | 1 + 2 files changed, 2 insertions(+) diff --git a/src/sage/modules/meson.build b/src/sage/modules/meson.build index 48aecfdf0f2..546b5b17a9c 100644 --- a/src/sage/modules/meson.build +++ b/src/sage/modules/meson.build @@ -16,6 +16,7 @@ py.install_sources( 'module.pxd', 'module_functors.py', 'multi_filtered_vector_space.py', + 'numpy_util.pxd', 'quotient_module.py', 'real_double_vector.py', 'submodule.py', diff --git a/src/sage/schemes/elliptic_curves/meson.build b/src/sage/schemes/elliptic_curves/meson.build index 3448c5d1c0a..5f12079dc03 100644 --- a/src/sage/schemes/elliptic_curves/meson.build +++ b/src/sage/schemes/elliptic_curves/meson.build @@ -1,6 +1,7 @@ py.install_sources( 'BSD.py', 'Qcurves.py', + 'addition_formulas_ring.py', 'all.py', 'cardinality.py', 'cm.py', From 6c004389e7c6bdd78f8db7d546c2b0b1595d0a5d Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 13:31:06 +0000 Subject: [PATCH 31/56] fix doctest errors 2 --- .../hyperelliptic_constructor.py | 2 +- .../hyperelliptic_finite_field.py | 2 +- .../hyperelliptic_generic.py | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index fe80f88d25d..7b2cdc9de7f 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -299,7 +299,7 @@ def __defining_polynomial(f, h): raise ValueError(f"arguments f = {f} and h = {h} must define a curve of genus at least one.") # Compute the smooth model for the hyperelliptic curve - # using a weighted projective space (via Toric Variety) + # using a weighted projective space defining_polynomial = __defining_polynomial(f, h) # ----------------------- diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index c102107e304..740001b80a7 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -35,7 +35,7 @@ def random_point(self): sage: C.random_point() # random (4 : 1 : 1) sage: type(C.random_point()) - + """ k = self.base_ring() n = 2 * k.order() + 1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 40ce5aab0ea..83f92514965 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -20,11 +20,19 @@ def __init__(self, defining_polynomial, f, h, genus): # TODO: is this simply genus + 1 self._d = max(h.degree(), (f.degree() + 1) // 2) + assert self._d == self._genus + 1 # Initalise the underlying curve A = WeightedProjectiveSpace([1, self._genus + 1, 1], self._base_ring) WeightedProjectiveCurve.__init__(self, A, defining_polynomial) + def weights(self) -> list[int]: + """ + Return the weights of the weighted projective space this hyperelliptic curve lives in, i.e. + `[1, g + 1, 1]`, where `g` is the genus of the curve. + """ + return [1, self._genus + 1, 1] + def _repr_(self): old_gen = str(self._polynomial_ring.gen()) f, h = self._hyperelliptic_polynomials From d478cb76df5389b06858f6c4d4a9148028d8367d Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 19 Dec 2024 13:42:44 +0000 Subject: [PATCH 32/56] (TODO?) fix normalisation of points in weighted projective space We chose not to normalise all points, but rather just the points where the weight of the coordinate corresponding to the last nonzero entry of the points --- .../schemes/weighted_projective/weighted_projective_point.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index 16d583cc978..3f55954c3c4 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -476,6 +476,9 @@ def __init__(self, X, v, check=True): break if c: weights = X.extended_codomain().weights() + # we only normalise when weights[last] == 1, otherwise we have to take roots + if weights[last] != 1: + break for j in range(last): v[j] /= c ** weights[j] v[last] = R.one() From 34dd9acdad986d56542c302fcbc829b9a71b4a62 Mon Sep 17 00:00:00 2001 From: Sabrina Kunzweiler Date: Thu, 9 Jan 2025 15:40:48 +0100 Subject: [PATCH 33/56] fix and add some docstrings --- .../hyperelliptic_constructor.py | 89 +------ .../hyperelliptic_g2.py | 88 +++++++ .../hyperelliptic_generic.py | 137 +++++++++- .../jacobian_generic.py | 246 +++++++----------- .../jacobian_homset_generic.py | 80 +++++- .../jacobian_homset_split.py | 70 ++++- .../jacobian_morphism.py | 19 ++ 7 files changed, 487 insertions(+), 242 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 7b2cdc9de7f..c38e343c140 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -1,82 +1,6 @@ r""" - Constructor for hyperelliptic curves using the smooth model - - In Sage, a hyperelliptic curve of genus `g` is always - specified by an (affine) equation in Weierstrass form - - .. MATH:: - - y^2 + h(x) y = f(x), - - for some polynomials `h` and `f`. This defines a smooth - model in weighted projective space `\PP(1 : g + 1 : 1)` - - .. MATH:: - Y^2 + H(X,Z) Y = F(X,Z), - - where `H` is the degree `g + 1` homogenization of `h`, - and `F` is the degree `2 g + 2` homogenization of `f`. - - There are either 0, 1 or 2 points at infinity (`Z=0`), - in which case we say that the hyperelliptic curve is - inert, ramified or split, respectively. - - - EXAMPLES: - - We create a hyperelliptic curve over the rationals:: - - sage: R. = PolynomialRing(QQ) - sage: H = HyperellipticCurveSmoothModel(x^8+1, x^4+1); H - Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 - - This hyperelliptic curve has no points at infinity, i.e. `H` is inert:: - sage: H.points_at_infinity() - [] - sage: H.is_inert() - True - - We can extend the base field to obtain a hyperelliptic curve with two points at infinity:: - sage: K. = QQ.extension(x^2+x-1) - sage: HK = H.change_ring(K) - sage: HK.points_at_infinity() - [(1 : alpha : 0), (1 : -alpha - 1 : 0)] - sage: HK.is_split() - True - - The construction of hyperelliptic curves is supported over different fields. The correct class is chosen automatically:: - sage: F = FiniteField(13) - sage: S. = PolynomialRing(F) - sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF - Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^3 + x)*y = x^5 + x^4 + x^3 + x^2 + x + 1 - sage: type(HF) - - sage: Q5 = Qp(5,10) - sage: T. = Q5[] - sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 - Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 = x^7 + 1 + O(5^10) - sage: type(H5) - - - The input polynomials need not be monic:: - sage: R. = QQ[] - sage: HyperellipticCurveSmoothModel(3*x^5+1) - Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 - - The polynomials f and h need to define a smooth curve of genus at least one. In particular polynomials defining elliptic curves are allowed as input. - sage: E = HyperellipticCurveSmoothModel(x^3+1) - sage: E.genus() - 1 - sage: HyperellipticCurveSmoothModel(x) - Traceback (most recent call last): - ... - ValueError: arguments f = x and h = 0 must define a curve of genus at least one. - - The following polynomials define a singular curve and are not allowed as input:: - sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) - Traceback (most recent call last): - ... - ValueError: not a hyperelliptic curve: singularity in the provided affine patch +Constructor for hyperelliptic curves using the smooth model +in weighted projective space `\PP(1 : g + 1 : 1)`. Adapted from /hyperelliptic/constructor.py @@ -182,10 +106,10 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The construction of hyperelliptic curves is supported over different fields. The correct class is chosen automatically:: sage: F = FiniteField(13) sage: S. = PolynomialRing(F) - sage: HF = HyperellipticCurve(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF + sage: HF = HyperellipticCurveSmoothModel(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^3 + x)*y = x^5 + x^4 + x^3 + x^2 + x + 1 sage: type(HF) - + sage: Q5 = Qp(5,10) sage: T. = Q5[] sage: H5 = HyperellipticCurveSmoothModel(x^7 + 1); H5 @@ -213,10 +137,11 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The following polynomials define a singular curve and are not allowed as input:: - sage: C = HyperellipticCurve(x^6 + 2*x - 1, 2*x - 2) + sage: C = HyperellipticCurveSmoothModel(x^6 + 2*x - 1, 2*x - 2) Traceback (most recent call last): ... - ValueError: not a hyperelliptic curve: singularity in the provided affine patch + ValueError: singularity in the provided affine patch + """ # --------------------------- diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py index eecd79b58f5..0af34ca3268 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -50,6 +50,94 @@ def is_odd_degree(self): @cached_method def jacobian(self): + r""" + Returns the Jacobian of the hyperelliptic curve. + + Elements of the Jacobian are represented by tuples + of the form `(u, v : n)`, where + - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, + - n is a non-negative integer + + This tuple represents the equivalence class + + ..MATH:: + + [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], + + where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + points at infinity of the hyperelliptic curve, + + ..MATH:: + D_\infty = + \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. + + Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. + + + EXAMPLES:: + + We construct the Jacobian of a hyperelliptic curve with affine equation + `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. + This curve has two points at infinity:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x + + The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: + + sage: P = H.point([0, 0]) + sage: Q = H.point([-1, -1]) + sage: D1 = J(P,Q); D1 + (x^2 + x, -2*x : 0) + + Elements of the Jacobian can also be constructed by directly providing + the Mumford representation:: + + sage: D1 == J(x^2 + x, -2*x, 0) + True + + We can also embed single points into the Jacobian. Below we construct + `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` + (by default one of the points at infinity):: + + sage: D2 = J(P); D2 + (x, 0 : 0) + sage: P0 = H.distinguished_point(); P0 + (1 : 0 : 0) + sage: D2 == J(P, P0) + True + + We may add elements, or multiply by integers:: + + sage: 2*D1 + (x, -1 : 1) + sage: D1 + D2 + (x^2 + x, -1 : 0) + sage: -D2 + (x, -1 : 1) + + Note that the neutral element is given by `[D_\infty - D_\infty]`, + in particular `n = 1`:: + + sage: J.zero() + (1, 0 : 1) + + There are two more elements of the Jacobian that are only supported + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: + + sage: [P_plus, P_minus] = H.points_at_infinity() + sage: P_plus == P0 + True + sage: J(P_plus,P_minus) + (1, 0 : 2) + sage: J(P_minus, P_plus) + (1, 0 : 0) + """ + + from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_g2_generic import ( HyperellipticJacobian_g2_generic, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 83f92514965..d0ca86d6c99 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -110,7 +110,7 @@ def change_ring(self, R): the base ring to the residue field:: sage: R. = PolynomialRing(QQ); - sage: H = HyperellipticCurve(R([0, -1, 2, 0, -2]), R([0, 1, 0, 1])); #LMFDB label: 763.a.763.1 + sage: H = HyperellipticCurveSmoothModel(R([0, -1, 2, 0, -2]), R([0, 1, 0, 1])); #LMFDB label: 763.a.763.1 sage: H.change_ring(FiniteField(2)) Hyperelliptic Curve over Finite Field of size 2 defined by y^2 + (x^3 + x)*y = x sage: H.change_ring(FiniteField(3)) @@ -121,7 +121,7 @@ def change_ring(self, R): sage: H.change_ring(FiniteField(7)) Traceback (most recent call last): ... - ValueError: not a hyperelliptic curve: singularity in the provided affine patch + ValueError: singularity in the provided affine patch """ from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( HyperellipticCurveSmoothModel, @@ -781,6 +781,137 @@ def set_distinguished_point(self, P0): def jacobian(self): """ Returns the Jacobian of the hyperelliptic curve. + + Elements of the Jacobian are represented by tuples + of the form `(u, v : n)`, where + - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, + - n is a non-negative integer + + This tuple represents the equivalence class + + ..MATH:: + + [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], + + where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + points at infinity of the hyperelliptic curve, + + ..MATH:: + D_\infty = + \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. + + Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. + + Such a representation exists and is unique, unless the genus `g` is odd + and the curve is inert. + + If the hyperelliptic curve is ramified or inert, + then `n` can be deduced from `\deg(u)` and `g`. In these cases, + `n` is omitted in the description. + + + EXAMPLES: + + We construct the Jacobian of a hyperelliptic curve with affine equation + `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. + This curve has two points at infinity:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x + + The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: + + sage: P = H.point([0, 0]) + sage: Q = H.point([-1, -1]) + sage: D1 = J(P,Q); D1 + (x^2 + x, -2*x : 0) + + Elements of the Jacobian can also be constructed by directly providing + the Mumford representation:: + + sage: D1 == J(x^2 + x, -2*x, 0) + True + + We can also embed single points into the Jacobian. Below we construct + `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` + (by default one of the points at infinity):: + + sage: D2 = J(P); D2 + (x, 0 : 0) + sage: P0 = H.distinguished_point(); P0 + (1 : 0 : 0) + sage: D2 == J(P, P0) + True + + We may add elements, or multiply by integers:: + + sage: 2*D1 + (x, -1 : 1) + sage: D1 + D2 + (x^2 + x, -1 : 0) + sage: -D2 + (x, -1 : 1) + + Note that the neutral element is given by `[D_\infty - D_\infty]`, + in particular `n = 1`:: + + sage: J.zero() + (1, 0 : 1) + + There are two more elements of the Jacobian that are only supported + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: + + sage: [P_plus, P_minus] = H.points_at_infinity() + sage: P_plus == P0 + True + sage: J(P_plus,P_minus) + (1, 0 : 2) + sage: J(P_minus, P_plus) + (1, 0 : 0) + + Now, we consider the Jacobian of a hyperelliptic curve with only one + point at infinity, defined over a finite field:: + + sage: K = FiniteField(7) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) + sage: J = Jacobian(H); J + Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 + + Elements on the Jacobian can be constructed as before. But the value + `n` is not used here, since there is exactly one point at infinity:: + + sage: P = H.point([3, 0]) + sage: Q = H.point([5, 1]) + sage: D1 = J(P,Q); D1 + (x^2 + 6*x + 1, 3*x + 5) + sage: D2 = J(x^3 + 3*x^2 + 4*x + 3, 2*x^2 + 4*x) + sage: D1 + D2 + (x^3 + 2, 4) + + Over finite fields, we may also construct random elements and + compute the order of the Jacobian:: + + sage: J.random_element() #random + (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) + sage: J.order() + 344 + + Note that arithmetic on the Jacobian is not implemented if the + underlying hyperelliptic curve is inert (i.e. has no points at + infinity) and the genus is odd:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) + sage: J = Jacobian(H) + sage: J.zero() + Traceback (most recent call last): + ... + ValueError: unable to perform arithmetic for inert models of odd genus + """ from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_generic import ( HyperellipticJacobian_generic, @@ -1026,7 +1157,7 @@ def has_odd_degree_model(self): def _magma_init_(self, magma): """ - Internal function. Returns a string to initialize this elliptic + Internal function. Returns a string to initialize this hyperelliptic curve in the Magma subsystem. EXAMPLES:: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index e9b79cd17a6..183cece0b5b 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -1,150 +1,6 @@ r""" - Jacobians of hyperelliptic curves - - In Sage, a hyperelliptic curve `H` of genus `g` is always - specified by an (affine) equation in Weierstrass form - - .. MATH:: - - H : y^2 + h(x) y = f(x), - - for some polynomials `h` and `f`. - - Elements of the Jacobian of such curves can be identified - with equivalence classes of divisors. In the following, we - denote by `\infty_+`, \infty_-` the points at infinity of `H`, - and we set - - ..MATH:: - D_\infty = - \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. - - Here, `\infty_- = \infty_+`, if `H` is ramified. - Unless the genus `g` is odd and `H` is inert, the divisor - `D_\infty` is rational. In these cases, any element on - the Jacobian admits a unique representative of the form - - ..MATH:: - - [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], - - with `n` and `m` non-negative integers and `P_1 + ... + P_r` - an affine and reduced divisor on `H`. - This module implements the arithemtic for Jacobians of - hyperelliptic curves. Elements of the Jacobian are represented - by tuples of the form `(u, v : n)`, where - - (u,v) is the Mumford representative of the divisor `P_1 + ... + P_r`, - - n is the coefficient of `\infty_+` - - We note that `m = g - \deg(u) - n` and is therefore omitted in - the description. Similarly, if `H` ramified or inert, - then `n` can be deduced from `\deg(u)` and `g`. In these cases, - `n` is omitted in the description as well. - - - EXAMPLES: - - We construct the Jacobian of a hyperelliptic curve with affine equation - `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. - This curve has two points at infinity:: - - sage: R. = QQ[] - sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) - sage: J = Jacobian(H); J - Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x - - The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the - element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: - - sage: P = H.point([0, 0]) - sage: Q = H.point([-1, -1]) - sage: D1 = J(P,Q); D1 - (x^2 + x, -2*x : 0) - - Elements of the Jacobian can also be constructed by directly providing - the Mumford representation:: - - sage: D1 == J(x^2 + x, -2*x, 0) - True - - We can also embed single points into the Jacobian. Below we construct - `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` - (by default one of the points at infinity):: - - sage: D2 = J(P); D2 - (x, 0 : 0) - sage: P0 = H.distinguished_point(); P0 - (1 : 0 : 0) - sage: D2 == J(P, P0) - True - - We may add elements, or multiply by integers:: - - sage: 2*D1 - (x, -1 : 1) - sage: D1 + D2 - (x^2 + x, -1 : 0) - sage: -D2 - (x, -1 : 1) - - Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`:: - - sage: J.zero() - (1, 0 : 1) - - There are two more elements of the Jacobian that are only supported - at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: - - sage: [P_plus, P_minus] = H.points_at_infinity() - sage: P_plus == P0 - True - sage: J(P_plus,P_minus) - (1, 0 : 2) - sage: J(P_minus, P_plus) - (1, 0 : 0) - - Now, we consider the Jacobian of a hyperelliptic curve with only one - point at infinity, defined over a finite field:: - - sage: K = FiniteField(7) - sage: R. = K[] - sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) - sage: J = Jacobian(H); J - Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 - - Elements on the Jacobian can be constructed as before. But the value - `n` is not used here, since there is only one point at infinity:: - - sage: P = H.point([3, 0]) - sage: Q = H.point([5, 1]) - sage: D1 = J(P,Q); D1 - (x^2 + 6*x + 1, 3*x + 5) - sage: D2 = J(x^3 + 3*x^2 + 4*x + 3, 2*x^2 + 4*x) - sage: D1 + D2 - (x^3 + 2, 4) - - Over finite fields, we may also construct random elements and - compute the order of the Jacobian:: - - sage: J.random_element() #random - (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) - sage: J.order() - 344 - - Note that arithmetic on the Jacobian is not implemented if the - underlying hyperelliptic curve is inert (i.e. has no points at - infinity) and the genus is odd:: - - sage: R. = GF(13)[] - sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) - sage: J = Jacobian(H); J - Jacobian of Hyperelliptic Curve over Finite Field of size 13 defined by y^2 + (x^4 + 1)*y = x^8 + 1 - - - TODO: - - finish example for the inert case. + hyperelliptic curves. AUTHORS: @@ -170,17 +26,73 @@ class HyperellipticJacobian_generic(Jacobian_generic): - """ + r""" This is the base class for Jacobians of hyperelliptic curves. + + We represent elements of the Jacobian by tuples of the form + `(u, v : n)`, where + - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, + - n is a non-negative integer + + This tuple represents the equivalence class + + ..MATH:: + + [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], + + where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + points at infinity of the hyperelliptic curve, + + ..MATH:: + D_\infty = + \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. + + Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. + + Such a representation exists and is unique, unless the genus `g` is odd + and the curve is inert. + + If the hyperelliptic curve is ramified or inert, then `n` can be deduced + from `\deg(u)` and `g`. In these cases, `n` is omitted in the description. """ def dimension(self): """ Return the dimension of this Jacobian. + + EXAMPLES: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^2, x^4+1); H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^2 + sage: J = Jacobian(H) + sage: J.dimension() + 3 """ return Integer(self.curve().genus()) def point(self, *mumford, check=True, **kwargs): + r""" + Return a point on the Jacobian, given: + + 1. No arguments or the integer `0`; return `0 \in J`; + + 2. A point `P` on `J = Jac(C)`, return `P`; + + 3. A point `P` on the curve `H` such that `J = Jac(H)`; + return `[P - P_0]`, where `P_0` is the distinguished point of `H`. + By default, `P_0 = \infty`; + + 4. Two points `P, Q` on the curve `H` such that `J = Jac(H)`; + return `[P - Q]`; + + 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; + return `[(u(x), y - v(x))]`. + + .. SEEALSO:: + + :mod:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic`. + """ try: return self.point_homset()(*mumford, check=check) except AttributeError: @@ -207,21 +119,63 @@ def _point(self, *args, **kwds): @cached_method def order(self): + """ + Compute the order of the Jacobian. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.order`. + """ return self.point_homset().order() def count_points(self, *args, **kwds): + """ + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.count_points`. + """ return self.point_homset().count_points(*args, **kwds) def lift_u(self, *args, **kwds): + """ + Return one or all points with given `u`-coordinate. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.lift_u`. + """ return self.point_homset().lift_u(*args, **kwds) def random_element(self, *args, **kwds): + """ + Return a random element of the Jacobian. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.random_element`. + """ return self.point_homset().random_element(*args, **kwds) def points(self, *args, **kwds): + """ + Return all points on the Jacobian. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.points`. + """ + return self.point_homset().points(*args, **kwds) def list(self): + """ + Return all points on the Jacobian. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.points`. + """ + return self.point_homset().points() def __iter__(self): diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index b4a42058e44..ea66c949618 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -24,16 +24,38 @@ def _morphism(self, *args, **kwds): return self._morphism_element(*args, **kwds) def curve(self): - # It is unlike you will want to use this, as this returns the original curve H even when + """ + On input the set of L-rational points of a Jacobian Jac(H) defined over K, + return the curve H. + + NOTE: + The base field of H is not extended to L. + + EXAMPLES:: + sage: R. = QQ[] + sage: K. = QQ.extension(x^2+x+1) + sage: H = HyperellipticCurveSmoothModel(x^6-1) + sage: JK = Jacobian(H)(K); JK + Abelian group of points on Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^6 - 1 + sage: JK.curve() + Hyperelliptic Curve over Rational Field defined by y^2 = x^6 - 1 + """ return self.codomain().curve() def extended_curve(self): """ - TODO: + On input the set of L-rational points of a Jacobian Jac(H) defined over K, + return the curve H with base extended to L. EXAMPLES:: - sage: # TODO + sage: R. = QQ[] + sage: K. = QQ.extension(x^2+x+1) + sage: H = HyperellipticCurveSmoothModel(x^6-1) + sage: JK = Jacobian(H)(K); JK + Abelian group of points on Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^6 - 1 + sage: JK.extended_curve() + Hyperelliptic Curve over Number Field in omega with defining polynomial x^2 + x + 1 defined by y^2 = x^6 - 1 """ # Code from schemes/generic/homset.py if "_extended_curve" in self.__dict__: @@ -125,8 +147,14 @@ def point_to_mumford_coordinates(self, P): of (the affine part of) the divisor [P]. EXAMPLES:: - - sage: # TODO + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - 2*x^4 + 2*x^3 - x^2, 1) + sage: P = H([2,3]); P + (2 : 3 : 1) + sage: JQ = H.jacobian()(QQ) + sage: JQ.point_to_mumford_coordinates(P) + (x - 2, 3) """ R, x = self.extended_curve().polynomial_ring().objgen() X, Y, Z = P._coords @@ -152,7 +180,7 @@ def __call__(self, *args, check=True): return `[P - Q]`; 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; - reutrn `[(u(x), y - v(x))]`. + return `[(u(x), y - v(x))]`. EXAMPLES: @@ -285,6 +313,14 @@ def __call__(self, *args, check=True): def zero(self, check=True): """ Return the zero element of this jacobian homset. + + EXAMPLES: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1) + sage: JQ = H.jacobian()(QQ) + sage: JQ.zero() + (1, 0) """ H = self.extended_curve() R = H.polynomial_ring() @@ -398,19 +434,43 @@ def cantor_composition(self, u1, v1, u2, v2): Return the Cantor composition of ``(u1, v1)`` and ``(u2, v2)``. EXAMPLES:: - - sage: # TODO + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^7 + x^5 + x + 1) + sage: JF = Jacobian(H).point_homset() + sage: (u1, v1) = (x^3 + 4*x^2, 10*x^2 + 7*x + 1) + sage: (u2, v2) = (x^3 + 8*x^2 + 11*x + 2, x^2 + 9*x + 10) + sage: JF.cantor_composition(u1, v1, u2, v2) + (x^6 + 12*x^5 + 4*x^4 + 7*x^3 + 8*x^2, 5*x^5 + 2*x^4 + 12*x^2 + 7*x + 1) """ u3, v3, _ = self._cantor_composition_generic(u1, v1, u2, v2) return u3, v3 def cantor_reduction(self, u0, v0): """ - Return the Cantor reduced ``(u0, v0)``. + Apply one reduction step of Cantor's algorithm to ``(u0, v0)``. + + Note that, in general, several steps are necessary the + representation of a reduced divisor. EXAMPLES:: + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^7 + x^5 + x + 1) + sage: g = H.genus() + sage: JF = Jacobian(H).point_homset() + sage: (u0, v0) = (x^6 + 12*x^5 + 4*x^4 + 7*x^3 + 8*x^2, 5*x^5 + 2*x^4 + 12*x^2 + 7*x + 1) + sage: (u1, v1) = JF.cantor_reduction(u0, v0) + sage: u1.degree() <= g + False + sage: (u2, v2) = JF.cantor_reduction(u1, v1) + sage: u2.degree() <= g + True - sage: # TODO + Applying the reduction step to a reduced divisor might have unintended output, + as is illustrated below. + + sage: (u3, v3) = JF.cantor_reduction(u2, v2) + sage: u3.degree() >= g + True """ return self._cantor_reduction_generic(u0, v0) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index dc25510b970..9f5b27d854f 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -33,6 +33,19 @@ def point_to_mumford_coordinates(self, P): where * n = 1 if P is the point oo+ * n = 0 otherwise . + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 8*x^4 + 6*x^3 + 8*x^2 - 4*x + 1) + sage: P = H([-1, 46, 3]); P + (-1/3 : 46/27 : 1) + sage: O = H([1,1,0]) + sage: JQ = H.jacobian()(QQ) + sage: JQ.point_to_mumford_coordinates(P) + (x + 1/3, 46/27, 0) + sage: JQ.point_to_mumford_coordinates(O) + (1, 0, 1) """ R, x = self.curve().polynomial_ring().objgen() @@ -50,6 +63,11 @@ def point_to_mumford_coordinates(self, P): def __call__(self, *args, check=True): r""" + Return a rational point in the abstract Homset `J(K)`, given: + + 1. No arguments or the integer `0`; return `0 \in J`; + + 2. A point `P` on `J = Jac(C)`, return `P`; 3. Polynomials u,v such that `v^2 + h*v - f = 0 mod u`, returning J(u,v,n) with n = ((g - deg(u))/2).ceil(). @@ -102,7 +120,7 @@ def __call__(self, *args, check=True): The points at infinity may also be embedded into the Jacobian:: - sage: [P0,P1] = H.points_at_infinity() + sage: [P0, P1] = H.points_at_infinity() sage: JH(P0) (1, 0 : 2) sage: JH(P1) @@ -183,6 +201,11 @@ def __call__(self, *args, check=True): def cantor_composition(self, u1, v1, n1, u2, v2, n2): """ + Return the Cantor composition of the divisors represented by + ``(u1, v1, n1)`` and ``(u2, v2, n2)``. + Here ``n1`` and ``n2`` denote the multiplicity of the point + infty_+. + Follows algorithm 3.4 of Efficient Arithmetic on Hyperelliptic Curves With Real Representation @@ -190,6 +213,18 @@ def cantor_composition(self, u1, v1, n1, u2, v2, n2): https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf TODO: when h = 0 we can speed this up. + + EXAMPLES:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 3*x + 2) + sage: JF = Jacobian(H).point_homset() + sage: D1 = [x^2 + 4*x + 3, 2*x + 2, 1] + sage: assert JF(D1) + sage: D2 = [x^3 + 6*x^2 + 6*x, 6*x^2 + 6*x + 3, 0] + sage: assert JF(D2) + sage: D3 = JF.cantor_composition(*D1, *D2); D3 + (x^5 + 3*x^4 + 5*x^3 + 4*x, 3*x^3 + 3*x^2 + 3*x + 3, -1) """ # Collect data from HyperellipticCurve H = self.curve() @@ -205,11 +240,26 @@ def cantor_composition(self, u1, v1, n1, u2, v2, n2): def cantor_reduction(self, u0, v0, n0): """ + Compute the Cantor reduction of ``(u0,v0,n0)``, + where ``(u0,v0)`` represent an affine semi-reduced divisor and + n0 is the multiplicity of the point infty+. + Follows algorithm 3.5 of Efficient Arithmetic on Hyperelliptic Curves With Real Representation David J. Mireles Morales (2008) https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + + EXAMPLES:: + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 3*x + 2) + sage: JF = Jacobian(H).point_homset() + sage: D1 = [x^2 + 4*x + 3, 2*x + 2, 1] + sage: D2 = [x^3 + 6*x^2 + 6*x, 6*x^2 + 6*x + 3, 0] + sage: D3 = JF.cantor_composition(*D1, *D2); D3 + (x^5 + 3*x^4 + 5*x^3 + 4*x, 3*x^3 + 3*x^2 + 3*x + 3, -1) + sage: JF.cantor_reduction(*D3) + (6*x^3 + 3*x^2 + 5*x + 2, 2*x^2 + 3*x + 5, 0) """ # Collect data from HyperellipticCurve H = self.curve() @@ -237,11 +287,29 @@ def cantor_reduction(self, u0, v0, n0): def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): """ + Compute the composition of ``(u0,v0,n0)`` with a divisor supported + at infinity+ (default) or infinity-, and apply a reduction step. + Follows algorithm 3.6 of Efficient Arithmetic on Hyperelliptic Curves With Real Representation David J. Mireles Morales (2008) https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + + EXAMPLES: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 3*x + 2) + sage: JF = Jacobian(H).point_homset() + sage: D1 = [x^2 + 4*x + 3, 2*x + 2, 1] + + Composing at infty+ decreases the value of n0, + while composing at infty- decreases that value. + + sage: JF.cantor_compose_at_infinity(*D1) + (x^2 + 3*x + 6, 5*x + 5, -1) + sage: JF.cantor_compose_at_infinity(*D1, plus=False) + (x^3 + 6*x^2 + x + 4, 5*x + 5, 2) """ # Collect data from HyperellipticCurve H = self.curve() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py index b5d5abe1229..b86faddf23c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -1,5 +1,6 @@ from sage.groups.generic import order_from_multiple from sage.misc.cachefunc import cached_method +from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic from sage.rings.polynomial.polynomial_element import Polynomial from sage.schemes.generic.morphism import SchemeMorphism from sage.structure.element import AdditiveGroupElement @@ -164,6 +165,24 @@ def __bool__(self): @cached_method def order(self): + """ + Returns the order of self. + This is only implemented over finite fields. + + EXAMPLES: + + sage: K = FiniteField(7) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 3*x + 2) + sage: JK = Jacobian(H)(K) + sage: D = JK(x^2 + 5*x + 6, 6*x + 3) + sage: D.order() + 38 + """ + if not isinstance(self.base_ring(), FiniteField_generic): + raise NotImplementedError( + "this is only implemented for Jacobians over a finite field" + ) n = self.parent().order() return order_from_multiple(self, n) From 6064885c95118d8722f73e4a8904681df3f05ae9 Mon Sep 17 00:00:00 2001 From: Sabrina Kunzweiler Date: Thu, 20 Feb 2025 13:50:14 +0100 Subject: [PATCH 34/56] include our files in the documentation and fix some docstrings --- .../en/reference/arithmetic_curves/index.rst | 39 ++++++- .../hyperelliptic_constructor.py | 8 +- .../hyperelliptic_finite_field.py | 5 + .../hyperelliptic_g2.py | 36 +++--- .../hyperelliptic_generic.py | 106 +++++++++++------- .../hyperelliptic_padic_field.py | 5 + .../hyperelliptic_rational_field.py | 3 + .../jacobian_g2_generic.py | 4 + .../jacobian_g2_homset_inert.py | 4 + .../jacobian_g2_homset_ramified.py | 3 + .../jacobian_g2_homset_split.py | 3 + .../jacobian_generic.py | 9 +- .../jacobian_homset_generic.py | 10 +- .../jacobian_homset_inert.py | 3 + .../jacobian_homset_ramified.py | 3 + .../jacobian_homset_split.py | 30 +++-- .../jacobian_morphism.py | 10 +- 17 files changed, 200 insertions(+), 81 deletions(-) diff --git a/src/doc/en/reference/arithmetic_curves/index.rst b/src/doc/en/reference/arithmetic_curves/index.rst index 9e249cf27cc..80a2f0464b9 100644 --- a/src/doc/en/reference/arithmetic_curves/index.rst +++ b/src/doc/en/reference/arithmetic_curves/index.rst @@ -126,4 +126,41 @@ Hyperelliptic curves sage/interfaces/genus2reduction -.. include:: ../footer.txt +Hyperelliptic curves (smooth model) +=================================== + +.. toctree:: + :maxdepth: 1 + + sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor + sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic + sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field + sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field + sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field + sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2 + + sage/schemes/hyperelliptic_curves_smooth_model/invariants + sage/schemes/hyperelliptic_curves_smooth_model/mestre + sage/schemes/hyperelliptic_curves_smooth_model/monsky_washnitzer + +Jacobians of hyperelliptic curves (smooth model) +------------------------------------------------ + +.. toctree:: + :maxdepth: 1 + + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert + + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert + + sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism + + +.. include:: ../footer.txt \ No newline at end of file diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index c38e343c140..6fc9f156eca 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -1,6 +1,6 @@ r""" Constructor for hyperelliptic curves using the smooth model -in weighted projective space `\PP(1 : g + 1 : 1)`. +in weighted projective space `\mathbb{P}(1 : g + 1 : 1)`. Adapted from /hyperelliptic/constructor.py @@ -64,7 +64,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): y^2 + h(x) y = f(x), for some polynomials `h` and `f`. This defines a smooth - model in weighted projective space `\PP(1 : g + 1 : 1)` + model in weighted projective space `\mathbb{P}(1 : g + 1 : 1)` .. MATH:: Y^2 + H(X,Z) Y = F(X,Z), @@ -90,12 +90,14 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^8 + 1 This hyperelliptic curve has no points at infinity, i.e. `H` is inert:: + sage: H.points_at_infinity() [] sage: H.is_inert() True We can extend the base field to obtain a hyperelliptic curve with two points at infinity:: + sage: K. = QQ.extension(x^2+x-1) sage: HK = H.change_ring(K) sage: HK.points_at_infinity() @@ -104,6 +106,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): True The construction of hyperelliptic curves is supported over different fields. The correct class is chosen automatically:: + sage: F = FiniteField(13) sage: S. = PolynomialRing(F) sage: HF = HyperellipticCurveSmoothModel(x^5 + x^4 + x^3 + x^2 + x + 1, x^3 + x); HF @@ -118,6 +121,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The input polynomials need not be monic:: + sage: R. = QQ[] sage: HyperellipticCurveSmoothModel(3*x^5+1) Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index 740001b80a7..34299fc73cc 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -1,3 +1,8 @@ +""" +Hyperelliptic curves (smooth model) over a finite field + +""" + from sage.arith.misc import binomial from sage.libs.pari.all import pari from sage.matrix.constructor import identity_matrix, matrix diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py index 0af34ca3268..731900c70c2 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -1,3 +1,7 @@ +""" +Hyperelliptic curves of genus 2 in the smooth model +""" + from sage.misc.cachefunc import cached_method from sage.schemes.hyperelliptic_curves_smooth_model import ( hyperelliptic_generic, @@ -55,19 +59,17 @@ def jacobian(self): Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where - - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, - - n is a non-negative integer - - This tuple represents the equivalence class - ..MATH:: + - `(u,v)` is the Mumford representative of a divisor `P_1 + ... + P_r`, + - `n` is a non-negative integer - [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], - - where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + This tuple represents the equivalence class + `[P_1 + ... + P_r + n \cdot \infty_+ + m \cdot \infty_- - D_\infty]`, + where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the points at infinity of the hyperelliptic curve, - ..MATH:: + .. MATH:: + D_\infty = \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. @@ -76,17 +78,13 @@ def jacobian(self): EXAMPLES:: - We construct the Jacobian of a hyperelliptic curve with affine equation - `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. - This curve has two points at infinity:: - sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) sage: J = Jacobian(H); J Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the - element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: + element `D_1 = [P - Q] = [P + (-Q) - D_\infty]` on the Jacobian. sage: P = H.point([0, 0]) sage: Q = H.point([-1, -1]) @@ -94,14 +92,14 @@ def jacobian(self): (x^2 + x, -2*x : 0) Elements of the Jacobian can also be constructed by directly providing - the Mumford representation:: + the Mumford representation. sage: D1 == J(x^2 + x, -2*x, 0) True We can also embed single points into the Jacobian. Below we construct `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` - (by default one of the points at infinity):: + (by default one of the points at infinity). sage: D2 = J(P); D2 (x, 0 : 0) @@ -110,7 +108,7 @@ def jacobian(self): sage: D2 == J(P, P0) True - We may add elements, or multiply by integers:: + We may add elements, or multiply by integers. sage: 2*D1 (x, -1 : 1) @@ -120,13 +118,13 @@ def jacobian(self): (x, -1 : 1) Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`:: + in particular `n = 1`. sage: J.zero() (1, 0 : 1) There are two more elements of the Jacobian that are only supported - at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. sage: [P_plus, P_minus] = H.points_at_infinity() sage: P_plus == P0 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index d0ca86d6c99..15719bbdeb1 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -1,3 +1,7 @@ +r""" +Hyperelliptic curves (smooth model) over a general ring +""" + from sage.functions.all import log from sage.misc.cachefunc import cached_method from sage.rings.big_oh import O @@ -58,7 +62,8 @@ def genus(self): """ Return the genus of the hyperelliptic curve. - EXAMPLES: + EXAMPLES:: + sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) sage: H.genus() @@ -159,7 +164,7 @@ def hyperelliptic_polynomials(self): Return the polynomials (f, h) such that C : y^2 + h*y = f - EXAMPLES: + EXAMPLES:: sage: R. = PolynomialRing(QQ) sage: H = HyperellipticCurveSmoothModel(R([0, 1, 2, 0, 0, 1]), R([1, 1, 0, 1])) @@ -200,7 +205,7 @@ def is_split(self): Return True if the curve is split, i.e. there are two rational points at infinity. - EXAMPLES: + EXAMPLES:: sage: R. = PolynomialRing(QQ) sage: H = HyperellipticCurveSmoothModel(x^6+1, x^3+1) @@ -218,7 +223,7 @@ def is_ramified(self): Return True if the curve is ramified, i.e. there is one rational point at infinity. - EXAMPLES: + EXAMPLES:: sage: R. = PolynomialRing(QQ) sage: H = HyperellipticCurveSmoothModel(x^5+1) @@ -236,7 +241,7 @@ def is_inert(self): Return True if the curve is inert, i.e. there are no rational points at infinity. - EXAMPLES: + EXAMPLES:: sage: R. = PolynomialRing(QQ) sage: H = HyperellipticCurveSmoothModel(x^6+1,-x^3+1) @@ -587,6 +592,7 @@ def affine_coordinates(self, P): That is for P = [X,Y,Z], the output is X/Z¸ Y/Z^(g+1). EXAMPLES:: + sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^6 - 1) sage: P = H.point([2,0,2]) @@ -611,22 +617,18 @@ def is_weierstrass_point(self, P): EXAMPLES:: - We consider the hyperelliptic curve `y^2 = x^6 -1` over the rational numbers. - For instance, the point P = (1,0) is a Weierstrass point, - while the points at infinity are not:: - - sage: R. = QQ[] - sage: H = HyperellipticCurveSmoothModel(x^6 - 1) - sage: P = H.point([1,0]) - sage: H.is_weierstrass_point(P) - True - sage: Q = H.point([1,1,0]) - sage: H.is_weierstrass_point(Q) - False + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 - 1) + sage: P = H.point([1,0]) + sage: H.is_weierstrass_point(P) + True + sage: Q = H.point([1,1,0]) + sage: H.is_weierstrass_point(Q) + False This also works for hyperelliptic curves with `h(x)` nonzero. Note that in this case the `y`-coordinate of a Weierstrass point - is not necessarily zero:: + is not necessarily zero. sage: R. = FiniteField(17)[] sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) @@ -639,7 +641,7 @@ def is_weierstrass_point(self, P): TESTS: - Check that the examples from the p-adic file work:: + Check that the examples from the p-adic file work. sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^3-10*x+9) @@ -744,6 +746,18 @@ def distinguished_point(self): """ Return the distinguished point of the hyperelliptic curve. By default, this is one of the points at infinity if possible. + + EXAMPLE:: + + sage: R. = GF(11)[] + sage: H1 = HyperellipticCurveSmoothModel(x^2 + x, x^5 - 3*x + 4) + sage: H1.distinguished_point() + (1 : 0 : 0) + sage: H2 = HyperellipticCurveSmoothModel(x^6 + x^5 + 1,2*x^3) + sage: H2.points_at_infinity() + [] + sage: H2.distinguished_point() + (0 : 1 : 1) """ if hasattr(self, "_distinguished_point"): return self._distinguished_point @@ -770,6 +784,18 @@ def distinguished_point(self): def set_distinguished_point(self, P0): """ Change the distinguished point of the hyperelliptic curve to P0. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: f = x^6 - 6*x^4 + x^2 + 28 + sage: H = HyperellipticCurveSmoothModel(f) + sage: H.distinguished_point() + (1 : 1 : 0) + sage: P = H(2,0) + sage: H.set_distinguished_point(P) + sage: H.distinguished_point() + (2 : 0 : 1) """ try: P0 = self.point(P0) @@ -779,26 +805,26 @@ def set_distinguished_point(self, P0): @cached_method def jacobian(self): - """ + r""" Returns the Jacobian of the hyperelliptic curve. Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where - - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, - - n is a non-negative integer + - `(u,v)` is the Mumford representative of a divisor `P_1 + ... + P_r`, + - `n` is a non-negative integer This tuple represents the equivalence class - ..MATH:: + .. MATH:: [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], - where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the points at infinity of the hyperelliptic curve, - ..MATH:: - D_\infty = - \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. + .. MATH:: + + D_\infty = \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. @@ -810,11 +836,7 @@ def jacobian(self): `n` is omitted in the description. - EXAMPLES: - - We construct the Jacobian of a hyperelliptic curve with affine equation - `y^2 + (x^3 + x + 1) y = 2*x^5 + 4*x^4 + x^3 - x` over the rationals. - This curve has two points at infinity:: + EXAMPLES:: sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) @@ -822,7 +844,7 @@ def jacobian(self): Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the - element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian. sage: P = H.point([0, 0]) sage: Q = H.point([-1, -1]) @@ -830,14 +852,14 @@ def jacobian(self): (x^2 + x, -2*x : 0) Elements of the Jacobian can also be constructed by directly providing - the Mumford representation:: + the Mumford representation. sage: D1 == J(x^2 + x, -2*x, 0) True We can also embed single points into the Jacobian. Below we construct `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` - (by default one of the points at infinity):: + (by default one of the points at infinity). sage: D2 = J(P); D2 (x, 0 : 0) @@ -846,7 +868,7 @@ def jacobian(self): sage: D2 == J(P, P0) True - We may add elements, or multiply by integers:: + We may add elements, or multiply by integers. sage: 2*D1 (x, -1 : 1) @@ -856,13 +878,13 @@ def jacobian(self): (x, -1 : 1) Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`:: + in particular `n = 1`. sage: J.zero() (1, 0 : 1) There are two more elements of the Jacobian that are only supported - at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. sage: [P_plus, P_minus] = H.points_at_infinity() sage: P_plus == P0 @@ -873,7 +895,7 @@ def jacobian(self): (1, 0 : 0) Now, we consider the Jacobian of a hyperelliptic curve with only one - point at infinity, defined over a finite field:: + point at infinity, defined over a finite field. sage: K = FiniteField(7) sage: R. = K[] @@ -882,7 +904,7 @@ def jacobian(self): Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 Elements on the Jacobian can be constructed as before. But the value - `n` is not used here, since there is exactly one point at infinity:: + `n` is not used here, since there is exactly one point at infinity. sage: P = H.point([3, 0]) sage: Q = H.point([5, 1]) @@ -893,7 +915,7 @@ def jacobian(self): (x^3 + 2, 4) Over finite fields, we may also construct random elements and - compute the order of the Jacobian:: + compute the order of the Jacobian. sage: J.random_element() #random (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) @@ -902,7 +924,7 @@ def jacobian(self): Note that arithmetic on the Jacobian is not implemented if the underlying hyperelliptic curve is inert (i.e. has no points at - infinity) and the genus is odd:: + infinity) and the genus is odd. sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 62d87dcb479..3e5e8aa597e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -1,3 +1,7 @@ +""" +Hyperelliptic curves (smooth model) over a p-adic field +""" + from sage.functions.log import log from sage.matrix.constructor import matrix from sage.modules.free_module import VectorSpace @@ -287,6 +291,7 @@ def residue_disc(self, P): (1 : 4 : 1) We note that `P` is in a Weierstrass disc and its reduction is indeed a Weierstrass point. + sage: HK.is_in_weierstrass_disc(P) True sage: HF = HK.change_ring(FiniteField(5)) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py index fb8eea5a43e..61295d6d65c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -1,3 +1,6 @@ +""" +Hyperelliptic curves (smooth model) over the rationals +""" import sage.rings.abc from sage.rings.padics.factory import Qp as pAdicField from sage.schemes.hyperelliptic_curves_smooth_model import hyperelliptic_generic diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py index 23f79df888b..cb917291c1c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py @@ -1,3 +1,7 @@ +""" +Jacobians of genus-2 curves +""" + from sage.schemes.hyperelliptic_curves_smooth_model import ( jacobian_g2_homset_inert, jacobian_g2_homset_ramified, diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py index 310c9fe06d8..378a324d7ce 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py @@ -1,3 +1,7 @@ +""" +Rational point sets of Jacobians of genus-2 curves (inert case) +""" + from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_inert import ( HyperellipticJacobianHomsetInert, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py index c171c4bbd87..ce1b234fe8a 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py @@ -1,3 +1,6 @@ +""" +Rational point sets of Jacobians of genus-2 curves (ramified case) +""" from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_ramified import ( HyperellipticJacobianHomsetRamified, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py index b0385e4b92e..d334915ab87 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py @@ -1,3 +1,6 @@ +""" +Rational point sets of Jacobians of genus-2 curves (split case) +""" from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_split import ( HyperellipticJacobianHomsetSplit, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index 183cece0b5b..360ef7a0d52 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -1,6 +1,5 @@ r""" - This module implements the arithemtic for Jacobians of - hyperelliptic curves. + Jacobian of a general hyperelliptic curve AUTHORS: @@ -36,14 +35,14 @@ class HyperellipticJacobian_generic(Jacobian_generic): This tuple represents the equivalence class - ..MATH:: + .. MATH:: [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the points at infinity of the hyperelliptic curve, - ..MATH:: + .. MATH:: D_\infty = \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. @@ -60,7 +59,7 @@ def dimension(self): """ Return the dimension of this Jacobian. - EXAMPLES: + EXAMPLES:: sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^2, x^4+1); H diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index ea66c949618..3527c905e42 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -1,3 +1,6 @@ +""" +Rational point sets on a Jacobian of a general hyperelliptic curve +""" from sage.misc.cachefunc import cached_method from sage.misc.functional import symbolic_prod as product from sage.misc.prandom import choice @@ -32,6 +35,7 @@ def curve(self): The base field of H is not extended to L. EXAMPLES:: + sage: R. = QQ[] sage: K. = QQ.extension(x^2+x+1) sage: H = HyperellipticCurveSmoothModel(x^6-1) @@ -97,7 +101,7 @@ def _curve_frobenius_roots(self): def cardinality(self, extension_degree=1): r""" - Return `|Jac(C) / \F_{q^n}|`. + Return `|Jac(C) / \mathbb{F}_{q^n}|`. EXAMPLES:: @@ -314,7 +318,7 @@ def zero(self, check=True): """ Return the zero element of this jacobian homset. - EXAMPLES: + EXAMPLES:: sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^5 + 1) @@ -434,6 +438,7 @@ def cantor_composition(self, u1, v1, u2, v2): Return the Cantor composition of ``(u1, v1)`` and ``(u2, v2)``. EXAMPLES:: + sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^7 + x^5 + x + 1) sage: JF = Jacobian(H).point_homset() @@ -453,6 +458,7 @@ def cantor_reduction(self, u0, v0): representation of a reduced divisor. EXAMPLES:: + sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^7 + x^5 + x + 1) sage: g = H.genus() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py index e88b84d8906..4f0facf0a98 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py @@ -1,3 +1,6 @@ +""" +Rational point sets on a Jacobian of a hyperelliptic curve (inert case) +""" from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( HyperellipticJacobianHomset, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py index 8f8045a7500..b4eab03f1fc 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py @@ -1,3 +1,6 @@ +""" +Rational point sets on a Jacobian of a hyperelliptic curve (ramified case) +""" from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( HyperellipticJacobianHomset, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index 9f5b27d854f..7c419c8278f 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -1,3 +1,6 @@ +""" +Rational point sets on a Jacobian of a hyperelliptic curve (split case) +""" from sage.rings.integer import Integer from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import ( HyperellipticJacobianHomset, @@ -20,6 +23,14 @@ def __init__(self, Y, X, **kwds): def zero(self, check=True): """ Return the zero element of the Jacobian + + EXAMPLES :: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: J = Jacobian(H) + sage: J.zero() + (1, 0 : 2) """ g = self.curve().genus() R = self.curve().polynomial_ring() @@ -204,7 +215,7 @@ def cantor_composition(self, u1, v1, n1, u2, v2, n2): Return the Cantor composition of the divisors represented by ``(u1, v1, n1)`` and ``(u2, v2, n2)``. Here ``n1`` and ``n2`` denote the multiplicity of the point - infty_+. + `\infty_+`. Follows algorithm 3.4 of @@ -251,6 +262,7 @@ def cantor_reduction(self, u0, v0, n0): https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf EXAMPLES:: + sage: R. = GF(7)[] sage: H = HyperellipticCurveSmoothModel(x^8 + 3*x + 2) sage: JF = Jacobian(H).point_homset() @@ -286,9 +298,9 @@ def cantor_reduction(self, u0, v0, n0): return u1, v1, n1 def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): - """ - Compute the composition of ``(u0,v0,n0)`` with a divisor supported - at infinity+ (default) or infinity-, and apply a reduction step. + r""" + Compute the composition of `(u_0,v_0,n_0)` with a divisor supported + at `\infty_+` (default) or `\infty_-` , and apply a reduction step. Follows algorithm 3.6 of @@ -296,19 +308,19 @@ def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): David J. Mireles Morales (2008) https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf - EXAMPLES: + EXAMPLES:: sage: R. = GF(7)[] sage: H = HyperellipticCurveSmoothModel(x^8 + 3*x + 2) sage: JF = Jacobian(H).point_homset() sage: D1 = [x^2 + 4*x + 3, 2*x + 2, 1] - Composing at infty+ decreases the value of n0, - while composing at infty- decreases that value. + Composing at `\infty_+` decreases the value of `n_0` , + while composing at `\infty_-` increases that value. - sage: JF.cantor_compose_at_infinity(*D1) + sage: JF.cantor_compose_at_infinity(x^2 + 4*x + 3, 2*x + 2, 1) (x^2 + 3*x + 6, 5*x + 5, -1) - sage: JF.cantor_compose_at_infinity(*D1, plus=False) + sage: JF.cantor_compose_at_infinity(x^2 + 4*x + 3, 2*x + 2, 1, plus=False) (x^3 + 6*x^2 + x + 4, 5*x + 5, 2) """ # Collect data from HyperellipticCurve diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py index b86faddf23c..7abb9068b01 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -1,3 +1,11 @@ +""" +Arithmetic on the Jacobian + +This module implements the group operation in the Picard group of a +hyperelliptic curve, represented as divisors in Mumford +representation, using Cantor's algorithm. +""" + from sage.groups.generic import order_from_multiple from sage.misc.cachefunc import cached_method from sage.rings.finite_rings.finite_field_base import FiniteField as FiniteField_generic @@ -169,7 +177,7 @@ def order(self): Returns the order of self. This is only implemented over finite fields. - EXAMPLES: + EXAMPLES:: sage: K = FiniteField(7) sage: R. = K[] From 17f6f802eac299f4241a0866433e836d7bd2cf40 Mon Sep 17 00:00:00 2001 From: Sabrina Kunzweiler Date: Thu, 20 Feb 2025 14:39:13 +0100 Subject: [PATCH 35/56] fix docstrings again --- .../hyperelliptic_g2.py | 12 +++++----- .../hyperelliptic_generic.py | 24 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py index 731900c70c2..f297dd43567 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -84,7 +84,7 @@ def jacobian(self): Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the - element `D_1 = [P - Q] = [P + (-Q) - D_\infty]` on the Jacobian. + element `D_1 = [P - Q] = [P + (-Q) - D_\infty]` on the Jacobian:: sage: P = H.point([0, 0]) sage: Q = H.point([-1, -1]) @@ -92,14 +92,14 @@ def jacobian(self): (x^2 + x, -2*x : 0) Elements of the Jacobian can also be constructed by directly providing - the Mumford representation. + the Mumford representation:: sage: D1 == J(x^2 + x, -2*x, 0) True We can also embed single points into the Jacobian. Below we construct `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` - (by default one of the points at infinity). + (by default one of the points at infinity):: sage: D2 = J(P); D2 (x, 0 : 0) @@ -108,7 +108,7 @@ def jacobian(self): sage: D2 == J(P, P0) True - We may add elements, or multiply by integers. + We may add elements, or multiply by integers:: sage: 2*D1 (x, -1 : 1) @@ -118,13 +118,13 @@ def jacobian(self): (x, -1 : 1) Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`. + in particular `n = 1`:: sage: J.zero() (1, 0 : 1) There are two more elements of the Jacobian that are only supported - at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: sage: [P_plus, P_minus] = H.points_at_infinity() sage: P_plus == P0 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 15719bbdeb1..94a948b6f4f 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -628,7 +628,7 @@ def is_weierstrass_point(self, P): This also works for hyperelliptic curves with `h(x)` nonzero. Note that in this case the `y`-coordinate of a Weierstrass point - is not necessarily zero. + is not necessarily zero:: sage: R. = FiniteField(17)[] sage: H = HyperellipticCurveSmoothModel(x^6 + 2, x^2 + 1) @@ -753,7 +753,7 @@ def distinguished_point(self): sage: H1 = HyperellipticCurveSmoothModel(x^2 + x, x^5 - 3*x + 4) sage: H1.distinguished_point() (1 : 0 : 0) - sage: H2 = HyperellipticCurveSmoothModel(x^6 + x^5 + 1,2*x^3) + sage: H2 = HyperellipticCurveSmoothModel(x^6 + x^5 + 1, 2*x^3) sage: H2.points_at_infinity() [] sage: H2.distinguished_point() @@ -844,7 +844,7 @@ def jacobian(self): Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the - element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian. + element `D_1 = [P - Q] = [P + (-Q) - D_\infty`] on the Jacobian:: sage: P = H.point([0, 0]) sage: Q = H.point([-1, -1]) @@ -852,14 +852,14 @@ def jacobian(self): (x^2 + x, -2*x : 0) Elements of the Jacobian can also be constructed by directly providing - the Mumford representation. + the Mumford representation:: sage: D1 == J(x^2 + x, -2*x, 0) True We can also embed single points into the Jacobian. Below we construct `D_2 = [P - P_0]`, where `P_0` is the distinguished point of `H` - (by default one of the points at infinity). + (by default one of the points at infinity):: sage: D2 = J(P); D2 (x, 0 : 0) @@ -868,7 +868,7 @@ def jacobian(self): sage: D2 == J(P, P0) True - We may add elements, or multiply by integers. + We may add elements, or multiply by integers:: sage: 2*D1 (x, -1 : 1) @@ -878,13 +878,13 @@ def jacobian(self): (x, -1 : 1) Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`. + in particular `n = 1`:: sage: J.zero() (1, 0 : 1) There are two more elements of the Jacobian that are only supported - at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`. + at infinity: `[\infty_+ - \infty_-]` and `[\infty_- - \infty_+]`:: sage: [P_plus, P_minus] = H.points_at_infinity() sage: P_plus == P0 @@ -895,7 +895,7 @@ def jacobian(self): (1, 0 : 0) Now, we consider the Jacobian of a hyperelliptic curve with only one - point at infinity, defined over a finite field. + point at infinity, defined over a finite field:: sage: K = FiniteField(7) sage: R. = K[] @@ -904,7 +904,7 @@ def jacobian(self): Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 Elements on the Jacobian can be constructed as before. But the value - `n` is not used here, since there is exactly one point at infinity. + `n` is not used here, since there is exactly one point at infinity:: sage: P = H.point([3, 0]) sage: Q = H.point([5, 1]) @@ -915,7 +915,7 @@ def jacobian(self): (x^3 + 2, 4) Over finite fields, we may also construct random elements and - compute the order of the Jacobian. + compute the order of the Jacobian:: sage: J.random_element() #random (x^3 + x^2 + 4*x + 5, 3*x^2 + 3*x) @@ -924,7 +924,7 @@ def jacobian(self): Note that arithmetic on the Jacobian is not implemented if the underlying hyperelliptic curve is inert (i.e. has no points at - infinity) and the genus is odd. + infinity) and the genus is odd:: sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) From fe038174df147fa03cb568fae39a525ea3185394 Mon Sep 17 00:00:00 2001 From: sabrinakunzweiler Date: Thu, 20 Feb 2025 19:07:11 +0100 Subject: [PATCH 36/56] Update src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Frédéric Chapoton --- .../hyperelliptic_curves_smooth_model/jacobian_homset_split.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index 7c419c8278f..a518dca3da3 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -211,7 +211,7 @@ def __call__(self, *args, check=True): return self._morphism_element(self, u, v, n=n, check=check) def cantor_composition(self, u1, v1, n1, u2, v2, n2): - """ + r""" Return the Cantor composition of the divisors represented by ``(u1, v1, n1)`` and ``(u2, v2, n2)``. Here ``n1`` and ``n2`` denote the multiplicity of the point From ab08002943993f29d50a8f6feb7eb4b2cfaaada3 Mon Sep 17 00:00:00 2001 From: sabrinakunzweiler Date: Fri, 21 Feb 2025 13:00:07 +0100 Subject: [PATCH 37/56] Update src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Frédéric Chapoton --- .../hyperelliptic_constructor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 6fc9f156eca..8d3f09c31ee 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -121,7 +121,6 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The input polynomials need not be monic:: - sage: R. = QQ[] sage: HyperellipticCurveSmoothModel(3*x^5+1) Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 From 009a98ee3026d84ad6ba5ea2f140a02ce5e09e51 Mon Sep 17 00:00:00 2001 From: sabrinakunzweiler Date: Fri, 21 Feb 2025 13:00:21 +0100 Subject: [PATCH 38/56] Update src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Frédéric Chapoton --- .../hyperelliptic_curves_smooth_model/hyperelliptic_g2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py index f297dd43567..e9bc70b0744 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -55,7 +55,7 @@ def is_odd_degree(self): @cached_method def jacobian(self): r""" - Returns the Jacobian of the hyperelliptic curve. + Return the Jacobian of the hyperelliptic curve. Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where From 4ae3357b9d2a0d00796a4874e1c16d55db330fbe Mon Sep 17 00:00:00 2001 From: Sabrina Kunzweiler Date: Sat, 22 Feb 2025 14:50:06 +0100 Subject: [PATCH 39/56] delete whitespace --- .../hyperelliptic_finite_field.py | 2 +- .../hyperelliptic_g2.py | 9 ++--- .../hyperelliptic_generic.py | 16 ++++---- .../hyperelliptic_padic_field.py | 6 +-- .../jacobian_generic.py | 38 +++++++++---------- .../jacobian_homset_generic.py | 16 ++++---- .../jacobian_homset_split.py | 14 +++---- 7 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index 34299fc73cc..d5ec9487ff1 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -1062,7 +1062,7 @@ def frobenius_polynomial_matrix(self, M=None, algorithm="hypellfrob"): M = self.frobenius_matrix(N=N, algorithm=algorithm).change_ring(ZZ) # get a_g, ..., a_0 in ZZ (i.e. with correct signs) - f = M.charpoly().list()[g : 2 * g + 1] + f = M.charpoly().list()[g: 2 * g + 1] ppow = p**N f = [x % ppow for x in f] f = [x if 2 * x < ppow else x - ppow for x in f] diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py index e9bc70b0744..1e9670d4156 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -57,7 +57,7 @@ def jacobian(self): r""" Return the Jacobian of the hyperelliptic curve. - Elements of the Jacobian are represented by tuples + Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where - `(u,v)` is the Mumford representative of a divisor `P_1 + ... + P_r`, @@ -65,16 +65,16 @@ def jacobian(self): This tuple represents the equivalence class `[P_1 + ... + P_r + n \cdot \infty_+ + m \cdot \infty_- - D_\infty]`, - where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the + where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the points at infinity of the hyperelliptic curve, .. MATH:: D_\infty = \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. - + Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. - + EXAMPLES:: @@ -135,7 +135,6 @@ def jacobian(self): (1, 0 : 0) """ - from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_g2_generic import ( HyperellipticJacobian_g2_generic, ) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 94a948b6f4f..8821eb0f6c7 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -808,7 +808,7 @@ def jacobian(self): r""" Returns the Jacobian of the hyperelliptic curve. - Elements of the Jacobian are represented by tuples + Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where - `(u,v)` is the Mumford representative of a divisor `P_1 + ... + P_r`, - `n` is a non-negative integer @@ -818,19 +818,19 @@ def jacobian(self): .. MATH:: [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], - - where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the + + where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the points at infinity of the hyperelliptic curve, .. MATH:: D_\infty = \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. - + Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. - - Such a representation exists and is unique, unless the genus `g` is odd - and the curve is inert. - + + Such a representation exists and is unique, unless the genus `g` is odd + and the curve is inert. + If the hyperelliptic curve is ramified or inert, then `n` can be deduced from `\deg(u)` and `g`. In these cases, `n` is omitted in the description. diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 3e5e8aa597e..618a532ed25 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -291,7 +291,7 @@ def residue_disc(self, P): (1 : 4 : 1) We note that `P` is in a Weierstrass disc and its reduction is indeed a Weierstrass point. - + sage: HK.is_in_weierstrass_disc(P) True sage: HF = HK.change_ring(FiniteField(5)) @@ -699,7 +699,7 @@ def coleman_integrals_on_basis(self, P, Q, algorithm=None): try: prof("eval f %s" % R) if PP is None: - L = [-ff(R(QQ[0]), R(QQ[1])) for ff in forms] ##changed + L = [-ff(R(QQ[0]), R(QQ[1])) for ff in forms] # changed elif QQ is None: L = [ff(R(PP[0]), R(PP[1])) for ff in forms] else: @@ -709,7 +709,7 @@ def coleman_integrals_on_basis(self, P, Q, algorithm=None): forms = [ff.change_ring(self.base_ring()) for ff in forms] prof("eval f %s" % self.base_ring()) if PP is None: - L = [-ff(QQ[0], QQ[1]) for ff in forms] ##changed + L = [-ff(QQ[0], QQ[1]) for ff in forms] # changed elif QQ is None: L = [ff(PP[0], PP[1]) for ff in forms] else: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index 360ef7a0d52..e158f580406 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -28,7 +28,7 @@ class HyperellipticJacobian_generic(Jacobian_generic): r""" This is the base class for Jacobians of hyperelliptic curves. - We represent elements of the Jacobian by tuples of the form + We represent elements of the Jacobian by tuples of the form `(u, v : n)`, where - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, - n is a non-negative integer @@ -38,20 +38,20 @@ class HyperellipticJacobian_generic(Jacobian_generic): .. MATH:: [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], - - where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + + where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the points at infinity of the hyperelliptic curve, .. MATH:: D_\infty = \lceil g/2 \rceil \infty_+ + \lfloor g/2 \rfloor \infty_-. - + Here, `\infty_- = \infty_+`, if the hyperelliptic curve is ramified. - - Such a representation exists and is unique, unless the genus `g` is odd - and the curve is inert. - - If the hyperelliptic curve is ramified or inert, then `n` can be deduced + + Such a representation exists and is unique, unless the genus `g` is odd + and the curve is inert. + + If the hyperelliptic curve is ramified or inert, then `n` can be deduced from `\deg(u)` and `g`. In these cases, `n` is omitted in the description. """ @@ -60,7 +60,7 @@ def dimension(self): Return the dimension of this Jacobian. EXAMPLES:: - + sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^2, x^4+1); H Hyperelliptic Curve over Rational Field defined by y^2 + (x^4 + 1)*y = x^2 @@ -86,10 +86,10 @@ def point(self, *mumford, check=True, **kwargs): return `[P - Q]`; 5. Polynomials `(u, v)` such that `v^2 + hv - f \equiv 0 \pmod u`; - return `[(u(x), y - v(x))]`. - - .. SEEALSO:: - + return `[(u(x), y - v(x))]`. + + .. SEEALSO:: + :mod:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic`. """ try: @@ -130,7 +130,7 @@ def order(self): def count_points(self, *args, **kwds): """ .. SEEALSO:: - + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.count_points`. """ return self.point_homset().count_points(*args, **kwds) @@ -140,7 +140,7 @@ def lift_u(self, *args, **kwds): Return one or all points with given `u`-coordinate. .. SEEALSO:: - + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.lift_u`. """ return self.point_homset().lift_u(*args, **kwds) @@ -150,7 +150,7 @@ def random_element(self, *args, **kwds): Return a random element of the Jacobian. .. SEEALSO:: - + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.random_element`. """ return self.point_homset().random_element(*args, **kwds) @@ -160,7 +160,7 @@ def points(self, *args, **kwds): Return all points on the Jacobian. .. SEEALSO:: - + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.points`. """ @@ -171,7 +171,7 @@ def list(self): Return all points on the Jacobian. .. SEEALSO:: - + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.points`. """ diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 3527c905e42..b4ba78015c5 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -33,7 +33,7 @@ def curve(self): NOTE: The base field of H is not extended to L. - + EXAMPLES:: sage: R. = QQ[] @@ -151,7 +151,7 @@ def point_to_mumford_coordinates(self, P): of (the affine part of) the divisor [P]. EXAMPLES:: - + sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^5 - 2*x^4 + 2*x^3 - x^2, 1) sage: P = H([2,3]); P @@ -453,27 +453,27 @@ def cantor_composition(self, u1, v1, u2, v2): def cantor_reduction(self, u0, v0): """ Apply one reduction step of Cantor's algorithm to ``(u0, v0)``. - + Note that, in general, several steps are necessary the representation of a reduced divisor. EXAMPLES:: - + sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^7 + x^5 + x + 1) sage: g = H.genus() sage: JF = Jacobian(H).point_homset() sage: (u0, v0) = (x^6 + 12*x^5 + 4*x^4 + 7*x^3 + 8*x^2, 5*x^5 + 2*x^4 + 12*x^2 + 7*x + 1) sage: (u1, v1) = JF.cantor_reduction(u0, v0) - sage: u1.degree() <= g + sage: u1.degree() <= g False sage: (u2, v2) = JF.cantor_reduction(u1, v1) sage: u2.degree() <= g True - Applying the reduction step to a reduced divisor might have unintended output, - as is illustrated below. - + Applying the reduction step to a reduced divisor might have unintended output, + as is illustrated below. + sage: (u3, v3) = JF.cantor_reduction(u2, v2) sage: u3.degree() >= g True diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index a518dca3da3..5e3cc3ba6e9 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -46,7 +46,7 @@ def point_to_mumford_coordinates(self, P): * n = 0 otherwise . EXAMPLES:: - + sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^6 - 8*x^4 + 6*x^3 + 8*x^2 - 4*x + 1) sage: P = H([-1, 46, 3]); P @@ -213,8 +213,8 @@ def __call__(self, *args, check=True): def cantor_composition(self, u1, v1, n1, u2, v2, n2): r""" Return the Cantor composition of the divisors represented by - ``(u1, v1, n1)`` and ``(u2, v2, n2)``. - Here ``n1`` and ``n2`` denote the multiplicity of the point + ``(u1, v1, n1)`` and ``(u2, v2, n2)``. + Here ``n1`` and ``n2`` denote the multiplicity of the point `\infty_+`. Follows algorithm 3.4 of @@ -252,7 +252,7 @@ def cantor_composition(self, u1, v1, n1, u2, v2, n2): def cantor_reduction(self, u0, v0, n0): """ Compute the Cantor reduction of ``(u0,v0,n0)``, - where ``(u0,v0)`` represent an affine semi-reduced divisor and + where ``(u0,v0)`` represent an affine semi-reduced divisor and n0 is the multiplicity of the point infty+. Follows algorithm 3.5 of @@ -260,7 +260,7 @@ def cantor_reduction(self, u0, v0, n0): Efficient Arithmetic on Hyperelliptic Curves With Real Representation David J. Mireles Morales (2008) https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf - + EXAMPLES:: sage: R. = GF(7)[] @@ -299,7 +299,7 @@ def cantor_reduction(self, u0, v0, n0): def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): r""" - Compute the composition of `(u_0,v_0,n_0)` with a divisor supported + Compute the composition of `(u_0,v_0,n_0)` with a divisor supported at `\infty_+` (default) or `\infty_-` , and apply a reduction step. Follows algorithm 3.6 of @@ -315,7 +315,7 @@ def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): sage: JF = Jacobian(H).point_homset() sage: D1 = [x^2 + 4*x + 3, 2*x + 2, 1] - Composing at `\infty_+` decreases the value of `n_0` , + Composing at `\infty_+` decreases the value of `n_0` , while composing at `\infty_-` increases that value. sage: JF.cantor_compose_at_infinity(x^2 + 4*x + 3, 2*x + 2, 1) From 0fb70afd4afa7755bd4798887ddd7f32fafd4492 Mon Sep 17 00:00:00 2001 From: Sabrina Kunzweiler Date: Tue, 6 May 2025 17:20:22 +0200 Subject: [PATCH 40/56] add more docstrings --- .../hyperelliptic_constructor.py | 3 +- .../hyperelliptic_finite_field.py | 76 ++++- .../hyperelliptic_g2.py | 2 +- .../hyperelliptic_generic.py | 221 ++++++++++----- .../hyperelliptic_padic_field.py | 109 +++++--- .../hyperelliptic_rational_field.py | 10 + .../jacobian_g2_generic.py | 14 +- .../jacobian_generic.py | 12 +- .../jacobian_homset_generic.py | 166 ++++++++++- .../jacobian_homset_inert.py | 36 ++- .../jacobian_homset_ramified.py | 13 + .../jacobian_homset_split.py | 13 + .../jacobian_morphism.py | 261 ++++++++++++++++++ 13 files changed, 816 insertions(+), 120 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index 8d3f09c31ee..a1757624139 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -54,7 +54,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): r""" Constructor function for creating a hyperelliptic curve with - smooth model with polynomials f, h. + smooth model with polynomials `f`, `h`. In Sage, a hyperelliptic curve of genus `g` is always specified by an (affine) equation in Weierstrass form @@ -121,6 +121,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): The input polynomials need not be monic:: + sage: R. = QQ[] sage: HyperellipticCurveSmoothModel(3*x^5+1) Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index d5ec9487ff1..ad332fa93bb 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -1,6 +1,5 @@ """ Hyperelliptic curves (smooth model) over a finite field - """ from sage.arith.misc import binomial @@ -22,10 +21,58 @@ class HyperellipticCurveSmoothModel_finite_field( hyperelliptic_generic.HyperellipticCurveSmoothModel_generic ): """ - TODO: write some examples of this class + Class of hyperelliptic curves (smooth model) over a finite field. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + x^2 + 1); H + Hyperelliptic Curve over Finite Field of size 5 defined by y^2 = x^8 + x^2 + 1 + sage: type(H) + + + Over finite fields, there are methods to construct random points on a + hyperelliptic curve, find all rational points of the curve, or compute + the cardinality over different field extensions:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: H.random_point() # random + (6 : 1 : 1) + sage: H.rational_points() + [(1 : 1 : 0), + (1 : 6 : 0), + (0 : 1 : 1), + (0 : 6 : 1), + (2 : 2 : 1), + (2 : 5 : 1), + (5 : 0 : 1), + (6 : 1 : 1), + (6 : 6 : 1)] + sage: H.count_points(4) + [9, 67, 339, 2443] + + These methods also work in characteristic 2 and 3:: + + sage: R. = GF(4)[] + sage: H = HyperellipticCurveSmoothModel(x^5+1, x^2+1) + sage: H.rational_points() + [(1 : 0 : 0), (0 : z2 : 1), (0 : z2 + 1 : 1), (1 : 0 : 1)] + sage: H.count_points(4) + [4, 24, 64, 288] """ def __init__(self, projective_model, f, h, genus): + """ + Create a hyperelliptic curve over a finite field. + + TESTS:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^8+1) # indirect doctest + sage: type(H) + + """ super().__init__(projective_model, f, h, genus) def random_point(self): @@ -605,7 +652,16 @@ def cardinality_hypellfrob(self, extension_degree=1, algorithm=None): @cached_method def cardinality(self, extension_degree=1): r""" - Count points on a single extension of the base field. + Return the cardinality of the curve over an extension of degree ``extension_degree``. + + INPUT: + + - ``self`` - Hyperelliptic Curve over a finite field, `\GF{q}` + - ``extensions_degree`` - positive integer (default: ``1```) + + OUTPUT: + + - The cardinality of ``self`` over an extension of degree ``extension_degree``. EXAMPLES:: @@ -1370,9 +1426,11 @@ def _Cartier_matrix_cached(self): # This is what is called from command line def Cartier_matrix(self): r""" + Return the Cartier matrix of the hyperelliptic curve. + INPUT: - - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + - ``H`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` OUTPUT: @@ -1568,9 +1626,11 @@ def frob_mat(Coeffs, k): # This is the function which is actually called by command line def Hasse_Witt(self): r""" + Return the Hasse--Witt matrix of the hyperelliptic curve. + INPUT: - - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + - ``H`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` OUTPUT: @@ -1626,6 +1686,8 @@ def Hasse_Witt(self): def a_number(self): r""" + Return the `a`-number of the hyperelliptic curve. + INPUT: - ``E``: Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` @@ -1669,9 +1731,11 @@ def a_number(self): def p_rank(self): r""" + Return the `p`-rank of the hyperelliptic curve. + INPUT: - - ``E`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` + - ``H`` : Hyperelliptic Curve of the form `y^2 = f(x)` over a finite field, `\GF{q}` OUTPUT: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py index 1e9670d4156..4a2f2b62bd1 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -80,7 +80,7 @@ def jacobian(self): sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) - sage: J = Jacobian(H); J + sage: J = H.jacobian(); J Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 8821eb0f6c7..b4374a18cb5 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -16,6 +16,20 @@ class HyperellipticCurveSmoothModel_generic(WeightedProjectiveCurve): def __init__(self, defining_polynomial, f, h, genus): + """ + Create a hyperelliptic curve as a weighted projective curve. + + TESTS:: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic import HyperellipticCurveSmoothModel_generic + sage: R. = QQ[] + sage: f = x^4 + x^3 + 3*x^2 + x + 2 + sage: h = x^3 + x^2 + x + sage: S. = QQ[] + sage: F = Y^2 + Y*(X^3 + X^2*Z + X*Z^2) + X^4*Z^2 + X^3*Z^3 + 3*X^2*Z^4 + X*Z^5 + 2*Z^6 + sage: H = HyperellipticCurveSmoothModel_generic(F, f, h, 2); H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x^2 + x)*y = x^4 + x^3 + 3*x^2 + x + 2 + """ self._genus = genus self._hyperelliptic_polynomials = (f, h) @@ -34,10 +48,32 @@ def weights(self) -> list[int]: """ Return the weights of the weighted projective space this hyperelliptic curve lives in, i.e. `[1, g + 1, 1]`, where `g` is the genus of the curve. + + EXAMPLES:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^5 - 1) + sage: H.weights() + [1, 3, 1] + + sage: H = HyperellipticCurveSmoothModel(x^9 - 1) + sage: H.weights() + [1, 5, 1] + """ return [1, self._genus + 1, 1] def _repr_(self): + """ + Return a representation of the hyperelliptic curve. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 + x, 3*x^2) + sage: H + Hyperelliptic Curve over Rational Field defined by y^2 + (3*x^2)*y = x^5 + x + """ old_gen = str(self._polynomial_ring.gen()) f, h = self._hyperelliptic_polynomials @@ -94,7 +130,7 @@ def base_ring(self): def change_ring(self, R): """ - Return this hyperelliptic curve over a new ring "R". + Return this hyperelliptic curve over a new ring ``R``. EXAMPLES:: @@ -121,7 +157,7 @@ def change_ring(self, R): sage: H.change_ring(FiniteField(3)) Hyperelliptic Curve over Finite Field of size 3 defined by y^2 + (x^3 + x)*y = x^4 + 2*x^2 + 2*x - Note that this only works when the curve has good reduction at p:: + Note that this only works when the curve has good reduction at `p`:: sage: H.change_ring(FiniteField(7)) Traceback (most recent call last): @@ -141,8 +177,8 @@ def change_ring(self, R): def polynomial_ring(self): """ - Returns the parent of f,h, where self is the hyperelliptic curve - defined by y^2 + h*y = f. + Return the parent of `f,h`, where ``self`` is the hyperelliptic curve + defined by `y^2 + h*y = f`. EXAMPLES:: @@ -161,8 +197,8 @@ def polynomial_ring(self): def hyperelliptic_polynomials(self): """ - Return the polynomials (f, h) such that - C : y^2 + h*y = f + Return the polynomials `(f, h)` such that + `C : y^2 + h*y = f`. EXAMPLES:: @@ -179,9 +215,25 @@ def hyperelliptic_polynomials(self): def roots_at_infinity(self): """ - Compute the roots of: Y^2 + h[d]Y - f[2d] = 0. + Compute the roots of: `Y^2 + h_{g+1} Y - f_{2g+2} = 0`. + When the curve is ramified, we expect one root, when the curve is inert or split we expect zero or two roots. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^7 + 1) + sage: H.roots_at_infinity() + [0] + + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: H.roots_at_infinity() + [1, -1] + + sage: H = HyperellipticCurveSmoothModel(-x^8 + 1) + sage: H.roots_at_infinity() + [] """ if hasattr(self, "_alphas"): return self._alphas @@ -195,14 +247,16 @@ def roots_at_infinity(self): # Handle the ramified case if coeff.is_zero(): return [coeff] - return f[2 * d].sqrt(all=True) + if not coeff.is_square(): + return [] + return coeff.sqrt(all=True) self._alphas = (x**2 + x * h[d] - f[2 * d]).roots(multiplicities=False) return self._alphas def is_split(self): """ - Return True if the curve is split, i.e. there are two rational + Return ``True`` if the curve is split, i.e. there are two rational points at infinity. EXAMPLES:: @@ -220,7 +274,7 @@ def is_split(self): def is_ramified(self): """ - Return True if the curve is ramified, i.e. there is one rational + Return ``True`` if the curve is ramified, i.e. there is one rational point at infinity. EXAMPLES:: @@ -238,7 +292,7 @@ def is_ramified(self): def is_inert(self): """ - Return True if the curve is inert, i.e. there are no rational + Return ``True`` if the curve is inert, i.e. there are no rational points at infinity. EXAMPLES:: @@ -261,15 +315,39 @@ def is_inert(self): def infinite_polynomials(self): """ - TODO: the name of this function could be better? + Return `G^\\pm(x)` for curves in the split degree model. - Computes G^±(x) for curves in the split degree model used for - Cantor composition with points at infinity when performing - arithmetic with the Jac(H). + This is used for Cantor composition with points at infinity + when performing arithmetic on the Jacobian. See Definition 4 + in [GHM2008]_. + + TODO: the name of this function could be better? .. SEEALSO:: :func:`~sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_split.cantor_compose_at_infinity` + + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x^4 + 1) + sage: H.infinite_polynomials() + (x^3 + 1/2*x, -x^3 - 1/2*x) + sage: H = HyperellipticCurveSmoothModel(4*x^6 + x^4 + 1) + sage: H.infinite_polynomials() + (2*x^3 + 1/4*x, -2*x^3 - 1/4*x) + + The function is only defined for hyperelliptic curves with + two rational points at infinity:: + + sage: H = HyperellipticCurveSmoothModel(2*x^6 + x^4 + 1) + sage: H.is_split() + False + sage: H.infinite_polynomials() + Traceback (most recent call last): + ... + ValueError: hyperelliptic curve does not have the split model """ if hasattr(self, "_infinite_polynomials"): return self._infinite_polynomials @@ -308,14 +386,23 @@ def infinite_polynomials(self): def points_at_infinity(self): """ - Compute the points at infinity on the curve. Assumes we are using - a weighted projective model for the curve + Compute the points at infinity on the curve. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurveSmoothModel(x^6 + 1, x^3 + 1) + sage: H.points_at_infinity() + [] + sage: K. = QQ.extension(x^2 + x -1) + sage: H.change_ring(K).points_at_infinity() + [(1 : omega : 0), (1 : -omega - 1 : 0)] """ return [self.point([1, y, 0], check=True) for y in self.roots_at_infinity()] def is_x_coord(self, x): """ - Return True if ``x`` is the `x`-coordinate of a point on this curve. + Return ``True`` if ``x`` is the `x`-coordinate of a point on this curve. .. SEEALSO:: @@ -588,8 +675,8 @@ def lift_x(self, x, all=False): def affine_coordinates(self, P): """ - Returns the affine coordinates of a point P of self. - That is for P = [X,Y,Z], the output is X/Z¸ Y/Z^(g+1). + Return the affine coordinates of a point ``P`` of ``self``. + That is for `P = [X,Y,Z]`, the output is `X/Z, Y/Z^{(g+1)}`. EXAMPLES:: @@ -612,7 +699,8 @@ def affine_coordinates(self, P): def is_weierstrass_point(self, P): """ - Return True if P is a Weierstrass point of self. + Return ``True`` if ``P`` is a Weierstrass point of ``self``. + TODO: It would be better to define this function for points directly. EXAMPLES:: @@ -708,7 +796,7 @@ def rational_weierstrass_points(self): def hyperelliptic_involution(self, P): """ - Returns the image of P under the hyperelliptic involution. + Return the image of ``P`` under the hyperelliptic involution. EXAMPLES:: @@ -783,7 +871,7 @@ def distinguished_point(self): def set_distinguished_point(self, P0): """ - Change the distinguished point of the hyperelliptic curve to P0. + Change the distinguished point of the hyperelliptic curve to ``P0``. EXAMPLES:: @@ -806,11 +894,13 @@ def set_distinguished_point(self, P0): @cached_method def jacobian(self): r""" - Returns the Jacobian of the hyperelliptic curve. + Return the Jacobian of the hyperelliptic curve. Elements of the Jacobian are represented by tuples of the form `(u, v : n)`, where + - `(u,v)` is the Mumford representative of a divisor `P_1 + ... + P_r`, + - `n` is a non-negative integer This tuple represents the equivalence class @@ -840,7 +930,7 @@ def jacobian(self): sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) - sage: J = Jacobian(H); J + sage: J = H.jacobian(); J Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x + 1)*y = 2*x^5 + 4*x^4 + x^3 - x The points `P = (0, 0)` and `Q = (-1, -1)` are on `H`. We construct the @@ -878,7 +968,7 @@ def jacobian(self): (x, -1 : 1) Note that the neutral element is given by `[D_\infty - D_\infty]`, - in particular `n = 1`:: + in particular `n = \lceil g/2 \rceil`:: sage: J.zero() (1, 0 : 1) @@ -900,7 +990,7 @@ def jacobian(self): sage: K = FiniteField(7) sage: R. = K[] sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2) - sage: J = Jacobian(H); J + sage: J = H.jacobian(); J Jacobian of Hyperelliptic Curve over Finite Field of size 7 defined by y^2 = x^7 + 3*x + 2 Elements on the Jacobian can be constructed as before. But the value @@ -928,7 +1018,7 @@ def jacobian(self): sage: R. = GF(13)[] sage: H = HyperellipticCurveSmoothModel(x^8+1,x^4+1) - sage: J = Jacobian(H) + sage: J = H.jacobian() sage: J.zero() Traceback (most recent call last): ... @@ -944,7 +1034,7 @@ def jacobian(self): @cached_method def projective_curve(self): """ - Returns a singular plane model of the hyperelliptic curve self. + Return a (singular) plane model of the hyperelliptic curve ``self``. TODO: renaming to plane_model ? @@ -958,7 +1048,7 @@ def projective_curve(self): Projective Plane Curve over Finite Field of size 11 defined by -x^6 + y^2*z^4 - 2*z^6 - Note that the projective coordinates of points on H and their images in C + Note that the projective coordinates of points on `H` and their images in `C` are in general not the same:: sage: P = H.point([9,4,2]) @@ -974,7 +1064,7 @@ def projective_curve(self): (10, 6) sage: Q = C.point([10,6,1]) - The model C has one singular point at infinity, while H is non-singular + The model `C` has one singular point at infinity, while `H` is non-singular and has two points at infinity. sage: H.points_at_infinity() @@ -996,7 +1086,7 @@ def rational_points(self, **kwds): ALGORITHM: We use :meth:`points_at_infinity` to compute the points at infinity, and - `sage.schemes.generic.algebraic_scheme.rational_points` on this curve's + :meth:`sage.schemes.generic.algebraic_scheme.rational_points` on this curve's :meth:`projective_curve` for the affine points. EXAMPLES: @@ -1037,7 +1127,7 @@ def rational_points(self, **kwds): def is_singular(self, *args, **kwargs): r""" - Returns False, because hyperelliptic curves are smooth projective + Return ``False``, because hyperelliptic curves are smooth projective curves, as checked on construction. EXAMPLES:: @@ -1051,7 +1141,7 @@ def is_singular(self, *args, **kwargs): def is_smooth(self): r""" - Returns True, because hyperelliptic curves are smooth projective + Return ``True``, because hyperelliptic curves are smooth projective curves, as checked on construction. EXAMPLES:: @@ -1069,9 +1159,9 @@ def is_smooth(self): def odd_degree_model(self): r""" - Return an odd degree model of self, or raise ValueError if one does not + Return an odd degree model of ``self``, or raise ``ValueError`` if one does not exist over the field of definition. The term odd degree model refers to - a model of the form y^2 = f(x) with deg(f) = 2*g + 1. + a model of the form `y^2 = f(x)` with `\deg(f) = 2 g + 1`. EXAMPLES:: @@ -1095,11 +1185,11 @@ def odd_degree_model(self): with defining polynomial x^2 + 3 with b = 1.732050807568878?*I defined by y^2 = -4*b*x^5 - 14*x^4 - 20*b*x^3 - 35*x^2 + 6*b*x + 1 - Of course, ``Hp2`` and ``Hp3`` are isomorphic over the composite - extension. One consequence of this is that odd degree models - reduced over "different" fields should have the same number of - points on their reductions. 43 and 67 split completely in the - compositum, so when we reduce we find: + Of course, ``Hp2`` and ``Hp3`` are isomorphic over the composite + extension. One consequence of this is that odd degree models + reduced over "different" fields should have the same number of + points on their reductions. 43 and 67 split completely in the + compositum, so when we reduce we find:: sage: # needs sage.rings.number_field sage: P2 = K2.factor(43)[0][0] @@ -1123,7 +1213,7 @@ def odd_degree_model(self): sage: H.change_ring(GF(67)).odd_degree_model().frobenius_polynomial() # needs sage.rings.finite_rings x^4 - 8*x^3 + 150*x^2 - 536*x + 4489 - The case where `h(x)` is nonzero is also supported:: + The case where `h(x)` is nonzero is also supported:: sage: HyperellipticCurveSmoothModel(x^5 + 1, 1).odd_degree_model() Hyperelliptic Curve over Rational Field defined by y^2 = 4*x^5 + 5 @@ -1154,7 +1244,7 @@ def odd_degree_model(self): def has_odd_degree_model(self): r""" - Return True if an odd degree model of self exists over the field of definition; False otherwise. + Return ``True`` if an odd degree model of ``self`` exists over the field of definition; ``False`` otherwise. Use ``odd_degree_model`` to calculate an odd degree model. @@ -1179,7 +1269,7 @@ def has_odd_degree_model(self): def _magma_init_(self, magma): """ - Internal function. Returns a string to initialize this hyperelliptic + Internal function. Return a string to initialize this hyperelliptic curve in the Magma subsystem. EXAMPLES:: @@ -1212,7 +1302,12 @@ def monsky_washnitzer_gens(self): EXAMPLES:: - TODO + sage: R. = QQ[] + sage: H = HyperellipticCurve(x^5+1) + sage: [x0, y0] = H.monsky_washnitzer_gens(); x0, y0 + (x, y*1) + sage: x0^10 + (1-2*y^2+y^4)*1 """ from sage.schemes.hyperelliptic_curves_smooth_model import monsky_washnitzer @@ -1221,7 +1316,7 @@ def monsky_washnitzer_gens(self): def invariant_differential(self): """ - Returns `dx/2y`, as an element of the Monsky-Washnitzer cohomology + Return `dx/2y`, as an element of the Monsky-Washnitzer cohomology of ``self``. EXAMPLES:: @@ -1243,13 +1338,13 @@ def invariant_differential(self): def local_coordinates_at_nonweierstrass(self, P, prec=20, name="t"): """ - For a non-Weierstrass point `P = (a,b)` on the hyperelliptic - curve `y^2 + y*h(x) = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, + For a non-Weierstrass point ``P = (a,b)`` on the hyperelliptic + curve `y^2 + h(x) * y = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = x - a` is the local parameter. INPUT: - - ``P = (a, b)`` -- a non-Weierstrass point on self + - ``P = (a, b)`` -- a non-Weierstrass point on ``self`` - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: ``t``) @@ -1318,13 +1413,13 @@ def local_coordinates_at_nonweierstrass(self, P, prec=20, name="t"): def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): """ - For a finite Weierstrass point P = (a,b) on the hyperelliptic - curve `y^2 + h(x)*y = f(x)`, returns `(x(t), y(t))` such that + For a finite Weierstrass point ``P = (a,b)`` on the hyperelliptic + curve `y^2 + h(x)*y = f(x)`, return `(x(t), y(t))` such that `y(t)^2 + h(x(t))*y(t) = f(x(t))`, where `t = y - b` is the local parameter. INPUT: - - ``P`` -- a finite Weierstrass point on self + - ``P`` -- a finite Weierstrass point on ``self`` - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: `t`) @@ -1336,7 +1431,7 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): EXAMPLES: We compute the local coordinates of the Weierstrass point `P = (4,0)` - on the hyperelliptic curve `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`:: + on the hyperelliptic curve `y^2 = x^5 - 23 x^3 + 18 x^2 + 40 x`:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) @@ -1354,7 +1449,7 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name="t"): True We compute the local coordinates at the Weierstrass point `(1,-1)` - of the hyperelliptic curve `y^2 + (x^3 + 1)*y = -x^2`:: + of the hyperelliptic curve `y^2 + (x^3 + 1) y = -x^2`:: sage: H = HyperellipticCurveSmoothModel(-x^2, x^3+1) sage: P = H(1,-1) @@ -1414,7 +1509,7 @@ def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): EXAMPLES: We compute the local coordinates at the point at infinity of the - hyperelliptic curve `y^2 = x^5 - 5*x^2 + 1`:: + hyperelliptic curve `y^2 = x^5 - 5 x^2 + 1`:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^5 - 5*x^2 + 1) @@ -1433,7 +1528,7 @@ def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): The method also works when `h` is nonzero. We compute the local coordinates of the point at infinity of the hyperelliptic curve - `y^2 + y = x^5 - 9*x^4 + 14*x^3 - 19*x^2 + 11*x - 6`:: + `y^2 + y = x^5 - 9 x^4 + 14 x^3 - 19 x^2 + 11 x - 6`:: sage: H = HyperellipticCurveSmoothModel(x^5-9*x^4+14*x^3-19*x^2+11*x-6,1) sage: f,h = H.hyperelliptic_polynomials() @@ -1476,10 +1571,10 @@ def local_coordinates_at_infinity_ramified(self, prec=20, name="t"): def local_coordinates_at_infinity_split(self, P, prec=20, name="t"): """ - For a point at infinity P = (a:b:0) on a hyperelliptic curve with + For a point at infinity ``P = (a:b:0)`` on a hyperelliptic curve with split model `y^2 + h(x)*y = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`. - Here `t = a/x` is the local parameter at P. + Here `t = a/x` is the local parameter at ``P``. INPUT: @@ -1551,14 +1646,14 @@ def local_coordinates_at_infinity_split(self, P, prec=20, name="t"): def local_coord(self, P, prec=20, name="t"): """ - For point `P = (a,b)` on the hyperelliptic curve - `y^2 + y*h(x) = f(x)`, return `(x(t), y(t))` such that + For point ``P = (a,b)`` on the hyperelliptic curve + `y^2 + h(x)*y = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 + h(x(t))*y(t) = f(x(t))`, where `t` is the local parameter at that point. INPUT: - - ``P`` -- a point on self + - ``P`` -- a point on ``self`` - ``prec`` -- desired precision of the local coordinates - ``name`` -- generator of the power series ring (default: ``t``) @@ -1570,7 +1665,7 @@ def local_coord(self, P, prec=20, name="t"): EXAMPLES: We compute the local coordinates of several points of the curve with - defining equation `y^2 = x^5 - 23*x^3 + 18*x^2 + 40*x`:: + defining equation `y^2 = x^5 - 23 x^3 + 18 x^2 + 40 x`:: sage: R. = QQ['x'] sage: H = HyperellipticCurveSmoothModel(x^5 - 23*x^3 + 18*x^2 + 40*x) @@ -1583,7 +1678,7 @@ def local_coord(self, P, prec=20, name="t"): t^-5 - 69*t^-1 + 54*t - 1467*t^3 + 3726*t^5 + O(t^6)) We compute the local coordinates of several points of the curve with - defining equation `y^2 + (x^3 + 1)*y = x^4 + 2*x^3 + x^2 - x`. :: + defining equation `y^2 + (x^3 + 1) y = x^4 + 2 x^3 + x^2 - x`. :: sage: H = HyperellipticCurveSmoothModel(x^4+2*x^3+x^2-x,x^3+1) sage: H.local_coord(H(1,-3,1), prec=5) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 618a532ed25..9018da2f6e2 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -1,5 +1,13 @@ """ Hyperelliptic curves (smooth model) over a p-adic field + +The functions in this module were prototyped at the 2007 Arizona Winter School by +Robert Bradshaw and Ralf Gerkmann, working with Miljan Brakovevic and +Kiran Kedlaya. +They were adapted by Sabrina Kunzweiler, Gareth Ma, Giacomo Pope (2024) +to work for hyperelliptic curves in weighted projective space. + +All of the below is with respect to the Monsky Washnitzer cohomology. """ from sage.functions.log import log @@ -20,25 +28,60 @@ class HyperellipticCurveSmoothModel_padic_field( hyperelliptic_generic.HyperellipticCurveSmoothModel_generic ): + + r""" + Class of hyperelliptic curves (smooth model) over a `p`-adic field. + In particular this class implements the necessary functionality to + compute Coleman integrals. + + EXAMPLES: + + This is the curve with LMFDB label 1091.a.1091.1 :: + + sage: K = pAdicField(7,10) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1/4*x^4 -3/2* x^3 -1/4*x^2 + 1/2*x + 1/4) + sage: P0 = H(1,0,0) + sage: Q = H(-1,-1/2) + sage: P = H(0,1/2) + + We note that `[P-P_0]` has order `7` on the Jacobian, hence the + Coleman integral `\int_P^{P_0} dx/2y` vanishes. + On the other hand `[Q-Q_0]` has infinite order, and the corresponding + integral is nonzero:: + + sage: w = H.invariant_differential() + sage: H.coleman_integral(w,P,P0) + O(7^9) + sage: H.coleman_integral(w,Q,P0) + 3*7 + 4*7^2 + 5*7^4 + 2*7^5 + 2*7^6 + 6*7^7 + 4*7^8 + O(7^9) + """ + def __init__(self, projective_model, f, h, genus): + """ + Create a hyperelliptic curve over a p-adic field. + + TESTS:: + + sage: R. = Qp(5,10)[] + sage: H = HyperellipticCurveSmoothModel(-x^2, x^3 + 1) + sage: H # indirect doctest + Hyperelliptic Curve over 5-adic Field with capped relative precision 10 defined by y^2 + (x^3 + 1 + O(5^10))*y = (4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10))*x^2 + """ super().__init__(projective_model, f, h, genus) - # The functions below were prototyped at the 2007 Arizona Winter School by - # Robert Bradshaw and Ralf Gerkmann, working with Miljan Brakovevic and - # Kiran Kedlaya - # All of the below is with respect to the Monsky Washnitzer cohomology. def local_analytic_interpolation(self, P, Q): """ - For points `P`, `Q` in the same residue disc, - this constructs an interpolation from `P` to `Q` + For points ``P``, ``Q`` in the same residue disc, + construct an interpolation from ``P`` to ``Q`` (in weighted homogeneous coordinates) in a power series in the local parameter `t`, with precision equal to the `p`-adic precision of the underlying ring. INPUT: - - P and Q points on self in the same residue disc + - ``P`` and ``Q`` points on ``self`` in the same residue disc OUTPUT: @@ -738,9 +781,9 @@ def coleman_integral(self, w, P, Q, algorithm="None"): INPUT: - - w differential (if one of P,Q is Weierstrass, w must be odd) - - P point on self - - Q point on self + - ``w`` differential (if one of P,Q is Weierstrass, w must be odd) + - ``P`` point on self + - ``Q`` point on self - algorithm (optional) = None (uses Frobenius) or teichmuller (uses Teichmuller points) OUTPUT: @@ -1153,13 +1196,13 @@ def get_boundary_point(self, curve_over_extn, P): def P_to_S(self, P, S): r""" - Given a finite Weierstrass point `P` and a point `S` - in the same disc, computes the Coleman integrals `\{\int_P^S x^i dx/2y \}_{i=0}^{2g-1}` + Given a finite Weierstrass point ``P`` and a point ``S`` + in the same disc, compute the Coleman integrals `\{\int_P^S x^i dx/2y \}_{i=0}^{2g-1}` INPUT: - - P: finite Weierstrass point - - S: point in disc of P + - ``P``: finite Weierstrass point + - ``S``: point in disc of ``P`` OUTPUT: @@ -1195,15 +1238,15 @@ def P_to_S(self, P, S): def coleman_integral_P_to_S(self, w, P, S): r""" - Given a finite Weierstrass point `P` and a point `S` - in the same disc, computes the Coleman integral `\int_P^S w` + Given a finite Weierstrass point ``P`` and a point ``S`` + in the same disc, compute the Coleman integral `\int_P^S w` INPUT: - - w: differential - - P: Weierstrass point - - S: point in the same disc of P (S is defined over an extension of `\QQ_p`; coordinates - of S are given in terms of uniformizer `a`) + - ``w``: differential + - ``P``: Weierstrass point + - ``S``: point in the same disc of ``P`` (``S`` is defined over an extension of `\QQ_p`; coordinates + of ``S`` are given in terms of uniformizer `a`) OUTPUT: @@ -1240,16 +1283,16 @@ def coleman_integral_P_to_S(self, w, P, S): def S_to_Q(self, S, Q): r""" - Given `S` a point on self over an extension field, computes the + Given ``S`` a point on ``self`` over an extension field, compute the Coleman integrals `\{\int_S^Q x^i dx/2y \}_{i=0}^{2g-1}` - **one should be able to feed `S,Q` into coleman_integral, + **one should be able to feed ``S,Q`` into coleman_integral, but currently that segfaults** INPUT: - - S: a point with coordinates in an extension of `\QQ_p` (with unif. a) - - Q: a non-Weierstrass point defined over `\QQ_p` + - ``S``: a point with coordinates in an extension of `\QQ_p` (with unif. `a`) + - ``Q``: a non-Weierstrass point defined over `\QQ_p` OUTPUT: @@ -1334,14 +1377,14 @@ def coleman_integral_S_to_Q(self, w, S, Q): r""" Compute the Coleman integral `\int_S^Q w` - **one should be able to feed `S,Q` into coleman_integral, + **one should be able to feed ``S,Q`` into coleman_integral, but currently that segfaults** INPUT: - - w: a differential - - S: a point with coordinates in an extension of `\QQ_p` - - Q: a non-Weierstrass point defined over `\QQ_p` + - ``w``: a differential + - ``S``: a point with coordinates in an extension of `\QQ_p` + - ``Q``: a non-Weierstrass point defined over `\QQ_p` OUTPUT: @@ -1390,14 +1433,14 @@ def coleman_integral_S_to_Q(self, w, S, Q): def coleman_integral_from_weierstrass_via_boundary(self, w, P, Q, d): r""" Computes the Coleman integral `\int_P^Q w` via a boundary point - in the disc of `P`, defined over a degree `d` extension + in the disc of ``P``, defined over a degree ``d`` extension INPUT: - - w: a differential - - P: a Weierstrass point - - Q: a non-Weierstrass point - - d: degree of extension where coordinates of boundary point lie + - ``w``: a differential + - ``P``: a Weierstrass point + - ``Q``: a non-Weierstrass point + - ``d``: degree of extension where coordinates of boundary point lie OUTPUT: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py index 61295d6d65c..881d6da6b3d 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -10,6 +10,16 @@ class HyperellipticCurveSmoothModel_rational_field( hyperelliptic_generic.HyperellipticCurveSmoothModel_generic ): def __init__(self, projective_model, f, h, genus): + """ + Create a hyperelliptic curve over the rationals. + + TESTS:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(-x^2, x^3 + 1) + sage: H + Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + 1)*y = -x^2 + """ super().__init__(projective_model, f, h, genus) def matrix_of_frobenius(self, p, prec=20): diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py index cb917291c1c..d76c809dd8b 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py @@ -19,7 +19,19 @@ class HyperellipticJacobian_g2_generic(HyperellipticJacobian_generic): """ def _point_homset(self, *args, **kwds): - # TODO: make a constructor for this?? + """ + Create the point Hom-set of the Jacobian of a genus-2 curve. + + TODO: make a constructor for this?? + + TESTS:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(-x^6 + 15*x^4 - 75*x^2 -56, x^3 + x) + sage: J = Jacobian(H)(QQ) + sage: type(J) # indirect doctest + + """ H = self.curve() if H.is_ramified(): return jacobian_g2_homset_ramified.HyperellipticJacobianHomsetRamified_g2( diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index e158f580406..37b98772887 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -98,6 +98,13 @@ def point(self, *mumford, check=True, **kwargs): raise ValueError("Arguments must determine a valid Mumford divisor.") def _point_homset(self, *args, **kwds): + """ + Create the Hom-Set of the Jacobian according to the type of `self`. + + TESTS:: + + + """ # TODO: make a constructor for this?? H = self.curve() if H.is_ramified(): @@ -168,7 +175,7 @@ def points(self, *args, **kwds): def list(self): """ - Return all points on the Jacobian. + Return all rational elements of the Jacobian. .. SEEALSO:: @@ -178,6 +185,9 @@ def list(self): return self.point_homset().points() def __iter__(self): + """ + Return an iterator over the elements of the Jacobian. + """ yield from self.list() rational_points = points diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index b4ba78015c5..3d378830da5 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -16,14 +16,52 @@ class HyperellipticJacobianHomset(SchemeHomset_points): + r""" + Set of rational points of the Jacobian. + """ def __init__(self, Y, X, **kwds): + r""" + Create the Hom-set of a Jacobian. + + The `K`-rational points of the Jacobian `J` over `k` + are identified with the set of morphisms + `\mathrm{Spec}(K) \to J`. + + INPUT: + + - ``Y`` -- domain (Spec(K) where K is the field of definition) + - ``X`` -- codomain (the Jacobian) + + EXAMPLE:: + + sage: from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic import HyperellipticJacobianHomset + sage: R. = QQ[] + sage: H = HyperellipticCurve(2*x^4 - x^3 + 4*x^2 - x, x^3 + x) + sage: J = H.jacobian() + sage: JQ = HyperellipticJacobianHomset(Spec(QQ), J); JQ + Abelian group of points on Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 + (x^3 + x)*y = 2*x^4 - x^3 + 4*x^2 - x + """ SchemeHomset_points.__init__(self, Y, X, **kwds) self._morphism_element = None def _repr_(self) -> str: + """ + Return the string representation of the Jacobian Hom-set. + + EXAMPLES:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 2*x + 1) + sage: J = H.jacobian() + sage: J(GF(13)) # indirect doctest + Abelian group of points on Jacobian of Hyperelliptic Curve over Finite Field of size 13 defined by y^2 = x^5 + 2*x + 1 + """ return f"Abelian group of points on {self.codomain()}" def _morphism(self, *args, **kwds): + """ + TODO + """ return self._morphism_element(*args, **kwds) def curve(self): @@ -75,13 +113,21 @@ def extended_curve(self): @cached_method def order(self): """ - Compute the order of the Jacobian + Compute the order of the Jacobian. TODO: currently using lazy methods by calling sage - EXAMPLES:: + EXAMPLES: - sage: # TODO + We compute the order of a superspecial hyperelliptic curve of genus 3:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^8 - 1) + sage: J = H.jacobian() + sage: J(GF(7)).order() == (7+1)^3 + True + sage: J(GF(7^2)).order() == (7+1)^6 + True """ return sum(self.extended_curve().frobenius_polynomial()) @@ -90,9 +136,20 @@ def _curve_frobenius_roots(self): r""" Return the roots of the charpoly of frobenius on the extended curve. - EXAMPLES:: + EXAMPLES: - sage: # TODO + The following genus-2 curve is supersingular. The roots of Frobenius + over `\mathbb{F}_5` are given by `\pm \sqrt{5}`, whereas over + `\FF_{5^2}` Frobenius acts as multiplication by `-5`:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6-1) + sage: J = Jacobian(H) + sage: rts = J(GF(5))._curve_frobenius_roots() + sage: rts[0]^2 == -5 + True + sage: J(GF(5^2))._curve_frobenius_roots() + [-5, -5, -5, -5] """ from sage.rings.qqbar import QQbar @@ -108,11 +165,10 @@ def cardinality(self, extension_degree=1): sage: R. = GF(5)[] sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) sage: J = H.jacobian() - sage: J.count_points(10) == [J.change_ring(GF((5, k))).order() for k in range(1, 11)] - True - sage: J2 = J(GF((5, 2))) - sage: J2.count_points(5) == J.count_points(10)[1::2] - True + sage: J(GF(5)).cardinality() + 31 + sage: J(GF(5^2)).cardinality() + 961 """ K = self.extended_curve().base_ring() if not isinstance(K, FiniteField_generic): @@ -126,11 +182,23 @@ def cardinality(self, extension_degree=1): def count_points(self, n=1): """ - TODO + Count the number of points of the Jacobian over all finite extensions + of the base fields of degree less than or equal to n. + + INPUT: + + - ``n`` -- a positive integer EXAMPLES:: - sage: # TODO + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: J = H.jacobian() + sage: J.count_points(10) == [J.change_ring(GF((5, k))).order() for k in range(1, 11)] + True + sage: J2 = J(GF((5, 2))) + sage: J2.count_points(5) == J.count_points(10)[1::2] + True """ try: n = Integer(n) @@ -336,7 +404,8 @@ def __cantor_double_generic(self, u1, v1): Returns the Cantor composition of (u1, v1) with (u1, v1) together with the degree of the polynomial ``s`` which is - needed for computing weights for the split and inert models + needed for computing weights for the split and inert models. + """ f, h = self.extended_curve().hyperelliptic_polynomials() @@ -362,6 +431,17 @@ def _cantor_composition_generic(self, u1, v1, u2, v2): The Cantor composition of ``(u1, v1)`` with ``(u2, v2)``, together with the degree of the polynomial ``s`` which is needed for computing the weights for the split and inert models. + + TESTS:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 2*x + 1) + sage: JK = H.jacobian()(GF(13)) + sage: (u1,v1) = (x^2 + 1, 10*x + 6) + sage: (u2,v2) = (x + 5, R(8)) + sage: JK._cantor_composition_generic(u1,v1,u2,v2) + (x^3 + 5*x^2 + x + 5, 9*x^2 + 10*x + 2, 0) + """ # Collect data from HyperellipticCurve H = self.extended_curve() @@ -422,6 +502,17 @@ def _cantor_reduction_generic(self, u0, v0): The `u`-coordinate of the output is not necessarily monic. That step is delayed to :meth:`cantor_reduction` to save time. + + TESTS:: + + sage: R. = GF(13)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 2*x + 1) + sage: JK = H.jacobian()(GF(13)) + sage: (u1,v1) = (x^2 + 1, 10*x + 6) + sage: (u2,v2) = (x + 5, R(8)) + sage: (u3,v3,s) = JK._cantor_composition_generic(u1,v1,u2,v2) + sage: JK._cantor_reduction_generic(u3,v3) + (12*x^2 + 8*x + 11, 9*x + 3) """ # Collect data from HyperellipticCurve H = self.extended_curve() @@ -610,6 +701,22 @@ def _random_element_cover(self, degree=None): Return a random element from the Jacobian. Distribution is not uniformly random, but returns the entire group. + + TESTS:: + + sage: K = FiniteField(101) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^7 + x) + sage: JK = H.jacobian()(K) + sage: JK._random_element_cover() # random + (x^3 + 29*x^2 + 81*x + 66, 96*x^2 + 22*x + 32) + + sage: K = FiniteField(2) + sage: R. = K[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1, x) + sage: JK = H.jacobian()(K) + sage: JK._random_element_cover() # random + (x + 1, 1) """ H = self.extended_curve() R = H.polynomial_ring() @@ -715,6 +822,39 @@ def points(self): .. WARNING:: This code is not efficient at all. + + EXAMPLES:: + + sage: R. = GF(3)[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 2*x + 1) + sage: J3 = H.jacobian()(GF(3)) + sage: Pts = J3.points() + sage: len(Pts) + 94 + sage: Pts[10] + (x^2 + 2, x) + sage: Pts[-1] + (x^3 + 2*x^2 + 2*x + 1, 1) + + TESTS: + + The function also works in the split and inert cases. + + sage: R. = GF(3)[] + sage: H = HyperellipticCurveSmoothModel(x^8 + x + 2) + sage: H.is_split() + True + sage: J3 = H.jacobian()(GF(3)) + sage: Pts = J3.points() + sage: len(Pts) + 35 + + sage: H = HyperellipticCurveSmoothModel(2*x^8 + 2*x + 1) + sage: H.is_inert() + True + sage: J3 = H.jacobian()(GF(3)) + sage: Pts = J3.points(); len(Pts) + 29 """ H = self.extended_curve() R = H.polynomial_ring() diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py index 4f0facf0a98..0bf66ab054f 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py @@ -11,12 +11,46 @@ class HyperellipticJacobianHomsetInert(HyperellipticJacobianHomset): def __init__(self, Y, X, **kwds): + """ + Create the Jacobian Hom-set of a hyperelliptic curve without + rational points at infinity. + + TESTS:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(3*x^6 + 2*x^2 + 1) + sage: assert H.is_inert() + sage: JK = Jacobian(H)(GF(7)) + sage: type(JK) + + """ super().__init__(Y, X, **kwds) self._morphism_element = MumfordDivisorClassFieldInert def zero(self, check=True): """ - Return the zero element of the Jacobian + Return the zero element of the Jacobian. + + The Mumford presentation of the zero element is given by + `(1, 0 : g/2)`, `g` is the genus of the hyperelliptic curve. + + NOTE: We require that the genus is even if the hyperelliptic + curve is inert. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^6 + 1) + sage: H.is_inert() + True + sage: J = H.jacobian() + sage: J.zero() + (1, 0 : 1) + + sage: H = HyperellipticCurveSmoothModel(3*x^10 + 1) + sage: J = H.jacobian() + sage: J.zero() + (1, 0 : 2) """ g = self.curve().genus() if g % 2: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py index b4eab03f1fc..cdbae300b1e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py @@ -11,5 +11,18 @@ class HyperellipticJacobianHomsetRamified(HyperellipticJacobianHomset): def __init__(self, Y, X, **kwds): + """ + Create the Jacobian Hom-set of a hyperelliptic curve with + precisely one rational points at infinity. + + TESTS:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 2*x^2 + 1) + sage: assert H.is_ramified() + sage: JK = Jacobian(H)(GF(7)) + sage: type(JK) + + """ super().__init__(Y, X, **kwds) self._morphism_element = MumfordDivisorClassFieldRamified diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index 5e3cc3ba6e9..feadfdf64a2 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -17,6 +17,19 @@ class HyperellipticJacobianHomsetSplit(HyperellipticJacobianHomset): def __init__(self, Y, X, **kwds): + """ + Create the Jacobian Hom-set of a hyperelliptic curve with + two rational points at infinity. + + TESTS:: + + sage: R. = GF(7)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 2*x^2 + 1) + sage: assert H.is_split() + sage: JK = Jacobian(H)(GF(7)) + sage: type(JK) + + """ super().__init__(Y, X, **kwds) self._morphism_element = MumfordDivisorClassFieldSplit diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py index 7abb9068b01..8aae18a14b9 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -22,6 +22,17 @@ class MumfordDivisorClassField(AdditiveGroupElement, SchemeMorphism): """ def __init__(self, parent, u, v, check=True): + """ + Create an element of the Jacobian of a hyperelliptic curve. + + EXAMPLES:: + + sage: R. = GF(19)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + x, x^2 + 1) + sage: J = H.jacobian() + sage: D = J(x^2 + 14*x + 16, 3*x + 4); D # indirect doctest + (x^2 + 14*x + 16, 3*x + 4) + """ SchemeMorphism.__init__(self, parent) if not isinstance(u, Polynomial) or not isinstance(v, Polynomial): @@ -46,6 +57,18 @@ def __init__(self, parent, u, v, check=True): self._v = v def parent(self): + """ + Return the parent of the divisor class. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurve(x^5 - 1) + sage: J = H.jacobian() + sage: D = J(x-1,0) + sage: D.parent() + Set of rational points of Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^5 - 1 + """ return self._parent def scheme(self): @@ -80,6 +103,19 @@ def scheme(self): return self.codomain() def _repr_(self) -> str: + """ + Return the Mumford presentation of the divisor class. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: H = HyperellipticCurve(x^5 + 2*x^3 + x, x^3 + 1) + sage: P = H([0,-1]) + sage: Q = H([0,0]) + sage: J = H.jacobian() + sage: D = J(P,Q); D # indirect doctest + (x^2, y + x + 1) + """ return f"({self._u}, {self._v})" def is_zero(self): @@ -163,12 +199,51 @@ def __iter__(self): yield from [self._u, self._v] def __getitem__(self, n): + """ + Return the n-th item in the Mumford presentation of the divisor. + + TESTS:: + + sage: R. = GF(23)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + x + 1, x^2 + x + 1) + sage: J = H.jacobian() + sage: D = J(x^2 + 21*x + 10, 14*x + 22) + sage: D[0] # indirect doctest + x^2 + 21*x + 10 + sage: D[1] # indirect doctest + 14*x + 22 + """ return (self._u, self._v)[n] def __hash__(self): + """ + Compute the hash value of this element. + + TESTS:: + + sage: R. = GF(23)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + x + 1, x^2 + x + 1) + sage: J = H.jacobian() + sage: D = J(x^2 + 21*x + 10, 14*x + 22) + sage: hash(D) == hash(2*D) + False + """ return hash(tuple(self)) def __bool__(self): + """ + Return "True" if this is not the zero element of the Jacobian. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^2 + x, x^3 + 1) + sage: J = H.jacobian() + sage: bool(J(x+1,0)) + True + sage: bool(J.zero()) + False + """ return not self.is_zero() @cached_method @@ -197,10 +272,40 @@ def order(self): def degree(self): """ Returns the degree of the affine part of the divisor. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(6*x^5 + 9*x^4 - x^3 - 3*x^2, 1) + sage: J = H.jacobian() + sage: J.zero().degree() + 0 + sage: J(x, 0).degree() + 1 + sage: J(x^2 + 1/2*x, -x - 1).degree() + 2 """ return self._u.degree() def _add_(self, other): + """ + Add `other` to the divisor `self`. + + TESTS:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x^4 + x^2 - x, 1) + sage: J = H.jacobian() + sage: D1 = J(x^2 + x, x) + sage: D2 = J(x^2, x - 1) + sage: D1 + D2 + (x^2 + x, -1) + + sage: Jp = J.change_ring(GF(13)) + sage: D1, D2, D3 = [Jp.random_element() for i in range(3)] + sage: D1 + D2 + D3 == D3 + D1 + D2 + True + """ # `other` should be coerced automatically before reaching here assert isinstance(other, type(self)) @@ -225,6 +330,23 @@ def _add_(self, other): return self._parent(u3, v3, check=False) def _neg_(self): + """ + Multiply the divisor by `[-1]`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 2*x^4 + 3*x^3 + 2*x^2 + x, 1) + sage: P = H(1,0,0) + sage: Q = H(0,0) + sage: J = H.jacobian() + sage: D = J(P,Q); D + (x, -1) + sage: -D #indirect doctest + (x, 0) + sage: J(Q,P) == - D #indirect doctest + True + """ _, h = self.parent().curve().hyperelliptic_polynomials() u0, v0 = self.uv() @@ -242,6 +364,21 @@ def _neg_(self): class MumfordDivisorClassFieldRamified(MumfordDivisorClassField): def __init__(self, parent, u, v, check=True): + """ + Create an element of the Jacobian of a ramified + hyperelliptic curve. + + TESTS:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^7 + 3*x + 2, x^2 + 1) + sage: H.is_ramified() + True + sage: J = Jacobian(H) + sage: D = J(x^3 + x^2 + 3*x + 1, 4*x^2 + 2*x + 3) + sage: type(D) + + """ if not parent.curve().is_ramified(): raise TypeError("hyperelliptic curve must be ramified") @@ -250,6 +387,21 @@ def __init__(self, parent, u, v, check=True): class MumfordDivisorClassFieldInert(MumfordDivisorClassField): def __init__(self, parent, u, v, check=True): + """ + Create an element of the Jacobian of a ramified + hyperelliptic curve. + + TESTS:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(2*x^6 + 1) + sage: H.is_inert() + True + sage: J = Jacobian(H) + sage: D = J(x^2 + x + 4, 4*x) + sage: type(D) + + """ if not parent.curve().is_inert(): raise TypeError("hyperelliptic curve must be inert") @@ -261,11 +413,39 @@ def __init__(self, parent, u, v, check=True): super().__init__(parent, u, v, check=check) def __repr__(self): + """ + Return a representation of the element. + + TESTS:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(2*x^6 + 1) + sage: H.is_inert() + True + sage: J = Jacobian(H) + sage: D = J(x^2 + x + 4, 4*x); D # indirect doctest + (x^2 + x + 4, 4*x : 0) + """ return f"({self._u}, {self._v} : {self._n})" class MumfordDivisorClassFieldSplit(MumfordDivisorClassField): def __init__(self, parent, u, v, n=0, check=True): + """ + Create an element of the Jacobian of a split + hyperelliptic curve. + + TESTS:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 1) + sage: H.is_split() + True + sage: J = Jacobian(H) + sage: D = J(x^2 + 3, 3) + sage: type(D) + + """ if not parent.curve().is_split(): raise TypeError("hyperelliptic curve must be split") @@ -278,9 +458,48 @@ def __init__(self, parent, u, v, n=0, check=True): super().__init__(parent, u, v, check=check) def __repr__(self): + """ + Return a representation of the element. + + TESTS:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 1) + sage: J = Jacobian(H) + sage: D = J(x^2 + 3, 3); D + (x^2 + 3, 3 : 0) + """ return f"({self._u}, {self._v} : {self._n})" def is_zero(self): + """ + Return ``True`` if this element is zero. + + EXAMPLES: + + We consider a genus-3 hyperelliptic curve with two points + at infinity ``P_0``, ``P_1``. Elements of the Jacobian are + represented as ``[D - 2P_0 - P_1]``. + In particular the zero element has Mumford presentation + `(1, 0 : 2)`:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 + 1) + sage: J = Jacobian(H) + sage: D = J(1,0,2); D + (1, 0 : 2) + sage: D.is_zero() + True + + There are more elements that are only supported at infinity, + but are non-zero:: + + sage: [P0,P1] = H.points_at_infinity() + sage: D = J(P0,P1); D + (1, 0 : 3) + sage: D.is_zero() + False + """ g = self._parent.curve().genus() return self._u.is_one() and self._v.is_zero() and self._n == (g + 1) // 2 @@ -309,15 +528,43 @@ def __iter__(self): yield from [self._u, self._v, self._n] def __hash__(self): + """ + Compute the hash value of this element. + + TESTS:: + + sage: R. = GF(23)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + 1) + sage: J = H.jacobian() + sage: D1 = J(x + 6, 17, 0) + sage: D2 = J(x + 6, 17, 1) + sage: hash(D1) == hash(D2) + False + """ return hash(tuple(self)) def _add_(self, other): r""" + Return the sum of the two elements. + Follows algorithm 3.7 of Efficient Arithmetic on Hyperelliptic Curves With Real Representation David J. Mireles Morales (2008) https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + + EXAMPLES:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^5 - x^4 + x^2 - x, x^3 + 1) + sage: J = Jacobian(H) + sage: D1 = J(x^2 + x, 0) + sage: D2 = J(x^2, -x) + sage: D1 + D2 + (x^2 - 3/2*x + 1/2, -5/2*x + 1/2 : 0) + sage: D3 = J(x - 1, -2) + sage: D1 + D3 + (x^2 - 1/2*x, 5/4*x - 1 : 0) """ # Ensure we are adding two divisors assert isinstance(other, type(self)) @@ -358,11 +605,25 @@ def _add_(self, other): def _neg_(self): r""" + Return the divisor multiplied by -1. + Follows algorithm 3.8 of Efficient Arithmetic on Hyperelliptic Curves With Real Representation David J. Mireles Morales (2008) https://www.math.auckland.ac.nz/~sgal018/Dave-Mireles-Full.pdf + + TESTS:: + + sage: R. = GF(101)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1, x^3 + x) + sage: assert H.is_split() + sage: J = Jacobian(H) + sage: [P0,P1] = H.points_at_infinity() + sage: P = H.random_point() + sage: Q = H.hyperelliptic_involution(P) + sage: - J(P,P0) == J(Q,P1) + True """ # Collect data from HyperellipticCurve H = self.parent().curve() From 679b2e4f7142263e4f3ea9c2f91b523118a3ea09 Mon Sep 17 00:00:00 2001 From: Sabrina Kunzweiler Date: Wed, 7 May 2025 12:27:02 +0200 Subject: [PATCH 41/56] minor --- src/doc/en/reference/references/index.rst | 4 ++++ .../hyperelliptic_constructor.py | 2 +- .../hyperelliptic_generic.py | 6 +++++- .../jacobian_generic.py | 8 +++++--- .../jacobian_homset_generic.py | 10 +++++----- .../jacobian_homset_split.py | 17 +++++++++-------- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index ca81cda3f75..2387912efac 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2904,6 +2904,10 @@ REFERENCES: .. [GHJV1994] \E. Gamma, R. Helm, R. Johnson, J. Vlissides, *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley (1994). ISBN 0-201-63361-2. + +.. [GHM2008] \S. Galbraith, M. Harrison, D. Mireles Morales, + *Efficient hyperelliptic arithmetic using balanced representation for divisors*, + Algorithmic Number Theory: 8th International Symposium, ANTS-VIII Banff, Canada, May 17-22, 2008 Proceedings 8. .. [Gil1959] Edgar Nelson Gilbert. "Random Graphs", Annals of Mathematical Statistics. 30 (4): 1141–1144, 1959. diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index a1757624139..d78876363bf 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -126,7 +126,7 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): sage: HyperellipticCurveSmoothModel(3*x^5+1) Hyperelliptic Curve over Rational Field defined by y^2 = 3*x^5 + 1 - The polynomials f and h need to define a smooth curve of genus at + The polynomials `f` and `h` need to define a smooth curve of genus at least one. In particular polynomials defining elliptic curves are allowed as input:: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index b4374a18cb5..f8affa9d625 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -417,7 +417,7 @@ def is_x_coord(self, x): OUTPUT: - A bool stating whether or not `x` is a x-coordinate of a point on the curve + A bool stating whether or not ``x`` is the `x`-coordinate of a point on the curve EXAMPLES: @@ -835,6 +835,10 @@ def distinguished_point(self): Return the distinguished point of the hyperelliptic curve. By default, this is one of the points at infinity if possible. + .. SEEALSO:: + + :func:`~sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic.set_distinguished_point` + EXAMPLE:: sage: R. = GF(11)[] diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index 37b98772887..b54dcc47b07 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -30,8 +30,10 @@ class HyperellipticJacobian_generic(Jacobian_generic): We represent elements of the Jacobian by tuples of the form `(u, v : n)`, where - - (u,v) is the Mumford representative of a divisor `P_1 + ... + P_r`, - - n is a non-negative integer + + - `(u,v)` is the Mumford representative of a divisor `P_1 + ... + P_r`, + + - `n` is a non-negative integer This tuple represents the equivalence class @@ -39,7 +41,7 @@ class HyperellipticJacobian_generic(Jacobian_generic): [P_1 + ... + P_r + n \cdot \infty_+ + m\cdot \infty_- - D_\infty], - where `m = g - \deg(u) - n`, and `\infty_+`, \infty_-` are the + where `m = g - \deg(u) - n`, and `\infty_+`, `\infty_-` are the points at infinity of the hyperelliptic curve, .. MATH:: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 3d378830da5..834c2c3fe52 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -66,11 +66,11 @@ def _morphism(self, *args, **kwds): def curve(self): """ - On input the set of L-rational points of a Jacobian Jac(H) defined over K, - return the curve H. + On input the set of `L`-rational points of a Jacobian `Jac(H)` defined over `K`, + return the curve `H`. NOTE: - The base field of H is not extended to L. + The base field of `H` is not extended to `L`. EXAMPLES:: @@ -86,8 +86,8 @@ def curve(self): def extended_curve(self): """ - On input the set of L-rational points of a Jacobian Jac(H) defined over K, - return the curve H with base extended to L. + On input the set of `L`-rational points of a Jacobian `Jac(H)` defined over `K`, + return the curve `H` with base extended to `L`. EXAMPLES:: diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py index feadfdf64a2..2aee823378c 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -51,12 +51,13 @@ def zero(self, check=True): return self._morphism_element(self, R.one(), R.zero(), n) def point_to_mumford_coordinates(self, P): - """ - On input a point P, return the Mumford coordinates - of (the affine part of) the divisor [P] and an integer n, + r""" + On input a point ``P``, return the Mumford coordinates + of (the affine part of) the divisor `[P]` and an integer `n`, where - * n = 1 if P is the point oo+ - * n = 0 otherwise . + + - `n = 1` if ``P`` is the point `\infty_+` + - `n = 0` otherwise . EXAMPLES:: @@ -263,10 +264,10 @@ def cantor_composition(self, u1, v1, n1, u2, v2, n2): return u3, v3, n3 def cantor_reduction(self, u0, v0, n0): - """ + r""" Compute the Cantor reduction of ``(u0,v0,n0)``, where ``(u0,v0)`` represent an affine semi-reduced divisor and - n0 is the multiplicity of the point infty+. + ``n0`` is the multiplicity of the point `\infty_+`. Follows algorithm 3.5 of @@ -329,7 +330,7 @@ def cantor_compose_at_infinity(self, u0, v0, n0, plus=True): sage: D1 = [x^2 + 4*x + 3, 2*x + 2, 1] Composing at `\infty_+` decreases the value of `n_0` , - while composing at `\infty_-` increases that value. + while composing at `\infty_-` increases that value:: sage: JF.cantor_compose_at_infinity(x^2 + 4*x + 3, 2*x + 2, 1) (x^2 + 3*x + 6, 5*x + 5, -1) From bb4b129364ed7f653bf1d30c86f858496b8844f2 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 May 2025 10:47:22 +0100 Subject: [PATCH 42/56] add basic weighted projective space functionality --- src/sage/schemes/all.py | 2 + src/sage/schemes/meson.build | 1 + .../schemes/weighted_projective/__init__.py | 1 + src/sage/schemes/weighted_projective/all.py | 23 + .../weighted_projective_curve.py | 25 ++ .../weighted_projective_homset.py | 38 ++ .../weighted_projective_point.py | 297 +++++++++++++ .../weighted_projective_space.py | 420 ++++++++++++++++++ 8 files changed, 807 insertions(+) create mode 100644 src/sage/schemes/weighted_projective/__init__.py create mode 100644 src/sage/schemes/weighted_projective/all.py create mode 100644 src/sage/schemes/weighted_projective/weighted_projective_curve.py create mode 100644 src/sage/schemes/weighted_projective/weighted_projective_homset.py create mode 100644 src/sage/schemes/weighted_projective/weighted_projective_point.py create mode 100644 src/sage/schemes/weighted_projective/weighted_projective_space.py diff --git a/src/sage/schemes/all.py b/src/sage/schemes/all.py index f5126a0dee1..9c9fbc871dd 100644 --- a/src/sage/schemes/all.py +++ b/src/sage/schemes/all.py @@ -42,6 +42,8 @@ from sage.schemes.product_projective.all import * +from sage.schemes.weighted_projective.all import * + from sage.schemes.cyclic_covers.all import * from sage.schemes.berkovich.all import * diff --git a/src/sage/schemes/meson.build b/src/sage/schemes/meson.build index 4dac80900e9..63e470443b6 100644 --- a/src/sage/schemes/meson.build +++ b/src/sage/schemes/meson.build @@ -18,4 +18,5 @@ install_subdir('plane_quartics', install_dir: sage_install_dir / 'schemes') install_subdir('product_projective', install_dir: sage_install_dir / 'schemes') install_subdir('projective', install_dir: sage_install_dir / 'schemes') install_subdir('riemann_surfaces', install_dir: sage_install_dir / 'schemes') +install_subdir('weighted_projective', install_dir: sage_install_dir / 'schemes') subdir('toric') diff --git a/src/sage/schemes/weighted_projective/__init__.py b/src/sage/schemes/weighted_projective/__init__.py new file mode 100644 index 00000000000..9530e959fd3 --- /dev/null +++ b/src/sage/schemes/weighted_projective/__init__.py @@ -0,0 +1 @@ +# Here so that cython creates the correct module name \ No newline at end of file diff --git a/src/sage/schemes/weighted_projective/all.py b/src/sage/schemes/weighted_projective/all.py new file mode 100644 index 00000000000..cc7d9c99adb --- /dev/null +++ b/src/sage/schemes/weighted_projective/all.py @@ -0,0 +1,23 @@ +"""nodoctest +all.py -- export of projective schemes to Sage +""" + +# ***************************************************************************** +# +# Sage: Open Source Mathematical Software +# +# Copyright (C) 2005 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# https://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace diff --git a/src/sage/schemes/weighted_projective/weighted_projective_curve.py b/src/sage/schemes/weighted_projective/weighted_projective_curve.py new file mode 100644 index 00000000000..d555f8ffc36 --- /dev/null +++ b/src/sage/schemes/weighted_projective/weighted_projective_curve.py @@ -0,0 +1,25 @@ +from sage.schemes.curves.curve import Curve_generic +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace_ring + + +class WeightedProjectiveCurve(Curve_generic): + """ + Curves in weighted projective spaces. + + EXAMPLES: + + We construct a hyperelliptic curve manually:: + + sage: P. = WeightedProjectiveSpace([1, 3, 1], QQ) + sage: C = P.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6); C + """ + def __init__(self, A, X, *kwargs): + # TODO ensure that A is the right type? + # Something like a `is_WeightProjectiveSpace` which means making a + # WeightProjectiveSpace class? + if not isinstance(A, WeightedProjectiveSpace_ring): + raise TypeError(f"A(={A}) is not a weighted projective space") + super().__init__(A, X, *kwargs) + + def curve(self): + return diff --git a/src/sage/schemes/weighted_projective/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py new file mode 100644 index 00000000000..ab3c7a2e491 --- /dev/null +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -0,0 +1,38 @@ +""" +Hom-sets of weighted projective schemes + +AUTHORS: + +- Gareth Ma (2024): initial version, based on unweighted version. +""" + +# ***************************************************************************** +# Copyright (C) 2024 Gareth Ma +# Copyright (C) 2011 Volker Braun +# Copyright (C) 2006 William Stein +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.schemes.generic.homset import SchemeHomset_points + + +class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): + """ + Set of rational points of a weighted projective variety over a ring. + + INPUT: + + See :class:`SchemeHomset_generic`. + + EXAMPLES:: + + sage: W = WeightedProjectiveSpace([3, 4, 5], QQ) + sage: W.point_homset() + Set of rational points of Weighted Projective Space of dimension 2 with weights (3, 4, 5) over Rational Field + sage: W.an_element().parent() is W.point_homset() + True + """ diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py new file mode 100644 index 00000000000..91b1a87423e --- /dev/null +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -0,0 +1,297 @@ +r""" +Points on projective schemes + +This module implements scheme morphism for points on weighted projective schemes. + +AUTHORS: + +- Gareth Ma (2024): initial version, based on unweighted version. +""" + +# **************************************************************************** +# Copyright (C) 2006 David Kohel +# Copyright (C) 2006 William Stein +# Copyright (C) 2011 Volker Braun +# Copyright (C) 2024 Gareth Ma +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.categories.fields import Fields +from sage.rings.fraction_field import FractionField +from sage.misc.misc_c import prod + +from sage.schemes.generic.morphism import (SchemeMorphism, + SchemeMorphism_point) +from sage.structure.sequence import Sequence +from sage.structure.richcmp import richcmp, op_EQ, op_NE + + +# -------------------- +# Projective varieties +# -------------------- + +class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): + """ + A rational point of weighted projective space over a ring. + + INPUT: + + - ``X`` -- a homset of a subscheme of an ambient weighted projective space over a ring `R`. + + - ``v`` -- a list or tuple of coordinates in `R`. + + - ``check`` -- boolean (default:``True``). Whether to check the input for consistency. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(2, ZZ) + sage: WP(2, 3, 4) + (2 : 3 : 4) + """ + + def __init__(self, X, v, check=True): + """ + The Python constructor. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([3, 4, 5], QQ) + sage: P1 = WP(2, 3, 4); P1 + (2 : 3 : 4) + sage: P2 = WP(2000, 30000, 400000); P2 + (2000 : 30000 : 400000) + sage: P1 == P2 + True + + The point constructor normalises coordinates at the last position with + weight `1`:: + + sage: WP = WeightedProjectiveSpace([3, 1, 4, 1], QQ) + sage: P = WP(8, 3, 16, 2); P + (1 : 3/2 : 1 : 1) + sage: P == WP(1, 3 / 2, 1, 1) + True + """ + SchemeMorphism.__init__(self, X) + + self._normalized = False + + if check: + # check parent + from weighted_projective_homset import SchemeHomset_points_weighted_projective_ring + if not isinstance(X, SchemeHomset_points_weighted_projective_ring): + raise TypeError(f"ambient space {X} must be a weighted projective space") + + from sage.rings.ring import CommutativeRing + d = X.codomain().ambient_space().ngens() + if isinstance(v, SchemeMorphism): + v = list(v) + else: + try: + if isinstance(v.parent(), CommutativeRing): + v = [v] + except AttributeError: + pass + if not isinstance(v, (list, tuple)): + raise TypeError("argument v (= %s) must be a scheme point, list, or tuple" % str(v)) + if len(v) != d and len(v) != d-1: + raise TypeError("v (=%s) must have %s components" % (v, d)) + + R = X.value_ring() + v = Sequence(v, R) + if len(v) == d-1: # very common special case + v.append(R.one()) + + # (0 : 0 : ... : 0) is not a valid (weighted) projective point + if not any(v): + raise ValueError(f"{v} does not define a valid projective " + "point since all entries are zero") + + # over other rings, we do not have a generic method to check + # whether the given coordinates is a multiple of a zero divisor + # so we just let it pass. + X.extended_codomain()._check_satisfies_equations(v) + + self._coords = tuple(v) + + # we (try to) normalise coordinates! + self.normalize_coordinates() + + else: + self._coords = tuple(v) + + def _repr_(self): + return "({})".format(" : ".join(map(repr, self._coords))) + + def _richcmp_(self, other, op): + """ + Test the weighted projective equality of two points. + + INPUT: + + - ``other`` -- a point on weighted projective space + + OUTPUT: + + Boolean + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([3, 4, 5], QQ) + sage: u = abs(QQ.random_element()) + 1 / 4 # nonzero + sage: P, Q = WP(2, 3, 4), WP(2 * u^3, 3 * u^4, 4 * u^5) + sage: P == Q + True + sage: P != Q + False + + :: + + sage: P, Q = WP(2, 3, 4), WP(2, 3, 5) + sage: P < Q + True + sage: P <= Q + True + sage: P > Q + False + sage: P >= Q + False + """ + assert isinstance(other, SchemeMorphism_point) + + space = self.codomain() + if space is not other.codomain(): + return op == op_NE + + if op in [op_EQ, op_NE]: + weights = space._weights + # (other[i] / self[i])^(1 / weight[i]) all equal + prod_weights = prod(weights) + # check weights + bw = weights == other.codomain()._weights + if bw != (op == op_EQ): + return False + + # check zeros + b1 = all(c1 == c2 + for c1, c2 in zip(self._coords, other._coords) + if c1 == 0 or c2 == 0) + if b1 != (op == op_EQ): + return False + + # check nonzeros + ratio = [(c1 / c2) ** (prod_weights // w) + for c1, c2, w in zip(self._coords, other._coords, weights) + if c1 != 0 and c2 != 0] + r0 = ratio[0] + b2 = all(r == r0 for r in ratio) + return b2 == (op == op_EQ) + + return richcmp(self._coords, other._coords, op) + + def __hash__(self): + """ + Compute the hash value of this point. + + We attempt to normalise the coordinates of this point over the field of + fractions of the base ring. If this is not possible, return the hash of + the parent. See :meth:`normalize_coordinates` for more details. + + OUTPUT: Integer. + + .. SEEALSO :: + + :meth:`normalize_coordinates` + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([1, 3, 1], QQ) + sage: hash(WP(6, 24, 2)) == hash(WP(3, 3, 1)) + True + sage: WP = WeightedProjectiveSpace([1, 3, 1], ZZ) + sage: hash(WP(6, 24, 2)) == hash(WP(3, 3, 1)) + True + """ + R = self.codomain().base_ring() + P = self.change_ring(FractionField(R)) + P.normalize_coordinates() + if P._normalized: + return hash(tuple(P)) + # if there is no good way to normalize return a constant value + return hash(self.codomain()) + + def scale_by(self, t): + """ + Scale the coordinates of the point by ``t``. + + A :exc:`TypeError` occurs if the point is not in the + base_ring of the codomain after scaling. + + INPUT: + + - ``t`` -- a ring element + + OUTPUT: none + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([3, 4, 5], ZZ) + sage: P = WP([8, 16, 32]); P + (8 : 16 : 32) + sage: P.scale_by(1 / 2); P + (1 : 1 : 1) + sage: P.scale_by(1 / 3); P + Traceback (most recent call last): + ... + TypeError: ... + """ + if t.is_zero(): + raise ValueError("Cannot scale by 0") + R = self.codomain().base_ring() + self._coords = tuple([R(u * t**w) for u, w in zip(self._coords, self.codomain()._weights)]) + self._normalized = False + + def normalize_coordinates(self): + """ + Normalise coordinates of this weighted projective point if possible. + + Currently, this method checks if (1) the ambient weighted projective + space is defined over a field and (2) the weight of any index is `1`. + If so, the last of which is rescaled to `1`. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([3, 1, 5], QQ) + sage: P = WP([8, 16, 32]); P + (1/512 : 1 : 1/32768) + sage: P.scale_by(13); P + (2197/512 : 13 : 371293/32768) + sage: P.normalize_coordinates(); P + (1/512 : 1 : 1/32768) + + :: + + sage: WP = WeightedProjectiveSpace([3, 4, 5], ZZ) + sage: P = WP([8, 16, 32]); P + (8 : 16 : 32) + sage: P.normalize_coordinates(); P + (8 : 16 : 32) + """ + if self._normalized: + return + + if self.base_ring() in Fields(): + weights = self.codomain()._weights + coords = self._coords + for i in reversed(range(len(coords))): + w, c = weights[i], coords[i] + if w.is_one() and not c.is_zero(): + # we normalise w.r.t this coordinate + self.scale_by(~c) + self._normalized = True + return diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py new file mode 100644 index 00000000000..d4060163834 --- /dev/null +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -0,0 +1,420 @@ +from sage.categories.fields import Fields +from sage.categories.map import Map +from sage.misc.latex import latex +from sage.misc.prandom import shuffle +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base +from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.term_order import TermOrder +from sage.schemes.generic.ambient_space import AmbientSpace +from sage.schemes.projective.projective_space import ProjectiveSpace, _CommRings +from sage.structure.all import UniqueRepresentation +from sage.structure.category_object import normalize_names + +from sage.schemes.weighted_projective.weighted_projective_homset import SchemeHomset_points_weighted_projective_ring + + + +def WeightedProjectiveSpace(weights, R=None, names=None): + r""" + Return a weighted projective space with the given ``weights`` over the ring ``R``. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([1, 3, 1]); WP + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + """ + if ( + isinstance(weights, (MPolynomialRing_base, PolynomialRing_generic)) + and R is None + ): + if names is not None: + # Check for the case that the user provided a variable name + # That does not match what we wanted to use from R + names = normalize_names(weights.ngens(), names) + if weights.variable_names() != names: + # The provided name doesn't match the name of R's variables + raise NameError( + "variable names passed to ProjectiveSpace conflict with names in ring" + ) + A = WeightedProjectiveSpace( + weights.ngens() - 1, weights.base_ring(), names=weights.variable_names() + ) + A._coordinate_ring = weights + return A + + if isinstance(R, (int, Integer, list, tuple)): + weights, R = R, weights + elif R is None: + R = ZZ + + # WeightedProjectiveSpace(5) -> just return unweighted version + if isinstance(weights, (int, Integer)): + return ProjectiveSpace(weights, R=R, names=names) + elif isinstance(weights, (list, tuple)): + # Make it hashable + weights = tuple(map(Integer, weights)) + if any(w <= 0 for w in weights): + raise TypeError( + f"weights(={weights}) should only consist of positive integers" + ) + else: + raise TypeError(f"weights={weights} must be an integer, a list or a tuple") + + if names is None: + names = "x" + + # TODO: Specialise implementation to projective spaces over non-rings. + if R in _CommRings: + return WeightedProjectiveSpace_ring(weights, R=R, names=names) + + raise TypeError(f"R (={R}) must be a commutative ring") + + +class WeightedProjectiveSpace_ring(UniqueRepresentation, AmbientSpace): + @staticmethod + def __classcall__(cls, weights: tuple[Integer], R=ZZ, names=None): + # __classcall_ is the "preprocessing" step for UniqueRepresentation + # see docs of CachedRepresentation + # weights should be a tuple, also because it should be hashable + if not isinstance(weights, tuple): + raise TypeError( + f"weights(={weights}) is not a tuple. Please use the" + "`WeightedProjectiveSpace` constructor" + ) + + normalized_names = normalize_names(len(weights), names) + return super().__classcall__(cls, weights, R, normalized_names) + + def __init__(self, weights: tuple[Integer], R=ZZ, names=None): + """ + Initialization function. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(Zp(5), [1, 3, 1], 'y') # needs sage.rings.padics + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over 5-adic Ring with + capped relative precision 20 + sage: WeightedProjectiveSpace(QQ, 5, 'y') + Projective Space of dimension 5 over Rational Field + sage: _ is ProjectiveSpace(QQ, 5, 'y') + True + """ + AmbientSpace.__init__(self, len(weights) - 1, R) + self._weights = weights + self._assign_names(names) + + def weights(self) -> tuple[Integer]: + """ + Return the tuple of weights of this weighted projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(QQ, [1, 3, 1]).weights() + (1, 3, 1) + """ + return self._weights + + def ngens(self) -> Integer: + """ + Return the number of generators of this weighted projective space. + + This is the number of variables in the coordinate ring of ``self``. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(QQ, [1, 3, 1]).ngens() + 3 + sage: WeightedProjectiveSpace(ZZ, 5).ngens() + 6 + """ + return self.dimension_relative() + 1 + + def _check_satisfies_equations(self, v: list[Integer] | tuple[Integer]) -> bool: + """ + Return ``True`` if ``v`` defines a point on the weighted projective + plane; raise a :class:`TypeError` otherwise. + + EXAMPLES:: + + sage: P = WeightedProjectiveSpace(ZZ, [1, 3, 1]) + sage: P._check_satisfies_equations([1, 1, 0]) + True + + sage: P._check_satisfies_equations([1, 0]) + Traceback (most recent call last): + ... + TypeError: the list v=[1, 0] must have 3 components + + sage: P._check_satisfies_equations([1/2, 0, 1]) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + """ + if not isinstance(v, (list, tuple)): + raise TypeError(f"the argument v={v} must be a list or tuple") + + n = self.ngens() + if len(v) != n: + raise TypeError(f"the list v={v} must have {n} components") + + v = list(map(self.base_ring(), v)) + if all(vi.is_zero() for vi in v): + raise TypeError("the zero vector is not a point in projective space") + + return True + + def coordinate_ring(self) -> PolynomialRing_generic: + """ + Return the coordinate ring of this weighted projective space. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(GF(19^2, 'α'), [1, 3, 4, 1], 'abcd') + sage: # needs sage.rings.finite_rings + sage: R = WP.coordinate_ring(); R + Multivariate Polynomial Ring in a, b, c, d over Finite Field in α of size 19^2 + sage: R.term_order() + Weighted degree reverse lexicographic term order with weights (1, 3, 4, 1) + + :: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 1, 1], ['alpha', 'beta', 'gamma']) + sage: R = WP.coordinate_ring(); R + Multivariate Polynomial Ring in alpha, beta, gamma over Rational Field + sage: R.term_order() + Weighted degree reverse lexicographic term order with weights (1, 1, 1) + """ + if not hasattr(self, "_coordinate_ring"): + term_order = TermOrder("wdegrevlex", self.weights()) + self._coordinate_ring = PolynomialRing( + self.base_ring(), + self.dimension_relative() + 1, + names=self.variable_names(), + order=term_order, + ) + return self._coordinate_ring + + def _validate(self, polynomials): + """ + If ``polynomials`` is a tuple of valid polynomial functions on ``self``, + return ``polynomials``, otherwise raise ``TypeError``. + + Since this is a weighted projective space, polynomials must be + homogeneous with respect to the grading of this space. + + INPUT: + + - ``polynomials`` -- tuple of polynomials in the coordinate ring of + this space. + + OUTPUT: + + - tuple of polynomials in the coordinate ring of this space. + + EXAMPLES:: + + sage: P. = WeightedProjectiveSpace(QQ, [1, 3, 1]) + sage: P._validate([x*y - z^4, x]) + [x*y - z^4, x] + sage: P._validate([x*y - z^2, x]) + Traceback (most recent call last): + ... + TypeError: x*y - z^2 is not homogeneous with weights (1, 3, 1) + sage: P._validate(x*y - z) + Traceback (most recent call last): + ... + TypeError: the argument polynomials=x*y - z must be a list or tuple + """ + if not isinstance(polynomials, (list, tuple)): + raise TypeError( + f"the argument polynomials={polynomials} must be a list or tuple" + ) + + R = self.coordinate_ring() + for f in map(R, polynomials): + if not f.is_homogeneous(): + raise TypeError(f"{f} is not homogeneous with weights {self.weights()}") + + return polynomials + + def _latex_(self): + r""" + Return a LaTeX representation of this projective space. + + EXAMPLES:: + + sage: print(latex(WeightedProjectiveSpace(ZZ, [1, 3, 1], 'x'))) + {\mathbf P}_{\Bold{Z}}^{[1, 3, 1]} + + TESTS:: + + sage: WeightedProjectiveSpace(Zp(5), [2, 1, 3], 'y')._latex_() # needs sage.rings.padics + '{\\mathbf P}_{\\Bold{Z}_{5}}^{[2, 1, 3]}' + """ + return ( + f"{{\\mathbf P}}_{{{latex(self.base_ring())}}}^{{{list(self.weights())}}}" + ) + + def _morphism(self, *_, **__): + """ + Construct a morphism. + + For internal use only. See :mod:`morphism` for details. + """ + raise NotImplementedError( + "_morphism not implemented for weighted projective space" + ) + + def _homset(self, *_, **__): + """ + Construct the Hom-set. + """ + raise NotImplementedError( + "_homset not implemented for weighted projective space" + ) + + def _point_homset(self, *args, **kwds): + """ + Construct a point Hom-set. + + For internal use only. See :mod:`morphism` for details. + """ + # raise NotImplementedError("_point_homset not implemented for weighted projective space") + return SchemeHomset_points_weighted_projective_ring(*args, **kwds) + + def point(self, v, check=True): + """ + Create a point on this weighted projective space. + + INPUT: + + INPUT: + + - ``v`` -- anything that defines a point + + - ``check`` -- boolean (default: ``True``); whether + to check the defining data for consistency + + OUTPUT: A point of this weighted projective space. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace(QQ, [1, 3, 1]) + sage: WP.point([2, 3, 1]) + (2 : 3 : 1) + """ + from sage.rings.infinity import infinity + + if v is infinity or ( + isinstance(v, (list, tuple)) and len(v) == 1 and v[0] is infinity + ): + if self.dimension_relative() > 1: + raise ValueError("%s not well defined in dimension > 1" % v) + v = [1, 0] + + return self.point_homset()(v, check=check) + + def _point(self, *args, **kwds): + """ + Construct a point. + + For internal use only. See :mod:`morphism` for details. + """ + from weighted_projective_point import SchemeMorphism_point_weighted_projective_ring + return SchemeMorphism_point_weighted_projective_ring(*args, **kwds) + + def _repr_(self) -> str: + """ + Return a string representation of this projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(Qp(5), [1, 3, 1], 'x') # needs sage.rings.padics + Weighted Projective Space of dimension 2 with weights (1, 3, 1) + over 5-adic Field with capped relative precision 20 + """ + return ( + f"Weighted Projective Space of dimension {self.dimension_relative()} with weights" + f" {self.weights()} over {self.base_ring()}" + ) + + def change_ring(self, R): + r""" + Return a weighted projective space over ring ``R``. + + INPUT: + + - ``R`` -- commutative ring or morphism + + OUTPUT: weighted projective space over ``R`` + + .. NOTE:: + + There is no need to have any relation between ``R`` and the base ring + of this space, if you want to have such a relation, use + ``self.base_extend(R)`` instead. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([1, 3, 1], ZZ); WP + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + sage: WP.change_ring(QQ) + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field + sage: WP.change_ring(GF(5)) + Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Finite Field of size 5 + """ + if isinstance(R, Map): + return WeightedProjectiveSpace(self.weights(), R.codomain(), + self.variable_names()) + else: + return WeightedProjectiveSpace(self.weights(), R, + self.variable_names()) + + def _an_element_(self): + r""" + Return a (preferably typical) element of this space. + + This is used both for illustration and testing purposes. + + OUTPUT: a point in this projective space. + + EXAMPLES:: + + sage: WeightedProjectiveSpace(ZZ, [1, 3, 1], 'x').an_element() # random + (1 : 2 : 3) + sage: WeightedProjectiveSpace(ZZ["y"], [2, 3, 1], 'x').an_element() # random + (3*y : 2*y : y) + """ + n = self.dimension_relative() + R = self.base_ring() + coords = [(n + 1 - i) * R.an_element() for i in range(n + 1)] + shuffle(coords) + return self(coords) + + def subscheme(self, *_, **__): + raise NotImplementedError("subscheme of weighted projective space has not been implemented") + + def curve(self, F): + r""" + Return a curve defined by ``F`` in this weighted projective space. + + INPUT: + + - ``F`` -- a polynomial, or a list or tuple of polynomials in + the coordinate ring of this weighted projective space + + EXAMPLES:: + + sage: WP. = WeightedProjectiveSpace(QQ, 2) + sage: P.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6) # needs sage.schemes + Projective Plane Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 + """ + if self.base_ring() not in Fields(): + raise NotImplementedError("curves in weighted projective space over" + "rings not implemented") + from sage.schemes.curves.constructor import Curve + return Curve(F, self) + From 8aae3d4e21a60757ce20b5eab56590e8b91f7117 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 May 2025 10:47:48 +0100 Subject: [PATCH 43/56] remove finished TODO --- .../schemes/weighted_projective/weighted_projective_curve.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_curve.py b/src/sage/schemes/weighted_projective/weighted_projective_curve.py index d555f8ffc36..9e6f0724642 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_curve.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_curve.py @@ -14,9 +14,6 @@ class WeightedProjectiveCurve(Curve_generic): sage: C = P.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6); C """ def __init__(self, A, X, *kwargs): - # TODO ensure that A is the right type? - # Something like a `is_WeightProjectiveSpace` which means making a - # WeightProjectiveSpace class? if not isinstance(A, WeightedProjectiveSpace_ring): raise TypeError(f"A(={A}) is not a weighted projective space") super().__init__(A, X, *kwargs) From ebe3a942c36e3ca894d3fee73184cb24f5289a82 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 8 May 2025 19:52:58 +0100 Subject: [PATCH 44/56] clean up implementation of weighted (plane) curves and other docs --- src/sage/schemes/curves/constructor.py | 16 ++++++ .../curves/weighted_projective_curve.py | 57 +++++++++++++++++++ .../weighted_projective_curve.py | 22 ------- .../weighted_projective_homset.py | 2 +- .../weighted_projective_point.py | 4 +- .../weighted_projective_space.py | 14 ++--- 6 files changed, 83 insertions(+), 32 deletions(-) create mode 100644 src/sage/schemes/curves/weighted_projective_curve.py delete mode 100644 src/sage/schemes/weighted_projective/weighted_projective_curve.py diff --git a/src/sage/schemes/curves/constructor.py b/src/sage/schemes/curves/constructor.py index beff4dd649d..659ec49ed96 100644 --- a/src/sage/schemes/curves/constructor.py +++ b/src/sage/schemes/curves/constructor.py @@ -18,6 +18,12 @@ Projective Plane Curve over Finite Field of size 5 defined by -x^9 + y^2*z^7 - x*z^8 +Here, we construct a hyperelliptic curve manually:: + + sage: WP. = WeightedProjectiveSpace([1, 3, 1], GF(103)) + sage: Curve(y^2 - (x^5*z + 17*x^2*z^4 + 92*z^6), WP) + Weighted Projective Curve over Finite Field of size 103 defined by y^2 - x^5*z - 17*x^2*z^4 + 11*z^6 + AUTHORS: - William Stein (2005-11-13) @@ -49,6 +55,7 @@ from sage.schemes.generic.algebraic_scheme import AlgebraicScheme from sage.schemes.affine.affine_space import AffineSpace, AffineSpace_generic from sage.schemes.projective.projective_space import ProjectiveSpace, ProjectiveSpace_ring +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace_ring from sage.schemes.plane_conics.constructor import Conic from .projective_curve import (ProjectiveCurve, @@ -71,6 +78,8 @@ IntegralAffinePlaneCurve, IntegralAffinePlaneCurve_finite_field) +from .weighted_projective_curve import WeightedProjectiveCurve + def _is_irreducible_and_reduced(F) -> bool: """ @@ -376,5 +385,12 @@ def Curve(F, A=None): return ProjectivePlaneCurve_field(A, F) return ProjectivePlaneCurve(A, F) + elif isinstance(A, WeightedProjectiveSpace_ring): + # currently, we only support curves in a weighted projective plane + if n != 2: + raise NotImplementedError("ambient space has to be a weighted projective plane") + # currently, we do not perform checks on weighted projective curves + return WeightedProjectiveCurve(A, F) + else: raise TypeError('ambient space neither affine nor projective') diff --git a/src/sage/schemes/curves/weighted_projective_curve.py b/src/sage/schemes/curves/weighted_projective_curve.py new file mode 100644 index 00000000000..39b92b8461b --- /dev/null +++ b/src/sage/schemes/curves/weighted_projective_curve.py @@ -0,0 +1,57 @@ +# sage.doctest: needs sage.libs.singular +r""" +Weighted projective curves + +Weighted projective curves in Sage are curves in a weighted projective space or +a weighted projective plane. + +EXAMPLES: + +For now, only curves in weighted projective plane is supported:: + + sage: WP. = WeightedProjectiveSpace([1, 3, 1], QQ) + sage: C1 = WP.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6); C1 + Weighted Projective Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 + sage: C2 = Curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6, WP); C2 + Weighted Projective Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 + sage: C1 == C2 + True +""" + +from sage.schemes.curves.curve import Curve_generic +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace_ring + + +class WeightedProjectiveCurve(Curve_generic): + """ + Curves in weighted projective spaces. + + EXAMPLES: + + We construct a hyperelliptic curve manually:: + + sage: WP. = WeightedProjectiveSpace([1, 3, 1], QQ) + sage: C = Curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6, WP); C + Weighted Projective Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 + """ + def __init__(self, A, X, *kwargs): + if not isinstance(A, WeightedProjectiveSpace_ring): + raise TypeError(f"A(={A}) is not a weighted projective space") + super().__init__(A, X, *kwargs) + + def _repr_type(self): + r""" + Return a string representation of the type of this curve. + + EXAMPLES:: + + sage: WP. = WeightedProjectiveSpace([1, 3, 1], QQ) + sage: C = Curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6, WP); C + Weighted Projective Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 + sage: C._repr_type() + 'Weighted Projective' + """ + return "Weighted Projective" + + def curve(self): + return self diff --git a/src/sage/schemes/weighted_projective/weighted_projective_curve.py b/src/sage/schemes/weighted_projective/weighted_projective_curve.py deleted file mode 100644 index 9e6f0724642..00000000000 --- a/src/sage/schemes/weighted_projective/weighted_projective_curve.py +++ /dev/null @@ -1,22 +0,0 @@ -from sage.schemes.curves.curve import Curve_generic -from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace_ring - - -class WeightedProjectiveCurve(Curve_generic): - """ - Curves in weighted projective spaces. - - EXAMPLES: - - We construct a hyperelliptic curve manually:: - - sage: P. = WeightedProjectiveSpace([1, 3, 1], QQ) - sage: C = P.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6); C - """ - def __init__(self, A, X, *kwargs): - if not isinstance(A, WeightedProjectiveSpace_ring): - raise TypeError(f"A(={A}) is not a weighted projective space") - super().__init__(A, X, *kwargs) - - def curve(self): - return diff --git a/src/sage/schemes/weighted_projective/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py index ab3c7a2e491..024384883db 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_homset.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -26,7 +26,7 @@ class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): INPUT: - See :class:`SchemeHomset_generic`. + See :class:`SchemeHomset_points`. EXAMPLES:: diff --git a/src/sage/schemes/weighted_projective/weighted_projective_point.py b/src/sage/schemes/weighted_projective/weighted_projective_point.py index 91b1a87423e..006bb759de7 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_point.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_point.py @@ -45,7 +45,7 @@ class SchemeMorphism_point_weighted_projective_ring(SchemeMorphism_point): - ``v`` -- a list or tuple of coordinates in `R`. - - ``check`` -- boolean (default:``True``). Whether to check the input for consistency. + - ``check`` -- boolean (default: ``True``). Whether to check the input for consistency. EXAMPLES:: @@ -83,7 +83,7 @@ def __init__(self, X, v, check=True): if check: # check parent - from weighted_projective_homset import SchemeHomset_points_weighted_projective_ring + from sage.schemes.weighted_projective.weighted_projective_homset import SchemeHomset_points_weighted_projective_ring if not isinstance(X, SchemeHomset_points_weighted_projective_ring): raise TypeError(f"ambient space {X} must be a weighted projective space") diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py index d4060163834..2dc6d39b9a4 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -242,7 +242,7 @@ def _validate(self, polynomials): def _latex_(self): r""" - Return a LaTeX representation of this projective space. + Return a LaTeX representation of this weighted projective space. EXAMPLES:: @@ -323,12 +323,12 @@ def _point(self, *args, **kwds): For internal use only. See :mod:`morphism` for details. """ - from weighted_projective_point import SchemeMorphism_point_weighted_projective_ring + from sage.schemes.weighted_projective.weighted_projective_point import SchemeMorphism_point_weighted_projective_ring return SchemeMorphism_point_weighted_projective_ring(*args, **kwds) def _repr_(self) -> str: """ - Return a string representation of this projective space. + Return a string representation of this weighted projective space. EXAMPLES:: @@ -379,7 +379,7 @@ def _an_element_(self): This is used both for illustration and testing purposes. - OUTPUT: a point in this projective space. + OUTPUT: a point in this weighted projective space. EXAMPLES:: @@ -408,9 +408,9 @@ def curve(self, F): EXAMPLES:: - sage: WP. = WeightedProjectiveSpace(QQ, 2) - sage: P.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6) # needs sage.schemes - Projective Plane Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 + sage: WP. = WeightedProjectiveSpace([1, 3, 1], QQ) + sage: WP.curve(y^2 - x^5 * z - 3 * x^2 * z^4 - 2 * z^6) # needs sage.schemes + Weighted Projective Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 - 2*z^6 """ if self.base_ring() not in Fields(): raise NotImplementedError("curves in weighted projective space over" From 54fb0b507284c853ba5075add72cc0b322c3fd11 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 15 May 2025 15:11:31 +0100 Subject: [PATCH 45/56] add additional tests for points_at_infinity --- .../schemes/curves/weighted_projective_curve.py | 1 + .../hyperelliptic_generic.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/sage/schemes/curves/weighted_projective_curve.py b/src/sage/schemes/curves/weighted_projective_curve.py index 39b92b8461b..69eab187abf 100644 --- a/src/sage/schemes/curves/weighted_projective_curve.py +++ b/src/sage/schemes/curves/weighted_projective_curve.py @@ -37,6 +37,7 @@ class WeightedProjectiveCurve(Curve_generic): def __init__(self, A, X, *kwargs): if not isinstance(A, WeightedProjectiveSpace_ring): raise TypeError(f"A(={A}) is not a weighted projective space") + self._weights = A._weights super().__init__(A, X, *kwargs) def _repr_type(self): diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index f8affa9d625..e4422b7954e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -397,6 +397,19 @@ def points_at_infinity(self): sage: K. = QQ.extension(x^2 + x -1) sage: H.change_ring(K).points_at_infinity() [(1 : omega : 0), (1 : -omega - 1 : 0)] + + :: + + sage: _. = GF(103)[] + sage: H = HyperellipticCurveSmoothModel(x^5 + 1) + sage: H.points_at_infinity() + [(1 : 0 : 0)] + sage: H = HyperellipticCurveSmoothModel(x^6 + 1) + sage: H.points_at_infinity() + [(1 : 1 : 0), (1 : 102 : 0)] + sage: H = HyperellipticCurveSmoothModel(3*x^6 + 1) + sage: H.points_at_infinity() + [] """ return [self.point([1, y, 0], check=True) for y in self.roots_at_infinity()] From 8b83992c40dad7aee2707a09ed106e59c8b83f1a Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 15 May 2025 15:17:13 +0100 Subject: [PATCH 46/56] fix doctest errors --- .../weighted_projective_homset.py | 49 +++++-------------- .../weighted_projective_space.py | 1 - 2 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py index 07f263ae6c3..9c3949753b3 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_homset.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -28,52 +28,25 @@ class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): EXAMPLES:: - sage: W = WeightedProjectiveSpace([3, 4, 5], QQ) - sage: W.point_homset() - Set of rational points of Weighted Projective Space of dimension 2 with weights (3, 4, 5) over Rational Field + sage: W = WeightedProjectiveSpace([1, 3, 1], QQ) sage: W.an_element().parent() is W.point_homset() True sage: W.point_homset() - Set of rational points of Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Integer Ring + Set of rational points of Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field sage: type(W.point_homset()) sage: W(2, 24, 2) - (2 : 24 : 2) + (1 : 3 : 1) sage: W(2, 24, 2) == W(1, 3, 1) True - """ - - def points(self, **__): - """ - Return some or all rational points of this weighted projective scheme. - - For dimension 0 subschemes points are determined through a groebner - basis calculation. For schemes or subschemes with dimension greater than 1 - points are determined through enumeration up to the specified bound. - - .. TODO:: - - Modify implementation from projective space. - """ - raise NotImplementedError("enumerating points on weighted projective scheme is not implemented") + :: -class SchemeHomset_points_weighted_projective_field(SchemeHomset_points): - """ - Set of rational points of a weighted projective variety over a field. - - Placeholder class. - - EXAMPLES:: - - sage: WP = WeightedProjectiveSpace(QQ, [1, 3, 1]); WP - Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field - sage: WP.point_homset() - Set of rational points of Weighted Projective Space of dimension 2 with weights (1, 3, 1) over Rational Field - sage: type(WP.point_homset()) - - sage: WP(2, 24, 2) - (1 : 3 : 1) - sage: WP(2, 24, 2) == WP(1, 3, 1) - True + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x - 17); H + Hyperelliptic Curve over Rational Field defined by y^2 = x^6 + x - 17 + Page: P = H(2, -7); P + (2 : -7 : 1) + sage: type(P) + """ diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py index 2dc6d39b9a4..bde3cce87ed 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -282,7 +282,6 @@ def _point_homset(self, *args, **kwds): For internal use only. See :mod:`morphism` for details. """ - # raise NotImplementedError("_point_homset not implemented for weighted projective space") return SchemeHomset_points_weighted_projective_ring(*args, **kwds) def point(self, v, check=True): From 00dce0abdc6f7253e1ffdb413f242c18729eeb12 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 16 May 2025 01:40:29 +0100 Subject: [PATCH 47/56] remove useless .curve method --- src/sage/schemes/curves/weighted_projective_curve.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/schemes/curves/weighted_projective_curve.py b/src/sage/schemes/curves/weighted_projective_curve.py index 39b92b8461b..228db81cb1e 100644 --- a/src/sage/schemes/curves/weighted_projective_curve.py +++ b/src/sage/schemes/curves/weighted_projective_curve.py @@ -52,6 +52,3 @@ def _repr_type(self): 'Weighted Projective' """ return "Weighted Projective" - - def curve(self): - return self From 633004320225c03271cd81a7e41ec569cc4b2455 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 16 May 2025 02:03:11 +0100 Subject: [PATCH 48/56] implement converting wp curve to proj curve --- .../curves/weighted_projective_curve.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/sage/schemes/curves/weighted_projective_curve.py b/src/sage/schemes/curves/weighted_projective_curve.py index 228db81cb1e..a61d95fbfb6 100644 --- a/src/sage/schemes/curves/weighted_projective_curve.py +++ b/src/sage/schemes/curves/weighted_projective_curve.py @@ -52,3 +52,39 @@ def _repr_type(self): 'Weighted Projective' """ return "Weighted Projective" + + def projective_curve(self): + r""" + Return this weighted projective curve as a projective curve. + + A weighted homogeneous polynomial `f(x_1, \ldots, x_n)`, where `x_i` has + weight `w_i`, can be viewed as an unweighted homogeneous polynomial + `f(y_1^{w_1}, \ldots, y_n^{w_n})`. This correspondence extends to + varieties. + + .. TODO: + + Implement homsets for weighted projective spaces and implement this + as a ``projective_embedding`` method instead. + + EXAMPLES:: + + sage: WP = WeightedProjectiveSpace([1, 3, 1], QQ, "x, y, z") + sage: x, y, z = WP.gens() + sage: C = WP.curve(y^2 - (x^5*z + 3*x^2*z^4 - 2*x*z^5 + 4*z^6)); C + Weighted Projective Curve over Rational Field defined by y^2 - x^5*z - 3*x^2*z^4 + 2*x*z^5 - 4*z^6 + sage: C.projective_curve() + Projective Plane Curve over Rational Field defined by y^6 - x^5*z - 3*x^2*z^4 + 2*x*z^5 - 4*z^6 + """ + from sage.schemes.projective.projective_space import ProjectiveSpace + + WP = self.ambient_space() + PP = ProjectiveSpace(WP.dimension_relative(), WP.base_ring(), WP.variable_names()) + PP_ring = PP.coordinate_ring() + subs_dict = {name: var**weight for (name, var), weight in + zip(WP.gens_dict().items(), WP.weights())} + + wp_polys = self.defining_polynomials() + pp_polys = [PP_ring(poly.subs(**subs_dict)) for poly in wp_polys] + + return PP.curve(pp_polys) From 608233a815b274f93374c69e224dabf03d2ad9ad Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 16 May 2025 02:36:18 +0100 Subject: [PATCH 49/56] implement discriminant formula for hyperelliptic curves --- .../hyperelliptic_generic.py | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index e4422b7954e..81976f66fab 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -175,6 +175,41 @@ def change_ring(self, R): base_extend = change_ring + def discriminant(self): + r""" + Return the discriminant of this curve. + + The discriminant of the hyperelliptic curve `y^2 + hy = f` is `\Delta = + 2^{-4(g + 1)} \Delta(h^2 + 4f)`. See + ``_ for the + derivation. + + EXAMPLES:: + + sage: R. = QQ[] + sage: f = 2*x^6 + x^2 - 3*x + 1 + sage: h = x^2 + x + 1 + sage: H = HyperellipticCurveSmoothModel(f, h) + sage: H.discriminant() + -1622970410 + sage: F = 4*f + h^2 + sage: F.discriminant() / 16^3 + -1622970410 + sage: G = f.derivative()^2 - f*h.derivative()^2 + f.derivative()*h.derivative()*h + sage: F.resultant(G) == Delta^2*F.lc()^2 + True + + :: + + sage: # H.affine_patch() is not implemented + sage: H_patch = H.projective_curve().affine_patch(2) + sage: for p in prime_range(3, 300): + ....: Hp = H_patch.change_ring(GF(p)) + ....: assert Hp.is_smooth() == (H.discriminant() % p != 0) + """ + f, h = self._hyperelliptic_polynomials + return (4 * f + h**2).discriminant() / 16**(self.genus() + 1) + def polynomial_ring(self): """ Return the parent of `f,h`, where ``self`` is the hyperelliptic curve @@ -197,8 +232,7 @@ def polynomial_ring(self): def hyperelliptic_polynomials(self): """ - Return the polynomials `(f, h)` such that - `C : y^2 + h*y = f`. + Return the polynomials `(f, h)` such that `C : y^2 + hy = f`. EXAMPLES:: @@ -798,9 +832,7 @@ def rational_weierstrass_points(self): f, h = self.hyperelliptic_polynomials() F = h**2 + 4 * f - affine_weierstrass_points = [ - self(r, -h(r) / 2) for r in F.roots(multiplicities=False) - ] + affine_weierstrass_points = [self(r, -h(r) / 2) for r in F.roots(multiplicities=False)] if self.is_ramified(): # the point at infinity is Weierstrass return self.points_at_infinity() + affine_weierstrass_points @@ -1241,9 +1273,7 @@ def odd_degree_model(self): f, h = self._hyperelliptic_polynomials if f.base_ring().characteristic() == 2: - raise ValueError( - "There are no odd degree models over a field with characteristic 2." - ) + raise ValueError("There are no odd degree models over a field with characteristic 2.") if h: f = 4 * f + h**2 # move h to the right side of the equation if f.degree() % 2: From ba8448bacb5c31ba3fbe5c24659e7544a63e0977 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Thu, 28 Aug 2025 21:31:05 +0100 Subject: [PATCH 50/56] J.order only works over finite fields --- .../jacobian_homset_generic.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 834c2c3fe52..924a282b1e1 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -129,7 +129,10 @@ def order(self): sage: J(GF(7^2)).order() == (7+1)^6 True """ - return sum(self.extended_curve().frobenius_polynomial()) + if not isinstance(self.base_ring(), FiniteField_generic): + return sum(self.extended_curve().frobenius_polynomial()) + + raise NotImplementedError @cached_method def _curve_frobenius_roots(self): From ef00a855178ba55e954c6a13d45991d29b5c6520 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 29 Aug 2025 00:10:44 +0100 Subject: [PATCH 51/56] fix issues with coerce_map_from not firing for polynomial rings --- .../jacobian_homset_generic.py | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 924a282b1e1..1f2a0ecc593 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -128,8 +128,12 @@ def order(self): True sage: J(GF(7^2)).order() == (7+1)^6 True + sage: J(QQ).order() + Traceback (most recent call last): + ... + NotImplementedError """ - if not isinstance(self.base_ring(), FiniteField_generic): + if isinstance(self.base_ring(), FiniteField_generic): return sum(self.extended_curve().frobenius_polynomial()) raise NotImplementedError @@ -327,6 +331,21 @@ def __call__(self, *args, check=True): sage: _ == J(x + 4, 0) == J(x + 4, R(0)) True + Ensure that element constructor is user-friendly:: + + sage: R. = QQ[] + sage: f = (x^4 - 2*x^2 - 8*x + 1) * (x^3 + x + 1) + sage: H = HyperellipticCurveSmoothModel(f) + sage: J = H.jacobian() + sage: D = J(H(0, 1)) + sage: D.base_ring() + Rational Field + sage: J.change_ring(GF(13))(D).parent().base_ring() + Finite Field of size 13 + sage: R. = QQ[] + sage: J(y^3 + y + 1, 0) + (x^3 + x + 1, 0) + TODO: - Allow sending a field element corresponding to the x-coordinate of a point? @@ -351,7 +370,11 @@ def __call__(self, *args, check=True): if P1 == 0: return self.zero(check=check) elif isinstance(P1, self._morphism_element): - return P1 + if parent(P1) is self: + return P1 + # may have to change polynomial ring etc. + # this case will now be handled below. + args = P1.uv() elif isinstance(P1, SchemeMorphism_point_weighted_projective_ring): args = args + ( self.extended_curve().distinguished_point(), @@ -371,14 +394,15 @@ def __call__(self, *args, check=True): P2_inv = self.extended_curve().hyperelliptic_involution(P2) u2, v2 = self.point_to_mumford_coordinates(P2_inv) u, v = self.cantor_composition(u1, v1, u2, v2) - # This checks whether P1 and P2 can be interpreted as polynomials - elif R.coerce_map_from(parent(P1)) and R.coerce_map_from(parent(P2)): - u = R(P1) - v = R(P2) else: - raise ValueError( - "the input must consist of one or two points, or Mumford coordinates" - ) + # We try to coerce input Mumford coordinates to polynomials + try: + u = R(P1) + v = R(P2) + except ValueError: + raise ValueError( + "the input must consist of one or two points, or Mumford coordinates" + ) if len(args) > 2: raise ValueError("at most two arguments are allowed as input") From 882085182ab0b07c972fb29ab355c50ee502b6f0 Mon Sep 17 00:00:00 2001 From: Gareth Ma Date: Fri, 29 Aug 2025 00:10:57 +0100 Subject: [PATCH 52/56] add `distinguished_point` to hyperelliptic curve constructor --- .../hyperelliptic_constructor.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py index d78876363bf..c3a694dd279 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -51,7 +51,8 @@ """ -def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): +def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True, + distinguished_point=None): r""" Constructor function for creating a hyperelliptic curve with smooth model with polynomials `f`, `h`. @@ -146,6 +147,12 @@ def HyperellipticCurveSmoothModel(f, h=0, check_squarefree=True): ... ValueError: singularity in the provided affine patch + The constructor accepts a distinguished point as an argument:: + + sage: C = HyperellipticCurveSmoothModel(x^5 + x + 2, distinguished_point=(1, -2)) + sage: C.distinguished_point() + (1 : -2 : 1) + """ # --------------------------- @@ -259,4 +266,7 @@ def __defining_polynomial(f, h): else: cls = HyperellipticCurveSmoothModel_generic - return cls(defining_polynomial, f, h, genus) + H = cls(defining_polynomial, f, h, genus) + if distinguished_point: + H.set_distinguished_point(H(distinguished_point)) + return H From 40c71ec2aab56e319c4da9b9fc30a5963d68116e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 24 Nov 2025 20:00:09 +0100 Subject: [PATCH 53/56] Update weighted_projective_space.py partial fix for the linter --- .../schemes/weighted_projective/weighted_projective_space.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/schemes/weighted_projective/weighted_projective_space.py b/src/sage/schemes/weighted_projective/weighted_projective_space.py index bde3cce87ed..4501b7e4957 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_space.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -16,7 +16,6 @@ from sage.schemes.weighted_projective.weighted_projective_homset import SchemeHomset_points_weighted_projective_ring - def WeightedProjectiveSpace(weights, R=None, names=None): r""" Return a weighted projective space with the given ``weights`` over the ring ``R``. @@ -416,4 +415,3 @@ def curve(self, F): "rings not implemented") from sage.schemes.curves.constructor import Curve return Curve(F, self) - From cd1d85f751587190a7945ce87c7efe083c3abad0 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 25 Nov 2025 11:52:39 +0000 Subject: [PATCH 54/56] fix RST lint issue --- .../hyperelliptic_curves_smooth_model/jacobian_generic.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py index b54dcc47b07..4ff595df501 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -101,11 +101,7 @@ def point(self, *mumford, check=True, **kwargs): def _point_homset(self, *args, **kwds): """ - Create the Hom-Set of the Jacobian according to the type of `self`. - - TESTS:: - - + Create the Hom-Set of the Jacobian according to the type of `self`. """ # TODO: make a constructor for this?? H = self.curve() From 8398ccf8999dd4e422c70d4066137b7090ef6a11 Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 25 Nov 2025 11:53:16 +0000 Subject: [PATCH 55/56] fix whitespace linter issue --- .../hyperelliptic_padic_field.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py index 9018da2f6e2..52746f1bd0e 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -70,7 +70,6 @@ def __init__(self, projective_model, f, h, genus): """ super().__init__(projective_model, f, h, genus) - def local_analytic_interpolation(self, P, Q): """ For points ``P``, ``Q`` in the same residue disc, From ea1fbe225ee1a9150402e637c88a6364599d09df Mon Sep 17 00:00:00 2001 From: Giacomo Pope Date: Tue, 25 Nov 2025 12:08:16 +0000 Subject: [PATCH 56/56] fix doctests --- .../hyperelliptic_finite_field.py | 2 +- .../hyperelliptic_generic.py | 2 +- .../jacobian_homset_generic.py | 7 ++++--- .../weighted_projective/weighted_projective_homset.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py index ad332fa93bb..386b9c84461 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -87,7 +87,7 @@ def random_point(self): sage: C.random_point() # random (4 : 1 : 1) sage: type(C.random_point()) - + """ k = self.base_ring() n = 2 * k.order() + 1 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py index 81976f66fab..88bc5ba5fe0 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -193,7 +193,7 @@ def discriminant(self): sage: H.discriminant() -1622970410 sage: F = 4*f + h^2 - sage: F.discriminant() / 16^3 + sage: Delta = F.discriminant() / 16^3; Delta -1622970410 sage: G = f.derivative()^2 - f*h.derivative()^2 + f.derivative()*h.derivative()*h sage: F.resultant(G) == Delta^2*F.lc()^2 diff --git a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py index 1f2a0ecc593..9a687d16359 100644 --- a/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -115,8 +115,6 @@ def order(self): """ Compute the order of the Jacobian. - TODO: currently using lazy methods by calling sage - EXAMPLES: We compute the order of a superspecial hyperelliptic curve of genus 3:: @@ -128,7 +126,10 @@ def order(self): True sage: J(GF(7^2)).order() == (7+1)^6 True - sage: J(QQ).order() + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 - 1) + sage: J = H.jacobian() + sage: J.order() Traceback (most recent call last): ... NotImplementedError diff --git a/src/sage/schemes/weighted_projective/weighted_projective_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py index 9c3949753b3..9c1cf6605ff 100644 --- a/src/sage/schemes/weighted_projective/weighted_projective_homset.py +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -45,7 +45,7 @@ class SchemeHomset_points_weighted_projective_ring(SchemeHomset_points): sage: R. = QQ[] sage: H = HyperellipticCurveSmoothModel(x^6 + x - 17); H Hyperelliptic Curve over Rational Field defined by y^2 = x^6 + x - 17 - Page: P = H(2, -7); P + sage: P = H(2, -7); P (2 : -7 : 1) sage: type(P)