Skip to content

Commit 10edc54

Browse files
author
Release Manager
committed
gh-39215: Class polynomial for Drinfeld modules This PR implements an algorithm for computing the class polynomial (that is, the Fitting ideal of the class module) of a Drinfeld modules over $K = \text{Frac}(A)$. We recall that the class module of a Drinfeld module $E : A \to A\\{\tau\\}$ is defined as the quotient $$H(E) := \frac{E(K_\infty)}{E(A) + \exp_E(K_\infty)}$$ where: - for an $A$-algebra $R$, the notation $E(R)$ refers to $R$ equipped with the structure of $A$-module coming from $E$ - $K_\infty$ is the completion of $\text{Frac}(A)$ at infinity - $\exp_E : K_\infty \to K_\infty$ is the exponential associated to $E$. The implementation provided in this PR is based on the following remark: for $s$ large enough, $H(E)$ is also the quotient of $E(K_\infty/A)$ by the $A$-submodule generated by $T^{-n}$ with $n \geq s$. This is due to the fact that, when $n$ is large, $T^{-n}$ is in the domain of convergence of the logarithm of $E$. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. URL: #39215 Reported by: Xavier Caruso Reviewer(s): Antoine Leudière, Xavier Caruso
2 parents 2e0d6d2 + 67496ed commit 10edc54

File tree

4 files changed

+283
-4
lines changed

4 files changed

+283
-4
lines changed

Diff for: src/doc/en/reference/references/index.rst

+3
Original file line numberDiff line numberDiff line change
@@ -6497,6 +6497,9 @@ REFERENCES:
64976497

64986498
**T**
64996499

6500+
.. [Tae2012] Lenny Taelman, *Special L-values of Drinfeld modules*,
6501+
Ann. Math. 175 (1), 2012, 369–391
6502+
65006503
.. [Tak1999] Kisao Takeuchi, Totally real algebraic number fields of
65016504
degree 9 with small discriminant, Saitama Math. J. 17
65026505
(1999), 63--85 (2000).

Diff for: src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py

+230-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
r"""
33
Drinfeld modules over rings of characteristic zero
44
5-
This module provides the class
6-
:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_charzero`,
7-
which inherits
5+
This module provides the classes
6+
:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_charzero` and
7+
:class:`sage.rings.function_fields.drinfeld_module.charzero_drinfeld_module.DrinfeldModule_rational`,
8+
which both inherit
89
:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`.
910
1011
AUTHORS:
1112
1213
- David Ayotte (2023-09)
14+
- Xavier Caruso (2024-12) - computation of class polynomials
1315
"""
1416

1517
# *****************************************************************************
@@ -27,6 +29,9 @@
2729
from sage.rings.integer_ring import ZZ
2830
from sage.rings.infinity import Infinity
2931

32+
from sage.matrix.constructor import matrix
33+
from sage.modules.free_module_element import vector
34+
3035
from sage.misc.cachefunc import cached_method
3136
from sage.misc.lazy_import import lazy_import
3237

