diff --git a/src/doc/en/reference/arithmetic_curves/index.rst b/src/doc/en/reference/arithmetic_curves/index.rst index a1b528234e2..f4ec31bdd06 100644 --- a/src/doc/en/reference/arithmetic_curves/index.rst +++ b/src/doc/en/reference/arithmetic_curves/index.rst @@ -128,4 +128,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/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1f1cd007290..6de04e7b9f3 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2980,6 +2980,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/all.py b/src/sage/schemes/all.py index f5126a0dee1..c3b8c49976f 100644 --- a/src/sage/schemes/all.py +++ b/src/sage/schemes/all.py @@ -42,6 +42,12 @@ 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 * + +from sage.schemes.hyperelliptic_curves_smooth_model.all import * + +from sage.schemes.weighted_projective.all import * diff --git a/src/sage/schemes/curves/constructor.py b/src/sage/schemes/curves/constructor.py index 2c407a4e7be..51abc8f5a13 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..de05e3c059d --- /dev/null +++ b/src/sage/schemes/curves/weighted_projective_curve.py @@ -0,0 +1,91 @@ +# 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") + self._weights = A._weights + 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 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) 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..6d9736c739c --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/all.py @@ -0,0 +1,3 @@ +from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_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..c3a694dd279 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_constructor.py @@ -0,0 +1,272 @@ +r""" +Constructor for hyperelliptic curves using the smooth model +in weighted projective space `\mathbb{P}(1 : g + 1 : 1)`. + +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, + distinguished_point=None): + 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 `\mathbb{P}(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 = 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 + 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 = HyperellipticCurveSmoothModel(x^6 + 2*x - 1, 2*x - 2) + Traceback (most recent call last): + ... + 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) + + """ + + # --------------------------- + # 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 = {f} and h = {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 = {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 + 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 + + H = cls(defining_polynomial, f, h, genus) + if distinguished_point: + H.set_distinguished_point(H(distinguished_point)) + return 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 new file mode 100644 index 00000000000..386b9c84461 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_finite_field.py @@ -0,0 +1,1772 @@ +""" +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 +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 +): + """ + 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): + """ + 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` + """ + # 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) + + @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 _ 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") + + lower_bound = (2 * g + 1) * (2 * N - 1) + if p <= lower_bound: + raise ValueError( + f"p={p} should be greater than (2*g+1)(2*N-1)={lower_bound}" + ) + + 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): + 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 + + 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). + + # Handle the special case for char 2 + 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 + 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 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""" + 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:: + + 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 -- David Harvey + 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""" + Return the Cartier matrix of the hyperelliptic curve. + + INPUT: + + - ``H`` : 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""" + Return the Hasse--Witt matrix of the hyperelliptic curve. + + INPUT: + + - ``H`` : 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""" + 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}` + + 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""" + Return the `p`-rank of the hyperelliptic curve. + + INPUT: + + - ``H`` : 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..4a2f2b62bd1 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_g2.py @@ -0,0 +1,277 @@ +""" +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, + 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): + r""" + 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 + `[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:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) + 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 + 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, + ) + + 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..88bc5ba5fe0 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_generic.py @@ -0,0 +1,1751 @@ +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 +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 + +from sage.schemes.curves.weighted_projective_curve import WeightedProjectiveCurve +from sage.schemes.weighted_projective.weighted_projective_space import WeightedProjectiveSpace + + +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) + + 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) + 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. + + 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 + + # 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 = 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)) + 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: 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 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: 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 + 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 + 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 + hy = 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_{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 + + 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] + 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 + 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): + """ + Return `G^\\pm(x)` for curves in the split degree model. + + 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 + + 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. + + 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)] + + :: + + 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()] + + 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 the `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): + """ + 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:: + + 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:: + + 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): + """ + Return 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. + + .. SEEALSO:: + + :func:`~sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_generic.set_distinguished_point` + + 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 + + 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 + + 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``. + + 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) + except ValueError: + raise TypeError("P0 must be a point on the hyperelliptic curve") + self._distinguished_point = P0 + + @cached_method + def jacobian(self): + r""" + 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 + + .. 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:: + + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(2*x^5 + 4*x^4 + x^3 - x, x^3 + x + 1) + 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 + 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 = \lceil g/2 \rceil`:: + + 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 = 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 + `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 = H.jacobian() + 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, + ) + + return HyperellipticJacobian_generic(self) + + @cached_method + def projective_curve(self): + """ + Return 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 + :meth:`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 functions from old implementation. + # ------------------------------------------- + + def is_singular(self, *args, **kwargs): + r""" + Return ``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""" + Return ``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 + """ + 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() + f_new = f((x * rt + 1) / x).numerator() # move rt to "infinity" + + return HyperellipticCurveSmoothModel(f_new, 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. Return a string to initialize this hyperelliptic + 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): + """ + Compute the generators of the special hyperelliptic quotient ring + + EXAMPLES:: + + 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 + + S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self) + return S.gens() + + def invariant_differential(self): + """ + Return `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 + + S = monsky_washnitzer.SpecialHyperellipticQuotientRing(self) + MW = monsky_washnitzer.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 + 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`` + - ``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( + f"P = {P} is a point at infinity. Use local_coordinates_at_infinity instead" + ) + if self.is_weierstrass_point(P): + raise TypeError( + f"P = {P} is a Weierstrass point. Use local_coordinates_at_weierstrass instead" + ) + f, h = self.hyperelliptic_polynomials() + 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)`, 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`` + - ``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( + f"P = {P} is a point at infinity. Use local_coordinates_at_infinity instead" + ) + if not self.is_weierstrass_point(P): + raise TypeError( + f"P = {P} is not a Weierstrass point. Use local_coordinates_at_nonweierstrass instead" + ) + + 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() + 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( + 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) + 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 + 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`` + - ``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..52746f1bd0e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_padic_field.py @@ -0,0 +1,1476 @@ +""" +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 +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 +): + + 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) + + def local_analytic_interpolation(self, P, 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 + + 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 + from sage.schemes.hyperelliptic_curves_smooth_model.hyperelliptic_constructor import ( + HyperellipticCurveSmoothModel, + ) + + 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, 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`` + + 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, 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`) + + 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, 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, + 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..881d6da6b3d --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/hyperelliptic_rational_field.py @@ -0,0 +1,143 @@ +""" +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 + + +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): + """ + 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..d76c809dd8b --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_generic.py @@ -0,0 +1,46 @@ +""" +Jacobians of genus-2 curves +""" + +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): + """ + 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( + *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..378a324d7ce --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_inert.py @@ -0,0 +1,16 @@ +""" +Rational point sets of Jacobians of genus-2 curves (inert case) +""" + +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..ce1b234fe8a --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_ramified.py @@ -0,0 +1,15 @@ +""" +Rational point sets of Jacobians of genus-2 curves (ramified case) +""" +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..d334915ab87 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_g2_homset_split.py @@ -0,0 +1,15 @@ +""" +Rational point sets of Jacobians of genus-2 curves (split case) +""" +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..4ff595df501 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_generic.py @@ -0,0 +1,191 @@ +r""" + Jacobian of a general hyperelliptic curve + + 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): + 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: + 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`. + """ + # 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): + """ + 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 rational elements of the Jacobian. + + .. SEEALSO:: + + :meth:`sage.schemes.hyperelliptic_curves_smooth_model.jacobian_homset_generic.points`. + """ + + 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 new file mode 100644 index 00000000000..9a687d16359 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_generic.py @@ -0,0 +1,912 @@ +""" +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 +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 + +from sage.schemes.weighted_projective.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, +) +from sage.structure.element import parent + + +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): + """ + 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): + """ + 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: 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__: + 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. + + EXAMPLES: + + 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 + sage: R. = QQ[] + sage: H = HyperellipticCurveSmoothModel(x^8 - 1) + sage: J = H.jacobian() + sage: J.order() + Traceback (most recent call last): + ... + NotImplementedError + """ + if isinstance(self.base_ring(), FiniteField_generic): + return sum(self.extended_curve().frobenius_polynomial()) + + raise NotImplementedError + + @cached_method + def _curve_frobenius_roots(self): + r""" + Return the roots of the charpoly of frobenius on the extended curve. + + EXAMPLES: + + 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 + + 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) / \mathbb{F}_{q^n}|`. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: H = HyperellipticCurveSmoothModel(x^6 + x + 1) + sage: J = H.jacobian() + 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): + 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): + """ + 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: 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) + 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: 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 + 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`; + return `[(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 + + 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? + + - 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): + 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(), + ) # 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) + else: + # 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") + + return self._morphism_element(self, u, v, check=check) + + 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() + 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. + + 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() + 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. + + 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() + 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:: + + 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): + """ + 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 + + 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) + + 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) + + import itertools + 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. + + 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() + 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. + + 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() + 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..0bf66ab054f --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_inert.py @@ -0,0 +1,61 @@ +""" +Rational point sets on a Jacobian of a hyperelliptic curve (inert case) +""" +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): + """ + 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. + + 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: + 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..cdbae300b1e --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_ramified.py @@ -0,0 +1,28 @@ +""" +Rational point sets on a Jacobian of a hyperelliptic curve (ramified case) +""" +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): + """ + 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 new file mode 100644 index 00000000000..2aee823378c --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_homset_split.py @@ -0,0 +1,363 @@ +""" +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, +) +from sage.schemes.hyperelliptic_curves_smooth_model.jacobian_morphism import ( + MumfordDivisorClassFieldSplit, +) + +from sage.schemes.weighted_projective.weighted_projective_point import ( + SchemeMorphism_point_weighted_projective_ring, +) +from sage.structure.element import parent + + +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 + + 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() + n = (g + 1) // 2 + return self._morphism_element(self, R.one(), R.zero(), n) + + def point_to_mumford_coordinates(self, P): + 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 `\infty_+` + - `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() + [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""" + 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(). + + 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): + # TODO: Test this path when args is a tuple + 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): + 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 + `\infty_+`. + + 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. + + 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() + 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): + 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_+`. + + 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() + 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): + 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 + + 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 `n_0` , + 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) + 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 + 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..8aae18a14b9 --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/jacobian_morphism.py @@ -0,0 +1,656 @@ +""" +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 +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): + """ + 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): + raise TypeError(f"arguments u={u} and v={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={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? + + self._parent = parent + self._u = u + 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): + 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 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): + """ + 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 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 + 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) + + 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)) + + # 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): + """ + 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() + + 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): + """ + 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") + + super().__init__(parent, u, v, check=check) + + +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") + + 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 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") + + # 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 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 + + 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): + """ + 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)) + + # 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""" + 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() + _, 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..da68694587d --- /dev/null +++ b/src/sage/schemes/hyperelliptic_curves_smooth_model/meson.build @@ -0,0 +1,27 @@ +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', + 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/meson.build b/src/sage/schemes/meson.build index 4dac80900e9..a957fc42fa4 100644 --- a/src/sage/schemes/meson.build +++ b/src/sage/schemes/meson.build @@ -12,10 +12,12 @@ 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') 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/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index da3f5b502cd..fd6a2d5e846 100644 --- 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 60ba4628d67..b2a65fcdea7 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -1080,7 +1080,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/__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_homset.py b/src/sage/schemes/weighted_projective/weighted_projective_homset.py new file mode 100644 index 00000000000..9c1cf6605ff --- /dev/null +++ b/src/sage/schemes/weighted_projective/weighted_projective_homset.py @@ -0,0 +1,52 @@ +""" +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: + + EXAMPLES:: + + 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 Rational Field + sage: type(W.point_homset()) + + sage: W(2, 24, 2) + (1 : 3 : 1) + sage: W(2, 24, 2) == W(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 + sage: P = H(2, -7); P + (2 : -7 : 1) + sage: type(P) + + """ 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..006bb759de7 --- /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 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 + 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..4501b7e4957 --- /dev/null +++ b/src/sage/schemes/weighted_projective/weighted_projective_space.py @@ -0,0 +1,417 @@ +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 weighted 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. + """ + 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.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 weighted 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 weighted 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([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" + "rings not implemented") + from sage.schemes.curves.constructor import Curve + return Curve(F, self)