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)
Xavier Caruso (2022-06)
- 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.
Note
See also
sage.categories.drinfeld_modules
.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 fieldgen
– the generator of the Drinfeld module; as a list of coefficients or an Ore polynomialname
(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 if the rank is two:
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 ifP
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 asage.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
- 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
isTrue
(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.
A rank two Drinfeld module is supersingular if and only if its height equals its rank.
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: B.<Y> = Fq[] sage: L = Frac(B) sage: phi = DrinfeldModule(A, [L(2), L(1)]) sage: phi.height() Traceback (most recent call last): ... NotImplementedError: height not implemented in this case
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
- 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
- j_invariant()#
Return the j-invariant of the Drinfeld module if the rank is two; raise a NotImplementedError otherwise.
Assume the rank is two. Write the generator \(\phi_T = \omega + g\tau + \Delta\tau^2\). The j-invariant is defined by \(\frac{g^{q+1}}{\Delta}\), \(q\) being the order of the base field of the function ring. In our case, this field is always finite.
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.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
The rank must be two:
sage: sigma = DrinfeldModule(A, [p_root, 1, 0]) sage: sigma.j_invariant() Traceback (most recent call last): ... NotImplementedError: rank must be 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
- 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