Drinfeld modules#

This module provides the class sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule.

For finite Drinfeld modules and their theory of complex multiplication, see class sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule.

AUTHORS:

  • Antoine Leudière (2022-04): initial version

  • Xavier Caruso (2022-06): initial version

  • David Ayotte (2023-03): added basic \(j\)-invariants

class sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule(gen, category)#

Bases: Parent, UniqueRepresentation

This class implements Drinfeld \(\mathbb{F}_q[T]\)-modules.

Let \(\mathbb{F}_q[T]\) be a polynomial ring with coefficients in a finite field \(\mathbb{F}_q\) and let \(K\) be a field. Fix a ring morphism \(\gamma: \mathbb{F}_q[T] \to K\); we say that \(K\) is an \(\mathbb{F}_q[T]\)-field. Let \(K\{\tau\}\) be the ring of Ore polynomials with coefficients in \(K\), whose multiplication is given by the rule \(\tau \lambda = \lambda^q \tau\) for any \(\lambda \in K\).

A Drinfeld \(\mathbb{F}_q[T]\)-module over the base \(\mathbb{F}_q[T]\)-field \(K\) is an \(\mathbb{F}_q\)-algebra morphism \(\phi: \mathbb{F}_q[T] \to K\{\tau\}\) such that \(\mathrm{Im}(\phi) \not\subset K\) and \(\phi\) agrees with \(\gamma\) on \(\mathbb{F}_q\).

For \(a\) in \(\mathbb{F}_q[T]\), \(\phi(a)\) is denoted \(\phi_a\).

The Drinfeld \(\mathbb{F}_q[T]\)-module \(\phi\) is uniquely determined by the image \(\phi_T\) of \(T\); this serves as input of the class.

The base morphism is the morphism \(\gamma: \mathbb{F}_q[T] \to K\). The monic polynomial that generates the kernel of \(\gamma\) is called the \(\mathbb{F}_q[T]\)-characteristic, or function-field characteristic, of the base field. We say that \(\mathbb{F}_q[T]\) is the function ring of \(\phi\); \(K\{\tau\}\) is the Ore polynomial ring. Further, the generator is \(\phi_T\) and the constant coefficient is the constant coefficient of \(\phi_T\).

A Drinfeld module is said to be finite if the field \(K\) is. Despite an emphasis on this case, the base field can be any extension of \(\mathbb{F}_q\):

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z> = Fq.extension(6)
sage: phi = DrinfeldModule(A, [z, 4, 1])
sage: phi
Drinfeld module defined by T |--> t^2 + 4*t + z
sage: Fq = GF(49)
sage: A.<T> = Fq[]
sage: K = Frac(A)
sage: psi = DrinfeldModule(A, [K(T), T+1])
sage: psi
Drinfeld module defined by T |--> (T + 1)*t + T

Note

Finite Drinfeld modules are implemented in the class sage.rings.function_field.drinfeld_modules.finite_drinfeld_module.

Classical references on Drinfeld modules include [Gos1998], [Rosen2002], [VS06] and [Gek1991].

Note

Drinfeld modules are defined in a larger setting, in which the polynomial ring \(\mathbb{F}_q[T]\) is replaced by a more general function ring: the ring of functions in \(k\) that are regular outside \(\infty\), where \(k\) is a function field over \(\mathbb{F}_q\) with transcendence degree \(1\) and \(\infty\) is a fixed place of \(k\). This is out of the scope of this implementation.

INPUT:

  • function_ring – a univariate polynomial ring whose base field is a finite field

  • gen – the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial

  • name (default: 't') – the name of the Ore polynomial ring generator

Construction

A Drinfeld module object is constructed by giving the function ring and the generator:

sage: Fq.<z2> = GF(3^2)
sage: A.<T> = Fq[]
sage: K.<z> = Fq.extension(6)
sage: phi = DrinfeldModule(A, [z, 1, 1])
sage: phi
Drinfeld module defined by T |--> t^2 + t + z

Note

Note that the definition of the base field is implicit; it is automatically defined as the compositum of all the parents of the coefficients.

The above Drinfeld module is finite; it can also be infinite:

sage: L = Frac(A)
sage: psi = DrinfeldModule(A, [L(T), 1, T^3 + T + 1])
sage: psi
Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T
sage: phi.is_finite()
True
sage: psi.is_finite()
False

