From 2368519b76c203ec17c68dfff688b1650a6108fe Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 19 Aug 2025 18:32:42 +0200 Subject: [PATCH 01/59] completion of polynomial rings and their fraction fields --- src/sage/rings/fraction_field.py | 91 +++++++++++++++ src/sage/rings/laurent_series_ring.py | 6 +- src/sage/rings/polynomial/meson.build | 1 + src/sage/rings/polynomial/polynomial_ring.py | 111 +++++++++++++++---- src/sage/rings/power_series_ring.py | 15 ++- src/sage/structure/category_object.pyx | 32 +++++- 6 files changed, 225 insertions(+), 31 deletions(-) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 5a633c8a846..82603ed6be1 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1206,6 +1206,97 @@ def _coerce_map_from_(self, R): return super()._coerce_map_from_(R) + def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): + r""" + Return the completion of this rational functions ring with + respect to the irreducible polynomial `p`. + + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infiniity``; if ``None``, the generator of this polynomial + ring + + - ``prec`` (default: 20) -- an integer or ``Infinity``; if + ``Infinity``, return a + :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. + + - ``name`` (default: ``None``) -- a string, the variable name; + if ``None`` and the completion is at `0`, the name of the + variable is this polynomial ring is reused + + - ``residue_name`` (default: ``None``) -- a string, the variable + name for the residue field (only relevant for places of degree + at least `2`) + + - ``names`` (default: ``None``) -- a tuple of strings with the + previous variable names + + EXAMPLES:: + + sage: A. = PolynomialRing(QQ) + sage: K = A.fraction_field() + + Without any argument, this method constructs the completion at + the ideal `x`:: + + sage: Kx = K.completion() + sage: Kx + Laurent Series Ring in x over Rational Field + + We can construct the completion at other ideals by passing in an + irreducible polynomial. In this case, we should also provide a name + for the uniformizer (set to be `x - a` where `a` is a root of the + given polynomial):: + + sage: K1. = K.completion(x - 1) + sage: K1 + Laurent Series Ring in u over Rational Field + sage: x - u + 1 + + :: + + sage: K2. = K.completion(x^2 + x + 1) + sage: K2 + Laurent Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 + sage: a^2 + a + 1 + 0 + sage: x - v + a + + When the precision is infinity, a lazy series ring is returned:: + + sage: # needs sage.combinat + sage: L = K.completion(x, prec=oo); L + Lazy Laurent Series Ring in x over Rational Field + """ + from sage.rings.polynomial.morphism import MorphismToCompletion + if names is not None: + if name is not None or residue_name is not None: + raise ValueError("") + if not isinstance(names, (list, tuple)): + raise TypeError("names must a a list or a tuple") + name = names[0] + if len(names) > 1: + residue_name = names[1] + x = self.gen() + if p is None: + p = x + if p == x and name is None: + name = self.variable_name() + incl = MorphismToCompletion(self, p, prec, name, residue_name) + C = incl.codomain() + if C.has_coerce_map_from(self): + if C(x) != incl(x): + raise ValueError("a different coercion map is already set; try to change the variable name") + else: + C.register_coercion(incl) + ring = self.ring() + if not C.has_coerce_map_from(ring): + C.register_coercion(incl * self.coerce_map_from(ring)) + return C + class FractionFieldEmbedding(DefaultConvertMap_unique): r""" diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 80a84b67f26..c95cfbbbb4a 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -221,12 +221,14 @@ def __classcall__(cls, *args, **kwds): 'q' """ from .power_series_ring import PowerSeriesRing - + if 'default_prec' in kwds and kwds['default_prec'] is infinity: + from sage.rings.lazy_series_ring import LazyLaurentSeriesRing + del kwds['default_prec'] + return LazyLaurentSeriesRing(*args, **kwds) if not kwds and len(args) == 1 and isinstance(args[0], (PowerSeriesRing_generic, LazyPowerSeriesRing)): power_series = args[0] else: power_series = PowerSeriesRing(*args, **kwds) - return UniqueRepresentation.__classcall__(cls, power_series) def __init__(self, power_series): diff --git a/src/sage/rings/polynomial/meson.build b/src/sage/rings/polynomial/meson.build index e7de57dc2fb..954fe217845 100644 --- a/src/sage/rings/polynomial/meson.build +++ b/src/sage/rings/polynomial/meson.build @@ -19,6 +19,7 @@ py.install_sources( 'laurent_polynomial_mpair.pxd', 'laurent_polynomial_ring.py', 'laurent_polynomial_ring_base.py', + 'morphism.py', 'msolve.py', 'multi_polynomial.pxd', 'multi_polynomial_element.py', diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index e0d1ac37eb3..61ea633186d 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -671,23 +671,42 @@ def construction(self): """ return categories.pushout.PolynomialFunctor(self.variable_name(), sparse=self.__is_sparse), self.base_ring() - def completion(self, p=None, prec=20, extras=None): + def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): r""" - Return the completion of ``self`` with respect to the irreducible - polynomial ``p``. + Return the completion of this polynomial ring with respect to + the irreducible polynomial ``p``. - Currently only implemented for ``p=self.gen()`` (the default), i.e. you - can only complete `R[x]` with respect to `x`, the result being a ring - of power series in `x`. The ``prec`` variable controls the precision - used in the power series ring. If ``prec`` is `\infty`, then this - returns a :class:`LazyPowerSeriesRing`. + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infiniity``; if ``None``, the generator of this polynomial + ring + + - ``prec`` (default: 20) -- an integer or ``Infinity``; if + ``Infinity``, return a + :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. + + - ``name`` (default: ``None``) -- a string, the variable name; + if ``None`` and the completion is at `0`, the name of the + variable is this polynomial ring is reused + + - ``residue_name`` (default: ``None``) -- a string, the variable + name for the residue field (only relevant for places of degree + at least `2`) + + - ``names`` (default: ``None``) -- a tuple of strings with the + previous variable names EXAMPLES:: sage: P. = PolynomialRing(QQ) sage: P Univariate Polynomial Ring in x over Rational Field - sage: PP = P.completion(x) + + Without any argument, this method constructs the completion at + the ideal `x`:: + + sage: PP = P.completion() sage: PP Power Series Ring in x over Rational Field sage: f = 1 - x @@ -701,6 +720,42 @@ def completion(self, p=None, prec=20, extras=None): sage: 1 / g 1 - x + O(x^20) + We can construct the completion at other ideals by passing in an + irreducible polynomial. In this case, we should also provide a name + for the uniformizer (set to be `x - a` where `a` is a root of the + given polynomial):: + + sage: C1. = P.completion(x - 1) + sage: C1 + Power Series Ring in u over Rational Field + + A coercion map from the polynomial ring to its completion is set:: + + sage: x - u + 1 + + It is possible to complete at an ideal generated by a polynomial + of higher degree. In this case, we should nevertheless provide an + extra name for the generator of the residue field:: + + sage: C2. = P.completion(x^2 + x + 1) + sage: C2 + Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 + sage: a^2 + a + 1 + 0 + sage: x - v + a + + Constructing the completion at the place of infinity also works:: + + sage: C3. = P.completion(infinity) + sage: C3 + Laurent Series Ring in w over Rational Field + sage: C3(x) + w^-1 + + When the precision is infinity, a lazy series ring is returned:: + sage: # needs sage.combinat sage: PP = P.completion(x, prec=oo); PP Lazy Taylor Series Ring in x over Rational Field @@ -709,16 +764,34 @@ def completion(self, p=None, prec=20, extras=None): sage: 1 / g == f True """ - if p is None or str(p) == self._names[0]: - if prec == float('inf'): - from sage.rings.lazy_series_ring import LazyPowerSeriesRing - return LazyPowerSeriesRing(self.base_ring(), names=(self._names[0],), - sparse=self.is_sparse()) - from sage.rings.power_series_ring import PowerSeriesRing - return PowerSeriesRing(self.base_ring(), name=self._names[0], - default_prec=prec, sparse=self.is_sparse()) - - raise NotImplementedError("cannot complete %s with respect to %s" % (self, p)) + from sage.rings.polynomial.morphism import MorphismToCompletion + if names is not None: + if name is not None or residue_name is not None: + raise ValueError("") + if not isinstance(names, (list, tuple)): + raise TypeError("names must a a list or a tuple") + name = names[0] + if len(names) > 1: + residue_name = names[1] + x = self.gen() + if p is None: + p = x + if p == x and name is None: + name = self.variable_name() + if p is sage.rings.infinity.infinity: + ring = self.fraction_field() + else: + ring = self + incl = MorphismToCompletion(ring, p, prec, name, residue_name) + C = incl.codomain() + if C.has_coerce_map_from(ring): + if C(x) != incl(x): + raise ValueError("a different coercion map is already set; try to change the variable name") + else: + C.register_coercion(incl) + if not (ring is self and C.has_coerce_map_from(self)): + C.register_coercion(incl * ring.coerce_map_from(self)) + return C def _coerce_map_from_base_ring(self): """ diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index 129477ccc8c..a3bdbd178d1 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -392,7 +392,7 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, # the following is the original, univariate-only code - if isinstance(name, (int, integer.Integer)): + if isinstance(name, (int, integer.Integer)) or name is infinity: default_prec = name if names is not None: name = names @@ -412,17 +412,21 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, if not (name is None or isinstance(name, str)): raise TypeError("variable name must be a string or None") - if base_ring in _Fields: + if base_ring not in _CommutativeRings: + raise TypeError("base_ring must be a commutative ring") + + if default_prec is infinity: + from sage.rings.lazy_series_ring import LazyPowerSeriesRing + R = LazyPowerSeriesRing(base_ring, name, sparse) + elif base_ring in _Fields: R = PowerSeriesRing_over_field(base_ring, name, default_prec, sparse=sparse, implementation=implementation) elif base_ring in _IntegralDomains: R = PowerSeriesRing_domain(base_ring, name, default_prec, sparse=sparse, implementation=implementation) - elif base_ring in _CommutativeRings: + else: R = PowerSeriesRing_generic(base_ring, name, default_prec, sparse=sparse, implementation=implementation) - else: - raise TypeError("base_ring must be a commutative ring") return R @@ -614,7 +618,6 @@ def variable_names_recursive(self, depth=None): ('y', 'z') """ if depth is None: - from sage.rings.infinity import infinity depth = infinity if depth <= 0: diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index aaf0c285322..5620bf06189 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -370,13 +370,37 @@ cdef class CategoryObject(SageObject): sage: B. = EquationOrder(x^2 + 3) # needs sage.rings.number_field sage: z.minpoly() # needs sage.rings.number_field x^2 + 3 + + If the ring has less than `n` generators, the generators + of the base rings are happened recursively:: + + sage: S. = PolynomialRing(R) + sage: T. = PolynomialRing(S) + sage: T._first_ngens(1) + (z,) + sage: T._first_ngens(2) + (z, y) + sage: T._first_ngens(3) + (z, y, x) """ names = self._defining_names() - if isinstance(names, (list, tuple)): + if not isinstance(names, (list, tuple)): + # case of Family + it = iter(names) + names = [] + for _ in range(n): + try: + names.append(next(it)) + except StopIteration: + break + names = tuple(names) + m = n - len(names) + if m <= 0: return names[:n] - # case of Family - it = iter(names) - return tuple(next(it) for i in range(n)) + else: + if hasattr(self, 'base'): + names += tuple(self(x) for x in self.base()._first_ngens(m)) + return names @cached_method def _defining_names(self): From 573dae0c900079008640f142bb969d0aca66f3b8 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 19 Aug 2025 23:47:14 +0200 Subject: [PATCH 02/59] file morphism.py --- src/sage/rings/polynomial/morphism.py | 158 ++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/sage/rings/polynomial/morphism.py diff --git a/src/sage/rings/polynomial/morphism.py b/src/sage/rings/polynomial/morphism.py new file mode 100644 index 00000000000..edeea117064 --- /dev/null +++ b/src/sage/rings/polynomial/morphism.py @@ -0,0 +1,158 @@ +r""" +Morphisms attached to polynomial rings. +""" + +# ***************************************************************************** +# Copyright (C) 2025 Xavier Caruso +# +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.structure.category_object import normalize_names +from sage.categories.rings import Rings + +from sage.rings.morphism import RingHomomorphism +from sage.rings.integer_ring import ZZ +from sage.rings.infinity import Infinity +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.laurent_series_ring import LaurentSeriesRing + + +class MorphismToCompletion(RingHomomorphism): + r""" + Morphisms from a polynomial ring (or its fraction field) + to the completion at one place. + + TESTS:: + + sage: A. = GF(5)[] + sage: B. = A.completion(t+1) + sage: f = B.coerce_map_from(A) + sage: type(f) + + + sage: TestSuite(f).run(skip='_test_category') + """ + def __init__(self, domain, place, prec, name, residue_name): + r""" + Initialize this morphism. + + INPUT: + + - ``domain`` -- a polynomial ring of its fraction field + + - ``place`` -- an irreducible polynomial or ``Infinity`` + + - ``prec`` -- an integer or ``Infinity`` + + - ``name`` -- a string, the variable name of the uniformizer + + - ``residue_name`` -- a string, the variable name of the + generator of the residue ring + + TESTS:: + + sage: A. = ZZ[] + sage: B. = A.completion(2*t + 1) + Traceback (most recent call last): + ... + NotImplementedError: the leading coefficient of the place is not a unit + + :: + + sage: A. = QQ[] + sage: B. = A.completion(x^2 + 2*x + 1) + Traceback (most recent call last): + ... + ValueError: place must be Infinity or an irreducible polynomial + """ + if domain.is_field(): + ring = domain.ring() + SeriesRing = LaurentSeriesRing + else: + ring = domain + SeriesRing = PowerSeriesRing + k = base = ring.base_ring() + x = ring.gen() + if place is Infinity: + pass + elif place in ring: + place = ring(place) + if place.leading_coefficient().is_unit(): + place = ring(place.monic()) + if not place.is_irreducible(): + raise ValueError("place must be Infinity or an irreducible polynomial") + else: + raise NotImplementedError("the leading coefficient of the place is not a unit") + else: + raise ValueError("place must be Infinity or an irreducible polynomial") + self._place = place + if name is None: + raise ValueError("you must specify a variable name") + name = normalize_names(1, name) + if place is Infinity: + codomain = LaurentSeriesRing(base, names=name, default_prec=prec) + image = codomain.one() >> 1 + elif place.degree() == 1: + codomain = SeriesRing(base, names=name, default_prec=prec) + image = codomain.gen() - place[0] + else: + if residue_name is None: + raise ValueError("you must specify a variable name for the residue field") + residue_name = normalize_names(1, residue_name) + k = base.extension(place, names=residue_name) + codomain = SeriesRing(k, names=name, default_prec=prec) + image = codomain.gen() + k.gen() + parent = domain.Hom(codomain, category=Rings()) + RingHomomorphism.__init__(self, parent) + self._image = image + self._k = k + self._q = k.cardinality() + + def _repr_type(self): + r""" + Return a string that describes the type of this morphism. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = A.completion(x + 1) + sage: f = B.coerce_map_from(A) + sage: f # indirect doctest + Completion morphism: + From: Univariate Polynomial Ring in x over Rational Field + To: Power Series Ring in u over Rational Field + """ + return "Completion" + + def place(self): + r""" + Return the place at which we completed. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = A.completion(x + 1) + sage: f = B.coerce_map_from(A) + sage: f.place() + x + 1 + """ + return self._place + + def _call_(self, P): + r""" + Return the image of ``P`` under this morphism. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = A.completion(x + 1) + sage: f = B.coerce_map_from(A) + sage: f(x) # indirect doctest + -1 + u + """ + return P(self._image) From ce729800871080c4f52357d59a8e6d42bdec161b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 08:54:03 +0200 Subject: [PATCH 03/59] fix failing doctests --- src/sage/rings/polynomial/morphism.py | 14 +++---- src/sage/rings/polynomial/polynomial_ring.py | 39 +++++++++++++------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/sage/rings/polynomial/morphism.py b/src/sage/rings/polynomial/morphism.py index edeea117064..7651573e108 100644 --- a/src/sage/rings/polynomial/morphism.py +++ b/src/sage/rings/polynomial/morphism.py @@ -78,14 +78,15 @@ def __init__(self, domain, place, prec, name, residue_name): SeriesRing = PowerSeriesRing k = base = ring.base_ring() x = ring.gen() + sparse = ring.is_sparse() if place is Infinity: pass elif place in ring: place = ring(place) if place.leading_coefficient().is_unit(): place = ring(place.monic()) - if not place.is_irreducible(): - raise ValueError("place must be Infinity or an irreducible polynomial") + # We do not check irreducibility; it causes too much troubles: + # it can be long, be not implemented and even sometimes fail else: raise NotImplementedError("the leading coefficient of the place is not a unit") else: @@ -95,23 +96,22 @@ def __init__(self, domain, place, prec, name, residue_name): raise ValueError("you must specify a variable name") name = normalize_names(1, name) if place is Infinity: - codomain = LaurentSeriesRing(base, names=name, default_prec=prec) + codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) image = codomain.one() >> 1 - elif place.degree() == 1: - codomain = SeriesRing(base, names=name, default_prec=prec) + elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 + codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) image = codomain.gen() - place[0] else: if residue_name is None: raise ValueError("you must specify a variable name for the residue field") residue_name = normalize_names(1, residue_name) k = base.extension(place, names=residue_name) - codomain = SeriesRing(k, names=name, default_prec=prec) + codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) image = codomain.gen() + k.gen() parent = domain.Hom(codomain, category=Rings()) RingHomomorphism.__init__(self, parent) self._image = image self._k = k - self._q = k.cardinality() def _repr_type(self): r""" diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 61ea633186d..395500af1ee 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -671,7 +671,7 @@ def construction(self): """ return categories.pushout.PolynomialFunctor(self.variable_name(), sparse=self.__is_sparse), self.base_ring() - def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): + def completion(self, p=None, prec=20, extras=None, names=None): r""" Return the completion of this polynomial ring with respect to the irreducible polynomial ``p``. @@ -686,16 +686,14 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): ``Infinity``, return a :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. - - ``name`` (default: ``None``) -- a string, the variable name; - if ``None`` and the completion is at `0`, the name of the - variable is this polynomial ring is reused + - ``extras`` (default: ``None``) -- ignored; for compatibility + with the construction mecanism - - ``residue_name`` (default: ``None``) -- a string, the variable - name for the residue field (only relevant for places of degree - at least `2`) - - - ``names`` (default: ``None``) -- a tuple of strings with the - previous variable names + - ``names`` (default: ``None``) -- a tuple of strings containing + - the variable name; if not given and the completion is at `0`, + the name of the variable is this polynomial ring is reused + - the variable name for the residue field (only relevant for + places of degree at least `2`) EXAMPLES:: @@ -763,19 +761,34 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): 1 + x + x^2 + O(x^3) sage: 1 / g == f True + + TESTS:: + + sage: P.completion('x') + Power Series Ring in x over Rational Field + sage: P.completion('y') + Power Series Ring in y over Rational Field + sage: Pz. = P.completion('y') + Traceback (most recent call last): + ... + ValueError: conflict of variable names """ from sage.rings.polynomial.morphism import MorphismToCompletion + name = residue_name = None if names is not None: - if name is not None or residue_name is not None: - raise ValueError("") if not isinstance(names, (list, tuple)): - raise TypeError("names must a a list or a tuple") + raise TypeError("names must be a list or a tuple") name = names[0] if len(names) > 1: residue_name = names[1] x = self.gen() if p is None: p = x + elif isinstance(p, str): + if name is not None and name != p: + raise ValueError("conflict of variable names") + name = p + p = x if p == x and name is None: name = self.variable_name() if p is sage.rings.infinity.infinity: From d990b8f66d2cab1f4099bfa105fb6692b366e20d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 09:57:25 +0200 Subject: [PATCH 04/59] fix doctest --- src/sage/rings/polynomial/polynomial_ring.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 395500af1ee..47c447d4374 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -690,8 +690,10 @@ def completion(self, p=None, prec=20, extras=None, names=None): with the construction mecanism - ``names`` (default: ``None``) -- a tuple of strings containing + - the variable name; if not given and the completion is at `0`, the name of the variable is this polynomial ring is reused + - the variable name for the residue field (only relevant for places of degree at least `2`) From eff3e2250acbba2be964461c4c6a4c471c1f9507 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 11:44:26 +0200 Subject: [PATCH 05/59] avoid recursive loop in coercion --- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 47c447d4374..d379f50644d 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -800,7 +800,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): incl = MorphismToCompletion(ring, p, prec, name, residue_name) C = incl.codomain() if C.has_coerce_map_from(ring): - if C(x) != incl(x): + if C(x) != incl._image: raise ValueError("a different coercion map is already set; try to change the variable name") else: C.register_coercion(incl) From 2f06fbc7373af55c6643b63ab8c41dafe3681303 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 13:49:09 +0200 Subject: [PATCH 06/59] address easy remarks by user202729 --- src/sage/rings/polynomial/polynomial_ring.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index d379f50644d..abca12bc27e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -697,6 +697,9 @@ def completion(self, p=None, prec=20, extras=None, names=None): - the variable name for the residue field (only relevant for places of degree at least `2`) + The argument ``names`` is usually implicitly given by the `.<...>` + syntactic sugar (see examples below). + EXAMPLES:: sage: P. = PolynomialRing(QQ) @@ -704,7 +707,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): Univariate Polynomial Ring in x over Rational Field Without any argument, this method constructs the completion at - the ideal `x`:: + the ideal `(x)`:: sage: PP = P.completion() sage: PP From e6a894242980b6f3669e5854213b60d9a71f3a64 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 13:52:16 +0200 Subject: [PATCH 07/59] typo --- src/sage/structure/category_object.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index 5620bf06189..880ebf51cb8 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -372,7 +372,7 @@ cdef class CategoryObject(SageObject): x^2 + 3 If the ring has less than `n` generators, the generators - of the base rings are happened recursively:: + of the base rings are appended recursively:: sage: S. = PolynomialRing(R) sage: T. = PolynomialRing(S) From 5f008e644fd2cfcd68fec5202c10bb5a190106e2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 14:32:25 +0200 Subject: [PATCH 08/59] remove class MorphismToCompletion --- src/sage/rings/fraction_field.py | 30 +- src/sage/rings/polynomial/meson.build | 1 - src/sage/rings/polynomial/morphism.py | 158 -------- src/sage/rings/polynomial/polynomial_ring.py | 373 ++++++++++++------- 4 files changed, 235 insertions(+), 327 deletions(-) delete mode 100644 src/sage/rings/polynomial/morphism.py diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 82603ed6be1..163d5c46233 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1206,7 +1206,7 @@ def _coerce_map_from_(self, R): return super()._coerce_map_from_(R) - def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): + def completion(self, p=None, prec=20, names=None): r""" Return the completion of this rational functions ring with respect to the irreducible polynomial `p`. @@ -1221,14 +1221,6 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): ``Infinity``, return a :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. - - ``name`` (default: ``None``) -- a string, the variable name; - if ``None`` and the completion is at `0`, the name of the - variable is this polynomial ring is reused - - - ``residue_name`` (default: ``None``) -- a string, the variable - name for the residue field (only relevant for places of degree - at least `2`) - - ``names`` (default: ``None``) -- a tuple of strings with the previous variable names @@ -1238,7 +1230,7 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): sage: K = A.fraction_field() Without any argument, this method constructs the completion at - the ideal `x`:: + the ideal `(x)`:: sage: Kx = K.completion() sage: Kx @@ -1271,23 +1263,11 @@ def completion(self, p=None, prec=20, name=None, residue_name=None, names=None): sage: L = K.completion(x, prec=oo); L Lazy Laurent Series Ring in x over Rational Field """ - from sage.rings.polynomial.morphism import MorphismToCompletion - if names is not None: - if name is not None or residue_name is not None: - raise ValueError("") - if not isinstance(names, (list, tuple)): - raise TypeError("names must a a list or a tuple") - name = names[0] - if len(names) > 1: - residue_name = names[1] - x = self.gen() - if p is None: - p = x - if p == x and name is None: - name = self.variable_name() - incl = MorphismToCompletion(self, p, prec, name, residue_name) + from sage.rings.polynomial.polynomial_ring import morphism_to_completion + incl = morphism_to_completion(self, p, prec, names) C = incl.codomain() if C.has_coerce_map_from(self): + x = self.gen() if C(x) != incl(x): raise ValueError("a different coercion map is already set; try to change the variable name") else: diff --git a/src/sage/rings/polynomial/meson.build b/src/sage/rings/polynomial/meson.build index 954fe217845..e7de57dc2fb 100644 --- a/src/sage/rings/polynomial/meson.build +++ b/src/sage/rings/polynomial/meson.build @@ -19,7 +19,6 @@ py.install_sources( 'laurent_polynomial_mpair.pxd', 'laurent_polynomial_ring.py', 'laurent_polynomial_ring_base.py', - 'morphism.py', 'msolve.py', 'multi_polynomial.pxd', 'multi_polynomial_element.py', diff --git a/src/sage/rings/polynomial/morphism.py b/src/sage/rings/polynomial/morphism.py deleted file mode 100644 index 7651573e108..00000000000 --- a/src/sage/rings/polynomial/morphism.py +++ /dev/null @@ -1,158 +0,0 @@ -r""" -Morphisms attached to polynomial rings. -""" - -# ***************************************************************************** -# Copyright (C) 2025 Xavier Caruso -# -# 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. -# http://www.gnu.org/licenses/ -# ***************************************************************************** - -from sage.structure.category_object import normalize_names -from sage.categories.rings import Rings - -from sage.rings.morphism import RingHomomorphism -from sage.rings.integer_ring import ZZ -from sage.rings.infinity import Infinity -from sage.rings.power_series_ring import PowerSeriesRing -from sage.rings.laurent_series_ring import LaurentSeriesRing - - -class MorphismToCompletion(RingHomomorphism): - r""" - Morphisms from a polynomial ring (or its fraction field) - to the completion at one place. - - TESTS:: - - sage: A. = GF(5)[] - sage: B. = A.completion(t+1) - sage: f = B.coerce_map_from(A) - sage: type(f) - - - sage: TestSuite(f).run(skip='_test_category') - """ - def __init__(self, domain, place, prec, name, residue_name): - r""" - Initialize this morphism. - - INPUT: - - - ``domain`` -- a polynomial ring of its fraction field - - - ``place`` -- an irreducible polynomial or ``Infinity`` - - - ``prec`` -- an integer or ``Infinity`` - - - ``name`` -- a string, the variable name of the uniformizer - - - ``residue_name`` -- a string, the variable name of the - generator of the residue ring - - TESTS:: - - sage: A. = ZZ[] - sage: B. = A.completion(2*t + 1) - Traceback (most recent call last): - ... - NotImplementedError: the leading coefficient of the place is not a unit - - :: - - sage: A. = QQ[] - sage: B. = A.completion(x^2 + 2*x + 1) - Traceback (most recent call last): - ... - ValueError: place must be Infinity or an irreducible polynomial - """ - if domain.is_field(): - ring = domain.ring() - SeriesRing = LaurentSeriesRing - else: - ring = domain - SeriesRing = PowerSeriesRing - k = base = ring.base_ring() - x = ring.gen() - sparse = ring.is_sparse() - if place is Infinity: - pass - elif place in ring: - place = ring(place) - if place.leading_coefficient().is_unit(): - place = ring(place.monic()) - # We do not check irreducibility; it causes too much troubles: - # it can be long, be not implemented and even sometimes fail - else: - raise NotImplementedError("the leading coefficient of the place is not a unit") - else: - raise ValueError("place must be Infinity or an irreducible polynomial") - self._place = place - if name is None: - raise ValueError("you must specify a variable name") - name = normalize_names(1, name) - if place is Infinity: - codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.one() >> 1 - elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 - codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() - place[0] - else: - if residue_name is None: - raise ValueError("you must specify a variable name for the residue field") - residue_name = normalize_names(1, residue_name) - k = base.extension(place, names=residue_name) - codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() + k.gen() - parent = domain.Hom(codomain, category=Rings()) - RingHomomorphism.__init__(self, parent) - self._image = image - self._k = k - - def _repr_type(self): - r""" - Return a string that describes the type of this morphism. - - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = A.completion(x + 1) - sage: f = B.coerce_map_from(A) - sage: f # indirect doctest - Completion morphism: - From: Univariate Polynomial Ring in x over Rational Field - To: Power Series Ring in u over Rational Field - """ - return "Completion" - - def place(self): - r""" - Return the place at which we completed. - - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = A.completion(x + 1) - sage: f = B.coerce_map_from(A) - sage: f.place() - x + 1 - """ - return self._place - - def _call_(self, P): - r""" - Return the image of ``P`` under this morphism. - - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = A.completion(x + 1) - sage: f = B.coerce_map_from(A) - sage: f(x) # indirect doctest - -1 + u - """ - return P(self._image) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index abca12bc27e..ab1fa3be791 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -144,6 +144,7 @@ from sage.misc.superseded import deprecation from sage.structure.element import Element from sage.structure.category_object import check_default_category +from sage.structure.category_object import normalize_names import sage.categories as categories from sage.categories.morphism import IdentityMorphism @@ -154,6 +155,7 @@ from sage.structure.element import RingElement import sage.rings.rational_field as rational_field from sage.rings.rational_field import QQ +from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ from sage.rings.integer import Integer from sage.rings.number_field.number_field_base import NumberField @@ -671,146 +673,6 @@ def construction(self): """ return categories.pushout.PolynomialFunctor(self.variable_name(), sparse=self.__is_sparse), self.base_ring() - def completion(self, p=None, prec=20, extras=None, names=None): - r""" - Return the completion of this polynomial ring with respect to - the irreducible polynomial ``p``. - - INPUT: - - - ``p`` (default: ``None``) -- an irreduclible polynomial or - ``Infiniity``; if ``None``, the generator of this polynomial - ring - - - ``prec`` (default: 20) -- an integer or ``Infinity``; if - ``Infinity``, return a - :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. - - - ``extras`` (default: ``None``) -- ignored; for compatibility - with the construction mecanism - - - ``names`` (default: ``None``) -- a tuple of strings containing - - - the variable name; if not given and the completion is at `0`, - the name of the variable is this polynomial ring is reused - - - the variable name for the residue field (only relevant for - places of degree at least `2`) - - The argument ``names`` is usually implicitly given by the `.<...>` - syntactic sugar (see examples below). - - EXAMPLES:: - - sage: P. = PolynomialRing(QQ) - sage: P - Univariate Polynomial Ring in x over Rational Field - - Without any argument, this method constructs the completion at - the ideal `(x)`:: - - sage: PP = P.completion() - sage: PP - Power Series Ring in x over Rational Field - sage: f = 1 - x - sage: PP(f) - 1 - x - sage: 1 / f - -1/(x - 1) - sage: g = 1 / PP(f); g - 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + x^10 + x^11 - + x^12 + x^13 + x^14 + x^15 + x^16 + x^17 + x^18 + x^19 + O(x^20) - sage: 1 / g - 1 - x + O(x^20) - - We can construct the completion at other ideals by passing in an - irreducible polynomial. In this case, we should also provide a name - for the uniformizer (set to be `x - a` where `a` is a root of the - given polynomial):: - - sage: C1. = P.completion(x - 1) - sage: C1 - Power Series Ring in u over Rational Field - - A coercion map from the polynomial ring to its completion is set:: - - sage: x - u - 1 - - It is possible to complete at an ideal generated by a polynomial - of higher degree. In this case, we should nevertheless provide an - extra name for the generator of the residue field:: - - sage: C2. = P.completion(x^2 + x + 1) - sage: C2 - Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 - sage: a^2 + a + 1 - 0 - sage: x - v - a - - Constructing the completion at the place of infinity also works:: - - sage: C3. = P.completion(infinity) - sage: C3 - Laurent Series Ring in w over Rational Field - sage: C3(x) - w^-1 - - When the precision is infinity, a lazy series ring is returned:: - - sage: # needs sage.combinat - sage: PP = P.completion(x, prec=oo); PP - Lazy Taylor Series Ring in x over Rational Field - sage: g = 1 / PP(f); g - 1 + x + x^2 + O(x^3) - sage: 1 / g == f - True - - TESTS:: - - sage: P.completion('x') - Power Series Ring in x over Rational Field - sage: P.completion('y') - Power Series Ring in y over Rational Field - sage: Pz. = P.completion('y') - Traceback (most recent call last): - ... - ValueError: conflict of variable names - """ - from sage.rings.polynomial.morphism import MorphismToCompletion - name = residue_name = None - if names is not None: - if not isinstance(names, (list, tuple)): - raise TypeError("names must be a list or a tuple") - name = names[0] - if len(names) > 1: - residue_name = names[1] - x = self.gen() - if p is None: - p = x - elif isinstance(p, str): - if name is not None and name != p: - raise ValueError("conflict of variable names") - name = p - p = x - if p == x and name is None: - name = self.variable_name() - if p is sage.rings.infinity.infinity: - ring = self.fraction_field() - else: - ring = self - incl = MorphismToCompletion(ring, p, prec, name, residue_name) - C = incl.codomain() - if C.has_coerce_map_from(ring): - if C(x) != incl._image: - raise ValueError("a different coercion map is already set; try to change the variable name") - else: - C.register_coercion(incl) - if not (ring is self and C.has_coerce_map_from(self)): - C.register_coercion(incl * ring.coerce_map_from(self)) - return C - def _coerce_map_from_base_ring(self): """ Return a coercion map from the base ring of ``self``. @@ -1241,7 +1103,7 @@ def extend_variables(self, added_names, order='degrevlex'): added_names = added_names.split(',') return PolynomialRing(self.base_ring(), names=self.variable_names() + tuple(added_names), order=order) - def variable_names_recursive(self, depth=sage.rings.infinity.infinity): + def variable_names_recursive(self, depth=infinity): r""" Return the list of variable names of this ring and its base rings, as if it were a single multi-variate polynomial. @@ -1833,7 +1695,7 @@ def polynomials(self, of_degree=None, max_degree=None): - Joel B. Mohler """ - if self.base_ring().order() is sage.rings.infinity.infinity: + if self.base_ring().order() is infinity: raise NotImplementedError if of_degree is not None and max_degree is None: return self._polys_degree( of_degree ) @@ -1893,7 +1755,7 @@ def monics(self, of_degree=None, max_degree=None): - Joel B. Mohler """ - if self.base_ring().order() is sage.rings.infinity.infinity: + if self.base_ring().order() is infinity: raise NotImplementedError if of_degree is not None and max_degree is None: return self._monics_degree( of_degree ) @@ -1906,6 +1768,108 @@ def monics(self, of_degree=None, max_degree=None): PolynomialRing_general = PolynomialRing_generic +def morphism_to_completion(domain, place, prec, names): + r""" + Return the inclusion morphism from ``domain`` to its completion + at the place ``place``. + + INPUT: + + - ``domain`` -- a polynomial ring of its fraction field + + - ``place`` -- an irreducible polynomial or ``Infinity`` + + - ``prec`` -- an integer or ``Infinity`` + + - ``names`` -- a tuple of strings containing + + - the variable name; if not given and the completion is at `0`, + the name of the variable is this polynomial ring is reused + + - the variable name for the residue field (only relevant for + places of degree at least `2`) + + TESTS:: + + sage: A. = ZZ[] + sage: B. = A.completion(2*t + 1) + Traceback (most recent call last): + ... + NotImplementedError: the leading coefficient of the place is not a unit + + :: + + sage: A. = QQ[] + sage: B. = A.completion(x^2 + 2*x + 1) + Traceback (most recent call last): + ... + ValueError: place must be Infinity or an irreducible polynomial + """ + from sage.rings.power_series_ring import PowerSeriesRing + from sage.rings.laurent_series_ring import LaurentSeriesRing + if domain.is_field(): + ring = domain.ring() + SeriesRing = LaurentSeriesRing + else: + ring = domain + SeriesRing = PowerSeriesRing + k = base = ring.base_ring() + x = ring.gen() + sparse = ring.is_sparse() + + # Parse names + name = residue_name = None + if names is not None: + if not isinstance(names, (list, tuple)): + raise TypeError("names must be a list or a tuple") + name = names[0] + if len(names) > 1: + residue_name = names[1] + + # Parse place + if place is None: + place = x + elif isinstance(place, str): + if name is not None and name != place: + raise ValueError("conflict of variable names") + name = p + place = x + + if place == x and name is None: + name = ring.variable_name() + + if place is infinity: + pass + elif place in ring: + place = ring(place) + if place.leading_coefficient().is_unit(): + place = ring(place.monic()) + # We do not check irreducibility; it causes too much troubles: + # it can be long, be not implemented and even sometimes fail + else: + raise NotImplementedError("the leading coefficient of the place is not a unit") + else: + raise ValueError("place must be Infinity or an irreducible polynomial") + + # Construct the completion + if place is infinity: + codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) + image = codomain.one() >> 1 + elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 + codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) + image = codomain.gen() - place[0] + else: + if residue_name is None: + raise ValueError("you must specify a variable name for the residue field") + residue_name = normalize_names(1, residue_name) + k = base.extension(place, names=residue_name) + codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) + image = codomain.gen() + k.gen() + + # Return the morphism + return domain.hom([image]) + + class PolynomialRing_commutative(PolynomialRing_generic): """ Univariate polynomial ring over a commutative ring. @@ -1924,6 +1888,129 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sparse=sparse, implementation=implementation, element_class=element_class, category=category) + def completion(self, p=None, prec=20, extras=None, names=None): + r""" + Return the completion of this polynomial ring with respect to + the irreducible polynomial ``p``. + + INPUT: + + - ``p`` (default: ``None``) -- an irreduclible polynomial or + ``Infiniity``; if ``None``, the generator of this polynomial + ring + + - ``prec`` (default: 20) -- an integer or ``Infinity``; if + ``Infinity``, return a + :class:`sage.rings.lazy_series_ring.LazyPowerSeriesRing`. + + - ``extras`` (default: ``None``) -- ignored; for compatibility + with the construction mecanism + + - ``names`` (default: ``None``) -- a tuple of strings containing + + - the variable name; if not given and the completion is at `0`, + the name of the variable is this polynomial ring is reused + + - the variable name for the residue field (only relevant for + places of degree at least `2`) + + The argument ``names`` is usually implicitly given by the `.<...>` + syntactic sugar (see examples below). + + EXAMPLES:: + + sage: P. = PolynomialRing(QQ) + sage: P + Univariate Polynomial Ring in x over Rational Field + + Without any argument, this method constructs the completion at + the ideal `(x)`:: + + sage: PP = P.completion() + sage: PP + Power Series Ring in x over Rational Field + sage: f = 1 - x + sage: PP(f) + 1 - x + sage: 1 / f + -1/(x - 1) + sage: g = 1 / PP(f); g + 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + x^10 + x^11 + + x^12 + x^13 + x^14 + x^15 + x^16 + x^17 + x^18 + x^19 + O(x^20) + sage: 1 / g + 1 - x + O(x^20) + + We can construct the completion at other ideals by passing in an + irreducible polynomial. In this case, we should also provide a name + for the uniformizer (set to be `x - a` where `a` is a root of the + given polynomial):: + + sage: C1. = P.completion(x - 1) + sage: C1 + Power Series Ring in u over Rational Field + + A coercion map from the polynomial ring to its completion is set:: + + sage: x - u + 1 + + It is possible to complete at an ideal generated by a polynomial + of higher degree. In this case, we should nevertheless provide an + extra name for the generator of the residue field:: + + sage: C2. = P.completion(x^2 + x + 1) + sage: C2 + Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 + sage: a^2 + a + 1 + 0 + sage: x - v + a + + Constructing the completion at the place of infinity also works:: + + sage: C3. = P.completion(infinity) + sage: C3 + Laurent Series Ring in w over Rational Field + sage: C3(x) + w^-1 + + When the precision is infinity, a lazy series ring is returned:: + + sage: # needs sage.combinat + sage: PP = P.completion(x, prec=oo); PP + Lazy Taylor Series Ring in x over Rational Field + sage: g = 1 / PP(f); g + 1 + x + x^2 + O(x^3) + sage: 1 / g == f + True + + TESTS:: + + sage: P.completion('x') + Power Series Ring in x over Rational Field + sage: P.completion('y') + Power Series Ring in y over Rational Field + sage: Pz. = P.completion('y') + Traceback (most recent call last): + ... + ValueError: conflict of variable names + """ + if p is infinity: + ring = self.fraction_field() + else: + ring = self + incl = morphism_to_completion(ring, p, prec, names) + C = incl.codomain() + if C.has_coerce_map_from(ring): + x = ring.gen() + if C(x) != incl(x): + raise ValueError("a different coercion map is already set; try to change the variable name") + else: + C.register_coercion(incl) + if not (ring is self and C.has_coerce_map_from(self)): + C.register_coercion(incl * ring.coerce_map_from(self)) + return C + def quotient_by_principal_ideal(self, f, names=None, **kwds): """ Return the quotient of this polynomial ring by the principal From 7468d4a6f7aea43460f57b79b27e8e78b69493b2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 14:33:18 +0200 Subject: [PATCH 09/59] typo --- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index ab1fa3be791..4e3c67bb3ff 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1896,7 +1896,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): INPUT: - ``p`` (default: ``None``) -- an irreduclible polynomial or - ``Infiniity``; if ``None``, the generator of this polynomial + ``Infinity``; if ``None``, the generator of this polynomial ring - ``prec`` (default: 20) -- an integer or ``Infinity``; if From 779cb91fef0ddb7737192c3aef9788ebe84cd246 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 20 Aug 2025 14:43:03 +0200 Subject: [PATCH 10/59] documentation of the function morphism_to_completion --- src/sage/rings/polynomial/polynomial_ring.py | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 4e3c67bb3ff..4b84dd63be3 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1789,21 +1789,31 @@ def morphism_to_completion(domain, place, prec, names): - the variable name for the residue field (only relevant for places of degree at least `2`) - TESTS:: + EXAMPLES:: - sage: A. = ZZ[] - sage: B. = A.completion(2*t + 1) - Traceback (most recent call last): - ... - NotImplementedError: the leading coefficient of the place is not a unit + sage: from sage.rings.polynomial.polynomial_ring import morphism_to_completion + sage: A. = QQ[] + sage: morphism_to_completion(A, t, 20, None) + Ring morphism: + From: Univariate Polynomial Ring in t over Rational Field + To: Power Series Ring in t over Rational Field + Defn: t |--> t :: - sage: A. = QQ[] - sage: B. = A.completion(x^2 + 2*x + 1) + sage: morphism_to_completion(A, t^2 + t + 1, 20, ('u','a')) + Ring morphism: + From: Univariate Polynomial Ring in t over Rational Field + To: Power Series Ring in u over Number Field in a with defining polynomial t^2 + t + 1 + Defn: t |--> a + u + + TESTS:: + + sage: A. = ZZ[] + sage: morphism_to_completion(A, 2*t + 1, 20, ('u',)) Traceback (most recent call last): ... - ValueError: place must be Infinity or an irreducible polynomial + NotImplementedError: the leading coefficient of the place is not a unit """ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.laurent_series_ring import LaurentSeriesRing @@ -1832,7 +1842,7 @@ def morphism_to_completion(domain, place, prec, names): elif isinstance(place, str): if name is not None and name != place: raise ValueError("conflict of variable names") - name = p + name = place place = x if place == x and name is None: From aa9e296d4619a1b1901c97f0e831a0a28254ca69 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 24 Aug 2025 15:43:33 +0200 Subject: [PATCH 11/59] back to the initial implementation of _first_ngens --- src/sage/structure/category_object.pyx | 32 ++++---------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/sage/structure/category_object.pyx b/src/sage/structure/category_object.pyx index 880ebf51cb8..aaf0c285322 100644 --- a/src/sage/structure/category_object.pyx +++ b/src/sage/structure/category_object.pyx @@ -370,37 +370,13 @@ cdef class CategoryObject(SageObject): sage: B. = EquationOrder(x^2 + 3) # needs sage.rings.number_field sage: z.minpoly() # needs sage.rings.number_field x^2 + 3 - - If the ring has less than `n` generators, the generators - of the base rings are appended recursively:: - - sage: S. = PolynomialRing(R) - sage: T. = PolynomialRing(S) - sage: T._first_ngens(1) - (z,) - sage: T._first_ngens(2) - (z, y) - sage: T._first_ngens(3) - (z, y, x) """ names = self._defining_names() - if not isinstance(names, (list, tuple)): - # case of Family - it = iter(names) - names = [] - for _ in range(n): - try: - names.append(next(it)) - except StopIteration: - break - names = tuple(names) - m = n - len(names) - if m <= 0: + if isinstance(names, (list, tuple)): return names[:n] - else: - if hasattr(self, 'base'): - names += tuple(self(x) for x in self.base()._first_ngens(m)) - return names + # case of Family + it = iter(names) + return tuple(next(it) for i in range(n)) @cached_method def _defining_names(self): From 5388244583f5febd4be268f8f4951e6ae02887ac Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 24 Aug 2025 21:03:17 +0200 Subject: [PATCH 12/59] gives generator to completion functor --- src/sage/rings/power_series_ring.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index a3bdbd178d1..a6d5ea599e8 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -915,7 +915,8 @@ def construction(self): extras = {'sparse': True} else: extras = None - return CompletionFunctor(self._names[0], self.default_prec(), extras), self._poly_ring() + A = self._poly_ring() + return CompletionFunctor(A.gen(), self.default_prec(), extras), A def _coerce_impl(self, x): """ From a5d3d7d6f49aef5c81f92ab42ffd9a8e953fef2e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 25 Aug 2025 10:19:12 +0200 Subject: [PATCH 13/59] a specific class for completions --- src/sage/rings/completion.py | 70 ++++++++++++++++++++ src/sage/rings/polynomial/polynomial_ring.py | 19 ++---- 2 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 src/sage/rings/completion.py diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py new file mode 100644 index 00000000000..269495b6dc9 --- /dev/null +++ b/src/sage/rings/completion.py @@ -0,0 +1,70 @@ +from sage.misc.cachefunc import cached_method +from sage.structure.parent import Parent + +from sage.rings.infinity import Infinity +from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic +from sage.rings.power_series_ring import PowerSeriesRing, PowerSeriesRing_generic +from sage.rings.power_series_ring_element import PowerSeries + + +class CompletionPolynomialRing(PowerSeriesRing_generic): + def __classcall_private__(cls, A, p, default_prec=20, names=None, sparse=False): + if not isinstance(A, PolynomialRing_generic): + raise ValueError("not a polynomial ring") + if p == A.gen(): + return PowerSeriesRing(A.base_ring(), default_prec=default_prec, + names=A.variable_name(), sparse=sparse) + lc = p.leading_coefficient() + if not lc.is_unit(): + raise NotImplementedError("leading coefficient must be a unit") + p = lc.inverse_of_unit() * p + if not isinstance(names, (list, tuple)): + names = (names,) + if p.degree() == 0: + raise ValueError("place must be an irreducible polynomial") + if p.degree() == 1: + if len(names) != 1: + raise ValueError("you should provide a variable name") + else: + if len(names) != 2: + raise ValueError("you should provide two variable names") + return cls.__classcall__(cls, A, p, default_prec, names) + + def __init__(self, A, p, default_prec, names): + name_uniformizer = names[0] + name_residue = None + if len(names) > 1: + name_residue = names[1] + base = A.base_ring() + self._polynomial_ring = A + self._place = p + if p.degree() > 1: + k = base.extension(p, names=name_residue) + a = k.gen() + else: + k = base + a = -p[0] + super().__init__(k, name_uniformizer, default_prec) + self._a = a + self._inject = A.hom([self.gen() + a]) + self.register_coercion(self._inject) + + def _repr_(self): + A = self._polynomial_ring + s = "Completion of %s at %s:\n" % (A, self._place) + s += "Power Series Ring in %s = %s - %s over %s" % (self.gen(), A.gen(), self._a, self.residue_field()) + return s + + def construction(self): + from sage.categories.pushout import CompletionFunctor + extras = { + 'names': [str(x) for x in self._defining_names()], + 'sparse': self.is_sparse() + } + return CompletionFunctor(self._place, self.default_prec(), extras), self._poly_ring() + + def _defining_names(self): + if self._place.degree() > 1: + return (self.gen(), self(self.base_ring().gen())) + else: + return (self.gen()) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 4b84dd63be3..162e3368742 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2006,20 +2006,11 @@ def completion(self, p=None, prec=20, extras=None, names=None): ValueError: conflict of variable names """ if p is infinity: - ring = self.fraction_field() - else: - ring = self - incl = morphism_to_completion(ring, p, prec, names) - C = incl.codomain() - if C.has_coerce_map_from(ring): - x = ring.gen() - if C(x) != incl(x): - raise ValueError("a different coercion map is already set; try to change the variable name") - else: - C.register_coercion(incl) - if not (ring is self and C.has_coerce_map_from(self)): - C.register_coercion(incl * ring.coerce_map_from(self)) - return C + raise NotImplementedError + from sage.rings.completion import CompletionPolynomialRing + if extras is not None and 'names' in extras: + names = extras['names'] + return CompletionPolynomialRing(self, p, sparse=self.is_sparse(), names=names) def quotient_by_principal_ideal(self, f, names=None, **kwds): """ From 679114559d63bcf93faaf02caf27b13729056bc1 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 26 Aug 2025 15:12:51 +0200 Subject: [PATCH 14/59] implementation using RingExtension --- src/sage/rings/completion.py | 166 ++++++++++++++----- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 2 files changed, 129 insertions(+), 39 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 269495b6dc9..5dfa5961034 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -1,58 +1,124 @@ from sage.misc.cachefunc import cached_method -from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + +from sage.categories.fields import Fields from sage.rings.infinity import Infinity from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic -from sage.rings.power_series_ring import PowerSeriesRing, PowerSeriesRing_generic -from sage.rings.power_series_ring_element import PowerSeries +from sage.rings.power_series_ring import PowerSeriesRing + +from sage.rings.ring_extension import RingExtension_generic +from sage.rings.ring_extension_element import RingExtensionElement + + +class CompletionPolynomial(RingExtensionElement): + def _repr_(self): + prec = self.precision_absolute() + + # Uniformizer + u = self.parent()._place + if u._is_atomic(): + unif = str(u) + else: + unif = "(%s)" % u + + # Bigoh + if prec is Infinity: + prec = self.parent().default_prec() + bigoh = "..." + elif prec == 0: + bigoh = "O(1)" + elif prec == 1: + bigoh = "O(%s)" % unif + else: + bigoh = "O(%s^%s)" % (unif, prec) + + S = self.parent() + A = S._base + incl = S._incl + v = S._place.derivative()(S._a).inverse() + w = v.parent().one() + uu = A.one() + elt = self.backend(force=True) + terms = [] + is_exact = False + for i in range(prec): + if elt.precision_absolute() is Infinity and elt.is_zero(): + is_exact = True + break + coeff = A((w * elt[i]).polynomial()) + elt -= incl(uu * coeff) + uu *= u + w *= v + if coeff.is_zero(): + continue + if coeff.is_one(): + if i == 0: + term = "1" + elif i == 1: + term = unif + else: + term = "%s^%s" % (unif, i) + else: + if coeff._is_atomic(): + coeff = str(coeff) + else: + coeff = "(%s)" % coeff + if i == 0: + term = coeff + elif i == 1: + term = "%s*%s" % (coeff, unif) + else: + term = "%s*%s^%s" % (coeff, unif, i) + terms.append(term) + if len(terms) == 0 and bigoh == "...": + terms = ["0"] + if not is_exact: + terms.append(bigoh) + return " + ".join(terms) + def teichmuller(self): + elt = self.backend(force=True) + lift = elt.parent()(elt[0]) + return self.parent()(lift) -class CompletionPolynomialRing(PowerSeriesRing_generic): - def __classcall_private__(cls, A, p, default_prec=20, names=None, sparse=False): + +class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): + Element = CompletionPolynomial + + def __classcall_private__(cls, A, p, default_prec=20, sparse=False): if not isinstance(A, PolynomialRing_generic): raise ValueError("not a polynomial ring") if p == A.gen(): return PowerSeriesRing(A.base_ring(), default_prec=default_prec, names=A.variable_name(), sparse=sparse) - lc = p.leading_coefficient() - if not lc.is_unit(): - raise NotImplementedError("leading coefficient must be a unit") - p = lc.inverse_of_unit() * p - if not isinstance(names, (list, tuple)): - names = (names,) - if p.degree() == 0: - raise ValueError("place must be an irreducible polynomial") - if p.degree() == 1: - if len(names) != 1: - raise ValueError("you should provide a variable name") - else: - if len(names) != 2: - raise ValueError("you should provide two variable names") - return cls.__classcall__(cls, A, p, default_prec, names) - - def __init__(self, A, p, default_prec, names): - name_uniformizer = names[0] - name_residue = None - if len(names) > 1: - name_residue = names[1] + if not A.base_ring() in Fields(): + raise NotImplementedError + if not p.is_irreducible(): + raise ValueError("the place must be an irreducible polynomial") + p = p.monic() + return cls.__classcall__(cls, A, p, default_prec, sparse) + + def __init__(self, A, p, default_prec, sparse): base = A.base_ring() - self._polynomial_ring = A self._place = p if p.degree() > 1: - k = base.extension(p, names=name_residue) + self._extension = True + k = base.extension(p, names='a') a = k.gen() else: + self._extension = False k = base a = -p[0] - super().__init__(k, name_uniformizer, default_prec) + backend = PowerSeriesRing(k, 'u', sparse=sparse) + self._gen = backend.gen() + a + self._incl = A.hom([self._gen]) + super().__init__(self._incl) self._a = a - self._inject = A.hom([self.gen() + a]) - self.register_coercion(self._inject) def _repr_(self): - A = self._polynomial_ring - s = "Completion of %s at %s:\n" % (A, self._place) - s += "Power Series Ring in %s = %s - %s over %s" % (self.gen(), A.gen(), self._a, self.residue_field()) + A = self._base + s = "Completion of %s at %s" % (A, self._place) return s def construction(self): @@ -63,8 +129,32 @@ def construction(self): } return CompletionFunctor(self._place, self.default_prec(), extras), self._poly_ring() - def _defining_names(self): - if self._place.degree() > 1: - return (self.gen(), self(self.base_ring().gen())) + @cached_method + def uniformizer(self): + return self(self._place) + + def gen(self): + return self._gen + + def residue_field(self, names=None): + base = self._base.base_ring() + if self._extension: + if names is None: + raise ValueError("you must give a variable name") + return base.extension(self._place, names=names) + else: + return base + + def power_series(self, sparse=False, names=None): + if not isinstance(names, (list, tuple)): + names = [names] + base = self._base.base_ring() + if self._extension: + if len(names) < 2: + raise ValueError("you must give variable names for the uniformizer and the generator of the residue field") + k = self.residue_field(names[1]) else: - return (self.gen()) + if len(names) < 1: + raise ValueError("you must give variable names for the uniformizer") + k = self.residue_field() + return PowerSeriesRing(k, names[0], sparse=sparse) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 162e3368742..f9d62375067 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2010,7 +2010,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): from sage.rings.completion import CompletionPolynomialRing if extras is not None and 'names' in extras: names = extras['names'] - return CompletionPolynomialRing(self, p, sparse=self.is_sparse(), names=names) + return CompletionPolynomialRing(self, p, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): """ From dede4c3cb6c5dcb884506ef9e187184833b65349 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 26 Aug 2025 15:22:15 +0200 Subject: [PATCH 15/59] fix default precision --- src/sage/rings/completion.py | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 5dfa5961034..e91e2cd362a 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -110,7 +110,7 @@ def __init__(self, A, p, default_prec, sparse): self._extension = False k = base a = -p[0] - backend = PowerSeriesRing(k, 'u', sparse=sparse) + backend = PowerSeriesRing(k, 'u', default_prec=default_prec, sparse=sparse) self._gen = backend.gen() + a self._incl = A.hom([self._gen]) super().__init__(self._incl) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index f9d62375067..0fe72ad7121 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -2010,7 +2010,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): from sage.rings.completion import CompletionPolynomialRing if extras is not None and 'names' in extras: names = extras['names'] - return CompletionPolynomialRing(self, p, sparse=self.is_sparse()) + return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): """ From e31740c00354cbd2678421c4493c90ae3ceb35fe Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 27 Aug 2025 10:41:53 +0200 Subject: [PATCH 16/59] use quotient instead of extension --- src/sage/rings/completion.py | 118 ++++++++------ src/sage/rings/polynomial/polynomial_ring.py | 160 ++----------------- src/sage/rings/power_series_ring.py | 8 +- 3 files changed, 80 insertions(+), 206 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index e91e2cd362a..fbb7a54303f 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -3,6 +3,7 @@ from sage.categories.fields import Fields +from sage.rings.morphism import RingHomomorphism from sage.rings.infinity import Infinity from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic from sage.rings.power_series_ring import PowerSeriesRing @@ -11,6 +12,14 @@ from sage.rings.ring_extension_element import RingExtensionElement +class CompletionToPowerSeries(RingHomomorphism): + def __init__(self, parent): + super().__init__(parent) + + def _call_(self, x): + return self.codomain()(x.backend(force=True)) + + class CompletionPolynomial(RingExtensionElement): def _repr_(self): prec = self.precision_absolute() @@ -33,23 +42,15 @@ def _repr_(self): else: bigoh = "O(%s^%s)" % (unif, prec) - S = self.parent() - A = S._base - incl = S._incl - v = S._place.derivative()(S._a).inverse() - w = v.parent().one() - uu = A.one() - elt = self.backend(force=True) - terms = [] + E = self.expansion(include_final_zeroes=False) is_exact = False + terms = [] for i in range(prec): - if elt.precision_absolute() is Infinity and elt.is_zero(): + try: + coeff = next(E) + except StopIteration: is_exact = True break - coeff = A((w * elt[i]).polynomial()) - elt -= incl(uu * coeff) - uu *= u - w *= v if coeff.is_zero(): continue if coeff.is_one(): @@ -77,6 +78,28 @@ def _repr_(self): terms.append(bigoh) return " + ".join(terms) + def expansion(self, include_final_zeroes=True): + S = self.parent() + A = S._base + incl = S._incl + u = self.parent()._place + v = S._place.derivative()(S._xbar).inverse() + vv = v.parent().one() + uu = A.one() + elt = self.backend(force=True) + prec = self.precision_absolute() + n = 0 + while n < prec: + if (not include_final_zeroes + and elt.precision_absolute() is Infinity and elt.is_zero()): + break + coeff = (vv * elt[n]).lift() + yield coeff + elt -= incl(uu * coeff) + uu *= u + vv *= v + n += 1 + def teichmuller(self): elt = self.backend(force=True) lift = elt.parent()(elt[0]) @@ -89,36 +112,35 @@ class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): def __classcall_private__(cls, A, p, default_prec=20, sparse=False): if not isinstance(A, PolynomialRing_generic): raise ValueError("not a polynomial ring") - if p == A.gen(): + if isinstance(p, str): return PowerSeriesRing(A.base_ring(), default_prec=default_prec, - names=A.variable_name(), sparse=sparse) + names=p, sparse=sparse) if not A.base_ring() in Fields(): raise NotImplementedError - if not p.is_irreducible(): - raise ValueError("the place must be an irreducible polynomial") + try: + p = p.squarefree_part() + except (AttributeError, NotImplementedError): + pass p = p.monic() return cls.__classcall__(cls, A, p, default_prec, sparse) def __init__(self, A, p, default_prec, sparse): + self._A = A base = A.base_ring() self._place = p - if p.degree() > 1: - self._extension = True - k = base.extension(p, names='a') - a = k.gen() - else: - self._extension = False - k = base - a = -p[0] - backend = PowerSeriesRing(k, 'u', default_prec=default_prec, sparse=sparse) - self._gen = backend.gen() + a + self._residue_ring = k = A.quotient(p) + self._xbar = xbar = k(A.gen()) + name = "u_%s" % id(self) + backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + self._gen = backend.gen() + xbar self._incl = A.hom([self._gen]) - super().__init__(self._incl) - self._a = a + self._base_morphism = self._incl * A.coerce_map_from(base) + super().__init__(self._base_morphism) + coerce = A.Hom(self)(self._incl) + self.register_coercion(coerce) def _repr_(self): - A = self._base - s = "Completion of %s at %s" % (A, self._place) + s = "Completion of %s at %s" % (self._A, self._place) return s def construction(self): @@ -127,7 +149,7 @@ def construction(self): 'names': [str(x) for x in self._defining_names()], 'sparse': self.is_sparse() } - return CompletionFunctor(self._place, self.default_prec(), extras), self._poly_ring() + return CompletionFunctor(self._place, self.default_prec(), extras), self._A @cached_method def uniformizer(self): @@ -136,25 +158,17 @@ def uniformizer(self): def gen(self): return self._gen - def residue_field(self, names=None): - base = self._base.base_ring() - if self._extension: - if names is None: - raise ValueError("you must give a variable name") - return base.extension(self._place, names=names) - else: - return base + def residue_ring(self): + return self._residue_ring + + residue_field = residue_ring - def power_series(self, sparse=False, names=None): - if not isinstance(names, (list, tuple)): - names = [names] + def power_series(self, names=None, sparse=None): + if isinstance(names, (list, tuple)): + names = names[0] + if sparse is None: + sparse = self.is_sparse() base = self._base.base_ring() - if self._extension: - if len(names) < 2: - raise ValueError("you must give variable names for the uniformizer and the generator of the residue field") - k = self.residue_field(names[1]) - else: - if len(names) < 1: - raise ValueError("you must give variable names for the uniformizer") - k = self.residue_field() - return PowerSeriesRing(k, names[0], sparse=sparse) + S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + S.register_conversion(CompletionToPowerSeries(self.Hom(S))) + return S diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 0fe72ad7121..a2e96a72163 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1768,118 +1768,6 @@ def monics(self, of_degree=None, max_degree=None): PolynomialRing_general = PolynomialRing_generic -def morphism_to_completion(domain, place, prec, names): - r""" - Return the inclusion morphism from ``domain`` to its completion - at the place ``place``. - - INPUT: - - - ``domain`` -- a polynomial ring of its fraction field - - - ``place`` -- an irreducible polynomial or ``Infinity`` - - - ``prec`` -- an integer or ``Infinity`` - - - ``names`` -- a tuple of strings containing - - - the variable name; if not given and the completion is at `0`, - the name of the variable is this polynomial ring is reused - - - the variable name for the residue field (only relevant for - places of degree at least `2`) - - EXAMPLES:: - - sage: from sage.rings.polynomial.polynomial_ring import morphism_to_completion - sage: A. = QQ[] - sage: morphism_to_completion(A, t, 20, None) - Ring morphism: - From: Univariate Polynomial Ring in t over Rational Field - To: Power Series Ring in t over Rational Field - Defn: t |--> t - - :: - - sage: morphism_to_completion(A, t^2 + t + 1, 20, ('u','a')) - Ring morphism: - From: Univariate Polynomial Ring in t over Rational Field - To: Power Series Ring in u over Number Field in a with defining polynomial t^2 + t + 1 - Defn: t |--> a + u - - TESTS:: - - sage: A. = ZZ[] - sage: morphism_to_completion(A, 2*t + 1, 20, ('u',)) - Traceback (most recent call last): - ... - NotImplementedError: the leading coefficient of the place is not a unit - """ - from sage.rings.power_series_ring import PowerSeriesRing - from sage.rings.laurent_series_ring import LaurentSeriesRing - if domain.is_field(): - ring = domain.ring() - SeriesRing = LaurentSeriesRing - else: - ring = domain - SeriesRing = PowerSeriesRing - k = base = ring.base_ring() - x = ring.gen() - sparse = ring.is_sparse() - - # Parse names - name = residue_name = None - if names is not None: - if not isinstance(names, (list, tuple)): - raise TypeError("names must be a list or a tuple") - name = names[0] - if len(names) > 1: - residue_name = names[1] - - # Parse place - if place is None: - place = x - elif isinstance(place, str): - if name is not None and name != place: - raise ValueError("conflict of variable names") - name = place - place = x - - if place == x and name is None: - name = ring.variable_name() - - if place is infinity: - pass - elif place in ring: - place = ring(place) - if place.leading_coefficient().is_unit(): - place = ring(place.monic()) - # We do not check irreducibility; it causes too much troubles: - # it can be long, be not implemented and even sometimes fail - else: - raise NotImplementedError("the leading coefficient of the place is not a unit") - else: - raise ValueError("place must be Infinity or an irreducible polynomial") - - # Construct the completion - if place is infinity: - codomain = LaurentSeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.one() >> 1 - elif place == ring.gen() or place.degree() == 1: # sometimes ring.gen() has not degree 1 - codomain = SeriesRing(base, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() - place[0] - else: - if residue_name is None: - raise ValueError("you must specify a variable name for the residue field") - residue_name = normalize_names(1, residue_name) - k = base.extension(place, names=residue_name) - codomain = SeriesRing(k, names=name, default_prec=prec, sparse=sparse) - image = codomain.gen() + k.gen() - - # Return the morphism - return domain.hom([image]) - - class PolynomialRing_commutative(PolynomialRing_generic): """ Univariate polynomial ring over a commutative ring. @@ -1898,7 +1786,7 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sparse=sparse, implementation=implementation, element_class=element_class, category=category) - def completion(self, p=None, prec=20, extras=None, names=None): + def completion(self, p=None, prec=20, extras=None): r""" Return the completion of this polynomial ring with respect to the irreducible polynomial ``p``. @@ -1906,8 +1794,7 @@ def completion(self, p=None, prec=20, extras=None, names=None): INPUT: - ``p`` (default: ``None``) -- an irreduclible polynomial or - ``Infinity``; if ``None``, the generator of this polynomial - ring + ``Infinity`` - ``prec`` (default: 20) -- an integer or ``Infinity``; if ``Infinity``, return a @@ -1916,25 +1803,14 @@ def completion(self, p=None, prec=20, extras=None, names=None): - ``extras`` (default: ``None``) -- ignored; for compatibility with the construction mecanism - - ``names`` (default: ``None``) -- a tuple of strings containing - - - the variable name; if not given and the completion is at `0`, - the name of the variable is this polynomial ring is reused - - - the variable name for the residue field (only relevant for - places of degree at least `2`) - - The argument ``names`` is usually implicitly given by the `.<...>` - syntactic sugar (see examples below). - EXAMPLES:: sage: P. = PolynomialRing(QQ) sage: P Univariate Polynomial Ring in x over Rational Field - Without any argument, this method constructs the completion at - the ideal `(x)`:: + Without any argument, this method returns the power series ring + with the same variable name:: sage: PP = P.completion() sage: PP @@ -1951,30 +1827,14 @@ def completion(self, p=None, prec=20, extras=None, names=None): 1 - x + O(x^20) We can construct the completion at other ideals by passing in an - irreducible polynomial. In this case, we should also provide a name - for the uniformizer (set to be `x - a` where `a` is a root of the - given polynomial):: + irreducible polynomial:: - sage: C1. = P.completion(x - 1) + sage: C1 = P.completion(x - 1) sage: C1 - Power Series Ring in u over Rational Field - - A coercion map from the polynomial ring to its completion is set:: - - sage: x - u - 1 - - It is possible to complete at an ideal generated by a polynomial - of higher degree. In this case, we should nevertheless provide an - extra name for the generator of the residue field:: - - sage: C2. = P.completion(x^2 + x + 1) + Completion of Univariate Polynomial Ring in x over Rational Field at x - 1 + sage: C2 = P.completion(x^2 + x + 1) sage: C2 - Power Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 - sage: a^2 + a + 1 - 0 - sage: x - v - a + Completion of Univariate Polynomial Ring in x over Rational Field at x^2 + x + 1 Constructing the completion at the place of infinity also works:: @@ -2007,6 +1867,8 @@ def completion(self, p=None, prec=20, extras=None, names=None): """ if p is infinity: raise NotImplementedError + if p is None: + p = self.variable_name() from sage.rings.completion import CompletionPolynomialRing if extras is not None and 'names' in extras: names = extras['names'] diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index a6d5ea599e8..f272e71e699 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -396,10 +396,9 @@ def PowerSeriesRing(base_ring, name=None, arg2=None, names=None, default_prec = name if names is not None: name = names - name = normalize_names(1, name) - if name is None: - raise TypeError("You must specify the name of the indeterminate of the Power series ring.") + raise TypeError("you must specify the name of the indeterminate of the power series ring") + name = normalize_names(1, name) key = (base_ring, name, default_prec, sparse, implementation) if PowerSeriesRing_generic.__classcall__.is_in_cache(key): @@ -915,8 +914,7 @@ def construction(self): extras = {'sparse': True} else: extras = None - A = self._poly_ring() - return CompletionFunctor(A.gen(), self.default_prec(), extras), A + return CompletionFunctor(self._names[0], self.default_prec(), extras), self._poly_ring() def _coerce_impl(self, x): """ From d0bf69c042e1ce7bba4f7dff0cb4bdd9057faede Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 27 Aug 2025 10:48:56 +0200 Subject: [PATCH 17/59] set up correctly the generator --- src/sage/rings/completion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index fbb7a54303f..64bb6f3c86a 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -132,10 +132,11 @@ def __init__(self, A, p, default_prec, sparse): self._xbar = xbar = k(A.gen()) name = "u_%s" % id(self) backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - self._gen = backend.gen() + xbar - self._incl = A.hom([self._gen]) + gen = backend.gen() + xbar + self._incl = A.hom([gen]) self._base_morphism = self._incl * A.coerce_map_from(base) super().__init__(self._base_morphism) + self._gen = self(gen) coerce = A.Hom(self)(self._incl) self.register_coercion(coerce) From b2ee24f55aa1b4040139b4b386fa0c611ec648f7 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 27 Aug 2025 19:32:36 +0200 Subject: [PATCH 18/59] completion of rational function field --- src/sage/rings/completion.py | 119 ++++++++++++++----- src/sage/rings/fraction_field.py | 18 +-- src/sage/rings/polynomial/polynomial_ring.py | 4 - src/sage/rings/ring_extension.pyx | 3 +- 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 64bb6f3c86a..9bac661bd1e 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -5,8 +5,11 @@ from sage.rings.morphism import RingHomomorphism from sage.rings.infinity import Infinity + from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic +from sage.rings.fraction_field import FractionField_1poly_field from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.laurent_series_ring import LaurentSeriesRing from sage.rings.ring_extension import RingExtension_generic from sage.rings.ring_extension_element import RingExtensionElement @@ -22,16 +25,16 @@ def _call_(self, x): class CompletionPolynomial(RingExtensionElement): def _repr_(self): - prec = self.precision_absolute() - # Uniformizer - u = self.parent()._place + S = self.parent() + u = S._p if u._is_atomic(): unif = str(u) else: unif = "(%s)" % u # Bigoh + prec = self.precision_absolute() if prec is Infinity: prec = self.parent().default_prec() bigoh = "..." @@ -45,7 +48,13 @@ def _repr_(self): E = self.expansion(include_final_zeroes=False) is_exact = False terms = [] - for i in range(prec): + if S._integer_ring is S: + start = 0 + else: + start = self.valuation() + if start is Infinity: + return "0" + for i in range(start, prec): try: coeff = next(E) except StopIteration: @@ -79,16 +88,25 @@ def _repr_(self): return " + ".join(terms) def expansion(self, include_final_zeroes=True): + # TODO: improve performance S = self.parent() A = S._base incl = S._incl - u = self.parent()._place - v = S._place.derivative()(S._xbar).inverse() - vv = v.parent().one() - uu = A.one() + u = self.parent()._p + v = S._p.derivative()(S._xbar).inverse() elt = self.backend(force=True) prec = self.precision_absolute() n = 0 + if S._integer_ring is not S: + val = elt.valuation() + if val is Infinity: + return + if val < 0: + elt *= incl(u) ** (-val) + else: + n = val + vv = v**n + uu = u**n while n < prec: if (not include_final_zeroes and elt.precision_absolute() is Infinity and elt.is_zero()): @@ -105,43 +123,67 @@ def teichmuller(self): lift = elt.parent()(elt[0]) return self.parent()(lift) + def shift(self, n): + raise NotImplementedError + class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): Element = CompletionPolynomial - def __classcall_private__(cls, A, p, default_prec=20, sparse=False): - if not isinstance(A, PolynomialRing_generic): - raise ValueError("not a polynomial ring") - if isinstance(p, str): - return PowerSeriesRing(A.base_ring(), default_prec=default_prec, - names=p, sparse=sparse) + def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + if isinstance(ring, PolynomialRing_generic): + if isinstance(p, str): + return PowerSeriesRing(ring.base_ring(), + default_prec=default_prec, + names=p, sparse=sparse) + A = ring + elif isinstance(ring, FractionField_1poly_field): + if isinstance(p, str): + return LaurentSeriesRing(ring.base_ring(), + default_prec=default_prec, + names=p, sparse=sparse) + A = ring.ring() + else: + raise ValueError("not a polynomial ring or a rational function field") if not A.base_ring() in Fields(): raise NotImplementedError + p = A(p) try: p = p.squarefree_part() except (AttributeError, NotImplementedError): pass p = p.monic() - return cls.__classcall__(cls, A, p, default_prec, sparse) + return cls.__classcall__(cls, ring, p, default_prec, sparse) - def __init__(self, A, p, default_prec, sparse): - self._A = A + def __init__(self, ring, p, default_prec, sparse): + A = self._ring = ring + if not isinstance(A, PolynomialRing_generic): + A = A.ring() base = A.base_ring() - self._place = p + self._p = p + # Construct backend self._residue_ring = k = A.quotient(p) self._xbar = xbar = k(A.gen()) - name = "u_%s" % id(self) - backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - gen = backend.gen() + xbar - self._incl = A.hom([gen]) - self._base_morphism = self._incl * A.coerce_map_from(base) - super().__init__(self._base_morphism) - self._gen = self(gen) - coerce = A.Hom(self)(self._incl) - self.register_coercion(coerce) + name = "u_%s" % hash(self._p) + if isinstance(ring, PolynomialRing_generic): + self._integer_ring = self + name = "u_%s" % id(self) + backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + else: + self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) + name = "u_%s" % id(self._integer_ring) + backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base)) + # Set generator + x = backend.gen() + xbar + self._gen = self(x) + # Set coercions + self._incl = ring.hom([x]) + self.register_coercion(A.Hom(self)(self._incl)) + self.register_coercion(self._integer_ring) def _repr_(self): - s = "Completion of %s at %s" % (self._A, self._place) + s = "Completion of %s at %s" % (self._ring, self._p) return s def construction(self): @@ -150,11 +192,11 @@ def construction(self): 'names': [str(x) for x in self._defining_names()], 'sparse': self.is_sparse() } - return CompletionFunctor(self._place, self.default_prec(), extras), self._A + return CompletionFunctor(self._p, self.default_prec(), extras), self._ring @cached_method def uniformizer(self): - return self(self._place) + return self(self._p) def gen(self): return self._gen @@ -170,6 +212,21 @@ def power_series(self, names=None, sparse=None): if sparse is None: sparse = self.is_sparse() base = self._base.base_ring() - S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + if self._integer_ring is self: + S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + else: + S = LaurentSeriesRing(self._residue_ring, names, sparse=sparse) S.register_conversion(CompletionToPowerSeries(self.Hom(S))) return S + + def integer_ring(self): + return self._integer_ring + + def fraction_field(self): + field = self._ring.fraction_field() + if field is self._ring: + return self + else: + return CompletionPolynomialRing(field, self._p, + default_prec = self.default_prec(), + sparse = self.is_sparse()) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 163d5c46233..c83c73bdeed 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1263,20 +1263,10 @@ def completion(self, p=None, prec=20, names=None): sage: L = K.completion(x, prec=oo); L Lazy Laurent Series Ring in x over Rational Field """ - from sage.rings.polynomial.polynomial_ring import morphism_to_completion - incl = morphism_to_completion(self, p, prec, names) - C = incl.codomain() - if C.has_coerce_map_from(self): - x = self.gen() - if C(x) != incl(x): - raise ValueError("a different coercion map is already set; try to change the variable name") - else: - C.register_coercion(incl) - ring = self.ring() - if not C.has_coerce_map_from(ring): - C.register_coercion(incl * self.coerce_map_from(ring)) - return C - + if p is None: + p = self.variable_name() + from sage.rings.completion import CompletionPolynomialRing + return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.ring().is_sparse()) class FractionFieldEmbedding(DefaultConvertMap_unique): r""" diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index a2e96a72163..5d0f82de77c 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1865,13 +1865,9 @@ def completion(self, p=None, prec=20, extras=None): ... ValueError: conflict of variable names """ - if p is infinity: - raise NotImplementedError if p is None: p = self.variable_name() from sage.rings.completion import CompletionPolynomialRing - if extras is not None and 'names' in extras: - names = extras['names'] return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index 9c83ab605ff..7d1527201e7 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -123,6 +123,7 @@ from sage.structure.element cimport Element from sage.structure.category_object import normalize_names from sage.categories.map cimport Map from sage.categories.commutative_rings import CommutativeRings +from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.rings.integer_ring import ZZ from sage.rings.infinity import Infinity @@ -593,7 +594,7 @@ cdef class RingExtension_generic(Parent): # Some checks if (base not in CommutativeRings() or ring not in CommutativeRings() - or not defining_morphism.category_for().is_subcategory(CommutativeRings())): + or not defining_morphism.category_for().is_subcategory(Rings())): raise TypeError("only commutative rings are accepted") f = ring.Hom(ring).identity() b = self From 4dea431bcdc6d05004d16347a7e8d73af707db8b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 28 Aug 2025 15:30:23 +0200 Subject: [PATCH 19/59] infinite place --- src/sage/rings/completion.py | 157 ++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 65 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 9bac661bd1e..1e20c971055 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -28,10 +28,15 @@ def _repr_(self): # Uniformizer S = self.parent() u = S._p - if u._is_atomic(): - unif = str(u) + if u is Infinity: + step = -1 + unif = S._ring.variable_name() else: - unif = "(%s)" % u + step = 1 + if u._is_atomic(): + unif = str(u) + else: + unif = "(%s)" % u # Bigoh prec = self.precision_absolute() @@ -40,10 +45,10 @@ def _repr_(self): bigoh = "..." elif prec == 0: bigoh = "O(1)" - elif prec == 1: + elif step*prec == 1: bigoh = "O(%s)" % unif else: - bigoh = "O(%s^%s)" % (unif, prec) + bigoh = "O(%s^%s)" % (unif, step*prec) E = self.expansion(include_final_zeroes=False) is_exact = False @@ -54,7 +59,7 @@ def _repr_(self): start = self.valuation() if start is Infinity: return "0" - for i in range(start, prec): + for e in range(step*start, step*prec, step): try: coeff = next(E) except StopIteration: @@ -62,30 +67,26 @@ def _repr_(self): break if coeff.is_zero(): continue - if coeff.is_one(): - if i == 0: - term = "1" - elif i == 1: - term = unif - else: - term = "%s^%s" % (unif, i) + if coeff._is_atomic(): + coeff = str(coeff) else: - if coeff._is_atomic(): - coeff = str(coeff) - else: - coeff = "(%s)" % coeff - if i == 0: - term = coeff - elif i == 1: - term = "%s*%s" % (coeff, unif) - else: - term = "%s*%s^%s" % (coeff, unif, i) + coeff = "(%s)" % coeff + if e == 0: + term = coeff + elif e == 1: + term = "%s*%s" % (coeff, unif) + else: + term = "%s*%s^%s" % (coeff, unif, e) terms.append(term) if len(terms) == 0 and bigoh == "...": terms = ["0"] if not is_exact: terms.append(bigoh) - return " + ".join(terms) + s = " " + " + ".join(terms) + s = s.replace(" + -", " - ") + s = s.replace(" 1*"," ") + s = s.replace(" -1*", " -") + return s[1:] def expansion(self, include_final_zeroes=True): # TODO: improve performance @@ -93,30 +94,40 @@ def expansion(self, include_final_zeroes=True): A = S._base incl = S._incl u = self.parent()._p - v = S._p.derivative()(S._xbar).inverse() elt = self.backend(force=True) prec = self.precision_absolute() n = 0 - if S._integer_ring is not S: - val = elt.valuation() - if val is Infinity: - return - if val < 0: - elt *= incl(u) ** (-val) - else: - n = val - vv = v**n - uu = u**n - while n < prec: - if (not include_final_zeroes - and elt.precision_absolute() is Infinity and elt.is_zero()): - break - coeff = (vv * elt[n]).lift() - yield coeff - elt -= incl(uu * coeff) - uu *= u - vv *= v - n += 1 + if u is Infinity: + n = elt.valuation() + while n < prec: + if (not include_final_zeroes + and elt.precision_absolute() is Infinity and elt.is_zero()): + break + coeff = elt[n] + yield coeff + elt -= elt.parent()(coeff) << n + n += 1 + else: + if S._integer_ring is not S: + val = elt.valuation() + if val is Infinity: + return + if val < 0: + elt *= incl(u) ** (-val) + else: + n = val + vv = v**n + uu = u**n + while n < prec: + if (not include_final_zeroes + and elt.precision_absolute() is Infinity and elt.is_zero()): + break + coeff = (vv * elt[n]).lift() + yield coeff + elt -= incl(uu * coeff) + uu *= u + vv *= v + n += 1 def teichmuller(self): elt = self.backend(force=True) @@ -147,12 +158,13 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): raise ValueError("not a polynomial ring or a rational function field") if not A.base_ring() in Fields(): raise NotImplementedError - p = A(p) - try: - p = p.squarefree_part() - except (AttributeError, NotImplementedError): - pass - p = p.monic() + if p is not Infinity: + p = A(p) + try: + p = p.squarefree_part() + except (AttributeError, NotImplementedError): + pass + p = p.monic() return cls.__classcall__(cls, ring, p, default_prec, sparse) def __init__(self, ring, p, default_prec, sparse): @@ -162,29 +174,39 @@ def __init__(self, ring, p, default_prec, sparse): base = A.base_ring() self._p = p # Construct backend - self._residue_ring = k = A.quotient(p) - self._xbar = xbar = k(A.gen()) - name = "u_%s" % hash(self._p) - if isinstance(ring, PolynomialRing_generic): - self._integer_ring = self + if p is Infinity: + self._ring = ring.fraction_field() + self._residue_ring = k = base + self._integer_ring = None name = "u_%s" % id(self) - backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - else: - self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) - name = "u_%s" % id(self._integer_ring) backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + x = backend.gen().inverse() + else: + self._residue_ring = k = A.quotient(p) + self._xbar = xbar = k(A.gen()) + if isinstance(ring, PolynomialRing_generic): + self._integer_ring = self + name = "u_%s" % id(self) + backend = PowerSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + else: + self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) + name = "u_%s" % id(self._integer_ring) + backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) + x = backend.gen() + xbar super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base)) # Set generator - x = backend.gen() + xbar self._gen = self(x) # Set coercions self._incl = ring.hom([x]) self.register_coercion(A.Hom(self)(self._incl)) - self.register_coercion(self._integer_ring) + if self._integer_ring is not None: + self.register_coercion(self._integer_ring) def _repr_(self): - s = "Completion of %s at %s" % (self._ring, self._p) - return s + if self._p is Infinity: + return "Completion of %s at infinity" % self._ring + else: + return "Completion of %s at %s" % (self._ring, self._p) def construction(self): from sage.categories.pushout import CompletionFunctor @@ -196,7 +218,10 @@ def construction(self): @cached_method def uniformizer(self): - return self(self._p) + if self._p is Infinity: + return self._gen.inverse() + else: + return self(self._p) def gen(self): return self._gen @@ -220,6 +245,8 @@ def power_series(self, names=None, sparse=None): return S def integer_ring(self): + if self._p is Infinity: + raise NotImplementedError return self._integer_ring def fraction_field(self): From 0f8b78d42d98dc2a2cc028020da2c4d06512028d Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 28 Aug 2025 21:09:53 +0200 Subject: [PATCH 20/59] bigoh --- src/sage/categories/pushout.py | 6 +-- src/sage/monoids/trace_monoid.py | 2 +- src/sage/rings/big_oh.py | 41 +++++++++++++++---- src/sage/rings/completion.py | 30 +++++++++----- src/sage/rings/fraction_field.py | 32 +++++---------- src/sage/rings/lazy_series.py | 2 + .../rings/polynomial/polynomial_element.pyx | 15 ++++++- src/sage/rings/polynomial/polynomial_ring.py | 22 ++++------ src/sage/rings/power_series_pari.pyx | 2 +- 9 files changed, 93 insertions(+), 59 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index a8458de23fb..078b4ba4d28 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2466,12 +2466,12 @@ class CompletionFunctor(ConstructionFunctor): True sage: P. = ZZ[] - sage: Px = P.completion(x) # currently the only implemented completion of P + sage: Px = P.completion(x) sage: Px - Power Series Ring in x over Integer Ring + Completion of Univariate Polynomial Ring in x over Integer Ring at x sage: F3 = Px.construction()[0] sage: F3(GF(3)['x']) - Power Series Ring in x over Finite Field of size 3 + Completion of Univariate Polynomial Ring in x over Finite Field of size 3 at x TESTS:: diff --git a/src/sage/monoids/trace_monoid.py b/src/sage/monoids/trace_monoid.py index cb1c036e039..8ab2ff14999 100644 --- a/src/sage/monoids/trace_monoid.py +++ b/src/sage/monoids/trace_monoid.py @@ -864,7 +864,7 @@ def number_of_words(self, length): sage: M.number_of_words(3) # needs sage.graphs 48 """ - psr = PowerSeriesRing(ZZ, default_prec=length + 1) + psr = PowerSeriesRing(ZZ, 'x', default_prec=length + 1) return psr(self.dependence_polynomial()).coefficients()[length] @cached_method diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 0d0f362cb47..7060037371f 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -13,6 +13,8 @@ lazy_import('sage.rings.padics.factory', ['Qp', 'Zp']) from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.fraction_field_element import FractionFieldElement +from sage.rings.fraction_field_FpT import FpTElement try: from .puiseux_series_ring_element import PuiseuxSeries @@ -47,6 +49,16 @@ def O(*x, **kwds): O(x^100) sage: 1/(1+x+O(x^5)) 1 - x + x^2 - x^3 + x^4 + O(x^5) + + Completion at other places also works:: + + sage: x^3 + O((x^2 + 1)^10) + -x + x*(x^2 + 1) + O((x^2 + 1)^10) + sage: x^3 + O(1/x^10) # completion at infinity + x^3 + O(x^-10) + + An example with several variables:: + sage: R. = QQ[[]] sage: 1 + u + v^2 + O(u, v)^5 1 + u + v^2 + O(u, v)^5 @@ -126,11 +138,6 @@ def O(*x, **kwds): :: - sage: R. = QQ[] - sage: O(2*x) - Traceback (most recent call last): - ... - NotImplementedError: completion only currently defined for the maximal ideal (x) sage: R. = LazyPowerSeriesRing(QQ) sage: O(x^5) O(x^5) @@ -169,13 +176,31 @@ def O(*x, **kwds): if isinstance(x, power_series_ring_element.PowerSeries): return x.parent()(0, x.degree(), **kwds) + if isinstance(x, (FractionFieldElement, FpTElement)): + if x.denominator().is_one(): + x = x.numerator() + elif x.numerator().is_one(): + x = x.denominator() + if isinstance(x, Polynomial) and x.is_monomial(): + from sage.rings.infinity import infinity + C = x.parent().completion(infinity) + n = x.degree() + return C.zero().add_bigoh(n) + if isinstance(x, Polynomial): - if x.parent().ngens() != 1: + A = x.parent() + if A.ngens() != 1: raise NotImplementedError("completion only currently defined " "for univariate polynomials") + if x.is_monomial(): + C = A.completion(A.variable_name()) + n = x.degree() if not x.is_monomial(): - raise NotImplementedError("completion only currently defined " - "for the maximal ideal (x)") + p, n = x.perfect_power() + C = A.completion(p) + return C.zero().add_bigoh(n) + + if isinstance(x, (int, Integer, Rational)): # p-adic number diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 1e20c971055..75394271445 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -41,7 +41,7 @@ def _repr_(self): # Bigoh prec = self.precision_absolute() if prec is Infinity: - prec = self.parent().default_prec() + prec = self.parent()._default_prec bigoh = "..." elif prec == 0: bigoh = "O(1)" @@ -56,9 +56,11 @@ def _repr_(self): if S._integer_ring is S: start = 0 else: - start = self.valuation() + start = min(prec, self.valuation()) if start is Infinity: return "0" + if prec is Infinity: + prec = start + 10 # ??? for e in range(step*start, step*prec, step): try: coeff = next(E) @@ -116,6 +118,7 @@ def expansion(self, include_final_zeroes=True): elt *= incl(u) ** (-val) else: n = val + v = S._p.derivative()(S._xbar).inverse() vv = v**n uu = u**n while n < prec: @@ -134,6 +137,9 @@ def teichmuller(self): lift = elt.parent()(elt[0]) return self.parent()(lift) + def valuation(self): + return self.backend(force=True).valuation() + def shift(self, n): raise NotImplementedError @@ -156,15 +162,16 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): A = ring.ring() else: raise ValueError("not a polynomial ring or a rational function field") - if not A.base_ring() in Fields(): - raise NotImplementedError if p is not Infinity: p = A(p) + if not p.leading_coefficient().is_unit(): + raise NotImplementedError("the leading coefficient of p must be invertible") + p = A(p.monic()) try: - p = p.squarefree_part() + if not p.is_squarefree(): + raise ValueError("p must be a squarefree polynomial") except (AttributeError, NotImplementedError): pass - p = p.monic() return cls.__classcall__(cls, ring, p, default_prec, sparse) def __init__(self, ring, p, default_prec, sparse): @@ -173,6 +180,7 @@ def __init__(self, ring, p, default_prec, sparse): A = A.ring() base = A.base_ring() self._p = p + self._default_prec = default_prec # Construct backend if p is Infinity: self._ring = ring.fraction_field() @@ -193,7 +201,7 @@ def __init__(self, ring, p, default_prec, sparse): name = "u_%s" % id(self._integer_ring) backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) x = backend.gen() + xbar - super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base)) + super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base), category=backend.category()) # Set generator self._gen = self(x) # Set coercions @@ -214,7 +222,7 @@ def construction(self): 'names': [str(x) for x in self._defining_names()], 'sparse': self.is_sparse() } - return CompletionFunctor(self._p, self.default_prec(), extras), self._ring + return CompletionFunctor(self._p, self._default_prec, extras), self._ring @cached_method def uniformizer(self): @@ -238,9 +246,9 @@ def power_series(self, names=None, sparse=None): sparse = self.is_sparse() base = self._base.base_ring() if self._integer_ring is self: - S = PowerSeriesRing(self._residue_ring, names, sparse=sparse) + S = PowerSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) else: - S = LaurentSeriesRing(self._residue_ring, names, sparse=sparse) + S = LaurentSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) S.register_conversion(CompletionToPowerSeries(self.Hom(S))) return S @@ -255,5 +263,5 @@ def fraction_field(self): return self else: return CompletionPolynomialRing(field, self._p, - default_prec = self.default_prec(), + default_prec = self._default_prec, sparse = self.is_sparse()) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index c83c73bdeed..7a5d2e28134 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1229,39 +1229,29 @@ def completion(self, p=None, prec=20, names=None): sage: A. = PolynomialRing(QQ) sage: K = A.fraction_field() - Without any argument, this method constructs the completion at - the ideal `(x)`:: + Without any argument, this method outputs the ring of Laurent + series:: sage: Kx = K.completion() sage: Kx Laurent Series Ring in x over Rational Field We can construct the completion at other ideals by passing in an - irreducible polynomial. In this case, we should also provide a name - for the uniformizer (set to be `x - a` where `a` is a root of the - given polynomial):: + irreducible polynomial:: - sage: K1. = K.completion(x - 1) + sage: K1 = K.completion(x - 1) sage: K1 - Laurent Series Ring in u over Rational Field - sage: x - u - 1 - - :: - - sage: K2. = K.completion(x^2 + x + 1) + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at x - 1 + sage: K2 = K.completion(x^2 + x + 1) sage: K2 - Laurent Series Ring in v over Number Field in a with defining polynomial x^2 + x + 1 - sage: a^2 + a + 1 - 0 - sage: x - v - a + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at x^2 + x + 1 - When the precision is infinity, a lazy series ring is returned:: + TESTS:: sage: # needs sage.combinat - sage: L = K.completion(x, prec=oo); L - Lazy Laurent Series Ring in x over Rational Field + sage: L = K.completion(x, prec=oo) + sage: L.backend(force=True) + Lazy Laurent Series Ring in u_... over Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x """ if p is None: p = self.variable_name() diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 9ffb120eb38..385b2d32f73 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -951,6 +951,8 @@ def prec(self): """ return infinity + precision_absolute = prec + def lift_to_precision(self, absprec=None): """ Return another element of the same parent with absolute diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 80f87ac3a48..2b88725d645 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2491,6 +2491,18 @@ cdef class Polynomial(CommutativePolynomial): # But if any degree is allowed then there should certainly be a factor if self has degree > 0 raise AssertionError(f"no irreducible factor was computed for {self}. Bug.") + def perfect_power(self): + f = self + n = Integer(1) + for e, m in self.degree().factor(): + for _ in range(m): + try: + f = f.nth_root(e) + n *= e + except ValueError: + break + return f, n + def any_root(self, ring=None, degree=None, assume_squarefree=False, assume_equal_deg=False): """ Return a root of this polynomial in the given ring. @@ -10099,7 +10111,8 @@ cdef class Polynomial(CommutativePolynomial): sage: f.add_bigoh(2).parent() Power Series Ring in x over Integer Ring """ - return self._parent.completion(self._parent.gen())(self).add_bigoh(prec) + A = self._parent + return A.completion(A.variable_name())(self).add_bigoh(prec) @cached_method def is_irreducible(self): diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 5d0f82de77c..0101875a169 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1754,7 +1754,6 @@ def monics(self, of_degree=None, max_degree=None): - Joel B. Mohler """ - if self.base_ring().order() is infinity: raise NotImplementedError if of_degree is not None and max_degree is None: @@ -1838,32 +1837,29 @@ def completion(self, p=None, prec=20, extras=None): Constructing the completion at the place of infinity also works:: - sage: C3. = P.completion(infinity) + sage: C3 = P.completion(infinity) sage: C3 - Laurent Series Ring in w over Rational Field - sage: C3(x) - w^-1 + Completion of Fraction Field of Univariate Polynomial Ring in x over Rational Field at infinity When the precision is infinity, a lazy series ring is returned:: + TESTS:: + sage: # needs sage.combinat - sage: PP = P.completion(x, prec=oo); PP - Lazy Taylor Series Ring in x over Rational Field + sage: PP = P.completion(x, prec=oo) + sage: PP.backend(force=True) + Lazy Taylor Series Ring in u_... over Univariate Quotient Polynomial Ring in xbar over Rational Field with modulus x sage: g = 1 / PP(f); g - 1 + x + x^2 + O(x^3) + 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + ... sage: 1 / g == f True - TESTS:: + :: sage: P.completion('x') Power Series Ring in x over Rational Field sage: P.completion('y') Power Series Ring in y over Rational Field - sage: Pz. = P.completion('y') - Traceback (most recent call last): - ... - ValueError: conflict of variable names """ if p is None: p = self.variable_name() diff --git a/src/sage/rings/power_series_pari.pyx b/src/sage/rings/power_series_pari.pyx index bd76579346e..6d102a817d3 100644 --- a/src/sage/rings/power_series_pari.pyx +++ b/src/sage/rings/power_series_pari.pyx @@ -434,7 +434,7 @@ cdef class PowerSeries_pari(PowerSeries): if a.valuation() <= 0: raise ValueError("can only substitute elements of positive valuation") elif isinstance(Q, PolynomialRing_generic): - Q = Q.completion(Q.gen()) + Q = Q.completion(Q.variable_name()) elif Q.is_exact() and not a: pass else: From c84ffe648503aa51d1a70abf6968422698741987 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 29 Aug 2025 01:00:55 +0200 Subject: [PATCH 21/59] implement quasi-linear algorithm for expansion (and printing) --- src/sage/rings/completion.py | 324 +++++++++++++++++++++++++++++++---- 1 file changed, 288 insertions(+), 36 deletions(-) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 75394271445..6a61cfb9014 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -1,5 +1,21 @@ +r""" +Completion of polynomial rings and their fraction fields +""" + +# *************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# 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.element import Element from sage.categories.fields import Fields @@ -16,15 +32,81 @@ class CompletionToPowerSeries(RingHomomorphism): - def __init__(self, parent): - super().__init__(parent) + r""" + Conversion morphism from a completion to the + underlying power series ring. + + TESTS:: + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: S. = Ap.power_series_ring() + sage: f = S.convert_map_from(Ap) + sage: type(f) + + + sage: # TestSuite(f).run() + """ def _call_(self, x): + r""" + Return the image of ``x`` by this morphism. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: S. = Ap.power_series_ring() + sage: S(Ap(x)) # indirect doctest + 1 + u + """ return self.codomain()(x.backend(force=True)) class CompletionPolynomial(RingExtensionElement): + r""" + An element in the completion of a polynomial ring + or a field of rational functions. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: u = Ap.random_element() + sage: type(u) + + """ + def __init__(self, parent, f): + if isinstance(f, Element): + R = f.parent() + ring = parent._ring + integer_ring = parent._integer_ring + if ring.has_coerce_map_from(R): + f = ring(f) + f = f(parent._gen) + #elif integer_ring.has_coerce_map_from(R): + # f = integer_ring(f).backend(force=True) + return super().__init__(parent, f) + def _repr_(self): + r""" + Return a string representation of this element. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: y = Ap(x) # indirect doctest + sage: y + 1 + (x - 1) + sage: y.add_bigoh(5) # indirect doctest + 1 + (x - 1) + O((x - 1)^5) + + :: + + sage: Ainf = A.completion(infinity) + sage: Ainf.uniformizer() # indirect doctest + x^-1 + """ # Uniformizer S = self.parent() u = S._p @@ -91,15 +173,84 @@ def _repr_(self): return s[1:] def expansion(self, include_final_zeroes=True): - # TODO: improve performance + r""" + Return a generator producing the list of coefficients + of this element, when written as a series in `p`. + + If the parent of this element does not contain elements + of negative valuation, the expansion starts at `0`; + otherwise, it starts at the valuation of the elment. + + INPUT: + + - ``include_final_zeroes`` (default: ``True``) : a boolean; + if ``False``, stop the iterator as soon as all the next + coefficients are all known to be zero + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: y = Ap(x) # indirect doctest + sage: y + 1 + (x - 1) + sage: E = y.expansion() + sage: next(E) + 1 + sage: next(E) + 1 + sage: next(E) + 0 + + Using ``include_final_zeroes=False`` stops the iterator after + the two first `1`:: + + sage: E = y.expansion(include_final_zeroes=False) + sage: next(E) + 1 + sage: next(E) + 1 + sage: next(E) + Traceback (most recent call last): + ... + StopIteration + + We underline that, for a nonexact element the iterator always + stops at the precision:: + + sage: z = y.add_bigoh(3) + sage: z + 1 + (x - 1) + O((x - 1)^3) + sage: E = z.expansion(include_final_zeroes=False) + sage: next(E) + 1 + sage: next(E) + 1 + sage: next(E) + 0 + sage: next(E) + Traceback (most recent call last): + ... + StopIteration + + Over the completion of a field of rational functions, the + iterator always starts at the first nonzero coefficient + (correspoding to the valuation of the element):: + + sage: Kp = Ap.fraction_field() + sage: u = Kp.uniformizer() + sage: u + (x - 1) + sage: E = u.expansion() + sage: next(E) + 1 + """ S = self.parent() A = S._base - incl = S._incl - u = self.parent()._p - elt = self.backend(force=True) + p = S._p prec = self.precision_absolute() - n = 0 - if u is Infinity: + if p is Infinity: + elt = self.backend(force=True) n = elt.valuation() while n < prec: if (not include_final_zeroes @@ -110,41 +261,118 @@ def expansion(self, include_final_zeroes=True): elt -= elt.parent()(coeff) << n n += 1 else: - if S._integer_ring is not S: - val = elt.valuation() - if val is Infinity: - return - if val < 0: - elt *= incl(u) ** (-val) - else: - n = val - v = S._p.derivative()(S._xbar).inverse() - vv = v**n - uu = u**n + if S._integer_ring is S: + n = 0 + else: + n = self.valuation() + if n < 0: + self *= p ** (-n) + prec -= n + n = 0 + self = S._integer_ring(self) + try: + f = self.polynomial() + current_prec = prec + except ValueError: + current_prec = n + 20 + f = self.add_bigoh(current_prec).polynomial() + if current_prec is not Infinity: + include_final_zeroes = True + f //= p ** n while n < prec: - if (not include_final_zeroes - and elt.precision_absolute() is Infinity and elt.is_zero()): + if n >= current_prec: + current_prec *= 2 + f = self.add_bigoh(current_prec).polynomial() + f //= p ** n + if not include_final_zeroes and f.is_zero(): break - coeff = (vv * elt[n]).lift() + f, coeff = f.quo_rem(p) yield coeff - elt -= incl(uu * coeff) - uu *= u - vv *= v n += 1 - def teichmuller(self): + def teichmuller_lift(self): + r""" + Return the Teichmüller representative of this element. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x^2 + x + 1, prec=5) + sage: a = Ap(x).teichmuller_lift() + sage: a + x + (4*x + 2)*(x^2 + x + 1) + (4*x + 2)*(x^2 + x + 1)^2 + ... + sage: a^2 + a + 1 + 0 + """ elt = self.backend(force=True) lift = elt.parent()(elt[0]) return self.parent()(lift) def valuation(self): + r""" + Return the valuation of this element. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x^2 + x + 1) + sage: Ap(x).valuation() + 0 + sage: u = Ap(x^2 + x + 1) + sage: u.valuation() + 1 + sage: (1/u).valuation() + -1 + """ return self.backend(force=True).valuation() + def lift_to_precision(self, prec): + elt = self.backend(force=True) + elt = elt.lift_to_precision(prec) + return self.parent()(elt) + + def polynomial(self): + S = self.parent() + A = S._ring + p = S._p + d = p.degree() + k = S.residue_field() + f = self.backend(force=True).polynomial().change_ring(k) + g = f(f.parent().gen() - k.gen()) + gs = [A([c[i] for c in g.list()]) for i in range(d)] + prec = self.precision_absolute() + if prec is Infinity: + for i in range(1, d): + if gs[i]: + raise ValueError("exact element which is not in the polynomial ring") + return gs[0] + xbar = S._xbar(prec) + modulus = p ** prec + res = gs[0] + xbari = A.one() + for i in range(1, d): + xbari = (xbari * xbar) % modulus + res += xbari * gs[i] + return res % modulus + def shift(self, n): raise NotImplementedError class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): + r""" + A class for completions of polynomial rings and their fraction fields. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: type(Ap) + + + sage: TestSuite(Ap).run() + + """ Element = CompletionPolynomial def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): @@ -178,6 +406,7 @@ def __init__(self, ring, p, default_prec, sparse): A = self._ring = ring if not isinstance(A, PolynomialRing_generic): A = A.ring() + self._A = A base = A.base_ring() self._p = p self._default_prec = default_prec @@ -191,7 +420,9 @@ def __init__(self, ring, p, default_prec, sparse): x = backend.gen().inverse() else: self._residue_ring = k = A.quotient(p) - self._xbar = xbar = k(A.gen()) + self._xbar_approx = A.gen() + self._xbar_modulus = p + self._xbar_prec = 1 if isinstance(ring, PolynomialRing_generic): self._integer_ring = self name = "u_%s" % id(self) @@ -200,13 +431,14 @@ def __init__(self, ring, p, default_prec, sparse): self._integer_ring = CompletionPolynomialRing(A, p, default_prec, sparse) name = "u_%s" % id(self._integer_ring) backend = LaurentSeriesRing(k, name, default_prec=default_prec, sparse=sparse) - x = backend.gen() + xbar + x = backend.gen() + k(A.gen()) super().__init__(backend.coerce_map_from(k) * k.coerce_map_from(base), category=backend.category()) # Set generator self._gen = self(x) # Set coercions - self._incl = ring.hom([x]) - self.register_coercion(A.Hom(self)(self._incl)) + self.register_coercion(ring) + if A is not ring: + self.register_coercion(A) if self._integer_ring is not None: self.register_coercion(self._integer_ring) @@ -234,21 +466,41 @@ def uniformizer(self): def gen(self): return self._gen + def _xbar(self, prec): + if self._p is Infinity: + return + # We solve the equation p(xbar) = 0 in A_p + p = self._p + pp = p.derivative() + while self._xbar_prec < prec: + self._xbar_modulus *= self._xbar_modulus + self._xbar_prec *= 2 + u = p(self._xbar_approx) + _, v, _ = pp(self._xbar_approx).xgcd(self._xbar_modulus) + self._xbar_approx -= u * v + self._xbar_approx %= self._xbar_modulus + return self._xbar_approx + def residue_ring(self): return self._residue_ring residue_field = residue_ring - def power_series(self, names=None, sparse=None): + def power_series_ring(self, names=None, sparse=None): if isinstance(names, (list, tuple)): names = names[0] if sparse is None: sparse = self.is_sparse() - base = self._base.base_ring() - if self._integer_ring is self: - S = PowerSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) - else: - S = LaurentSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) + S = PowerSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) + S.register_conversion(CompletionToPowerSeries(self.Hom(S))) + return S + + def laurent_series_ring(self, names=None, sparse=None): + if isinstance(names, (list, tuple)): + names = names[0] + if sparse is None: + sparse = self.is_sparse() + S = LaurentSeriesRing(self._residue_ring, names, default_prec=self._default_prec, sparse=sparse) S.register_conversion(CompletionToPowerSeries(self.Hom(S))) return S From ee5902239dd03b34783a872979546c665be16876 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 29 Aug 2025 18:14:11 +0200 Subject: [PATCH 22/59] doctests --- src/sage/categories/pushout.py | 8 +- src/sage/rings/big_oh.py | 2 - src/sage/rings/completion.py | 377 +++++++++++++++++- .../rings/polynomial/polynomial_element.pyx | 21 + 4 files changed, 383 insertions(+), 25 deletions(-) diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 078b4ba4d28..0b8d20bba15 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2484,7 +2484,7 @@ class CompletionFunctor(ConstructionFunctor): (1 + O(5^20))*a + 3 + 2*5 + 2*5^2 + 2*5^3 + 2*5^4 + 2*5^5 + 2*5^6 + 2*5^7 + 2*5^8 + 2*5^9 + 2*5^10 + 2*5^11 + 2*5^12 + 2*5^13 + 2*5^14 + 2*5^15 + 2*5^16 + 2*5^17 + 2*5^18 + 2*5^19 + O(5^20) """ rank = 4 - _real_types = ['Interval', 'Ball', 'MPFR', 'RDF', 'RLF', 'RR'] + _real_types = [None, 'Interval', 'Ball', 'MPFR', 'RDF', 'RLF', 'RR'] _dvr_types = [None, 'fixed-mod', 'floating-point', 'capped-abs', 'capped-rel', 'lattice-cap', 'lattice-float', 'relaxed'] def __init__(self, p, prec, extras=None): @@ -2541,7 +2541,7 @@ def __init__(self, p, prec, extras=None): from sage.rings.infinity import Infinity if self.p == Infinity: if self.type not in self._real_types: - raise ValueError("completion type must be one of %s" % (", ".join(self._real_types))) + raise ValueError("completion type must be one of %s" % (", ".join(self._real_types[1:]))) elif self.type not in self._dvr_types: raise ValueError("completion type must be one of %s" % (", ".join(self._dvr_types[1:]))) @@ -2777,9 +2777,9 @@ def commutes(self, other): functors in opposite order works. It does:: sage: P. = ZZ[] - sage: C = P.completion(x).construction()[0] + sage: C = P.completion('x').construction()[0] sage: R = FractionField(P) - sage: hasattr(R,'completion') + sage: hasattr(R, 'completion') False sage: C(R) is Frac(C(P)) True diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 7060037371f..6a05e39c849 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -200,8 +200,6 @@ def O(*x, **kwds): C = A.completion(p) return C.zero().add_bigoh(n) - - if isinstance(x, (int, Integer, Rational)): # p-adic number if x <= 0: diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion.py index 6a61cfb9014..ed1f79c3a05 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion.py @@ -275,7 +275,7 @@ def expansion(self, include_final_zeroes=True): current_prec = prec except ValueError: current_prec = n + 20 - f = self.add_bigoh(current_prec).polynomial() + f = self.polynomial(current_prec) if current_prec is not Infinity: include_final_zeroes = True f //= p ** n @@ -327,24 +327,94 @@ def valuation(self): return self.backend(force=True).valuation() def lift_to_precision(self, prec): + r""" + Return this element lifted to precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x^2 + x + 1) + sage: y = Ap(x).add_bigoh(10) + sage: y + x + O((x^2 + x + 1)^10) + sage: y.lift_to_precision(20) + x + O((x^2 + x + 1)^20) + + When ``prec`` is less than the absolute precision of + the element, the same element is returned without any + change on the precision:: + + sage: y.lift_to_precision(5) + x + O((x^2 + x + 1)^10) + """ elt = self.backend(force=True) elt = elt.lift_to_precision(prec) return self.parent()(elt) - def polynomial(self): + def polynomial(self, prec=None): + r""" + Return a polynomial (in the underlying polynomial ring) + which is indistinguishable from ``self`` at precision ``prec``. + + INPUT: + + - ``prec`` (optional) -- an integer; if not given, defaults + to the absolute precision of this element + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: Ap = A.completion(x - 2, prec=5) + sage: y = 1 / Ap(1 - x) + sage: y + 4 + (x + 3) + 4*(x + 3)^2 + (x + 3)^3 + 4*(x + 3)^4 + O((x + 3)^5) + sage: y.polynomial() + 4*x^4 + 4*x^3 + 4*x^2 + 4*x + 4 + + When called with a nonintegral element, this method raises an error:: + + sage: z = 1 / Ap(2 - x) + sage: z.polynomial() + Traceback (most recent call last): + ... + ValueError: this element of negative valuation cannot be approximated by a polynomial + + TESTS:: + + sage: Kq = A.completion(infinity) + sage: Kq(1/x).polynomial() + Traceback (most recent call last): + ... + ValueError: approximation by a polynomial does not make sense for a completion at infinity + """ S = self.parent() - A = S._ring + A = S._A p = S._p + if p is Infinity: + raise ValueError("approximation by a polynomial does not make sense for a completion at infinity") + backend = self.backend(force=True) + if backend.valuation() < 0: + raise ValueError("this element of negative valuation cannot be approximated by a polynomial") + try: + backend = backend.power_series() + except AttributeError: + pass d = p.degree() k = S.residue_field() - f = self.backend(force=True).polynomial().change_ring(k) + if prec is not None: + backend = backend.add_bigoh(prec) + prec = backend.precision_absolute() + f = backend.polynomial().change_ring(k) g = f(f.parent().gen() - k.gen()) gs = [A([c[i] for c in g.list()]) for i in range(d)] - prec = self.precision_absolute() if prec is Infinity: for i in range(1, d): if gs[i]: - raise ValueError("exact element which is not in the polynomial ring") + raise ValueError("this element is exact and does not lie in the polynomial ring") return gs[0] xbar = S._xbar(prec) modulus = p ** prec @@ -362,20 +432,51 @@ def shift(self, n): class CompletionPolynomialRing(UniqueRepresentation, RingExtension_generic): r""" A class for completions of polynomial rings and their fraction fields. + """ + Element = CompletionPolynomial - TESTS:: + def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + r""" + Normalize the parameters and call the appropriate constructor. - sage: A. = QQ[] - sage: Ap = A.completion(x - 1) - sage: type(Ap) - + INPUT: - sage: TestSuite(Ap).run() + - ``ring`` -- the underlying polynomial ring or field - """ - Element = CompletionPolynomial + - ``p`` -- a generator of the ideal at which the completion is + done; it could also be ``Infinity`` - def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): + - ``default_pres`` (default: ``20``) -- the default precision + of the completion + + - ``sparse`` (default: ``False``) -- a boolean + + TESTS:: + + sage: A. = ZZ[] + sage: A.completion(x^2 + x + 1) + Completion of Univariate Polynomial Ring in x over Integer Ring at x^2 + x + 1 + sage: A.completion(2*x - 1) + Traceback (most recent call last): + ... + NotImplementedError: the leading coefficient of p must be invertible + + :: + + sage: B. = QQ[] + sage: B.completion(x^2 + x^3) + Traceback (most recent call last): + ... + ValueError: p must be a squarefree polynomial + + When passing a variable name, a standard ring of power series is + constructed:: + + sage: A.completion('x') + Power Series Ring in x over Integer Ring + sage: A.completion(x) + Completion of Univariate Polynomial Ring in x over Integer Ring at x + """ if isinstance(ring, PolynomialRing_generic): if isinstance(p, str): return PowerSeriesRing(ring.base_ring(), @@ -389,8 +490,10 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): names=p, sparse=sparse) A = ring.ring() else: - raise ValueError("not a polynomial ring or a rational function field") - if p is not Infinity: + raise NotImplementedError("not a polynomial ring or a rational function field") + if p is Infinity: + ring = ring.fraction_field() + else: p = A(p) if not p.leading_coefficient().is_unit(): raise NotImplementedError("the leading coefficient of p must be invertible") @@ -403,6 +506,36 @@ def __classcall_private__(cls, ring, p, default_prec=20, sparse=False): return cls.__classcall__(cls, ring, p, default_prec, sparse) def __init__(self, ring, p, default_prec, sparse): + r""" + Initialize this ring. + + INPUT: + + - ``ring`` -- the underlying polynomial ring or field + + - ``p`` -- a generator of the ideal at which the completion is + done; it could also be ``Infinity`` + + - ``default_pres`` -- the default precision of the completion + + - ``sparse`` -- a boolean + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: type(Ap) + + sage: TestSuite(Ap).run() + + :: + + sage: K = Frac(A) + sage: Kp = K.completion(x - 1) + sage: type(Kp) + + sage: TestSuite(Kp).run() + """ A = self._ring = ring if not isinstance(A, PolynomialRing_generic): A = A.ring() @@ -412,7 +545,6 @@ def __init__(self, ring, p, default_prec, sparse): self._default_prec = default_prec # Construct backend if p is Infinity: - self._ring = ring.fraction_field() self._residue_ring = k = base self._integer_ring = None name = "u_%s" % id(self) @@ -441,14 +573,60 @@ def __init__(self, ring, p, default_prec, sparse): self.register_coercion(A) if self._integer_ring is not None: self.register_coercion(self._integer_ring) + if p is not Infinity and p.is_gen(): + S = PowerSeriesRing(base, A.variable_name(), default_prec=default_prec, sparse=sparse) + self.register_coercion(S) + if self._integer_ring is not self: + S = LaurentSeriesRing(base, A.variable_name(), default_prec=default_prec, sparse=sparse) + self.register_coercion(S) def _repr_(self): + r""" + Return a string representation of this ring. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: A.completion(x) # indirect doctest + Completion of Univariate Polynomial Ring in x over Finite Field of size 7 at x + sage: A.completion(infinity) # indirect doctest + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at infinity + + :: + + sage: K = Frac(A) + sage: K.completion(x^2 + 2*x + 2) # indirect doctest + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + """ if self._p is Infinity: return "Completion of %s at infinity" % self._ring else: return "Completion of %s at %s" % (self._ring, self._p) def construction(self): + r""" + Return the functorial construction of this ring. + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x^2 - 1) + sage: F, X = Ap.construction() + sage: F + Completion[x^2 - 1, prec=20] + sage: X + Univariate Polynomial Ring in x over Rational Field + sage: F(X) is Ap + True + + TESTS:: + + sage: R = PolynomialRing(QQ, 'y', sparse=True) + sage: Kinf = R.completion(infinity) + sage: F, X = Kinf.construction() + sage: F(X) is Kinf + True + """ from sage.categories.pushout import CompletionFunctor extras = { 'names': [str(x) for x in self._defining_names()], @@ -458,15 +636,55 @@ def construction(self): @cached_method def uniformizer(self): + r""" + Return a uniformizer of this ring. + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x^2 - 1) + sage: Ap.uniformizer() + (x^2 - 1) + """ if self._p is Infinity: return self._gen.inverse() else: return self(self._p) def gen(self): + r""" + Return the generator of this ring. + + EXAMPLES:: + + sage: A. = QQ[] + sage: Ap = A.completion(x^2 - 1) + sage: Ap.gen() + x + """ return self._gen def _xbar(self, prec): + r""" + Return an approximation at precision ``prec`` of the + Teichmüller representative of `x`, the generator of + this ring. + + This method is only for internal use. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap._xbar(5) + 4*x^15 + 4*x^14 + x^8 + x + 6 + + The method returns nothing in case of a completion + at infinity:: + + sage: Ap = A.completion(infinity) + sage: Ap._xbar(5) + """ if self._p is Infinity: return # We solve the equation p(xbar) = 0 in A_p @@ -482,11 +700,59 @@ def _xbar(self, prec): return self._xbar_approx def residue_ring(self): + r""" + Return the residue ring of this completion. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap.residue_ring() + Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + """ return self._residue_ring residue_field = residue_ring def power_series_ring(self, names=None, sparse=None): + r""" + Return a power series ring, which is isomorphic to + the integer ring of this completion. + + INPUT: + + - ``names`` -- a string, the variable name + + - ``sparse`` (optional) -- a boolean; if not given, + use the sparcity of this ring + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: S. = Ap.power_series_ring() + sage: S + Power Series Ring in u over Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + + A conversion from ``Ap`` to ``S`` is set:: + + sage: xp = Ap.gen() + sage: S(xp) + xbar + u + + :: + + sage: Kp = Frac(Ap) + sage: T. = Kp.power_series_ring() + sage: T + Power Series Ring in u over Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + sage: T is S + True + + .. SEEALSO:: + + :meth:`laurent_series_ring` + """ if isinstance(names, (list, tuple)): names = names[0] if sparse is None: @@ -496,6 +762,29 @@ def power_series_ring(self, names=None, sparse=None): return S def laurent_series_ring(self, names=None, sparse=None): + r""" + Return a Laurent series ring, which is isomorphic to + the fraction field of this completion. + + INPUT: + + - ``names`` -- a string, the variable name + + - ``sparse`` (optional) -- a boolean; if not given, + use the sparcity of this ring + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: S. = Ap.laurent_series_ring() + sage: S + Laurent Series Ring in u over Univariate Quotient Polynomial Ring in xbar over Finite Field of size 7 with modulus x^2 + 2*x + 2 + + .. SEEALSO:: + + :meth:`power_series_ring` + """ if isinstance(names, (list, tuple)): names = names[0] if sparse is None: @@ -505,11 +794,61 @@ def laurent_series_ring(self, names=None, sparse=None): return S def integer_ring(self): + r""" + Return the integer ring of this completion. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: K = Frac(A) + sage: Kp = K.completion(x^2 + 2*x + 2) + sage: Kp + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + sage: Kp.integer_ring() + Completion of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + """ if self._p is Infinity: raise NotImplementedError return self._integer_ring - def fraction_field(self): + def is_integral_domain(self): + return self._residue_ring.is_integral_domain() + + def fraction_field(self, permissive=False): + r""" + Return the fraction field of this completion. + + - ``permissive`` (default: ``False``) -- a boolean; + if ``True`` and this ring is a domain, return + instead the completion at the same place of the + fraction field of the underlying polynomial ring + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap + Completion of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + sage: Ap.fraction_field() + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + 2*x + 2 + + Trying to apply this method with a completion at a nonprime ideal + produces an error:: + + sage: Aq = A.completion(x^2 + x + 1) + sage: Aq.fraction_field() + Traceback (most recent call last): + ... + ValueError: this ring is not an integral domain + + Nonetheless, if the flag ``permissive`` is set to ``True``, + the localization at all nonzero divisors is returned:: + + sage: Aq.fraction_field(permissive=True) + Completion of Fraction Field of Univariate Polynomial Ring in x over Finite Field of size 7 at x^2 + x + 1 + """ + if not (permissive or self.is_integral_domain()): + raise ValueError("this ring is not an integral domain") field = self._ring.fraction_field() if field is self._ring: return self diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 2b88725d645..a1546c9a4ba 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2492,6 +2492,27 @@ cdef class Polynomial(CommutativePolynomial): raise AssertionError(f"no irreducible factor was computed for {self}. Bug.") def perfect_power(self): + r""" + Return ``(P, n)``, where this polynomial is `P^n` and `n` is maximal. + + EXAMPLES:: + + sage: A. = QQ[] + sage: f = x^2 - 2*x + 1 + sage: f.perfect_power() + (-x + 1, 2) + + :: + + sage: P = (x + 1)^100 + sage: Q = (x + 2)^50 + sage: P.perfect_power() + (x + 1, 100) + sage: Q.perfect_power() + (x + 2, 50) + sage: (P*Q).perfect_power() + (x^3 + 4*x^2 + 5*x + 2, 50) + """ f = self n = Integer(1) for e, m in self.degree().factor(): From d816dc5423c07e89bb57d7e3560d4784579df454 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 30 Aug 2025 09:34:39 +0200 Subject: [PATCH 23/59] completion -> completion_polynomial_ring --- .../{completion.py => completion_polynomial_ring.py} | 8 ++++---- src/sage/rings/fraction_field.py | 2 +- src/sage/rings/meson.build | 1 + src/sage/rings/polynomial/polynomial_ring.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename src/sage/rings/{completion.py => completion_polynomial_ring.py} (98%) diff --git a/src/sage/rings/completion.py b/src/sage/rings/completion_polynomial_ring.py similarity index 98% rename from src/sage/rings/completion.py rename to src/sage/rings/completion_polynomial_ring.py index ed1f79c3a05..ec65f68c0c6 100644 --- a/src/sage/rings/completion.py +++ b/src/sage/rings/completion_polynomial_ring.py @@ -43,7 +43,7 @@ class CompletionToPowerSeries(RingHomomorphism): sage: S. = Ap.power_series_ring() sage: f = S.convert_map_from(Ap) sage: type(f) - + sage: # TestSuite(f).run() """ @@ -73,7 +73,7 @@ class CompletionPolynomial(RingExtensionElement): sage: Ap = A.completion(x - 1) sage: u = Ap.random_element() sage: type(u) - + """ def __init__(self, parent, f): if isinstance(f, Element): @@ -525,7 +525,7 @@ def __init__(self, ring, p, default_prec, sparse): sage: A. = QQ[] sage: Ap = A.completion(x - 1) sage: type(Ap) - + sage: TestSuite(Ap).run() :: @@ -533,7 +533,7 @@ def __init__(self, ring, p, default_prec, sparse): sage: K = Frac(A) sage: Kp = K.completion(x - 1) sage: type(Kp) - + sage: TestSuite(Kp).run() """ A = self._ring = ring diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 7a5d2e28134..c06540b110f 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1255,7 +1255,7 @@ def completion(self, p=None, prec=20, names=None): """ if p is None: p = self.variable_name() - from sage.rings.completion import CompletionPolynomialRing + from sage.rings.completion_polynomial_ring import CompletionPolynomialRing return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.ring().is_sparse()) class FractionFieldEmbedding(DefaultConvertMap_unique): diff --git a/src/sage/rings/meson.build b/src/sage/rings/meson.build index 55a99548aca..6e8648d34f0 100644 --- a/src/sage/rings/meson.build +++ b/src/sage/rings/meson.build @@ -9,6 +9,7 @@ py.install_sources( 'cfinite_sequence.py', 'cif.py', 'commutative_algebra.py', + 'completion_polynomial_ring.py', 'complex_arb.pxd', 'complex_conversion.pxd', 'complex_double.pxd', diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index 0101875a169..d77dfb5403e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1863,7 +1863,7 @@ def completion(self, p=None, prec=20, extras=None): """ if p is None: p = self.variable_name() - from sage.rings.completion import CompletionPolynomialRing + from sage.rings.completion_polynomial_ring import CompletionPolynomialRing return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.is_sparse()) def quotient_by_principal_ideal(self, f, names=None, **kwds): From 973dffa9c4b31e759d1faf9314974153d0f43f77 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 30 Aug 2025 10:01:24 +0200 Subject: [PATCH 24/59] oops --- src/sage/rings/completion_polynomial_ring.py | 10 +++++----- src/sage/rings/fraction_field.py | 1 + src/sage/rings/polynomial/polynomial_element.pyx | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 4 +--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/completion_polynomial_ring.py b/src/sage/rings/completion_polynomial_ring.py index ec65f68c0c6..5e33af0ae81 100644 --- a/src/sage/rings/completion_polynomial_ring.py +++ b/src/sage/rings/completion_polynomial_ring.py @@ -85,7 +85,7 @@ def __init__(self, parent, f): f = f(parent._gen) #elif integer_ring.has_coerce_map_from(R): # f = integer_ring(f).backend(force=True) - return super().__init__(parent, f) + super().__init__(parent, f) def _repr_(self): r""" @@ -255,7 +255,7 @@ def expansion(self, include_final_zeroes=True): while n < prec: if (not include_final_zeroes and elt.precision_absolute() is Infinity and elt.is_zero()): - break + break coeff = elt[n] yield coeff elt -= elt.parent()(coeff) << n @@ -285,7 +285,7 @@ def expansion(self, include_final_zeroes=True): f = self.add_bigoh(current_prec).polynomial() f //= p ** n if not include_final_zeroes and f.is_zero(): - break + break f, coeff = f.quo_rem(p) yield coeff n += 1 @@ -854,5 +854,5 @@ def fraction_field(self, permissive=False): return self else: return CompletionPolynomialRing(field, self._p, - default_prec = self._default_prec, - sparse = self.is_sparse()) + default_prec=self._default_prec, + sparse=self.is_sparse()) diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index c06540b110f..595b1589697 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1258,6 +1258,7 @@ def completion(self, p=None, prec=20, names=None): from sage.rings.completion_polynomial_ring import CompletionPolynomialRing return CompletionPolynomialRing(self, p, default_prec=prec, sparse=self.ring().is_sparse()) + class FractionFieldEmbedding(DefaultConvertMap_unique): r""" The embedding of an integral domain into its field of fractions. diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index a1546c9a4ba..e6c9118413f 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -2510,7 +2510,7 @@ cdef class Polynomial(CommutativePolynomial): (x + 1, 100) sage: Q.perfect_power() (x + 2, 50) - sage: (P*Q).perfect_power() + sage: (P*Q).perfect_power() (x^3 + 4*x^2 + 5*x + 2, 50) """ f = self diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index d77dfb5403e..b1dc3da48dc 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1843,8 +1843,6 @@ def completion(self, p=None, prec=20, extras=None): When the precision is infinity, a lazy series ring is returned:: - TESTS:: - sage: # needs sage.combinat sage: PP = P.completion(x, prec=oo) sage: PP.backend(force=True) @@ -1854,7 +1852,7 @@ def completion(self, p=None, prec=20, extras=None): sage: 1 / g == f True - :: + TESTS:: sage: P.completion('x') Power Series Ring in x over Rational Field From 065f97f5b495a4a985b90cffef3c42c7390cf825 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 3 Sep 2025 17:01:50 +0200 Subject: [PATCH 25/59] address some comments by Travis --- src/sage/rings/completion_polynomial_ring.py | 42 +++++++++++++++----- src/sage/rings/fraction_field.py | 4 ++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/completion_polynomial_ring.py b/src/sage/rings/completion_polynomial_ring.py index 5e33af0ae81..f7b35390c0a 100644 --- a/src/sage/rings/completion_polynomial_ring.py +++ b/src/sage/rings/completion_polynomial_ring.py @@ -1,9 +1,13 @@ r""" Completion of polynomial rings and their fraction fields + +AUTHORS: + +- Xavier Caruso (2025-09): Initial implementation """ # *************************************************************************** -# Copyright (C) 2024 Xavier Caruso +# Copyright (C) 2025 Xavier Caruso # # 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 @@ -66,16 +70,19 @@ class CompletionPolynomial(RingExtensionElement): r""" An element in the completion of a polynomial ring or a field of rational functions. - - TESTS:: - - sage: A. = QQ[] - sage: Ap = A.completion(x - 1) - sage: u = Ap.random_element() - sage: type(u) - """ def __init__(self, parent, f): + r""" + Initialize this element. + + TESTS:: + + sage: A. = QQ[] + sage: Ap = A.completion(x - 1) + sage: u = Ap.random_element() + sage: type(u) + + """ if isinstance(f, Element): R = f.parent() ring = parent._ring @@ -812,6 +819,23 @@ def integer_ring(self): return self._integer_ring def is_integral_domain(self): + r""" + Return ``True`` is this ring is an integral domain; + ``False`` otherwise. + + EXAMPLES:: + + sage: A. = GF(7)[] + sage: Ap = A.completion(x^2 + 2*x + 2) + sage: Ap.is_integral_domain() + True + + :: + + sage: Aq = A.completion(x^2 + x + 1) + sage: Aq.is_integral_domain() + False + """ return self._residue_ring.is_integral_domain() def fraction_field(self, permissive=False): diff --git a/src/sage/rings/fraction_field.py b/src/sage/rings/fraction_field.py index 595b1589697..a2f07f7bda6 100644 --- a/src/sage/rings/fraction_field.py +++ b/src/sage/rings/fraction_field.py @@ -1224,6 +1224,10 @@ def completion(self, p=None, prec=20, names=None): - ``names`` (default: ``None``) -- a tuple of strings with the previous variable names + .. SEEALSO:: + + :mod:`sage.rings.completion_polynomial_ring` + EXAMPLES:: sage: A. = PolynomialRing(QQ) From 9a276ccdcb412d4a63a9fcfa27898ebde4a0ebbf Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 3 Sep 2025 17:14:04 +0200 Subject: [PATCH 26/59] more comments --- src/sage/rings/laurent_series_ring.py | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index c95cfbbbb4a..b87c933b7f1 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -221,7 +221,7 @@ def __classcall__(cls, *args, **kwds): 'q' """ from .power_series_ring import PowerSeriesRing - if 'default_prec' in kwds and kwds['default_prec'] is infinity: + if kwds.get('default_prec', None) is infinity: from sage.rings.lazy_series_ring import LazyLaurentSeriesRing del kwds['default_prec'] return LazyLaurentSeriesRing(*args, **kwds) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index b1dc3da48dc..9fab40bedb9 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1802,6 +1802,10 @@ def completion(self, p=None, prec=20, extras=None): - ``extras`` (default: ``None``) -- ignored; for compatibility with the construction mecanism + .. SEEALSO:: + + :mod:`sage.rings.completion_polynomial_ring` + EXAMPLES:: sage: P. = PolynomialRing(QQ) From 5329139f3f2398503682f632ebcbd90b50e366ea Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 6 Nov 2025 12:00:19 +0100 Subject: [PATCH 27/59] import code --- src/sage/categories/anderson_motives.py | 132 +++++++++++++ src/sage/categories/drinfeld_modules.py | 1 + src/sage/categories/meson.build | 1 + .../drinfeld_modules/anderson_motive.py | 176 ++++++++++++++++++ .../anderson_motive_constructor.py | 157 ++++++++++++++++ .../anderson_motive_morphism.py | 86 +++++++++ .../drinfeld_modules/drinfeld_module.py | 4 + 7 files changed, 557 insertions(+) create mode 100644 src/sage/categories/anderson_motives.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/anderson_motive.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py new file mode 100644 index 00000000000..0fd43358e77 --- /dev/null +++ b/src/sage/categories/anderson_motives.py @@ -0,0 +1,132 @@ +r""" +Anderson motives +""" + +# ***************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + + +from sage.misc.latex import latex + +from sage.categories.modules import Modules +from sage.categories.ore_modules import OreModules +from sage.categories.homsets import Homsets +from sage.categories.drinfeld_modules import DrinfeldModules + +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing + + +class AndersonMotives(OreModules): + @staticmethod + def __classcall_private__(cls, category, dispatch=True): + if isinstance(category, AndersonMotives): + return category + if isinstance(category, DrinfeldModules): + category = DrinfeldModules(category.base_morphism()) + else: + category = DrinfeldModules(category) + return AndersonMotives.__classcall__(cls, category) + + def __init__(self, category): + self._base_morphism = category.base_morphism() + self._base_field = category.base() + self._function_ring = A = category.function_ring() + self._base_over_constants_field = category.base_over_constants_field() + self._ore_variable_name = category._ore_variable_name + self._characteristic = category._characteristic + K = self._base_morphism.codomain() + self._base_combined = AK = PolynomialRing(K, A.variable_name()) # TODO: find a better name + self._constant_coefficient = category.constant_coefficient() + self._divisor = AK.gen() - self._constant_coefficient + FAK = AK.fraction_field() + twisting_morphism = category.ore_polring().twisting_morphism() + twisting_morphism = FAK.hom([FAK.gen()], base_map=twisting_morphism) + self._ore_polring = OrePolynomialRing(FAK, twisting_morphism, names=self._ore_variable_name) + super().__init__(self._ore_polring) + + def _latex_(self): + return f'\\text{{Category{{ }}of{{ }}Anderson{{ }}motives{{ }}' \ + f'over{{ }}{latex(self._base_field)}' + + def _repr_(self): + return f'Category of Anderson motives over {self._base_field}' + + def Homsets(self): + return Homsets() + + def Endsets(self): + return Homsets().Endsets() + + def base_morphism(self): + return self._base_morphism + + def base_over_constants_field(self): + return self._base_over_constants_field + + def base_combined(self): + return self._base_combined + + def divisor(self): + return self._divisor + + def characteristic(self): + if self._characteristic is None: + raise NotImplementedError('function ring characteristic not ' + 'implemented in this case') + return self._characteristic + + def constant_coefficient(self): + return self._constant_coefficient + + def function_ring(self): + return self._function_ring + + def object(self, gen): + raise NotImplementedError + + def super_categories(self): + """ + EXAMPLES:: + + sage: Fq = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) + sage: C = phi.category() + sage: C.super_categories() + [Category of objects] + """ + S = self._ore_polring + return [OreModules(S.base(), S)] + + class ParentMethods: + + def base(self): + return self._anderson_category.base() + + def base_morphism(self): + return self._anderson_category.base_morphism() + + def base_combined(self): + return self._anderson_category.base_combined() + + def base_over_constants_field(self): + return self._anderson_category.base_over_constants_field() + + def characteristic(self): + return self._anderson_category.characteristic() + + def function_ring(self): + return self._anderson_category.function_ring() + + def constant_coefficient(self): + return self._anderson_category.constant_coefficient() diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 4f54127ba26..707ee92def9 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -260,6 +260,7 @@ def __init__(self, base_morphism, name='τ'): tau = K.frobenius_endomorphism(d) self._ore_polring = OrePolynomialRing(K, tau, names=name, polcast=False) + self._ore_variable_name = name # Create constant coefficient self._constant_coefficient = base_morphism(T) # Create characteristic diff --git a/src/sage/categories/meson.build b/src/sage/categories/meson.build index 984e8d50d99..ad4a0eb70c3 100644 --- a/src/sage/categories/meson.build +++ b/src/sage/categories/meson.build @@ -13,6 +13,7 @@ py.install_sources( 'algebras.py', 'algebras_with_basis.py', 'all.py', + 'anderson_motives.py', 'aperiodic_semigroups.py', 'associative_algebras.py', 'basic.py', diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py new file mode 100644 index 00000000000..6890848e2e2 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -0,0 +1,176 @@ +# ***************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +import operator + +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.latex import latex +from sage.misc.functional import log + +from sage.categories.homset import Homset +from sage.categories.anderson_motives import AndersonMotives + +from sage.rings.integer_ring import ZZ +from sage.rings.infinity import Infinity +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.fraction_field import FractionField_1poly_field + +from sage.matrix.constructor import matrix +from sage.matrix.special import identity_matrix, block_diagonal_matrix + +from sage.modules.ore_module import OreModule +from sage.modules.ore_module import OreAction +from sage.modules.ore_module import normalize_names + +from sage.rings.function_field.drinfeld_modules.anderson_motive_morphism import DrinfeldToAnderson, AndersonToDrinfeld + + + +class AndersonMotive_general(OreModule): + @staticmethod + def __classcall_private__(self, category, tau, twist=0, names=None, normalize=True): + K = category.base() + AK = category.base_combined() + + # We normalize the inputs + twist = ZZ(twist) + tau = tau.change_ring(AK) + if normalize: + divisor = category.divisor() + exponent = Infinity + for entry in tau.list(): + if not entry: + continue + e = 0 + while entry.degree() > 0 and e < exponent: + entry, R = entry.quo_rem(divisor) + if R: + break + e += 1 + exponent = e + if exponent == 0: + break + if exponent is not Infinity and exponent > 0: + denom = divisor ** exponent + tau = tau.parent()([entry // denom for entry in tau.list()]) + twist -= exponent + + #if (isinstance(K, FractionField_1poly_field) + # and category.constant_coefficient() == K.gen()): + # from sage.rings.function_field.drinfeld_modules.anderson_motive_rational import AndersonMotive_rational + # cls = AndersonMotive_rational + #else: + cls = AndersonMotive_general + return cls.__classcall__(cls, category, tau, twist, names) + + def __init__(self, category, tau, twist, names): + self._twist = twist + self._anderson_category = category + self._A = A = category.function_ring() + self._t_name = A.variable_name() + self._Fq = Fq = A.base_ring() + self._q = Fq.cardinality() + self._deg = ZZ(log(self._q, Fq.characteristic())) + self._K = self._base = K = category.base() + self._theta = category.constant_coefficient() + self._AK = base = category.base_combined() + self._t = base.gen() + self._tau = tau + ore = category._ore_polring + mat = ((self._t - self._theta) ** (-twist)) * tau # Would be better if we can avoid this computation + mat = mat.change_ring(ore.base_ring()) + names = normalize_names(names, mat.nrows()) + OreModule.__init__(self, mat, ore, None, names, category) + self.register_action(OreAction(ore, self, True, operator.mul)) + + def _set_dettau(self, disc, degree, twist): + self._dettau = disc, degree - twist + self._twist + + @lazy_attribute + def _dettau(self): + det = self._tau.det() + return det.leading_coefficient(), det.degree() + + def _repr_(self): + s = "Anderson motive " + if self._names is None: + s += "of rank %s " % self.rank() + else: + s += "<" + ", ".join(self._names) + "> " + s += "over %s" % self._AK + return s + + def _latex_(self): + if self._names is None: + s = "\\texttt{Anderson motive of rank } %s" % self.rank() + s += "\\texttt{ over } %s" % latex(self._AK) + else: + s = "\\left<" + ", ".join(self._latex_names) + "\\right>" + s += "_{%s}" % latex(self._AK) + return s + + def twist(self, n): + n = ZZ(n) + return AndersonMotive_general(self._category, self._tau, self._twist + n, normalize=False) + + def _Hom_(self, codomain, category): + from sage.rings.function_field.drinfeld_modules.anderson_motive_morphism import AndersonMotive_homspace + return AndersonMotive_homspace(self, codomain) + + def hodge_pink_weights(self): + S = self._tau.smith_form(transformation=False) + return [-self._twist + S[i,i].degree() for i in range(self.rank())] + + def is_effective(self): + return self._twist <= 0 + + def ore_variable(self): + return self._anderson_category._ore_polring.gen() + + def ore_polring(self, names=None, action=True): + if names is None: + names = self._anderson_category._ore_variable_name + S = self._ore_category.ore_ring(names) + if action: + self._unset_coercions_used() + self.register_action(OreAction(S, self, True, operator.mul)) + return S + + def random_element(self, *args, **kwds): + AK = self.base_ring().ring() + r = self.rank() + vs = [AK.random_element(*args, **kwds) for _ in range(r)] + return self(vs) + + +class AndersonMotive_drinfeld(AndersonMotive_general): + def __init__(self, phi, names): + category = AndersonMotives(phi.category()) + A = category.function_ring() + K = category._base_field + AK = A.change_ring(K) + r = phi.rank() + tau = matrix(AK, r) + P = phi.gen() + tau[r-1, 0] = (AK.gen() - P[0]) / P[r] + for i in range(1, r): + tau[i-1, i] = 1 + tau[r-1, i] = -P[i]/P[r] + AndersonMotive_general.__init__(self, category, tau, 0, names) + Ktau = phi.ore_polring() + self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) + try: + Ktau.register_conversion(AndersonToDrinfeld(Homset(self, Ktau), phi)) + except AssertionError: + pass + self._drinfeld_module = phi + + def drinfeld_module(self): + return self._drinfeld_module diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py new file mode 100644 index 00000000000..5176b3181f3 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py @@ -0,0 +1,157 @@ +r""" +Constructor for Anderson motives +""" + +# ***************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + + +from sage.categories.drinfeld_modules import DrinfeldModules +from sage.categories.anderson_motives import AndersonMotives + +from sage.rings.ring import CommutativeRing +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.morphism import RingHomomorphism + +from sage.matrix.matrix0 import Matrix +from sage.matrix.constructor import matrix +from sage.matrix.special import identity_matrix + +from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule +from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general + + +def AndersonMotive(arg1, tau=None, names=None): + r""" + Construct an Anderson motive + + INPUT: + + The input can be one of the followings: + + - a Drinfeld module + + - a pair `(A, \tau)` where + + - `A` is either the underlying function ring (which + currently needs to be of the form `\FF_q[t]`) or + a category (of Drinfeld modules or Anderson motives) + + - `\tau` is the matrix defining the Anderson motive + + - a pair '(A, K)` where `A = \FF_q[t]` is the function + base ring and `K` is the coefficient `A`-field; these + parameters correspond to the trivial Anderson motive + over `A \otimes K` + + OUTPUT: + + An anderson motive + + EXAMPLES:: + + + """ + # Options for *args: + # . a Drinfeld module + # . a category (of Drinfeld modules or AndersonMotives) + # . a ring, a matrix + # . a ring, a A-field + # arg1 is a Drinfeld module + if isinstance(arg1, DrinfeldModule): + if tau is not None: + raise ValueError("") + category = AndersonMotives(arg1.category()) + A = category.function_ring() + K = category._base_field + AK = A.change_ring(K) + r = arg1.rank() + tau = matrix(AK, r) + P = arg1.gen() + tau[r-1, 0] = (AK.gen() - P[0]) / P[r] + for i in range(1, r): + tau[i-1, i] = 1 + tau[r-1, i] = -P[i]/P[r] + return AndersonMotive_general(category, tau, names=names) + + # arg1 is a category + category = None + if isinstance(arg1, DrinfeldModules): + category = AndersonMotives(arg1) + if isinstance(arg1, AndersonMotives): + category = arg1 + if category is not None: + if tau is None: + tau = identity_matrix(category.base_combined(), 1) + det = tau.determinant() + if det == 0: + raise ValueError("tau does not define an Anderson motive") + h = det.degree() + disc, R = det.quo_rem(category.divisor() ** h) + if R: + raise ValueError("tau does not define an Anderson motive") + M = AndersonMotive_general(category, tau, names=names) + M._set_dettau(disc[0], h, 0) + return M + + # arg1 is the function ring + if isinstance(arg1, CommutativeRing): + A = arg1 + if not isinstance(A, PolynomialRing_general): + raise NotImplementedError("Anderson motives over arbitrary Dedekind domain are not supported") + else: + raise ValueError("first argument must be the function ring") + + # tau is the base ring + K = None + if isinstance(tau, RingHomomorphism) and tau.domain() is A: + K = tau.codomain() + gamma = tau + elif isinstance(tau, CommutativeRing): + K = tau + gamma = A + if K is not None: + try: + if K.variable_name() == A.variable_name(): + K = K.base_ring() + except (AttributeError, ValueError): + pass + category = AndersonMotives(K.over(gamma)) + AK = category.base_combined() + tau = identity_matrix(AK, 1) + return AndersonMotive_general(category, tau, names=names) + + # tau is a matrix + if isinstance(tau, Matrix): + AK = tau.base_ring() + if not isinstance(AK, PolynomialRing_general) or AK.variable_name() != A.variable_name(): + raise ValueError("incompatible base rings") + det = tau.determinant() + if det == 0: + raise ValueError("tau does not define an Anderson motive") + h = det.degree() + K = AK.base_ring() + gamma = K.coerce_map_from(A) + if gamma is None: + p = A.characteristic() + if h.gcd(p) == 1: + theta = -det[h-1] / det[h] / h + else: + raise NotImplementedError("cannot determine the structure of A-field") + gamma = A.hom([theta]) + category = AndersonMotives(K.over(gamma)) + disc, R = det.quo_rem(category.divisor() ** h) + if R: + raise ValueError("tau does not define an Anderson motive") + M = AndersonMotive_general(category, tau, names=names) + M._set_dettau(disc[0], h, 0) + return M + + raise ValueError("unable to parse arguments") diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py new file mode 100644 index 00000000000..eaa861d5fa0 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py @@ -0,0 +1,86 @@ +from sage.categories.map import Map + +from sage.modules.ore_module_homspace import OreModule_homspace +from sage.modules.ore_module_morphism import OreModuleMorphism + +from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism + + +# Morphisms between Anderson modules + +class AndersonMotiveMorphism(OreModuleMorphism): + def _repr_type(self): + return "Anderson motive" + + def __init__(self, parent, im_gens, check=True): + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld + if isinstance(im_gens, DrinfeldModuleMorphism): + domain = parent.domain() + codomain = parent.codomain() + if not isinstance(domain, AndersonMotive_drinfeld)\ + or domain.drinfeld_module() is not im_gens.codomain(): + raise ValueError("the domain must be the Anderson module of the codomain of the isogeny") + if not isinstance(codomain, AndersonMotive_drinfeld)\ + or codomain.drinfeld_module() is not im_gens.domain(): + raise ValueError("the codomain must be the Anderson module of the domain of the isogeny") + u = im_gens._ore_polynomial + im_gens = {codomain.gen(0): u*domain.gen(0)} + check = False + OreModuleMorphism.__init__(self, parent, im_gens, check) + + def characteristic_polynomial(self, var='X'): + chi = OreModuleMorphism.characteristic_polynomial(self, var) + A = self.domain().function_ring() + return chi.change_ring(A) + + charpoly = characteristic_polynomial + + +class AndersonMotive_homspace(OreModule_homspace): + Element = AndersonMotiveMorphism + + +# Coercion maps + +class DrinfeldToAnderson(Map): + def __init__(self, parent, phi): + Map.__init__(self, parent) + self._phi = phi + self._motive = parent.codomain() + self._AK = self._motive.base_combined() + + def _call_(self, f): + phi = self._phi + r = phi.rank() + phiT = phi.gen() + coords = [] + for _ in range(r): + coords.append([]) + while f: + f, rem = f.right_quo_rem(phiT) + for i in range(r): + coords[i].append(rem[i]) + coords = [self._AK(c) for c in coords] + return self._motive(coords) + +class AndersonToDrinfeld(Map): + def __init__(self, parent, phi): + Map.__init__(self, parent) + self._phi = phi + self._Ktau = parent.codomain() + + def _call_(self, x): + phi = self._phi + r = phi.rank() + phiT = phi.gen() + S = self._Ktau + xs = [] + for i in range(r): + if x[i].denominator() != 1: + raise ValueError("not in the Anderson motive") + xs.append(x[i].numerator()) + ans = S.zero() + d = max(xi.degree() for xi in xs) + for j in range(d, -1, -1): + ans = ans*phiT + S([xs[i][j] for i in range(r)]) + return ans diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index efa6b58eab5..db84997d391 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2084,6 +2084,10 @@ def scalar_multiplication(self, x): raise ValueError("%s is not element of the function ring" % x) return self.Hom(self)(x) + def anderson_motive(self, names=None): + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld + return AndersonMotive_drinfeld(self, names=names) + def frobenius_relative(self, n=1): r""" Return the `n`-th iterate relative Frobenius of this Drinfeld module. From 4b7b753b5ad9f6cde1360c5ec197913fb76ea703 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 6 Nov 2025 18:38:56 +0100 Subject: [PATCH 28/59] use denominator --- src/sage/categories/anderson_motives.py | 19 +++++++-------- .../drinfeld_modules/anderson_motive.py | 24 +++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index 0fd43358e77..7d4a0375f71 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -46,10 +46,9 @@ def __init__(self, category): self._base_combined = AK = PolynomialRing(K, A.variable_name()) # TODO: find a better name self._constant_coefficient = category.constant_coefficient() self._divisor = AK.gen() - self._constant_coefficient - FAK = AK.fraction_field() twisting_morphism = category.ore_polring().twisting_morphism() - twisting_morphism = FAK.hom([FAK.gen()], base_map=twisting_morphism) - self._ore_polring = OrePolynomialRing(FAK, twisting_morphism, names=self._ore_variable_name) + twisting_morphism = AK.hom([AK.gen()], base_map=twisting_morphism) + self._ore_polring = OrePolynomialRing(AK, twisting_morphism, names=self._ore_variable_name) super().__init__(self._ore_polring) def _latex_(self): @@ -111,22 +110,22 @@ def super_categories(self): class ParentMethods: def base(self): - return self._anderson_category.base() + return self._category.base() def base_morphism(self): - return self._anderson_category.base_morphism() + return self._category.base_morphism() def base_combined(self): - return self._anderson_category.base_combined() + return self._category.base_combined() def base_over_constants_field(self): - return self._anderson_category.base_over_constants_field() + return self._category.base_over_constants_field() def characteristic(self): - return self._anderson_category.characteristic() + return self._category.characteristic() def function_ring(self): - return self._anderson_category.function_ring() + return self._category.function_ring() def constant_coefficient(self): - return self._anderson_category.constant_coefficient() + return self._category.constant_coefficient() diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 6890848e2e2..061eead66be 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -16,6 +16,7 @@ from sage.categories.homset import Homset from sage.categories.anderson_motives import AndersonMotives +from sage.structure.factorization import Factorization from sage.rings.integer_ring import ZZ from sage.rings.infinity import Infinity @@ -72,7 +73,7 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr def __init__(self, category, tau, twist, names): self._twist = twist - self._anderson_category = category + self._category = category self._A = A = category.function_ring() self._t_name = A.variable_name() self._Fq = Fq = A.base_ring() @@ -84,10 +85,9 @@ def __init__(self, category, tau, twist, names): self._t = base.gen() self._tau = tau ore = category._ore_polring - mat = ((self._t - self._theta) ** (-twist)) * tau # Would be better if we can avoid this computation - mat = mat.change_ring(ore.base_ring()) - names = normalize_names(names, mat.nrows()) - OreModule.__init__(self, mat, ore, None, names, category) + names = normalize_names(names, tau.nrows()) + denominator = Factorization([(self._t - self._theta, twist)]) + OreModule.__init__(self, tau, ore, denominator, names, category) self.register_action(OreAction(ore, self, True, operator.mul)) def _set_dettau(self, disc, degree, twist): @@ -132,30 +132,22 @@ def is_effective(self): return self._twist <= 0 def ore_variable(self): - return self._anderson_category._ore_polring.gen() + return self._category._ore_polring.gen() def ore_polring(self, names=None, action=True): if names is None: - names = self._anderson_category._ore_variable_name + names = self._category._ore_variable_name S = self._ore_category.ore_ring(names) if action: self._unset_coercions_used() self.register_action(OreAction(S, self, True, operator.mul)) return S - def random_element(self, *args, **kwds): - AK = self.base_ring().ring() - r = self.rank() - vs = [AK.random_element(*args, **kwds) for _ in range(r)] - return self(vs) - class AndersonMotive_drinfeld(AndersonMotive_general): def __init__(self, phi, names): category = AndersonMotives(phi.category()) - A = category.function_ring() - K = category._base_field - AK = A.change_ring(K) + AK = category.base_combined() r = phi.rank() tau = matrix(AK, r) P = phi.gen() From 3ee5ece0162ab9ed381310f95b7dcf9523a30788 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 6 Nov 2025 21:34:41 +0100 Subject: [PATCH 29/59] submotives and quotients --- src/sage/modules/ore_module.py | 19 +++--- .../drinfeld_modules/anderson_motive.py | 64 +++++++++++++------ .../drinfeld_modules/morphism.py | 6 ++ 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/sage/modules/ore_module.py b/src/sage/modules/ore_module.py index 83e335cdf85..b991fe14598 100644 --- a/src/sage/modules/ore_module.py +++ b/src/sage/modules/ore_module.py @@ -434,8 +434,9 @@ def __init__(self, mat, ore, denominator, names, category) -> None: rank = mat.nrows() FreeModule_ambient.__init__(self, base, rank, category=category) self.register_action(ScalarAction(base, self, True, operator.mul)) + self.register_action(OreAction(ore, self, True, operator.mul)) self._ore = ore - self._ore_category = category + self._category = category self._names = names if names is not None: self._latex_names = [latex_variable_name(name) for name in names] @@ -696,7 +697,7 @@ def rename_basis(self, names, coerce=False): names = normalize_names(names, rank) cls = self.__class__ M = cls.__classcall__(cls, self._pseudohom._matrix, self._ore, - self._denominator, names, self._ore_category) + self._denominator, names, self._category) if coerce: mat = identity_matrix(self.base_ring(), rank) id = self.hom(mat, codomain=M) @@ -800,7 +801,7 @@ def ore_ring(self, names='x', action=True): 'Ore Polynomial Ring in U over Finite Field in a of size 5^3 twisted by a |--> a^5' and 'Ore module over Finite Field in a of size 5^3 twisted by a |--> a^5' """ - S = self._ore_category.ore_ring(names) + S = self._category.ore_ring(names) if action: self._unset_coercions_used() self.register_action(OreAction(S, self, True, operator.mul)) @@ -1937,7 +1938,7 @@ class OreSubmodule(OreModule): r""" Class for submodules of Ore modules. """ - def __classcall_private__(cls, ambient, gens, saturate, names): + def __classcall__(cls, ambient, gens, saturate, names): r""" Normalize the input before passing it to the init function (useful to ensure the uniqueness assupmtion). @@ -1987,7 +1988,7 @@ def __classcall_private__(cls, ambient, gens, saturate, names): basis = matrix(base, gens) submodule = SubmoduleHelper(basis, saturate) names = normalize_names(names, submodule.rank) - return cls.__classcall__(cls, ambient, submodule, names) + return super().__classcall__(cls, ambient, submodule, names) def __init__(self, ambient, submodule, names) -> None: r""" @@ -2026,7 +2027,7 @@ def __init__(self, ambient, submodule, names) -> None: ambient._general_class.__init__( self, matrix(base, rows), ambient.ore_ring(action=False), - ambient._denominator, names, ambient._ore_category) + ambient._denominator, names, ambient._category) coerce = self.hom(submodule.basis, codomain=ambient) ambient.register_coercion(coerce) self._inject = coerce.__copy__() @@ -2493,7 +2494,7 @@ class OreQuotientModule(OreModule): r""" Class for quotients of Ore modules. """ - def __classcall_private__(cls, cover, gens, remove_torsion, names): + def __classcall__(cls, cover, gens, remove_torsion, names): r""" Normalize the input before passing it to the init function (useful to ensure the uniqueness assumption). @@ -2544,7 +2545,7 @@ def __classcall_private__(cls, cover, gens, remove_torsion, names): if not submodule.is_saturated: raise NotImplementedError("torsion Ore modules are not implemented") names = normalize_names(names, cover.rank() - submodule.rank) - return cls.__classcall__(cls, cover, submodule, names) + return super().__classcall__(cls, cover, submodule, names) def __init__(self, cover, submodule, names) -> None: r""" @@ -2585,7 +2586,7 @@ def __init__(self, cover, submodule, names) -> None: cover._general_class.__init__( self, matrix(base, d-rank, d, images) * coerce, cover.ore_ring(action=False), - cover._denominator, names, cover._ore_category) + cover._denominator, names, cover._category) self._project = coerce = cover.hom(coerce, codomain=self) self.register_coercion(coerce) section = self._section = OreModuleSection(self, cover) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 061eead66be..58f502fd474 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -26,7 +26,8 @@ from sage.matrix.constructor import matrix from sage.matrix.special import identity_matrix, block_diagonal_matrix -from sage.modules.ore_module import OreModule +from sage.modules.ore_module import OreModule, OreSubmodule, OreQuotientModule +from sage.modules.ore_module_element import OreModuleElement from sage.modules.ore_module import OreAction from sage.modules.ore_module import normalize_names @@ -34,7 +35,16 @@ +class AndersonMotiveElement(OreModuleElement): + def image(self, integral=None): + if integral is None: + integral = self.parent().is_effective() + return super().image(integral=integral) + + class AndersonMotive_general(OreModule): + Element = AndersonMotiveElement + @staticmethod def __classcall_private__(self, category, tau, twist=0, names=None, normalize=True): K = category.base() @@ -63,17 +73,25 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr tau = tau.parent()([entry // denom for entry in tau.list()]) twist -= exponent + names = normalize_names(names, tau.nrows()) + denominator = Factorization([(category.divisor(), twist)]) + ore = category._ore_polring + #if (isinstance(K, FractionField_1poly_field) # and category.constant_coefficient() == K.gen()): # from sage.rings.function_field.drinfeld_modules.anderson_motive_rational import AndersonMotive_rational # cls = AndersonMotive_rational #else: cls = AndersonMotive_general - return cls.__classcall__(cls, category, tau, twist, names) - def __init__(self, category, tau, twist, names): - self._twist = twist - self._category = category + return cls.__classcall__(cls, tau, ore, denominator, names, category) + + def __init__(self, mat, ore, denominator, names, category) -> None: + OreModule.__init__(self, mat, ore, denominator, names, category) + self._initialize_attributes() + + def _initialize_attributes(self): + category = self._category self._A = A = category.function_ring() self._t_name = A.variable_name() self._Fq = Fq = A.base_ring() @@ -83,15 +101,13 @@ def __init__(self, category, tau, twist, names): self._theta = category.constant_coefficient() self._AK = base = category.base_combined() self._t = base.gen() - self._tau = tau - ore = category._ore_polring - names = normalize_names(names, tau.nrows()) - denominator = Factorization([(self._t - self._theta, twist)]) - OreModule.__init__(self, tau, ore, denominator, names, category) - self.register_action(OreAction(ore, self, True, operator.mul)) - - def _set_dettau(self, disc, degree, twist): - self._dettau = disc, degree - twist + self._twist + self._tau = self._pseudohom.matrix() + if self._denominator: + self._twist = self._denominator[0][1] + else: + self._twist = 0 + self._submodule_class = AndersonSubMotive + self._quotientModule_class = AndersonQuotientMotive @lazy_attribute def _dettau(self): @@ -116,9 +132,9 @@ def _latex_(self): s += "_{%s}" % latex(self._AK) return s - def twist(self, n): - n = ZZ(n) - return AndersonMotive_general(self._category, self._tau, self._twist + n, normalize=False) + def twist(self, n, names): + return AndersonMotive_general(self._category, self._tau, self._twist + ZZ(n), + names, normalize=False) def _Hom_(self, codomain, category): from sage.rings.function_field.drinfeld_modules.anderson_motive_morphism import AndersonMotive_homspace @@ -155,7 +171,7 @@ def __init__(self, phi, names): for i in range(1, r): tau[i-1, i] = 1 tau[r-1, i] = -P[i]/P[r] - AndersonMotive_general.__init__(self, category, tau, 0, names) + AndersonMotive_general.__init__(self, tau, category._ore_polring, None, names, category) Ktau = phi.ore_polring() self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) try: @@ -166,3 +182,15 @@ def __init__(self, phi, names): def drinfeld_module(self): return self._drinfeld_module + + +class AndersonSubMotive(AndersonMotive_general, OreSubmodule): + def __init__(self, ambient, submodule, names): + OreSubmodule.__init__(self, ambient, submodule, names) + self._initialize_attributes() + + +class AndersonQuotientMotive(AndersonMotive_general, OreQuotientModule): + def __init__(self, cover, submodule, names): + OreQuotientModule.__init__(self, cover, submodule, names) + self._initialize_attributes() diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 692ff9511d8..6825b1a09f9 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -930,3 +930,9 @@ def charpoly(self, var='X'): Y^3 + (T + 1)*Y^2 + (2*T + 3)*Y + 2*T^3 + T + 1 """ return self.characteristic_polynomial(var) + + def anderson_motive(self, names_domain=None, names_codomain=None): + M = self.domain().anderson_motive(names=names_domain) + N = self.codomain().anderson_motive(names=names_codomain) + H = N.Hom(M) + return H(self) From 4f8a3e4e691618401297f733011b87e691894850 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 6 Nov 2025 22:33:45 +0100 Subject: [PATCH 30/59] refactorisation of files --- src/sage/modules/ore_module.py | 6 +- src/sage/rings/function_field/all.py | 2 + .../drinfeld_modules/anderson_motive.py | 235 +++++++++++++++++- .../anderson_motive_constructor.py | 157 ------------ .../anderson_motive_morphism.py | 86 ------- .../drinfeld_modules/drinfeld_module.py | 66 ++++- ..._module.py => drinfeld_module_charzero.py} | 8 +- ...ld_module.py => drinfeld_module_finite.py} | 4 +- 8 files changed, 302 insertions(+), 262 deletions(-) delete mode 100644 src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py delete mode 100644 src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py rename src/sage/rings/function_field/drinfeld_modules/{charzero_drinfeld_module.py => drinfeld_module_charzero.py} (97%) rename src/sage/rings/function_field/drinfeld_modules/{finite_drinfeld_module.py => drinfeld_module_finite.py} (99%) diff --git a/src/sage/modules/ore_module.py b/src/sage/modules/ore_module.py index b991fe14598..12c42652fe0 100644 --- a/src/sage/modules/ore_module.py +++ b/src/sage/modules/ore_module.py @@ -2350,8 +2350,7 @@ def rename_basis(self, names, coerce=False): """ rank = self.rank() names = normalize_names(names, rank) - cls = self.__class__ - M = cls.__classcall__(cls, self._ambient, self._submodule, names) + M = super().__classcall__(self.__class__, self._ambient, self._submodule, names) if coerce: mat = identity_matrix(self.base_ring(), rank) id = self.hom(mat, codomain=M) @@ -2896,8 +2895,7 @@ def rename_basis(self, names, coerce=False): """ rank = self.rank() names = normalize_names(names, rank) - cls = self.__class__ - M = cls.__classcall__(cls, self._cover, self._submodule, names) + M = super().__classcall__(self.__class__, self._cover, self._submodule, names) if coerce: mat = identity_matrix(self.base_ring(), rank) id = self.hom(mat, codomain=M) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 459ad867b0d..88f439d1743 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -6,3 +6,5 @@ lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "CarlitzModule") lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_exponential") lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_logarithm") + +lazy_import("sage.rings.function_field.drinfeld_modules.anderson_motive_constructor", "AndersonMotive") diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 58f502fd474..82cff246c8e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -1,3 +1,7 @@ +r""" +Anderson motives +""" + # ***************************************************************************** # Copyright (C) 2024 Xavier Caruso # @@ -14,26 +18,37 @@ from sage.misc.latex import latex from sage.misc.functional import log +from sage.categories.map import Map from sage.categories.homset import Homset +from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.anderson_motives import AndersonMotives from sage.structure.factorization import Factorization from sage.rings.integer_ring import ZZ from sage.rings.infinity import Infinity +from sage.rings.ring import CommutativeRing +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.morphism import RingHomomorphism from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.fraction_field import FractionField_1poly_field +from sage.matrix.matrix0 import Matrix from sage.matrix.constructor import matrix from sage.matrix.special import identity_matrix, block_diagonal_matrix from sage.modules.ore_module import OreModule, OreSubmodule, OreQuotientModule -from sage.modules.ore_module_element import OreModuleElement from sage.modules.ore_module import OreAction from sage.modules.ore_module import normalize_names +from sage.modules.ore_module_element import OreModuleElement +from sage.modules.ore_module_homspace import OreModule_homspace +from sage.modules.ore_module_morphism import OreModuleMorphism -from sage.rings.function_field.drinfeld_modules.anderson_motive_morphism import DrinfeldToAnderson, AndersonToDrinfeld +from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule +from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism +# Classes for Anderson motives +############################## class AndersonMotiveElement(OreModuleElement): def image(self, integral=None): @@ -171,6 +186,7 @@ def __init__(self, phi, names): for i in range(1, r): tau[i-1, i] = 1 tau[r-1, i] = -P[i]/P[r] + names = normalize_names(names, r) AndersonMotive_general.__init__(self, tau, category._ore_polring, None, names, category) Ktau = phi.ore_polring() self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) @@ -194,3 +210,218 @@ class AndersonQuotientMotive(AndersonMotive_general, OreQuotientModule): def __init__(self, cover, submodule, names): OreQuotientModule.__init__(self, cover, submodule, names) self._initialize_attributes() + + +# Morphisms +########### + +# Morphisms between Anderson modules + +class AndersonMotiveMorphism(OreModuleMorphism): + def _repr_type(self): + return "Anderson motive" + + def __init__(self, parent, im_gens, check=True): + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld + if isinstance(im_gens, DrinfeldModuleMorphism): + domain = parent.domain() + codomain = parent.codomain() + if not isinstance(domain, AndersonMotive_drinfeld)\ + or domain.drinfeld_module() is not im_gens.codomain(): + raise ValueError("the domain must be the Anderson module of the codomain of the isogeny") + if not isinstance(codomain, AndersonMotive_drinfeld)\ + or codomain.drinfeld_module() is not im_gens.domain(): + raise ValueError("the codomain must be the Anderson module of the domain of the isogeny") + u = im_gens._ore_polynomial + im_gens = {codomain.gen(0): u*domain.gen(0)} + check = False + OreModuleMorphism.__init__(self, parent, im_gens, check) + + def characteristic_polynomial(self, var='X'): + chi = OreModuleMorphism.characteristic_polynomial(self, var) + A = self.domain().function_ring() + return chi.change_ring(A) + + charpoly = characteristic_polynomial + + +class AndersonMotive_homspace(OreModule_homspace): + Element = AndersonMotiveMorphism + + +# Coercion maps + +class DrinfeldToAnderson(Map): + def __init__(self, parent, phi): + Map.__init__(self, parent) + self._phi = phi + self._motive = parent.codomain() + self._AK = self._motive.base_combined() + + def _call_(self, f): + phi = self._phi + r = phi.rank() + phiT = phi.gen() + coords = [] + for _ in range(r): + coords.append([]) + while f: + f, rem = f.right_quo_rem(phiT) + for i in range(r): + coords[i].append(rem[i]) + coords = [self._AK(c) for c in coords] + return self._motive(coords) + +class AndersonToDrinfeld(Map): + def __init__(self, parent, phi): + Map.__init__(self, parent) + self._phi = phi + self._Ktau = parent.codomain() + + def _call_(self, x): + phi = self._phi + r = phi.rank() + phiT = phi.gen() + S = self._Ktau + xs = [] + for i in range(r): + if x[i].denominator() != 1: + raise ValueError("not in the Anderson motive") + xs.append(x[i].numerator()) + ans = S.zero() + d = max(xi.degree() for xi in xs) + for j in range(d, -1, -1): + ans = ans*phiT + S([xs[i][j] for i in range(r)]) + return ans + + +# Constructor +############# + +def AndersonMotive(arg1, tau=None, names=None): + r""" + Construct an Anderson motive + + INPUT: + + The input can be one of the followings: + + - a Drinfeld module + + - a pair `(A, \tau)` where + + - `A` is either the underlying function ring (which + currently needs to be of the form `\FF_q[t]`) or + a category (of Drinfeld modules or Anderson motives) + + - `\tau` is the matrix defining the Anderson motive + + - a pair '(A, K)` where `A = \FF_q[t]` is the function + base ring and `K` is the coefficient `A`-field; these + parameters correspond to the trivial Anderson motive + over `A \otimes K` + + OUTPUT: + + An anderson motive + + EXAMPLES:: + + + """ + # Options for *args: + # . a Drinfeld module + # . a category (of Drinfeld modules or AndersonMotives) + # . a ring, a matrix + # . a ring, a A-field + # arg1 is a Drinfeld module + if isinstance(arg1, DrinfeldModule): + if tau is not None: + raise ValueError("") + category = AndersonMotives(arg1.category()) + A = category.function_ring() + K = category._base_field + AK = A.change_ring(K) + r = arg1.rank() + tau = matrix(AK, r) + P = arg1.gen() + tau[r-1, 0] = (AK.gen() - P[0]) / P[r] + for i in range(1, r): + tau[i-1, i] = 1 + tau[r-1, i] = -P[i]/P[r] + return AndersonMotive_general(category, tau, names=names) + + # arg1 is a category + category = None + if isinstance(arg1, DrinfeldModules): + category = AndersonMotives(arg1) + if isinstance(arg1, AndersonMotives): + category = arg1 + if category is not None: + if tau is None: + tau = identity_matrix(category.base_combined(), 1) + det = tau.determinant() + if det == 0: + raise ValueError("tau does not define an Anderson motive") + h = det.degree() + disc, R = det.quo_rem(category.divisor() ** h) + if R: + raise ValueError("tau does not define an Anderson motive") + M = AndersonMotive_general(category, tau, names=names) + M._set_dettau(disc[0], h, 0) + return M + + # arg1 is the function ring + if isinstance(arg1, CommutativeRing): + A = arg1 + if not isinstance(A, PolynomialRing_general): + raise NotImplementedError("Anderson motives over arbitrary Dedekind domain are not supported") + else: + raise ValueError("first argument must be the function ring") + + # tau is the base ring + K = None + if isinstance(tau, RingHomomorphism) and tau.domain() is A: + K = tau.codomain() + gamma = tau + elif isinstance(tau, CommutativeRing): + K = tau + gamma = A + if K is not None: + try: + if K.variable_name() == A.variable_name(): + K = K.base_ring() + except (AttributeError, ValueError): + pass + category = AndersonMotives(K.over(gamma)) + AK = category.base_combined() + tau = identity_matrix(AK, 1) + return AndersonMotive_general(category, tau, names=names) + + # tau is a matrix + if isinstance(tau, Matrix): + AK = tau.base_ring() + if not isinstance(AK, PolynomialRing_general) or AK.variable_name() != A.variable_name(): + raise ValueError("incompatible base rings") + det = tau.determinant() + if det == 0: + raise ValueError("tau does not define an Anderson motive") + h = det.degree() + K = AK.base_ring() + gamma = K.coerce_map_from(A) + if gamma is None: + p = A.characteristic() + if h.gcd(p) == 1: + theta = -det[h-1] / det[h] / h + else: + raise NotImplementedError("cannot determine the structure of A-field") + gamma = A.hom([theta]) + category = AndersonMotives(K.over(gamma)) + disc, R = det.quo_rem(category.divisor() ** h) + if R: + raise ValueError("tau does not define an Anderson motive") + M = AndersonMotive_general(category, tau, names=names) + M._set_dettau(disc[0], h, 0) + return M + + raise ValueError("unable to parse arguments") diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py deleted file mode 100644 index 5176b3181f3..00000000000 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_constructor.py +++ /dev/null @@ -1,157 +0,0 @@ -r""" -Constructor for Anderson motives -""" - -# ***************************************************************************** -# Copyright (C) 2024 Xavier Caruso -# -# 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. -# http://www.gnu.org/licenses/ -# ***************************************************************************** - - -from sage.categories.drinfeld_modules import DrinfeldModules -from sage.categories.anderson_motives import AndersonMotives - -from sage.rings.ring import CommutativeRing -from sage.rings.polynomial.polynomial_ring import PolynomialRing_general -from sage.rings.morphism import RingHomomorphism - -from sage.matrix.matrix0 import Matrix -from sage.matrix.constructor import matrix -from sage.matrix.special import identity_matrix - -from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule -from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general - - -def AndersonMotive(arg1, tau=None, names=None): - r""" - Construct an Anderson motive - - INPUT: - - The input can be one of the followings: - - - a Drinfeld module - - - a pair `(A, \tau)` where - - - `A` is either the underlying function ring (which - currently needs to be of the form `\FF_q[t]`) or - a category (of Drinfeld modules or Anderson motives) - - - `\tau` is the matrix defining the Anderson motive - - - a pair '(A, K)` where `A = \FF_q[t]` is the function - base ring and `K` is the coefficient `A`-field; these - parameters correspond to the trivial Anderson motive - over `A \otimes K` - - OUTPUT: - - An anderson motive - - EXAMPLES:: - - - """ - # Options for *args: - # . a Drinfeld module - # . a category (of Drinfeld modules or AndersonMotives) - # . a ring, a matrix - # . a ring, a A-field - # arg1 is a Drinfeld module - if isinstance(arg1, DrinfeldModule): - if tau is not None: - raise ValueError("") - category = AndersonMotives(arg1.category()) - A = category.function_ring() - K = category._base_field - AK = A.change_ring(K) - r = arg1.rank() - tau = matrix(AK, r) - P = arg1.gen() - tau[r-1, 0] = (AK.gen() - P[0]) / P[r] - for i in range(1, r): - tau[i-1, i] = 1 - tau[r-1, i] = -P[i]/P[r] - return AndersonMotive_general(category, tau, names=names) - - # arg1 is a category - category = None - if isinstance(arg1, DrinfeldModules): - category = AndersonMotives(arg1) - if isinstance(arg1, AndersonMotives): - category = arg1 - if category is not None: - if tau is None: - tau = identity_matrix(category.base_combined(), 1) - det = tau.determinant() - if det == 0: - raise ValueError("tau does not define an Anderson motive") - h = det.degree() - disc, R = det.quo_rem(category.divisor() ** h) - if R: - raise ValueError("tau does not define an Anderson motive") - M = AndersonMotive_general(category, tau, names=names) - M._set_dettau(disc[0], h, 0) - return M - - # arg1 is the function ring - if isinstance(arg1, CommutativeRing): - A = arg1 - if not isinstance(A, PolynomialRing_general): - raise NotImplementedError("Anderson motives over arbitrary Dedekind domain are not supported") - else: - raise ValueError("first argument must be the function ring") - - # tau is the base ring - K = None - if isinstance(tau, RingHomomorphism) and tau.domain() is A: - K = tau.codomain() - gamma = tau - elif isinstance(tau, CommutativeRing): - K = tau - gamma = A - if K is not None: - try: - if K.variable_name() == A.variable_name(): - K = K.base_ring() - except (AttributeError, ValueError): - pass - category = AndersonMotives(K.over(gamma)) - AK = category.base_combined() - tau = identity_matrix(AK, 1) - return AndersonMotive_general(category, tau, names=names) - - # tau is a matrix - if isinstance(tau, Matrix): - AK = tau.base_ring() - if not isinstance(AK, PolynomialRing_general) or AK.variable_name() != A.variable_name(): - raise ValueError("incompatible base rings") - det = tau.determinant() - if det == 0: - raise ValueError("tau does not define an Anderson motive") - h = det.degree() - K = AK.base_ring() - gamma = K.coerce_map_from(A) - if gamma is None: - p = A.characteristic() - if h.gcd(p) == 1: - theta = -det[h-1] / det[h] / h - else: - raise NotImplementedError("cannot determine the structure of A-field") - gamma = A.hom([theta]) - category = AndersonMotives(K.over(gamma)) - disc, R = det.quo_rem(category.divisor() ** h) - if R: - raise ValueError("tau does not define an Anderson motive") - M = AndersonMotive_general(category, tau, names=names) - M._set_dettau(disc[0], h, 0) - return M - - raise ValueError("unable to parse arguments") diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py deleted file mode 100644 index eaa861d5fa0..00000000000 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_morphism.py +++ /dev/null @@ -1,86 +0,0 @@ -from sage.categories.map import Map - -from sage.modules.ore_module_homspace import OreModule_homspace -from sage.modules.ore_module_morphism import OreModuleMorphism - -from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism - - -# Morphisms between Anderson modules - -class AndersonMotiveMorphism(OreModuleMorphism): - def _repr_type(self): - return "Anderson motive" - - def __init__(self, parent, im_gens, check=True): - from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld - if isinstance(im_gens, DrinfeldModuleMorphism): - domain = parent.domain() - codomain = parent.codomain() - if not isinstance(domain, AndersonMotive_drinfeld)\ - or domain.drinfeld_module() is not im_gens.codomain(): - raise ValueError("the domain must be the Anderson module of the codomain of the isogeny") - if not isinstance(codomain, AndersonMotive_drinfeld)\ - or codomain.drinfeld_module() is not im_gens.domain(): - raise ValueError("the codomain must be the Anderson module of the domain of the isogeny") - u = im_gens._ore_polynomial - im_gens = {codomain.gen(0): u*domain.gen(0)} - check = False - OreModuleMorphism.__init__(self, parent, im_gens, check) - - def characteristic_polynomial(self, var='X'): - chi = OreModuleMorphism.characteristic_polynomial(self, var) - A = self.domain().function_ring() - return chi.change_ring(A) - - charpoly = characteristic_polynomial - - -class AndersonMotive_homspace(OreModule_homspace): - Element = AndersonMotiveMorphism - - -# Coercion maps - -class DrinfeldToAnderson(Map): - def __init__(self, parent, phi): - Map.__init__(self, parent) - self._phi = phi - self._motive = parent.codomain() - self._AK = self._motive.base_combined() - - def _call_(self, f): - phi = self._phi - r = phi.rank() - phiT = phi.gen() - coords = [] - for _ in range(r): - coords.append([]) - while f: - f, rem = f.right_quo_rem(phiT) - for i in range(r): - coords[i].append(rem[i]) - coords = [self._AK(c) for c in coords] - return self._motive(coords) - -class AndersonToDrinfeld(Map): - def __init__(self, parent, phi): - Map.__init__(self, parent) - self._phi = phi - self._Ktau = parent.codomain() - - def _call_(self, x): - phi = self._phi - r = phi.rank() - phiT = phi.gen() - S = self._Ktau - xs = [] - for i in range(r): - if x[i].denominator() != 1: - raise ValueError("not in the Anderson motive") - xs.append(x[i].numerator()) - ans = S.zero() - d = max(xi.degree() for xi in xs) - for j in range(d, -1, -1): - ans = ans*phiT + S([xs[i][j] for i in range(r)]) - return ans diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index db84997d391..dfec4b045ca 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -7,7 +7,7 @@ For finite Drinfeld modules and their theory of complex multiplication, see class -:class:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_module.drinfeld_module_finite.DrinfeldModule`. AUTHORS: @@ -106,7 +106,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: Finite Drinfeld modules are implemented in the class - :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. + :class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite`. Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1991]_. @@ -533,7 +533,7 @@ def __classcall_private__(cls, function_ring, gen, A_field=None, name='τ'): TESTS:: - sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import DrinfeldModule_finite + sage: from sage.rings.function_field.drinfeld_modules.drinfeld_module_finite import DrinfeldModule_finite sage: Fq = GF(25) sage: A. = Fq[] sage: K. = Fq.extension(6) @@ -608,17 +608,17 @@ def __classcall_private__(cls, function_ring, gen, A_field=None, name='τ'): # Instantiate the appropriate class: if A_field.is_finite(): - from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import DrinfeldModule_finite + from sage.rings.function_field.drinfeld_modules.drinfeld_module_finite import DrinfeldModule_finite return DrinfeldModule_finite(gen, category) if isinstance(A_field, FractionField_generic): ring = A_field.ring() if (isinstance(ring, PolynomialRing_generic) and ring.base_ring() is function_ring_base and base_morphism(T) == ring.gen()): - from .charzero_drinfeld_module import DrinfeldModule_rational + from .drinfeld_module_charzero import DrinfeldModule_rational return DrinfeldModule_rational(gen, category) if not category._characteristic: - from .charzero_drinfeld_module import DrinfeldModule_charzero + from .drinfeld_module_charzero import DrinfeldModule_charzero return DrinfeldModule_charzero(gen, category) return cls.__classcall__(cls, gen, category) @@ -1474,7 +1474,7 @@ def is_finite(self) -> bool: sage: psi.is_finite() False """ - from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import DrinfeldModule_finite + from sage.rings.function_field.drinfeld_modules.drinfeld_module_finite import DrinfeldModule_finite return isinstance(self, DrinfeldModule_finite) def j_invariant(self, parameter=None, check=True): @@ -2085,6 +2085,58 @@ def scalar_multiplication(self, x): return self.Hom(self)(x) def anderson_motive(self, names=None): + r""" + Return the Anderson motive attached to this Drinfeld module. + + By definition, the Anderson motive of a Drinfeld module + `\phi : A \to K\{\tau\}` is `K\{\tau\}` endowed by: + + - the structure of `A`-module where `a \in A` acts by + right multiplication by `phi_a` + + - the structure of `K`-vector space given by standard + left multiplication + + INPUT: + + - ``names`` - a string of a list of strings (default: ``None``), + the names of the vector of the canonical basis; if ``None``, + elements are represented as vectors in `K^d` + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z, 0, 1, z]) + sage: M = phi.anderson_motive() + sage: M + Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + + Here the rank of the Anderson motive should be understood as its + rank over `A \otimes K`; it is also the rank `r` of the underlying + Drinfeld module. More precisely, `M` has a canonical basis, which + is formed by the Ore polynomials `1, \ldots, \tau^{r-1}`. + + sage: tau = phi.ore_variable() + sage: [M(tau^i) for i in range(phi.rank())] + [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + + Setting the argument ``names`` allows to give names to the vectors + of the aforementionned canonical basis:: + + sage: M = phi.anderson_motive(names='e') + sage: M + Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + sage: M.basis() + [e0, e1, e2] + + .. SEEALSO:: + + :mod:`sage.rings.function_field.drinfeld_modules.anderson_motive` + for more documentation on the implementation of Anderson motives + in SageMath. + """ from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld return AndersonMotive_drinfeld(self, names=names) diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py similarity index 97% rename from src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py rename to src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py index 620d85245a0..cce91911a7a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py @@ -3,8 +3,8 @@ Drinfeld modules over rings of characteristic zero This module provides the classes -:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_charzero` and -:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_rational`, +:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module_charzero.DrinfeldModule_charzero` and +:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module_charzero.DrinfeldModule_rational`, which both inherit :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. @@ -65,7 +65,7 @@ class DrinfeldModule_charzero(DrinfeldModule): sage: isinstance(phi, DrinfeldModule) True - sage: from sage.rings.function_field.drinfeld_modules.charzero_drinfeld_module import DrinfeldModule_charzero + sage: from sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero import DrinfeldModule_charzero sage: isinstance(phi, DrinfeldModule_charzero) True @@ -450,7 +450,7 @@ class DrinfeldModule_rational(DrinfeldModule_charzero): sage: C = DrinfeldModule(A, [T, 1]); C Drinfeld module defined by T |--> τ + T sage: type(C) - + """ def coefficient_in_function_ring(self, n): r""" diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py similarity index 99% rename from src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py rename to src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py index 8a0f9234721..1a38962220a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py @@ -3,7 +3,7 @@ Finite Drinfeld modules This module provides the class -:class:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule_finite`, +:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module_finite.DrinfeldModule_finite`, which inherits :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. @@ -64,7 +64,7 @@ class DrinfeldModule_finite(DrinfeldModule): sage: isinstance(phi, DrinfeldModule) True - sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import DrinfeldModule_finite + sage: from sage.rings.function_field.drinfeld_modules.drinfeld_module_finite import DrinfeldModule_finite sage: isinstance(phi, DrinfeldModule_finite) True From 86df1a69e84932d3e4a6434ca07a72122d53958b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 00:05:17 +0100 Subject: [PATCH 31/59] short introduction to Anderson motives --- src/sage/rings/function_field/all.py | 2 +- .../drinfeld_modules/anderson_motive.py | 137 +++++++++++++++++- 2 files changed, 130 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 88f439d1743..adb602488d8 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -7,4 +7,4 @@ lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_exponential") lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_logarithm") -lazy_import("sage.rings.function_field.drinfeld_modules.anderson_motive_constructor", "AndersonMotive") +lazy_import("sage.rings.function_field.drinfeld_modules.anderson_motive", "AndersonMotive") diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 82cff246c8e..b55de13e931 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -1,9 +1,120 @@ r""" Anderson motives + +Let `\GF{q}[T]` be a polynomial ring with coefficients in a finite +field `\GF{q}` and let `K` be an extension of `\GF{q}` equipped +with a distinguished element `z`. + +By definition, an Anderson motive attached to these data is a free +module of finite rank `M` over `K[T]`, equipped with a linear +automorphism + +.. MATH:: + + tau_M : \tau^\star M \left[\frac 1[T-z]\right] \to M \left[\frac 1[T-z]\right] + +where `\tau^\star M = K \otimes_{K, \text{Frob}} M`. + +Any Drinfeld module `phi` over `(A, \gamma)` with `gamma : A \to K, +T \mapsto z` gives rise to an Anderson motive. By definition, it is +`M(\phi) := K\{\tau\}` (the ring of Ore polynomials with commutation +rule `\tau \lambda = \lambda^q \tau` for `\lambda \in K`) where + +- the structure of `\GF{q}[T]`-module is given by right multiplication + by `phi_a` (`a \in \GF{q}[T]`), + +- the structure of `K`-module is given by left multiplication, + +- the automorphism `\tau_{M(\phi)}` is the left multiplication + by `tau` in the Ore polynomial ring. + +Anderson motives are nevertheless much more general than Drinfeld +modules. Besides, their linear nature allows for importing many +interesting construction of linear and bilinear algebra. + +In SageMath, one can create the Anderson motive corresponding to +a Drinfeld module as follows:: + + sage: k = GF(5) + sage: A. = k[] + sage: K. = k.extension(3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3, z^4]) + sage: M = phi.anderson_motive() + sage: M + Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + +We see that `M` has rank `3`; it is actually a general fact that +the Anderson motive attached a Drinfeld module has the same rank +than the underlying Drinfeld module. + +The canonical basis corresponds to the vectors `tau^i` for `i` +varying between `0` and `r-1` where `r` is the rank:: + + sage: tau = phi.ore_variable() + sage: M(tau^0) + (1, 0, 0) + sage: M(tau^1) + (0, 1, 0) + sage: M(tau^2) + (0, 0, 1) + +Higher powers of `\tau` can be rewritten as linear combinations +(over `K[T]`!) of those three ones:: + + sage: M(tau^3) + ((z^2 + 3*z)*T + 2*z^2 + 3*z + 3, 3*z^2 + 2*z + 4, 2*z^2 + 1) + sage: M(tau^4) + ((4*z^2 + 4*z + 3)*T + z^2 + 4*z + 2, (z^2 + 4*z)*T + 3, 3*z^2 + 4*z + 4) + +The matrix of the operator `\tau_M` can be obtained using the method +:meth:`matrix`:: + + sage: M.matrix() + [ 0 1 0] + [ 0 0 1] + [(z^2 + 3*z)*T + 2*z^2 + 3*z + 3 3*z^2 + 2*z + 4 2*z^2 + 1] + +SageMath provides facilities to pick elements in `M` and perform +basic operations with them:: + + sage: u, v, w = M.basis() + sage: T*u + z*w + (T, 0, z) + sage: w.image() # image by tau_M + ((z^2 + 3*z)*T + 2*z^2 + 3*z + 3, 3*z^2 + 2*z + 4, 2*z^2 + 1) + +Some basic constructions on Anderson modules are also available. +For example, one can form the dual:: + + sage: Md = M.dual() + sage: Md + Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + sage: Md.matrix() + [ z^2/(T + 4*z) 1 0] + [ (2*z + 2)/(T + 4*z) 0 1] + [(2*z^2 + 2*z)/(T + 4*z) 0 0] + +or Carlitz twists:: + + sage: M2 = M.carlitz_twist(2) + sage: M2 + Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + sage: M2.matrix() + [ 0 1/(T^2 + 3*z*T + z^2) 0] + [ 0 0 1/(T^2 + 3*z*T + z^2)] + [ (z^2 + 3*z)/(T + 4*z) (3*z^2 + 2*z + 4)/(T^2 + 3*z*T + z^2) (2*z^2 + 1)/(T^2 + 3*z*T + z^2)] + +We observe that the entries of the previous matrices have denominators which are +`T-z` or powers of it. This corresponds to the fact that `\tau_M` is only defined +after inverting `T-z` in full generality. + +AUTHOR: + +- Xavier Caruso (2025-11): initial version """ # ***************************************************************************** -# Copyright (C) 2024 Xavier Caruso +# Copyright (C) 2025 Xavier Caruso # # 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 @@ -147,10 +258,17 @@ def _latex_(self): s += "_{%s}" % latex(self._AK) return s - def twist(self, n, names): + def carlitz_twist(self, n, names=None): return AndersonMotive_general(self._category, self._tau, self._twist + ZZ(n), names, normalize=False) + def dual(self, names=None): + disc, deg = self._dettau + scalar = self._K(~disc) + tau = scalar * self._tau.adjugate().transpose() + twist = deg - self._twist + return AndersonMotive_general(self._category, tau, twist, names, normalize=True) + def _Hom_(self, codomain, category): from sage.rings.function_field.drinfeld_modules.anderson_motive_morphism import AndersonMotive_homspace return AndersonMotive_homspace(self, codomain) @@ -368,7 +486,7 @@ def AndersonMotive(arg1, tau=None, names=None): if R: raise ValueError("tau does not define an Anderson motive") M = AndersonMotive_general(category, tau, names=names) - M._set_dettau(disc[0], h, 0) + #M._set_dettau(disc[0], h, 0) return M # arg1 is the function ring @@ -384,16 +502,19 @@ def AndersonMotive(arg1, tau=None, names=None): if isinstance(tau, RingHomomorphism) and tau.domain() is A: K = tau.codomain() gamma = tau - elif isinstance(tau, CommutativeRing): + elif isinstance(tau, CommutativeRing) and tau.has_coerce_map_from(A): K = tau - gamma = A + gamma = K.coerce_map_from(A) + elif hasattr(tau, 'parent') and isinstance(tau.parent(), CommutativeRing): + K = tau.parent() + gamma = A.hom([tau]) if K is not None: try: if K.variable_name() == A.variable_name(): K = K.base_ring() except (AttributeError, ValueError): pass - category = AndersonMotives(K.over(gamma)) + category = AndersonMotives(gamma) AK = category.base_combined() tau = identity_matrix(AK, 1) return AndersonMotive_general(category, tau, names=names) @@ -416,12 +537,12 @@ def AndersonMotive(arg1, tau=None, names=None): else: raise NotImplementedError("cannot determine the structure of A-field") gamma = A.hom([theta]) - category = AndersonMotives(K.over(gamma)) + category = AndersonMotives(gamma) disc, R = det.quo_rem(category.divisor() ** h) if R: raise ValueError("tau does not define an Anderson motive") M = AndersonMotive_general(category, tau, names=names) - M._set_dettau(disc[0], h, 0) + #M._set_dettau(disc[0], h, 0) return M raise ValueError("unable to parse arguments") From 1475aa1a73b6054914519142e197ebb408a5bb41 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 00:28:24 +0100 Subject: [PATCH 32/59] more documentation --- .../drinfeld_modules/anderson_motive.py | 70 +++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index b55de13e931..060b23978ff 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -15,6 +15,8 @@ where `\tau^\star M = K \otimes_{K, \text{Frob}} M`. +.. RUBRIC:: Anderson motives attached to Drinfeld modules + Any Drinfeld module `phi` over `(A, \gamma)` with `gamma : A \to K, T \mapsto z` gives rise to an Anderson motive. By definition, it is `M(\phi) := K\{\tau\}` (the ring of Ore polynomials with commutation @@ -83,6 +85,8 @@ sage: w.image() # image by tau_M ((z^2 + 3*z)*T + 2*z^2 + 3*z + 3, 3*z^2 + 2*z + 4, 2*z^2 + 1) +.. RUBRIC:: More Anderson motives + Some basic constructions on Anderson modules are also available. For example, one can form the dual:: @@ -104,9 +108,68 @@ [ 0 0 1/(T^2 + 3*z*T + z^2)] [ (z^2 + 3*z)/(T + 4*z) (3*z^2 + 2*z + 4)/(T^2 + 3*z*T + z^2) (2*z^2 + 1)/(T^2 + 3*z*T + z^2)] -We observe that the entries of the previous matrices have denominators which are -`T-z` or powers of it. This corresponds to the fact that `\tau_M` is only defined -after inverting `T-z` in full generality. +We observe that the entries of the previous matrices have denominators +which are `T-z` or powers of it. This corresponds to the fact that +`\tau_M` is only defined after inverting `T-z` in full generality. + +SageMath also provides a general constructor :func:`AndersonMotive` +which allows in particular to explicitely provide the matrix of `tau_M`:: + + sage: mat = matrix(2, 2, [[T, z], [1, 1]]) + sage: N = AndersonMotive(A, mat) + sage: N + Anderson motive of rank 2 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + sage: N.matrix() + [T z] + [1 1] + +.. RUBRIC:: Morphisms between Anderson motives + +One important class of morphisms between Anderson motives are those +which comes from isogenies between Drinfeld modules. +Such morphisms can be built easily as follows:: + + sage: u = phi.hom(tau + z) + sage: u + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (2*z^2 + 2*z)*τ^3 + (2*z + 2)*τ^2 + z^2*τ + z + To: Drinfeld module defined by T |--> (4*z^2 + 2*z + 4)*τ^3 + (4*z^2 + 1)*τ^2 + (z^2 + 2)*τ + z + Defn: τ + z + sage: Mu = u.anderson_motive() + sage: Mu + Anderson motive morphism: + From: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + To: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + sage: Mu.matrix() + [ z 1 0] + [ 0 2*z^2 + 4*z + 4 1] + [(z^2 + 3*z)*T + 2*z^2 + 3*z + 3 3*z^2 + 2*z + 4 2] + +Standard methods of linear algebra are available:: + + sage: Mu.is_injective() + True + sage: Mu.is_surjective() + False + sage: Mu.image().basis() + [(T + 3, 0, 0), (z, 1, 0), (z^2 + 2*z + 1, 0, 1)] + +We check below that the characteristic polynomial of the Frobenius of +`\phi` is equal to the characteristic polynomial of the action of the +Frobenius on the motive:: + + sage: f = phi.frobenius_endomorphism() + sage: f + Endomorphism of Drinfeld module defined by T |--> (2*z^2 + 2*z)*τ^3 + (2*z + 2)*τ^2 + z^2*τ + z + Defn: τ^3 + sage: Mf = f.anderson_motive() + sage: Mf.characteristic_polynomial() + X^3 + (T + 4)*X^2 + 3*T^2*X + 4*T^3 + 2*T + 2 + +:: + + sage: phi.frobenius_charpoly() + X^3 + (T + 4)*X^2 + 3*T^2*X + 4*T^3 + 2*T + 2 AUTHOR: @@ -270,7 +333,6 @@ def dual(self, names=None): return AndersonMotive_general(self._category, tau, twist, names, normalize=True) def _Hom_(self, codomain, category): - from sage.rings.function_field.drinfeld_modules.anderson_motive_morphism import AndersonMotive_homspace return AndersonMotive_homspace(self, codomain) def hodge_pink_weights(self): From 44e5e52688c5c958d0526c2bc1f9df44b5c29067 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 00:35:12 +0100 Subject: [PATCH 33/59] include Anderson motives in the reference manual --- src/doc/en/reference/categories/index.rst | 2 ++ .../en/reference/drinfeld_modules/index.rst | 27 +++++++------------ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index 0475e856129..8bf3b951984 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -48,6 +48,7 @@ Individual Categories sage/categories/algebra_modules sage/categories/algebras sage/categories/algebras_with_basis + sage/categories/anderson_motives sage/categories/aperiodic_semigroups sage/categories/associative_algebras sage/categories/bialgebras @@ -76,6 +77,7 @@ Individual Categories sage/categories/distributive_magmas_and_additive_magmas sage/categories/division_rings sage/categories/domains + sage/categories/drinfeld_modules sage/categories/enumerated_sets sage/categories/euclidean_domains sage/categories/fields diff --git a/src/doc/en/reference/drinfeld_modules/index.rst b/src/doc/en/reference/drinfeld_modules/index.rst index 1aa080d7475..083f2c0404f 100644 --- a/src/doc/en/reference/drinfeld_modules/index.rst +++ b/src/doc/en/reference/drinfeld_modules/index.rst @@ -1,8 +1,8 @@ -Drinfeld modules -==================================== +Drinfeld modules and Anderson motives +===================================== -SageMath include facilities to manipulate Drinfeld modules and their morphisms. The -main entry point is the class +SageMath include facilities to manipulate Drinfeld modules and their morphisms. +The main entry point is the class :class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. Drinfeld modules @@ -12,8 +12,9 @@ Drinfeld modules :maxdepth: 2 sage/rings/function_field/drinfeld_modules/drinfeld_module - sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module - sage/rings/function_field/drinfeld_modules/finite_drinfeld_module + sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero + sage/rings/function_field/drinfeld_modules/drinfeld_module_finite + sage/rings/function_field/drinfeld_modules/action Morphisms and isogenies ----------------------- @@ -24,20 +25,12 @@ Morphisms and isogenies sage/rings/function_field/drinfeld_modules/morphism sage/rings/function_field/drinfeld_modules/homset -The module action induced by a Drinfeld module ----------------------------------------------- - -.. toctree:: - :maxdepth: 2 - - sage/rings/function_field/drinfeld_modules/action - -The category of Drinfeld modules --------------------------------- +Anderson motives +---------------- .. toctree:: :maxdepth: 2 - sage/categories/drinfeld_modules + sage/rings/function_field/drinfeld_modules/anderson_motive .. include:: ../footer.txt From 16c99cdada4a17ebda839f7620bcdeb75cf255c3 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 11:38:25 +0100 Subject: [PATCH 34/59] TestSuite passes --- src/sage/categories/anderson_motives.py | 4 + src/sage/modules/free_module.py | 10 ++- src/sage/modules/ore_module.py | 7 ++ src/sage/modules/ore_module_element.py | 7 +- src/sage/modules/ore_module_morphism.py | 2 +- .../drinfeld_modules/anderson_motive.py | 82 ++++++++++++++++--- 6 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index 7d4a0375f71..d23d11dcbcd 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -36,6 +36,7 @@ def __classcall_private__(cls, category, dispatch=True): return AndersonMotives.__classcall__(cls, category) def __init__(self, category): + self._drinfeld_category = category self._base_morphism = category.base_morphism() self._base_field = category.base() self._function_ring = A = category.function_ring() @@ -55,6 +56,9 @@ def _latex_(self): return f'\\text{{Category{{ }}of{{ }}Anderson{{ }}motives{{ }}' \ f'over{{ }}{latex(self._base_field)}' + def __reduce__(self): + return AndersonMotives, (self._drinfeld_category,) + def _repr_(self): return f'Category of Anderson motives over {self._base_field}' diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index e22f7460edc..2363a5e932c 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -1076,8 +1076,16 @@ def some_elements(self): sage: F = FreeModule(SR, 2) # needs sage.symbolic sage: tuple(F.some_elements()) # needs sage.symbolic ((1, 0), (some_variable, some_variable)) + + TESTS:: + + sage: F = FreeModule(QQ, 0) + sage: tuple(F.some_elements()) + ((),) """ yield self.an_element() + if not self.rank(): + return yield self.base().an_element() * sum(self.gens()) some_elements_base = iter(self.base().some_elements()) n = self.degree() @@ -2481,7 +2489,7 @@ def __iter__(self): """ G = self.gens() if not G: - yield self(0) + yield self.zero() return R = self.base_ring() diff --git a/src/sage/modules/ore_module.py b/src/sage/modules/ore_module.py index 12c42652fe0..fdaf7b4f8ce 100644 --- a/src/sage/modules/ore_module.py +++ b/src/sage/modules/ore_module.py @@ -472,6 +472,13 @@ def _element_constructor_(self, x): (t, 0) sage: M(v) (t, 0) + + TESTS:: + + sage: M(0) + 0 + sage: N(0) + 0 """ if isinstance(x, OreModuleElement): M = x.parent()._pushout_(self) diff --git a/src/sage/modules/ore_module_element.py b/src/sage/modules/ore_module_element.py index 1c5cad90d76..9c6b62ceb79 100644 --- a/src/sage/modules/ore_module_element.py +++ b/src/sage/modules/ore_module_element.py @@ -220,9 +220,10 @@ def image(self, integral=False): M = self.parent() y = M._pseudohom(self) if M._denominator is not None: - den = M._denominator.value() - coords = [num/den for num in y.list()] - if not integral: + base = M.base_ring() + scalar = base(M._denominator.value()).inverse() + coords = [scalar*c for c in y.list()] + if not integral and scalar not in base: M = M.over_fraction_field() y = M(coords) return y diff --git a/src/sage/modules/ore_module_morphism.py b/src/sage/modules/ore_module_morphism.py index 45f66b87773..aaccce0171e 100644 --- a/src/sage/modules/ore_module_morphism.py +++ b/src/sage/modules/ore_module_morphism.py @@ -731,7 +731,7 @@ def _composition_(self, other, homset): True """ if not isinstance(other, OreModuleMorphism): - raise ValueError("the morphism is not a morphism of Ore modules") + raise NotImplementedError("the morphism is not a morphism of Ore modules") return homset(other._matrix * self._matrix, check=False) def inverse(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 060b23978ff..5bac9eb0641 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -224,16 +224,17 @@ # Classes for Anderson motives ############################## -class AndersonMotiveElement(OreModuleElement): - def image(self, integral=None): - if integral is None: - integral = self.parent().is_effective() - return super().image(integral=integral) - - class AndersonMotive_general(OreModule): - Element = AndersonMotiveElement + r""" + General class for Anderson motives. + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: TestSuite(M).run() + """ @staticmethod def __classcall_private__(self, category, tau, twist=0, names=None, normalize=True): K = category.base() @@ -295,9 +296,13 @@ def _initialize_attributes(self): self._twist = self._denominator[0][1] else: self._twist = 0 + self._general_class = AndersonMotive_general self._submodule_class = AndersonSubMotive self._quotientModule_class = AndersonQuotientMotive + def __reduce__(self): + return self._general_class, (self._category, self._tau, self._twist, self._names, False) + @lazy_attribute def _dettau(self): det = self._tau.det() @@ -356,7 +361,18 @@ def ore_polring(self, names=None, action=True): class AndersonMotive_drinfeld(AndersonMotive_general): - def __init__(self, phi, names): + r""" + A class for Anderson motives coming from Drinfeld modules. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: TestSuite(M).run() + """ + def __classcall_private__(cls, phi, names): category = AndersonMotives(phi.category()) AK = category.base_combined() r = phi.rank() @@ -367,7 +383,11 @@ def __init__(self, phi, names): tau[i-1, i] = 1 tau[r-1, i] = -P[i]/P[r] names = normalize_names(names, r) - AndersonMotive_general.__init__(self, tau, category._ore_polring, None, names, category) + denominator = Factorization([]) + return cls.__classcall__(cls, tau, category._ore_polring, denominator, names, category, phi) + + def __init__(self, mat, ore, denominator, names, category, phi) -> None: + super().__init__(mat, ore, denominator, names, category) Ktau = phi.ore_polring() self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) try: @@ -379,18 +399,53 @@ def __init__(self, phi, names): def drinfeld_module(self): return self._drinfeld_module + def __reduce__(self): + return AndersonMotive_drinfeld, (self._drinfeld_module, self._names) + class AndersonSubMotive(AndersonMotive_general, OreSubmodule): + r""" + A class for Anderson motives defined as submodules of an + other Anderson motive. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: N = M.span(v) + sage: TestSuite(N).run() + """ def __init__(self, ambient, submodule, names): OreSubmodule.__init__(self, ambient, submodule, names) self._initialize_attributes() + def __reduce__(self): + return OreSubmodule.__reduce__(self) + class AndersonQuotientMotive(AndersonMotive_general, OreQuotientModule): + r""" + A class for Anderson motives defined as quotients of an + other Anderson motive. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: Q = M.quo(u) + sage: TestSuite(Q).run() + """ def __init__(self, cover, submodule, names): OreQuotientModule.__init__(self, cover, submodule, names) self._initialize_attributes() + def __reduce__(self): + return OreQuotientModule.__reduce__(self) + # Morphisms ########### @@ -564,9 +619,12 @@ def AndersonMotive(arg1, tau=None, names=None): if isinstance(tau, RingHomomorphism) and tau.domain() is A: K = tau.codomain() gamma = tau - elif isinstance(tau, CommutativeRing) and tau.has_coerce_map_from(A): + elif isinstance(tau, CommutativeRing): K = tau - gamma = K.coerce_map_from(A) + if K.has_coerce_map_from(A): + gamma = K.coerce_map_from(A) + else: + gamma = A.hom([K.gen()]) elif hasattr(tau, 'parent') and isinstance(tau.parent(), CommutativeRing): K = tau.parent() gamma = A.hom([tau]) From 88c44d3ab4b63094437a59ba729f301fb681ab44 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 18:04:02 +0100 Subject: [PATCH 35/59] more doctests --- src/sage/categories/anderson_motives.py | 7 + src/sage/modules/ore_module.py | 7 +- src/sage/modules/ore_module_element.py | 3 +- .../drinfeld_modules/anderson_motive.py | 483 ++++++++++++++++-- .../drinfeld_modules/drinfeld_module.py | 2 +- 5 files changed, 467 insertions(+), 35 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index d23d11dcbcd..b7e9a65d9c0 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -133,3 +133,10 @@ def function_ring(self): def constant_coefficient(self): return self._category.constant_coefficient() + + def ore_polring(self): + return self._category._ore_polring + + def ore_variable(self): + return self._category._ore_polring.gen() + diff --git a/src/sage/modules/ore_module.py b/src/sage/modules/ore_module.py index fdaf7b4f8ce..9f5ef29b2c8 100644 --- a/src/sage/modules/ore_module.py +++ b/src/sage/modules/ore_module.py @@ -905,7 +905,12 @@ def matrix(self): """ mat = self._pseudohom.matrix() if self._denominator is not None: - mat /= self._denominator.value() + base = self.base_ring() + scalar = self._denominator.value().inverse() + scalar = base.fraction_field()(scalar) + if scalar in base: + scalar = base(scalar) + mat *= scalar return mat def over_fraction_field(self): diff --git a/src/sage/modules/ore_module_element.py b/src/sage/modules/ore_module_element.py index 9c6b62ceb79..ebb03da128b 100644 --- a/src/sage/modules/ore_module_element.py +++ b/src/sage/modules/ore_module_element.py @@ -221,7 +221,8 @@ def image(self, integral=False): y = M._pseudohom(self) if M._denominator is not None: base = M.base_ring() - scalar = base(M._denominator.value()).inverse() + scalar = M._denominator.value().inverse() + scalar = base.fraction_field()(scalar) coords = [scalar*c for c in y.list()] if not integral and scalar not in base: M = M.over_fraction_field() diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 5bac9eb0641..d671be6bd4a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -211,7 +211,6 @@ from sage.matrix.special import identity_matrix, block_diagonal_matrix from sage.modules.ore_module import OreModule, OreSubmodule, OreQuotientModule -from sage.modules.ore_module import OreAction from sage.modules.ore_module import normalize_names from sage.modules.ore_module_element import OreModuleElement from sage.modules.ore_module_homspace import OreModule_homspace @@ -237,6 +236,24 @@ class AndersonMotive_general(OreModule): """ @staticmethod def __classcall_private__(self, category, tau, twist=0, names=None, normalize=True): + r""" + Normalize the input and return an instance of the appropriate class. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: type(M) + + + :: + + sage: M1 = M.carlitz_twist(-2) + sage: M2 = AndersonMotive(A, M1.matrix()) + sage: M1 is M2 + True + """ K = category.base() AK = category.base_combined() @@ -277,20 +294,21 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr return cls.__classcall__(cls, tau, ore, denominator, names, category) def __init__(self, mat, ore, denominator, names, category) -> None: + r""" + Initialize this Anderson motive. + """ OreModule.__init__(self, mat, ore, denominator, names, category) self._initialize_attributes() def _initialize_attributes(self): - category = self._category - self._A = A = category.function_ring() - self._t_name = A.variable_name() - self._Fq = Fq = A.base_ring() - self._q = Fq.cardinality() - self._deg = ZZ(log(self._q, Fq.characteristic())) - self._K = self._base = K = category.base() - self._theta = category.constant_coefficient() - self._AK = base = category.base_combined() - self._t = base.gen() + r""" + Set the main attributes to this Anderson motive. + + .. NOTE:: + + Separating this method from `__init__` makes it easier + to call it in subclasses. + """ self._tau = self._pseudohom.matrix() if self._denominator: self._twist = self._denominator[0][1] @@ -301,6 +319,24 @@ def _initialize_attributes(self): self._quotientModule_class = AndersonQuotientMotive def __reduce__(self): + r""" + Return the necessary arguments to construct this object, + as per the pickle protocol. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: loads(dumps(M)) is M + True + + :: + + sage: N = M.carlitz_twist(5) + sage: loads(dumps(N)) is N + True + """ return self._general_class, (self._category, self._tau, self._twist, self._names, False) @lazy_attribute @@ -309,31 +345,124 @@ def _dettau(self): return det.leading_coefficient(), det.degree() def _repr_(self): + r""" + Return a string representation of this Anderson motive. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: M # indirect doctest + Anderson motive of rank 1 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + + :: + + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: M # indirect doctest + Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + """ s = "Anderson motive " if self._names is None: s += "of rank %s " % self.rank() else: s += "<" + ", ".join(self._names) + "> " - s += "over %s" % self._AK + s += "over %s" % self.base_combined() return s def _latex_(self): + r""" + Return a string representation of this Anderson motive. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: latex(M) # indirect doctest + \texttt{Anderson motive of rank } 1\texttt{ over } \Bold{F}_{5^{3}}[T] + + :: + + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: latex(M) # indirect doctest + \left_{\Bold{F}_{5^{3}}[T]} + """ + AK = self.base_combined() if self._names is None: s = "\\texttt{Anderson motive of rank } %s" % self.rank() - s += "\\texttt{ over } %s" % latex(self._AK) + s += "\\texttt{ over } %s" % latex(AK) else: s = "\\left<" + ", ".join(self._latex_names) + "\\right>" - s += "_{%s}" % latex(self._AK) + s += "_{%s}" % latex(AK) return s - def carlitz_twist(self, n, names=None): + def carlitz_twist(self, n=1, names=None): + r""" + Return this Anderson motive twisted `n` times. + + INPUT: + + - ``n`` -- an integer (default: ``1``) + + - ``names`` - a string of a list of strings (default: ``None``), + the names of the vector of the canonical basis; if ``None``, + elements are represented as row vectors + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: M.matrix() + [1] + sage: N = M.carlitz_twist() + sage: N.matrix() + [1/(T + 4*z)] + + Negative twist are also permitted:: + + sage: N = M.carlitz_twist(-1) + sage: N.matrix() + [T + 4*z] + """ return AndersonMotive_general(self._category, self._tau, self._twist + ZZ(n), names, normalize=False) def dual(self, names=None): + r""" + Return the dual of this Anderson motive. + + INPUT: + + - ``n`` -- an integer (default: ``1``) + + - ``names`` - a string of a list of strings (default: ``None``), + the names of the vector of the canonical basis; if ``None``, + elements are represented as row vectors + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^4) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: N = M.dual() + sage: N.matrix() + [z^2/(T + 4*z) 1] + [z^3/(T + 4*z) 0] + + We check that the matrix of `\tau_M` is the transpose of the + inverse of the matrix of `\tau_N`:: + + sage: M.matrix() * N.matrix().transpose() + [1 0] + [0 1] + """ disc, deg = self._dettau - scalar = self._K(~disc) - tau = scalar * self._tau.adjugate().transpose() + tau = disc.inverse() * self._tau.adjugate().transpose() twist = deg - self._twist return AndersonMotive_general(self._category, tau, twist, names, normalize=True) @@ -341,24 +470,60 @@ def _Hom_(self, codomain, category): return AndersonMotive_homspace(self, codomain) def hodge_pink_weights(self): + r""" + Return the Hodge-Pink weights of this Anderson motive, + sorted by increasing order. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^4) + sage: phi = DrinfeldModule(A, [z, z^2, z^3, z^4]) + sage: M = phi.anderson_motive() + sage: M.hodge_pink_weights() + [0, 0, 1] + + We check that the Hodge-Pink weights of the dual are the opposite + of the Hodge-Pink weights of the initial Anderson motive:: + + sage: N = M.dual() + sage: N.hodge_pink_weights() + [-1, 0, 0] + + Similarly, we check that Hodge-Pink weights are all shifted by `-1` + after a Carlitz twist:: + + sage: N = M.carlitz_twist() + sage: N.hodge_pink_weights() + [-1, -1, 0] + """ S = self._tau.smith_form(transformation=False) return [-self._twist + S[i,i].degree() for i in range(self.rank())] def is_effective(self): + r""" + Return whether this Anderson module is effective, that is, + whether the action of `\tau` stabilizes it. + This is also equivalent to the fact that all Hodge-Pink weights + are nonnegative. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^4) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: M.is_effective() + True + + :: + + sage: N = M.dual() + sage: N.is_effective() + False + """ return self._twist <= 0 - def ore_variable(self): - return self._category._ore_polring.gen() - - def ore_polring(self, names=None, action=True): - if names is None: - names = self._category._ore_variable_name - S = self._ore_category.ore_ring(names) - if action: - self._unset_coercions_used() - self.register_action(OreAction(S, self, True, operator.mul)) - return S - class AndersonMotive_drinfeld(AndersonMotive_general): r""" @@ -373,6 +538,24 @@ class AndersonMotive_drinfeld(AndersonMotive_general): sage: TestSuite(M).run() """ def __classcall_private__(cls, phi, names): + r""" + Normalize the input and construct this Anderson motive. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive(names='e') + sage: type(M) + + + :: + + sage: N. = phi.anderson_motive() + sage: M is N + True + """ category = AndersonMotives(phi.category()) AK = category.base_combined() r = phi.rank() @@ -387,6 +570,19 @@ def __classcall_private__(cls, phi, names): return cls.__classcall__(cls, tau, category._ore_polring, denominator, names, category, phi) def __init__(self, mat, ore, denominator, names, category, phi) -> None: + r""" + Initialize this Anderson motive. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: tau = phi.ore_variable() + sage: M = phi.anderson_motive() + sage: M(tau) + (0, 1) + """ super().__init__(mat, ore, denominator, names, category) Ktau = phi.ore_polring() self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) @@ -396,12 +592,40 @@ def __init__(self, mat, ore, denominator, names, category, phi) -> None: pass self._drinfeld_module = phi - def drinfeld_module(self): - return self._drinfeld_module - def __reduce__(self): + r""" + Return the necessary arguments to construct this object, + as per the pickle protocol. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: loads(dumps(M)) is M + True + """ return AndersonMotive_drinfeld, (self._drinfeld_module, self._names) + def drinfeld_module(self): + r""" + Return the Drinfeld module from which this Anderson motive + was constructed. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^5) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: M.drinfeld_module() + Drinfeld module defined by T |--> z^3*τ^2 + z^2*τ + z + sage: M.drinfeld_module() is phi + True + """ + return self._drinfeld_module + class AndersonSubMotive(AndersonMotive_general, OreSubmodule): r""" @@ -418,10 +642,42 @@ class AndersonSubMotive(AndersonMotive_general, OreSubmodule): sage: TestSuite(N).run() """ def __init__(self, ambient, submodule, names): + r""" + Initialize this Anderson motive. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: N = M.span(v) + sage: N.ambient_module() is M + True + + :: + + sage: type(N) + + """ OreSubmodule.__init__(self, ambient, submodule, names) self._initialize_attributes() def __reduce__(self): + r""" + Return the necessary arguments to construct this object, + as per the pickle protocol. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: N = M.span(v) + sage: loads(dumps(N)) is N + True + """ return OreSubmodule.__reduce__(self) @@ -440,10 +696,37 @@ class AndersonQuotientMotive(AndersonMotive_general, OreQuotientModule): sage: TestSuite(Q).run() """ def __init__(self, cover, submodule, names): + r""" + Initialize this Anderson motive. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M. = AndersonMotive(A, diagonal_matrix([1, T - z])) + sage: Q = M.quo(u) + sage: Q.cover() is M + True + sage: type(Q) + + """ OreQuotientModule.__init__(self, cover, submodule, names) self._initialize_attributes() def __reduce__(self): + r""" + Return the necessary arguments to construct this object, + as per the pickle protocol. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: loads(dumps(M)) is M + True + """ return OreQuotientModule.__reduce__(self) @@ -453,10 +736,49 @@ def __reduce__(self): # Morphisms between Anderson modules class AndersonMotiveMorphism(OreModuleMorphism): + r""" + A class for morphisms betweeen Anderson motives. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: u = phi.scalar_multiplication(T) + sage: f = u.anderson_motive() + sage: TestSuite(f).run() + """ def _repr_type(self): + r""" + Return a string representation of the type of this morphism. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: u = phi.scalar_multiplication(T) + sage: u.anderson_motive() # indirect doctest + Anderson motive endomorphism of Anderson motive of rank 2 over + Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + """ return "Anderson motive" def __init__(self, parent, im_gens, check=True): + r""" + Initialize this morphism. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: M = AndersonMotive(A, K) + sage: E = End(M) + sage: f = E(matrix(1, 1, [T])) + sage: f + Anderson motive endomorphism of Anderson motive of rank 1 over + Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + """ from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld if isinstance(im_gens, DrinfeldModuleMorphism): domain = parent.domain() @@ -473,6 +795,35 @@ def __init__(self, parent, im_gens, check=True): OreModuleMorphism.__init__(self, parent, im_gens, check) def characteristic_polynomial(self, var='X'): + r""" + Return the characteristic polynomial of this morphism. + + INPUT: + + - ``var`` -- a string (default: ``X``), the name of the variable + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: f = phi.scalar_multiplication(T).anderson_motive() + sage: chi = f.characteristic_polynomial() + sage: chi + X^2 + 3*T*X + T^2 + sage: chi.factor() + (4*X + T)^2 + + We compute the characteristic polynomial of the Frobenius and + compare the result with the output of the method + :meth:`frobenius_charpoly`:: + + sage: Frob = phi.frobenius_endomorphism().anderson_motive() + sage: Frob.characteristic_polynomial() + X^2 + X + 3*T^3 + 4*T + 4 + sage: phi.frobenius_charpoly() + X^2 + X + 3*T^3 + 4*T + 4 + """ chi = OreModuleMorphism.characteristic_polynomial(self, var) A = self.domain().function_ring() return chi.change_ring(A) @@ -487,13 +838,49 @@ class AndersonMotive_homspace(OreModule_homspace): # Coercion maps class DrinfeldToAnderson(Map): + r""" + The canonical isomorphism `K\{\tau\} \to M(\phi)` + for a Drinfeld module `\phi : A \to K\{\tau\}`. + """ def __init__(self, parent, phi): + r""" + Initialize this map. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: Ktau = phi.ore_polring() + sage: M = phi.anderson_motive() + sage: f = Ktau.convert_map_from(M) + sage: type(f) + + sage: # TestSuite(f).run() + """ Map.__init__(self, parent) self._phi = phi self._motive = parent.codomain() self._AK = self._motive.base_combined() def _call_(self, f): + r""" + Return the image of `f` in the Anderson motive. + + INPUT: + + - ``f`` -- a Ore polynomial + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: tau = phi.ore_variable() + sage: M(tau) # indirect doctest + (0, 1) + """ phi = self._phi r = phi.rank() phiT = phi.gen() @@ -508,12 +895,44 @@ def _call_(self, f): return self._motive(coords) class AndersonToDrinfeld(Map): + r""" + The canonical isomorphism `M(\phi) \to K\{\tau\}` + for a Drinfeld module `\phi : A \to K\{\tau\}`. + """ def __init__(self, parent, phi): + r""" + Initialize this map. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^4]) + sage: Ktau = phi.ore_polring() + sage: M = phi.anderson_motive() + sage: f = M.coerce_map_from(Ktau) + sage: type(f) + + sage: # TestSuite(f).run() + """ Map.__init__(self, parent) self._phi = phi self._Ktau = parent.codomain() def _call_(self, x): + r""" + Initialize this map. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^4]) + sage: tau = phi.ore_variable() + sage: M. = phi.anderson_motive() + sage: u + tau + u + v + """ phi = self._phi r = phi.rank() phiT = phi.gen() diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index dfec4b045ca..4e7ab57fe53 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2101,7 +2101,7 @@ def anderson_motive(self, names=None): - ``names`` - a string of a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, - elements are represented as vectors in `K^d` + elements are represented as row vectors EXAMPLES:: From e32fb36641170c5e0d5595b60c2f6b06a4008562 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 20:04:52 +0100 Subject: [PATCH 36/59] doctests in anderson_motive.py --- src/sage/categories/anderson_motives.py | 23 +-- .../drinfeld_modules/anderson_motive.py | 172 +++++++++++++++--- 2 files changed, 149 insertions(+), 46 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index b7e9a65d9c0..7206a974fb0 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -68,13 +68,11 @@ def Homsets(self): def Endsets(self): return Homsets().Endsets() - def base_morphism(self): - return self._base_morphism + def A_field(self): + f = self._base_morphism + return f.codomain().over(f) - def base_over_constants_field(self): - return self._base_over_constants_field - - def base_combined(self): + def base(self): return self._base_combined def divisor(self): @@ -113,18 +111,12 @@ def super_categories(self): class ParentMethods: + def A_field(self): + return self._category.A_field() + def base(self): return self._category.base() - def base_morphism(self): - return self._category.base_morphism() - - def base_combined(self): - return self._category.base_combined() - - def base_over_constants_field(self): - return self._category.base_over_constants_field() - def characteristic(self): return self._category.characteristic() @@ -139,4 +131,3 @@ def ore_polring(self): def ore_variable(self): return self._category._ore_polring.gen() - diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index d671be6bd4a..69287809c81 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -254,8 +254,8 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr sage: M1 is M2 True """ - K = category.base() - AK = category.base_combined() + AK = category.base() + K = AK.base_ring() # We normalize the inputs twist = ZZ(twist) @@ -341,6 +341,21 @@ def __reduce__(self): @lazy_attribute def _dettau(self): + r""" + Return the leading coefficient of the determinant of `tau` + and its degree. + + Only for internal use. + + TESTS:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M = phi.anderson_motive() + sage: M._dettau + (2*z^2 + 3*z + 3, 1) + """ det = self._tau.det() return det.leading_coefficient(), det.degree() @@ -368,7 +383,7 @@ def _repr_(self): s += "of rank %s " % self.rank() else: s += "<" + ", ".join(self._names) + "> " - s += "over %s" % self.base_combined() + s += "over %s" % self.base() return s def _latex_(self): @@ -390,7 +405,7 @@ def _latex_(self): sage: latex(M) # indirect doctest \left_{\Bold{F}_{5^{3}}[T]} """ - AK = self.base_combined() + AK = self.base() if self._names is None: s = "\\texttt{Anderson motive of rank } %s" % self.rank() s += "\\texttt{ over } %s" % latex(AK) @@ -466,8 +481,33 @@ def dual(self, names=None): twist = deg - self._twist return AndersonMotive_general(self._category, tau, twist, names, normalize=True) - def _Hom_(self, codomain, category): - return AndersonMotive_homspace(self, codomain) + def _Hom_(self, other, category): + r""" + Return the set of morphisms from ``self`` to ``other``. + + INPUT: + + - ``other`` -- the codomain of the homset + + - ``category`` -- the category in which we consider the + morphisms, usually ``self.category()`` + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^4) + sage: phi = DrinfeldModule(A, [z, z^2, z^3, z^4]) + sage: M = phi.anderson_motive() + sage: End(M) # indirect doctest + Set of Morphisms + from Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^4 + to Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^4 + in Category of finite dimensional Ore modules with basis + over Univariate Polynomial Ring in T over Finite Field in z of size 5^4 twisted by T |--> T, with map of base ring + """ + if category is None: + category = self._category + return AndersonMotive_homspace(self, other, category) def hodge_pink_weights(self): r""" @@ -557,7 +597,7 @@ def __classcall_private__(cls, phi, names): True """ category = AndersonMotives(phi.category()) - AK = category.base_combined() + AK = category.base() r = phi.rank() tau = matrix(AK, r) P = phi.gen() @@ -856,12 +896,11 @@ def __init__(self, parent, phi): sage: f = Ktau.convert_map_from(M) sage: type(f) - sage: # TestSuite(f).run() """ Map.__init__(self, parent) self._phi = phi self._motive = parent.codomain() - self._AK = self._motive.base_combined() + self._AK = self._motive.base() def _call_(self, f): r""" @@ -913,7 +952,6 @@ def __init__(self, parent, phi): sage: f = M.coerce_map_from(Ktau) sage: type(f) - sage: # TestSuite(f).run() """ Map.__init__(self, parent) self._phi = phi @@ -960,7 +998,15 @@ def AndersonMotive(arg1, tau=None, names=None): The input can be one of the followings: - - a Drinfeld module + - a pair '(A, K)` where `A = \FF_q[t]` is the function + base ring and `K` is the coefficient `A`-field; these + parameters correspond to the trivial Anderson motive + over `A \otimes K` + + - a pair '(A, z)` where `A = \FF_q[t]` is the function + base ring and `z` is an element; the `A`-field is then + then parent `K` of `z` viewed as an algebra over `A` + through `A \mapsto K, T \mapsto z`. - a pair `(A, \tau)` where @@ -970,18 +1016,89 @@ def AndersonMotive(arg1, tau=None, names=None): - `\tau` is the matrix defining the Anderson motive - - a pair '(A, K)` where `A = \FF_q[t]` is the function - base ring and `K` is the coefficient `A`-field; these - parameters correspond to the trivial Anderson motive - over `A \otimes K` + - a Drinfeld module + + EXAMPLES: + + sage: A. = GF(7)[] + sage: K. = GF(7^3) + + We first construct the trivial Anderson motive over `K`:: + + sage: M = AndersonMotive(A, K) + sage: M + Anderson motive of rank 1 over Univariate Polynomial Ring in T over Finite Field in z of size 7^3 + sage: M.matrix() + [1] + + Here the structure of `A`-field on `K` is given by the map + that takes `T` to the canonical generator of `K`, namely `z`:: + + sage: M.A_field() + Finite Field in z of size 7^3 over its base + sage: M.A_field().defining_morphism() + Ring morphism: + From: Univariate Polynomial Ring in T over Finite Field of size 7 + To: Finite Field in z of size 7^3 over its base + Defn: T |--> z + + Specifying another element in `K` leads to a different + structure of `A`-field:: + + sage: N = AndersonMotive(A, z^2) + sage: N.A_field().defining_morphism() + Ring morphism: + From: Univariate Polynomial Ring in T over Finite Field of size 7 + To: Finite Field in z of size 7^3 over its base + Defn: T |--> z^2 + + One can also directly construct the Anderson motive attached + to a Drinfeld module as follows:: + + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: AndersonMotive(phi) + Anderson motive of rank 2 over Univariate Polynomial Ring in T over Finite Field in z of size 7^3 + + Finally, another possibility is to give the matrix of `\tau` as an + argument:: + + sage: tau = matrix(2, 2, [[T, z], [z+1, 1]]) + sage: tau + [ T z] + [z + 1 1] + sage: M = AndersonMotive(A, tau) + sage: M + Anderson motive of rank 2 over Univariate Polynomial Ring in T over Finite Field in z of size 7^3 + sage: M.matrix() + [ T z] + [z + 1 1] + + In this case, the structure of `A`-field is automatically inferred:: + + sage: M.A_field().defining_morphism() + Ring morphism: + From: Univariate Polynomial Ring in T over Finite Field of size 7 + To: Finite Field in z of size 7^3 over its base + Defn: T |--> z^2 + z + + TESTS:: - OUTPUT: + sage: AndersonMotive(ZZ, K) + Traceback (most recent call last): + ... + TypeError: the first argument must be a Drinfeld module or a polynomial ring - An anderson motive + :: - EXAMPLES:: + sage: tau = matrix(2, 2, [[T^2, z], [z+1, 1]]) + sage: AndersonMotive(A, tau) + Traceback (most recent call last): + ... + ValueError: tau does not define an Anderson motive + .. SEEALSO:: + :mod:`sage.rings.function_field.drinfeld_modules.anderson_motive` """ # Options for *args: # . a Drinfeld module @@ -993,9 +1110,7 @@ def AndersonMotive(arg1, tau=None, names=None): if tau is not None: raise ValueError("") category = AndersonMotives(arg1.category()) - A = category.function_ring() - K = category._base_field - AK = A.change_ring(K) + AK = category.base() r = arg1.rank() tau = matrix(AK, r) P = arg1.gen() @@ -1013,7 +1128,7 @@ def AndersonMotive(arg1, tau=None, names=None): category = arg1 if category is not None: if tau is None: - tau = identity_matrix(category.base_combined(), 1) + tau = identity_matrix(category.base(), 1) det = tau.determinant() if det == 0: raise ValueError("tau does not define an Anderson motive") @@ -1026,12 +1141,9 @@ def AndersonMotive(arg1, tau=None, names=None): return M # arg1 is the function ring - if isinstance(arg1, CommutativeRing): - A = arg1 - if not isinstance(A, PolynomialRing_general): - raise NotImplementedError("Anderson motives over arbitrary Dedekind domain are not supported") - else: - raise ValueError("first argument must be the function ring") + A = arg1 + if not isinstance(A, PolynomialRing_general): + raise TypeError("the first argument must be a Drinfeld module or a polynomial ring") # tau is the base ring K = None @@ -1054,7 +1166,7 @@ def AndersonMotive(arg1, tau=None, names=None): except (AttributeError, ValueError): pass category = AndersonMotives(gamma) - AK = category.base_combined() + AK = category.base() tau = identity_matrix(AK, 1) return AndersonMotive_general(category, tau, names=names) @@ -1062,7 +1174,7 @@ def AndersonMotive(arg1, tau=None, names=None): if isinstance(tau, Matrix): AK = tau.base_ring() if not isinstance(AK, PolynomialRing_general) or AK.variable_name() != A.variable_name(): - raise ValueError("incompatible base rings") + raise TypeError("incompatible base rings") det = tau.determinant() if det == 0: raise ValueError("tau does not define an Anderson motive") From 104bef05da02b2cb2994ebe56af85aca80eca587 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 20:19:59 +0100 Subject: [PATCH 37/59] doctest for the method anderson_motive in morphism.py --- .../drinfeld_modules/anderson_motive.py | 4 +- .../drinfeld_modules/morphism.py | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 69287809c81..3077e7812b0 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -422,7 +422,7 @@ def carlitz_twist(self, n=1, names=None): - ``n`` -- an integer (default: ``1``) - - ``names`` - a string of a list of strings (default: ``None``), + - ``names`` -- a string of a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements are represented as row vectors @@ -856,7 +856,7 @@ def characteristic_polynomial(self, var='X'): We compute the characteristic polynomial of the Frobenius and compare the result with the output of the method - :meth:`frobenius_charpoly`:: + :meth:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite.frobenius_charpoly`:: sage: Frob = phi.frobenius_endomorphism().anderson_motive() sage: Frob.characteristic_polynomial() diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 6825b1a09f9..fe69aaf5580 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -932,6 +932,59 @@ def charpoly(self, var='X'): return self.characteristic_polynomial(var) def anderson_motive(self, names_domain=None, names_codomain=None): + r""" + Return the morphism giving the action of this isogeny on + the Anderson motives. + + INPUT: + + - ``names_domain`` -- a string of a list of strings (default: + ``None``), the names of the vector of the canonical basis + of the Anderson motive of the domain of this isogeny; if + ``None``, elements are represented as row vectors + + - ``names_codomain`` -- a string of a list of strings (default: + ``None``), the same with the codomain + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z, 0, 1, z]) + sage: tau = phi.ore_variable() + sage: u = phi.hom(tau + 1) + sage: f = u.anderson_motive() + sage: f + Anderson motive morphism: + From: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + To: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + sage: f.matrix() + [ 1 1 0] + [ 0 1 1] + [(3*z^2 + 4)*T + 4 0 2*z^2 + 2] + + We underline that this construction is contravariant: the domain + of `f` is the Anderson motive of the codomain of `u` and vice versa:: + + sage: psi = u.codomain() + sage: f.domain() is psi.anderson_motive() + True + sage: f.codomain() is phi.anderson_motive() + True + + An example with given names:: + + sage: f = u.anderson_motive(names_domain='a', names_codomain='b') + sage: f + Anderson motive morphism: + From: Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + To: Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + + .. SEEALSO:: + + :mod:`sage.rings.function_field.drinfeld_modules.anderson_motive` + """ M = self.domain().anderson_motive(names=names_domain) N = self.codomain().anderson_motive(names=names_codomain) H = N.Hom(M) From b69013d4c72a56dc62bc7d310184c8244523e5e5 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 22:06:29 +0100 Subject: [PATCH 38/59] doctests in the category --- src/sage/categories/anderson_motives.py | 352 ++++++++++++++++++++++-- src/sage/categories/drinfeld_modules.py | 1 - 2 files changed, 329 insertions(+), 24 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index 7206a974fb0..b2b53f28885 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -1,9 +1,13 @@ r""" Anderson motives + +AUTHOR: + +- Xavier Caruso (2025-11): initial version """ # ***************************************************************************** -# Copyright (C) 2024 Xavier Caruso +# Copyright (C) 2025 Xavier Caruso # # 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 @@ -25,8 +29,42 @@ class AndersonMotives(OreModules): + r""" + The category of Anderson motives. + + .. SEEALSO:: + + :class:`sage.category.drinfeld_modules.DrinfeldModules`, + :mod:`sage.rings.function_field.drinfeld_modules.anderson_motive` + """ @staticmethod - def __classcall_private__(cls, category, dispatch=True): + def __classcall_private__(cls, category): + r""" + Normalize the input and construct the category. + + INPUT: + + - ``category`` -- the corresponding category of Drinfeld + modules, or an object for constructing it + + TESTS:: + + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: C1 = AndersonMotives(phi.category()) + sage: C1 + Category of Anderson motives over Univariate Polynomial Ring in T over Finite Field in z of size 3^3 + + sage: C2 = AndersonMotives(phi.base_morphism()) + sage: C2 + Category of Anderson motives over Univariate Polynomial Ring in T over Finite Field in z of size 3^3 + + sage: C1 is C2 + True + """ if isinstance(category, AndersonMotives): return category if isinstance(category, DrinfeldModules): @@ -36,6 +74,22 @@ def __classcall_private__(cls, category, dispatch=True): return AndersonMotives.__classcall__(cls, category) def __init__(self, category): + r""" + Initialize this category. + + INPUT: + + - ``category`` -- the corresponding category of Drinfeld modules + + TESTS:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: TestSuite(C).run() + """ self._drinfeld_category = category self._base_morphism = category.base_morphism() self._base_field = category.base() @@ -52,82 +106,334 @@ def __init__(self, category): self._ore_polring = OrePolynomialRing(AK, twisting_morphism, names=self._ore_variable_name) super().__init__(self._ore_polring) + def _repr_(self): + r""" + Return a string representation of this category. + + TESTS:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: C # indirect doctest + Category of Anderson motives over Univariate Polynomial Ring in T over Finite Field in z of size 3^3 + """ + return f'Category of Anderson motives over {self.base()}' + def _latex_(self): + r""" + Return a LaTeX representation of this category. + + TESTS:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: latex(C) # indirect doctest + \text{Category{ }of{ }Anderson{ }motives{ }over{ }\Bold{F}_{3^{3}}[T] + """ return f'\\text{{Category{{ }}of{{ }}Anderson{{ }}motives{{ }}' \ - f'over{{ }}{latex(self._base_field)}' + f'over{{ }}{latex(self.base())}' def __reduce__(self): - return AndersonMotives, (self._drinfeld_category,) + r""" + Return the necessary arguments to construct this object, + as per the pickle protocol. - def _repr_(self): - return f'Category of Anderson motives over {self._base_field}' + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: loads(dumps(C)) is C + True + """ + return AndersonMotives, (self._drinfeld_category,) def Homsets(self): + r""" + Return the category of homsets. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + + sage: from sage.categories.homsets import Homsets + sage: C.Homsets() is Homsets() + True + """ return Homsets() def Endsets(self): + r""" + Return the category of endsets. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + + sage: from sage.categories.homsets import Homsets + sage: C.Endsets() is Homsets().Endsets() + True + """ return Homsets().Endsets() def A_field(self): + r""" + Return the underlying `A`-field of this category. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: C.A_field() + Finite Field in z of size 3^3 over its base + """ f = self._base_morphism return f.codomain().over(f) def base(self): + r""" + Return the base over which the Anderson motives in this + category are defined. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: C.base() + Univariate Polynomial Ring in T over Finite Field in z of size 3^3 + """ return self._base_combined def divisor(self): + r""" + Return the polynomial `T - z` if `T` denotes the generator of + the function ring `A` and `z` is the image of `T` in the `A`-field. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: C.divisor() + T + 2*z + """ return self._divisor def characteristic(self): + r""" + Return the characteristic of the underlying `A`-field. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: C.divisor() + T + 2*z + """ if self._characteristic is None: raise NotImplementedError('function ring characteristic not ' 'implemented in this case') return self._characteristic - def constant_coefficient(self): - return self._constant_coefficient - def function_ring(self): + r""" + Return the underlying function ring of this category. + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + sage: C.function_ring() + Univariate Polynomial Ring in T over Finite Field of size 3 + """ return self._function_ring - def object(self, gen): - raise NotImplementedError + def object(self, tau=None): + r""" + Return the object in this category with `\tau`-action + given by the matrix ``tau``. + + INPUT: + + - ``tau`` -- a matrix or ``None`` (default: ``None``); + if ``None``, return the trivial Anderson module in this + category + + EXAMPLES:: + + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) + + sage: M = C.object() + sage: M + Anderson motive of rank 1 over Univariate Polynomial Ring in T over Finite Field in z of size 3^3 + sage: M.matrix() + [1] + + :: + + sage: tau = matrix(2, 2, [[T, 1], [z, 1]]) + sage: N = C.object(tau) + sage: N.matrix() + [T 1] + [z 1] + """ + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive + return AndersonMotive(self, tau) def super_categories(self): """ EXAMPLES:: - sage: Fq = GF(11) - sage: A. = Fq[] - sage: K. = Fq.extension(4) - sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: C = phi.category() + sage: from sage.categories.anderson_motives import AndersonMotives + sage: A. = GF(3)[] + sage: K. = GF(3^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: C = AndersonMotives(phi.category()) sage: C.super_categories() - [Category of objects] + [Category of Ore modules over Univariate Polynomial Ring in T over Finite Field in z of size 3^3 twisted by T |--> T, with map of base ring] """ S = self._ore_polring return [OreModules(S.base(), S)] class ParentMethods: + def function_ring(self): + r""" + Return the underlying function ring of this Anderson motive. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, z^3, z^5]) + sage: M = phi.anderson_motive() + sage: M.function_ring() + Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 + """ + return self._category.function_ring() + def A_field(self): + r""" + Return the underlying `A`-field of this Anderson motive. + + This is an instance of the class + :class:`sage.rings.ring_extension.RingExtension`. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, z^3, z^5]) + sage: M = phi.anderson_motive() + sage: M.A_field() + Finite Field in z of size 5^12 over its base + """ return self._category.A_field() def base(self): + r""" + Return the base ring over which this Anderson motive + is defined. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, z^3, z^5]) + sage: M = phi.anderson_motive() + sage: M.base() + Univariate Polynomial Ring in T over Finite Field in z of size 5^12 + """ return self._category.base() def characteristic(self): - return self._category.characteristic() + r""" + Return the characteristic of the underlying `A`-field. - def function_ring(self): - return self._category.function_ring() + EXAMPLES:: - def constant_coefficient(self): - return self._category.constant_coefficient() + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, z^3, z^5]) + sage: M = phi.anderson_motive() + sage: M.characteristic() + T^6 + (4*z2 + 3)*T^5 + T^4 + (3*z2 + 1)*T^3 + T^2 + (4*z2 + 1)*T + z2 + """ + return self._category.characteristic() def ore_polring(self): + r""" + Return the Ore polynomial ring over which this Anderson + motive is defined. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, z^3, z^5]) + sage: M = phi.anderson_motive() + sage: M.ore_polring() + Ore Polynomial Ring in τ over Univariate Polynomial Ring in T + over Finite Field in z of size 5^12 twisted by T |--> T, with map of base ring + """ return self._category._ore_polring def ore_variable(self): + r""" + Return the generator of the Ore polynomial ring over which + this Anderson motive is defined. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, z^3, z^5]) + sage: M = phi.anderson_motive() + sage: tau = M.ore_variable() + sage: tau + τ + sage: tau.parent() + Ore Polynomial Ring in τ over Univariate Polynomial Ring in T + over Finite Field in z of size 5^12 twisted by T |--> T, with map of base ring + """ return self._category._ore_polring.gen() diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 707ee92def9..d9987e192e3 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -205,7 +205,6 @@ class DrinfeldModules(Category_over_base_ring): ... TypeError: function ring base must be a finite field """ - def __init__(self, base_morphism, name='τ'): r""" Initialize ``self``. From 02d73812cd059f8ce01ddd23f4e5df8e184ddb21 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 22:17:44 +0100 Subject: [PATCH 39/59] add INPUT sections --- .../drinfeld_modules/anderson_motive.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 3077e7812b0..4a6669fdcb5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -239,6 +239,25 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr r""" Normalize the input and return an instance of the appropriate class. + INPUT: + + - ``category`` -- the category of Anderson motives where this + Anderson motive leaves + + - ``tau`` -- a matrix + + - ``twist`` -- an integer (default: ``0``) + + - ``names`` -- a string of a list of strings (default: ``None``), + the names of the vector of the canonical basis; if ``None``, + elements will be represented as row vectors + + - ``normalize`` -- a boolean (default: ``True``) + + The action of `\tau` on the Anderson motive will be given by + the matrix ``tau * (T - z)**(-twist)`` where `T` is the variable + of the function ring and `z` is its image in the `A`-field. + TESTS:: sage: A. = GF(5)[] @@ -581,6 +600,14 @@ def __classcall_private__(cls, phi, names): r""" Normalize the input and construct this Anderson motive. + INPUT: + + - ``phi`` -- a Drinfeld module + + - ``names`` -- a string of a list of strings (default: ``None``), + the names of the vector of the canonical basis; if ``None``, + elements will be represented as row vectors + TESTS:: sage: A. = GF(5)[] From 333410c876f9f6b0dd5bb2d4a098633bd7972937 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 7 Nov 2025 22:20:57 +0100 Subject: [PATCH 40/59] lint --- .../rings/function_field/drinfeld_modules/anderson_motive.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 4a6669fdcb5..b72c82d169e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -960,6 +960,7 @@ def _call_(self, f): coords = [self._AK(c) for c in coords] return self._motive(coords) + class AndersonToDrinfeld(Map): r""" The canonical isomorphism `M(\phi) \to K\{\tau\}` From 874e6d4e4cffe7b5f43a1936269e25164088bfe4 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 8 Nov 2025 08:46:53 +0100 Subject: [PATCH 41/59] fix failing doctests and documentation --- src/sage/modules/free_module.py | 5 +++-- src/sage/modules/ore_module.py | 4 ++-- .../drinfeld_modules/anderson_motive.py | 22 +++++++++---------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 2363a5e932c..d94f903abc7 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -1084,9 +1084,10 @@ def some_elements(self): ((),) """ yield self.an_element() - if not self.rank(): + gens = self.gens() + if not gens: return - yield self.base().an_element() * sum(self.gens()) + yield self.base().an_element() * sum(gens) some_elements_base = iter(self.base().some_elements()) n = self.degree() while True: diff --git a/src/sage/modules/ore_module.py b/src/sage/modules/ore_module.py index 9f5ef29b2c8..94e3ff7c06d 100644 --- a/src/sage/modules/ore_module.py +++ b/src/sage/modules/ore_module.py @@ -476,9 +476,9 @@ def _element_constructor_(self, x): TESTS:: sage: M(0) - 0 + (0, 0) sage: N(0) - 0 + (0, 0) """ if isinstance(x, OreModuleElement): M = x.parent()._pushout_(self) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index b72c82d169e..69c3966826d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -11,24 +11,24 @@ .. MATH:: - tau_M : \tau^\star M \left[\frac 1[T-z]\right] \to M \left[\frac 1[T-z]\right] + \tau_M : \tau^\star M \left[\frac 1{T-z}\right] \to M \left[\frac 1{T-z}\right] where `\tau^\star M = K \otimes_{K, \text{Frob}} M`. .. RUBRIC:: Anderson motives attached to Drinfeld modules -Any Drinfeld module `phi` over `(A, \gamma)` with `gamma : A \to K, +Any Drinfeld module `\phi` over `(A, \gamma)` with `\gamma : A \to K, T \mapsto z` gives rise to an Anderson motive. By definition, it is `M(\phi) := K\{\tau\}` (the ring of Ore polynomials with commutation rule `\tau \lambda = \lambda^q \tau` for `\lambda \in K`) where - the structure of `\GF{q}[T]`-module is given by right multiplication - by `phi_a` (`a \in \GF{q}[T]`), + by `\phi_a` (`a \in \GF{q}[T]`), - the structure of `K`-module is given by left multiplication, - the automorphism `\tau_{M(\phi)}` is the left multiplication - by `tau` in the Ore polynomial ring. + by `\tau` in the Ore polynomial ring. Anderson motives are nevertheless much more general than Drinfeld modules. Besides, their linear nature allows for importing many @@ -49,7 +49,7 @@ the Anderson motive attached a Drinfeld module has the same rank than the underlying Drinfeld module. -The canonical basis corresponds to the vectors `tau^i` for `i` +The canonical basis corresponds to the vectors `\tau^i` for `i` varying between `0` and `r-1` where `r` is the rank:: sage: tau = phi.ore_variable() @@ -113,7 +113,7 @@ `\tau_M` is only defined after inverting `T-z` in full generality. SageMath also provides a general constructor :func:`AndersonMotive` -which allows in particular to explicitely provide the matrix of `tau_M`:: +which allows in particular to explicitely provide the matrix of `\tau_M`:: sage: mat = matrix(2, 2, [[T, z], [1, 1]]) sage: N = AndersonMotive(A, mat) @@ -361,7 +361,7 @@ def __reduce__(self): @lazy_attribute def _dettau(self): r""" - Return the leading coefficient of the determinant of `tau` + Return the leading coefficient of the determinant of `\tau` and its degree. Only for internal use. @@ -1026,12 +1026,12 @@ def AndersonMotive(arg1, tau=None, names=None): The input can be one of the followings: - - a pair '(A, K)` where `A = \FF_q[t]` is the function + - a pair '(A, K)` where `A = \GF{q}[t]` is the function base ring and `K` is the coefficient `A`-field; these parameters correspond to the trivial Anderson motive over `A \otimes K` - - a pair '(A, z)` where `A = \FF_q[t]` is the function + - a pair '(A, z)` where `A = \GF{q}[t]` is the function base ring and `z` is an element; the `A`-field is then then parent `K` of `z` viewed as an algebra over `A` through `A \mapsto K, T \mapsto z`. @@ -1039,14 +1039,14 @@ def AndersonMotive(arg1, tau=None, names=None): - a pair `(A, \tau)` where - `A` is either the underlying function ring (which - currently needs to be of the form `\FF_q[t]`) or + currently needs to be of the form `\GF{q}[t]`) or a category (of Drinfeld modules or Anderson motives) - `\tau` is the matrix defining the Anderson motive - a Drinfeld module - EXAMPLES: + EXAMPLES:: sage: A. = GF(7)[] sage: K. = GF(7^3) From 40bdce98eb170ae9ebca6582c2dcada2128889d4 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 8 Nov 2025 09:08:40 +0100 Subject: [PATCH 42/59] two more typos --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 4e7ab57fe53..2ea040337dc 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2092,7 +2092,7 @@ def anderson_motive(self, names=None): `\phi : A \to K\{\tau\}` is `K\{\tau\}` endowed by: - the structure of `A`-module where `a \in A` acts by - right multiplication by `phi_a` + right multiplication by `\phi_a` - the structure of `K`-vector space given by standard left multiplication @@ -2116,7 +2116,7 @@ def anderson_motive(self, names=None): Here the rank of the Anderson motive should be understood as its rank over `A \otimes K`; it is also the rank `r` of the underlying Drinfeld module. More precisely, `M` has a canonical basis, which - is formed by the Ore polynomials `1, \ldots, \tau^{r-1}`. + is formed by the Ore polynomials `1, \ldots, \tau^{r-1}`:: sage: tau = phi.ore_variable() sage: [M(tau^i) for i in range(phi.rank())] From d0da98f8ff03a1cced33e1fa994203e90eeecad8 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 8 Nov 2025 11:54:57 +0100 Subject: [PATCH 43/59] fix several broken links in the documentation --- .../function_field/drinfeld_modules/action.py | 2 +- .../drinfeld_modules/anderson_motive.py | 15 ++- .../drinfeld_modules/drinfeld_module.py | 16 +-- .../drinfeld_module_charzero.py | 8 +- .../drinfeld_module_finite.py | 101 +++++++++--------- .../function_field/drinfeld_modules/homset.py | 4 +- .../drinfeld_modules/morphism.py | 6 +- 7 files changed, 73 insertions(+), 79 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 9bb50a00a18..f14931365c1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -3,7 +3,7 @@ The module action induced by a Drinfeld module This module provides the class -:class:`sage.rings.function_field.drinfeld_module.action.DrinfeldModuleAction`. +:class:`sage.rings.function_field.drinfeld_modules.action.DrinfeldModuleAction`. AUTHORS: diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 69c3966826d..9d25e226d72 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -1026,21 +1026,20 @@ def AndersonMotive(arg1, tau=None, names=None): The input can be one of the followings: - - a pair '(A, K)` where `A = \GF{q}[t]` is the function - base ring and `K` is the coefficient `A`-field; these - parameters correspond to the trivial Anderson motive - over `A \otimes K` + - a pair `(A, K)` where `A` is the underlying function + ring (which currently needs to be of the form \GF{q}[t]`) + and `K` is the `A`-field; these parameters correspond to + the trivial Anderson motive over `A \otimes K` - - a pair '(A, z)` where `A = \GF{q}[t]` is the function + - a pair `(A, z)` where `A = \GF{q}[t]` is the function base ring and `z` is an element; the `A`-field is then then parent `K` of `z` viewed as an algebra over `A` through `A \mapsto K, T \mapsto z`. - a pair `(A, \tau)` where - - `A` is either the underlying function ring (which - currently needs to be of the form `\GF{q}[t]`) or - a category (of Drinfeld modules or Anderson motives) + - `A` is either `\GF{q}[t]` or a category (of Drinfeld + modules or Anderson motives) - `\tau` is the matrix defining the Anderson motive diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 2ea040337dc..ecca4dadb1d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -3,11 +3,11 @@ Drinfeld modules This module provides the class -:class:`sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. For finite Drinfeld modules and their theory of complex multiplication, see class -:class:`sage.rings.function_field.drinfeld_module.drinfeld_module_finite.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite.DrinfeldModule`. AUTHORS: @@ -71,7 +71,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: - See also :class:`sage.categories.drinfeld_modules`. + See also :mod:`sage.categories.drinfeld_modules`. The *base morphism* is the morphism `\gamma: \GF{q}[T] \to K`. The monic polynomial that generates the kernel of `\gamma` is called @@ -225,7 +225,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): Note that the base field is *not* the field `K`. Rather, it is a ring extension - (see :class:`sage.rings.ring_extension.RingExtension`) whose + (see :mod:`sage.rings.ring_extension`) whose underlying ring is `K` and whose base is the base morphism:: sage: phi.base() is K @@ -338,7 +338,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): Defn: 0 The underlying Ore polynomial is retrieved with the method - :meth:`ore_polynomial`:: + :meth:`sage.rings.function_field.drinfeld_modules.morphism.DrinfeldModuleMorphism.ore_polynomial`:: sage: frobenius_endomorphism.ore_polynomial() τ^6 @@ -394,7 +394,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): `\GF{q}[T]`-module structure on any field extension `L/K`. Let `x \in L` and `a` be in the function ring; the action is defined as `(a, x) \mapsto \phi_a(x)`. The method :meth:`action` returns a - :class:`sage.rings.function_field.drinfeld_modules.action.Action` + :class:`sage.rings.function_field.drinfeld_modules.action.DrinfeldModuleAction` object representing the Drinfeld module action. .. NOTE:: @@ -844,7 +844,7 @@ def __hash__(self) -> int: def action(self): r""" Return the action object - (:class:`sage.rings.function_field.drinfeld_modules.action.Action`) + (:class:`sage.rings.function_field.drinfeld_modules.action.DrinfeldModuleAction`) that represents the module action, on the base codomain, that is induced by the Drinfeld module. @@ -1192,7 +1192,7 @@ def change_A_field(self, A_field): INPUT: - ``A_field`` -- a field or an instance of - class:`sage.rings.ring_extension.RingExtension` + :class:`sage.rings.ring_extension.RingExtension_generic` EXAMPLES:: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py index cce91911a7a..2bd56e5b486 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py @@ -3,10 +3,10 @@ Drinfeld modules over rings of characteristic zero This module provides the classes -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module_charzero.DrinfeldModule_charzero` and -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module_charzero.DrinfeldModule_rational`, +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero.DrinfeldModule_charzero` and +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero.DrinfeldModule_rational`, which both inherit -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. AUTHORS: @@ -47,7 +47,7 @@ class DrinfeldModule_charzero(DrinfeldModule): Recall that the `\GF{q}[T]`-*characteristic* is defined as the kernel of the underlying structure morphism. For general definitions and help on Drinfeld modules, see class - :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. + :class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. .. RUBRIC:: Construction: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py index 1a38962220a..1b4567ab796 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py @@ -3,9 +3,9 @@ Finite Drinfeld modules This module provides the class -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module_finite.DrinfeldModule_finite`, +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite.DrinfeldModule_finite`, which inherits -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. AUTHORS: @@ -234,7 +234,7 @@ def frobenius_endomorphism(self): Return the Frobenius endomorphism of the Drinfeld module. The *Frobenius endomorphism* is defined by the Ore polynomial - `tau^n`, where `n` is the degree of the base field `K` over + `\tau^n`, where `n` is the degree of the base field `K` over `\mathbb F_q`. EXAMPLES:: @@ -294,21 +294,30 @@ def frobenius_charpoly(self, var='X', algorithm=None): - ``algorithm`` (default: ``None``) -- the algorithm used to compute the characteristic polynomial - .. NOTE: - Available algorithms are: - - ``'CSA'`` -- it exploits the fact that `K\{\tau\}` is a - central simple algebra (CSA) over - `\GF{q}[\text{Frob}_\phi]` (see Chapter 4 of [CL2023]_). - - ``'crystalline'`` -- it uses the action of the Frobenius - on the crystalline cohomology (see [MS2023]_). - - ``'gekeler'`` -- it tries to identify coefficients by - writing that the characteristic polynomial annihilates the - Frobenius endomorphism; this algorithm may fail is some - cases (see [Gek2008]_). - - ``'motive'`` -- it uses the action of the Frobenius on the - Anderson motive (see Chapter 2 of [CL2023]_). + - ``'CSA'`` -- it exploits the fact that `K\{\tau\}` is a + central simple algebra (CSA) over + `\GF{q}[\text{Frob}_\phi]` (see Chapter 4 of [CL2023]_). + - ``'crystalline'`` -- it uses the action of the Frobenius + on the crystalline cohomology (see [MS2023]_). + - ``'gekeler'`` -- it tries to identify coefficients by + writing that the characteristic polynomial annihilates the + Frobenius endomorphism; this algorithm may fail is some + cases (see [Gek2008]_). + - ``'motive'`` -- it uses the action of the Frobenius on the + Anderson motive (see Chapter 2 of [CL2023]_). + + If the user specifies an algorithm, then the characteristic + polynomial is computed according to the user's input (see + the note above), even if it had already been computed. + + If no algorithm is given, then the function either returns a + cached value, or if no cached value is available, the + function computes the Frobenius characteristic polynomial + from scratch. In that case, if the rank `r` is less than the + extension degree `n`, then the ``crystalline`` algorithm is + used, while the ``CSA`` algorithm is used otherwise. The method raises an exception if the user asks for an unimplemented algorithm, even if the characteristic polynomial @@ -350,19 +359,6 @@ def frobenius_charpoly(self, var='X', algorithm=None): ... NotImplementedError: algorithm "NotImplemented" not implemented - ALGORITHM: - - If the user specifies an algorithm, then the characteristic - polynomial is computed according to the user's input (see - the note above), even if it had already been computed. - - If no algorithm is given, then the function either returns a - cached value, or if no cached value is available, the - function computes the Frobenius characteristic polynomial - from scratch. In that case, if the rank `r` is less than the - extension degree `n`, then the ``crystalline`` algorithm is - used, while the ``CSA`` algorithm is used otherwise. - TESTS:: sage: Fq = GF(9) @@ -701,7 +697,7 @@ def frobenius_norm(self): where `K` is the ground field, which as degree `n` over `\GF{q}`, `r` is the rank of the Drinfeld module, and `\Delta` is the leading coefficient of the generator. - This formula is given in Theorem~4.2.7 of [Pap2023]_. + This formula is given in Theorem 4.2.7 of [Pap2023]_. Note that the Frobenius norm computed by this method may be different than what is computed as the isogeny norm of the @@ -710,6 +706,11 @@ def frobenius_norm(self): given by its monic generator; the Frobenius norm may not be monic. + ALGORITHM: + + The Frobenius norm is computed using the formula, by + Gekeler, given in [MS2019]_, Section 4, Proposition 3. + EXAMPLES:: sage: Fq = GF(343) @@ -739,11 +740,6 @@ def frobenius_norm(self): True sage: A.ideal(frobenius_norm) == isogeny_norm True - - ALGORITHM: - - The Frobenius norm is computed using the formula, by - Gekeler, given in [MS2019]_, Section 4, Proposition 3. """ if self._frobenius_norm is not None: return self._frobenius_norm @@ -772,15 +768,25 @@ def frobenius_trace(self, algorithm=None): - ``algorithm`` (default: ``None``) -- the algorithm used to compute the characteristic polynomial - .. NOTE: - Available algorithms are: - - ``'CSA'`` -- it exploits the fact that `K\{\tau\}` is a - central simple algebra (CSA) over - `\GF{q}[\text{Frob}_\phi]` (see Chapter 4 of [CL2023]_). - - ``'crystalline'`` -- it uses the action of the Frobenius - on the crystalline cohomology (see [MS2023]_). + - ``'CSA'`` -- it exploits the fact that `K\{\tau\}` is a + central simple algebra (CSA) over + `\GF{q}[\text{Frob}_\phi]` (see Chapter 4 of [CL2023]_). + - ``'crystalline'`` -- it uses the action of the Frobenius + on the crystalline cohomology (see [MS2023]_). + + If the user specifies an algorithm, then the trace is + computed according to the user's input (see the note above), + even if the Frobenius trace or the Frobenius characteristic + polynomial had already been computed. + + If no algorithm is given, then the function either returns a + cached value, or if no cached value is available, the + function computes the Frobenius trace from scratch. In that + case, if the rank `r` is less than the extension degree `n`, + then the ``crystalline`` algorithm is used, while the + ``CSA`` algorithm is used otherwise. The method raises an exception if the user asks for an unimplemented algorithm, even if the characteristic has already @@ -817,17 +823,6 @@ def frobenius_trace(self, algorithm=None): ALGORITHM: - If the user specifies an algorithm, then the trace is - computed according to the user's input (see the note above), - even if the Frobenius trace or the Frobenius characteristic - polynomial had already been computed. - - If no algorithm is given, then the function either returns a - cached value, or if no cached value is available, the - function computes the Frobenius trace from scratch. In that - case, if the rank `r` is less than the extension degree `n`, - then the ``crystalline`` algorithm is used, while the - ``CSA`` algorithm is used otherwise. TESTS: diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index d3c294a3158..a89981b9c2b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -3,7 +3,7 @@ Set of morphisms between two Drinfeld modules This module provides the class -:class:`sage.rings.function_field.drinfeld_module.homset.DrinfeldModuleHomset`. +:class:`sage.rings.function_field.drinfeld_modules.homset.DrinfeldModuleHomset`. AUTHORS: @@ -852,7 +852,7 @@ def basis_over_frobenius(self): We return the basis of the kernel of a matrix derived from the constraint that `\iota \phi_T = \psi_T \iota` for any morphism - `iota: \phi \to \psi`. + `\iota: \phi \to \psi`. We refer to [Mus2023]_, Section 7.3 for more details. EXAMPLES:: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index fe69aaf5580..00bda4820b4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -3,7 +3,7 @@ Drinfeld module morphisms This module provides the class -:class:`sage.rings.function_fields.drinfeld_module.morphism.DrinfeldModuleMorphism`. +:class:`sage.rings.function_field.drinfeld_modules.morphism.DrinfeldModuleMorphism`. AUTHORS: - Antoine Leudière (2022-04) @@ -606,7 +606,7 @@ def right_gcd(self, other): ... ValueError: the two morphisms must have the same domain - SEEALSO:: + .. SEEALSO:: :meth:`left_lcm` """ @@ -654,7 +654,7 @@ def left_lcm(self, other): ... ValueError: the two morphisms must have the same domain - SEEALSO:: + .. SEEALSO:: :meth:`right_gcd` """ From 8cf31cfffcf64dbfc92e62b6a05f31946e4eedd2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 8 Nov 2025 15:05:57 +0100 Subject: [PATCH 44/59] more fixed in documentation --- .../drinfeld_modules/drinfeld_module.py | 10 +++--- .../drinfeld_module_charzero.py | 6 ---- .../drinfeld_module_finite.py | 32 ++++++++----------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ecca4dadb1d..c637daf0c89 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2,12 +2,12 @@ r""" Drinfeld modules -This module provides the class -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. +For Drinfeld modules in characteristic zero and the analytic theory, see +:mod:`sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero` -For finite Drinfeld modules and their theory of complex multiplication, see -class -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite.DrinfeldModule`. +For Drinfeld modules over finite field and their theory of complex +multiplication, see +:mod:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite` AUTHORS: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py index 2bd56e5b486..2bfac25eb07 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py @@ -2,12 +2,6 @@ r""" Drinfeld modules over rings of characteristic zero -This module provides the classes -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero.DrinfeldModule_charzero` and -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero.DrinfeldModule_rational`, -which both inherit -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. - AUTHORS: - David Ayotte (2023-09) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py index 1b4567ab796..355754a1ded 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_finite.py @@ -2,11 +2,6 @@ r""" Finite Drinfeld modules -This module provides the class -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite.DrinfeldModule_finite`, -which inherits -:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. - AUTHORS: - Antoine Leudière (2022-04) @@ -69,7 +64,8 @@ class DrinfeldModule_finite(DrinfeldModule): True The user should never use ``DrinfeldModule_finite`` to test if a - Drinfeld module is finite, but rather the ``is_finite`` method:: + Drinfeld module is finite, but rather the + :meth:`sage.rings.function_field.drinfeld_modules.DrinfeldModule.is_finite`` method:: sage: phi.is_finite() True @@ -309,8 +305,8 @@ def frobenius_charpoly(self, var='X', algorithm=None): Anderson motive (see Chapter 2 of [CL2023]_). If the user specifies an algorithm, then the characteristic - polynomial is computed according to the user's input (see - the note above), even if it had already been computed. + polynomial is computed according to the user's input, even + if it had already been computed. If no algorithm is given, then the function either returns a cached value, or if no cached value is available, the @@ -777,7 +773,7 @@ def frobenius_trace(self, algorithm=None): on the crystalline cohomology (see [MS2023]_). If the user specifies an algorithm, then the trace is - computed according to the user's input (see the note above), + computed according to the user's input, even if the Frobenius trace or the Frobenius characteristic polynomial had already been computed. @@ -1022,6 +1018,15 @@ def is_isogenous(self, psi): If the Drinfeld modules do not belong to the same category, an exception is raised. + ALGORITHM: + + Two Drinfeld `A`-modules of equal characteristic are isogenous + if and only if: + + - they have the same rank + - the characteristic polynomial of the Frobenius endomorphism + for both Drinfeld modules are equal. + EXAMPLES:: sage: Fq = GF(2) @@ -1053,15 +1058,6 @@ def is_isogenous(self, psi): Traceback (most recent call last): ... TypeError: input must be a Drinfeld module - - ALGORITHM: - - Two Drinfeld A-modules of equal characteristic are isogenous - if and only if: - - - they have the same rank - - the characteristic polynomial of the Frobenius endomorphism - for both Drinfeld modules are equal. """ if not isinstance(psi, DrinfeldModule): raise TypeError("input must be a Drinfeld module") From e7ff65b572bf8baf2d15aa353e7a143d83cc369f Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 14 Nov 2025 09:27:10 +0100 Subject: [PATCH 45/59] a first round of corrections --- src/doc/en/reference/index.rst | 2 +- src/sage/categories/anderson_motives.py | 11 +- src/sage/categories/morphism.pyx | 11 +- .../drinfeld_modules/anderson_motive.py | 157 ++++++++++-------- .../drinfeld_modules/morphism.py | 2 +- 5 files changed, 102 insertions(+), 81 deletions(-) diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 5c5a01ae21e..3d53912268a 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -115,7 +115,7 @@ Number Fields, Function Fields, and Valuations * :doc:`Number Fields ` * :doc:`Function Fields ` * :doc:`Discrete Valuations ` -* :doc:`Drinfeld Modules ` +* :doc:`Drinfeld Modules and Anderson motives ` Number Theory ------------- diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index b2b53f28885..7cc30b08f0d 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -3,7 +3,7 @@ AUTHOR: -- Xavier Caruso (2025-11): initial version +- Xavier Caruso, Antoine Leudière (2025-11): initial version """ # ***************************************************************************** @@ -92,7 +92,7 @@ def __init__(self, category): """ self._drinfeld_category = category self._base_morphism = category.base_morphism() - self._base_field = category.base() + self._A_field = category.base() self._function_ring = A = category.function_ring() self._base_over_constants_field = category.base_over_constants_field() self._ore_variable_name = category._ore_variable_name @@ -206,8 +206,7 @@ def A_field(self): sage: C.A_field() Finite Field in z of size 3^3 over its base """ - f = self._base_morphism - return f.codomain().over(f) + return self._A_field def base(self): r""" @@ -326,8 +325,8 @@ def super_categories(self): sage: C.super_categories() [Category of Ore modules over Univariate Polynomial Ring in T over Finite Field in z of size 3^3 twisted by T |--> T, with map of base ring] """ - S = self._ore_polring - return [OreModules(S.base(), S)] + AKtau = self._ore_polring + return [OreModules(AKtau.base(), AKtau)] class ParentMethods: diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index 4e398ec7c38..a7fb18ede6f 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -94,10 +94,17 @@ cdef class Morphism(Map): D = self.domain() if D is None: return "Defunct morphism" + t = self._repr_type() if self.is_endomorphism(): - s = "{} endomorphism of {}".format(self._repr_type(), self.domain()) + if t is None: + s = "Endomorphism of {}".format(self.domain()) + else: + s = t + " endomorphism of {}".format(self.domain()) else: - s = "{} morphism:".format(self._repr_type()) + if t is None: + s = "Morphism:" + else: + s = t + " morphism:" s += "\n From: {}".format(self.domain()) s += "\n To: {}".format(self._codomain) if isinstance(self.domain, ConstantFunction): diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 9d25e226d72..4aa7db04573 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -13,7 +13,9 @@ \tau_M : \tau^\star M \left[\frac 1{T-z}\right] \to M \left[\frac 1{T-z}\right] -where `\tau^\star M = K \otimes_{K, \text{Frob}} M`. +where `\tau^\star M = K \otimes_{K, \text{Frob}} M` and the notation +means that `K` is viewed as an algebra over itself through the +Frobenius `\text{Frob} : x \mapsto x^q`. .. RUBRIC:: Anderson motives attached to Drinfeld modules @@ -43,7 +45,7 @@ sage: phi = DrinfeldModule(A, [z, z^2, z^3, z^4]) sage: M = phi.anderson_motive() sage: M - Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Anderson motive of Drinfeld module defined by T |--> (2*z^2 + 2*z)*τ^3 + (2*z + 2)*τ^2 + z^2*τ + z We see that `M` has rank `3`; it is actually a general fact that the Anderson motive attached a Drinfeld module has the same rank @@ -76,6 +78,12 @@ [ 0 0 1] [(z^2 + 3*z)*T + 2*z^2 + 3*z + 3 3*z^2 + 2*z + 4 2*z^2 + 1] +.. NOTE:: + + Here, as it is conventional in SageMath, we use the row + representation, meaning that the coordinates of the image + by `\tau_M(\tau^i)` are written in the `i`-th row. + SageMath provides facilities to pick elements in `M` and perform basic operations with them:: @@ -85,10 +93,19 @@ sage: w.image() # image by tau_M ((z^2 + 3*z)*T + 2*z^2 + 3*z + 3, 3*z^2 + 2*z + 4, 2*z^2 + 1) +It is also possible to give names to the vectors of the canonical +basis and then use when printing:: + + sage: psi = DrinfeldModule(A, [z, z+1, z+2]) + sage: N. = psi.anderson_motive() + sage: N.random_element() # random + ((4*z+4)*T^2+(3*z^2+1)*T+z^2+3*z+3)*e0 + (T^2+(2*z^2+1)*T+3*z^2)*e1 + .. RUBRIC:: More Anderson motives Some basic constructions on Anderson modules are also available. -For example, one can form the dual:: +For example, one can form the dual using the method +:meth:`AndersonMotive_general.dual`:: sage: Md = M.dual() sage: Md @@ -98,7 +115,7 @@ [ (2*z + 2)/(T + 4*z) 0 1] [(2*z^2 + 2*z)/(T + 4*z) 0 0] -or Carlitz twists:: +or Carlitz twists (using :meth:`AndersonMotive_general.carlitz_twist`):: sage: M2 = M.carlitz_twist(2) sage: M2 @@ -111,9 +128,11 @@ We observe that the entries of the previous matrices have denominators which are `T-z` or powers of it. This corresponds to the fact that `\tau_M` is only defined after inverting `T-z` in full generality. +We underline that the previous observation also implies that `M_2` +does not come from a Drinfeld module. SageMath also provides a general constructor :func:`AndersonMotive` -which allows in particular to explicitely provide the matrix of `\tau_M`:: +which allows in particular to explicitly provide the matrix of `\tau_M`:: sage: mat = matrix(2, 2, [[T, z], [1, 1]]) sage: N = AndersonMotive(A, mat) @@ -125,8 +144,12 @@ .. RUBRIC:: Morphisms between Anderson motives -One important class of morphisms between Anderson motives are those -which comes from isogenies between Drinfeld modules. +By definition, a morphism between Anderson motives is a +`A \otimes K`-linear morphism commuting with the action of +`\tau`. + +One important class of morphisms of Anderson motives are those +coming from isogenies between Drinfeld modules. Such morphisms can be built easily as follows:: sage: u = phi.hom(tau + z) @@ -137,9 +160,9 @@ Defn: τ + z sage: Mu = u.anderson_motive() sage: Mu - Anderson motive morphism: - From: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 - To: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Morphism: + From: Anderson motive of Drinfeld module defined by T |--> (4*z^2 + 2*z + 4)*τ^3 + (4*z^2 + 1)*τ^2 + (z^2 + 2)*τ + z + To: Anderson motive of Drinfeld module defined by T |--> (2*z^2 + 2*z)*τ^3 + (2*z + 2)*τ^2 + z^2*τ + z sage: Mu.matrix() [ z 1 0] [ 0 2*z^2 + 4*z + 4 1] @@ -173,7 +196,7 @@ AUTHOR: -- Xavier Caruso (2025-11): initial version +- Xavier Caruso, Antoine Leudière (2025-11): initial version """ # ***************************************************************************** @@ -186,11 +209,8 @@ # http://www.gnu.org/licenses/ # ***************************************************************************** -import operator - from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex -from sage.misc.functional import log from sage.categories.map import Map from sage.categories.homset import Homset @@ -203,16 +223,13 @@ from sage.rings.ring import CommutativeRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.morphism import RingHomomorphism -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.fraction_field import FractionField_1poly_field from sage.matrix.matrix0 import Matrix from sage.matrix.constructor import matrix -from sage.matrix.special import identity_matrix, block_diagonal_matrix +from sage.matrix.special import identity_matrix from sage.modules.ore_module import OreModule, OreSubmodule, OreQuotientModule from sage.modules.ore_module import normalize_names -from sage.modules.ore_module_element import OreModuleElement from sage.modules.ore_module_homspace import OreModule_homspace from sage.modules.ore_module_morphism import OreModuleMorphism @@ -235,7 +252,7 @@ class AndersonMotive_general(OreModule): sage: TestSuite(M).run() """ @staticmethod - def __classcall_private__(self, category, tau, twist=0, names=None, normalize=True): + def __classcall_private__(cls, category, tau, twist=0, names=None, normalize=True): r""" Normalize the input and return an instance of the appropriate class. @@ -248,7 +265,7 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr - ``twist`` -- an integer (default: ``0``) - - ``names`` -- a string of a list of strings (default: ``None``), + - ``names`` -- a string or a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements will be represented as row vectors @@ -274,7 +291,6 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr True """ AK = category.base() - K = AK.base_ring() # We normalize the inputs twist = ZZ(twist) @@ -303,12 +319,10 @@ def __classcall_private__(self, category, tau, twist=0, names=None, normalize=Tr denominator = Factorization([(category.divisor(), twist)]) ore = category._ore_polring - #if (isinstance(K, FractionField_1poly_field) - # and category.constant_coefficient() == K.gen()): - # from sage.rings.function_field.drinfeld_modules.anderson_motive_rational import AndersonMotive_rational - # cls = AndersonMotive_rational - #else: - cls = AndersonMotive_general + # if (isinstance(K, FractionField_1poly_field) + # and category.constant_coefficient() == K.gen()): + # from sage.rings.function_field.drinfeld_modules.anderson_motive_rational import AndersonMotive_rational + # cls = AndersonMotive_rational return cls.__classcall__(cls, tau, ore, denominator, names, category) @@ -330,7 +344,7 @@ def _initialize_attributes(self): """ self._tau = self._pseudohom.matrix() if self._denominator: - self._twist = self._denominator[0][1] + [(_, self._twist)] = self._denominator else: self._twist = 0 self._general_class = AndersonMotive_general @@ -389,13 +403,6 @@ def _repr_(self): sage: M = AndersonMotive(A, K) sage: M # indirect doctest Anderson motive of rank 1 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 - - :: - - sage: phi = DrinfeldModule(A, [z, z^2, z^3]) - sage: M. = phi.anderson_motive() - sage: M # indirect doctest - Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 """ s = "Anderson motive " if self._names is None: @@ -441,7 +448,7 @@ def carlitz_twist(self, n=1, names=None): - ``n`` -- an integer (default: ``1``) - - ``names`` -- a string of a list of strings (default: ``None``), + - ``names`` -- a string or a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements are represented as row vectors @@ -471,9 +478,7 @@ def dual(self, names=None): INPUT: - - ``n`` -- an integer (default: ``1``) - - - ``names`` - a string of a list of strings (default: ``None``), + - ``names`` - a string or a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements are represented as row vectors @@ -519,8 +524,8 @@ def _Hom_(self, other, category): sage: M = phi.anderson_motive() sage: End(M) # indirect doctest Set of Morphisms - from Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^4 - to Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^4 + from Anderson motive of Drinfeld module defined by T |--> (z^2 + z + 3)*τ^3 + z^3*τ^2 + z^2*τ + z + to Anderson motive of Drinfeld module defined by T |--> (z^2 + z + 3)*τ^3 + z^3*τ^2 + z^2*τ + z in Category of finite dimensional Ore modules with basis over Univariate Polynomial Ring in T over Finite Field in z of size 5^4 twisted by T |--> T, with map of base ring """ @@ -604,7 +609,7 @@ def __classcall_private__(cls, phi, names): - ``phi`` -- a Drinfeld module - - ``names`` -- a string of a list of strings (default: ``None``), + - ``names`` -- a string or a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements will be represented as row vectors @@ -675,6 +680,25 @@ def __reduce__(self): """ return AndersonMotive_drinfeld, (self._drinfeld_module, self._names) + def _repr_(self): + r""" + Return a string representation of this Anderson motive. + + EXAMPLES:: + + sage: A. = GF(5)[] + sage: K. = GF(5^3) + sage: phi = DrinfeldModule(A, [z, z^2, z^3]) + sage: M. = phi.anderson_motive() + sage: M # indirect doctest + Anderson motive of Drinfeld module defined by T |--> (2*z + 2)*τ^2 + z^2*τ + z + """ + s = "Anderson motive " + if self._names is not None: + s += "<" + ", ".join(self._names) + "> " + s += "of %s" % self._drinfeld_module + return s + def drinfeld_module(self): r""" Return the Drinfeld module from which this Anderson motive @@ -826,10 +850,9 @@ def _repr_type(self): sage: phi = DrinfeldModule(A, [z, z^2, z^3]) sage: u = phi.scalar_multiplication(T) sage: u.anderson_motive() # indirect doctest - Anderson motive endomorphism of Anderson motive of rank 2 over - Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Endomorphism of Anderson motive of Drinfeld module defined by T |--> (2*z + 2)*τ^2 + z^2*τ + z """ - return "Anderson motive" + return None def __init__(self, parent, im_gens, check=True): r""" @@ -843,8 +866,7 @@ def __init__(self, parent, im_gens, check=True): sage: E = End(M) sage: f = E(matrix(1, 1, [T])) sage: f - Anderson motive endomorphism of Anderson motive of rank 1 over - Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Endomorphism of Anderson motive of rank 1 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 """ from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld if isinstance(im_gens, DrinfeldModuleMorphism): @@ -856,8 +878,7 @@ def __init__(self, parent, im_gens, check=True): if not isinstance(codomain, AndersonMotive_drinfeld)\ or codomain.drinfeld_module() is not im_gens.domain(): raise ValueError("the codomain must be the Anderson module of the domain of the isogeny") - u = im_gens._ore_polynomial - im_gens = {codomain.gen(0): u*domain.gen(0)} + im_gens = im_gens._motive_matrix() check = False OreModuleMorphism.__init__(self, parent, im_gens, check) @@ -925,7 +946,7 @@ def __init__(self, parent, phi): """ Map.__init__(self, parent) - self._phi = phi + self._drinfeld_module = phi self._motive = parent.codomain() self._AK = self._motive.base() @@ -947,7 +968,7 @@ def _call_(self, f): sage: M(tau) # indirect doctest (0, 1) """ - phi = self._phi + phi = self._drinfeld_module r = phi.rank() phiT = phi.gen() coords = [] @@ -982,7 +1003,7 @@ def __init__(self, parent, phi): """ Map.__init__(self, parent) - self._phi = phi + self._drinfeld_module = phi self._Ktau = parent.codomain() def _call_(self, x): @@ -999,15 +1020,11 @@ def _call_(self, x): sage: u + tau u + v """ - phi = self._phi + phi = self._drinfeld_module r = phi.rank() phiT = phi.gen() S = self._Ktau - xs = [] - for i in range(r): - if x[i].denominator() != 1: - raise ValueError("not in the Anderson motive") - xs.append(x[i].numerator()) + xs = x.list() ans = S.zero() d = max(xi.degree() for xi in xs) for j in range(d, -1, -1): @@ -1020,14 +1037,14 @@ def _call_(self, x): def AndersonMotive(arg1, tau=None, names=None): r""" - Construct an Anderson motive + Construct an Anderson motive. INPUT: The input can be one of the followings: - a pair `(A, K)` where `A` is the underlying function - ring (which currently needs to be of the form \GF{q}[t]`) + ring (which currently needs to be of the form `\GF{q}[t]`) and `K` is the `A`-field; these parameters correspond to the trivial Anderson motive over `A \otimes K` @@ -1110,6 +1127,13 @@ def AndersonMotive(arg1, tau=None, names=None): TESTS:: + sage: AndersonMotive(phi, tau) + Traceback (most recent call last): + ... + ValueError: too many arguments + + :: + sage: AndersonMotive(ZZ, K) Traceback (most recent call last): ... @@ -1135,17 +1159,8 @@ def AndersonMotive(arg1, tau=None, names=None): # arg1 is a Drinfeld module if isinstance(arg1, DrinfeldModule): if tau is not None: - raise ValueError("") - category = AndersonMotives(arg1.category()) - AK = category.base() - r = arg1.rank() - tau = matrix(AK, r) - P = arg1.gen() - tau[r-1, 0] = (AK.gen() - P[0]) / P[r] - for i in range(1, r): - tau[i-1, i] = 1 - tau[r-1, i] = -P[i]/P[r] - return AndersonMotive_general(category, tau, names=names) + raise ValueError("too many arguments") + return AndersonMotive_drinfeld(arg1, names=names) # arg1 is a category category = None diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 00bda4820b4..6678eee67c1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -933,7 +933,7 @@ def charpoly(self, var='X'): def anderson_motive(self, names_domain=None, names_codomain=None): r""" - Return the morphism giving the action of this isogeny on + Return the morphism giving the action of this morphism on the Anderson motives. INPUT: From 6a1c908f3293f054d3570a0739d9b6f978a7cd1a Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 14 Nov 2025 12:22:12 +0100 Subject: [PATCH 46/59] improve documentation in constructor --- src/sage/categories/anderson_motives.py | 20 ++++- .../drinfeld_modules/anderson_motive.py | 86 ++++++++----------- .../drinfeld_modules/drinfeld_module.py | 4 +- .../drinfeld_modules/morphism.py | 12 +-- 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index 7cc30b08f0d..7589f6c24d6 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -18,6 +18,7 @@ from sage.misc.latex import latex +from sage.matrix.special import identity_matrix from sage.categories.modules import Modules from sage.categories.ore_modules import OreModules @@ -277,7 +278,7 @@ def function_ring(self): """ return self._function_ring - def object(self, tau=None): + def object(self, tau=None, names=None): r""" Return the object in this category with `\tau`-action given by the matrix ``tau``. @@ -288,6 +289,10 @@ def object(self, tau=None): if ``None``, return the trivial Anderson module in this category + - ``names`` -- a matrix or ``None`` (default: ``None``); + if ``None``, return the trivial Anderson module in this + category + EXAMPLES:: sage: from sage.categories.anderson_motives import AndersonMotives @@ -310,8 +315,17 @@ def object(self, tau=None): [T 1] [z 1] """ - from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive - return AndersonMotive(self, tau) + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general + if tau is None: + tau = identity_matrix(self._base_combined, 1) + det = tau.determinant() + if det == 0: + raise ValueError("the given matrix does not define an Anderson motive in this category") + h = det.degree() + disc, R = det.quo_rem(self._divisor ** h) + if R: + raise ValueError("the given matrix does not define an Anderson motive in this category") + return AndersonMotive_general(self, tau, names=names) def super_categories(self): """ diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 4aa7db04573..7d7d986be58 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -226,7 +226,6 @@ from sage.matrix.matrix0 import Matrix from sage.matrix.constructor import matrix -from sage.matrix.special import identity_matrix from sage.modules.ore_module import OreModule, OreSubmodule, OreQuotientModule from sage.modules.ore_module import normalize_names @@ -1035,13 +1034,13 @@ def _call_(self, x): # Constructor ############# -def AndersonMotive(arg1, tau=None, names=None): +def AndersonMotive(arg1, arg2=None, names=None): r""" Construct an Anderson motive. INPUT: - The input can be one of the followings: + The two first arguments can be one of the followings: - a pair `(A, K)` where `A` is the underlying function ring (which currently needs to be of the form `\GF{q}[t]`) @@ -1101,7 +1100,7 @@ def AndersonMotive(arg1, tau=None, names=None): sage: phi = DrinfeldModule(A, [z, z^2, z^3]) sage: AndersonMotive(phi) - Anderson motive of rank 2 over Univariate Polynomial Ring in T over Finite Field in z of size 7^3 + Anderson motive of Drinfeld module defined by T |--> (z^2 + 3)*τ^2 + z^2*τ + z Finally, another possibility is to give the matrix of `\tau` as an argument:: @@ -1137,7 +1136,7 @@ def AndersonMotive(arg1, tau=None, names=None): sage: AndersonMotive(ZZ, K) Traceback (most recent call last): ... - TypeError: the first argument must be a Drinfeld module or a polynomial ring + ValueError: unable to parse arguments :: @@ -1145,7 +1144,7 @@ def AndersonMotive(arg1, tau=None, names=None): sage: AndersonMotive(A, tau) Traceback (most recent call last): ... - ValueError: tau does not define an Anderson motive + ValueError: the given matrix does not define an Anderson motive in this category .. SEEALSO:: @@ -1156,64 +1155,56 @@ def AndersonMotive(arg1, tau=None, names=None): # . a category (of Drinfeld modules or AndersonMotives) # . a ring, a matrix # . a ring, a A-field - # arg1 is a Drinfeld module + + # We first try to parse the first argument + # We have several cases: + # (1) arg1 is a Drinfeld module if isinstance(arg1, DrinfeldModule): - if tau is not None: + if arg2 is not None: raise ValueError("too many arguments") return AndersonMotive_drinfeld(arg1, names=names) - # arg1 is a category + # (2) arg1 is a category category = None if isinstance(arg1, DrinfeldModules): category = AndersonMotives(arg1) if isinstance(arg1, AndersonMotives): category = arg1 if category is not None: - if tau is None: - tau = identity_matrix(category.base(), 1) - det = tau.determinant() - if det == 0: - raise ValueError("tau does not define an Anderson motive") - h = det.degree() - disc, R = det.quo_rem(category.divisor() ** h) - if R: - raise ValueError("tau does not define an Anderson motive") - M = AndersonMotive_general(category, tau, names=names) - #M._set_dettau(disc[0], h, 0) - return M + return category.object(arg2, names=names) - # arg1 is the function ring + # (3) arg1 is the function ring + if not isinstance(arg1, PolynomialRing_general): + raise ValueError("unable to parse arguments") A = arg1 - if not isinstance(A, PolynomialRing_general): - raise TypeError("the first argument must be a Drinfeld module or a polynomial ring") - - # tau is the base ring + # This case requires to parse the second argument + # Again we have several cases: + # (3a) arg2 encodes the base field K = None - if isinstance(tau, RingHomomorphism) and tau.domain() is A: - K = tau.codomain() - gamma = tau - elif isinstance(tau, CommutativeRing): - K = tau + if isinstance(arg2, RingHomomorphism) and arg2.domain() is A: + # (3a-i) arg2 is the morphism of the form A -> K + K = arg2.codomain() + gamma = arg2 + elif isinstance(arg2, CommutativeRing): + # (3a-ii) arg2 is the A-field K + K = arg2 if K.has_coerce_map_from(A): gamma = K.coerce_map_from(A) else: gamma = A.hom([K.gen()]) - elif hasattr(tau, 'parent') and isinstance(tau.parent(), CommutativeRing): - K = tau.parent() - gamma = A.hom([tau]) + elif hasattr(arg2, 'parent') and isinstance(arg2.parent(), CommutativeRing): + # (3a-iii) arg2 is an element in K + K = arg2.parent() + gamma = A.hom([arg2]) if K is not None: - try: - if K.variable_name() == A.variable_name(): - K = K.base_ring() - except (AttributeError, ValueError): - pass + # The case (3a) was successful: + # We construct and return the Anderson motive category = AndersonMotives(gamma) - AK = category.base() - tau = identity_matrix(AK, 1) - return AndersonMotive_general(category, tau, names=names) + return category.object(names=names) - # tau is a matrix - if isinstance(tau, Matrix): + # (3b) arg2 is a matrix given the action of tau + if isinstance(arg2, Matrix): + tau = arg2 AK = tau.base_ring() if not isinstance(AK, PolynomialRing_general) or AK.variable_name() != A.variable_name(): raise TypeError("incompatible base rings") @@ -1231,11 +1222,6 @@ def AndersonMotive(arg1, tau=None, names=None): raise NotImplementedError("cannot determine the structure of A-field") gamma = A.hom([theta]) category = AndersonMotives(gamma) - disc, R = det.quo_rem(category.divisor() ** h) - if R: - raise ValueError("tau does not define an Anderson motive") - M = AndersonMotive_general(category, tau, names=names) - #M._set_dettau(disc[0], h, 0) - return M + return category.object(tau, names=names) raise ValueError("unable to parse arguments") diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c637daf0c89..997eb4aea0f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2111,7 +2111,7 @@ def anderson_motive(self, names=None): sage: phi = DrinfeldModule(A, [z, 0, 1, z]) sage: M = phi.anderson_motive() sage: M - Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Anderson motive of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z Here the rank of the Anderson motive should be understood as its rank over `A \otimes K`; it is also the rank `r` of the underlying @@ -2127,7 +2127,7 @@ def anderson_motive(self, names=None): sage: M = phi.anderson_motive(names='e') sage: M - Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Anderson motive of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z sage: M.basis() [e0, e1, e2] diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 6678eee67c1..e9226d8451a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -956,9 +956,9 @@ def anderson_motive(self, names_domain=None, names_codomain=None): sage: u = phi.hom(tau + 1) sage: f = u.anderson_motive() sage: f - Anderson motive morphism: - From: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 - To: Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Morphism: + From: Anderson motive of Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + To: Anderson motive of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z sage: f.matrix() [ 1 1 0] [ 0 1 1] @@ -977,9 +977,9 @@ def anderson_motive(self, names_domain=None, names_codomain=None): sage: f = u.anderson_motive(names_domain='a', names_codomain='b') sage: f - Anderson motive morphism: - From: Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 - To: Anderson motive over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Morphism: + From: Anderson motive of Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + To: Anderson motive of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z .. SEEALSO:: From dcfd39ddcd625ff87469900cc5b250993b717b81 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 14 Nov 2025 13:48:10 +0100 Subject: [PATCH 47/59] second round of corrections --- .../drinfeld_modules/anderson_motive.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 7d7d986be58..0f21a070699 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -441,7 +441,10 @@ def _latex_(self): def carlitz_twist(self, n=1, names=None): r""" - Return this Anderson motive twisted `n` times. + Return this Anderson motive twisted `n` times, that is the + tensor product of this Anderson motive with the `n`-th power + of the dual of the Carlitz motive (the Anderson motive + attached to the Carlitz module). INPUT: @@ -657,10 +660,7 @@ def __init__(self, mat, ore, denominator, names, category, phi) -> None: super().__init__(mat, ore, denominator, names, category) Ktau = phi.ore_polring() self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) - try: - Ktau.register_conversion(AndersonToDrinfeld(Homset(self, Ktau), phi)) - except AssertionError: - pass + Ktau.register_conversion(AndersonToDrinfeld(Homset(self, Ktau), phi)) self._drinfeld_module = phi def __reduce__(self): @@ -1126,6 +1126,12 @@ def AndersonMotive(arg1, arg2=None, names=None): TESTS:: + sage: gamma = A.hom([z+1]) + sage: AndersonMotive(A, gamma) + Anderson motive of rank 1 over Univariate Polynomial Ring in T over Finite Field in z of size 7^3 + + :: + sage: AndersonMotive(phi, tau) Traceback (most recent call last): ... From fd818790b1a01e7f79d1acb3351237e55ce4cc3e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 15 Nov 2025 08:01:32 +0100 Subject: [PATCH 48/59] remove carlitz_twist and dual (will come back later with tensor products) --- .../drinfeld_modules/anderson_motive.py | 177 +++++------------- .../drinfeld_modules/drinfeld_module.py | 9 +- 2 files changed, 58 insertions(+), 128 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 0f21a070699..af85a15c887 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -103,35 +103,23 @@ .. RUBRIC:: More Anderson motives -Some basic constructions on Anderson modules are also available. -For example, one can form the dual using the method -:meth:`AndersonMotive_general.dual`:: +One can also build the dual of the Anderson motive attached to +a Drinfeld simply by setting the attribute ``dual=True``:: - sage: Md = M.dual() + sage: Md = phi.anderson_motive(dual=True) sage: Md - Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 + Dual Anderson motive of Drinfeld module defined by T |--> (2*z^2 + 2*z)*τ^3 + (2*z + 2)*τ^2 + z^2*τ + z sage: Md.matrix() [ z^2/(T + 4*z) 1 0] [ (2*z + 2)/(T + 4*z) 0 1] [(2*z^2 + 2*z)/(T + 4*z) 0 0] -or Carlitz twists (using :meth:`AndersonMotive_general.carlitz_twist`):: +We observe that some entries of the previous matrix have denominator +`T-z`. This corresponds to the fact that `\tau_M` is only defined +after inverting `T-z` in full generality, and it implies in particular +that ``Md`` does not come itself from a Drinfeld module. - sage: M2 = M.carlitz_twist(2) - sage: M2 - Anderson motive of rank 3 over Univariate Polynomial Ring in T over Finite Field in z of size 5^3 - sage: M2.matrix() - [ 0 1/(T^2 + 3*z*T + z^2) 0] - [ 0 0 1/(T^2 + 3*z*T + z^2)] - [ (z^2 + 3*z)/(T + 4*z) (3*z^2 + 2*z + 4)/(T^2 + 3*z*T + z^2) (2*z^2 + 1)/(T^2 + 3*z*T + z^2)] - -We observe that the entries of the previous matrices have denominators -which are `T-z` or powers of it. This corresponds to the fact that -`\tau_M` is only defined after inverting `T-z` in full generality. -We underline that the previous observation also implies that `M_2` -does not come from a Drinfeld module. - -SageMath also provides a general constructor :func:`AndersonMotive` +Finally, SageMath also provides a general constructor :func:`AndersonMotive` which allows in particular to explicitly provide the matrix of `\tau_M`:: sage: mat = matrix(2, 2, [[T, z], [1, 1]]) @@ -281,13 +269,6 @@ def __classcall_private__(cls, category, tau, twist=0, names=None, normalize=Tru sage: M = AndersonMotive(A, K) sage: type(M) - - :: - - sage: M1 = M.carlitz_twist(-2) - sage: M2 = AndersonMotive(A, M1.matrix()) - sage: M1 is M2 - True """ AK = category.base() @@ -362,12 +343,6 @@ def __reduce__(self): sage: M = AndersonMotive(A, K) sage: loads(dumps(M)) is M True - - :: - - sage: N = M.carlitz_twist(5) - sage: loads(dumps(N)) is N - True """ return self._general_class, (self._category, self._tau, self._twist, self._names, False) @@ -439,74 +414,6 @@ def _latex_(self): s += "_{%s}" % latex(AK) return s - def carlitz_twist(self, n=1, names=None): - r""" - Return this Anderson motive twisted `n` times, that is the - tensor product of this Anderson motive with the `n`-th power - of the dual of the Carlitz motive (the Anderson motive - attached to the Carlitz module). - - INPUT: - - - ``n`` -- an integer (default: ``1``) - - - ``names`` -- a string or a list of strings (default: ``None``), - the names of the vector of the canonical basis; if ``None``, - elements are represented as row vectors - - EXAMPLES:: - - sage: A. = GF(5)[] - sage: K. = GF(5^3) - sage: M = AndersonMotive(A, K) - sage: M.matrix() - [1] - sage: N = M.carlitz_twist() - sage: N.matrix() - [1/(T + 4*z)] - - Negative twist are also permitted:: - - sage: N = M.carlitz_twist(-1) - sage: N.matrix() - [T + 4*z] - """ - return AndersonMotive_general(self._category, self._tau, self._twist + ZZ(n), - names, normalize=False) - - def dual(self, names=None): - r""" - Return the dual of this Anderson motive. - - INPUT: - - - ``names`` - a string or a list of strings (default: ``None``), - the names of the vector of the canonical basis; if ``None``, - elements are represented as row vectors - - EXAMPLES:: - - sage: A. = GF(5)[] - sage: K. = GF(5^4) - sage: phi = DrinfeldModule(A, [z, z^2, z^3]) - sage: M = phi.anderson_motive() - sage: N = M.dual() - sage: N.matrix() - [z^2/(T + 4*z) 1] - [z^3/(T + 4*z) 0] - - We check that the matrix of `\tau_M` is the transpose of the - inverse of the matrix of `\tau_N`:: - - sage: M.matrix() * N.matrix().transpose() - [1 0] - [0 1] - """ - disc, deg = self._dettau - tau = disc.inverse() * self._tau.adjugate().transpose() - twist = deg - self._twist - return AndersonMotive_general(self._category, tau, twist, names, normalize=True) - def _Hom_(self, other, category): r""" Return the set of morphisms from ``self`` to ``other``. @@ -552,16 +459,9 @@ def hodge_pink_weights(self): We check that the Hodge-Pink weights of the dual are the opposite of the Hodge-Pink weights of the initial Anderson motive:: - sage: N = M.dual() + sage: N = phi.anderson_motive(dual=True) sage: N.hodge_pink_weights() [-1, 0, 0] - - Similarly, we check that Hodge-Pink weights are all shifted by `-1` - after a Carlitz twist:: - - sage: N = M.carlitz_twist() - sage: N.hodge_pink_weights() - [-1, -1, 0] """ S = self._tau.smith_form(transformation=False) return [-self._twist + S[i,i].degree() for i in range(self.rank())] @@ -584,7 +484,7 @@ def is_effective(self): :: - sage: N = M.dual() + sage: N = phi.anderson_motive(dual=True) sage: N.is_effective() False """ @@ -603,7 +503,7 @@ class AndersonMotive_drinfeld(AndersonMotive_general): sage: M = phi.anderson_motive() sage: TestSuite(M).run() """ - def __classcall_private__(cls, phi, names): + def __classcall_private__(cls, phi, dual, names): r""" Normalize the input and construct this Anderson motive. @@ -611,6 +511,8 @@ def __classcall_private__(cls, phi, names): - ``phi`` -- a Drinfeld module + - ``dual`` -- a boolean + - ``names`` -- a string or a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements will be represented as row vectors @@ -635,15 +537,23 @@ def __classcall_private__(cls, phi, names): r = phi.rank() tau = matrix(AK, r) P = phi.gen() - tau[r-1, 0] = (AK.gen() - P[0]) / P[r] - for i in range(1, r): - tau[i-1, i] = 1 - tau[r-1, i] = -P[i]/P[r] + if dual: + divisor = category.divisor() + for i in range(1, r): + tau[i-1, i] = divisor + tau[i-1, 0] = P[i] + tau[r-1, 0] = P[r] + denominator = Factorization([(divisor, 1)]) + else: + tau[r-1, 0] = (AK.gen() - P[0]) / P[r] + for i in range(1, r): + tau[i-1, i] = 1 + tau[r-1, i] = -P[i]/P[r] + denominator = Factorization([]) names = normalize_names(names, r) - denominator = Factorization([]) - return cls.__classcall__(cls, tau, category._ore_polring, denominator, names, category, phi) + return cls.__classcall__(cls, tau, category._ore_polring, denominator, names, category, phi, dual) - def __init__(self, mat, ore, denominator, names, category, phi) -> None: + def __init__(self, mat, ore, denominator, names, category, phi, dual) -> None: r""" Initialize this Anderson motive. @@ -658,10 +568,12 @@ def __init__(self, mat, ore, denominator, names, category, phi) -> None: (0, 1) """ super().__init__(mat, ore, denominator, names, category) - Ktau = phi.ore_polring() - self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) - Ktau.register_conversion(AndersonToDrinfeld(Homset(self, Ktau), phi)) + if not dual: + Ktau = phi.ore_polring() + self.register_coercion(DrinfeldToAnderson(Homset(Ktau, self), phi)) + Ktau.register_conversion(AndersonToDrinfeld(Homset(self, Ktau), phi)) self._drinfeld_module = phi + self._dual = dual def __reduce__(self): r""" @@ -676,8 +588,14 @@ def __reduce__(self): sage: M = phi.anderson_motive() sage: loads(dumps(M)) is M True + + :: + + sage: Md = phi.anderson_motive(dual=True) + sage: loads(dumps(Md)) is Md + True """ - return AndersonMotive_drinfeld, (self._drinfeld_module, self._names) + return AndersonMotive_drinfeld, (self._drinfeld_module, self._dual, self._names) def _repr_(self): r""" @@ -691,8 +609,17 @@ def _repr_(self): sage: M. = phi.anderson_motive() sage: M # indirect doctest Anderson motive of Drinfeld module defined by T |--> (2*z + 2)*τ^2 + z^2*τ + z + + :: + + sage: Md = phi.anderson_motive(dual=True) + sage: Md # indirect doctest + Dual Anderson motive of Drinfeld module defined by T |--> (2*z + 2)*τ^2 + z^2*τ + z """ - s = "Anderson motive " + if self._dual: + s = "Dual Anderson motive " + else: + s = "Anderson motive " if self._names is not None: s += "<" + ", ".join(self._names) + "> " s += "of %s" % self._drinfeld_module @@ -1168,7 +1095,7 @@ def AndersonMotive(arg1, arg2=None, names=None): if isinstance(arg1, DrinfeldModule): if arg2 is not None: raise ValueError("too many arguments") - return AndersonMotive_drinfeld(arg1, names=names) + return AndersonMotive_drinfeld(arg1, False, names=names) # (2) arg1 is a category category = None diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 997eb4aea0f..1766e1390e0 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2084,9 +2084,10 @@ def scalar_multiplication(self, x): raise ValueError("%s is not element of the function ring" % x) return self.Hom(self)(x) - def anderson_motive(self, names=None): + def anderson_motive(self, dual=False, names=None): r""" - Return the Anderson motive attached to this Drinfeld module. + Return the Anderson motive, or its dual depending on the + attribute ``dual``, attached to this Drinfeld module. By definition, the Anderson motive of a Drinfeld module `\phi : A \to K\{\tau\}` is `K\{\tau\}` endowed by: @@ -2099,6 +2100,8 @@ def anderson_motive(self, names=None): INPUT: + - ``dual`` - a boolean (default: ``False``) + - ``names`` - a string of a list of strings (default: ``None``), the names of the vector of the canonical basis; if ``None``, elements are represented as row vectors @@ -2138,7 +2141,7 @@ def anderson_motive(self, names=None): in SageMath. """ from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_drinfeld - return AndersonMotive_drinfeld(self, names=names) + return AndersonMotive_drinfeld(self, dual, names=names) def frobenius_relative(self, n=1): r""" From 510d18b344b9415fdfb688306d241b6922401317 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 15 Nov 2025 08:10:07 +0100 Subject: [PATCH 49/59] rework documentation of AndersonMotive --- .../drinfeld_modules/anderson_motive.py | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index af85a15c887..4c32b2cd5e2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -253,7 +253,7 @@ def __classcall_private__(cls, category, tau, twist=0, names=None, normalize=Tru - ``twist`` -- an integer (default: ``0``) - ``names`` -- a string or a list of strings (default: ``None``), - the names of the vector of the canonical basis; if ``None``, + the names of the vectors of the canonical basis; if ``None``, elements will be represented as row vectors - ``normalize`` -- a boolean (default: ``True``) @@ -514,7 +514,7 @@ def __classcall_private__(cls, phi, dual, names): - ``dual`` -- a boolean - ``names`` -- a string or a list of strings (default: ``None``), - the names of the vector of the canonical basis; if ``None``, + the names of the vectors of the canonical basis; if ``None``, elements will be represented as row vectors TESTS:: @@ -967,26 +967,30 @@ def AndersonMotive(arg1, arg2=None, names=None): INPUT: - The two first arguments can be one of the followings: + - ``arg1``, ``arg2`` -- arguments defining the Anderson + motive, they can be: - - a pair `(A, K)` where `A` is the underlying function - ring (which currently needs to be of the form `\GF{q}[t]`) - and `K` is the `A`-field; these parameters correspond to - the trivial Anderson motive over `A \otimes K` + - a Drinfeld module and ``None`` - - a pair `(A, z)` where `A = \GF{q}[t]` is the function - base ring and `z` is an element; the `A`-field is then - then parent `K` of `z` viewed as an algebra over `A` - through `A \mapsto K, T \mapsto z`. + - the underlying function ring `A` (which currently needs + to be of the form `\GF{q}[t]`) and a `A`-field `K`; these + parameters correspond to the trivial Anderson motive over + `A \otimes K` - - a pair `(A, \tau)` where + - the underlying function ring `A` and an element `z` in it; + the `A`-field is then the parent `K` of `z` viewed as an + algebra over `A` through `A \mapsto K, T \mapsto z`, and + the returned Anderson motive is again the trivial one over + `A \otimes K` - - `A` is either `\GF{q}[t]` or a category (of Drinfeld - modules or Anderson motives) + - `A` and `\tau` where + - `A` is either `\GF{q}[t]` or a category (of Drinfeld + modules or Anderson motives) + - `\tau` is the matrix defining the Anderson motive - - `\tau` is the matrix defining the Anderson motive - - - a Drinfeld module + - ``names`` -- a string or a list of strings (default: ``None``), + the names of the vectors of the canonical basis; if ``None``, + elements will be represented as row vectors EXAMPLES:: From e7be6eadec50828917132a0807e6765d6b521419 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 15 Nov 2025 08:54:32 +0100 Subject: [PATCH 50/59] fix indentation --- .../rings/function_field/drinfeld_modules/anderson_motive.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 4c32b2cd5e2..80827abd062 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -983,9 +983,11 @@ def AndersonMotive(arg1, arg2=None, names=None): the returned Anderson motive is again the trivial one over `A \otimes K` - - `A` and `\tau` where + - `A` and `\tau` where: + - `A` is either `\GF{q}[t]` or a category (of Drinfeld modules or Anderson motives) + - `\tau` is the matrix defining the Anderson motive - ``names`` -- a string or a list of strings (default: ``None``), From c4920401391d3881030410f80fad369a4e01039b Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 13 Dec 2025 10:15:58 +0100 Subject: [PATCH 51/59] distinguish between base and A_field --- src/sage/categories/drinfeld_modules.py | 35 ++++++------------- .../function_field/drinfeld_modules/action.py | 4 +-- .../drinfeld_modules/drinfeld_module.py | 19 +++------- src/sage/rings/ring_extension_element.pyx | 5 ++- 4 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 4f54127ba26..62c4b8cbecc 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -79,7 +79,7 @@ class DrinfeldModules(Category_over_base_ring): The base field is retrieved using the method :meth:`base`:: sage: C.base() - Finite Field in z of size 11^4 over its base + Finite Field in z of size 11^4 Equivalently, one can use :meth:`base_morphism` to retrieve the base morphism:: @@ -170,13 +170,6 @@ class DrinfeldModules(Category_over_base_ring): ... TypeError: base field must be a ring extension - Note that `C.base_morphism()` has codomain `K` while - the defining morphism of `C.base()` has codomain `K` viewed - as an `A`-field. Thus, they differ:: - - sage: C.base().defining_morphism() == C.base_morphism() - False - :: sage: base = Hom(A, A)(1) @@ -276,7 +269,7 @@ def __init__(self, base_morphism, name='τ'): i = A.coerce_map_from(Fq) Fq_to_K = self._base_morphism * i self._base_over_constants_field = base_field.over(Fq_to_K) - super().__init__(base=base_field.over(base_morphism)) + super().__init__(base=base_field) def _latex_(self): r""" @@ -377,7 +370,7 @@ def A_field(self): sage: C.A_field() Finite Field in z of size 5^12 over its base """ - return self.base() + return self.base().over(self._base_morphism) def base_morphism(self): r""" @@ -461,7 +454,7 @@ def constant_coefficient(self): sage: C = phi.category() sage: C.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: C.constant_coefficient() == C.base()(T) + sage: C.constant_coefficient() == C.A_field()(T) True """ return self._constant_coefficient @@ -616,15 +609,8 @@ def A_field(self): def base(self): r""" - Return the underlying `A`-field of this Drinfeld module, - viewed as an algebra over the function ring `A`. - - This is an instance of the class - :class:`sage.rings.ring_extension.RingExtension`. - - .. NOTE:: - - This method has the same behavior as :meth:`A_field`. + Return the field over which this Drinfeld module + is defined. EXAMPLES:: @@ -634,14 +620,15 @@ def base(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.base() - Finite Field in z12 of size 5^12 over its base + Finite Field in z12 of size 5^12 The base can be infinite:: sage: sigma = DrinfeldModule(A, [T, 1]) sage: sigma.base() - Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 over its base + Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 """ + # should we add a deprecation? return self.category().base() def base_morphism(self): @@ -758,8 +745,8 @@ def constant_coefficient(self): `\gamma(T)`:: sage: C = phi.category() - sage: base = C.base() - sage: base(T) == phi.constant_coefficient() + sage: F = C.A_field() + sage: F(T) == phi.constant_coefficient() True Naturally, two Drinfeld modules in the same category have the diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 9bb50a00a18..953d8bc9d1c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -59,7 +59,7 @@ class DrinfeldModuleAction(Action): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by T |--> τ^3 + z The action on elements is computed as follows:: @@ -174,7 +174,7 @@ def _repr_(self) -> str: sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> τ^3 + z + Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by T |--> τ^3 + z """ return f'Action on {self._base} induced by ' \ f'{self._drinfeld_module}' diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index efa6b58eab5..51df74c0afc 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -101,7 +101,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: psi Drinfeld module defined by T |--> (T + 1)*τ + T sage: psi.base() - Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 7^2 over its base + Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 7^2 .. NOTE:: @@ -213,7 +213,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): :meth:`base`:: sage: phi.base() - Finite Field in z of size 3^12 over its base + Finite Field in z of size 3^12 The base morphism is retrieved using :meth:`base_morphism`:: @@ -223,14 +223,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 Defn: T |--> z - Note that the base field is *not* the field `K`. Rather, it is a - ring extension - (see :class:`sage.rings.ring_extension.RingExtension`) whose - underlying ring is `K` and whose base is the base morphism:: - - sage: phi.base() is K - False - .. RUBRIC:: Getters One can retrieve basic properties:: @@ -403,7 +395,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: action = phi.action() sage: action - Action on Finite Field in z of size 3^12 over its base + Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by T |--> τ^2 + τ + z The action on elements is computed by calling the action object:: @@ -711,7 +703,7 @@ def __call__(self, a): :: sage: a = A.random_element(5) - sage: phi(a)[0] == phi.category().base()(a) + sage: phi(a)[0] == phi.A_field()(a) True """ return self._morphism(a) @@ -859,7 +851,7 @@ def action(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: action = phi.action() sage: action - Action on Finite Field in z12 of size 5^12 over its base + Action on Finite Field in z12 of size 5^12 induced by Drinfeld module defined by T |--> z12^5*τ^2 + z12^3*τ + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 @@ -1446,7 +1438,6 @@ def is_isomorphic(self, other, absolutely=False) -> bool: if absolutely: return True else: - ue = ue.backend(force=True) try: _ = ue.nth_root(e) except ValueError: diff --git a/src/sage/rings/ring_extension_element.pyx b/src/sage/rings/ring_extension_element.pyx index c96172db27c..e49aee4fdb1 100644 --- a/src/sage/rings/ring_extension_element.pyx +++ b/src/sage/rings/ring_extension_element.pyx @@ -167,7 +167,10 @@ cdef class RingExtensionElement(CommutativeAlgebraElement): """ if (self._parent)._import_methods: output = self._backend(*to_backend(args), **to_backend(kwargs)) - return from_backend(output, self._parent) + if args: + return from_backend(output, args[0].parent()) + else: + return from_backend(output, self._parent) return TypeError("this element is not callable") def __dir__(self): From 95704d09d8f27902318628dc6255574f863f8024 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 13 Dec 2025 10:24:17 +0100 Subject: [PATCH 52/59] fix A_field --- src/sage/categories/anderson_motives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index 7589f6c24d6..9aaed13c4e0 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -93,7 +93,7 @@ def __init__(self, category): """ self._drinfeld_category = category self._base_morphism = category.base_morphism() - self._A_field = category.base() + self._A_field = category.A_field() self._function_ring = A = category.function_ring() self._base_over_constants_field = category.base_over_constants_field() self._ore_variable_name = category._ore_variable_name From 3aaf4952a9b5077a526769a0fb99ecfb0fce5e81 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 13 Dec 2025 22:28:09 +0100 Subject: [PATCH 53/59] set default_prec in the O(.) construction --- src/sage/rings/big_oh.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/big_oh.py b/src/sage/rings/big_oh.py index 6a05e39c849..5c93dea3912 100644 --- a/src/sage/rings/big_oh.py +++ b/src/sage/rings/big_oh.py @@ -183,8 +183,8 @@ def O(*x, **kwds): x = x.denominator() if isinstance(x, Polynomial) and x.is_monomial(): from sage.rings.infinity import infinity - C = x.parent().completion(infinity) n = x.degree() + C = x.parent().completion(infinity, prec=n) return C.zero().add_bigoh(n) if isinstance(x, Polynomial): @@ -193,11 +193,11 @@ def O(*x, **kwds): raise NotImplementedError("completion only currently defined " "for univariate polynomials") if x.is_monomial(): - C = A.completion(A.variable_name()) n = x.degree() + C = A.completion(A.variable_name(), prec=n) if not x.is_monomial(): p, n = x.perfect_power() - C = A.completion(p) + C = A.completion(p, prec=n) return C.zero().add_bigoh(n) if isinstance(x, (int, Integer, Rational)): From efa36ae392ecfa17453caba45a5c5d8f45aaaebe Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 15 Dec 2025 05:41:44 +0100 Subject: [PATCH 54/59] use dynamic classes --- src/sage/categories/anderson_motives.py | 12 ++++++++ .../drinfeld_modules/anderson_motive.py | 29 ++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py index 9aaed13c4e0..f12ac9c1d8b 100644 --- a/src/sage/categories/anderson_motives.py +++ b/src/sage/categories/anderson_motives.py @@ -342,6 +342,18 @@ def super_categories(self): AKtau = self._ore_polring return [OreModules(AKtau.base(), AKtau)] + def cls(self): + from sage.rings.fraction_field import FractionField_1poly_field + K = self._base_combined.base_ring() + if (isinstance(K, FractionField_1poly_field) + and self._constant_coefficient == K.gen()): + from sage.rings.function_field.drinfeld_modules.anderson_motive_rational import AndersonMotive_rational + cls = AndersonMotive_rational + else: + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general + cls = AndersonMotive_general + return cls + class ParentMethods: def function_ring(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py index 80827abd062..bf7eb28ad1d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -197,6 +197,7 @@ # http://www.gnu.org/licenses/ # ***************************************************************************** +from sage.structure.dynamic_class import dynamic_class from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex @@ -299,11 +300,7 @@ def __classcall_private__(cls, category, tau, twist=0, names=None, normalize=Tru denominator = Factorization([(category.divisor(), twist)]) ore = category._ore_polring - # if (isinstance(K, FractionField_1poly_field) - # and category.constant_coefficient() == K.gen()): - # from sage.rings.function_field.drinfeld_modules.anderson_motive_rational import AndersonMotive_rational - # cls = AndersonMotive_rational - + cls = category.cls() return cls.__classcall__(cls, tau, ore, denominator, names, category) def __init__(self, mat, ore, denominator, names, category) -> None: @@ -313,7 +310,7 @@ def __init__(self, mat, ore, denominator, names, category) -> None: OreModule.__init__(self, mat, ore, denominator, names, category) self._initialize_attributes() - def _initialize_attributes(self): + def _initialize_attributes(self, cls=None): r""" Set the main attributes to this Anderson motive. @@ -327,9 +324,14 @@ def _initialize_attributes(self): [(_, self._twist)] = self._denominator else: self._twist = 0 - self._general_class = AndersonMotive_general - self._submodule_class = AndersonSubMotive - self._quotientModule_class = AndersonQuotientMotive + cls = self._general_class = self._category.cls() + if cls is AndersonMotive_general: + self._submodule_class = AndersonSubMotive + self._quotientModule_class = AndersonQuotientMotive + else: + postfix = cls.__name__[14:] + self._submodule_class = dynamic_class("AndersonSubMotive" + postfix, (AndersonSubMotive,), cls) + self._quotientModule_class = dynamic_class("AndersonQuotientMotive" + postfix, (AndersonQuotientMotive,), cls) def __reduce__(self): r""" @@ -551,6 +553,11 @@ def __classcall_private__(cls, phi, dual, names): tau[r-1, i] = -P[i]/P[r] denominator = Factorization([]) names = normalize_names(names, r) + + cls = category.cls() + if cls is not AndersonMotive_drinfeld: + postfix = cls.__name__[14:] + cls = dynamic_class("AndersonMotive_drinfeld" + postfix, (AndersonMotive_drinfeld,), cls) return cls.__classcall__(cls, tau, category._ore_polring, denominator, names, category, phi, dual) def __init__(self, mat, ore, denominator, names, category, phi, dual) -> None: @@ -678,7 +685,7 @@ def __init__(self, ambient, submodule, names): """ OreSubmodule.__init__(self, ambient, submodule, names) - self._initialize_attributes() + self._initialize_attributes(ambient._general_class) def __reduce__(self): r""" @@ -728,7 +735,7 @@ def __init__(self, cover, submodule, names): """ OreQuotientModule.__init__(self, cover, submodule, names) - self._initialize_attributes() + self._initialize_attributes(cover._general_class) def __reduce__(self): r""" From 57b0382feab5aca5d28c1037148ad7def0030346 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 15 Dec 2025 08:00:40 +0100 Subject: [PATCH 55/59] import code --- .../anderson_motive_rational.py | 437 ++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py new file mode 100644 index 00000000000..8f343de4486 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py @@ -0,0 +1,437 @@ +# ***************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.misc.timing import walltime + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute + +from sage.functions.other import ceil +from sage.functions.other import binomial +from sage.matrix.constructor import matrix + +from sage.rings.infinity import Infinity +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.fraction_field import FractionField_1poly_field +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.laurent_series_ring import LaurentSeriesRing + +from sage.categories.anderson_motives import AndersonMotives +from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general + + +def normalize_place(A, place, infty=True): + if infty and place is Infinity: + return place + if place in A.base_ring(): + return A.gen() - place + elif place in A: + place = A(place) + if place.degree() == 0: + return A.gen() - place + if place.is_irreducible(): + return place.monic() + if infty: + raise ValueError("place must be Infinity or an irreducible polynomial") + else: + raise ValueError("place must an irreducible polynomial") + + +class AndersonMotive_rational(AndersonMotive_general): + def _initialize_attributes(self): + r""" + Set the main attributes to this Anderson motive. + + .. NOTE:: + + Separating this method from `__init__` makes it easier + to call it in subclasses. + """ + AndersonMotive_general._initialize_attributes(self) + AK = self.base() + K = self._K = AK.base_ring() + self._Fq = K.base_ring() + self._q = self._Fq.cardinality() + self._deg = self._Fq.degree() + self._t_name = self.base().variable_name() + self._K_int = Kint = K.ring() + self._AK_int = PolynomialRing(Kint, name=self._t_name) + self._theta_int = Kint.gen() + self._theta_name = Kint.variable_name() + + @lazy_attribute + def _tau_int(self): + q = self._q + K = self._K + Kint = self._K_int + denom = Kint.one() + tau = self._tau + for entry in tau.list(): + for coeff in entry.list(): + denom = denom.lcm(coeff.denominator()) + scalar = Kint.one() + for F, m in denom.factor(): + e = (-m) // (q-1) + scalar *= F ** (-e*(q-1)) + return (scalar * tau).change_ring(self._AK_int) + + @lazy_attribute + def _dettau_int(self): + det = self._tau_int.det() + return det.leading_coefficient(), det.degree() + + @lazy_attribute + def _bad_places(self): + disc, _ = self._dettau_int + return [F for F, _ in disc.factor()] + + @cached_method + def _local_maximal_model(self, place): + q = self._q + Fq = self._Fq + r = self.rank() + + F = Fq.extension(place, name='w') + if place.degree() == 1: + a = -place[0] + else: + a = F.gen() + A = PolynomialRing(F, self._t_name) + K = PolynomialRing(F, 'v') + v = K.gen() + S = PolynomialRing(K, self._t_name) + base_map = F.frobenius_endomorphism(self._deg * (place.degree() - 1)) + psiA = A.hom([A.gen()], base_map=base_map) + + tau = matrix(S, r, r, + [ S([c(v+a) for c in entry.list()]) for entry in self._tau_int.list() ]) + disc, _ = self._dettau_int + val = val0 = disc.valuation(place) + + # 1st sequence: the N_i + while val >= q - 1: + rows = [ [ A([f[e] for f in c.list()]) + for c in taurow.list() for e in range(q) ] + for taurow in tau.rows() ] + M = matrix(A, rows) + basis = M.minimal_kernel_basis() + dim = basis.nrows() + if dim == 0: + break + M = basis.stack(basis.basis_completion()) + N = M.inverse_of_unit() + N = N.parent()([psiA(x) for x in N.list()]) + tau = M * tau * N + for i in range(dim): + for j in range(dim): + tau[i,j] = S([x >> q-1 for x in tau[i,j].list()]) + for j in range(dim, r): + tau[i,j] = S([x >> q for x in tau[i,j].list()]) + tau[j,i] = S([x << 1 for x in tau[j,i].list()]) + val -= (q-1) * dim + + # 2nd sequence: the L_i + if val >= q - 1: + # The following can be improved + while True: + rows = [ [ A([f[e] for f in c.list()]) + for c in taurow.list() for e in range(q-1) ] + for taurow in tau.rows() ] + M = matrix(A, rows) + if M.is_zero(): + break + basis = M.minimal_kernel_basis() + dim = basis.nrows() + M = basis.stack(basis.basis_completion()) + N = M.inverse_of_unit() + N = N.parent()([psiA(x) for x in N.list()]) + tau = M * tau * N + for i in range(dim, r): + for j in range(dim, r): + tau[i,j] = S([x << q-1 for x in tau[i,j].list()]) + for j in range(dim): + tau[i,j] = S([x << q for x in tau[i,j].list()]) + tau[j,i] = S([x >> 1 for x in tau[j,i].list()]) + val += (q-1) * (r - dim) + for i in range(r): + for j in range(r): + tau[i,j] = S([x >> q-1 for x in tau[i,j].list()]) + val -= (q-1) * r + + if val < val0: + tau0 = matrix(A, r, r, + [ A([c(a) for c in entry.list()]) for entry in self._tau_int.list() ]) + tau1 = matrix(A, r, r, + [ A([c[0] for c in entry.list()]) for entry in tau.list() ]) + return val, (tau0, tau1) + else: + return val, None + + def discriminant(self): + disc = self._K_int.one() + for place in self._bad_places: + val = self._local_maximal_model(place)[0] + disc *= place ** val + return disc + + def bad_reduction_places(self): + return [place for place in self._bad_places if self._local_maximal_model(place)[0]] + + def has_good_reduction(self): + return not bool(self.bad_reduction_places()) + + def has_good_reduction_at(self, place): + place = normalize_place(self._K_int, place, infty=False) + return not bool(self._local_maximal_model(place)[0]) + + def reduce(self, place, a=None): + place = normalize_place(self._K_int, place, infty=False) + val, taus = self._local_maximal_model(place) + if val > 0: + raise ValueError("bad reduction") + + if a is None: + a = 'a' + Fq = self._Fq + F = Fq.extension(place, name=a) + if place.degree() == 1: + a = -place[0] + else: + a = F.gen() + A = self._A + F = F.over(A.hom([a])) + + B = PolynomialRing(F, self._t_name) + r = self.rank() + if taus is None: + tau = matrix(B, r, r, + [ B([c(a) for c in entry.list()]) for entry in self._tau_int.list() ]) + else: + _, tau = taus + tau = matrix(B, r, r, + [ B([F(c) for c in entry.list()]) for entry in tau.list() ]) + + category = AndersonMotives(F) + return AndersonMotive_general(category, tau, self._twist) + + def local_factor(self, place, x='x'): + place = normalize_place(self._K_int, place, infty=False) + _, taus = self._local_maximal_model(place) + + Fq = self._Fq + F = Fq.extension(place, name='w') + d = place.degree() + if d == 1: + a = -place[0] + else: + a = F.gen() + A = self._A + B = PolynomialRing(F, self._t_name) + phiB = B.hom([B.gen()], base_map = F.frobenius_endomorphism(self._deg)) + r = self.rank() + if taus is None: + tau = matrix(B, r, r, + [ B([c(a) for c in entry.list()]) for entry in self._tau_int.list() ]) + else: + _, tau = taus + tau = matrix(B, r, r, + [ B([F(c) for c in entry.list()]) for entry in tau.list() ]) + + T = tau + for _ in range(1, d): + tau = tau.parent()([phiB(y) for y in tau.list()]) + T = tau * T + + chi = T.charpoly(var=x).reverse() # .change_ring(A), fix this! + x = chi.parent().gen() + t = B.gen() + return chi(x**d / place(t) ** self._twist) + + def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): + n = self.rank() + h = self._twist + q = self._q + + tme = walltime() + + # Figure out which precision is needed + val = 0 + if place is Infinity: + for entry in self._tau_int.list(): + val = max(val, entry.degree()) # t-degree + prectau = prec + n*val + max(0, h) + else: + prectau = prec + + # Construct the completion + if a is None: + a = 'a' + if u is None: + u = 'u' + completion = MorphismToCompletion(self._A, place, prectau, a, u) + place = completion.place() + C = completion.codomain() + k = completion.residue_field() + t = completion(self._A.gen()) # t in C + Ctheta = PolynomialRing(C, self._theta_name) + theta = Ctheta.gen() + + S = PolynomialRing(C, name='x') + + if verbose: + print(" [%.5f] rings created" % walltime(tme)) + + # Correction at bad places + corr_num = S.one() + corr_denom = S.one() + for pl in self._bad_places: + if place is not Infinity and pl == place(self._theta): + continue + _, taus = self._local_maximal_model(pl) + if taus is None: + continue + tau0, tau1 = taus + d = pl.degree() + A = tau0.base_ring() + F = A.base_ring() + phiA = A.hom([A.gen()], base_map = F.frobenius_endomorphism(self._deg)) + T0 = tau0; T1 = tau1 + for _ in range(1, d): + tau0 = tau0.parent()([phiA(y) for y in tau0.list()]) + T0 = tau0 * T0 + tau1 = tau1.parent()([phiA(y) for y in tau1.list()]) + T1 = tau1 * T1 + if d > 1: + ell = PolynomialRing(k, name='w').quo(pl) + Aell = A.change_ring(ell) + T0 = T0.change_ring(Aell) + T1 = T1.change_ring(Aell) + Cell = PowerSeriesRing(ell, name='x') + v = Cell([t[i] for i in range(prectau)], prec=prectau) + else: + Cell = C + v = t.add_bigoh(prectau) + T0 = matrix(n, n, [f(v).add_bigoh(prectau) for f in T0.list()]) + T1 = matrix(n, n, [f(v).add_bigoh(prectau) for f in T1.list()]) + scalar = pl(v) ** (-h) + chi0 = (scalar*T0).charpoly() + chi1 = (scalar*T1).charpoly() + if d > 1: + coeffs = [] + for z in chi0.list(): + coeff = C([y.lift()[0] for y in z.list()], z.valuation(), z.precision_absolute()) + coeffs += [coeff] + (d-1)*[C.zero()] + chi0 = S(coeffs) + coeffs = [] + for z in chi1.list(): + coeff = C([y.lift()[0] for y in z.list()], z.valuation(), z.precision_absolute()) + coeffs += [coeff] + (d-1)*[C.zero()] + chi1 = S(coeffs) + corr_num *= chi0 + corr_denom *= chi1 + + if verbose: + print(" [%.5f] bad places handled" % walltime(tme)) + + # Computation of rho + hi = h + current_prec = 1 + if place is Infinity: + v = Ctheta(C.gen().add_bigoh(prectau - h)) + rho = v ** h + while current_prec < prectau - h: + hj = ceil(hi / q) + e = q*hj - hi + vt = 1 - v*theta + rho *= vt ** e + hi = hj + v = v ** q + current_prec *= q + else: + d = completion.degree() + v = t.add_bigoh(prec) + a = v[0] + m = 0 + ev = -h + rho = Ctheta(1) + while current_prec < prectau: + hj = ceil(hi / q) + e = q*hj - hi + rho *= (v - theta) ** e + ev -= e * q**m + hi = hj + v = v ** q + current_prec *= q + m = (m + 1) % d + ev %= q**d - 1 + for _ in range(d): + ev, e = ev.quo_rem(q) + rho *= (a - theta) ** (e + q - 1) + a = a ** q + + if verbose: + print(" [%.5f] rho computed" % walltime(tme)) + + # Computation of tau* + u = Ctheta.gen() + B = [ ] + vals = [ ] + kmax = 1 + for entry in self._tau_int.list(): + e = Ctheta.zero() + for i in range(entry.degree() + 1): + e += entry[i](theta) * t**i + e *= rho + B.append(e) + kmax = max(kmax, e.degree() // (q - 1)) + if verbose: + print(" [%.5f] matrix B computed" % walltime(tme)) + rows = [ ] + for i in range(n): + for k in range(kmax): + row = [ ] + for j in range(n): + s = B[i + n*j] + row += [s[q*kp - k + q - 1] for kp in range(kmax)] + rows.append(row) + taudual = matrix(rows) + + if verbose: + print(" [%.5f] tau* computed (size=%s)" % (walltime(tme), len(rows))) + + # Computation of the L-series + chi = taudual.charpoly() + if verbose: + print(" [%.5f] characteristic polynomial computed" % walltime(tme)) + chi *= corr_num + chi //= corr_denom + L = S([l.add_bigoh(prec) for l in chi]).reverse() + + if verbose: + print(" [%.5f] L-series computed" % walltime(tme)) + + # Format and return the final result + if x is None: + return L + elif isinstance(x, str): + return L.change_variable_name(x) + else: + return L(completion(x)) + + def special_value(self, place, prec, a=None, u=None, verbose=False): + L = self.Lseries(place, prec, a=a, u=u, verbose=verbose) + x = L.parent().gen() + order = 0 + value = L(1) + while value == 0: + L = L // (x - 1) + value = L(1) + order += 1 + return value, order From 423ed7bb803a205f9a0ba4510dca5609c44e62c7 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 15 Dec 2025 18:52:08 +0100 Subject: [PATCH 56/59] new completion framework --- .../en/reference/drinfeld_modules/index.rst | 2 + .../anderson_motive_rational.py | 71 ++++++++++--------- .../drinfeld_modules/carlitz_module.py | 2 +- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/doc/en/reference/drinfeld_modules/index.rst b/src/doc/en/reference/drinfeld_modules/index.rst index 083f2c0404f..6d48f7935ff 100644 --- a/src/doc/en/reference/drinfeld_modules/index.rst +++ b/src/doc/en/reference/drinfeld_modules/index.rst @@ -11,6 +11,7 @@ Drinfeld modules .. toctree:: :maxdepth: 2 + sage/rings/function_field/drinfeld_modules/carlitz_module sage/rings/function_field/drinfeld_modules/drinfeld_module sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero sage/rings/function_field/drinfeld_modules/drinfeld_module_finite @@ -32,5 +33,6 @@ Anderson motives :maxdepth: 2 sage/rings/function_field/drinfeld_modules/anderson_motive + sage/rings/function_field/drinfeld_modules/anderson_motive_rational .. include:: ../footer.txt diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py index 8f343de4486..96cecbf7f8f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py @@ -1,5 +1,13 @@ +r""" +Anderson motives over $\GF{q}[T, z]$ + +AUTHOR: + +- Xavier Caruso (2025-12): initial version +""" + # ***************************************************************************** -# Copyright (C) 2024 Xavier Caruso +# Copyright (C) 2025 Xavier Caruso # # 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 @@ -63,8 +71,8 @@ def _initialize_attributes(self): self._t_name = self.base().variable_name() self._K_int = Kint = K.ring() self._AK_int = PolynomialRing(Kint, name=self._t_name) - self._theta_int = Kint.gen() - self._theta_name = Kint.variable_name() + self._z_int = Kint.gen() + self._z_name = Kint.variable_name() @lazy_attribute def _tau_int(self): @@ -205,7 +213,7 @@ def reduce(self, place, a=None): a = -place[0] else: a = F.gen() - A = self._A + A = self.function_ring() F = F.over(A.hom([a])) B = PolynomialRing(F, self._t_name) @@ -221,7 +229,7 @@ def reduce(self, place, a=None): category = AndersonMotives(F) return AndersonMotive_general(category, tau, self._twist) - def local_factor(self, place, x='x'): + def local_factor(self, place, var='X'): place = normalize_place(self._K_int, place, infty=False) _, taus = self._local_maximal_model(place) @@ -232,7 +240,7 @@ def local_factor(self, place, x='x'): a = -place[0] else: a = F.gen() - A = self._A + A = self.function_ring() B = PolynomialRing(F, self._t_name) phiB = B.hom([B.gen()], base_map = F.frobenius_endomorphism(self._deg)) r = self.rank() @@ -249,15 +257,17 @@ def local_factor(self, place, x='x'): tau = tau.parent()([phiB(y) for y in tau.list()]) T = tau * T - chi = T.charpoly(var=x).reverse() # .change_ring(A), fix this! + chi = T.charpoly(var=var).reverse() x = chi.parent().gen() t = B.gen() - return chi(x**d / place(t) ** self._twist) + L = chi(x**d / place(t) ** self._twist) + return L.change_ring(A) - def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): + def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): n = self.rank() h = self._twist q = self._q + place = normalize_place(self._K_int, place) tme = walltime() @@ -271,17 +281,12 @@ def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): prectau = prec # Construct the completion - if a is None: - a = 'a' - if u is None: - u = 'u' - completion = MorphismToCompletion(self._A, place, prectau, a, u) - place = completion.place() - C = completion.codomain() - k = completion.residue_field() - t = completion(self._A.gen()) # t in C - Ctheta = PolynomialRing(C, self._theta_name) - theta = Ctheta.gen() + A = self.function_ring() + C = A.completion(place, prectau) + k = C.residue_field() + t = C(A.gen()) # t in C + Cz = PolynomialRing(C, self._z_name) + z = Cz.gen() S = PolynomialRing(C, name='x') @@ -292,7 +297,7 @@ def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): corr_num = S.one() corr_denom = S.one() for pl in self._bad_places: - if place is not Infinity and pl == place(self._theta): + if place is not Infinity and pl == place(self._z): continue _, taus = self._local_maximal_model(pl) if taus is None: @@ -344,27 +349,27 @@ def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): hi = h current_prec = 1 if place is Infinity: - v = Ctheta(C.gen().add_bigoh(prectau - h)) + v = Cz(C.uniformizer().add_bigoh(prectau - h)) rho = v ** h while current_prec < prectau - h: hj = ceil(hi / q) e = q*hj - hi - vt = 1 - v*theta + vt = 1 - v*z rho *= vt ** e hi = hj v = v ** q current_prec *= q else: - d = completion.degree() + d = place.degree() v = t.add_bigoh(prec) a = v[0] m = 0 ev = -h - rho = Ctheta(1) + rho = Cz(1) while current_prec < prectau: hj = ceil(hi / q) e = q*hj - hi - rho *= (v - theta) ** e + rho *= (v - z) ** e ev -= e * q**m hi = hj v = v ** q @@ -373,21 +378,21 @@ def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): ev %= q**d - 1 for _ in range(d): ev, e = ev.quo_rem(q) - rho *= (a - theta) ** (e + q - 1) + rho *= (a - z) ** (e + q - 1) a = a ** q if verbose: print(" [%.5f] rho computed" % walltime(tme)) # Computation of tau* - u = Ctheta.gen() + u = Cz.gen() B = [ ] vals = [ ] kmax = 1 for entry in self._tau_int.list(): - e = Ctheta.zero() + e = Cz.zero() for i in range(entry.degree() + 1): - e += entry[i](theta) * t**i + e += entry[i](z) * t**i e *= rho B.append(e) kmax = max(kmax, e.degree() // (q - 1)) @@ -423,10 +428,10 @@ def Lseries(self, place, prec, x=None, a=None, u=None, verbose=False): elif isinstance(x, str): return L.change_variable_name(x) else: - return L(completion(x)) + return L(C(x)) - def special_value(self, place, prec, a=None, u=None, verbose=False): - L = self.Lseries(place, prec, a=a, u=u, verbose=verbose) + def special_value(self, place=Infinity, prec=20, verbose=False): + L = self.Lseries(place, prec, verbose=verbose) x = L.parent().gen() order = 0 value = L(1) diff --git a/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py b/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py index efac57955f2..cb34894aa92 100644 --- a/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py @@ -1,5 +1,5 @@ r""" -Carlitz module +The Carlitz module and related functions AUTHORS: From 06285c9a4c5f9e923ede04cf977d0a46b0c5ff99 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 16 Dec 2025 13:15:32 +0100 Subject: [PATCH 57/59] implement carlitz_zeta and L-series for Drinfeld modules --- src/sage/rings/function_field/all.py | 1 + .../anderson_motive_rational.py | 122 ++++++++---------- .../drinfeld_modules/carlitz_module.py | 11 ++ .../drinfeld_module_charzero.py | 27 ++++ 4 files changed, 93 insertions(+), 68 deletions(-) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index adb602488d8..037b81cbf41 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -6,5 +6,6 @@ lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "CarlitzModule") lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_exponential") lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_logarithm") +lazy_import("sage.rings.function_field.drinfeld_modules.carlitz_module", "carlitz_zeta") lazy_import("sage.rings.function_field.drinfeld_modules.anderson_motive", "AndersonMotive") diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py index 96cecbf7f8f..b7e2e3c7eaf 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py @@ -33,23 +33,7 @@ from sage.categories.anderson_motives import AndersonMotives from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general - - -def normalize_place(A, place, infty=True): - if infty and place is Infinity: - return place - if place in A.base_ring(): - return A.gen() - place - elif place in A: - place = A(place) - if place.degree() == 0: - return A.gen() - place - if place.is_irreducible(): - return place.monic() - if infty: - raise ValueError("place must be Infinity or an irreducible polynomial") - else: - raise ValueError("place must an irreducible polynomial") +from sage.rings.function_field.drinfeld_modules.drinfeld_module_charzero import normalize_place class AndersonMotive_rational(AndersonMotive_general): @@ -263,11 +247,10 @@ def local_factor(self, place, var='X'): L = chi(x**d / place(t) ** self._twist) return L.change_ring(A) - def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): + def _Lseries(self, place, prec, x, model, verbose): n = self.rank() h = self._twist q = self._q - place = normalize_place(self._K_int, place) tme = walltime() @@ -296,54 +279,52 @@ def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): # Correction at bad places corr_num = S.one() corr_denom = S.one() - for pl in self._bad_places: - if place is not Infinity and pl == place(self._z): - continue - _, taus = self._local_maximal_model(pl) - if taus is None: - continue - tau0, tau1 = taus - d = pl.degree() - A = tau0.base_ring() - F = A.base_ring() - phiA = A.hom([A.gen()], base_map = F.frobenius_endomorphism(self._deg)) - T0 = tau0; T1 = tau1 - for _ in range(1, d): - tau0 = tau0.parent()([phiA(y) for y in tau0.list()]) - T0 = tau0 * T0 - tau1 = tau1.parent()([phiA(y) for y in tau1.list()]) - T1 = tau1 * T1 - if d > 1: - ell = PolynomialRing(k, name='w').quo(pl) - Aell = A.change_ring(ell) - T0 = T0.change_ring(Aell) - T1 = T1.change_ring(Aell) - Cell = PowerSeriesRing(ell, name='x') - v = Cell([t[i] for i in range(prectau)], prec=prectau) - else: - Cell = C - v = t.add_bigoh(prectau) - T0 = matrix(n, n, [f(v).add_bigoh(prectau) for f in T0.list()]) - T1 = matrix(n, n, [f(v).add_bigoh(prectau) for f in T1.list()]) - scalar = pl(v) ** (-h) - chi0 = (scalar*T0).charpoly() - chi1 = (scalar*T1).charpoly() - if d > 1: - coeffs = [] - for z in chi0.list(): - coeff = C([y.lift()[0] for y in z.list()], z.valuation(), z.precision_absolute()) - coeffs += [coeff] + (d-1)*[C.zero()] - chi0 = S(coeffs) - coeffs = [] - for z in chi1.list(): - coeff = C([y.lift()[0] for y in z.list()], z.valuation(), z.precision_absolute()) - coeffs += [coeff] + (d-1)*[C.zero()] - chi1 = S(coeffs) - corr_num *= chi0 - corr_denom *= chi1 - - if verbose: - print(" [%.5f] bad places handled" % walltime(tme)) + if not model: + for pl in self._bad_places: + if place is not Infinity and pl == place(self._z): + continue + _, taus = self._local_maximal_model(pl) + if taus is None: + continue + tau0, tau1 = taus + d = pl.degree() + A = tau0.base_ring() + F = A.base_ring() + phiA = A.hom([A.gen()], base_map = F.frobenius_endomorphism(self._deg)) + T0 = tau0; T1 = tau1 + for _ in range(1, d): + tau0 = tau0.parent()([phiA(y) for y in tau0.list()]) + T0 = tau0 * T0 + tau1 = tau1.parent()([phiA(y) for y in tau1.list()]) + T1 = tau1 * T1 + if d > 1: + ell = PolynomialRing(k, name='w').quo(pl) + Aell = A.change_ring(ell) + T0 = T0.change_ring(Aell) + T1 = T1.change_ring(Aell) + Cell = Aell.completion(place, prectau) + v = Cell(t).add_bigoh(prectau) + else: + Cell = C + v = t.add_bigoh(prectau) + T0 = matrix(n, n, [f(v).add_bigoh(prectau) for f in T0.list()]) + T1 = matrix(n, n, [f(v).add_bigoh(prectau) for f in T1.list()]) + scalar = pl(v) ** (-h) + chi0 = (scalar*T0).charpoly() + chi1 = (scalar*T1).charpoly() + if d > 1: + coeffs = [] + for z in chi0.list(): + coeffs += [C(z)] + (d-1)*[C.zero()] + chi0 = S(coeffs) + coeffs = [] + for z in chi1.list(): + coeffs += [C(z)] + (d-1)*[C.zero()] + chi1 = S(coeffs) + corr_num *= chi0 + corr_denom *= chi1 + if verbose: + print(" [%.5f] bad places handled" % walltime(tme)) # Computation of rho hi = h @@ -430,8 +411,13 @@ def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): else: return L(C(x)) + def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): + # TODO: handle infinite precision here + place = normalize_place(self._K_int, place) + return self._Lseries(place, prec, x, True, verbose) + def special_value(self, place=Infinity, prec=20, verbose=False): - L = self.Lseries(place, prec, verbose=verbose) + L = self._Lseries(place, prec, None, True, verbose) x = L.parent().gen() order = 0 value = L(1) diff --git a/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py b/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py index cb34894aa92..8b7219516db 100644 --- a/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py @@ -21,6 +21,7 @@ from sage.categories.finite_fields import FiniteFields from sage.rings.infinity import Infinity +from sage.matrix.special import identity_matrix from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule @@ -195,3 +196,13 @@ def carlitz_logarithm(A, prec=+Infinity, name='z'): """ C = CarlitzModule(A) return C.logarithm(prec, name) + + +def carlitz_zeta(A, s, prec=20): + from sage.categories.anderson_motives import AndersonMotives + from sage.rings.function_field.drinfeld_modules.anderson_motive import AndersonMotive_general + K = A.fraction_field() + category = AndersonMotives(K.coerce_map_from(A)) + tau = identity_matrix(category.base(), 1) + M = AndersonMotive_general(category, tau, twist=s) + return M._Lseries(Infinity, prec, 1, True, False) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py index 2bfac25eb07..0f354b9501d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py @@ -431,6 +431,25 @@ def goss_polynomial(self, n, var='X'): return self._compute_goss_polynomial(n, q, poly_ring, X) +# Drinfeld modules over Frac(A) + +def normalize_place(A, place, infty=True): + if infty and place is Infinity: + return place + if place in A.base_ring(): + return A.gen() - place + elif place in A: + place = A(place) + if place.degree() == 0: + return A.gen() - place + if place.is_irreducible(): + return place.monic() + if infty: + raise ValueError("place must be Infinity or an irreducible polynomial") + else: + raise ValueError("place must an irreducible polynomial") + + class DrinfeldModule_rational(DrinfeldModule_charzero): """ A class for Drinfeld modules defined over the fraction @@ -644,3 +663,11 @@ def class_polynomial(self): # The class polynomial is then the characteristic # polynomial of N return A(N.charpoly()) + + def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): + # TODO: handle infinite precision here + place = normalize_place(self.function_ring(), place) + if any(g.denominator() != 1 for g in self.coefficients(True)): + raise ValueError("coefficients are not polynomials") + M = self.anderson_motive(dual=True) + return M._Lseries(place, prec, x, True, verbose) From b3de67c4690e74bb2d65c3f4a5fadbc182bb119a Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 16 Dec 2025 22:26:12 +0100 Subject: [PATCH 58/59] change order of arguments --- .../drinfeld_modules/anderson_motive_rational.py | 12 +++++------- .../drinfeld_modules/drinfeld_module_charzero.py | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py index b7e2e3c7eaf..bb5f29d18ce 100644 --- a/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive_rational.py @@ -247,7 +247,7 @@ def local_factor(self, place, var='X'): L = chi(x**d / place(t) ** self._twist) return L.change_ring(A) - def _Lseries(self, place, prec, x, model, verbose): + def _Lseries(self, x, place, prec, model, verbose): n = self.rank() h = self._twist q = self._q @@ -404,20 +404,18 @@ def _Lseries(self, place, prec, x, model, verbose): print(" [%.5f] L-series computed" % walltime(tme)) # Format and return the final result - if x is None: - return L - elif isinstance(x, str): + if isinstance(x, str): return L.change_variable_name(x) else: return L(C(x)) - def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): + def Lseries(self, x='X', place=Infinity, prec=20, verbose=False): # TODO: handle infinite precision here place = normalize_place(self._K_int, place) - return self._Lseries(place, prec, x, True, verbose) + return self._Lseries(x, place, prec, True, verbose) def special_value(self, place=Infinity, prec=20, verbose=False): - L = self._Lseries(place, prec, None, True, verbose) + L = self._Lseries('x', place, prec, True, verbose) x = L.parent().gen() order = 0 value = L(1) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py index 0f354b9501d..0fb14510b52 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module_charzero.py @@ -664,10 +664,10 @@ def class_polynomial(self): # polynomial of N return A(N.charpoly()) - def Lseries(self, place=Infinity, prec=20, x=None, verbose=False): + def Lseries(self, x='X', place=Infinity, prec=20, verbose=False): # TODO: handle infinite precision here place = normalize_place(self.function_ring(), place) if any(g.denominator() != 1 for g in self.coefficients(True)): raise ValueError("coefficients are not polynomials") M = self.anderson_motive(dual=True) - return M._Lseries(place, prec, x, True, verbose) + return M._Lseries(x, place, prec, True, verbose) From a6d96e62cd3c665d9aa246d144f0f5c98a2e036e Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 16 Dec 2025 22:33:07 +0100 Subject: [PATCH 59/59] oops --- .../rings/function_field/drinfeld_modules/carlitz_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py b/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py index 8b7219516db..a8474fac3f1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/carlitz_module.py @@ -205,4 +205,4 @@ def carlitz_zeta(A, s, prec=20): category = AndersonMotives(K.coerce_map_from(A)) tau = identity_matrix(category.base(), 1) M = AndersonMotive_general(category, tau, twist=s) - return M._Lseries(Infinity, prec, 1, True, False) + return M._Lseries(1, Infinity, prec, True, False)