Skip to content

Commit fdf9668

Browse files
author
Release Manager
committed
gh-35260: Implement the logarithm and the exponential of a Drinfeld module <!-- ^^^^^ Please provide a concise, informative and self-explanatory title. Don't put issue numbers in there, do this in the PR body below. For example, instead of "Fixes #1234" use "Introduce new method to calculate 1+1" --> ### 📚 Description The goal of this PR is to implement the logarithm and the exponential of a Drinfeld module. ### Background material Let $\phi:T \mapsto \gamma(T) + g_1 \tau + \cdots + g_r \tau^r$ with $g_r\neq 0$ be a rank $r$ Drinfeld module and let $\Lambda\subset \mathbb{C}\_{\infty}$ be the associated rank $r$ lattice. Then recall that the exponential $e\_{\phi}:\mathbb{C}\_{\infty} \rightarrow \mathbb{C}\_{\infty}$ of the Drinfeld module $\phi$ is defined by: $$ e_{\phi}(z) := z\prod_{\lambda\in \Lambda\setminus \{0\}} \left( 1 - z/\lambda \right). $$ It can be viewed as a power series in $z$: $$ e_{\phi}(z) = z + \alpha_1 z^{q} + \alpha_2 z^{q^2} + \alpha_3 z^{q^3} + \cdots $$ Moreover, it satisfies the following functional equation: $$ e_{\phi}(az) = \phi_a(e_{\phi}(z)). $$ The logarithm of $\phi$ is defined to be the compositional inverse of $e_{\phi}$, denoted $\mathrm{log}\_{\phi}$. By applying $\mathrm{log}\_{\phi}$ on both side of the functional equation, we obtain the following recurrence relation: $$ \beta_k = \frac{1}{\gamma(T) - \gamma(T)^{q^i - 1}} \sum_{i = 0}^{k-1} \beta_i g_{k-i}^{q^i} $$ where $\beta_k$ is the $q^k$-th coefficient of $\mathrm{log}_{\phi}$ and $\beta_0 = 1$. ### Examples ``` sage: A = GF(2)['T'] sage: K.<T> = Frac(A) sage: phi = DrinfeldModule(A, [T, 1]) sage: q = A.base_ring().cardinality() sage: log = phi.logarithm(); log z + ((1/(T^2+T))*z^2) + ((1/(T^6+T^5+T^3+T^2))*z^4) + O(z^7) sage: log[q] == -1/((T**q - T)) True sage: log[q**2] == 1/((T**q - T)*(T**(q**2) - T)) True sage: log[q**3] == -1/((T**q - T)*(T**(q**2) - T)*(T**(q**3) - T)) True ``` ### Ideas The idea here is to first compute the logarithm of a Drinfeld module using lazy power series and then compute its compositional inverse to find its exponential. <!-- Describe your changes here in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If it resolves an open issue, please link to the issue here. For example "Closes #1337" --> ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [x] I have made sure that the title is self-explanatory and the description concisely explains the PR. - [ ] I have linked an issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open pull requests that this PR logically depends on --> <!-- - #xyz: short description why this is a dependency - #abc: ... --> Depends on #35026 because this PR implements Drinfeld modules. CC: @xcaruso @ymusleh @kryzar URL: #35260 Reported by: David Ayotte Reviewer(s): Antoine Leudière, David Ayotte, Xavier Caruso
2 parents 9de8e26 + 2b242fb commit fdf9668

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed

src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@
2626

2727
from sage.categories.drinfeld_modules import DrinfeldModules
2828
from sage.categories.homset import Hom
29+
from sage.misc.cachefunc import cached_method
2930
from sage.misc.latex import latex
3031
from sage.misc.latex import latex_variable_name
32+
from sage.misc.lazy_import import lazy_import
3133
from sage.misc.lazy_string import _LazyString
3234
from sage.rings.integer import Integer
35+
from sage.rings.integer_ring import ZZ
3336
from sage.rings.polynomial.ore_polynomial_element import OrePolynomial
3437
from sage.rings.polynomial.polynomial_ring import PolynomialRing_general
3538
from sage.rings.ring_extension import RingExtension_generic
@@ -38,6 +41,8 @@
3841
from sage.structure.sequence import Sequence
3942
from sage.structure.unique_representation import UniqueRepresentation
4043

44+
lazy_import('sage.rings.lazy_series_ring', 'LazyPowerSeriesRing')
45+
4146

4247
class DrinfeldModule(Parent, UniqueRepresentation):
4348
r"""
@@ -958,6 +963,134 @@ def coefficients(self, sparse=True):
958963
"""
959964
return self._gen.coefficients(sparse=sparse)
960965