In those examples, we used a list of coefficients ([z, 1, 1]) to represent the generator \(\phi_T = z + t + t^2\). One can also use regular Ore polynomials:

sage: ore_polring = phi.ore_polring()
sage: t = ore_polring.gen()
sage: rho_T = z + t^3
sage: rho = DrinfeldModule(A, rho_T)
sage: rho
Drinfeld module defined by T |--> t^3 + z
sage: rho(T) == rho_T
True

Images under the Drinfeld module are computed by calling the object:

sage: phi(T)  # phi_T, the generator of the Drinfeld module
t^2 + t + z
sage: phi(T^3 + T + 1)  # phi_(T^3 + T + 1)
t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4
+ (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3
+ (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2
+ (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1
sage: phi(1)  # phi_1
1

The category of Drinfeld modules

Drinfeld modules have their own category (see class sage.categories.drinfeld_modules.DrinfeldModules):

sage: phi.category()
Category of Drinfeld modules over Finite Field in z of size 3^12 over its base
sage: phi.category() is psi.category()
False
sage: phi.category() is rho.category()
True

One can use the category to directly create new objects:

sage: cat = phi.category()
sage: cat.object([z, 0, 0, 1])
Drinfeld module defined by T |--> t^3 + z

The base field of a Drinfeld module

The base field of the Drinfeld module is retrieved using base():

sage: phi.base()
Finite Field in z of size 3^12 over its base

The base morphism is retrieved using base_morphism():

sage: phi.base_morphism()
Ring morphism:
  From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2
  To:   Finite Field in z of size 3^12 over its base
  Defn: T |--> z

Note that the base field is not the field \(K\). Rather, it is a ring extension (see sage.rings.ring_extension.RingExtension) whose underlying ring is \(K\) and whose base is the base morphism:

sage: phi.base() is K
False

Getters

One can retrieve basic properties:

sage: phi.base_morphism()
Ring morphism:
  From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2
  To:   Finite Field in z of size 3^12 over its base
  Defn: T |--> z
sage: phi.ore_polring()  # K{t}
Ore Polynomial Ring in t over Finite Field in z of size 3^12 over its base
 twisted by Frob^2
sage: phi.function_ring()  # Fq[T]
Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2
sage: phi.gen()  # phi_T
t^2 + t + z
sage: phi.gen() == phi(T)
True
sage: phi.constant_coefficient()  # Constant coefficient of phi_T
z
sage: phi.morphism()  # The Drinfeld module as a morphism
Ring morphism:
  From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2
  To:   Ore Polynomial Ring in t
        over Finite Field in z of size 3^12 over its base
        twisted by Frob^2
  Defn: T |--> t^2 + t + z

One can compute the rank and height:

sage: phi.rank()
2
sage: phi.height()
1

As well as the j-invariant:

sage: phi.j_invariant()  # j-invariant
1

A Drinfeld \(\mathbb{F}_q[T]\)-module can be seen as an Ore polynomial with positive degree and constant coefficient \(\gamma(T)\), where \(\gamma\) is the base morphism. This analogy is the motivation for the following methods:

sage: phi.coefficients()
[z, 1, 1]
sage: phi.coefficient(1)
1

Morphisms and isogenies

A morphism of Drinfeld modules \(\phi \to \psi\) is an Ore polynomial \(f \in K\{\tau\}\) such that \(f \phi_a = \psi_a f\) for every \(a\) in the function ring. In our case, this is equivalent to \(f \phi_T = \psi_T f\). An isogeny is a nonzero morphism.

Use the in syntax to test if an Ore polynomial defines a morphism:

sage: phi(T) in Hom(phi, phi)
True
sage: t^6 in Hom(phi, phi)
True
sage: t^5 + 2*t^3 + 1 in Hom(phi, phi)
False
sage: 1 in Hom(phi, rho)
False
sage: 1 in Hom(phi, phi)
True
sage: 0 in Hom(phi, rho)
True

To create a SageMath object representing the morphism, call the homset (hom):

sage: hom = Hom(phi, phi)
sage: frobenius_endomorphism = hom(t^6)
sage: identity_morphism = hom(1)
sage: zero_morphism = hom(0)
sage: frobenius_endomorphism
Endomorphism of Drinfeld module defined by T |--> t^2 + t + z
  Defn: t^6
sage: identity_morphism
Identity morphism of Drinfeld module defined by T |--> t^2 + t + z
sage: zero_morphism
Endomorphism of Drinfeld module defined by T |--> t^2 + t + z
  Defn: 0

The underlying Ore polynomial is retrieved with the method ore_polynomial():

sage: frobenius_endomorphism.ore_polynomial()
t^6
sage: identity_morphism.ore_polynomial()
1

One checks if a morphism is an isogeny, endomorphism or isomorphism:

sage: frobenius_endomorphism.is_isogeny()
True
sage: identity_morphism.is_isogeny()
True
sage: zero_morphism.is_isogeny()
False
sage: frobenius_endomorphism.is_isomorphism()
False
sage: identity_morphism.is_isomorphism()
True
sage: zero_morphism.is_isomorphism()
False

The Vélu formula

Let P be a nonzero Ore polynomial. We can decide if P defines an isogeny with a given domain and, if it does, find the codomain:

sage: P = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z
sage: psi = phi.velu(P)
sage: psi
Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2
 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z
sage: P in Hom(phi, psi)
True
sage: P * phi(T) == psi(T) * P
True

If the input does not define an isogeny, an exception is raised:

sage: phi.velu(0)
Traceback (most recent call last):
...
ValueError: the input does not define an isogeny
sage: phi.velu(t)
Traceback (most recent call last):
...
ValueError: the input does not define an isogeny

The action of a Drinfeld module

The \(\mathbb{F}_q[T]\)-Drinfeld module \(\phi\) induces a special left \(\mathbb{F}_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 action() returns a sage.rings.function_field.drinfeld_modules.action.Action object representing the Drinfeld module action.

Note

In this implementation, \(L\) is \(K\):

sage: action = phi.action()
sage: action
Action on Finite Field in z of size 3^12 over its base
 induced by Drinfeld module defined by T |--> t^2 + t + z

The action on elements is computed by calling the action object:

sage: P = T + 1
sage: a = z
sage: action(P, a)
...
z^9 + 2*z^8 + 2*z^7 + 2*z^6 + 2*z^3 + z^2
sage: action(0, K.random_element())
0
sage: action(A.random_element(), 0)
0

Warning

The class DrinfeldModuleAction may be replaced later on. See issues #34833 and #34834.

action()#

Return the action object (sage.rings.function_field.drinfeld_modules.action.Action) that represents the module action, on the base codomain, that is induced by the Drinfeld module.

OUTPUT: a Drinfeld module action object

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: action = phi.action()
sage: action
Action on Finite Field in z12 of size 5^12 over its base
 induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 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

The action on elements is computed as follows:

sage: P = T^2 + T + 1
sage: a = z12 + 1
sage: action(P, a)
3*z12^11 + 2*z12^10 + 3*z12^9 + 3*z12^7 + 4*z12^5 + z12^4 + z12^3 + 2*z12 + 1
sage: action(0, a)
0
sage: action(P, 0)
0
basic_j_invariant_parameters(coeff_indices=None, nonzero=False)#

Return the list of basic \(j\)-invariant parameters.

See the method j_invariant() for definitions.

INPUT:

  • coeff_indices (list or tuple, or NoneType; default: None) – indices of the Drinfeld module generator coefficients to be considered in the computation. If the parameter is None (default), all the coefficients are involved.

  • nonzero (boolean, default: False) – if this flag is set to True, then only the parameters for which the corresponding basic \(j\)-invariant is nonzero are returned.

Warning

The usage of this method can be computationally expensive e.g. if the rank is greater than four, or if \(q\) is large. Setting the nonzero flag to True can speed up the computation considerably if the Drinfeld module generator possesses multiple zero coefficients.

EXAMPLES:

sage: A = GF(5)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 0, T+1, T^2 + 1])
sage: phi.basic_j_invariant_parameters()
[((1,), (31, 1)),
 ((1, 2), (1, 5, 1)),
 ((1, 2), (7, 4, 1)),
 ((1, 2), (8, 9, 2)),
 ((1, 2), (9, 14, 3)),
 ((1, 2), (10, 19, 4)),
 ((1, 2), (11, 24, 5)),
 ((1, 2), (12, 29, 6)),
 ((1, 2), (13, 3, 1)),
 ((1, 2), (15, 13, 3)),
 ((1, 2), (17, 23, 5)),
 ((1, 2), (19, 2, 1)),
 ((1, 2), (20, 7, 2)),
 ((1, 2), (22, 17, 4)),
 ((1, 2), (23, 22, 5)),
 ((1, 2), (25, 1, 1)),
 ((1, 2), (27, 11, 3)),
 ((1, 2), (29, 21, 5)),
 ((1, 2), (31, 31, 7)),
 ((2,), (31, 6))]

Use the nonzero=True flag to display only the parameters whose \(j\)-invariant value is nonzero:

sage: phi.basic_j_invariant_parameters(nonzero=True)
[((2,), (31, 6))]

One can specify the list of coefficients indices to be considered in the computation:

sage: A = GF(2)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, T, 1, T])
sage: phi.basic_j_invariant_parameters([1, 2])
[((1,), (7, 1)),
 ((1, 2), (1, 2, 1)),
 ((1, 2), (4, 1, 1)),
 ((1, 2), (5, 3, 2)),
 ((1, 2), (6, 5, 3)),
 ((1, 2), (7, 7, 4)),
 ((2,), (7, 3))]