@@ -443,3 +448,225 @@ def goss_polynomial(self, n, var='X'):
443448
X = poly_ring.gen()
444449
q = self._Fq.cardinality()
445450
return self._compute_goss_polynomial(n, q, poly_ring, X)
451+
452+
453+
class DrinfeldModule_rational(DrinfeldModule_charzero):
454+
"""
455+
A class for Drinfeld modules defined over the fraction
456+
field of the underlying function field.
457+
458+
TESTS::
459+
460+
sage: q = 9
461+
sage: Fq = GF(q)
462+
sage: A = Fq['T']
463+
sage: K.<T> = Frac(A)
464+
sage: C = DrinfeldModule(A, [T, 1]); C
465+
Drinfeld module defined by T |--> t + T
466+
sage: type(C)
467+
<class 'sage.rings.function_field.drinfeld_modules.charzero_drinfeld_module.DrinfeldModule_rational_with_category'>
468+
"""
469+
def coefficient_in_function_ring(self, n):
470+
r"""
471+
Return the `n`-th coefficient of this Drinfeld module as
472+
an element of the underlying function ring.
473+
474+
INPUT:
475+
476+
- ``n`` -- an integer
477+
478+
EXAMPLES::
479+
480+
sage: q = 5
481+
sage: Fq = GF(q)
482+
sage: A = Fq['T']
483+
sage: R = Fq['U']
484+
sage: K.<U> = Frac(R)
485+
sage: phi = DrinfeldModule(A, [U, 0, U^2, U^3])
486+
sage: phi.coefficient_in_function_ring(2)
487+
T^2
488+
489+
Compare with the method meth:`coefficient`::
490+
491+
sage: phi.coefficient(2)
492+
U^2
493+
494+
If the required coefficient is not a polynomials,
495+
an error is raised::
496+
497+
sage: psi = DrinfeldModule(A, [U, 1/U])
498+
sage: psi.coefficient_in_function_ring(0)
499+
T
500+
sage: psi.coefficient_in_function_ring(1)
501+
Traceback (most recent call last):
502+
...
503+
ValueError: coefficient is not polynomial
504+
"""
505+
A = self.function_ring()
506+
g = self.coefficient(n)
507+
g = g.backend(force=True)
508+
if g.denominator().is_one():
509+
return A(g.numerator().list())
510+
else:
511+
raise ValueError("coefficient is not polynomial")
512+
513+
def coefficients_in_function_ring(self, sparse=True):
514+
r"""
515+
Return the coefficients of this Drinfeld module as elements
516+
of the underlying function ring.
517+
518+
INPUT:
519+
520+
- ``sparse`` -- a boolean (default: ``True``); if ``True``,
521+
only return the nonzero coefficients; otherwise, return
522+
all of them.
523+
524+
EXAMPLES::
525+
526+
sage: q = 5
527+
sage: Fq = GF(q)
528+
sage: A = Fq['T']
529+
sage: R = Fq['U']
530+
sage: K.<U> = Frac(R)
531+
sage: phi = DrinfeldModule(A, [U, 0, U^2, U^3])
532+
sage: phi.coefficients_in_function_ring()
533+
[T, T^2, T^3]
534+
sage: phi.coefficients_in_function_ring(sparse=False)
535+
[T, 0, T^2, T^3]
536+
537+
Compare with the method meth:`coefficients`::
538+
539+
sage: phi.coefficients()
540+
[U, U^2, U^3]
541+
542+
If the coefficients are not polynomials, an error is raised::
543+
544+
sage: psi = DrinfeldModule(A, [U, 1/U])
545+
sage: psi.coefficients_in_function_ring()
546+
Traceback (most recent call last):
547+
...
548+
ValueError: coefficients are not polynomials
549+
"""
550+
A = self.function_ring()
551+
gs = []
552+
for g in self.coefficients(sparse):
553+
g = g.backend(force=True)
554+
if g.denominator().is_one():
555+
gs.append(A(g.numerator().list()))
556+
else:
557+
raise ValueError("coefficients are not polynomials")
558+
return gs
559+
560+
def class_polynomial(self):
561+
r"""
562+
Return the class polynomial, that is the Fitting ideal
563+
of the class module, of this Drinfeld module.
564+
565+
We refer to [Tae2012]_ for the definition and basic
566+
properties of the class module.
567+
568+
EXAMPLES:
569+
570+
We check that the class module of the Carlitz module
571+
is trivial::
572+
573+
sage: q = 5
574+
sage: Fq = GF(q)
575+
sage: A = Fq['T']
576+
sage: K.<T> = Frac(A)
577+
sage: C = DrinfeldModule(A, [T, 1]); C
578+
Drinfeld module defined by T |--> t + T
579+
sage: C.class_polynomial()
580+
1
581+
582+
When the coefficients of the Drinfeld module have small
583+
enough degrees, the class module is always trivial::
584+
585+
sage: gs = [T] + [A.random_element(degree = q^i)
586+
....: for i in range(1, 5)]
587+
sage: phi = DrinfeldModule(A, gs)
588+
sage: phi.class_polynomial()
589+
1
590+
591+
Here is an example with a nontrivial class module::
592+
593+
sage: phi = DrinfeldModule(A, [T, 2*T^14 + 2*T^4])
594+
sage: phi.class_polynomial()
595+
T + 3
596+
597+
TESTS:
598+
599+
The Drinfeld module must have polynomial coefficients::
600+
601+
sage: phi = DrinfeldModule(A, [T, 1/T])
602+
sage: phi.class_polynomial()
603+
Traceback (most recent call last):
604+
...
605+
ValueError: coefficients are not polynomials
606+
"""
607+
# The algorithm is based on the following remark:
608+
# writing phi_T = g_0 + g_1*tau + ... + g_r*tau^r,
609+
# if s > deg(g_i/(q^i - 1)) - 1 for all i, then the
610+
# class module is equal to
611+
# H := E(Kinfty/A) / < T^(-s), T^(-s-1), ... >
612+
# where E(Kinfty/A) is Kinfty/A equipped with the
613+
# A-module structure coming from phi.
614+
615+
A = self.function_ring()
616+
Fq = A.base_ring()
617+
q = Fq.cardinality()
618+
r = self.rank()
619+
620+
# We compute the bound s
621+
gs = self.coefficients_in_function_ring(sparse=False)
622+
s = max(gs[i].degree() // (q**i - 1) for i in range(1, r+1))
623+
if s == 0:
624+
return A.one()
625+
626+
# We compute the matrix of phi_T acting on the quotient
627+
# M := (Kinfty/A) / < T^(-s), T^(-s-1), ... >
628+
# (for the standard structure of A-module!)
629+
M = matrix(Fq, s)
630+
qk = 1
631+
for k in range(r+1):
632+
for i in range(s):
633+
e = (i+1)*qk
634+
for j in range(s):
635+
e -= 1
636+
if e < 0:
637+
break
638+
M[i, j] += gs[k][e]
639+
qk *= q
640+
641+
# We compute the subspace of E(Kinfty/A) (for the twisted
642+
# structure of A-module!)
643+
# V = < T^(-s), T^(-s+1), ... >
644+
# It is also the phi_T-saturation of T^(-s+1) in M, i.e.
645+
# the Fq-vector space generated by the phi_T^i(T^(-s+1))
646+
# for i varying in NN.
647+
v = vector(Fq, s)
648+
v[s-1] = 1
649+
vs = [v]
650+
for i in range(s-1):
651+
v = v*M
652+
vs.append(v)
653+
V = matrix(vs)
654+
V.echelonize()
655+
656+
# We compute the action of phi_T on H = M/V
657+
# as an Fq-linear map (encoded in the matrix N)
658+
dim = V.rank()
659+
pivots = V.pivots()
660+
j = ip = 0
661+
for i in range(dim, s):
662+
while ip < dim and j == pivots[ip]:
663+
j += 1
664+
ip += 1
665+
V[i,j] = 1
666+
N = (V * M * ~V).submatrix(dim, dim)
667+
668+
# The class module is now H where the action of T
669+
# is given by the matrix N
670+
# The class polynomial is then the characteristic
671+
# polynomial of N
672+
return A(N.charpoly())

Diff for: src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from sage.misc.misc_c import prod
3838
from sage.rings.integer import Integer
3939
from sage.rings.integer_ring import ZZ
40+
from sage.rings.fraction_field import FractionField_generic
4041
from sage.rings.polynomial.ore_polynomial_element import OrePolynomial
4142
from sage.rings.polynomial.polynomial_ring import PolynomialRing_generic
4243
from sage.structure.parent import Parent
@@ -621,9 +622,17 @@ def __classcall_private__(cls, function_ring, gen, name='t'):
621622
raise ValueError('generator must have positive degree')
622623

623624
# Instantiate the appropriate class:
624-
if base_field.is_finite():
625+
backend = base_field.backend(force=True)
626+
if backend.is_finite():
625627
from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import DrinfeldModule_finite
626628
return DrinfeldModule_finite(gen, category)
629+
if isinstance(backend, FractionField_generic):
630+
ring = backend.ring()
631+
if (isinstance(ring, PolynomialRing_generic)
632+
and ring.base_ring() is function_ring_base
633+
and base_morphism(T) == ring.gen()):
634+
from .charzero_drinfeld_module import DrinfeldModule_rational
635+
return DrinfeldModule_rational(gen, category)
627636
if not category._characteristic:
628637
from .charzero_drinfeld_module import DrinfeldModule_charzero
629638
return DrinfeldModule_charzero(gen, category)

Diff for: src/sage/rings/ring_extension_element.pyx

+40
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,46 @@ cdef class RingExtensionElement(CommutativeAlgebraElement):
129129
wrapper.__doc__ = method.__doc__
130130
return wrapper
131131

132+
def __getitem__(self, i):
133+
r"""
134+
Return the `i`-th item of this element.
135+
136+
This methods calls the appropriate method of the backend if
137+
``import_methods`` is set to ``True``
138+
139+
EXAMPLES::
140+
141+
sage: R.<x> = QQ[]
142+
sage: E = R.over()
143+
sage: P = E(x^2 + 2*x + 3)
144+
sage: P[0]
145+
3
146+
"""
147+
if (<RingExtension_generic>self._parent)._import_methods:
148+
output = self._backend[to_backend(i)]
149+
return from_backend(output, self._parent)
150+
return TypeError("this element is not subscriptable")
151+
152+
def __call__(self, *args, **kwargs):
153+
r"""
154+
Call this element.
155+
156+
This methods calls the appropriate method of the backend if
157+
``import_methods`` is set to ``True``
158+
159+
EXAMPLES::
160+
161+
sage: R.<x> = QQ[]
162+
sage: E = R.over()
163+
sage: P = E(x^2 + 2*x + 3)
164+
sage: P(1)
165+
6
166+
"""
167+
if (<RingExtension_generic>self._parent)._import_methods:
168+
output = self._backend(*to_backend(args), **to_backend(kwargs))
169+
return from_backend(output, self._parent)
170+
return TypeError("this element is not callable")
171+
132172
def __dir__(self):
133173
"""
134174
Return the list of all the attributes of this element;

0 commit comments

Comments
 (0)