From 5329139f3f2398503682f632ebcbd90b50e366ea Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 6 Nov 2025 12:00:19 +0100 Subject: [PATCH 01/24] 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 02/24] 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 03/24] 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 04/24] 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 05/24] 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 06/24] 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 07/24] 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 08/24] 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 09/24] 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 10/24] 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 11/24] 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 12/24] 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 13/24] 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 14/24] 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 15/24] 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 16/24] 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 17/24] 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 18/24] 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 19/24] 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 20/24] 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 21/24] 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 22/24] 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 23/24] 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 24/24] 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``),