basic_j_invariants(nonzero=False)#

Return a dictionary whose keys are all the basic \(j\)-invariants parameters and values are the corresponding \(j\)-invariant.

See the method j_invariant() for definitions.

INPUT:

  • nonzero (boolean, default: False) – if this flag is set to True, then only the parameters for which the corresponding basic \(j\)-invariant is nonzero are returned.

Warning

The usage of this method can be computationally expensive e.g. if the rank is greater than four, or if \(q\) is large. Setting the nonzero flag to True can speed up the computation considerably if the Drinfeld module generator possesses multiple zero coefficients.

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.basic_j_invariants()
{((1,), (26, 1)): z12^10 + 4*z12^9 + 3*z12^8 + 2*z12^7 + 3*z12^6 + z12^5 + z12^3 + 4*z12^2 + z12 + 2}
sage: phi = DrinfeldModule(A, [p_root, 0, 1, z12])
sage: phi.basic_j_invariants(nonzero=True)
{((2,), (651, 26)): z12^11 + 3*z12^10 + 4*z12^9 + 3*z12^8 + z12^7 + 2*z12^6 + 3*z12^4 + 2*z12^3 + z12^2 + 4*z12}
sage: A = GF(5)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, T + 2, T+1, 1])
sage: J_phi = phi.basic_j_invariants(); J_phi
{((1,), (31, 1)): T^31 + 2*T^30 + 2*T^26 + 4*T^25 + 2*T^6 + 4*T^5 + 4*T + 3,
 ((1, 2), (1, 5, 1)): T^6 + 2*T^5 + T + 2,
 ((1, 2), (7, 4, 1)): T^11 + 3*T^10 + T^9 + 4*T^8 + T^7 + 2*T^6 + 2*T^4 + 3*T^3 + 2*T^2 + 3,
 ((1, 2), (8, 9, 2)): T^17 + 2*T^15 + T^14 + 4*T^13 + 4*T^11 + 4*T^10 + 3*T^9 + 2*T^8 + 3*T^7 + 2*T^6 + 3*T^5 + 2*T^4 + 3*T^3 + 4*T^2 + 3*T + 1,
 ((1, 2), (9, 14, 3)): T^23 + 2*T^22 + 2*T^21 + T^19 + 4*T^18 + T^17 + 4*T^16 + T^15 + 4*T^14 + 2*T^12 + 4*T^11 + 4*T^10 + 2*T^8 + 4*T^7 + 4*T^6 + 2*T^4 + T^2 + 2*T + 2,
 ((1, 2), (10, 19, 4)): T^29 + 4*T^28 + T^27 + 4*T^26 + T^25 + 2*T^24 + 3*T^23 + 2*T^22 + 3*T^21 + 2*T^20 + 4*T^19 + T^18 + 4*T^17 + T^16 + 4*T^15 + T^9 + 4*T^8 + T^7 + 4*T^6 + T^5 + 4*T^4 + T^3 + 4*T^2 + T + 4,
 ...
 ((2,), (31, 6)): T^31 + T^30 + T^26 + T^25 + T^6 + T^5 + T + 1}
