Skip to content

Constructor for the Carlitz module #40433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
32 changes: 16 additions & 16 deletions src/sage/categories/drinfeld_modules.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# sage_setup: distribution = sagemath-categories
# sage.doctest: needs sage.rings.finite_rings
r"""
Expand Down Expand Up @@ -123,7 +124,7 @@ class DrinfeldModules(Category_over_base_ring):
True

sage: C.ore_polring()
Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11
Ore Polynomial Ring in τ over Finite Field in z of size 11^4 twisted by z |--> z^11
sage: C.ore_polring() is phi.ore_polring()
True

Expand All @@ -135,7 +136,7 @@ class DrinfeldModules(Category_over_base_ring):

sage: psi = C.object([p_root, 1])
sage: psi
Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10
Drinfeld module defined by T |--> τ + z^3 + 7*z^2 + 6*z + 10
sage: psi.category() is C
True

Expand Down Expand Up @@ -207,7 +208,7 @@ class DrinfeldModules(Category_over_base_ring):
TypeError: function ring base must be a finite field
"""

def __init__(self, base_morphism, name='t'):
def __init__(self, base_morphism, name='τ'):
r"""
Initialize ``self``.

Expand All @@ -216,7 +217,7 @@ def __init__(self, base_morphism, name='t'):
- ``base_field`` -- the base field, which is a ring extension
over a base

- ``name`` -- (default: ``'t'``) the name of the Ore polynomial
- ``name`` -- (default: ``'τ'``) the name of the Ore polynomial
variable

TESTS::
Expand All @@ -227,7 +228,7 @@ def __init__(self, base_morphism, name='t'):
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: ore_polring.<t> = OrePolynomialRing(K, K.frobenius_endomorphism())
sage: ore_polring.<τ> = OrePolynomialRing(K, K.frobenius_endomorphism())
sage: C._ore_polring is ore_polring
True
sage: C._function_ring is A
Expand Down Expand Up @@ -438,7 +439,7 @@ def characteristic(self):

::

sage: psi = DrinfeldModule(A, [Frac(A).gen(), 1])
sage: psi = DrinfeldModule(A, [T, 1])
sage: C = psi.category()
sage: C.characteristic()
0
Expand Down Expand Up @@ -507,7 +508,7 @@ def object(self, gen):

sage: phi = C.object([p_root, 0, 1])
sage: phi
Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10
Drinfeld module defined by T |--> τ^2 + z^3 + 7*z^2 + 6*z + 10
sage: t = phi.ore_polring().gen()
sage: C.object(t^2 + z^3 + 7*z^2 + 6*z + 10) is phi
True
Expand All @@ -534,7 +535,7 @@ def ore_polring(self):
sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1])
sage: C = phi.category()
sage: C.ore_polring()
Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11
Ore Polynomial Ring in τ over Finite Field in z of size 11^4 twisted by z |--> z^11
"""
return self._ore_polring

Expand Down Expand Up @@ -639,7 +640,7 @@ def base(self):

The base can be infinite::

sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1])
sage: sigma = DrinfeldModule(A, [T, 1])
sage: sigma.base()
Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 over its base
"""
Expand All @@ -664,7 +665,7 @@ def base_morphism(self):

The base field can be infinite::

sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1])
sage: sigma = DrinfeldModule(A, [T, 1])
sage: sigma.base_morphism()
Coercion map:
From: Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2
Expand Down Expand Up @@ -711,8 +712,7 @@ def characteristic(self):
::

sage: B.<Y> = Fq[]
sage: L = Frac(B)
sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)])
sage: psi = DrinfeldModule(A, [B(1), 0, 0, 1])
sage: psi.characteristic()
Traceback (most recent call last):
...
Expand Down Expand Up @@ -770,7 +770,7 @@ def constant_coefficient(self):
sage: t = phi.ore_polring().gen()
sage: psi = C.object(phi.constant_coefficient() + t^3)
sage: psi
Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12
Drinfeld module defined by T |--> τ^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12

Reciprocally, it is impossible to create two Drinfeld modules in
this category if they do not share the same constant
Expand All @@ -796,7 +796,7 @@ def ore_polring(self):
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: S = phi.ore_polring()
sage: S
Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2)
Ore Polynomial Ring in τ over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2)

The Ore polynomial ring can also be retrieved from the category
of the Drinfeld module::
Expand Down Expand Up @@ -825,8 +825,8 @@ def ore_variable(self):
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])

sage: phi.ore_polring()
Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2)
Ore Polynomial Ring in τ over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2)
sage: phi.ore_variable()
t
τ
"""
return self.category().ore_polring().gen()
3 changes: 3 additions & 0 deletions src/sage/rings/function_field/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
from sage.misc.lazy_import import lazy_import