966+
@cached_method
967+
def _compute_coefficient_exp(self, k):
968+
r"""
969+
Return the `q^k`-th coefficient of the exponential of this Drinfeld module.
970+
971+
INPUT:
972+
973+
- ``k`` (integer) -- the index of the coefficient
974+
975+
TESTS::
976+
977+
sage: A = GF(2)['T']
978+
sage: K.<T> = Frac(A)
979+
sage: phi = DrinfeldModule(A, [T, 1])
980+
sage: q = A.base_ring().cardinality()
981+
sage: phi._compute_coefficient_exp(0)
982+
1
983+
sage: phi._compute_coefficient_exp(1)
984+
1/(T^2 + T)
985+
sage: phi._compute_coefficient_exp(2)
986+
1/(T^8 + T^6 + T^5 + T^3)
987+
sage: phi._compute_coefficient_exp(3)
988+
1/(T^24 + T^20 + T^18 + T^17 + T^14 + T^13 + T^11 + T^7)
989+
"""
990+
k = ZZ(k)
991+
if k.is_zero():
992+
return self._base.one()
993+
q = self._Fq.cardinality()
994+
c = self._base.zero()
995+
for i in range(k):
996+
j = k - i
997+
c += self._compute_coefficient_exp(i)*self._compute_coefficient_log(j)**(q**i)
998+
return -c
999+
1000+
def exponential(self, name='z'):
1001+
r"""
1002+
Return the exponential of this Drinfeld module.
1003+
1004+
Note that the exponential is only defined when the
1005+
`\mathbb{F}_q[T]`-characteristic is zero.
1006+
1007+
INPUT:
1008+
1009+
- ``name`` (string, default: ``'z'``) -- the name of the
1010+
generator of the lazy power series ring.
1011+
1012+
OUTPUT:
1013+
1014+
A lazy power series over the base field.
1015+
1016+
EXAMPLES::
1017+
1018+
sage: A = GF(2)['T']
1019+
sage: K.<T> = Frac(A)
1020+
sage: phi = DrinfeldModule(A, [T, 1])
1021+
sage: q = A.base_ring().cardinality()
1022+
sage: exp = phi.exponential(); exp
1023+
z + ((1/(T^2+T))*z^2) + ((1/(T^8+T^6+T^5+T^3))*z^4) + O(z^8)
1024+
1025+
The exponential is returned as a lazy power series, meaning that
1026+
any of its coefficients can be computed on demands::
1027+
1028+
sage: exp[2^4]
1029+
1/(T^64 + T^56 + T^52 + ... + T^27 + T^23 + T^15)
1030+
sage: exp[2^5]
1031+
1/(T^160 + T^144 + T^136 + ... + T^55 + T^47 + T^31)
1032+
1033+
Example in higher rank::
1034+
1035+
sage: A = GF(5)['T']
1036+
sage: K.<T> = Frac(A)
1037+
sage: phi = DrinfeldModule(A, [T, T^2, T + T^2 + T^4, 1])
1038+
sage: exp = phi.exponential(); exp
1039+
z + ((T/(T^4+4))*z^5) + O(z^8)
1040+
1041+
The exponential is the compositional inverse of the logarithm
1042+
(see :meth:`logarithm`)::
1043+
1044+
sage: log = phi.logarithm(); log
1045+
z + ((4*T/(T^4+4))*z^5) + O(z^8)
1046+
sage: exp.compose(log)
1047+
z + O(z^8)
1048+
sage: log.compose(exp)
1049+
z + O(z^8)
1050+
1051+
::
1052+
1053+
sage: Fq.<w> = GF(3)
1054+
sage: A = Fq['T']
1055+
sage: phi = DrinfeldModule(A, [w, 1])
1056+
sage: phi.exponential()
1057+
Traceback (most recent call last):
1058+
...
1059+
ValueError: characteristic must be zero (=T + 2)
1060+
1061+
TESTS::
1062+
1063+
sage: A = GF(2)['T']
1064+
sage: K.<T> = Frac(A)
1065+
sage: phi = DrinfeldModule(A, [T, 1])
1066+
sage: exp = phi.exponential()
1067+
sage: exp[2] == 1/(T**q - T) # expected value
1068+
True
1069+
sage: exp[2^2] == 1/((T**(q**2) - T)*(T**q - T)**q) # expected value
1070+
True
1071+
sage: exp[2^3] == 1/((T**(q**3) - T)*(T**(q**2) - T)**q*(T**q - T)**(q**2)) # expected value
1072+
True
1073+
1074+
REFERENCE:
1075+
1076+
See section 4.6 of [Gos1998]_ for the definition of the
1077+
exponential.
1078+
"""
1079+
if self.category()._characteristic:
1080+
raise ValueError(f"characteristic must be zero (={self.characteristic()})")
1081+
L = LazyPowerSeriesRing(self._base, name)
1082+
zero = self._base.zero()
1083+
q = self._Fq.cardinality()
1084+
1085+
def coeff_exp(k):
1086+
# Return the k-th coefficient of the exponential.
1087+
k = ZZ(k)
1088+
if k.is_power_of(q):
1089+
return self._compute_coefficient_exp(k.log(q))
1090+
else:
1091+
return zero
1092+
return L(coeff_exp, valuation=1)
1093+
9611094
def gen(self):
9621095
r"""
9631096
Return the generator of the Drinfeld module.
@@ -1103,6 +1236,120 @@ def j_invariant(self):
11031236
q = self._Fq.order()
11041237
return (g**(q+1)) / delta
11051238

1239+
@cached_method
1240+
def _compute_coefficient_log(self, k):
1241+
r"""
1242+
Return the `q^k`-th coefficient of the logarithm of this Drinfeld module.
1243+
1244+
TESTS::
1245+
1246+
sage: A = GF(2)['T']
1247+
sage: K.<T> = Frac(A)
1248+
sage: phi = DrinfeldModule(A, [T, 1])
1249+
sage: q = A.base_ring().cardinality()
1250+
sage: phi._compute_coefficient_log(0)
1251+
1
1252+
sage: phi._compute_coefficient_log(1)
1253+
1/(T^2 + T)
1254+
sage: phi._compute_coefficient_log(2)
1255+
1/(T^6 + T^5 + T^3 + T^2)
1256+
sage: phi._compute_coefficient_log(3)
1257+
1/(T^14 + T^13 + T^11 + T^10 + T^7 + T^6 + T^4 + T^3)
1258+
"""
1259+
k = ZZ(k)
1260+
if k.is_zero():
1261+
return self._base.one()
1262+
r = self._gen.degree()
1263+
T = self._gen[0]
1264+
q = self._Fq.cardinality()
1265+
c = self._base.zero()
1266+
for i in range(k):
1267+
j = k - i
1268+
if j < r + 1:
1269+
c += self._compute_coefficient_log(i)*self._gen[j]**(q**i)
1270+
return c/(T - T**(q**k))
1271+
1272+
def logarithm(self, name='z'):
1273+
r"""
1274+
Return the logarithm of the given Drinfeld module.
1275+
1276+
By definition, the logarithm is the compositional inverse of the
1277+
exponential (see :meth:`exponential`). Note that the logarithm
1278+
is only defined when the `\mathbb{F}_q[T]`-characteristic is
1279+
zero.
1280+
1281+
INPUT:
1282+
1283+
- ``name`` (string, default: ``'z'``) -- the name of the
1284+
generator of the lazy power series ring.
1285+
1286+
OUTPUT:
1287+
1288+
A lazy power series over the base field.
1289+
1290+
EXAMPLES::
1291+
1292+
sage: A = GF(2)['T']
1293+
sage: K.<T> = Frac(A)
1294+
sage: phi = DrinfeldModule(A, [T, 1])
1295+
sage: log = phi.logarithm(); log
1296+
z + ((1/(T^2+T))*z^2) + ((1/(T^6+T^5+T^3+T^2))*z^4) + O(z^8)
1297+
1298+
The logarithm is returned as a lazy power series, meaning that
1299+
any of its coefficients can be computed on demands::
1300+
1301+
sage: log[2^4]
1302+
1/(T^30 + T^29 + T^27 + ... + T^7 + T^5 + T^4)
1303+
sage: log[2^5]
1304+
1/(T^62 + T^61 + T^59 + ... + T^8 + T^6 + T^5)
1305+
1306+
Example in higher rank::
1307+
1308+
sage: A = GF(5)['T']
1309+
sage: K.<T> = Frac(A)
1310+
sage: phi = DrinfeldModule(A, [T, T^2, T + T^2 + T^4, 1])
1311+
sage: phi.logarithm()
1312+
z + ((4*T/(T^4+4))*z^5) + O(z^8)
1313+
1314+
TESTS::
1315+
1316+
sage: A = GF(2)['T']
1317+
sage: K.<T> = Frac(A)
1318+
sage: phi = DrinfeldModule(A, [T, 1])
1319+
sage: q = 2
1320+
sage: log[2] == -1/((T**q - T)) # expected value
1321+
True
1322+
sage: log[2**2] == 1/((T**q - T)*(T**(q**2) - T)) # expected value
1323+
True
1324+
sage: log[2**3] == -1/((T**q - T)*(T**(q**2) - T)*(T**(q**3) - T)) # expected value
1325+
True
1326+
1327+
::
1328+
1329+
sage: Fq.<w> = GF(3)
1330+
sage: A = Fq['T']
1331+
sage: phi = DrinfeldModule(A, [w, 1])
1332+
sage: phi.logarithm()
1333+
Traceback (most recent call last):
1334+
...
1335+
ValueError: characteristic must be zero (=T + 2)
1336+
"""
1337+
if self.category()._characteristic:
1338+
raise ValueError(f"characteristic must be zero (={self.characteristic()})")
1339+
L = LazyPowerSeriesRing(self._base, name)
1340+
zero = self._base.zero()
1341+
q = self._Fq.cardinality()
1342+
1343+
def coeff_log(k):
1344+
# Return the k-th coefficient of the logarithm
1345+
k = ZZ(k)
1346+
if k.is_power_of(q):
1347+
return self._compute_coefficient_log(k.log(q))
1348+
else:
1349+
return self._base.zero()
1350+
return L(coeff_log, valuation=1)
1351+
1352+
11061353
def morphism(self):
11071354
r"""
11081355
Return the morphism object that defines the Drinfeld module.

0 commit comments

Comments
 (0)