sage: J_phi[((1, 2), (7, 4, 1))]
T^11 + 3*T^10 + T^9 + 4*T^8 + T^7 + 2*T^6 + 2*T^4 + 3*T^3 + 2*T^2 + 3
coefficient(n)#

Return the \(n\)-th coefficient of the generator.

INPUT:

  • n – a nonnegative integer

OUTPUT: an element in the base codomain

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.coefficient(0)
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
sage: phi.coefficient(0) == p_root
True
sage: phi.coefficient(1)
z12^3
sage: phi.coefficient(2)
z12^5
sage: phi.coefficient(5)
Traceback (most recent call last):
...
ValueError: input must be >= 0 and <= rank
coefficients(sparse=True)#

Return the coefficients of the generator, as a list.

If the flag sparse is True (default), only return the nonzero coefficients; otherwise, return all of them.

INPUT:

  • sparse – a boolean

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.coefficients()
[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,
 z12^3,
 z12^5]

Careful, the method only returns the nonzero coefficients, unless otherwise specified:

sage: rho = DrinfeldModule(A, [p_root, 0, 0, 0, 1])
sage: rho.coefficients()
[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,
 1]
sage: rho.coefficients(sparse=False)
[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,
 0,
 0,
 0,
 1]
gen()#

Return the generator of the Drinfeld module.

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.gen() == phi(T)
True
height()#