lazy_import("sage.rings.function_field.drinfeld_modules.drinfeld_module", "DrinfeldModule")
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")
7 changes: 4 additions & 3 deletions src/sage/rings/function_field/drinfeld_modules/action.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# sage.doctest: needs sage.rings.finite_rings
r"""
The module action induced by a Drinfeld module
Expand Down Expand Up @@ -60,7 +61,7 @@ class DrinfeldModuleAction(Action):
sage: action = phi.action()
sage: action
Action on Finite Field in z of size 11^2 over its base
induced by Drinfeld module defined by T |--> t^3 + z
induced by Drinfeld module defined by T |--> τ^3 + z

The action on elements is computed as follows::

Expand Down Expand Up @@ -154,7 +155,7 @@ def _latex_(self):
sage: phi = DrinfeldModule(A, [z, 0, 0, 1])
sage: action = phi.action()
sage: latex(action)
\text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\phi: T \mapsto t^{3} + z
\text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\phi: T \mapsto τ^{3} + z
"""
return f'\\text{{Action{{ }}on{{ }}}}' \
f'{latex(self._base)}\\text{{{{ }}' \
Expand All @@ -174,7 +175,7 @@ def _repr_(self):
sage: phi = DrinfeldModule(A, [z, 0, 0, 1])
sage: action = phi.action()
sage: action
Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z
Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> τ^3 + z
"""
return f'Action on {self._base} induced by ' \
f'{self._drinfeld_module}'
Expand Down
197 changes: 197 additions & 0 deletions src/sage/rings/function_field/drinfeld_modules/carlitz_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
r"""
Carlitz module

AUTHORS:

- Xavier Caruso (2025-07): initial version
"""

# *****************************************************************************
# Copyright (C) 2025 Xavier Caruso <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# http://www.gnu.org/licenses/
# *****************************************************************************

from sage.structure.parent import Parent
from sage.structure.element import Element
from sage.categories.finite_fields import FiniteFields

from sage.rings.infinity import Infinity

from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule


def CarlitzModule(A, base=None):
r"""
Return the Carlitz module over `A`.

INPUT:

- ``A`` -- a polynomial ring over a finite field

- ``base`` -- a field, an element in a field or a
string (default: the fraction field of ``A``)

EXAMPLES::

sage: Fq = GF(7)
sage: A.<T> = Fq[]
sage: CarlitzModule(A)
Drinfeld module defined by T |--> τ + T

We can specify a different base.
This is interesting for instance for having two different variable
names::

sage: R.<z> = Fq[]
sage: CarlitzModule(A, R)
Drinfeld module defined by T |--> τ + z

One can even use the following shortcut, which avoids the
construction of `R`::

sage: CarlitzModule(A, 'z')
Drinfeld module defined by T |--> τ + z

Using a similar syntax, we can construct the reduction of the
Carlitz module modulo primes::

sage: F.<a> = Fq.extension(z^2 + 1)
sage: CarlitzModule(A, F)
Drinfeld module defined by T |--> τ + a

It is also possible to pass in any element in the base field
(in this case, the result might not be strictly speaking the
Carlitz module, but it is always a Drinfeld module of rank 1)::

sage: CarlitzModule(A, z^2)
Drinfeld module defined by T |--> τ + z^2

TESTS::

sage: CarlitzModule(Fq)
Traceback (most recent call last):
...
TypeError: the function ring must be defined over a finite field

::

