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 diff --git a/src/sage/categories/anderson_motives.py b/src/sage/categories/anderson_motives.py new file mode 100644 index 00000000000..b2b53f28885 --- /dev/null +++ b/src/sage/categories/anderson_motives.py @@ -0,0 +1,439 @@ +r""" +Anderson motives + +AUTHOR: + +- Xavier Caruso (2025-11): initial version +""" + +# ***************************************************************************** +# Copyright (C) 2025 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + + +from sage.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): + 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): + 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): + category = DrinfeldModules(category.base_morphism()) + else: + category = DrinfeldModules(category) + 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() + 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 + twisting_morphism = category.ore_polring().twisting_morphism() + 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 _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())}' + + def __reduce__(self): + r""" + Return the necessary arguments to construct this object, + as per the pickle protocol. + + 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 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, 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: 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 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): + r""" + Return the characteristic of the underlying `A`-field. + + 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.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 4f54127ba26..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``. @@ -260,6 +259,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/modules/free_module.py b/src/sage/modules/free_module.py index e22f7460edc..d94f903abc7 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -1076,9 +1076,18 @@ 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() - yield self.base().an_element() * sum(self.gens()) + gens = self.gens() + if not gens: + return + yield self.base().an_element() * sum(gens) some_elements_base = iter(self.base().some_elements()) n = self.degree() while True: @@ -2481,7 +2490,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 83e335cdf85..94e3ff7c06d 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] @@ -471,6 +472,13 @@ def _element_constructor_(self, x): (t, 0) sage: M(v) (t, 0) + + TESTS:: + + sage: M(0) + (0, 0) + sage: N(0) + (0, 0) """ if isinstance(x, OreModuleElement): M = x.parent()._pushout_(self) @@ -696,7 +704,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 +808,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)) @@ -897,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): @@ -1937,7 +1950,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 +2000,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 +2039,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__() @@ -2349,8 +2362,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) @@ -2493,7 +2505,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 +2556,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 +2597,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) @@ -2895,8 +2907,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/modules/ore_module_element.py b/src/sage/modules/ore_module_element.py index 1c5cad90d76..ebb03da128b 100644 --- a/src/sage/modules/ore_module_element.py +++ b/src/sage/modules/ore_module_element.py @@ -220,9 +220,11 @@ 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 = 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() 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/all.py b/src/sage/rings/function_field/all.py index 459ad867b0d..adb602488d8 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", "AndersonMotive") 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 new file mode 100644 index 00000000000..9d25e226d72 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/anderson_motive.py @@ -0,0 +1,1226 @@ +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`. + +.. 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 +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) + +.. RUBRIC:: More Anderson motives + +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. + +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: + +- Xavier Caruso (2025-11): initial version +""" + +# ***************************************************************************** +# Copyright (C) 2025 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +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 +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 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.drinfeld_module import DrinfeldModule +from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism + + +# Classes for Anderson motives +############################## + +class AndersonMotive_general(OreModule): + 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): + 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)[] + 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 + """ + AK = category.base() + K = AK.base_ring() + + # 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 + + 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, 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): + 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] + else: + self._twist = 0 + self._general_class = AndersonMotive_general + self._submodule_class = AndersonSubMotive + 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 + 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() + + 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.base() + 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() + if self._names is None: + s = "\\texttt{Anderson motive of rank } %s" % self.rank() + s += "\\texttt{ over } %s" % latex(AK) + else: + s = "\\left<" + ", ".join(self._latex_names) + "\\right>" + s += "_{%s}" % latex(AK) + return s + + 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 + 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``. + + 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""" + 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 + + +class AndersonMotive_drinfeld(AndersonMotive_general): + 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): + 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)[] + 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() + 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] + names = normalize_names(names, r) + denominator = Factorization([]) + 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)) + try: + Ktau.register_conversion(AndersonToDrinfeld(Homset(self, Ktau), phi)) + except AssertionError: + pass + self._drinfeld_module = phi + + 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""" + 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): + 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) + + +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): + 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) + + +# Morphisms +########### + +# 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() + 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'): + 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:`sage.rings.function_field.drinfeld_modules.drinfeld_module_finite.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) + + charpoly = characteristic_polynomial + + +class AndersonMotive_homspace(OreModule_homspace): + Element = AndersonMotiveMorphism + + +# 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) + + """ + Map.__init__(self, parent) + self._phi = phi + self._motive = parent.codomain() + self._AK = self._motive.base() + + 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() + 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): + 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) + + """ + 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() + 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 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 + 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 `\GF{q}[t]` or a category (of Drinfeld + modules or Anderson motives) + + - `\tau` is the matrix defining the Anderson motive + + - 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:: + + sage: AndersonMotive(ZZ, K) + Traceback (most recent call last): + ... + TypeError: the first argument must be a Drinfeld module or a polynomial ring + + :: + + 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 + # . 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()) + 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) + + # 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 + + # arg1 is 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 + if isinstance(tau, RingHomomorphism) and tau.domain() is A: + K = tau.codomain() + gamma = tau + elif isinstance(tau, CommutativeRing): + K = tau + 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]) + if K is not None: + try: + if K.variable_name() == A.variable_name(): + K = K.base_ring() + except (AttributeError, ValueError): + pass + category = AndersonMotives(gamma) + AK = category.base() + 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 TypeError("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(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/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index efa6b58eab5..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_module.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_module.finite_drinfeld_module.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: @@ -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 @@ -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]_. @@ -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:: @@ -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) @@ -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:: @@ -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): @@ -2084,6 +2084,62 @@ 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): + 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 row vectors + + 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) + def frobenius_relative(self, n=1): r""" Return the `n`-th iterate relative Frobenius of this Drinfeld module. 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..2bfac25eb07 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 @@ -2,12 +2,6 @@ r""" 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`, -which both inherit -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. - AUTHORS: - David Ayotte (2023-09) @@ -47,7 +41,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: @@ -65,7 +59,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 +444,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 93% 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..355754a1ded 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 @@ -2,11 +2,6 @@ r""" Finite Drinfeld modules -This module provides the class -:class:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule_finite`, -which inherits -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. - AUTHORS: - Antoine Leudière (2022-04) @@ -64,12 +59,13 @@ 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 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 @@ -234,7 +230,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 +290,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, 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 +355,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 +693,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 +702,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 +736,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 +764,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, + 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 +819,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: @@ -1027,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) @@ -1058,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") 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 692ff9511d8..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` """ @@ -930,3 +930,62 @@ 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): + 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) + return H(self)