Return the height of the Drinfeld module if the function field characteristic is a prime ideal; raise ValueError otherwise.

The height of a Drinfeld module is defined when the function field characteristic is a prime ideal. In our case, this ideal is even generated by a monic polynomial \(\mathfrak{p}\) in the function field. Write \(\phi_\mathfrak{p} = a_s \tau^s + \dots + \tau^{r*\deg(\mathfrak{p})}\). The height of the Drinfeld module is the well-defined positive integer \(h = \frac{s}{\deg(\mathfrak{p})}\).

Note

See [Gos1998], Definition 4.5.8 for the general definition.

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.height() == 1
True
sage: phi.is_ordinary()
True
sage: Fq = GF(343)
sage: A.<T> = Fq[]
sage: K.<z6> = Fq.extension(2)
sage: phi = DrinfeldModule(A, [1, 0, z6])
sage: phi.height()
2
sage: phi.is_supersingular()
True

In characteristic zero, height is not defined:

sage: L = A.fraction_field()
sage: phi = DrinfeldModule(A, [L(T), L(1)])
sage: phi.height()
Traceback (most recent call last):
...
ValueError: height is only defined for prime function field characteristic
hom(x, codomain=None)#

Return the homomorphism defined by x having this Drinfeld module as domain.

We recall that a homomorphism \(f : \phi \to \psi\) between two Drinfeld modules is defined by an Ore polynomial \(u\), which is subject to the relation \(phi_T u = u \psi_T\).

INPUT:

  • x – an element of the ring of functions, or an Ore polynomial

  • codomain – a Drinfeld module or None (default: None)

EXAMPLES:

sage: Fq = GF(5)
sage: A.<T> = Fq[]
sage: K.<z> = Fq.extension(3)
sage: phi = DrinfeldModule(A, [z, 0, 1, z])
sage: phi
Drinfeld module defined by T |--> z*t^3 + t^2 + z

An important class of endomorphisms of a Drinfeld module \(\phi\) is given by scalar multiplications, that are endomorphisms corresponding to the Ore polynomials \(\phi_a\) with \(a\) in the function ring \(A\). We construct them as follows:

sage: phi.hom(T)
Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z
  Defn: z*t^3 + t^2 + z
sage: phi.hom(T^2 + 1)
Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z
  Defn: z^2*t^6 + (3*z^2 + z + 1)*t^5 + t^4 + 2*z^2*t^3 + (3*z^2 + z + 1)*t^2 + z^2 + 1

We can also define a morphism by passing in the Ore polynomial defining it. For example, below, we construct the Frobenius endomorphism of \(\phi\):

sage: t = phi.ore_variable()
sage: phi.hom(t^3)
Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z
  Defn: t^3

If the input Ore polynomial defines a morphism to another Drinfeld module, the latter is determined automatically:

sage: phi.hom(t + 1)
Drinfeld Module morphism:
  From: Drinfeld module defined by T |--> z*t^3 + t^2 + z
  To:   Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*t^3 + (3*z^2 + 2*z + 2)*t^2 + (2*z^2 + 3*z + 4)*t + z
  Defn: t + 1
is_finite()#

Return True if this Drinfeld module is finite, False otherwise.

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.is_finite()
True
sage: B.<Y> = Fq[]
sage: L = Frac(B)
sage: psi = DrinfeldModule(A, [L(2), L(1)])
sage: psi.is_finite()
False
is_isomorphic(other, absolutely=False)#

Return True if this Drinfeld module is isomorphic to other; return False otherwise.

INPUT:

  • absolutely – a boolean (default: False); if True, check the existence of an isomorphism defined on the base field; if False, check over an algebraic closure.

EXAMPLES:

sage: Fq = GF(5)
sage: A.<T> = Fq[]
sage: K.<z> = Fq.extension(3)
sage: phi = DrinfeldModule(A, [z, 0, 1, z])
sage: t = phi.ore_variable()

We create a second Drinfeld module, which is isomorphic to \(\phi\) and then check that they are indeed isomorphic:

sage: psi = phi.velu(z)
sage: phi.is_isomorphic(psi)
True

In the example below, \(\phi\) and \(\psi\) are isogenous but not isomorphic:

sage: psi = phi.velu(t + 1)
sage: phi.is_isomorphic(psi)
False

Here is an example of two Drinfeld modules which are isomorphic on an algebraic closure but not on the base field:

sage: phi = DrinfeldModule(A, [z, 1])
sage: psi = DrinfeldModule(A, [z, z])
sage: phi.is_isomorphic(psi)
False
sage: phi.is_isomorphic(psi, absolutely=True)
True

On certain fields, testing isomorphisms over the base field may fail:

sage: L = A.fraction_field()
sage: T = L.gen()
sage: phi = DrinfeldModule(A, [T, 0, 1])
sage: psi = DrinfeldModule(A, [T, 0, T])
sage: psi.is_isomorphic(phi)
Traceback (most recent call last):
...
NotImplementedError: cannot solve the equation u^24 == T

However, it never fails over the algebraic closure:

sage: psi.is_isomorphic(phi, absolutely=True)
True

Note finally that when the constant coefficients of \(\phi_T\) and \(\psi_T\) differ, \(\phi\) and \(\psi\) do not belong to the same category and checking whether they are isomorphic does not make sense; in this case, an error is raised:

sage: phi = DrinfeldModule(A, [z, 0, 1])
sage: psi = DrinfeldModule(A, [z^2, 0, 1])
sage: phi.is_isomorphic(psi)
Traceback (most recent call last):
...
ValueError: Drinfeld modules are not in the same category
j_invariant(parameter=None, check=True)#

Return the \(j\)-invariant of the Drinfeld \(\mathbb{F}_q[T]\)-module for the given parameter.

Suppose that \(\phi_T = g_0 + g_1\tau + \cdots + g_r \tau^r\) with \(g_r \neq 0\). Then the \(((k_1, \ldots, k_n), (d_1, \ldots, d_n, d_r))\)-\(j\)-invariant of \(\phi\) is defined by

\[j_{k_1, \ldots, k_n}^{d_1, \ldots, d_n, d_r}(\phi) := \frac{1}{g_r^{d_q}}\prod_{i = 1}^n g_{k_i}^{d_i}\]

where \(1\leqslant k_1 < k_2 < \ldots < k_n \leqslant r - 1\) and the integers \(d_i\) satisfy the weight-0 condition:

\[d_1 (q^{k_1} - 1) + d_2 (q^{k_2} - 1) + \cdots + d_{n} (q^{k_n} - 1) = d_r (q^r - 1).\]

Furthermore, if \(\gcd(d_1,\ldots, d_n, d_r) = 1\) and

\[0 \leq d_i \leq (q^r - 1)/(q^{\gcd(i, r)} - 1), \quad 1 \leq i \leq n,\]

then the \(j\)-invariant is called basic. See the method basic_j_invariant_parameters() for computing the list of all basic \(j\)-invariant parameters.