sage: S.<x,y> = QQ[]
sage: CarlitzModule(A, S)
Traceback (most recent call last):
...
ValueError: function ring base must coerce into base field
"""
if (not isinstance(A, PolynomialRing_generic)
or A.base_ring() not in FiniteFields()):
raise TypeError('the function ring must be defined over a finite field')
if base is None:
K = A.fraction_field()
z = K.gen()
elif isinstance(base, Parent):
if base.has_coerce_map_from(A):
z = base(A.gen())
else:
z = base.gen()
elif isinstance(base, Element):
z = base
elif isinstance(base, str):
K = A.base_ring()[base]
z = K.gen()
else:
raise ValueError("cannot construct a Carlitz module from the given data")
return DrinfeldModule(A, [z, 1])


def carlitz_exponential(A, prec=+Infinity, name='z'):
r"""
Return the Carlitz exponential attached the ring `A`.

INPUT:

- ``prec`` -- an integer or ``Infinity`` (default: ``Infinity``);
the precision at which the series is returned; if ``Infinity``,
a lazy power series in returned, else, a classical power series
is returned.

- ``name`` -- string (default: ``'z'``); the name of the
generator of the lazy power series ring

EXAMPLES::

sage: A.<T> = GF(2)[]

When ``prec`` is ``Infinity`` (which is the default),
the exponential is returned as a lazy power series, meaning
that any of its coefficients can be computed on demands::

sage: exp = carlitz_exponential(A)
sage: exp
z + ((1/(T^2+T))*z^2) + ((1/(T^8+T^6+T^5+T^3))*z^4) + O(z^8)
sage: exp[2^4]
1/(T^64 + T^56 + T^52 + ... + T^27 + T^23 + T^15)
sage: exp[2^5]
1/(T^160 + T^144 + T^136 + ... + T^55 + T^47 + T^31)

On the contrary, when ``prec`` is a finite number, all the
required coefficients are computed at once::

sage: carlitz_exponential(A, prec=10)
z + (1/(T^2 + T))*z^2 + (1/(T^8 + T^6 + T^5 + T^3))*z^4 + (1/(T^24 + T^20 + T^18 + T^17 + T^14 + T^13 + T^11 + T^7))*z^8 + O(z^10)

We check that the Carlitz exponential is the compositional inverse
of the Carlitz logarithm::

sage: log = carlitz_logarithm(A)
sage: exp(log)
z + O(z^8)
sage: log(exp)
z + O(z^8)
"""
C = CarlitzModule(A)
return C.exponential(prec, name)


def carlitz_logarithm(A, prec=+Infinity, name='z'):
r"""
Return the Carlitz exponential attached the ring `A`.

INPUT:

- ``prec`` -- an integer or ``Infinity`` (default: ``Infinity``);
the precision at which the series is returned; if ``Infinity``,
a lazy power series in returned, else, a classical power series
is returned.

- ``name`` -- string (default: ``'z'``); the name of the
generator of the lazy power series ring

EXAMPLES::

sage: A.<T> = GF(2)[]

When ``prec`` is ``Infinity`` (which is the default),
the exponential is returned as a lazy power series, meaning
that any of its coefficients can be computed on demands::

sage: log = carlitz_logarithm(A)
sage: log
z + ((1/(T^2+T))*z^2) + ((1/(T^6+T^5+T^3+T^2))*z^4) + O(z^8)
sage: log[2^4]
1/(T^30 + T^29 + T^27 + ... + T^7 + T^5 + T^4)
sage: log[2^5]
1/(T^62 + T^61 + T^59 + ... + T^8 + T^6 + T^5)

On the contrary, when ``prec`` is a finite number, all the
required coefficients are computed at once::

sage: carlitz_logarithm(A, prec=10)
z + (1/(T^2 + T))*z^2 + (1/(T^6 + T^5 + T^3 + T^2))*z^4 + (1/(T^14 + T^13 + T^11 + T^10 + T^7 + T^6 + T^4 + T^3))*z^8 + O(z^10)
"""
C = CarlitzModule(A)
return C.logarithm(prec, name)
Loading
Loading