Skip to content
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

Implement the logarithm and the exponential of a Drinfeld module #35260

Merged
merged 31 commits into from
May 22, 2023
Merged
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c23ec60
inital commit
DavidAyotte Mar 8, 2023
1f608cf
enhance_compute_coeffcient_log
DavidAyotte Mar 10, 2023
3daba4a
add documentation
DavidAyotte Mar 10, 2023
451cf1f
implement exponential
DavidAyotte Mar 11, 2023
bfe9a0c
document exponential method
DavidAyotte Mar 11, 2023
01d620d
enhance documentation of logarithm
DavidAyotte Mar 11, 2023
9b83a03
Merge branch 'develop' into logarithm_exponential
DavidAyotte Mar 12, 2023
643cfde
fix some line length and add some details to the doc
DavidAyotte Mar 16, 2023
c7f9ffd
Merge branch 'develop' into logarithm_exponential
DavidAyotte Mar 19, 2023
5356e39
fix mistake
DavidAyotte Mar 24, 2023
237815f
Merge branch 'develop' into logarithm_exponential
DavidAyotte Apr 7, 2023
9c8d4a7
small updates
DavidAyotte Apr 12, 2023
644cafc
(re)add the valuation=1 flag
DavidAyotte Apr 13, 2023
8323f6f
fix failing doctests
DavidAyotte Apr 15, 2023
fad3fd3
Merge branch 'develop' into logarithm_exponential
DavidAyotte Apr 20, 2023
b73d1ae
remove unnecessary integer type conversion
DavidAyotte Apr 26, 2023
ba57245
add cached_method decorator
DavidAyotte Apr 27, 2023
364fd55
use ellipsis
DavidAyotte Apr 27, 2023
8f292e9
fix failing doctets
DavidAyotte Apr 27, 2023
eb78111
Update src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py
DavidAyotte Apr 27, 2023
68477a8
Merge remote-tracking branch 'origin/logarithm_exponential' into loga…
DavidAyotte Apr 26, 2023
1504a19
add checks for finite Drinfeld modules
DavidAyotte Apr 27, 2023
706ded5
update error message
DavidAyotte Apr 27, 2023
26498b8
refactor _compute_coefficient methods
DavidAyotte May 4, 2023
005acd5
Update src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py
DavidAyotte May 4, 2023
7f72a21
Update src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py
DavidAyotte May 4, 2023
962e70d
Update src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py
DavidAyotte May 5, 2023
3e873b9
Update src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py
DavidAyotte May 5, 2023
f8c867d
fix failing doctest
DavidAyotte May 5, 2023
e85227e
minor update to documentation of exponential and logarithm
DavidAyotte May 5, 2023
2b242fb
refactor exponential and logarithm
DavidAyotte May 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@

from sage.categories.drinfeld_modules import DrinfeldModules
from sage.categories.homset import Hom
from sage.misc.cachefunc import cached_method
from sage.misc.latex import latex
from sage.misc.latex import latex_variable_name
from sage.misc.lazy_import import lazy_import
from sage.misc.lazy_string import _LazyString
from sage.rings.integer import Integer
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.ore_polynomial_element import OrePolynomial
from sage.rings.polynomial.polynomial_ring import PolynomialRing_general
from sage.rings.ring_extension import RingExtension_generic
Expand All @@ -38,6 +41,8 @@
from sage.structure.sequence import Sequence
from sage.structure.unique_representation import UniqueRepresentation

lazy_import('sage.rings.lazy_series_ring', 'LazyPowerSeriesRing')


class DrinfeldModule(Parent, UniqueRepresentation):
r"""
Expand Down Expand Up @@ -958,6 +963,127 @@ def coefficients(self, sparse=True):
"""
return self._gen.coefficients(sparse=sparse)

@cached_method
def _compute_coefficient_exp(self, k):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are (nonterminal) recursive calls in this function.
Probably, we should consider adding the decorator @cached_method for performances.

r"""
Return the `q^k`-th coefficient of the exponential of this Drinfeld module.

INPUT:

- ``k`` (integer) -- the index of the coefficient

TESTS::

sage: A = GF(2)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1])
sage: q = A.base_ring().cardinality()
sage: phi._compute_coefficient_exp(0)
1
sage: phi._compute_coefficient_exp(1)
1/(T^2 + T)
sage: phi._compute_coefficient_exp(2)
1/(T^8 + T^6 + T^5 + T^3)
sage: phi._compute_coefficient_exp(3)
1/(T^24 + T^20 + T^18 + T^17 + T^14 + T^13 + T^11 + T^7)
"""
k = ZZ(k)
if k.is_zero():
return self._base.one()
q = self._Fq.cardinality()
c = self._base.zero()
for i in range(k):
j = k - i
c += self._compute_coefficient_exp(i)*self._compute_coefficient_log(j)**(q**i)
return -c