INPUT:

  • parameter (tuple or list, integer or NoneType; default: None) – the \(j\)-invariant parameter:

    • If parameter is a list or a tuple, then it must be of the form: \(((k_1, k_2, \ldots, k_n), (d_1, d_2, \ldots, d_n, d_r))\), where the \(k_i\) and \(d_i\) are integers satisfying the weight-0 condition described above.

    • If parameter is an integer \(k\) then the method returns the j-invariant associated to the parameter \(((k,), (d_k, d_r))\);

    • If parameter is None and the rank of the Drinfeld module is 2, then the method returns its usual \(j\)-invariant, that is the \(j\)-invariant for the parameter \(((1,), (q+1, 1))\).

  • check (bool, default: True) – if this flag is set to False then the code will not check if the given parameter is valid and satisfy the weight-0 condition.

OUTPUT: the \(j\)-invariant of self for the given parameter.

REFERENCE:

The notion of basic \(j\)-invariant was introduced by Potemine in [Pot1998].

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.j_invariant()
z12^10 + 4*z12^9 + 3*z12^8 + 2*z12^7 + 3*z12^6 + z12^5 + z12^3 + 4*z12^2 + z12 + 2
sage: psi = DrinfeldModule(A, [p_root, 1, 1])
sage: psi.j_invariant()
1
sage: rho = DrinfeldModule(A, [p_root, 0, 1])
sage: rho.j_invariant()
0
sage: A = GF(5)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, T^2, 1, T + 1, T^3])
sage: phi.j_invariant(1)
T^309
sage: phi.j_invariant(2)
1/T^3
sage: phi.j_invariant(3)
(T^156 + T^155 + T^151 + T^150 + T^131 + T^130 + T^126 + T^125 + T^31 + T^30 + T^26 + T^25 + T^6 + T^5 + T + 1)/T^93

The parameter can either be a tuple or a list:

sage: Fq.<a> = GF(7)
sage: A.<T> = Fq[]
sage: phi = DrinfeldModule(A, [a, a^2 + a, 0, 3*a, a^2+1])
sage: J = phi.j_invariant(((1, 3), (267, 269, 39))); J
5
sage: J == (phi.coefficient(1)**267)*(phi.coefficient(3)**269)/(phi.coefficient(4)**39)
True
sage: phi.j_invariant([[3], [400, 57]])
4
sage: phi.j_invariant([[3], [400, 57]]) == phi.j_invariant(3)
True

The list of all basic \(j\)-invariant parameters can be retrieved using the method basic_j_invariant_parameters():

sage: A = GF(3)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, T^2 + T + 1, 0, T^4 + 1, T - 1])
sage: param = phi.basic_j_invariant_parameters(nonzero=True)
sage: phi.j_invariant(param[1])
T^13 + 2*T^12 + T + 2
sage: phi.j_invariant(param[2])
T^35 + 2*T^31 + T^27 + 2*T^8 + T^4 + 2
jk_invariants()#

Return a dictionary whose keys are all the integers \(1 \leqslant k \leqslant r-1\) and the values are the corresponding \(j_k\)-invariants

Recall that the \(j_k\)-invariant of self is defined by:

\[j_k := \frac{g_k^{(q^r - 1)/(\mathrm{gcd}(k, r) - 1)}}{g_r^{(q^k - 1)/(\mathrm{gcd}(k, r) - 1)}}\]

where \(g_i\) is the \(i\)-th coefficient of the generator of self.

EXAMPLES:

sage: A = GF(3)['T']
sage: K.<T> = Frac(A)
sage: phi = DrinfeldModule(A, [T, 1, T+1, T^3, T^6])
sage: jk_inv = phi.jk_invariants(); jk_inv
{1: 1/T^6, 2: (T^10 + T^9 + T + 1)/T^6, 3: T^42}
sage: jk_inv[2]
(T^10 + T^9 + T + 1)/T^6
sage: F = GF(7**2)
sage: A = F['T']
sage: E.<z> = F.extension(4)
sage: phi = DrinfeldModule(A, [z^2, 1, z+1, z^2, z, z+1])
sage: phi.jk_invariants()
{1: 5*z^7 + 2*z^6 + 5*z^5 + 2*z^4 + 5*z^3 + z^2 + z + 2,
 2: 3*z^7 + 4*z^6 + 5*z^5 + 6*z^4 + 4*z,
 3: 5*z^7 + 6*z^6 + 6*z^5 + 4*z^3 + z^2 + 2*z + 1,
 4: 3*z^6 + 2*z^5 + 4*z^4 + 2*z^3 + 4*z^2 + 6*z + 2}
