diff --git a/src/doc/en/reference/curves/index.rst b/src/doc/en/reference/curves/index.rst index 2bc0d098cba..c7a4dfb1cef 100644 --- a/src/doc/en/reference/curves/index.rst +++ b/src/doc/en/reference/curves/index.rst @@ -1,5 +1,11 @@ +Plane and Space Curves +====================== + +Sage enables computations with curves in affine and projective ambient spaces, +curves over `\CC` as Riemann surfaces, and Jacobians of projective curves. + Curves -====== +------ .. toctree:: :maxdepth: 1 @@ -12,10 +18,8 @@ Curves sage/schemes/curves/closed_point sage/schemes/curves/zariski_vankampen - sage/schemes/jacobians/abstract_jacobian - Plane conics -============ +------------ .. toctree:: :maxdepth: 1 @@ -28,7 +32,7 @@ Plane conics sage/schemes/plane_conics/con_rational_function_field Plane quartics -========================= +-------------- .. toctree:: :maxdepth: 1 @@ -37,11 +41,20 @@ Plane quartics sage/schemes/plane_quartics/quartic_generic Riemann surfaces -================ +---------------- .. toctree:: :maxdepth: 1 sage/schemes/riemann_surfaces/riemann_surface +Jacobians +--------- + +.. toctree:: + :maxdepth: 1 + + sage/schemes/jacobians/abstract_jacobian + sage/schemes/jacobians/khuri_makdisi + .. include:: ../footer.txt diff --git a/src/doc/en/reference/function_fields/index.rst b/src/doc/en/reference/function_fields/index.rst index 9f9a7e8c42d..7100f300b47 100644 --- a/src/doc/en/reference/function_fields/index.rst +++ b/src/doc/en/reference/function_fields/index.rst @@ -38,6 +38,19 @@ algebraic closure of `\QQ`. A basic reference for the theory of algebraic function fields is [Stich2009]_. +Jacobians of function fields +---------------------------- + +Arithmetic in Jacobians of function fields are available in two flavors. + +.. toctree:: + :maxdepth: 1 + + sage/rings/function_field/jacobian_base + sage/rings/function_field/jacobian_hess + sage/rings/function_field/jacobian_khuri_makdisi + sage/rings/function_field/khuri_makdisi + A Support Module ---------------- diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 09b78514e1c..282fd59838d 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -3155,6 +3155,9 @@ REFERENCES: Cryptanalysis* ; 2002' available at http://www.engr.mun.ca/~howard/PAPERS/ldc_tutorial.pdf +.. [Hes2004] Florian Hess, "Computing relations in divisor class groups of + algebraic curves over finite fields," Preprint, 2004. + .. [Hes2002] Florian Hess, "Computing Riemann-Roch spaces in algebraic function fields and related topics," J. Symbolic Comput. 33 (2002), no. 4, 425--445. @@ -3750,6 +3753,9 @@ REFERENCES: block cipher*, Lightweight Cryptography Workshop, 2016. https://www.nist.gov/sites/default/files/documents/2016/10/18/karpman-paper-lwc2016.pdf +.. [Khu2004] \K. Khuri-Makdisi. *Linear algebra algorithms for divisors on an algebraic curve*, + Mathematics of Computation 73, no. 245 (2004) pp. 333-357. + .. [Kin1992] Nancy G. Kinnersley, *The vertex separation number of a graph equals its path-width*, Information Processing Letters 42(6):345-350, 1992. :doi:`10.1016/0020-0190(92)90234-M`. diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index af5b6c4d924..f3562454ca2 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1245,3 +1245,93 @@ def extension_constant_field(self, k): """ from .extensions import ConstantFieldExtension return ConstantFieldExtension(self, k) + + @cached_method + def jacobian(self, model=None, base_div=None, **kwds): + """ + Return the Jacobian of the function field. + + INPUT: + + - ``model`` -- (default: ``'hess'``) model to use for arithmetic + + - ``base_div`` -- an effective divisor + + The degree of the base divisor should satisfy certain degree condition + corresponding to the model used. The following table lists these + conditions. Let `g` be the genus of the function field. + + - ``hess``: ideal-based arithmetic; requires base divisor of degree `g` + + - ``km_large``: Khuri-Makdisi's large model; requires base divisor of + degree at least `2g + 1` + + - ``km_medium``: Khuri-Makdisi's medium model; requires base divisor of + degree at least `2g + 1` + + - ``km_small``: Khuri-Makdisi's small model requires base divisor of + degree at least `g + 1` + + We assume the function field has a rational place. If a base divisor is + not given, one is constructed using an arbitrary rational place. + + EXAMPLES:: + + sage: A. = AffineSpace(GF(5), 2) + sage: C = Curve(y^2*(x^3 - 1) - (x^3 - 2)) + sage: F = C.function_field() + sage: F.jacobian() + Jacobian of Function field in y defined by (x^3 + 4)*y^2 + 4*x^3 + 2 (Hess model) + """ + from .place import FunctionFieldPlace + + if model is None: + model = 'hess' + + if base_div is None: + try: + base_place = self.get_place(1) + except AttributeError: + raise ValueError('failed to obtain a rational place; provide a base divisor') + if base_place is None: + raise ValueError('the function field has no rational place') + # appropriate base divisor is constructed below. + else: + if isinstance(base_div, FunctionFieldPlace): + base_div = base_div.divisor() + + g = self.genus() + curve = kwds.get('curve') + + if model.startswith('km'): + from .jacobian_khuri_makdisi import Jacobian + if model == 'km' or model.endswith('large'): + if base_div is None: + base_div = (2*g + 1) * base_place + if not base_div.degree() >= 2*g + 1: + raise ValueError("Khuri-Makdisi large model requires base divisor of degree " + "at least 2*g + 1 for genus g") + return Jacobian(self, base_div, model='large', curve=curve) + elif model.endswith('medium'): + if base_div is None: + base_div = (2*g + 1) * base_place + if not base_div.degree() >= 2*g + 1: + raise ValueError("Khuri-Makdisi medium model requires base divisor of degree " + "at least 2*g + 1 for genus g") + return Jacobian(self, base_div, model='medium', curve=curve) + elif model.endswith('small'): + if base_div is None: + base_div = (g + 1) * base_place + if not base_div.degree() >= g + 1: + raise ValueError("Khuri-Makdisi small model requires base divisor of degree " + "at least g + 1 for genus g") + return Jacobian(self, base_div, model='small', curve=curve) + elif model == 'hess': + from .jacobian_hess import Jacobian + if base_div is None: + base_div = g * base_place + if base_div.degree() != g: + raise ValueError("Hess model requires base divisor of degree g for genus g") + return Jacobian(self, base_div, curve=curve) + + raise ValueError("unknown model") diff --git a/src/sage/rings/function_field/function_field_polymod.py b/src/sage/rings/function_field/function_field_polymod.py index 44c1390bc86..d1813c7d67a 100644 --- a/src/sage/rings/function_field/function_field_polymod.py +++ b/src/sage/rings/function_field/function_field_polymod.py @@ -1896,6 +1896,50 @@ def residue_field(self, place, name=None): """ return place.residue_field(name=name) + def places_infinite(self, degree=1): + """ + Return a list of the infinite places with ``degree``. + + INPUT: + + - ``degree`` -- positive integer (default: `1`) + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2) + sage: K. = FunctionField(F) + sage: R. = PolynomialRing(K) + sage: L. = K.extension(t^4 + t - x^5) + sage: L.places_infinite(1) + [Place (1/x, 1/x^4*y^3)] + """ + return list(self._places_infinite(degree)) + + def _places_infinite(self, degree): + """ + Return a generator of *infinite* places with ``degree``. + + INPUT: + + - ``degree`` -- positive integer + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2) + sage: K. = FunctionField(F) + sage: R. = PolynomialRing(K) + sage: L. = K.extension(t^4 + t - x^5) + sage: L._places_infinite(1) + + """ + Oinf = self.maximal_order_infinite() + for prime, _, _ in Oinf.decomposition(): + place = prime.place() + if place.degree() == degree: + yield place + class FunctionField_char_zero(FunctionField_simple): """ @@ -2129,50 +2173,6 @@ def _places_finite(self, degree): if place.degree() == degree: yield place - def places_infinite(self, degree=1): - """ - Return a list of the infinite places with ``degree``. - - INPUT: - - - ``degree`` -- positive integer (default: `1`) - - EXAMPLES:: - - sage: # needs sage.rings.finite_rings - sage: F. = GF(2) - sage: K. = FunctionField(F) - sage: R. = PolynomialRing(K) - sage: L. = K.extension(t^4 + t - x^5) - sage: L.places_infinite(1) - [Place (1/x, 1/x^4*y^3)] - """ - return list(self._places_infinite(degree)) - - def _places_infinite(self, degree): - """ - Return a generator of *infinite* places with ``degree``. - - INPUT: - - - ``degree`` -- positive integer - - EXAMPLES:: - - sage: # needs sage.rings.finite_rings - sage: F. = GF(2) - sage: K. = FunctionField(F) - sage: R. = PolynomialRing(K) - sage: L. = K.extension(t^4 + t - x^5) - sage: L._places_infinite(1) - - """ - Oinf = self.maximal_order_infinite() - for prime, _, _ in Oinf.decomposition(): - place = prime.place() - if place.degree() == degree: - yield place - def gaps(self): """ Return the gaps of the function field. diff --git a/src/sage/rings/function_field/jacobian_base.py b/src/sage/rings/function_field/jacobian_base.py new file mode 100644 index 00000000000..963e14f975b --- /dev/null +++ b/src/sage/rings/function_field/jacobian_base.py @@ -0,0 +1,794 @@ +r""" +Jacobians of function fields + +This module provides base classes for Jacobians of function fields. + +Jacobian +-------- + +The Jacobian of a function field is created by default in the Hess model, with +a base divisor of degree `g` the genus of the function field. The base divisor +is automatically chosen if not given. :: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: J = F.jacobian() + sage: J + Jacobian of Function field in z defined by z^3 + 23*y^2*z + 6 (Hess model) + sage: J.base_divisor().degree() + 1 + +Explicitly specify a model if you want Jacobians in different models. :: + + sage: J_km = F.jacobian(model='km_large') + sage: J_km + Jacobian of Function field in z defined by z^3 + 23*y^2*z + 6 (Khuri-Makdisi large model) + +Group of rational points +------------------------ + +The group of rational points of a Jacobian is created from the Jacobian. A +point of the Jacobian group is determined by a divisor of degree zero. To +represent the point, a divisor of the form `D-B` is selected where `D` is an +effective divisor of the same degree with the base divisor `B`. Hence the point +is simply represented by the divisor `D`. :: + + sage: G = J.group() + sage: G.order() + 30 + sage: pl1 = C([1,8,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: p1 = G.point(pl1 - pl2) + sage: p1 + [Place (y + 1, z + 6)] + sage: p2 = G.point(pl2 - pl1) + sage: p2 + [Place (y + 28, z + 6)] + sage: p1 + p2 == G.zero() + True + sage: p1.order() + 5 + +We can get the corresponding point in the Jacobian in a different model. :: + + sage: p1km = J_km(p1) + sage: p1km.order() + 5 + sage: p1km + Point of Jacobian determined by + [ 1 0 0 0 0 0 11 0 0] + [ 0 1 0 0 0 0 18 0 0] + [ 0 0 1 0 0 0 11 0 0] + [ 0 0 0 1 0 0 18 1 0] + [ 0 0 0 0 1 0 25 0 19] + [ 0 0 0 0 0 1 8 8 0] + +AUTHORS: + +- Kwankyu Lee (2022-01-24): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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/ +# **************************************************************************** + +import math + +from sage.arith.misc import integer_floor, integer_ceil + +from sage.structure.parent import Parent +from sage.structure.element import ModuleElement + +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups +from sage.categories.schemes import Schemes +from sage.categories.pushout import ConstructionFunctor, pushout + +from sage.rings.integer_ring import IntegerRing +from sage.rings.integer import Integer + + +class JacobianPoint_base(ModuleElement): + """ + Abstract base class of points of Jacobian groups. + """ + pass + + +class JacobianPoint_finite_field_base(JacobianPoint_base): + """ + Points of Jacobians over finite fields. + """ + def order(self): + """ + Return the order of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = F.get_place(1) + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.order() + 15 + + ALGORITHM: Shanks' Baby Step Giant Step + """ + G = self.parent() + B = G._bound_on_order() + q = integer_ceil(B.sqrt()) + zero = G.zero() + + # baby steps + b = [zero] + g = self + for i in range(q - 1): + if g == zero: + return i + 1 + b.append(g) + g = g + self + + # giant steps + g0 = self.multiple(-q) + g = g0 + for i in range(q - 1): + for r in range(q): + if g == b[r]: + return q * (i + 1) + r + g = g + g0 + + # order is neither smaller or nor larger than this + return q**2 + + def frobenius(self): + """ + Return the image of the point acted by the Frobenius automorphism. + + EXAMPLES:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G1 = J.group() + sage: G1.order() + 11 + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: pts1 = G1.get_points(11) + sage: pts3 = G3.get_points(12) + sage: pt = next(pt for pt in pts3 if pt not in pts1) + sage: pt.frobenius() == pt + False + sage: pt.frobenius().frobenius().frobenius() == pt + True + """ + G = self.parent() + return G._frobenius_on(self) + + +class Jacobians(Schemes): + """ + The category of Jacobians attached to function fields. + """ + pass + + +class JacobianGroupFunctor(ConstructionFunctor): + """ + A construction functor for Jacobian groups. + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: F, obj = G.construction() + sage: F + JacobianGroupFunctor + """ + rank = 20 + + def __init__(self, field): + """ + Initialize. + + TESTS:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K = k.extension(2) + sage: G = J.group(K) + sage: F, obj = G.construction() + sage: TestSuite(F).run() + """ + super().__init__(Jacobians(), CommutativeAdditiveGroups()) + + self._field = field + + def _apply_functor(self, jacobian): + """ + Apply this functor to ``jacobian``. + + INPUT: + + - ``jacobian`` -- a Jacobian + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K = k.extension(2) + sage: G = J.group(K) + sage: F, obj = G.construction() + sage: F(obj) is G # indirect doctest + True + """ + return jacobian.group(self._field) + + def merge(self, other): + """ + Return the functor merging ``self`` and ``other`` + + INPUT: + + - ``other`` -- a functor + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K2 = k.extension(2) + sage: G2 = J.group(K2) + sage: K3= k.extension(3) + sage: G3 = J.group(K3) + sage: sage.categories.pushout.pushout(G2, G3) # indirect doctest + Group of rational points of Jacobian over Finite Field in z6 of size 7^6 (Hess model) + """ + if not isinstance(other, JacobianGroupFunctor): + return None + K = pushout(self._field, other._field) + return JacobianGroupFunctor(K) + + +class JacobianGroup_base(Parent): + """ + Groups of rational points of Jacobians. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 7 (Hess model) + """ + _embedding_map_class = None + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements', '_test_pickling']) + """ + super().__init__(base=IntegerRing(), category=CommutativeAdditiveGroups()) + + self._parent = parent + self._function_field = function_field + self._genus = parent._function_field.genus() # equals function_field.genus() + self._base_div = base_div + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 7 (Hess model) + """ + F = self._function_field + k = F.constant_base_field() + return f'Group of rational points of Jacobian over {k}' + + def _coerce_map_from_(self, S): + """ + Return the coerce map from ``S`` if ``S`` is embedded to ``self``. + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.has_coerce_map_from(G1) + True + """ + if isinstance(S, JacobianGroup_base) and S.parent() is self.parent(): + K = self._function_field.constant_base_field() + k = S._function_field.constant_base_field() + if K.has_coerce_map_from(k): + return self._embedding_map_class(S, self) + return None + + def construction(self): + """ + Return the data for a functorial construction of this Jacobian group. + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K2 = k.extension(2) + sage: G2 = J.group(K2) + sage: K3= k.extension(3) + sage: G3 = J.group(K3) + sage: p1, p2 = G2.get_points(2) + sage: q1, q2 = G3.get_points(2) + sage: (p1 + q1).parent() is (p2 + q2).parent() + True + """ + K = self._function_field.constant_base_field() + return (JacobianGroupFunctor(K), self._parent) + + def parent(self): + """ + Return the Jacobian to which this Jacobian group belongs. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: G.parent() + Jacobian of Projective Plane Curve over Finite Field of size 7 + defined by x^3 - y^2*z - 2*z^3 (Hess model) + """ + return self._parent + + def function_field(self): + """ + Return the function field to which this Jacobian group attached. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: G.function_field() + Function field in z defined by z^3 + 4*y^2*z + 3 + """ + return self._function_field + + def base_divisor(self): + """ + Return the base divisor that is used to represent points of this group. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G.base_divisor() + Place (1/y, 1/y*z) + sage: _ == 1*b + True + + The base divisor is the denominator (negative part) of the divisor of + degree zero that represents a point. :: + + sage: p = C([-1,2,1]).place() + sage: G.point(p - b).divisor() + - Place (1/y, 1/y*z) + + Place (y + 2, z + 1) + """ + return self._base_div + + +class JacobianGroup_finite_field_base(JacobianGroup_base): + """ + Jacobian groups of function fields over finite fields. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 7 (Hess model) + """ + def _bound_on_order(self): + """ + Return an upper bound on the order of the abelian group. + + This bound depends on the genus and the order of the constant field + of the function field. This simple bound is from [Hes2004]_. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G._bound_on_order() + 23 + """ + F = self._function_field + q = F.constant_base_field().order() + g = self._genus + + c = 2*g/(q.sqrt() - 1) + return integer_floor(math.exp(c)*q**g) + + def order(self, algorithm='numeric'): + """ + Return the order of the Jacobian group. + + INPUT: + + - ``algorithm`` -- ``'numeric'`` (default) or ``'algebraic'`` + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G.order() + 7 + """ + F = self._parent._function_field + g = F.genus() + b = self._function_field.constant_base_field().degree() // F.constant_base_field().degree() + + f = F.L_polynomial() + + if algorithm == 'numeric': + # numeric method - fast but might be inaccurate by numerical noise + from sage.rings.qqbar import AlgebraicField + h = Integer(math.prod([(1-a**(-b))**m for a, m in f.change_ring(AlgebraicField()).roots()])) + return h + + # algebraic method - slow + + es = [] + s = -1 + for i in range(1, 2*g + 1): + es.append(s*f[i]) + s = -s + es + + ps = [es[0]] + for i in range(1, 2*g): + p = 0 + s = 1 + for j in range(i): + p = p + s*es[j]*ps[-j-1] + s = -s + ps.append(p + s*(i + 1)*es[i]) + + while len(ps) < b*2*g: + p = 0 + s = 1 + for j in range(2*g): + p = p + s*es[j]*ps[-j-1] + s = -s + ps.append(p) + + qs = [ps[b*(i + 1) - 1] for i in range(2*g)] + + fs = [qs[0]] + for i in range(1, 2*g): + k = qs[i] + s = -1 + for j in range(i): + k = k + s*fs[j]*qs[i - j - 1] + s = -s + fs.append(-s*k // (i + 1)) + + bs = [1] + s = -1 + for i in range(2*g): + bs.append(s*fs[i]) + s = -s + + return sum(bs) + + def get_points(self, n): + """ + Return `n` points of the Jacobian group. + + If `n` is greater than the order of the group, then returns + all points of the group. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: pts = G.get_points(G.order()) + sage: len(pts) + 11 + """ + lst = [] + S = iter(self) + try: + for i in range(n): + lst.append(next(S)) + except StopIteration: + pass + + return lst + + +class Jacobian_base(Parent): + """ + Jacobians of function fields. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: F.jacobian() + Jacobian of Function field in y defined by y^2 + y + (x^2 + 1)/x (Hess model) + """ + def __init__(self, function_field, base_div, **kwds): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: TestSuite(J).run() + """ + super().__init__(category=Jacobians(), facade=True) + + self._function_field = function_field + self._base_div = base_div + self._system = {} + self._base_place = None + self._curve = kwds.get('curve') + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: F.jacobian() + Jacobian of Function field in y defined by y^2 + y + (x^2 + 1)/x (Hess model) + """ + if self._curve is not None: + F = self._curve + else: + F = self._function_field + return f'Jacobian of {F}' + + def _an_element_(self): + """ + Return an element of ``self``. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.an_element() + [Place (1/x, 1/x*y)] + """ + return next(iter(self.group())) + + def __call__(self, x): + """ + Return the point of ``self`` constructed from ``x`` + + It is assumed that ``self`` and ``x`` are points of the Jacobians + attached to the same function field. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J_hess = F.jacobian(model='hess') + sage: G = J_hess.group() + sage: p = G.get_points(3)[2] + sage: Jkm = F.jacobian(model='km_large') + sage: q = Jkm(p) + sage: p.order() == q.order() + True + sage: J_hess(q) == p + True + """ + F = self._function_field + if isinstance(x, JacobianPoint_base): + Gx = x.parent() + Jx = Gx.parent() + if Jx._function_field is F: + k = Gx._function_field.constant_base_field() + G = self.group(k) + K = G._function_field + return G.point(K.divisor_group()(x.divisor())) + if x in F.place_set(): + return self(x - x.degree()*self._base_place) + if x in F.divisor_group(): + G = self.group() + return G.point(x) + raise ValueError(f"Cannot create a point of the Jacobian from {x}") + + def curve(self): + """ + Return the projective curve to which this Jacobian is attached. + + If the Jacobian was constructed from a function field, then returns nothing. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.curve() + """ + return self._curve + + def facade_for(self): + """ + Return a parent that this Jacobian is a facade for. + + Only one parent, the group of rational points, is returned, even though + the Jacobian can be seen as a facade for all groups of rational points + over field extensions of the base constant field of the function field. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.facade_for() + Group of rational points of Jacobian over Finite Field of size 2 (Hess model) + """ + return self.group() + + def base_divisor(self): + """ + Return the base divisor used to construct the Jacobian. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: b = F.get_place(1) + sage: J = F.jacobian(base_div=b) + sage: J.base_divisor() == b + True + """ + return self._base_div + + def group(self, k_ext=None): + """ + Return the group of rational points of the Jacobian. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: b = F.get_place(1) + sage: J = F.jacobian(base_div=b) + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 2 (Hess model) + """ + F = self._function_field + k = F.constant_base_field() + + if k_ext in self._system: + return self._system[k_ext][0] + + if k_ext is None or k_ext is k: + ext = F.extension_constant_field(k) + grp = self._group_class(self, F, self._base_div) + if self._base_place is not None: + grp._base_place = self._base_place + self._system[k] = (grp, ext) + else: + ext = F.extension_constant_field(k_ext) + base_div = ext.conorm_divisor(self._base_div) + grp = self._group_class(self, ext.top(), base_div) + if self._base_place is not None: + grp._base_place = ext.conorm_place(self._base_place) + self._system[k_ext] = (grp, ext) + + return grp + + def set_base_place(self, place): + """ + Set ``place`` as the base place. + + INPUT: + + - ``place`` -- a rational place of the function field. + + The base place `B` is used to map a rational place `P` of the function + field to the point of the Jacobian defined by the divisor `P - B`. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: B = F.get_place(1) + sage: J.set_base_place(B) + sage: Q = F.places()[-1] + sage: J(Q) + [Place (x + 1, x*y + 1)] + sage: J(Q).parent() + Group of rational points of Jacobian over Finite Field of size 2 (Hess model) + sage: J(B) + [Place (x, x*y)] + sage: J(B).is_zero() + True + """ + self._base_place = place + + for k in self._system: + grp, ext = self._system[k] + grp._base_place = ext.conorm_place(place) diff --git a/src/sage/rings/function_field/jacobian_hess.py b/src/sage/rings/function_field/jacobian_hess.py new file mode 100644 index 00000000000..ac8d3a6775f --- /dev/null +++ b/src/sage/rings/function_field/jacobian_hess.py @@ -0,0 +1,1046 @@ +r""" +Jacobians in Hess model + +This module implements Jacobian arithmetic based on divisor representation by +ideals. This approach to Jacobian arithmetic implementation is attributed to +Hess [Hes2002]_. + +Jacobian +-------- + +To create a Jacobian in Hess model, specify ``'hess'`` model and provide a base divisor +of degree `g`, which is the genus of the function field:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: C.geometric_genus() + 1 + sage: B = C([0,1,0]).place() + sage: B.degree() + 1 + sage: J = C.jacobian(model='hess', base_div=B) + sage: J + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Hess model) + +Group of rational points +------------------------ + +The group of rational points of a Jacobian is created from the Jacobian. A +point of the Jacobian group is determined by a divisor (of degree zero) of the +form `D - B` where `D` is an effective divisor of degree `g` and `B` is the base +divisor. Hence a point of the Jacobian group is represented by `D`. + +:: + + sage: G = J.group() + sage: P1 = C([1,8,1]).place() + sage: P2 = C([2,10,1]).place() + sage: p1 = G(P1) + sage: p2 = G(P2) + sage: p1 + [Place (y + 21, z + 28)] + sage: p2 + [Place (y + 24, z + 14)] + sage: p1 + p2 + [Place (y + 8, z + 28)] + +AUTHORS: + +- Kwankyu Lee (2022-01-24): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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.misc.cachefunc import cached_method + +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.richcmp import op_EQ, richcmp + +from sage.categories.map import Map +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups +from sage.categories.homset import Hom + +from sage.arith.misc import integer_ceil +from sage.arith.functions import lcm + +from sage.rings.integer import Integer +from sage.matrix.constructor import matrix + +from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + +from .place import FunctionFieldPlace +from .divisor import FunctionFieldDivisor + +from .jacobian_base import (Jacobian_base, + JacobianGroup_base, + JacobianGroup_finite_field_base, + JacobianPoint_base, + JacobianPoint_finite_field_base) + + +class JacobianPoint(JacobianPoint_base): + """ + Points of Jacobians represented by a pair of ideals. + + If a point of Jacobian is determined by `D`, then the divisor `D` is + represented by a pair of ideals in the finite maximal order and the + infinite maximal order of the function field. + + For efficiency reasons, the actual ideals stored are the inverted ideals + of the ideals representing the divisor `D`. + + INPUT: + + - ``parent`` -- Jacobian group + + - ``dS`` -- an ideal of the finite maximal order of a function field + + - ``ds`` -- an ideal of infinite maximal order of a function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([1,8,1]).place() + sage: p = G.point(pl - b) + sage: dS, ds = p._data + sage: -(dS.divisor() + ds.divisor()) == pl + True + """ + def __init__(self, parent, dS, ds): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([1,8,1]).place() + sage: p = G.point(pl - b) + sage: TestSuite(p).run(skip=['_test_category','_test_pickling']) + """ + super().__init__(parent) + self._data = (dS, ds) + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: G.zero() + [Place (1/y, 1/y*z)] + """ + dS, ds = self._data + divisor = (~dS).divisor() + (~ds).divisor() + return f'[{divisor}]' + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^3 - x^2*(x^2 + x + 1)^2) + sage: f = x/(y + 1) + sage: d = f.divisor() + sage: {d: 1} + {Place (1/x, 1/x^4*y^2 + 1/x^2*y + 1) + + Place (1/x, 1/x^2*y + 1) + + 3*Place (x, (1/(x^3 + x^2 + x))*y^2) + - 6*Place (x + 1, y + 1): 1} + """ + return hash(self._data) + + def _richcmp_(self, other, op): + """ + Compare ``self`` with ``other`` with respect to operator ``op``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 == p1 + True + sage: p1 != p2 + True + sage: p1 > p1 + False + sage: p1 > p2 + False + sage: p1 < p2 + True + """ + if op is op_EQ: + J = self.parent() + idS, ids = self._data + jdS, jds = other._data + return J._normalize(idS / jdS, ids / jds) is not None + else: + return richcmp(self._data, other._data, op) + + def _add_(self, other): + """ + Return the sum of ``self`` and ``other``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 + [Place (y + 8, z + 3)] + sage: p1 + p2 == p2 + p1 + True + """ + G = self.parent() + idS, ids = self._data + jdS, jds = other._data + bdS, bds = G._base_point + dS, ds = G._normalize(idS * jdS * bdS, ids * jds * bds) + return G.element_class(self.parent(), dS, ds) + + def _neg_(self): + """ + Return the negative of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: -p + [Place (y + 27, z + 1)] + sage: -(-p) == p + True + """ + G = self.parent() + idS, ids = self._data + bdS2, bds2 = G._base_point_double + dS, ds = G._normalize(~(idS * bdS2), ~(ids * bds2)) + return G.element_class(self.parent(), dS, ds) + + def multiple(self, n): + """ + Return the ``n``-th multiple of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.multiple(100) + [Place (1/y, 1/y*z + 8)] + """ + if n == 0: + return self.parent().zero() + + G = self.parent() + idS, ids = self._data + bdS, bds = G._base_point + bdS2, bds2 = G._base_point_double + idSbdS2 = idS * bdS2 + idsbds2 = ids * bds2 + + if n < 0: + bits = Integer(-n).digits(2) + else: + bits = Integer(n).digits(2) + bits.pop() + + dS = idS + ds = ids + for i in range(len(bits)): + b = bits.pop() + if b > 0: + dS, ds = G._normalize(dS * dS * idSbdS2 , ds * ds * idsbds2) + else: + dS, ds = G._normalize(dS * dS * bdS, ds * ds * bds) + if n < 0: + dS, ds = G._normalize(~(dS * bdS2), ~(ds * bds2)) + + return G.element_class(self.parent(), dS, ds) + + def addflip(self, other): + """ + Return the addflip of this and ``other`` point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,19,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1.addflip(p2) + [Place (y + 8, z + 27)] + sage: _ == -(p1 + p2) + True + """ + return -(self + other) + + def defining_divisor(self): + """ + Return the effective divisor that defines this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.defining_divisor() == pl + True + """ + dS, ds = self._data + return (~dS).divisor() + (~ds).divisor() + + def order(self, bound=None): + """ + Return the order of this point. + + ALGORITHM: Shanks' Baby Step Giant Step + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: p = C([-1,2,1]).place() + sage: pt = G.point(p - b) + sage: pt.order() + 30 + """ + if bound is None: # naive + J = self.parent() + zero = J.zero() + + m = self + r = 1 + while m != zero: + m = m + self + r += 1 + return r + + # if bound is given, deploy Shanks' Baby Step Giant Step + + J = self.parent() + B = J.bound_on_order() + q = integer_ceil(B.sqrt()) + zero = J.zero() + + # baby steps + b = [zero] + g = self + for i in range(q - 1): + if g == zero: + return i + 1 + b.append(g) + g = g + self + + # giant steps + g0 = (-q)*(self) + g = g0 + for i in range(q - 1): + for r in range(q): + if g == b[r]: + return q * (i + 1) + r + g = g + g0 + + # order is neither smaller or nor larger than this + return q**2 + + def divisor(self): + """ + Return the divisor representing this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: G.point(p.divisor()) == p + True + """ + J = self.parent() + dS, ds = self._data + return (~dS).divisor() + (~ds).divisor() - J._base_div + + +class JacobianPoint_finite_field(JacobianPoint, JacobianPoint_finite_field_base): + """ + Points of Jacobians over finite fields + """ + pass + + +class JacobianGroupEmbedding(Map): + """ + Embeddings between Jacobian groups. + + INPUT: + + - ``base_group`` -- Jacobian group over a base field + + - ``extension_group`` -- Jacobian group over an extension field + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Hess model) + """ + def __init__(self, base_group, extension_group): + """ + Initialize. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: map = G3.coerce_map_from(G1) + sage: TestSuite(map).run(skip=['_test_category', '_test_pickling']) + """ + F = base_group._function_field + F_base = F.base_field() + K = F.constant_base_field() + + F_ext = extension_group._function_field + F_ext_base = F_ext.base_field() + K_ext = F_ext.constant_base_field() + + # construct embedding of F into F_ext + embedK = K_ext.coerce_map_from(K) + embedF_base = F_base.hom(F_ext_base.gen(), embedK) + if F.degree() > 1: + embedF = F.hom(F_ext.gen(), embedF_base) + else: + embedF = embedF_base + + self._embedF = embedF + self._O_ext = F_ext.maximal_order() + self._Oinf_ext = F_ext.maximal_order_infinite() + + Map.__init__(self, Hom(base_group, extension_group, CommutativeAdditiveGroups())) + + def _repr_type(self): + """ + Return string representation of ``self``. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) # indirect doctest + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Hess model) + """ + return 'Jacobian group embedding' + + def _call_(self, x): + """ + Conorm map from F to F_ext. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k, 2) + sage: C = Curve(y^5 - x^3-2*x - 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: m = G3.coerce_map_from(G1) + sage: m(G1.zero()) == G3.zero() + True + """ + embedF = self._embedF + O_ext = self._O_ext + Oinf_ext = self._Oinf_ext + + idS,ids = x._data + dS = O_ext.ideal([embedF(g) for g in idS.gens()]) + ds = Oinf_ext.ideal([embedF(g) for g in ids.gens()]) + return self.codomain().element_class(self.codomain(), dS, ds) + + +class JacobianGroup(UniqueRepresentation, JacobianGroup_base): + """ + Groups of rational points of a Jacobian. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + """ + Element = JacobianPoint + _embedding_map_class = JacobianGroupEmbedding + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements', '_test_pickling']) + """ + super().__init__(parent, function_field, base_div) + + bdS, bds = self._get_dS_ds(-base_div) + try: + bdS._gens_two() # speed up multiplication with these ideals + bds._ideal._gens_two() # by storing vector forms of two generators + except AttributeError: + pass + self._base_point = (bdS, bds) + self._base_point_double = (bdS * bdS, bds * bds) + + self._base_place = None + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + """ + r = super()._repr_() + return r + ' (Hess model)' + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + If ``x`` is an effective divisor, then it is assumed to of + degree `g`, the genus of the function field. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: G(0) + [Place (1/y, 1/y*z)] + """ + if x == 0: + return self.zero() + + if isinstance(x, FunctionFieldPlace): + if (self._base_place is not None + and x in self._function_field.place_set() + and x.degree() == 1): + x = x - self._base_place + else: + x = x.divisor() + + if (isinstance(x, FunctionFieldDivisor) + and x in self._function_field.divisor_group()): + if x.degree() == 0: + return self.point(x) + if x.is_effective(): + return self.element_class(self, *self._get_dS_ds(x)) + + raise ValueError(f"Cannot construct a point from {x}") + + def _get_dS_ds(self, divisor): + """ + Return (dS,ds) representation of the divisor. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: pl = C([2,8,1]).place() + sage: dS, ds = G._get_dS_ds(2*pl) + sage: (~dS).divisor() + (~ds).divisor() == 2*pl + True + """ + F = self._function_field + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + I = O.ideal(1) + J = Oinf.ideal(1) + for p in divisor._data: + m = divisor._data[p] + if p.is_infinite_place(): + J *= p.prime_ideal() ** (-m) + else: + I *= p.prime_ideal() ** (-m) + + return I, J + + def _normalize(self, I, J): + """ + Return a pair of normalized ideals from `I` and `J`. + + INPUT: + + - ``I`` -- an ideal of the finite maximal order + + - ``J`` -- an ideal of the infinite maximal order + + The output represents an effective divisor linearly equivalent to the + divisor represented by the given ideals `I` and `J`. + + ALGORITHM: + + Computes a function `f` in the Riemann-Roch space of the divisor `D` + represented by the (inverted) ideals `I` and `J`. The output is the + pair of the (inverted) ideals representing the effective divisor `(f) + D`, + which is linearly equivalent to `D`. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: pl = C([2,8,1]).place() + sage: p = G.point(pl - b) + sage: dS, ds = (p + p)._data # indirect doctest + sage: G.point((~dS).divisor() + (~ds).divisor() - b) == p + p + True + """ + F = self._function_field + n = F.degree() + + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + # Step 1: construct matrix M of rational functions in x such that + # M * B == C where B = [b1,b1,...,bn], C =[v1,v2,...,vn] + V,fr,to = F.free_module(map=True) + B = matrix([to(b) for b in J.gens_over_base()]) + C = matrix([to(v) for v in I.gens_over_base()]) + M = C * B.inverse() + + # Step 2: get the denominator d of M and set mat = d * M + den = lcm([e.denominator() for e in M.list()]) + R = den.parent() # polynomial ring + one = R.one() + mat = matrix(R, n, [e.numerator() for e in (den*M).list()]) + gens = list(I.gens_over_base()) + + # Step 3: transform mat to a weak Popov form, together with gens + + # initialise pivot_row and conflicts list + found = None + pivot_row = [[] for i in range(n)] + conflicts = [] + for i in range(n): + bestp = -1 + best = -1 + for c in range(n): + d = mat[i,c].degree() + if d >= best: + bestp = c + best = d + + if best <= den.degree(): + found = i + break + + if best >= 0: + pivot_row[bestp].append((i,best)) + if len(pivot_row[bestp]) > 1: + conflicts.append(bestp) + + if found is None: + # while there is a conflict, do a simple transformation + while conflicts: + c = conflicts.pop() + row = pivot_row[c] + i,ideg = row.pop() + j,jdeg = row.pop() + + if jdeg > ideg: + i,j = j,i + ideg,jdeg = jdeg,ideg + + coeff = - mat[i,c].lc() / mat[j,c].lc() + s = coeff * one.shift(ideg - jdeg) + + mat.add_multiple_of_row(i, j, s) + gens[i] += s * gens[j] + + row.append((j,jdeg)) + + bestp = -1 + best = -1 + for c in range(n): + d = mat[i,c].degree() + if d >= best: + bestp = c + best = d + + if best <= den.degree(): + found = i + break + + if best >= 0: + pivot_row[bestp].append((i,best)) + if len(pivot_row[bestp]) > 1: + conflicts.append(bestp) + else: + return None + + f = gens[found] + return (O.ideal(~f) * I, Oinf.ideal(~f) * J) + + def point(self, divisor): + """ + Return the point represented by the divisor of degree zero. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: p = C([-1,2,1]).place() + sage: G.point(p - b) + [Place (y + 2, z + 1)] + """ + c = divisor + self._base_div + f = c.basis_function_space()[0] + d = f.divisor() + c + dS, ds = self._get_dS_ds(d) + return self.element_class(self, dS, ds) + + @cached_method + def zero(self): + """ + Return the zero element of this group. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G.zero() + [Place (1/y, 1/y*z)] + """ + bdS,bds = self._base_point + return self.element_class(self, ~bdS, ~bds) + + +class JacobianGroup_finite_field(JacobianGroup, JacobianGroup_finite_field_base): + """ + Jacobian groups of function fields over finite fields + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Hess model) + """ + Element = JacobianPoint_finite_field + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements','_test_pickling']) + """ + super().__init__(parent, function_field, base_div) + + F = self._function_field + K = F.constant_base_field() + + R = F.base_field() # base rational function field + x = R.gen() + y = F.gen() + + r = self._parent._function_field.constant_base_field().degree() + frob_K = K.frobenius_endomorphism(r) + frob_R = R.hom(x, base_morphism=frob_K) + frob_F = F.hom(y, base_morphism=frob_R) + + self._frobenius = frob_F + + def __iter__(self): + """ + Return generator of points of this group. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: len([pt for pt in G]) + 11 + """ + g = self._parent._function_field.genus() + F = self._function_field + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + deg = 1 + support = [] + degrees = [] + multiples = [] + lst = [] + + places_infinite = F.places_infinite() + generators = [iter(places_infinite)] + num_of_infinite_places = len(places_infinite) + while True: + while True: + try: + new_pl = next(generators[-1]) + break + except StopIteration: + if deg > g: + return + generators.append(F._places_finite(deg)) + deg += 1 + multiples.append((g + 1)*[None]) + P = ~new_pl.prime_ideal() + dn = new_pl.degree() + I0 = O.ideal(1) + J0 = Oinf.ideal(1) + dr = 0 + for r in range(1, g // new_pl.degree() + 1): + if new_pl.is_infinite_place(): + J0 = J0 * P + else: + I0 = I0 * P + multiples[-1][r] = (I0, J0) + dr = dr + dn + for weights in WeightedIntegerVectors(g - dr, degrees): + I = I0 + J = J0 + for i in range(len(support)): + w = weights[i] + if w > 0: + dS, ds = multiples[i][w] + if i < num_of_infinite_places: + J *= ds # dS is the unit ideal + else: + I *= dS # ds is the unit ideal + pt = self.element_class(self, I, J) + if pt not in lst: + lst.append(pt) + yield pt + support.append(new_pl) + degrees.append(new_pl.degree()) + + def _frobenius_on(self, pt): + """ + Return the image of ``pt`` acted by the Frobenius automorphism. + + EXAMPLES:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G1 = J.group() + sage: G1.order() + 11 + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: pts1 = G1.get_points(11) + sage: pts3 = G3.get_points(12) + sage: pt = next(pt for pt in pts3 if pt not in pts1) + sage: pt.frobenius().frobenius().frobenius() == pt # indirect doctest + True + sage: pt.frobenius() == pt + False + """ + frob_F = self._frobenius + + F = self._function_field + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + idS,ids = pt._data + dS = O.ideal([frob_F(g) for g in idS.gens()]) + ds = Oinf.ideal([frob_F(g) for g in ids.gens()]) + return self.element_class(self, dS, ds) + + +class Jacobian(Jacobian_base, UniqueRepresentation): + """ + Jacobians of function fields. + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: C.jacobian(model='hess', base_div=b) + Jacobian of Projective Plane Curve over Finite Field of size 17 + defined by x^3 - y^2*z + 5*z^3 (Hess model) + """ + def __init__(self, function_field, base_div, **kwds): + """ + Initialize. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: TestSuite(J).run(skip=['_test_elements','_test_pickling']) + """ + super().__init__(function_field, base_div, **kwds) + + if function_field.constant_base_field().is_finite(): + self._group_class = JacobianGroup_finite_field + else: + self._group_class = JacobianGroup + + def _repr_(self): + """ + Return the string representation of ``self``. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: C.jacobian(model='hess', base_div=b) + Jacobian of Projective Plane Curve over Finite Field of size 17 + defined by x^3 - y^2*z + 5*z^3 (Hess model) + """ + r = super()._repr_() + return r + ' (Hess model)' diff --git a/src/sage/rings/function_field/jacobian_khuri_makdisi.py b/src/sage/rings/function_field/jacobian_khuri_makdisi.py new file mode 100644 index 00000000000..cfddcb8fe13 --- /dev/null +++ b/src/sage/rings/function_field/jacobian_khuri_makdisi.py @@ -0,0 +1,1029 @@ +r""" +Jacobians in Khuri-Makdisi model + +This module implements Jacobian arithmetic by Khuri-Makdisi's algorithms +[Khu2004]_ based on divisor representation by linear spaces. + +Jacobian +-------- + +There are three models for Jacobian arithmetic by Khuri-Makdisi's algorithms. +For each of the models, one should provide a base divisor satisfying certain +degree condition. The following lists the names of the three models and the +corresponding conditions on base divisors. Let `g` be the genus of the function +field. + +- ``km_large``: large model; requires an effective divisor of degree at least `2g + 1` + +- ``km_medium``: medium model; requires an effective divisor of degree at least `2g + 1` + +- ``km_small``: small model; requires an effective divisor of degree at least `g + 1` + +To create a Jacobian in this model, specify ``'km_[large|medium|small]'`` as ``model`` and +provide a base divisor satisfying the degree condition. + +EXAMPLES: + +We construct a function field (of a projective curve) over a finite field:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: C.geometric_genus() + 1 + sage: H = C.function(y/x).divisor_of_poles() + sage: H.degree() + 3 + +Now we use `H` as base divisor for the large and medium models:: + + sage: J_large = C.jacobian(model='km_large', base_div=H) + sage: J_large + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi large model) + sage: J_medium = C.jacobian(model='km_medium', base_div=H) + sage: J_medium + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi medium model) + +and for the small model, we construct an effective divisor of degree 2:: + + sage: B = sum(H.support()[:2]) + sage: B.degree() + 2 + sage: J_small = C.jacobian(model='km_small', base_div=B) + sage: J_small + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi small model) + +Group of rational points +------------------------ + +The group of rational points of a Jacobian is created from the Jacobian. A +point of the Jacobian group is represented by a divisor `D - B` where `D` is +an effective divisor of the same degree with the base divisor `B`. The +divisor `D` in turn is determined by a linear subspace of the Riemann-Roch +space associated with certain multiple of `B` (depending on the model). This +allows representing points of Jacobian as matrices once we fix a basis of the +Riemann-Roch space. + + +EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: H = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=H) + sage: G = J.group() + sage: D = C([0,1,0]).place() + sage: P1 = C([-1,2,1]).place() + sage: P2 = C([3,7,1]).place() + sage: p1 = G.point(P1 - D) + sage: p2 = G.point(P2 - D) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 15] + [ 0 1 0 0 0 0 0 0 13] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 0 1 0 0 15] + [ 0 0 0 0 0 0 1 0 1] + sage: p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 5] + [ 0 1 0 0 0 0 0 0 2] + [ 0 0 1 0 0 0 0 0 13] + [ 0 0 0 1 0 0 0 0 8] + [ 0 0 0 0 0 1 0 0 10] + [ 0 0 0 0 0 0 1 0 14] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 0 16 0 5 3] + [ 0 1 0 0 0 6 0 8 16] + [ 0 0 1 0 0 15 0 3 10] + [ 0 0 0 1 0 3 0 0 0] + [ 0 0 0 0 1 12 0 16 8] + [ 0 0 0 0 0 0 1 3 0] + +AUTHORS: + +- Kwankyu Lee (2022-01-24): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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.misc.cachefunc import cached_method + +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.richcmp import op_EQ, richcmp + +from sage.categories.map import Map +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups +from sage.categories.homset import Hom + +from sage.matrix.constructor import matrix + +from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + +from .place import FunctionFieldPlace +from .divisor import FunctionFieldDivisor + +from .jacobian_base import (Jacobian_base, + JacobianGroup_base, + JacobianGroup_finite_field_base, + JacobianPoint_base, + JacobianPoint_finite_field_base) + + +class JacobianPoint(JacobianPoint_base): + """ + Points of a Jacobian group. + + INPUT: + + - `parent` -- Jacobian group + + - `w` -- matrix + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: pl = C([1,8,1]).place() + sage: G.point(pl - b) + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 24 21] + [ 0 1 0 0 0 0 0 0 23] + [ 0 0 1 0 0 0 0 0 21] + [ 0 0 0 1 0 0 0 0 28] + [ 0 0 0 0 0 1 0 0 21] + [ 0 0 0 0 0 0 1 0 28] + + """ + def __init__(self, parent, w): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: pl = C([1,8,1]).place() + sage: p = G.point(pl - b) + sage: TestSuite(p).run(skip=['_test_category','_test_pickling']) + """ + super().__init__(parent) + w.set_immutable() + self._w = w + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G.zero() + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + return f'Point of Jacobian determined by \n{self._w}' + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: zero = G.zero() + sage: {zero: 1} + {Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0]: 1} + """ + return hash(self._w) + + def _richcmp_(self, other, op): + """ + Compare ``self`` with ``other`` with respect to operator ``op``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 == p1 + True + sage: p1 != p2 + True + sage: p1 > p1 + False + sage: p1 > p2 + True + sage: p1 < p2 + False + """ + if op is op_EQ: + km = self.parent()._km + return km.equal(self._w, other._w) + else: + return richcmp(self._w, other._w, op) + + def _add_(self, other): + """ + Return the sum of ``self`` and ``other``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 19 28 25] + [ 0 1 0 0 0 0 25 23 6] + [ 0 0 1 0 0 0 0 5 15] + [ 0 0 0 1 0 0 23 23 2] + [ 0 0 0 0 1 0 0 7 2] + [ 0 0 0 0 0 1 5 5 24] + sage: p1 + p2 == p2 + p1 + True + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.add(self._w, other._w)) + + def _neg_(self): + """ + Return the negative of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = F.get_place(1) + sage: p = C([-1,2,1]).place() + sage: pt = G.point(p - b) + sage: -pt + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 25 0] + [ 0 1 0 0 0 0 20 10 5] + [ 0 0 1 0 0 0 10 6 17] + [ 0 0 0 1 0 0 16 23 13] + [ 0 0 0 0 1 0 0 27 0] + [ 0 0 0 0 0 1 10 5 17] + sage: -(-pt) == pt + True + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.negate(self._w)) + + def _rmul_(self, n): + """ + Return the ``n``-th multiple of this point. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: 10*(10*p) == 100*p + True + """ + return self.multiple(n) + + def multiple(self, n): + """ + Return the ``n``-th multiple of this point. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: p.multiple(100) + Point of Jacobian determined by + [ 1 0 0 0 0 0 26 24 24] + [ 0 1 0 0 0 0 6 0 11] + [ 0 0 1 0 0 0 5 0 4] + [ 0 0 0 1 0 0 13 0 1] + [ 0 0 0 0 1 0 23 18 1] + [ 0 0 0 0 0 1 25 0 11] + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.multiple(self._w, n)) + + def addflip(self, other): + """ + Return the addflip of this and ``other`` point. + + The addflip of two points is by definition the negative of the sum of + the points. This operation is faster than addition in Jacobian + arithmetic by Khuri-Makdisi algorithms. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,19,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1.addflip(p2) + Point of Jacobian determined by + [ 1 0 0 0 0 0 26 15 6] + [ 0 1 0 0 0 0 6 13 28] + [ 0 0 1 0 0 0 6 0 8] + [ 0 0 0 1 0 0 26 16 28] + [ 0 0 0 0 1 0 23 28 25] + [ 0 0 0 0 0 1 28 4 5] + sage: _ == -(p1 + p2) + True + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.addflip(self._w, other._w)) + + def defining_matrix(self): + """ + Return the matrix whose row span determines the effective divisor + representing this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.defining_matrix() + [ 1 0 0 0 0 0 0 24 27] + [ 0 1 0 0 0 0 0 0 25] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 28] + [ 0 0 0 0 0 1 0 0 27] + [ 0 0 0 0 0 0 1 0 1] + """ + return self._w + + def divisor(self): + """ + Return the divisor representing this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = F.get_place(1) + sage: p = C([-1,2,1]).place() + sage: pt = G.point(p - b) + sage: G.point(pt.divisor()) == pt + True + + ALGORITHM: Lemma 2.1 of [Khu2004]_. + """ + G = self.parent() + F = G._function_field + data = [G._from_L(f).divisor() for f in self._w.rows()] + supp = set() + for d in data: + supp.update(d.support()) + supp = list(supp) + d = F.divisor_group().zero() + for p in supp: + d += min(d.valuation(p) for d in data) * p.divisor() + return d + G._div_L - G._base_div + + +class JacobianPoint_finite_field(JacobianPoint, JacobianPoint_finite_field_base): + pass + + +class JacobianGroupEmbedding(Map): + """ + Embeddings between Jacobian groups. + + INPUT: + + - ``base_group`` -- Jacobian group over a base field + + - ``extension_group`` -- Jacobian group over an extension field + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Khuri-Makdisi large model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Khuri-Makdisi large model) + """ + def __init__(self, base_group, extension_group): + """ + Initialize. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: map = G3.coerce_map_from(G1) + sage: TestSuite(map).run(skip=['_test_category', '_test_pickling']) + """ + F_ext = extension_group._function_field + K_ext = F_ext.constant_base_field() + + self._K_ext = K_ext + + Map.__init__(self, Hom(base_group, extension_group, CommutativeAdditiveGroups())) + + def _repr_type(self): + """ + Return string representation of ``self``. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) # indirect doctest + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Khuri-Makdisi large model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Khuri-Makdisi large model) + """ + return 'Jacobian group embedding' + + def _call_(self, x): + """ + Conorm map from F to F_ext. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: m = G3.coerce_map_from(G1) + sage: m(G1.zero()) == G3.zero() + True + """ + w = (x._w).change_ring(self._K_ext) + + return self.codomain().element_class(self.codomain(), w) + + +class JacobianGroup(UniqueRepresentation, JacobianGroup_base): + """ + Groups of rational points of a Jacobian. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 29 (Khuri-Makdisi large model) + """ + Element = JacobianPoint + _embedding_map_class = JacobianGroupEmbedding + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + """ + super().__init__(parent, function_field, base_div) + + D0 = base_div + + self._V_cache = 10*[None] + + V_cache = self._V_cache + + def V(n): + if n in V_cache: + return V_cache[n] + + Vn, from_Vn, to_Vn = (n * D0).function_space() + V_cache[n] = (Vn, from_Vn, to_Vn) + + return Vn, from_Vn, to_Vn + + def mu(n, m, i, j): + Vn, from_Vn, to_Vn = V(n) + Vm, from_Vm, to_Vm = V(m) + Vnm, from_Vnm, to_Vnm = V(n + m) + return to_Vnm(from_Vn(Vn.gen(i)) * from_Vm(Vm.gen(j))) + + model = parent._model + + if model == 'large': + div_L = 3 * D0 + L, from_L, to_L = V(3) + from sage.rings.function_field.khuri_makdisi import KhuriMakdisi_large as KM + elif model == 'medium': + div_L = 2 * D0 + L, from_L, to_L = V(2) + from sage.rings.function_field.khuri_makdisi import KhuriMakdisi_medium as KM + elif model == 'small': + div_L = 3 * D0 + L, from_L, to_L = V(3) + from sage.rings.function_field.khuri_makdisi import KhuriMakdisi_small as KM + + self._div_L = div_L + self._L = L + self._from_L = from_L + self._to_L = to_L + + w0 = self.point(function_field.divisor_group().zero()).defining_matrix() + km = KM(lambda n: V(n)[0], mu, w0, D0.degree(), self._genus) + + self._km = km + self._w0 = w0 + + self._base_place = None + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 17 (Khuri-Makdisi large model) + """ + r = super()._repr_() + return r + f' (Khuri-Makdisi {self._parent._model} model)' + + def _wd_from_divisor(self, x): + """ + Return the matrix representing the divisor ``x``. + + INPUT: + + - ``x`` -- an effective divisor + + TESTS: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: P = C([-1,2,1]).place() + sage: G._wd_from_divisor(2*P) + [1 0 0 0 4 0] + [0 1 0 0 4 6] + [0 0 1 0 2 1] + [0 0 0 1 1 6] + """ + WD = (self._div_L - x).basis_function_space() + wd = matrix([self._to_L(f) for f in WD]) + wd.echelonize() + return wd + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + If ``x`` is an effective divisor, then it is assumed to have the same + degree with the base divisor. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G(0) + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + sage: b = C([0,1,0]).place() + sage: J.set_base_place(b) + sage: p = C([-1,2,1]).place() + sage: G(p) + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 15] + [ 0 1 0 0 0 0 0 0 13] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 0 1 0 0 15] + [ 0 0 0 0 0 0 1 0 1] + """ + if x == 0: + return self.zero() + + if isinstance(x, FunctionFieldPlace): + if (self._base_place is not None + and x in self._function_field.place_set() + and x.degree() == 1): + x = x - self._base_place + else: + x = x.divisor() + + if (isinstance(x, FunctionFieldDivisor) + and x in self._function_field.divisor_group()): + if x.degree() == 0: + return self.point(x) + if x.is_effective(): + wd = self._wd_from_divisor(x) + return self.element_class(self, wd) + + raise ValueError(f"Cannot construct a point from {x}") + + def point(self, divisor): + """ + Return the point represented by the divisor. + + INPUT: + + - ``divisor`` -- a divisor of degree zero + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: p = C([-1,2,1]).place() + sage: G.point(p - b) + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 15] + [ 0 1 0 0 0 0 0 0 13] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 0 1 0 0 15] + [ 0 0 0 0 0 0 1 0 1] + """ + if divisor.degree() != 0: + raise ValueError('divisor not of degree zero') + + c = divisor + self._base_div + f = c.basis_function_space()[0] + d = f.divisor() + c + + wd = self._wd_from_divisor(d) + return self.element_class(self, wd) + + @cached_method + def zero(self): + """ + Return the zero element of this group. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G.zero() + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + return self.element_class(self, self._w0) + + +class JacobianGroup_finite_field(JacobianGroup, JacobianGroup_finite_field_base): + """ + Jacobian groups of function fields over finite fields. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(2) + sage: G2 = J.group(K) + sage: G2.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Khuri-Makdisi large model) + To: Group of rational points of Jacobian + over Finite Field in z2 of size 17^2 (Khuri-Makdisi large model) + """ + Element = JacobianPoint_finite_field + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements', '_test_pickling']) + """ + super().__init__(parent, function_field, base_div) + + F = self._function_field + K = F.constant_base_field() + + r = self._parent._function_field.constant_base_field().degree() + frob_K = K.frobenius_endomorphism(r) + + self._frobenius_of_constant_field = frob_K + + def __iter__(self): + """ + Return generator of points of this group. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: b = C([0,0,1]).place() + sage: J = C.jacobian(model='km_large', base_div=3*b) + sage: G = J.group() + sage: len([pt for pt in G]) + 11 + """ + d0 = self._base_div.degree() + F = self._function_field + + zero_divisor = self._km.zero_divisor() + deg = 1 + support = [] + degrees = [] + multiples = [] + lst = [] + + places_infinite = F.places_infinite() + generators = [iter(places_infinite)] + while True: + while True: + try: + new_pl = next(generators[-1]) + break + except StopIteration: + if deg > d0: + return + generators.append(F._places_finite(deg)) + deg += 1 + multiples.append((d0 + 1)*[None]) + wn = self._wd_from_divisor(new_pl.divisor()) + dn = new_pl.degree() + wr = zero_divisor + dr = 0 + for r in range(1, d0 // dn + 1): + wr = self._km.add_divisor(wr, wn, dr, dn) + multiples[-1][r] = wr + dr += dn + for weights in WeightedIntegerVectors(d0 - dr, degrees): + d = dr + wD = wr + for i in range(len(support)): + w = weights[i] + if w > 0: + m = w * degrees[i] + wD = self._km.add_divisor(wD, multiples[i][w], d, m) + d += m + pt = self.element_class(self, wD) + if pt not in lst: + lst.append(pt) + yield pt + support.append(new_pl) + degrees.append(new_pl.degree()) + + def _frobenius_on(self, pt): + """ + Return the image of ``pt`` acted by the Frobenius automorphism. + + INPUT: + + - ``pt`` -- a point of ``self`` + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: b = C([0,0,1]).place() + sage: J = C.jacobian(model='km_large', base_div=3*b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: pt = G3.get_points(12)[-2] # expected to be a point rational over K + sage: pt.frobenius().frobenius().frobenius() == pt # indirect doctest + True + """ + w = pt._w.apply_morphism(self._frobenius_of_constant_field) + return self.element_class(self, w) + + +class Jacobian(UniqueRepresentation, Jacobian_base): + """ + Jacobians implemented by Khuri-Makdisi's algorithms. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: C.jacobian(model='km') + Jacobian of Projective Plane Curve over Finite Field of size 17 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi large model) + """ + def __init__(self, function_field, base_div, model, **kwds): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='km_large') + sage: TestSuite(J).run(skip=['_test_elements', '_test_pickling']) + + :: + + sage: J = C.jacobian(model='km_unknown') + Traceback (most recent call last): + ... + ValueError: unknown model + """ + super().__init__(function_field, base_div, **kwds) + + if model not in ['large', 'medium', 'small']: + raise ValueError('unknown model') + + self._model = model + + if function_field.constant_base_field().is_finite(): + self._group_class = JacobianGroup_finite_field + else: + self._group_class = JacobianGroup + + def _repr_(self): + """ + Return the string representation of ``self``. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: C.jacobian(model='km_large') + Jacobian of Projective Plane Curve over Finite Field of size 17 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi large model) + """ + r = super()._repr_() + return r + f' (Khuri-Makdisi {self._model} model)' diff --git a/src/sage/rings/function_field/khuri_makdisi.pyx b/src/sage/rings/function_field/khuri_makdisi.pyx new file mode 100644 index 00000000000..8f9802e9622 --- /dev/null +++ b/src/sage/rings/function_field/khuri_makdisi.pyx @@ -0,0 +1,820 @@ +r""" +Khuri-Makdisi algorithms for arithmetic in Jacobians + +This module implements Khuri-Makdisi's algorithms of [Khu2004]_. + +In the implementation, we use notations close to the ones used by +Khuri-Makdisi. We describe them below for readers of the code. + +Let `D_0` be the base divisor of the Jacobian in Khuri-Makdisi model. So `D_0` +is an effective divisor of appropriate degree `d_0` depending on the model. Let +`g` be the genus of the underlying function field. For large and medium models, +`d_0\ge 2g+1`. For small model `d_0\ge g+1`. A point of the Jacobian is a +divisor class containing a divisor `D - D_0` of degree `0` with an effective +divisor `D` of degree `d_0`. + +Let `V_n` denote the vector space `H^0(O(nD_0))` with a chosen +basis, and let `\mu_{n,m}` is a bilinear map from `V_n\times V_m\to V_{n+m}` +defined by `(f,g)\mapsto fg`. The map `\mu_{n,m}` can be represented by a +3-dimensional array as depicted below:: + + f + *------* + d /|e /| + *-|----* | + | *----|-* + |/ |/ + *------* + +where `d=\dim V_n`, `e=\dim V_m`, `f=\dim V_{n+m}`. In the implementation, we +instead use a matrix of size `d\times ef`. Each row of the matrix denotes a +matrix of size `e\times f`. + +A point of the Jacobian is represented by an effective divisor `D`. In +Khuri-Makdisi algorithms, the divisor `D` is represented by a subspace `W_D = +H^0(O(n_0D_0 - D))` of `V_{n_0}` with fixed `n_0` depending on the model. For +large and small models, `n_0=3` and `L = O(3D_0)`, and for medium model, +`n_0=2` and `L = O(2D_0)`. + +The subspace `W_D` is the row space of a matrix `w_D`. Thus in the +implementation, the matrix `w_D` represents a point of the Jacobian. The row +space of the matrix `w_L` is `V_{n_0}=H^0(O(n_0D_0))`. + +The function ``mu_image(w_D, w_E, mu_mat_n_m, expected_dim)`` computes the image +`\mu_{n,m}(W_D,W_E)` of the expected dimension. + +The function ``mu_preimage(w_E, w_F, mu_mat_n_m, expected_codim)`` computes the +preimage `W_D` such that `\mu_{n,m}(W_D,W_E)=W_F` of the expected codimension +`\dim V_n - \dim W_D`, which is a multiple of `d_0`. + +AUTHORS: + +- Kwankyu Lee (2022-01): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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.matrix.constructor import matrix +from sage.matrix.matrix cimport Matrix +from sage.modules.free_module_element cimport FreeModuleElement +from sage.rings.integer import Integer + + +cdef inline list listcat(list l): + flat_list = [] + for sublist in l: + flat_list.extend(sublist) + return flat_list + + +cdef class KhuriMakdisi_base(object): + cdef Matrix wL + cdef Matrix w0 + cdef int d0, g + + cdef Matrix mu_image(self, Matrix wd, Matrix we, Matrix mu_mat, int expected_dim=0): + """ + Lemma 2.2. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_large') + sage: G = J.group() + sage: b = C([0,0,1]).place() + sage: pl1 = C([1,2,1]).place() + sage: pl2 = C([3,1,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: w1 = p1._w + sage: w2 = p2._w + sage: w1 + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 1 0 0 1 0 2] + [0 0 0 0 1 0 6 0 6] + [0 0 0 0 0 1 4 0 1] + [0 0 0 0 0 0 0 1 0] + sage: w2 + [0 1 0 0 0 0 0 2 2] + [0 0 1 0 0 0 0 4 6] + [0 0 0 1 0 0 0 5 6] + [0 0 0 0 1 0 0 3 1] + [0 0 0 0 0 1 0 3 5] + [0 0 0 0 0 0 1 6 3] + sage: (p1 + p2)._w # indirect doctest + [1 0 0 0 0 0 2 2 4] + [0 1 0 0 0 0 3 5 0] + [0 0 1 0 0 0 0 3 6] + [0 0 0 1 0 0 1 1 3] + [0 0 0 0 1 0 5 6 3] + [0 0 0 0 0 1 5 5 3] + """ + cdef Matrix mat + cdef FreeModuleElement v + cdef Py_ssize_t n, c, r + + n = we.ncols() + c = mu_mat.ncols() // n + mat = matrix(0, c) + for v in wd: + mat = mat.stack(we * matrix(n, v * mu_mat)) + mat.echelonize() + r = mat.rank() + mat = mat.matrix_from_rows(range(r)) + if expected_dim and r == expected_dim: + break + + assert not expected_dim or r == expected_dim + + return mat + + cdef Matrix mu_preimage(self, Matrix we, Matrix wde, Matrix mu_mat, int expected_codim=0): + """ + Lemma 2.3 (division). + + This computes + + {s: mu(s*E) subset F} = {s: s*M*E*F_perp^ == 0} + = {s: s*M*v*F_perp^ == 0 for v in E} + = {s: F_perp*(v*M^)*s == 0 for v in E} + + for E = we, F = wde, M^ = mu_mat_reversed + """ + cdef Matrix mat, perp, vmu + cdef FreeModuleElement v + cdef Py_ssize_t nd, ne, nde, r + + ne = we.ncols() + nde = wde.ncols() + nd = nde - ne + + perp = wde.right_kernel_matrix() + mat = matrix(0, mu_mat.nrows()) + for v in we: + vmu = matrix([v * matrix(ne, row) for row in mu_mat]) + mat = mat.stack(perp * vmu.transpose()) + mat.echelonize() + r = mat.rank() + mat = mat.matrix_from_rows(range(r)) + if expected_codim and r == expected_codim: + break + + assert not expected_codim or r == expected_codim + + return mat.right_kernel_matrix() + + cpdef Matrix negate(self, Matrix wd): + """ + Theorem 4.4 (negation), first method. + """ + return self.addflip(wd, self.w0) + + cpdef Matrix add(self, Matrix wd1, Matrix wd2): + """ + Theorem 4.5 (addition). + """ + return self.negate(self.addflip(wd1, wd2)) + + cpdef Matrix subtract(self, Matrix wd1, Matrix wd2): + """ + Theorem 4.6 (subtraction), first method. + """ + return self.addflip(self.negate(wd1), wd2) + + cpdef Matrix multiple(self, Matrix wd, n): + """ + Compute multiple by additive square-and-multiply method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: 10*p + Point of Jacobian determined by + [ 1 0 0 0 0 0 5 2 2] + [ 0 1 0 0 0 0 13 6 11] + [ 0 0 1 0 0 0 1 11 4] + [ 0 0 0 1 0 0 1 13 7] + [ 0 0 0 0 1 0 12 16 2] + [ 0 0 0 0 0 1 6 9 10] + sage: (-10)*p + Point of Jacobian determined by + [ 1 0 0 0 0 13 0 10 6] + [ 0 1 0 0 0 5 0 4 16] + [ 0 0 1 0 0 2 0 0 4] + [ 0 0 0 1 0 9 0 6 9] + [ 0 0 0 0 1 6 0 0 9] + [ 0 0 0 0 0 0 1 9 5] + sage: 0*p + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + cdef Matrix w + cdef int sign, b + cdef list bits + + if n == 0: + return self.w0 + if n < 0: + bits = Integer(-n).digits(2) + else: + bits = Integer(n).digits(2) + bits.pop() + mwd = None + w = wd + sign = 1 + for i in range(len(bits)): + w = self.addflip(w, w) + sign = -sign + b = bits.pop() + if b > 0: + if sign < 0: + if mwd is None: + mwd = self.addflip(wd, self.w0) + w = self.addflip(w, mwd) + else: + w = self.addflip(w, wd) + sign = -sign + if sign < 0 and n > 0 or sign > 0 and n < 0: + w = self.addflip(w, self.w0) # negate + return w + + cpdef Matrix zero_divisor(self): + """ + Return the matrix `w_L` representing zero divisor. + """ + return self.wL + + +cdef class KhuriMakdisi_large(KhuriMakdisi_base): + r""" + Khuri-Makdisi's large model. + + INPUT: + + - ``V`` -- ``V(n)`` returns the vector space `V_n` + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 15] + [ 0 1 0 0 0 0 0 0 13] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 0 1 0 0 15] + [ 0 0 0 0 0 0 1 0 1] + sage: p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 5] + [ 0 1 0 0 0 0 0 0 2] + [ 0 0 1 0 0 0 0 0 13] + [ 0 0 0 1 0 0 0 0 8] + [ 0 0 0 0 0 1 0 0 10] + [ 0 0 0 0 0 0 1 0 14] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 0 16 0 5 3] + [ 0 1 0 0 0 6 0 8 16] + [ 0 0 1 0 0 15 0 3 10] + [ 0 0 0 1 0 3 0 0 0] + [ 0 0 0 0 1 12 0 16 8] + [ 0 0 0 0 0 0 1 3 0] + sage: p1 - p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 13 9 5] + [ 0 1 0 0 0 0 2 5 8] + [ 0 0 1 0 0 0 6 7 5] + [ 0 0 0 1 0 0 11 3 16] + [ 0 0 0 0 1 0 9 7 10] + [ 0 0 0 0 0 1 4 10 5] + sage: p1.addflip(p2) == -(p1 + p2) + True + """ + cdef Matrix mu_mat33 + + def __init__(self, V, mu, w0, d0, g): + """ + Initialize. + """ + self.wL = V(3).basis_matrix() + self.w0 = w0 + self.d0 = d0 + self.g = g + self.mu_mat33 = matrix(listcat([list(mu(3, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(3*d0-g+1)) + + def equal(self, wd, we): + """ + Theorem 4.1, second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: J = C.jacobian(model='km_large') + sage: b = C([0,1,0]).place() + sage: J.set_base_place(b) + sage: G = J.group() + sage: pl1 = C([3,2,1]).place() + sage: pl2 = C([5,5,1]).place() + sage: p1 = G(pl1) + sage: p2 = G(pl2) + sage: p1 + p2 == p2 + p1 + True + sage: p1 - p2 == -(p2 - p1) + True + sage: zero = G.zero() + sage: p1 + zero == p1 + True + sage: p1 - p1 == zero + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix wf, w1, w2 + + wf = matrix(wd[0]) + w1 = self.mu_image(wf, we, self.mu_mat33, 2*d0 - g + 1) + w2 = self.mu_preimage(wd, w1, self.mu_mat33) + return w2.rank() > 0 + + cdef Matrix _add(self, Matrix wd, Matrix we): + """ + Theorem 3.6 (addition of divisors, first method). + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd, we, self.mu_mat33, 4*d0 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat33, 2*d0) + return w2 + + cdef Matrix _flip(self, Matrix wd): + """ + Theorem 3.10 (flipping) + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + # efficient than + # wf = matrix(wd[0]) + # w1 = self.mu_image(wf, self.wL, mu_mat, 3*d0 - g + 1) + w1 = matrix(3*d0 - g + 1, wd[0] * self.mu_mat33) + w2 = self.mu_preimage(wd, w1, self.mu_mat33, d0) + return w2 + + cpdef Matrix addflip(self, Matrix wd1, Matrix wd2): + """ + Theorem 4.3 (addflip) + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1.addflip(p2) + Point of Jacobian determined by + [ 1 0 0 0 0 0 7 10 9] + [ 0 1 0 0 0 0 4 14 10] + [ 0 0 1 0 0 0 7 0 9] + [ 0 0 0 1 0 0 10 10 6] + [ 0 0 0 0 1 0 6 5 15] + [ 0 0 0 0 0 1 14 9 1] + """ + return self._flip(self._add(wd1, wd2)) + + cpdef Matrix add_divisor(self, Matrix wd1, Matrix wd2, int d1, int d2): + """ + Theorem 3.6 (addition of divisors, first method). + + We assume that `w_{D_1}`, `w_{D_2}` represent divisors of degree at most + `3d_0 - 2g - 1`. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_large') + sage: G = J.group() + sage: pts = G.get_points(G.order()) # indirect doctest + sage: len(pts) + 11 + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd1, wd2, self.mu_mat33, 6*d0 - d1 - d2 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat33, d1 + d2) + return w2 + + +cdef class KhuriMakdisi_medium(KhuriMakdisi_base): + """ + Khuri-Makdisi's *medium* model + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 16 12] + [ 0 1 0 0 15 0] + [ 0 0 1 0 1 0] + sage: p2 + Point of Jacobian determined by + [ 1 0 0 0 8 12] + [ 0 1 0 0 10 0] + [ 0 0 1 0 14 0] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 6 3 16] + [ 0 1 0 15 16 10] + [ 0 0 1 3 0 0] + sage: p1 - p2 + Point of Jacobian determined by + [ 1 0 0 8 0 14] + [ 0 1 0 1 10 10] + [ 0 0 1 15 3 6] + sage: p1.addflip(p2) == -(p1 + p2) + True + """ + cdef Matrix wV1, wV2, wV3, mu_mat22, mu_mat23, mu_mat31, mu_mat32 + + def __init__(self, V, mu, w0, d0, g): + """ + Initialize. + """ + self.wL = V(2).basis_matrix() + self.w0 = w0 + self.d0 = d0 + self.g = g + self.wV1 = V(1).basis_matrix() + self.wV2 = V(2).basis_matrix() + self.wV3 = V(3).basis_matrix() + self.mu_mat22 = matrix(listcat([list(mu(2, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat23 = matrix(listcat([list(mu(2, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat31 = matrix(listcat([list(mu(3, 1, i, j)) for j in range(1*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat32 = matrix(listcat([list(mu(3, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(3*d0-g+1)) + + def equal(self, wd, we): + """ + Theorem 4.1, second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 == p2 + p1 + True + sage: p1 - p2 == -(p2 - p1) + True + sage: zero = G.zero() + sage: p1 + zero == p1 + True + sage: p1 - p1 == zero + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix wf, w1, w2 + + wf = matrix(wd[0]) + w1 = self.mu_image(wf, we, self.mu_mat22, d0 - g + 1) + w2 = self.mu_preimage(wd, w1, self.mu_mat22) + return w2.rank() > 0 + + cpdef Matrix addflip(self, Matrix wd1, Matrix wd2): + """ + Theorem 5.1 (addflip in medium model). + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: af = p1.addflip(p2) + sage: af + Point of Jacobian determined by + [ 1 0 0 6 3 16] + [ 0 1 0 0 7 9] + [ 0 0 1 14 2 3] + + We check the computation in other model:: + + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: G.point(af.divisor()) == p1.addflip(p2) + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2, w3, w4 + + w1 = self.mu_image(wd1, wd2, self.mu_mat22, 2*d0 - g + 1) + w2 = self.mu_preimage(self.wV1, w1, self.mu_mat31, 2*d0) + # efficient than + # wf = matrix(w2[0]) + # w3 = self.mu_image(wf, self.wV2, self.mu_mat32, 2*d0 - g + 1) + w3 = matrix(2*d0 - g + 1, w2[0] * self.mu_mat32) + w4 = self.mu_preimage(w2, w3, self.mu_mat23, d0) + return w4 + + cpdef Matrix add_divisor(self, Matrix wd1, Matrix wd2, int d1, int d2): + """ + Theorem 3.6 (addition of divisors, first method). + + We assume that `w_{D_1}`, `w_{D_2}` represent divisors of degree at + most `4d_0 - 2g - 1`. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_medium') + sage: G = J.group() + sage: pts = G.get_points(G.order()) # indirect doctest + sage: len(pts) + 11 + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd1, wd2, self.mu_mat22, 4*d0 - d1 - d2 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat22, d1 + d2) + return w2 + + +cdef class KhuriMakdisi_small(KhuriMakdisi_base): + """ + Khuri-Makdisi's *small* model + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 0 11] + [ 0 1 0 0 2 0] + [ 0 0 1 0 16 10] + [ 0 0 0 1 0 3] + sage: p2 + Point of Jacobian determined by + [1 0 0 0 0 3] + [0 1 0 0 7 0] + [0 0 1 0 3 5] + [0 0 0 1 0 2] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 10 9] + [ 0 1 0 0 7 5] + [ 0 0 1 0 15 4] + [ 0 0 0 1 3 10] + sage: p1 - p2 + Point of Jacobian determined by + [ 1 0 0 0 10 9] + [ 0 1 0 0 9 8] + [ 0 0 1 0 15 4] + [ 0 0 0 1 15 11] + sage: p1.addflip(p2) == -(p1 + p2) + True + """ + cdef Matrix wV2, wV3, wV4, mu_mat22 + cdef Matrix mu_mat23, mu_mat24, mu_mat32, mu_mat33, mu_mat34, mu_mat42, mu_mat43 + + def __init__(self, V, mu, w0, d0, g): + """ + Initialize. + """ + self.wL = V(3).basis_matrix() + self.w0 = w0 + self.d0 = d0 + self.g = g + self.wV2 = V(2).basis_matrix() + self.wV3 = V(3).basis_matrix() + self.wV4 = V(4).basis_matrix() + self.mu_mat23 = matrix(listcat([list(mu(2, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat24 = matrix(listcat([list(mu(2, 4, i, j)) for j in range(4*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat32 = matrix(listcat([list(mu(3, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat33 = matrix(listcat([list(mu(3, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat34 = matrix(listcat([list(mu(3, 4, i, j)) for j in range(4*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat42 = matrix(listcat([list(mu(4, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(4*d0-g+1)) + self.mu_mat43 = matrix(listcat([list(mu(4, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(4*d0-g+1)) + + def equal(self, wd, we): + """ + Theorem 4.1, second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 == p2 + p1 + True + sage: p1 - p2 == -(p2 - p1) + True + sage: zero = G.zero() + sage: p1 + zero == p1 + True + sage: p1 - p1 == zero + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix wf, w1, w2 + + wf = matrix(wd[0]) + w1 = self.mu_image(wf, we, self.mu_mat33, 2*d0 - g + 1) + w2 = self.mu_preimage(wd, w1, self.mu_mat33) + return w2.rank() > 0 + + cpdef Matrix addflip(self, Matrix wd1, Matrix wd2): + """ + Theorem 5.3 (addflip in small model), second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: af = p1.addflip(p2) + sage: af + Point of Jacobian determined by + [ 1 0 0 0 10 9] + [ 0 1 0 0 10 12] + [ 0 0 1 0 15 4] + [ 0 0 0 1 14 7] + + We check the computation in other model:: + + sage: h = C.function(y/x).divisor_of_poles() + sage: Jl = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: q1 = G.point(pl1 - b) + sage: q2 = G.point(pl2 - b) + sage: G.point(af.divisor()) == q1.addflip(p2) + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2, w3, w4, w5 + + w1 = self.mu_image(wd1, wd2, self.mu_mat33, 4*d0 - g + 1) + w2 = self.mu_preimage(self.wV3, w1, self.mu_mat33, 2*d0) + w3 = self.mu_preimage(self.wV2, w1, self.mu_mat42, 2*d0) + # efficient than + # wf = matrix(w2[0]) + # w4 = self.mu_image(wf, self.wV4, self.mu_mat34, 4*d0 - g + 1) + w4 = matrix(4*d0 - g + 1, w2[0] * self.mu_mat34) + w5 = self.mu_preimage(w3, w4, self.mu_mat34, d0) + return w5 + + cpdef Matrix negate(self, Matrix wd): + """ + Theorem 5.4 (negation in small model). + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3- y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([3,2,1]).place() + sage: pl2 = C([5,5,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: -(-p1) == p1 + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2, w3, w4 + + w1 = self.mu_image(self.wV2, wd, self.mu_mat23, 4*d0 - g + 1) + w2 = self.mu_preimage(self.wV3, w1, self.mu_mat23, d0) + # efficient than + # wf = matrix(w2[0]) + # w3 = self.mu_image(wf, self.wV4, self.mu_mat24, 4*d0 - g + 1) + w3 = matrix(4*d0 - g + 1, w2[0] * self.mu_mat24) + w4 = self.mu_preimage(wd, w3, self.mu_mat33, d0) + return w4 + + cpdef Matrix add_divisor(self, Matrix wd1, Matrix wd2, int d1, int d2): + """ + Theorem 3.6 (addition of divisors, first method). + + We assume that `w_{D_1}`, `w_{D_2}` represent divisors of degree at most + `6d_0 - 2g - 1`. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_small') + sage: G = J.group() + sage: pts = G.get_points(G.order()) # indirect doctest + sage: len(pts) + 11 + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd1, wd2, self.mu_mat33, 6*d0 - d1 - d2 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat33, d1 + d2) + return w2 diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index 0b3f311eb62..c1c9794f4fc 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -2707,6 +2707,45 @@ def places_on(self, point): places.append(p) return places + def jacobian(self, model, base_div=None): + """ + Return the Jacobian of this curve. + + INPUT: + + - ``model`` -- model to use for arithmetic + + - ``base_div`` -- an effective divisor for the model + + The degree of the base divisor should satisfy certain degree condition + corresponding to the model used. The following table lists these + conditions. Let `g` be the geometric genus of the curve. + + - ``hess``: ideal-based arithmetic; requires base divisor of degree `g` + + - ``km_large``: Khuri-Makdisi's large model; requires base divisor of + degree at least `2g + 1` + + - ``km_medium``: Khuri-Makdisi's medium model; requires base divisor of + degree at least `2g + 1` + + - ``km_small``: Khuri-Makdisi's small model requires base divisor of + degree at least `g + 1` + + We assume the curve (or its function field) has a rational place. If a + base divisor is not given, one is chosen using a rational place. + + EXAMPLES:: + + sage: A. = AffineSpace(GF(5), 2) + sage: C = Curve(y^2*(x^3 - 1) - (x^3 - 2)).projective_closure() + sage: J = C.jacobian(model='hess'); J + Jacobian of Projective Plane Curve over Finite Field of size 5 defined by 2*x0^5 - x0^2*x1^3 - x0^3*x2^2 + x1^3*x2^2 (Hess model) + sage: J.base_divisor().degree() == C.genus() + True + """ + return self.function_field().jacobian(model, base_div, curve=self) + class IntegralProjectiveCurve_finite_field(IntegralProjectiveCurve): """ diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index eccbb917c11..6b47f2ab52f 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -2337,6 +2337,62 @@ def ate_pairing(self, Q, n, k, t, q=None): ret = ret**e return ret + def point_of_jacobian_of_projective_curve(self): + r""" + Return the point in the Jacobian of the curve. + + The Jacobian is the one attached to the projective curve associated + with this elliptic curve. + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: k. = GF((5,2)) + sage: E = EllipticCurve(k,[1,0]); E + Elliptic Curve defined by y^2 = x^3 + x over Finite Field in a of size 5^2 + sage: E.order() + 32 + sage: P = E([a, 2*a + 4]) + sage: P + (a : 2*a + 4 : 1) + sage: P.order() + 8 + sage: p = P.point_of_jacobian_of_projective_curve() + sage: p + [Place (x + 4*a, y + 3*a + 1)] + sage: p.order() + 8 + sage: Q = 3*P + sage: q = Q.point_of_jacobian_of_projective_curve() + sage: q == 3*p + True + sage: G = p.parent() + sage: G.order() + 32 + sage: G + Group of rational points of Jacobian over Finite Field in a of size 5^2 (Hess model) + sage: J = G.parent(); J + Jacobian of Projective Plane Curve over Finite Field in a of size 5^2 + defined by x^2*y + y^3 - x*z^2 (Hess model) + sage: J.curve() == E.affine_patch(2).projective_closure() + True + """ + from sage.schemes.curves.constructor import Curve + C = self.curve() + A = C.ambient_space() # projective plane + x, y, z = self + + X = Curve(C.defining_ideal().gens(), A) + X = X.affine_patch(2).projective_closure() + F = X.function_field() + P = X(z,x,y).place() + + Pinf = F.places_infinite()[0] + assert Pinf.degree() == 1, "no rational point at infinity" + + J = X.jacobian(model='hess', base_div=F.genus()*Pinf) + G = J.group(self.base_ring()) + return G(P - P.degree()*Pinf) class EllipticCurvePoint_number_field(EllipticCurvePoint_field): """ diff --git a/src/sage/schemes/elliptic_curves/jacobian.py b/src/sage/schemes/elliptic_curves/jacobian.py index 23151109453..fcd941b4276 100644 --- a/src/sage/schemes/elliptic_curves/jacobian.py +++ b/src/sage/schemes/elliptic_curves/jacobian.py @@ -99,7 +99,7 @@ def Jacobian(X, **kwds): """ try: return X.jacobian(**kwds) - except AttributeError: + except (AttributeError, TypeError): pass morphism = kwds.pop('morphism', False) diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py index e0dcc4bacfe..887fa28c9cb 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py @@ -489,33 +489,103 @@ def scheme(self): r""" Return the scheme this morphism maps to; or, where this divisor lives. - .. warning:: + .. 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. + 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: # needs sage.rings.number_field 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 + sage: F. = NumberField(x^2 - 2, 'a') + sage: J = H.jacobian()(F); J 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 + sage: P = J(H.lift_x(F(1))) + sage: P.scheme() Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x """ return self.codomain() + def point_of_jacobian_of_projective_curve(self): + r""" + Return the point in the Jacobian of the curve. + + The Jacobian is the one attached to the projective curve + corresponding to this hyperelliptic curve. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(11)) + sage: f = x^6 + x + 1 + sage: H = HyperellipticCurve(f) + sage: J = H.jacobian() + sage: D = J(H.lift_x(1)) + sage: D # divisor in Mumford representation + (x + 10, y + 6) + sage: jacobian_order = sum(H.frobenius_polynomial()) + sage: jacobian_order + 234 + sage: p = D.point_of_jacobian_of_projective_curve(); p + [Place (1/x0, 1/x0^3*x1 + 1) + + Place (x0 + 10, x1 + 6)] + sage: p # Jacobian point represented by an effective divisor + [Place (1/x0, 1/x0^3*x1 + 1) + + Place (x0 + 10, x1 + 6)] + sage: p.order() + 39 + sage: 234*p == 0 + True + sage: G = p.parent() + sage: G + Group of rational points of Jacobian over Finite Field of size 11 (Hess model) + sage: J = G.parent() + sage: J + Jacobian of Projective Plane Curve over Finite Field of size 11 + defined by x0^6 + x0^5*x1 + x1^6 - x0^4*x2^2 (Hess model) + sage: C = J.curve() + sage: C + Projective Plane Curve over Finite Field of size 11 + defined by x0^6 + x0^5*x1 + x1^6 - x0^4*x2^2 + sage: C.affine_patch(0) == H.affine_patch(2) + True + """ + from sage.schemes.curves.constructor import Curve + C = self.parent().curve() + P = C.ambient_space() # projective plane + x0, x1, x2 = P.gens() + + # X is the curve positioned in the ambient space + # such that x1 = x and x2 = y + X = Curve(C.defining_ideal().gens(), P) + X = X.affine_patch(2).projective_closure() + + u0, v0 = list(self) + u1 = u0.subs(x1).homogenize(x0) + v1 = (x2 - v0.subs(x1)).homogenize(x0) + u2 = u1/x0**u1.degree() + v2 = v1/x0**v1.degree() + u = X.function(u2) + v = X.function(v2) + + F = X.function_field() + O = F.maximal_order() + D = O.ideal([u,v]).divisor() + + Pinf = F.places_infinite()[0] + assert Pinf.degree() == 1, "no rational point at infinity" + + J = X.jacobian(model='hess', base_div=F.genus()*Pinf) + G = J.group(self.base_ring()) + return G(D - D.degree()*Pinf) + def __list__(self): r""" Return a list `(a(x), b(x))` of the polynomials giving the diff --git a/src/sage/schemes/jacobians/abstract_jacobian.py b/src/sage/schemes/jacobians/abstract_jacobian.py index 9ab2ef283ae..ee58e6f04c5 100644 --- a/src/sage/schemes/jacobians/abstract_jacobian.py +++ b/src/sage/schemes/jacobians/abstract_jacobian.py @@ -56,7 +56,7 @@ def Jacobian(C): """ try: return C.jacobian() - except AttributeError: + except (AttributeError, TypeError): return Jacobian_generic(C)