def exponential(self, name='z'):
r"""
Return the exponential of this Drinfeld module.

Note that the exponential is only defined when the
`\mathbb{F}_q[T]`-characteristic is zero.

INPUT:

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

OUTPUT:

A lazy power series over the base field.

EXAMPLES::

sage: A = GF(2)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1])
sage: q = A.base_ring().cardinality()
sage: exp = phi.exponential(); exp
z + ((1/(T^2+T))*z^2) + ((1/(T^8+T^6+T^5+T^3))*z^4) + O(z^8)

The exponential is returned as a lazy power series, meaning that
any of its coefficients can be computed on demands::

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)

Example in higher rank::

sage: A = GF(5)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, T^2, T + T^2 + T^4, 1])
sage: exp = phi.exponential(); exp
z + ((T/(T^4+4))*z^5) + O(z^8)

The exponential is the compositional inverse of the logarithm
(see :meth:`logarithm`)::

sage: log = phi.logarithm(); log
z + ((4*T/(T^4+4))*z^5) + O(z^8)
sage: exp.compose(log)
z + O(z^8)
sage: log.compose(exp)
z + O(z^8)

::

sage: Fq.<w> = GF(3)
sage: A = Fq['T']
sage: phi = DrinfeldModule(A, [w, 1])
sage: phi.exponential()
Traceback (most recent call last):
...
ValueError: characteristic must be zero (=T + 2)

TESTS::

sage: A = GF(2)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1])
sage: exp = phi.exponential()
sage: exp[2] == 1/(T**q - T) # expected value
True
sage: exp[2^2] == 1/((T**(q**2) - T)*(T**q - T)**q) # expected value
True
sage: exp[2^3] == 1/((T**(q**3) - T)*(T**(q**2) - T)**q*(T**q - T)**(q**2)) # expected value
True

REFERENCE:

See section 4.6 of [Gos1998]_ for the definition of the
exponential.
"""
if self.category()._characteristic:
raise ValueError(f"characteristic must be zero (={self.characteristic()})")
L = LazyPowerSeriesRing(self._base, name)
zero = self._base.zero()
q = self._Fq.cardinality()
exp = lambda k: self._compute_coefficient_exp(ZZ(k).log(q)) if ZZ(k).is_power_of(q) or k == 0 else zero
return L(exp, valuation=1)

def gen(self):
r"""
Return the generator of the Drinfeld module.
Expand Down Expand Up @@ -1103,6 +1229,113 @@ def j_invariant(self):
q = self._Fq.order()
return (g**(q+1)) / delta

@cached_method
def _compute_coefficient_log(self, k):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remarks as for exp.

r"""
Return the `q^k`-th coefficient of the logarithm of this Drinfeld module.

TESTS::

sage: A = GF(2)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1])
sage: q = A.base_ring().cardinality()
sage: phi._compute_coefficient_log(0)
1
sage: phi._compute_coefficient_log(1)
1/(T^2 + T)
sage: phi._compute_coefficient_log(2)
1/(T^6 + T^5 + T^3 + T^2)
sage: phi._compute_coefficient_log(3)
1/(T^14 + T^13 + T^11 + T^10 + T^7 + T^6 + T^4 + T^3)
"""
k = ZZ(k)
if k.is_zero():
return self._base.one()
r = self._gen.degree()
T = self._gen[0]
q = self._Fq.cardinality()
c = self._base.zero()
for i in range(k):
j = k - i
if j < r + 1:
c += self._compute_coefficient_log(i)*self._gen[j]**(q**i)
return c/(T - T**(q**k))

def logarithm(self, name='z'):
r"""
Return the logarithm of the given Drinfeld module.

By definition, the logarithm is the compositional inverse of the
exponential (see :meth:`exponential`). Note that the logarithm
is only defined when the `\mathbb{F}_q[T]`-characteristic is
zero.

INPUT:

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

OUTPUT:

A lazy power series over the base field.

EXAMPLES::

sage: A = GF(2)['T']
DavidAyotte marked this conversation as resolved.
Show resolved Hide resolved
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1])
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^8)

The logarithm is returned as a lazy power series, meaning that
any of its coefficients can be computed on demands::

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)

Example in higher rank::

sage: A = GF(5)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, T^2, T + T^2 + T^4, 1])
sage: phi.logarithm()
z + ((4*T/(T^4+4))*z^5) + O(z^8)

TESTS::

sage: A = GF(2)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1])
sage: q = 2
sage: log[2] == -1/((T**q - T)) # expected value
True
sage: log[2**2] == 1/((T**q - T)*(T**(q**2) - T)) # expected value
True
sage: log[2**3] == -1/((T**q - T)*(T**(q**2) - T)*(T**(q**3) - T)) # expected value
True

::

sage: Fq.<w> = GF(3)
sage: A = Fq['T']
sage: phi = DrinfeldModule(A, [w, 1])
sage: phi.logarithm()
Traceback (most recent call last):
...
ValueError: characteristic must be zero (=T + 2)
"""
if self.category()._characteristic:
raise ValueError(f"characteristic must be zero (={self.characteristic()})")
L = LazyPowerSeriesRing(self._base, name)
zero = self._base.zero()
q = self._Fq.cardinality()
log = lambda k: self._compute_coefficient_log(ZZ(k).log(q)) if ZZ(k).is_power_of(q) or k == 0 else zero
DavidAyotte marked this conversation as resolved.
Show resolved Hide resolved
return L(log, valuation=1)


def morphism(self):
r"""
Return the morphism object that defines the Drinfeld module.
Expand Down