morphism()#

Return the morphism object that defines the Drinfeld module.

OUTPUT: a ring morphism from the function ring to the Ore polynomial ring

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.morphism()
Ring morphism:
  From: Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2
  To:   Ore Polynomial Ring in t over Finite Field in z12 of size 5^12
        over its base twisted by Frob^2
  Defn: T |--> z12^5*t^2 + z12^3*t + 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
sage: from sage.rings.morphism import RingHomomorphism
sage: isinstance(phi.morphism(), RingHomomorphism)
True

Actually, the DrinfeldModule method __call__() simply class the __call__ method of this morphism:

sage: phi.morphism()(T) == phi(T)
True
sage: a = A.random_element()
sage: phi.morphism()(a) == phi(a)
True

And many methods of the Drinfeld module have a counterpart in the morphism object:

sage: m = phi.morphism()
sage: m.domain() is phi.function_ring()
True
sage: m.codomain() is phi.ore_polring()
True
sage: m.im_gens()
[z12^5*t^2 + z12^3*t + 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]
sage: phi(T) == m.im_gens()[0]
True
rank()#

Return the rank of the Drinfeld module.

In our case, the rank is the degree of the generator.

OUTPUT: an integer

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.rank()
2
sage: psi = DrinfeldModule(A, [p_root, 2])
sage: psi.rank()
1
sage: rho = DrinfeldModule(A, [p_root, 0, 0, 0, 1])
sage: rho.rank()
4
scalar_multiplication(x)#

Return the endomorphism of this Drinfeld module, which is the multiplication by \(x\), i.e. the isogeny defined by the Ore polynomial \(\phi_x\).

INPUT:

  • x – an element in the ring of functions

EXAMPLES:

sage: Fq = GF(5)
sage: A.<T> = Fq[]
sage: K.<z> = Fq.extension(3)
sage: phi = DrinfeldModule(A, [z, 0, 1, z])
sage: phi
Drinfeld module defined by T |--> z*t^3 + t^2 + z
sage: phi.hom(T)
Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z
  Defn: z*t^3 + t^2 + z
sage: phi.hom(T^2 + 1)
Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z
  Defn: z^2*t^6 + (3*z^2 + z + 1)*t^5 + t^4 + 2*z^2*t^3 + (3*z^2 + z + 1)*t^2 + z^2 + 1
velu(isog)#

Return a new Drinfeld module such that input is an isogeny to this module with domain self; if no such isogeny exists, raise an exception.

INPUT:

  • isog – the Ore polynomial that defines the isogeny

OUTPUT: a Drinfeld module

ALGORITHM:

The input defines an isogeny if only if:

1. The degree of the characteristic divides the height of the input. (The height of an Ore polynomial \(P(\tau)\) is the maximum \(n\) such that \(\tau^n\) right-divides \(P(\tau)\).)

2. The input right-divides the generator, which can be tested with Euclidean division.

We test if the input is an isogeny, and, if it is, we return the quotient of the Euclidean division.

Height and Euclidean division of Ore polynomials are implemented as methods of class sage.rings.polynomial.ore_polynomial_element.OrePolynomial.

Another possible algorithm is to recursively solve a system, see arXiv 2203.06970, Eq. 1.1.

EXAMPLES:

sage: Fq = GF(25)
sage: A.<T> = Fq[]
sage: K.<z12> = Fq.extension(6)
sage: p_root = 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
sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: t = phi.ore_polring().gen()
sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4
sage: psi = phi.velu(isog)
sage: psi
Drinfeld module defined by T |-->
 (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2
 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t
 + 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
sage: isog in Hom(phi, psi)
True

This method works for endomorphisms as well:

sage: phi.velu(phi(T)) is phi
True
sage: phi.velu(t^6) is phi
True

The following inputs do not define isogenies, and the method returns None:

sage: phi.velu(0)
Traceback (most recent call last):
...
ValueError: the input does not define an isogeny
sage: phi.velu(t)
Traceback (most recent call last):
...
ValueError: the input does not define an isogeny
sage: phi.velu(t^3 + t + 2)
Traceback (most recent call last):
...
ValueError: the input does not define an isogeny