From af1c2e9a9e47ae5b440bd5bc458d4b665ccfdc74 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Fri, 17 Nov 2023 20:50:50 -0500 Subject: [PATCH 01/30] Added basis computation over Fq[tau^n] --- .../function_field/drinfeld_modules/homset.py | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 9ca53442cf5..77d012e2cab 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -28,6 +28,10 @@ from sage.misc.latex import latex from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism from sage.structure.parent import Parent +from sage.functions.log import logb +from sage.matrix.constructor import Matrix +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.misc.randstate import set_random_seed class DrinfeldModuleMorphismAction(Action): @@ -421,3 +425,267 @@ def _element_constructor_(self, *args, **kwds): # would call __init__ instead of __classcall_private__. This # seems to work, but I don't know what I'm doing. return DrinfeldModuleMorphism(self, *args, **kwds) + + def element(self, degree): + r""" + Return an element of the space of morphisms between the domain and + codomain. By default, chooses an element of largest degree less than + or equal to the parameter `degree`. + + INPUT: + + - ``degree`` -- the maximum degree of the morphism + + OUTPUT: a univariate ore polynomials with coefficients in `K` + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: H = Hom(phi, psi) + sage: M = H.element(3) + sage: M_poly = M.ore_polynomial() + sage: M_poly*phi.gen() - psi.gen()*M_poly + 0 + + ALGORITHM: + + We scan the basis for the first element of maximal degree + and return it. + """ + basis = self.Fq_basis(degree) + elem = basis[0] + max_deg = elem.ore_polynomial().degree() + for basis_elem in basis: + if basis_elem.ore_polynomial().degree() > max_deg: + elem = basis_elem + max_deg = elem.ore_polynomial().degree() + return elem + + def Fq_basis(self, degree): + r""" + Return a basis for the `\mathbb{F}_q`-space of morphisms from `phi` to + a Drinfeld module `\psi` of degree at most `degree`. A morphism + `\iota: \phi \to psi` is an element `\iota \in K\{\tau\}` such that + `iota \phi_T = \psi_T \iota`. The degree of a morphism is the + `\tau`-degree of `\iota`. + + INPUT: + + - ``degree`` -- the maximum degree of the morphism + + OUTPUT: a list of univariate ore polynomials with coefficients in `K` + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: H = Hom(phi, psi) + sage: B = H.Fq_basis(3) + sage: M = B[0] + sage: M_poly = M.ore_polynomial() + sage: M_poly*phi.gen() - psi.gen()*M_poly + 0 + + ALGORITHM: + + We return the basis of the kernel of a matrix derived from the + constraint that `\iota \phi_T = \psi_T \iota`. See [Wes2022]_ for + details on this algorithm. + """ + domain, codomain = self.domain(), self.codomain() + Fq = domain._Fq + K = domain.base_over_constants_field() + q = Fq.cardinality() + char = Fq.characteristic() + r = domain.rank() + n = K.degree(Fq) + # shorten name for readability + d = degree + qorder = logb(q, char) + K_basis = K.basis_over(Fq) + dom_coeffs = domain.coefficients(sparse=False) + cod_coeffs = codomain.coefficients(sparse=False) + + sys = Matrix(Fq, (d + r + 1)*n, (d + 1)*n) + for k in range(0, d + r + 1): + for i in range(max(0, k - r), min(k, d) + 1): + # We represent multiplication and Frobenius + # as operators acting on K as a vector space + # over Fq + # Require matrices act on the right, so we + # take a transpose of operators here + oper = K(dom_coeffs[k-i] + .frobenius(qorder*i)).matrix().transpose() \ + - K(cod_coeffs[k-i]).matrix().transpose() \ + * self._frobenius_matrix(k - i) + for j in range(n): + for l in range(n): + sys[k*n + j, i*n + l] = oper[j, l] + sol = sys.right_kernel().basis() + # Reconstruct the Ore polynomial from the coefficients + basis = [] + tau = domain.ore_polring().gen() + for basis_elem in sol: + basis.append(self(sum([sum([K_basis[j]*basis_elem[i*n + j] + for j in range(n)])*(tau**i) + for i in range(d + 1)]))) + return basis + + def basis(self): + r""" + Return a basis for the `\mathbb{F}_q[\tau^n]`-module of morphisms from + the domain to the codomain. + + INPUT: + + - None + + OUTPUT: a list of univariate ore polynomials with coefficients in `K` + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: H = Hom(phi, psi) + sage: H.basis() + [(z^2 + 1)*t^2 + t + z + 1, (z^2 + 1)*t^5 + (z + 1)*t^4 + z*t^3 + t^2 + (z^2 + z)*t + z, z^2] + + :: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z, 3*z, 4*z]) + sage: chi = DrinfeldModule(A, [z, 2*z^2 + 3, 4*z^2 + 4*z]) + sage: H = Hom(phi, chi) + sage: H.basis() + [] + + ALGORITHM: + + We return the basis of the kernel of a matrix derived from the + constraint that `\iota \phi_T = \psi_T \iota` for any morphism + `iota`. + """ + domain, codomain = self.domain(), self.codomain() + Fq = domain._Fq + K = domain.base_over_constants_field() + q = Fq.cardinality() + char = Fq.characteristic() + r = domain.rank() + n = K.degree(Fq) + qorder = logb(q, char) + K_basis = [K.gen()**i for i in range(n)] + dom_coeffs = domain.coefficients(sparse=False) + cod_coeffs = codomain.coefficients(sparse=False) + # The commutative polynomial ring in tau^n. + poly_taun = PolynomialRing(Fq, 'taun') + + sys = Matrix(poly_taun, n**2, n**2) + + # Build a linear system over the commutative polynomial ring + # Fq[tau^n]. The kernel of this system consists of all + # morphisms from domain -> codomain. + for j in range(n): + for k in range(n): + for i in range(r+1): + # Coefficients of tau^{i + k} coming from the + # relation defining morphisms of Drinfeld modules + # These are elements of K, expanded in terms of + # K_basis. + c_tik = (dom_coeffs[i].frobenius(qorder*k)*K_basis[j] \ + - cod_coeffs[i]*K_basis[j].frobenius(qorder*i)) \ + .polynomial().coefficients(sparse=False) + c_tik += [0 for _ in range(n - len(c_tik))] + colpos = k*n + j + taudeg = i + k + for b in range(n): + sys[(taudeg % n)*n + b, colpos] += c_tik[b] * \ + poly_taun.gen()**(taudeg // n) + + sol = sys.right_kernel().basis() + # Reconstruct basis as skew polynomials. + basis = [] + tau = domain.ore_polring().gen() + for basis_vector in sol: + basis_poly = 0 + for i in range(n): + for j in range(n): + basis_poly += basis_vector[n*i + j].subs(tau**n)*K_basis[j]*tau**i + basis.append(basis_poly) + return basis + + def _frobenius_matrix(self, order=1, K_basis=None): + r""" + Internal method for computing the matrix of the Frobenius endomorphism + for K/Fq. This is a useful method for computing morphism ring bases so + we provide a helper method here. This should probably be part of the + Finite field implementation. + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: H = Hom(phi, psi) + sage: m = H._frobenius_matrix() + sage: e = K(z^2 + z + 1) + sage: frob = K.frobenius_endomorphism() + sage: frob(e) == K(m*vector(e.polynomial().coefficients(sparse=False))) + True + """ + Fq = self.domain()._Fq + K = self.domain().base_over_constants_field() + n = K.degree(Fq) + frob = K.frobenius_endomorphism(order) + if K_basis is None: + K_basis = [K.gen()**i for i in range(n)] + pol_var = K_basis[0].polynomial().parent().gen() + pol_ring = PolynomialRing(Fq, str(pol_var)) + frob_matrix = Matrix(Fq, n, n) + for i, elem in enumerate(K_basis): + col = pol_ring(frob(elem).polynomial()).coefficients(sparse=False) + col += [0 for _ in range(n - len(col))] + for j in range(n): + frob_matrix[j, i] = col[j] + return frob_matrix + + def random_element(self, degree, seed=None): + r""" + Return a random morphism chosen uniformly from the space of morphisms + of degree at most `degree`. + + INPUT: + + - ``degree`` -- the maximum degree of the morphism + + OUTPUT: a univariate ore polynomials with coefficients in `K` + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: H = Hom(phi, psi) + sage: M = H.random_element(3, seed=25) + sage: M_poly = M.ore_polynomial() + sage: M_poly*phi.gen() - psi.gen()*M_poly + 0 + """ + set_random_seed(seed) + return self(sum([self.domain()._Fq.random_element() + * elem.ore_polynomial() for elem in self.Fq_basis(degree)])) From 3a14482c0aa721d4310491b45498b7371ad44f09 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Sat, 18 Nov 2023 12:36:57 -0500 Subject: [PATCH 02/30] Morphism casting and citation --- src/doc/en/reference/references/index.rst | 3 +++ .../function_field/drinfeld_modules/homset.py | 17 +++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index fa67dade3ab..d133fff3262 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -6399,6 +6399,9 @@ REFERENCES: *A new keystream generator MUGI*; in FSE, (2002), pp. 179-194. +.. [Wes2022] \B. Wesolowski. 2022. *Computing isogenies between finite Drinfeld modules*. + Cryptology ePrint Archive, Paper 2022/438. https://eprint.iacr.org/2022/438 + .. [Whi1932] \H. Whitney, *Congruent graphs and the connectivity of graphs*, American Journal of Mathematics, pages 150--168, 1932, diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 77d012e2cab..fa3c2c447d8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -475,9 +475,9 @@ def Fq_basis(self, degree): INPUT: - - ``degree`` -- the maximum degree of the morphism + - ``degree`` -- the maximum degree of the morphisms in the span. - OUTPUT: a list of univariate ore polynomials with coefficients in `K` + OUTPUT: a list of Drinfeld module morphisms. EXAMPLES:: @@ -543,11 +543,7 @@ def basis(self): Return a basis for the `\mathbb{F}_q[\tau^n]`-module of morphisms from the domain to the codomain. - INPUT: - - - None - - OUTPUT: a list of univariate ore polynomials with coefficients in `K` + OUTPUT: a list of Drinfeld module morphisms. EXAMPLES:: @@ -557,7 +553,8 @@ def basis(self): sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) sage: H = Hom(phi, psi) - sage: H.basis() + sage: basis = H.basis() + sage: [b.ore_polynomial() for b in basis] [(z^2 + 1)*t^2 + t + z + 1, (z^2 + 1)*t^5 + (z + 1)*t^4 + z*t^3 + t^2 + (z^2 + z)*t + z, z^2] :: @@ -575,7 +572,7 @@ def basis(self): We return the basis of the kernel of a matrix derived from the constraint that `\iota \phi_T = \psi_T \iota` for any morphism - `iota`. + `iota`. """ domain, codomain = self.domain(), self.codomain() Fq = domain._Fq @@ -622,7 +619,7 @@ def basis(self): for i in range(n): for j in range(n): basis_poly += basis_vector[n*i + j].subs(tau**n)*K_basis[j]*tau**i - basis.append(basis_poly) + basis.append(self(basis_poly)) return basis def _frobenius_matrix(self, order=1, K_basis=None): From 25999df88bf6121fcb47a5f4a8770a0269288605 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Sat, 25 Nov 2023 13:23:22 -0500 Subject: [PATCH 03/30] Fixing linting --- .../function_field/drinfeld_modules/homset.py | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index fa3c2c447d8..b6efa760ce0 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -572,19 +572,16 @@ def basis(self): We return the basis of the kernel of a matrix derived from the constraint that `\iota \phi_T = \psi_T \iota` for any morphism - `iota`. + `iota: \phi \to \psi`. """ - domain, codomain = self.domain(), self.codomain() - Fq = domain._Fq - K = domain.base_over_constants_field() - q = Fq.cardinality() - char = Fq.characteristic() - r = domain.rank() + Fq = self.domain()._Fq + K = self.domain().base_over_constants_field() + r = self.domain().rank() n = K.degree(Fq) - qorder = logb(q, char) + qorder = logb(Fq.cardinality(), Fq.characteristic()) K_basis = [K.gen()**i for i in range(n)] - dom_coeffs = domain.coefficients(sparse=False) - cod_coeffs = codomain.coefficients(sparse=False) + dom_coeffs = self.domain().coefficients(sparse=False) + cod_coeffs = self.codomain().coefficients(sparse=False) # The commutative polynomial ring in tau^n. poly_taun = PolynomialRing(Fq, 'taun') @@ -600,20 +597,19 @@ def basis(self): # relation defining morphisms of Drinfeld modules # These are elements of K, expanded in terms of # K_basis. - c_tik = (dom_coeffs[i].frobenius(qorder*k)*K_basis[j] \ + c_tik = (dom_coeffs[i].frobenius(qorder*k)*K_basis[j] - cod_coeffs[i]*K_basis[j].frobenius(qorder*i)) \ .polynomial().coefficients(sparse=False) c_tik += [0 for _ in range(n - len(c_tik))] - colpos = k*n + j taudeg = i + k for b in range(n): - sys[(taudeg % n)*n + b, colpos] += c_tik[b] * \ + sys[(taudeg % n)*n + b, k*n + j] += c_tik[b] * \ poly_taun.gen()**(taudeg // n) sol = sys.right_kernel().basis() # Reconstruct basis as skew polynomials. basis = [] - tau = domain.ore_polring().gen() + tau = self.domain().ore_polring().gen() for basis_vector in sol: basis_poly = 0 for i in range(n): From a908f8aaacebc7d4b40481aba6d30b63ec2e330b Mon Sep 17 00:00:00 2001 From: ymusleh Date: Mon, 29 Jan 2024 12:05:18 -0500 Subject: [PATCH 04/30] adding fqx basis --- .../function_field/drinfeld_modules/homset.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index fa3c2c447d8..0bc09f68aab 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -622,6 +622,38 @@ def basis(self): basis.append(self(basis_poly)) return basis + def motive_power_decomposition(self, lower, upper): + r""" + Computes coefficients a_0, .., a_{r-1} such that + \tau^t = a_{r-1}\tau^{r-1} + .... + a_{0} + with each a_i \in Fq[x]. + """ + domain = self.domain() + Fq = domain._Fq + char, q = Fq.characteristic(), Fq.cardinality() + qord = logb(q, char) + coeff = domain.coefficients(sparse=False) + recurrence_relation = [-coeff[i]/coeff[r] for i in range(r)] +# base_expansions = matrix.identity(r) + expansion_list = [[1 if i == j else 0 for j in range(r)] for i in range(r)] + for i in range(0, upper - r + 1): + next_expansion = [ sum([recurrence_relation[j].frobenius(i*qord)*expansion_list[i+] + + + def motive_basis(self): + r""" + + """ + domain, codomain = self.domain(), self.codomain() + Fq = domain._Fq + K = domain.base_over_constants_field() + q = Fq.cardinality() + char = Fq.characteristic() + r = domain.rank() + n = K.degree(Fq) + qorder = logb(q, char) + + def _frobenius_matrix(self, order=1, K_basis=None): r""" Internal method for computing the matrix of the Frobenius endomorphism From c302d791c05ce5967712efe74b500ea7eeca39ea Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 29 May 2024 11:24:43 -0400 Subject: [PATCH 05/30] added motive basis expansion --- .../function_field/drinfeld_modules/homset.py | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 0bc09f68aab..2e22e15472f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -621,29 +621,43 @@ def basis(self): basis_poly += basis_vector[n*i + j].subs(tau**n)*K_basis[j]*tau**i basis.append(self(basis_poly)) return basis + - def motive_power_decomposition(self, lower, upper): - r""" - Computes coefficients a_0, .., a_{r-1} such that - \tau^t = a_{r-1}\tau^{r-1} + .... + a_{0} - with each a_i \in Fq[x]. - """ - domain = self.domain() - Fq = domain._Fq - char, q = Fq.characteristic(), Fq.cardinality() - qord = logb(q, char) - coeff = domain.coefficients(sparse=False) - recurrence_relation = [-coeff[i]/coeff[r] for i in range(r)] -# base_expansions = matrix.identity(r) - expansion_list = [[1 if i == j else 0 for j in range(r)] for i in range(r)] - for i in range(0, upper - r + 1): - next_expansion = [ sum([recurrence_relation[j].frobenius(i*qord)*expansion_list[i+] + def motive_power_decomposition(self, upper): + r""" + Computes coefficients a_0, .., a_{r-1} such that + \tau^t = a_{r-1}\tau^{r-1} + .... + a_{0} + with each a_i \in Fq[x] for each t < upper.. + """ + domain = self.domain() + r = domain.rank() + A = domain.function_ring() + x = A.gen() + Fq = domain._Fq + char, q = Fq.characteristic(), Fq.cardinality() + qord = char.log(q) + K = domain.base_over_constants_field() + coeff = domain.coefficients(sparse=False) + recurrence_relation = [K(coeff[i]/coeff[r]) for i in range(r)] +# base_expansions = matrix.identity(r) + expansion_list = [[K(1) if i == j else K(0) for j in range(r)] for i in range(r)] + for i in range(0, upper - r): + #print(f'x: {x}') + z = K.gen() + #print(f'genr: {z}') + #print(f'multi: {x*z}') + #print(f'{[expansion_list[i][k] for k in range(r)]}') + #print(f'expansions: {[K(expansion_list[i][k])*x for k in range(r)]}') + next_expansion = [-1*sum([recurrence_relation[j].frobenius(i*qord)*expansion_list[i+j][k] \ + for j in range(r)]) + expansion_list[i][k]*x for k in range(r)] + expansion_list.append(next_expansion) + return expansion_list - def motive_basis(self): - r""" - - """ + def motive_basis(self): + r""" + + """ domain, codomain = self.domain(), self.codomain() Fq = domain._Fq K = domain.base_over_constants_field() From b9ceb78dddf32aab888f82677f9a668dec209a54 Mon Sep 17 00:00:00 2001 From: ymusleh Date: Wed, 29 May 2024 18:09:21 -0400 Subject: [PATCH 06/30] updates to basis decomposition for elements of the motive --- .../function_field/drinfeld_modules/homset.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 2e22e15472f..ee136dfde08 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -621,7 +621,6 @@ def basis(self): basis_poly += basis_vector[n*i + j].subs(tau**n)*K_basis[j]*tau**i basis.append(self(basis_poly)) return basis - def motive_power_decomposition(self, upper): r""" @@ -639,26 +638,20 @@ def motive_power_decomposition(self, upper): K = domain.base_over_constants_field() coeff = domain.coefficients(sparse=False) recurrence_relation = [K(coeff[i]/coeff[r]) for i in range(r)] -# base_expansions = matrix.identity(r) expansion_list = [[K(1) if i == j else K(0) for j in range(r)] for i in range(r)] - for i in range(0, upper - r): - #print(f'x: {x}') + for i in range(0, upper - r + 1): z = K.gen() - #print(f'genr: {z}') - #print(f'multi: {x*z}') - #print(f'{[expansion_list[i][k] for k in range(r)]}') - #print(f'expansions: {[K(expansion_list[i][k])*x for k in range(r)]}') next_expansion = [-1*sum([recurrence_relation[j].frobenius(i*qord)*expansion_list[i+j][k] \ - for j in range(r)]) + expansion_list[i][k]*x for k in range(r)] + for j in range(r)]) + (expansion_list[i][k]/K(coeff[r]).frobenius(i*qord))*x for k in range(r)] expansion_list.append(next_expansion) return expansion_list - def motive_basis(self): r""" """ domain, codomain = self.domain(), self.codomain() + A = domain.function_ring() Fq = domain._Fq K = domain.base_over_constants_field() q = Fq.cardinality() @@ -666,7 +659,13 @@ def motive_basis(self): r = domain.rank() n = K.degree(Fq) qorder = logb(q, char) + basis_expansions = self.motive_power_decomposition(2*r - 2) + sys = Matrix(A, r*n) + for alpha in range(r-1): + for k in range(r-1): + for i in range(r-1): + pass def _frobenius_matrix(self, order=1, K_basis=None): r""" From 3cf46442404c3d0a5e01ec69c2befd4cf5d19cfa Mon Sep 17 00:00:00 2001 From: ymusleh Date: Fri, 31 May 2024 12:09:56 -0400 Subject: [PATCH 07/30] updates to Fq_basis and basis methods --- .../function_field/drinfeld_modules/homset.py | 126 +++++++++++++----- 1 file changed, 93 insertions(+), 33 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 4abba1dc6fe..081ffd362d4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -426,17 +426,17 @@ def _element_constructor_(self, *args, **kwds): # seems to work, but I don't know what I'm doing. return DrinfeldModuleMorphism(self, *args, **kwds) - def element(self, degree): + def an_element(self, degree): r""" - Return an element of the space of morphisms between the domain and - codomain. By default, chooses an element of largest degree less than - or equal to the parameter `degree`. + Return a non-zero element of the space of morphisms between the domain + and codomain. By default, chooses an element of largest degree less + than or equal to the parameter `degree`. INPUT: - ``degree`` -- the maximum degree of the morphism - OUTPUT: a univariate ore polynomials with coefficients in `K` + OUTPUT: a Drinfeld module morphism of degree at most ``degree`` EXAMPLES:: @@ -446,7 +446,7 @@ def element(self, degree): sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) sage: H = Hom(phi, psi) - sage: M = H.element(3) + sage: M = H.an_element(3) sage: M_poly = M.ore_polynomial() sage: M_poly*phi.gen() - psi.gen()*M_poly 0 @@ -456,7 +456,11 @@ def element(self, degree): We scan the basis for the first element of maximal degree and return it. """ - basis = self.Fq_basis(degree) + basis = self._Fq_basis(degree) + if len(basis) == 0: + raise EmptySetError( + f'no possible morphisms of degree {degree} between the' + + 'domain and codomain') elem = basis[0] max_deg = elem.ore_polynomial().degree() for basis_elem in basis: @@ -465,13 +469,15 @@ def element(self, degree): max_deg = elem.ore_polynomial().degree() return elem - def Fq_basis(self, degree): + def _Fq_basis(self, degree): r""" Return a basis for the `\mathbb{F}_q`-space of morphisms from `phi` to - a Drinfeld module `\psi` of degree at most `degree`. A morphism - `\iota: \phi \to psi` is an element `\iota \in K\{\tau\}` such that - `iota \phi_T = \psi_T \iota`. The degree of a morphism is the - `\tau`-degree of `\iota`. + a Drinfeld module `\psi` of degree at most `degree`. + + A morphism `f: \phi \to psi` is an element `f \in K\{\tau\}` such that + `f \phi_T = \psi_T f`. The degree of a morphism is the + `\tau`-degree of `f`. This method currently only works for Drinfeld + modules over finite fields. INPUT: @@ -487,16 +493,40 @@ def Fq_basis(self, degree): sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) sage: H = Hom(phi, psi) - sage: B = H.Fq_basis(3) + sage: B = H._Fq_basis(3) sage: M = B[0] sage: M_poly = M.ore_polynomial() sage: M_poly*phi.gen() - psi.gen()*M_poly 0 + :: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 1]) + sage: psi = DrinfeldModule(A, [z, 1, 1]) + sage: hom = Hom(phi, psi) + sage: hom._Fq_basis(4) + [] + + TESTS:: + + sage: Fq = GF(4) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z^5 + z^3 + 1, 1]) + sage: phi = DrinfeldModule(A, [z, z^4 + z^3 + 1, 1]) + sage: H = Hom(phi, psi) + sage: basis = H._Fq_basis(2); basis + [...Defn: t^2 + (z^5 + z^4 + z^3 + z^2)*t + z^5 + z^4 + z^3 + 1, ...Defn: t^2 + (z^5 + z^4 + z)*t + z^5 + z^4 + z^3 + z] + + + ALGORITHM: We return the basis of the kernel of a matrix derived from the - constraint that `\iota \phi_T = \psi_T \iota`. See [Wes2022]_ for + constraint that `f \phi_T = \psi_T f`. See [Wes2022]_ for details on this algorithm. """ domain, codomain = self.domain(), self.codomain() @@ -505,6 +535,8 @@ def Fq_basis(self, degree): q = Fq.cardinality() char = Fq.characteristic() r = domain.rank() + if codomain.rank() != r: + return [] n = K.degree(Fq) # shorten name for readability d = degree @@ -524,24 +556,32 @@ def Fq_basis(self, degree): oper = K(dom_coeffs[k-i] .frobenius(qorder*i)).matrix().transpose() \ - K(cod_coeffs[k-i]).matrix().transpose() \ - * self._frobenius_matrix(k - i) + * self._frobenius_matrix(k - i, K_basis) for j in range(n): for l in range(n): sys[k*n + j, i*n + l] = oper[j, l] sol = sys.right_kernel().basis() + #print(f'sol:{sol}') # Reconstruct the Ore polynomial from the coefficients basis = [] tau = domain.ore_polring().gen() for basis_elem in sol: - basis.append(self(sum([sum([K_basis[j]*basis_elem[i*n + j] + ore_poly = sum([sum([K_basis[j]*basis_elem[i*n + j] for j in range(n)])*(tau**i) - for i in range(d + 1)]))) + for i in range(d + 1)]) + try: + basis.append(self(ore_poly)) + except ValueError: + raise ValueError('solution doesn\'t correspond to a morphism') return basis def basis(self): r""" - Return a basis for the `\mathbb{F}_q[\tau^n]`-module of morphisms from - the domain to the codomain. + Let `n = [K:\mathbb{F}_q]` and recall that `\tau^n` corresponds to the + Frobenius endomorphism on any Drinfeld module `\phi`. Return a basis + for the `\mathbb{F}_q[\tau^n]`-module of morphisms from the domain to + the codomain. This method currently only works for Drinfeld modules + over finite fields. OUTPUT: a list of Drinfeld module morphisms. @@ -557,7 +597,7 @@ def basis(self): sage: [b.ore_polynomial() for b in basis] [(z^2 + 1)*t^2 + t + z + 1, (z^2 + 1)*t^5 + (z + 1)*t^4 + z*t^3 + t^2 + (z^2 + z)*t + z, z^2] - :: + :: sage: Fq = GF(5) sage: A. = Fq[] @@ -568,6 +608,19 @@ def basis(self): sage: H.basis() [] + TESTS:: + + sage: Fq = GF(4) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z^5 + z^3 + 1, 1]) + sage: phi = DrinfeldModule(A, [z, z^4 + z^3 + 1, 1]) + sage: H = Hom(phi, psi) + sage: basis = H.basis(); basis + [... Defn: t^2 + (z^5 + z^4 + z^3 + z^2)*t + z^5 + z^4 + z^3 + 1, ... Defn: (z^3 + z^2 + z + 1)*t^2 + (z^5 + z^4 + z^3 + 1)*t + z^4 + z^3 + z, ... Defn: (z^3 + z^2 + z + 1)*t^5 + (z^5 + z^4 + z^3)*t^4 + z^2*t^3 + (z^3 + 1)*t^2 + (z^5 + z^4 + z^2 + 1)*t + z^2] + sage: basis[2].ore_polynomial()*phi.gen() - psi.gen()*basis[2].ore_polynomial() + 0 + ALGORITHM: We return the basis of the kernel of a matrix derived from the @@ -597,10 +650,13 @@ def basis(self): # relation defining morphisms of Drinfeld modules # These are elements of K, expanded in terms of # K_basis. - c_tik = (dom_coeffs[i].frobenius(qorder*k)*K_basis[j] - - cod_coeffs[i]*K_basis[j].frobenius(qorder*i)) \ - .polynomial().coefficients(sparse=False) - c_tik += [0 for _ in range(n - len(c_tik))] + base_poly = K(dom_coeffs[i].frobenius(qorder*k)*K_basis[j] + - cod_coeffs[i]*K_basis[j].frobenius(qorder*i)) \ + .polynomial() + c_tik = [0 for _ in range(n+1)] + for mono, coeff in zip(base_poly.monomials(), + base_poly.coefficients()): + c_tik[mono.degree()] = coeff taudeg = i + k for b in range(n): sys[(taudeg % n)*n + b, k*n + j] += c_tik[b] * \ @@ -614,16 +670,17 @@ def basis(self): basis_poly = 0 for i in range(n): for j in range(n): - basis_poly += basis_vector[n*i + j].subs(tau**n)*K_basis[j]*tau**i + basis_poly += basis_vector[n*i + j] \ + .subs(tau**n)*K_basis[j]*tau**i basis.append(self(basis_poly)) return basis def motive_power_decomposition(self, upper): r""" - Computes coefficients a_0, .., a_{r-1} such that - \tau^t = a_{r-1}\tau^{r-1} + .... + a_{0} - with each a_i \in Fq[x] for each t < upper.. - """ + Computes coefficients a_0, .., a_{r-1} such that + \tau^t = a_{r-1}\tau^{r-1} + .... + a_{0} + with each a_i \in Fq[x] for each t < upper.. + """ domain = self.domain() r = domain.rank() A = domain.function_ring() @@ -644,7 +701,7 @@ def motive_power_decomposition(self, upper): def motive_basis(self): r""" - + """ domain, codomain = self.domain(), self.codomain() A = domain.function_ring() @@ -687,9 +744,12 @@ def _frobenius_matrix(self, order=1, K_basis=None): Fq = self.domain()._Fq K = self.domain().base_over_constants_field() n = K.degree(Fq) - frob = K.frobenius_endomorphism(order) + q = Fq.cardinality() + char = Fq.characteristic() + qorder = logb(q, char) + frob = K.frobenius_endomorphism(qorder*order) if K_basis is None: - K_basis = [K.gen()**i for i in range(n)] + K_basis = K.basis_over(Fq) pol_var = K_basis[0].polynomial().parent().gen() pol_ring = PolynomialRing(Fq, str(pol_var)) frob_matrix = Matrix(Fq, n, n) @@ -726,4 +786,4 @@ def random_element(self, degree, seed=None): """ set_random_seed(seed) return self(sum([self.domain()._Fq.random_element() - * elem.ore_polynomial() for elem in self.Fq_basis(degree)])) + * elem.ore_polynomial() for elem in self._Fq_basis(degree)])) From c2cfa250fe1e144bb24e22b7d12c72cf6169fcdc Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 6 Apr 2025 20:51:40 +0200 Subject: [PATCH 08/30] computation of a A-basis of Hom(phi, psi) --- conftest.py | 347 ------------------ .../function_field/drinfeld_modules/homset.py | 160 ++++++++ 2 files changed, 160 insertions(+), 347 deletions(-) delete mode 100644 conftest.py diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 5307d7f6233..00000000000 --- a/conftest.py +++ /dev/null @@ -1,347 +0,0 @@ -# pyright: strict -"""Configuration and fixtures for pytest. - -This file configures pytest and provides some global fixtures. -See https://docs.pytest.org/en/latest/index.html for more details. -""" - -from __future__ import annotations - -import doctest -import inspect -import sys -import warnings -from pathlib import Path -from typing import Any, Iterable, Optional - -import pytest -from _pytest.doctest import ( - DoctestItem, - DoctestModule, - _get_continue_on_failure, - _get_runner, - _is_mocked, - _patch_unwrap_mock_aware, - get_optionflags, -) -from _pytest.pathlib import ImportMode, import_path - -from sage.doctest.forker import ( - init_sage, - showwarning_with_traceback, -) -from sage.doctest.parsing import SageDocTestParser, SageOutputChecker - - -class SageDoctestModule(DoctestModule): - """ - This is essentially a copy of `DoctestModule` from - https://github.com/pytest-dev/pytest/blob/main/src/_pytest/doctest.py. - The only change is that we use `SageDocTestParser` to extract the doctests - and `SageOutputChecker` to verify the output. - """ - - def collect(self) -> Iterable[DoctestItem]: - import doctest - - class MockAwareDocTestFinder(doctest.DocTestFinder): - """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. - https://github.com/pytest-dev/pytest/issues/3456 - https://bugs.python.org/issue25532 - """ - - def __init__(self) -> None: - super().__init__(parser=SageDocTestParser(set(["sage"]))) - - def _find_lineno(self, obj, source_lines): - """Doctest code does not take into account `@property`, this - is a hackish way to fix it. https://bugs.python.org/issue17446 - Wrapped Doctests will need to be unwrapped so the correct - line number is returned. This will be reported upstream. #8796 - """ - if isinstance(obj, property): - obj = getattr(obj, "fget", obj) - - if hasattr(obj, "__wrapped__"): - # Get the main obj in case of it being wrapped - obj = inspect.unwrap(obj) - - # Type ignored because this is a private function. - return super()._find_lineno( # type:ignore[misc] - obj, - source_lines, - ) - - def _find( - self, tests, obj, name, module, source_lines, globs, seen - ) -> None: - if _is_mocked(obj): - return - with _patch_unwrap_mock_aware(): - # Type ignored because this is a private function. - super()._find( # type:ignore[misc] - tests, obj, name, module, source_lines, globs, seen - ) - - if self.path.name == "conftest.py": - module = self.config.pluginmanager._importconftest( - self.path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - consider_namespace_packages=True, - ) - else: - try: - module = import_path( - self.path, - mode=ImportMode.importlib, - root=self.config.rootpath, - consider_namespace_packages=True, - ) - except ImportError as exception: - if self.config.getvalue("doctest_ignore_import_errors"): - pytest.skip("unable to import module %r" % self.path) - else: - if isinstance(exception, ModuleNotFoundError): - # Ignore some missing features/modules for now - # TODO: Remove this once all optional things are using Features - if exception.name in ( - "valgrind", - "rpy2", - "sage.libs.coxeter3.coxeter", - ): - pytest.skip( - f"unable to import module { self.path } due to missing feature { exception.name }" - ) - raise - # Uses internal doctest module parsing mechanism. - finder = MockAwareDocTestFinder() - optionflags = get_optionflags(self.config) - from sage.features import FeatureNotPresentError - - runner = _get_runner( - verbose=False, - optionflags=optionflags, - checker=SageOutputChecker(), - continue_on_failure=_get_continue_on_failure(self.config), - ) - try: - for test in finder.find(module, module.__name__): - if test.examples: # skip empty doctests - yield DoctestItem.from_parent( - self, name=test.name, runner=runner, dtest=test - ) - except FeatureNotPresentError as exception: - pytest.skip( - f"unable to import module { self.path } due to missing feature { exception.feature.name }" - ) - except ModuleNotFoundError as exception: - # TODO: Remove this once all optional things are using Features - pytest.skip( - f"unable to import module { self.path } due to missing module { exception.name }" - ) - - -class IgnoreCollector(pytest.Collector): - """ - Ignore a file. - """ - - def __init__(self, parent: pytest.Collector) -> None: - super().__init__("ignore", parent) - - def collect(self) -> Iterable[pytest.Item | pytest.Collector]: - return [] - - -def pytest_collect_file( - file_path: Path, parent: pytest.Collector -) -> pytest.Collector | None: - """ - This hook is called when collecting test files, and can be used to - modify the file or test selection logic by returning a list of - ``pytest.Item`` objects which the ``pytest`` command will directly - add to the list of test items. - - See `pytest documentation `_. - """ - if ( - file_path.parent.name == "combinat" - or file_path.parent.parent.name == "combinat" - ): - # Crashes CI for some reason - return IgnoreCollector.from_parent(parent) - if file_path.suffix == ".pyx": - # We don't allow pytests to be defined in Cython files. - # Normally, Cython files are filtered out already by pytest and we only - # hit this here if someone explicitly runs `pytest some_file.pyx`. - return IgnoreCollector.from_parent(parent) - elif file_path.suffix == ".py": - if parent.config.option.doctest: - if file_path.name == "__main__.py" or file_path.name == "setup.py": - # We don't allow tests to be defined in __main__.py/setup.py files (because their import will fail). - return IgnoreCollector.from_parent(parent) - if ( - ( - file_path.name == "postprocess.py" - and file_path.parent.name == "nbconvert" - ) - or ( - file_path.name == "giacpy-mkkeywords.py" - and file_path.parent.name == "autogen" - ) - or ( - file_path.name == "flint_autogen.py" - and file_path.parent.name == "autogen" - ) - ): - # This is an executable file. - return IgnoreCollector.from_parent(parent) - - if file_path.name == "conftest_inputtest.py": - # This is an input file for testing the doctest machinery (and contains broken doctests). - return IgnoreCollector.from_parent(parent) - - if ( - ( - file_path.name == "finite_dimensional_lie_algebras_with_basis.py" - and file_path.parent.name == "categories" - ) - or ( - file_path.name == "__init__.py" - and file_path.parent.name == "crypto" - ) - or (file_path.name == "__init__.py" and file_path.parent.name == "mq") - ): - # TODO: Fix these (import fails with "RuntimeError: dictionary changed size during iteration") - return IgnoreCollector.from_parent(parent) - - if ( - file_path.name in ("forker.py", "reporting.py") - ) and file_path.parent.name == "doctest": - # Fails with many errors due to different testing framework - return IgnoreCollector.from_parent(parent) - - if ( - ( - file_path.name == "arithgroup_generic.py" - and file_path.parent.name == "arithgroup" - ) - or ( - file_path.name == "pari.py" - and file_path.parent.name == "lfunctions" - ) - or ( - file_path.name == "permgroup_named.py" - and file_path.parent.name == "perm_gps" - ) - or ( - file_path.name == "finitely_generated.py" - and file_path.parent.name == "matrix_gps" - ) - or ( - file_path.name == "libgap_mixin.py" - and file_path.parent.name == "groups" - ) - or ( - file_path.name == "finitely_presented.py" - and file_path.parent.name == "groups" - ) - or ( - file_path.name == "classical_geometries.py" - and file_path.parent.name == "generators" - ) - ): - # Fails with "Fatal Python error" - return IgnoreCollector.from_parent(parent) - - return SageDoctestModule.from_parent(parent, path=file_path) - - -def pytest_addoption(parser): - # Add a command line option to run doctests - # (we don't use the built-in --doctest-modules option because then doctests are collected twice) - group = parser.getgroup("collect") - group.addoption( - "--doctest", - action="store_true", - default=False, - help="Run doctests in all .py modules", - dest="doctest", - ) - - -# Monkey patch exception printing to replace the full qualified name of the exception by its short name -# TODO: Remove this hack once migration to pytest is complete -import traceback - -old_format_exception_only = traceback.format_exception_only - - -def format_exception_only(etype: type, value: BaseException) -> list[str]: - formatted_exception = old_format_exception_only(etype, value) - exception_name = etype.__name__ - if etype.__module__: - exception_full_name = etype.__module__ + "." + etype.__qualname__ - else: - exception_full_name = etype.__qualname__ - - for i, line in enumerate(formatted_exception): - if line.startswith(exception_full_name): - formatted_exception[i] = line.replace( - exception_full_name, exception_name, 1 - ) - return formatted_exception - - -# Initialize Sage-specific doctest stuff -init_sage() - -# Monkey patch doctest to use our custom printer etc -old_run = doctest.DocTestRunner.run - - -def doctest_run( - self: doctest.DocTestRunner, - test: doctest.DocTest, - compileflags: Optional[int] = None, - out: Any = None, - clear_globs: bool = True, -) -> doctest.TestResults: - from sage.repl.rich_output import get_display_manager - from sage.repl.user_globals import set_globals - - traceback.format_exception_only = format_exception_only - - # Display warnings in doctests - warnings.showwarning = showwarning_with_traceback - setattr(sys, "__displayhook__", get_display_manager().displayhook) - - # Ensure that injecting globals works as expected in doctests - set_globals(test.globs) - return old_run(self, test, compileflags, out, clear_globs) - - -doctest.DocTestRunner.run = doctest_run - - -@pytest.fixture(autouse=True, scope="session") -def add_imports(doctest_namespace: dict[str, Any]): - """ - Add global imports for doctests. - - See `pytest documentation `. - """ - # Inject sage.all into each doctest - import sage.repl.ipython_kernel.all_jupyter - - dict_all = sage.repl.ipython_kernel.all_jupyter.__dict__ - - # Remove '__package__' item from the globals since it is not - # always in the globals in an actual Sage session. - dict_all.pop("__package__", None) - - sage_namespace = dict(dict_all) - sage_namespace["__name__"] = "__main__" - - doctest_namespace.update(**sage_namespace) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index faf6b45c5b4..1624e740844 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -22,10 +22,13 @@ import operator +from sage.categories.finite_fields import FiniteFields from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.homset import Homset from sage.categories.action import Action from sage.misc.latex import latex +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.matrix.constructor import Matrix from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism @@ -417,3 +420,160 @@ def _element_constructor_(self, *args, **kwds): # would call __init__ instead of __classcall_private__. This # seems to work, but I don't know what I'm doing. return DrinfeldModuleMorphism(self, *args, **kwds) + + def basis(self): + r""" + Return a basis of this homset over the underlying + function ring `\mathbb F_q[T]`. + + ALGORITHM: + + The isogenies `u : \phi \to \psi' correspond to + the vectors `u \in M(\phi)` such that + + .. MATH:: + + \sum_{k=0}^r g_k \tau^k(u) = T u + + where the `g_k` are the coefficients of `\psi_T` + + The algorithm consists in solving the above system + viewed as a linear system over `\mathbb F_q[T]`. + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, 1, z]) + sage: End(phi).basis() + [Identity morphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z, + Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z + Defn: t^2, + Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z + Defn: 2*t^4 + z*t^3 + z] + + :: + + sage: psi = DrinfeldModule(A, [z, 3*z + 1, 2*z, 4*z + 1]) + sage: H = Hom(phi, psi) + sage: H.basis() + [Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*t^3 + t^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + Defn: t + 1, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*t^3 + t^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + Defn: (z + 4)*t^2 + 4*z*t + z + 4, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*t^3 + t^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + Defn: 3*t^3 + (z + 2)*t^2 + 4*z*t + z + 4] + + When `\phi` and `\psi` are not isogenous, an empty list is returned:: + + sage: psi = DrinfeldModule(A, [z, 3*z, 2*z, 4*z]) + sage: Hom(phi, psi).basis() + [] + + TESTS:: + + sage: K. = Frac(A) + sage: phi = DrinfeldModule(A, [T, 1]) + sage: End(phi).basis() + Traceback (most recent call last): + ... + NotImplementedError: computing basis of homsets are currently only implemented over finite fields + """ + if self.base() not in FiniteFields(): + raise NotImplementedError("computing basis of homsets are currently only implemented over finite fields") + phi = self.domain() + psi = self.codomain() + r = phi.rank() + if psi.rank() != r: + return [] + Fo = phi.base_over_constants_field() + Fq = Fo.base() + F = Fo.backend() + q = Fq.cardinality() + A = phi.function_ring() + T = A.gen() + + AF = PolynomialRing(F, name='T') + TF = AF.gen() + + Frob = lambda x: x**q + FrobT = lambda P: P.map_coefficients(Frob) + + phiT = phi.gen() + psiT = psi.gen() + + # We compute the tau^i in M(phi) + lc = ~(phiT[r]) + xT = [-AF(lc*phiT[i]) for i in range(r)] + xT[0] += lc*TF + taus = [] + for i in range(r): + taui = r * [AF.zero()] + taui[i] = AF.one() + taus.append(taui) + for i in range(r): + s = FrobT(taui[-1]) + taui = [s*xT[0]] + [FrobT(taui[j-1]) + s*xT[j] for j in range(1,r)] + taus.append(taui) + + # We precompute the Frob^k(z^i) + d = Fo.degree(Fq) + z = F(Fo.gen()) + zs = [] + zq = z + for k in range(r+1): + x = F.one() + for i in range(d): + zs.append(x) + x *= zq + zq = zq ** q + + # We compute the linear system to solve + rows = [] + for i in range(d): + for j in range(r): + # For x = z^i * tau^j, we compute + # sum(g_k*tau^k(x), k=0..r) - T*x + # = sum(g_k*Frob^k(z^i)*tau^(k+j), k=0..r) - T*x + row = r * [AF.zero()] + for k in range(r+1): + s = psiT[k] * zs[k*d + i] + for l in range(r): + row[l] += s*taus[k+j][l] + row[j] -= zs[i] * TF + # We write it in the A-basis + rowFq = [] + for c in row: + c0 = Fo(c[0]).vector() + c1 = Fo(c[1]).vector() + rowFq += [c0[k] + T*c1[k] for k in range(d)] + rows.append(rowFq) + M = Matrix(rows) + + # We solve the linear system + P, U = M.popov_form(transformation=True, include_zero_vectors=False) + if P.nrows() == r*d: + return [] + ker = U.submatrix(P.nrows()) + ker = ker.popov_form() # we try to minimize the output + + # We reconstruct the isogenies + us = [] + S = phi.ore_polring(); t = S.gen() + for row in range(ker.nrows()): + u = S.zero() + for i in range(d): + for j in range(r): + a = ker[row, i*r + j] + u += zs[i] * t**j * sum(a[k] * phiT**k for k in range(a.degree() + 1)) + us.append(u) + us.sort(key = lambda u: u.degree()) + + return [phi.hom(u) for u in us] From 6f52dc17e4befead246066c779946237a3075a74 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sun, 6 Apr 2025 21:15:48 +0200 Subject: [PATCH 09/30] conftest (grr) --- conftest.py | 347 ++++++++++++++++++ .../function_field/drinfeld_modules/homset.py | 2 +- 2 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000000..5307d7f6233 --- /dev/null +++ b/conftest.py @@ -0,0 +1,347 @@ +# pyright: strict +"""Configuration and fixtures for pytest. + +This file configures pytest and provides some global fixtures. +See https://docs.pytest.org/en/latest/index.html for more details. +""" + +from __future__ import annotations + +import doctest +import inspect +import sys +import warnings +from pathlib import Path +from typing import Any, Iterable, Optional + +import pytest +from _pytest.doctest import ( + DoctestItem, + DoctestModule, + _get_continue_on_failure, + _get_runner, + _is_mocked, + _patch_unwrap_mock_aware, + get_optionflags, +) +from _pytest.pathlib import ImportMode, import_path + +from sage.doctest.forker import ( + init_sage, + showwarning_with_traceback, +) +from sage.doctest.parsing import SageDocTestParser, SageOutputChecker + + +class SageDoctestModule(DoctestModule): + """ + This is essentially a copy of `DoctestModule` from + https://github.com/pytest-dev/pytest/blob/main/src/_pytest/doctest.py. + The only change is that we use `SageDocTestParser` to extract the doctests + and `SageOutputChecker` to verify the output. + """ + + def collect(self) -> Iterable[DoctestItem]: + import doctest + + class MockAwareDocTestFinder(doctest.DocTestFinder): + """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. + https://github.com/pytest-dev/pytest/issues/3456 + https://bugs.python.org/issue25532 + """ + + def __init__(self) -> None: + super().__init__(parser=SageDocTestParser(set(["sage"]))) + + def _find_lineno(self, obj, source_lines): + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. https://bugs.python.org/issue17446 + Wrapped Doctests will need to be unwrapped so the correct + line number is returned. This will be reported upstream. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + + if hasattr(obj, "__wrapped__"): + # Get the main obj in case of it being wrapped + obj = inspect.unwrap(obj) + + # Type ignored because this is a private function. + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, + ) + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + if _is_mocked(obj): + return + with _patch_unwrap_mock_aware(): + # Type ignored because this is a private function. + super()._find( # type:ignore[misc] + tests, obj, name, module, source_lines, globs, seen + ) + + if self.path.name == "conftest.py": + module = self.config.pluginmanager._importconftest( + self.path, + self.config.getoption("importmode"), + rootpath=self.config.rootpath, + consider_namespace_packages=True, + ) + else: + try: + module = import_path( + self.path, + mode=ImportMode.importlib, + root=self.config.rootpath, + consider_namespace_packages=True, + ) + except ImportError as exception: + if self.config.getvalue("doctest_ignore_import_errors"): + pytest.skip("unable to import module %r" % self.path) + else: + if isinstance(exception, ModuleNotFoundError): + # Ignore some missing features/modules for now + # TODO: Remove this once all optional things are using Features + if exception.name in ( + "valgrind", + "rpy2", + "sage.libs.coxeter3.coxeter", + ): + pytest.skip( + f"unable to import module { self.path } due to missing feature { exception.name }" + ) + raise + # Uses internal doctest module parsing mechanism. + finder = MockAwareDocTestFinder() + optionflags = get_optionflags(self.config) + from sage.features import FeatureNotPresentError + + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=SageOutputChecker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + try: + for test in finder.find(module, module.__name__): + if test.examples: # skip empty doctests + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + except FeatureNotPresentError as exception: + pytest.skip( + f"unable to import module { self.path } due to missing feature { exception.feature.name }" + ) + except ModuleNotFoundError as exception: + # TODO: Remove this once all optional things are using Features + pytest.skip( + f"unable to import module { self.path } due to missing module { exception.name }" + ) + + +class IgnoreCollector(pytest.Collector): + """ + Ignore a file. + """ + + def __init__(self, parent: pytest.Collector) -> None: + super().__init__("ignore", parent) + + def collect(self) -> Iterable[pytest.Item | pytest.Collector]: + return [] + + +def pytest_collect_file( + file_path: Path, parent: pytest.Collector +) -> pytest.Collector | None: + """ + This hook is called when collecting test files, and can be used to + modify the file or test selection logic by returning a list of + ``pytest.Item`` objects which the ``pytest`` command will directly + add to the list of test items. + + See `pytest documentation `_. + """ + if ( + file_path.parent.name == "combinat" + or file_path.parent.parent.name == "combinat" + ): + # Crashes CI for some reason + return IgnoreCollector.from_parent(parent) + if file_path.suffix == ".pyx": + # We don't allow pytests to be defined in Cython files. + # Normally, Cython files are filtered out already by pytest and we only + # hit this here if someone explicitly runs `pytest some_file.pyx`. + return IgnoreCollector.from_parent(parent) + elif file_path.suffix == ".py": + if parent.config.option.doctest: + if file_path.name == "__main__.py" or file_path.name == "setup.py": + # We don't allow tests to be defined in __main__.py/setup.py files (because their import will fail). + return IgnoreCollector.from_parent(parent) + if ( + ( + file_path.name == "postprocess.py" + and file_path.parent.name == "nbconvert" + ) + or ( + file_path.name == "giacpy-mkkeywords.py" + and file_path.parent.name == "autogen" + ) + or ( + file_path.name == "flint_autogen.py" + and file_path.parent.name == "autogen" + ) + ): + # This is an executable file. + return IgnoreCollector.from_parent(parent) + + if file_path.name == "conftest_inputtest.py": + # This is an input file for testing the doctest machinery (and contains broken doctests). + return IgnoreCollector.from_parent(parent) + + if ( + ( + file_path.name == "finite_dimensional_lie_algebras_with_basis.py" + and file_path.parent.name == "categories" + ) + or ( + file_path.name == "__init__.py" + and file_path.parent.name == "crypto" + ) + or (file_path.name == "__init__.py" and file_path.parent.name == "mq") + ): + # TODO: Fix these (import fails with "RuntimeError: dictionary changed size during iteration") + return IgnoreCollector.from_parent(parent) + + if ( + file_path.name in ("forker.py", "reporting.py") + ) and file_path.parent.name == "doctest": + # Fails with many errors due to different testing framework + return IgnoreCollector.from_parent(parent) + + if ( + ( + file_path.name == "arithgroup_generic.py" + and file_path.parent.name == "arithgroup" + ) + or ( + file_path.name == "pari.py" + and file_path.parent.name == "lfunctions" + ) + or ( + file_path.name == "permgroup_named.py" + and file_path.parent.name == "perm_gps" + ) + or ( + file_path.name == "finitely_generated.py" + and file_path.parent.name == "matrix_gps" + ) + or ( + file_path.name == "libgap_mixin.py" + and file_path.parent.name == "groups" + ) + or ( + file_path.name == "finitely_presented.py" + and file_path.parent.name == "groups" + ) + or ( + file_path.name == "classical_geometries.py" + and file_path.parent.name == "generators" + ) + ): + # Fails with "Fatal Python error" + return IgnoreCollector.from_parent(parent) + + return SageDoctestModule.from_parent(parent, path=file_path) + + +def pytest_addoption(parser): + # Add a command line option to run doctests + # (we don't use the built-in --doctest-modules option because then doctests are collected twice) + group = parser.getgroup("collect") + group.addoption( + "--doctest", + action="store_true", + default=False, + help="Run doctests in all .py modules", + dest="doctest", + ) + + +# Monkey patch exception printing to replace the full qualified name of the exception by its short name +# TODO: Remove this hack once migration to pytest is complete +import traceback + +old_format_exception_only = traceback.format_exception_only + + +def format_exception_only(etype: type, value: BaseException) -> list[str]: + formatted_exception = old_format_exception_only(etype, value) + exception_name = etype.__name__ + if etype.__module__: + exception_full_name = etype.__module__ + "." + etype.__qualname__ + else: + exception_full_name = etype.__qualname__ + + for i, line in enumerate(formatted_exception): + if line.startswith(exception_full_name): + formatted_exception[i] = line.replace( + exception_full_name, exception_name, 1 + ) + return formatted_exception + + +# Initialize Sage-specific doctest stuff +init_sage() + +# Monkey patch doctest to use our custom printer etc +old_run = doctest.DocTestRunner.run + + +def doctest_run( + self: doctest.DocTestRunner, + test: doctest.DocTest, + compileflags: Optional[int] = None, + out: Any = None, + clear_globs: bool = True, +) -> doctest.TestResults: + from sage.repl.rich_output import get_display_manager + from sage.repl.user_globals import set_globals + + traceback.format_exception_only = format_exception_only + + # Display warnings in doctests + warnings.showwarning = showwarning_with_traceback + setattr(sys, "__displayhook__", get_display_manager().displayhook) + + # Ensure that injecting globals works as expected in doctests + set_globals(test.globs) + return old_run(self, test, compileflags, out, clear_globs) + + +doctest.DocTestRunner.run = doctest_run + + +@pytest.fixture(autouse=True, scope="session") +def add_imports(doctest_namespace: dict[str, Any]): + """ + Add global imports for doctests. + + See `pytest documentation `. + """ + # Inject sage.all into each doctest + import sage.repl.ipython_kernel.all_jupyter + + dict_all = sage.repl.ipython_kernel.all_jupyter.__dict__ + + # Remove '__package__' item from the globals since it is not + # always in the globals in an actual Sage session. + dict_all.pop("__package__", None) + + sage_namespace = dict(dict_all) + sage_namespace["__name__"] = "__main__" + + doctest_namespace.update(**sage_namespace) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 1624e740844..703c58c20e2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -576,4 +576,4 @@ def basis(self): us.append(u) us.sort(key = lambda u: u.degree()) - return [phi.hom(u) for u in us] + return [self(u) for u in us] From bef913d22e62a4ef47f0ad62b196e2bbd5890ad5 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Mon, 7 Apr 2025 09:41:10 +0200 Subject: [PATCH 10/30] use cache --- .../function_field/drinfeld_modules/homset.py | 134 +++++++++--------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 703c58c20e2..ab37f031b66 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -282,6 +282,7 @@ def __init__(self, X, Y, category=None, check=True): self.register_action(DrinfeldModuleMorphismAction(A, self, False, operator.mul)) if X is Y: self.register_coercion(A) + self._basis = None def _latex_(self): r""" @@ -465,11 +466,11 @@ def basis(self): Drinfeld Module morphism: From: Drinfeld module defined by T |--> z*t^3 + t^2 + z To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: (z + 4)*t^2 + 4*z*t + z + 4, + Defn: 3*t^3 + (z + 2)*t^2 + 4*z*t + z + 4, Drinfeld Module morphism: From: Drinfeld module defined by T |--> z*t^3 + t^2 + z To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: 3*t^3 + (z + 2)*t^2 + 4*z*t + z + 4] + Defn: (z + 4)*t^2 + 4*z*t + z + 4] When `\phi` and `\psi` are not isogenous, an empty list is returned:: @@ -500,80 +501,77 @@ def basis(self): A = phi.function_ring() T = A.gen() - AF = PolynomialRing(F, name='T') - TF = AF.gen() - - Frob = lambda x: x**q - FrobT = lambda P: P.map_coefficients(Frob) - - phiT = phi.gen() - psiT = psi.gen() - - # We compute the tau^i in M(phi) - lc = ~(phiT[r]) - xT = [-AF(lc*phiT[i]) for i in range(r)] - xT[0] += lc*TF - taus = [] - for i in range(r): - taui = r * [AF.zero()] - taui[i] = AF.one() - taus.append(taui) - for i in range(r): - s = FrobT(taui[-1]) - taui = [s*xT[0]] + [FrobT(taui[j-1]) + s*xT[j] for j in range(1,r)] - taus.append(taui) - - # We precompute the Frob^k(z^i) - d = Fo.degree(Fq) - z = F(Fo.gen()) - zs = [] - zq = z - for k in range(r+1): - x = F.one() + if self._basis is None: + AF = PolynomialRing(F, name='T') + TF = AF.gen() + + Frob = lambda x: x**q + FrobT = lambda P: P.map_coefficients(Frob) + + phiT = phi.gen() + psiT = psi.gen() + + # We compute the tau^i in M(phi) + lc = ~(phiT[r]) + xT = [-AF(lc*phiT[i]) for i in range(r)] + xT[0] += lc*TF + taus = [] + for i in range(r): + taui = r * [AF.zero()] + taui[i] = AF.one() + taus.append(taui) + for i in range(r): + s = FrobT(taui[-1]) + taui = [s*xT[0]] + [FrobT(taui[j-1]) + s*xT[j] for j in range(1,r)] + taus.append(taui) + + # We precompute the Frob^k(z^i) + d = Fo.degree(Fq) + z = F(Fo.gen()) + zs = [] + zq = z + for k in range(r+1): + x = F.one() + for i in range(d): + zs.append(x) + x *= zq + zq = zq ** q + + # We compute the linear system to solve + rows = [] for i in range(d): - zs.append(x) - x *= zq - zq = zq ** q - - # We compute the linear system to solve - rows = [] - for i in range(d): - for j in range(r): - # For x = z^i * tau^j, we compute - # sum(g_k*tau^k(x), k=0..r) - T*x - # = sum(g_k*Frob^k(z^i)*tau^(k+j), k=0..r) - T*x - row = r * [AF.zero()] - for k in range(r+1): - s = psiT[k] * zs[k*d + i] - for l in range(r): - row[l] += s*taus[k+j][l] - row[j] -= zs[i] * TF - # We write it in the A-basis - rowFq = [] - for c in row: - c0 = Fo(c[0]).vector() - c1 = Fo(c[1]).vector() - rowFq += [c0[k] + T*c1[k] for k in range(d)] - rows.append(rowFq) - M = Matrix(rows) - - # We solve the linear system - P, U = M.popov_form(transformation=True, include_zero_vectors=False) - if P.nrows() == r*d: - return [] - ker = U.submatrix(P.nrows()) - ker = ker.popov_form() # we try to minimize the output + for j in range(r): + # For x = z^i * tau^j, we compute + # sum(g_k*tau^k(x), k=0..r) - T*x + # = sum(g_k*Frob^k(z^i)*tau^(k+j), k=0..r) - T*x + row = r * [AF.zero()] + for k in range(r+1): + s = psiT[k] * zs[k*d + i] + for l in range(r): + row[l] += s*taus[k+j][l] + row[j] -= zs[i] * TF + # We write it in the A-basis + rowFq = [] + for c in row: + c0 = Fo(c[0]).vector() + c1 = Fo(c[1]).vector() + rowFq += [c0[k] + T*c1[k] for k in range(d)] + rows.append(rowFq) + M = Matrix(rows) + + # We solve the linear system + self._basis = M.minimal_kernel_basis() # We reconstruct the isogenies - us = [] + isogenies = [] S = phi.ore_polring(); t = S.gen() + ker = self._basis for row in range(ker.nrows()): u = S.zero() for i in range(d): for j in range(r): a = ker[row, i*r + j] u += zs[i] * t**j * sum(a[k] * phiT**k for k in range(a.degree() + 1)) - us.append(u) - us.sort(key = lambda u: u.degree()) + isogenies.append(self(u)) - return [self(u) for u in us] + return isogenies From 7c1a26ba86f74aad70668ecb174ee986d96afc60 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 17 Jul 2025 11:18:34 +0200 Subject: [PATCH 11/30] =?UTF-8?q?t=20->=20=CF=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/categories/drinfeld_modules.py | 23 +-- .../function_field/drinfeld_modules/action.py | 6 +- .../charzero_drinfeld_module.py | 10 +- .../drinfeld_modules/drinfeld_module.py | 126 +++++++-------- .../finite_drinfeld_module.py | 28 ++-- .../function_field/drinfeld_modules/homset.py | 92 +++++------ .../drinfeld_modules/morphism.py | 144 +++++++++--------- 7 files changed, 215 insertions(+), 214 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 95b7f67c8e0..aa80f8cc3b8 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage_setup: distribution = sagemath-categories # sage.doctest: needs sage.rings.finite_rings r""" @@ -123,7 +124,7 @@ class DrinfeldModules(Category_over_base_ring): True sage: C.ore_polring() - Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 + Ore Polynomial Ring in τ over Finite Field in z of size 11^4 twisted by z |--> z^11 sage: C.ore_polring() is phi.ore_polring() True @@ -135,7 +136,7 @@ class DrinfeldModules(Category_over_base_ring): sage: psi = C.object([p_root, 1]) sage: psi - Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 + Drinfeld module defined by T |--> τ + z^3 + 7*z^2 + 6*z + 10 sage: psi.category() is C True @@ -207,7 +208,7 @@ class DrinfeldModules(Category_over_base_ring): TypeError: function ring base must be a finite field """ - def __init__(self, base_morphism, name='t'): + def __init__(self, base_morphism, name='τ'): r""" Initialize ``self``. @@ -216,7 +217,7 @@ def __init__(self, base_morphism, name='t'): - ``base_field`` -- the base field, which is a ring extension over a base - - ``name`` -- (default: ``'t'``) the name of the Ore polynomial + - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial variable TESTS:: @@ -227,7 +228,7 @@ def __init__(self, base_morphism, name='t'): sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: C = phi.category() - sage: ore_polring. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: ore_polring.<τ> = OrePolynomialRing(K, K.frobenius_endomorphism()) sage: C._ore_polring is ore_polring True sage: C._function_ring is A @@ -507,7 +508,7 @@ def object(self, gen): sage: phi = C.object([p_root, 0, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 + Drinfeld module defined by T |--> τ^2 + z^3 + 7*z^2 + 6*z + 10 sage: t = phi.ore_polring().gen() sage: C.object(t^2 + z^3 + 7*z^2 + 6*z + 10) is phi True @@ -534,7 +535,7 @@ def ore_polring(self): sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: C = phi.category() sage: C.ore_polring() - Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 + Ore Polynomial Ring in τ over Finite Field in z of size 11^4 twisted by z |--> z^11 """ return self._ore_polring @@ -770,7 +771,7 @@ def constant_coefficient(self): sage: t = phi.ore_polring().gen() sage: psi = C.object(phi.constant_coefficient() + t^3) sage: psi - Drinfeld module defined by T |--> t^3 + 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 + Drinfeld module defined by T |--> τ^3 + 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 Reciprocally, it is impossible to create two Drinfeld modules in this category if they do not share the same constant @@ -796,7 +797,7 @@ def ore_polring(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: S = phi.ore_polring() sage: S - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + Ore Polynomial Ring in τ over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) The Ore polynomial ring can also be retrieved from the category of the Drinfeld module:: @@ -825,8 +826,8 @@ def ore_variable(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.ore_polring() - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + Ore Polynomial Ring in τ over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) sage: phi.ore_variable() - t + τ """ return self.category().ore_polring().gen() diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 4962162424e..06f8897326d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -60,7 +60,7 @@ class DrinfeldModuleAction(Action): sage: action = phi.action() sage: action Action on Finite Field in z of size 11^2 over its base - induced by Drinfeld module defined by T |--> t^3 + z + induced by Drinfeld module defined by T |--> τ^3 + z The action on elements is computed as follows:: @@ -154,7 +154,7 @@ def _latex_(self): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: latex(action) - \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\phi: T \mapsto t^{3} + z + \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\phi: T \mapsto τ^{3} + z """ return f'\\text{{Action{{ }}on{{ }}}}' \ f'{latex(self._base)}\\text{{{{ }}' \ @@ -174,7 +174,7 @@ def _repr_(self): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> τ^3 + z """ return f'Action on {self._base} induced by ' \ f'{self._drinfeld_module}' diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 594f8645ae1..99dd8dc9ce5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -60,7 +60,7 @@ class DrinfeldModule_charzero(DrinfeldModule): sage: K. = Frac(A) sage: phi = DrinfeldModule(A, [T, 1]) sage: phi - Drinfeld module defined by T |--> t + T + Drinfeld module defined by T |--> τ + T :: @@ -110,7 +110,7 @@ class DrinfeldModule_charzero(DrinfeldModule): sage: L. = LaurentSeriesRing(GF(2)) # s = 1/T sage: phi = DrinfeldModule(A, [1/s, s + s^2 + s^5 + O(s^6), 1+1/s]) sage: phi(T) - (s^-1 + 1)*t^2 + (s + s^2 + s^5 + O(s^6))*t + s^-1 + (s^-1 + 1)*τ^2 + (s + s^2 + s^5 + O(s^6))*τ + s^-1 One can also construct Drinfeld modules over SageMath's global function fields:: @@ -119,7 +119,7 @@ class DrinfeldModule_charzero(DrinfeldModule): sage: K. = FunctionField(GF(5)) # z = T sage: phi = DrinfeldModule(A, [z, 1, z^2]) sage: phi(T) - z^2*t^2 + t + z + z^2*τ^2 + τ + z """ @cached_method def _compute_coefficient_exp(self, k): @@ -462,7 +462,7 @@ class DrinfeldModule_rational(DrinfeldModule_charzero): sage: A = Fq['T'] sage: K. = Frac(A) sage: C = DrinfeldModule(A, [T, 1]); C - Drinfeld module defined by T |--> t + T + Drinfeld module defined by T |--> τ + T sage: type(C) """ @@ -573,7 +573,7 @@ def class_polynomial(self): sage: A = Fq['T'] sage: K. = Frac(A) sage: C = DrinfeldModule(A, [T, 1]); C - Drinfeld module defined by T |--> t + T + Drinfeld module defined by T |--> τ + T sage: C.class_polynomial() 1 diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 6c2f2450118..4d26afc2abb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -90,7 +90,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z, 4, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + 4*t + z + Drinfeld module defined by T |--> τ^2 + 4*τ + z :: @@ -99,7 +99,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K = Frac(A) sage: psi = DrinfeldModule(A, [K(T), T+1]) sage: psi - Drinfeld module defined by T |--> (T + 1)*t + T + Drinfeld module defined by T |--> (T + 1)*τ + T .. NOTE:: @@ -140,7 +140,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z, 1, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + t + z + Drinfeld module defined by T |--> τ^2 + τ + z .. NOTE:: @@ -153,7 +153,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): 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 + Drinfeld module defined by T |--> (T^3 + T + 1)*τ^2 + τ + T :: @@ -163,15 +163,15 @@ class DrinfeldModule(Parent, UniqueRepresentation): 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 + represent the generator `\phi_T = z + τ + τ^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: tau = ore_polring.gen() + sage: rho_T = z + tau^3 sage: rho = DrinfeldModule(A, rho_T) sage: rho - Drinfeld module defined by T |--> t^3 + z + Drinfeld module defined by T |--> τ^3 + z sage: rho(T) == rho_T True @@ -179,12 +179,12 @@ class DrinfeldModule(Parent, UniqueRepresentation): object:: sage: phi(T) # phi_T, the generator of the Drinfeld module - t^2 + t + z + τ^2 + τ + 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 + τ^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*τ^4 + + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*τ^3 + + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*τ^2 + + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*τ + z^3 + z + 1 sage: phi(1) # phi_1 1 @@ -204,7 +204,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: cat = phi.category() sage: cat.object([z, 0, 0, 1]) - Drinfeld module defined by T |--> t^3 + z + Drinfeld module defined by T |--> τ^3 + z .. RUBRIC:: The base field of a Drinfeld module @@ -243,7 +243,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): :: sage: phi.ore_polring() # K{t} - Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + Ore Polynomial Ring in τ over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) :: @@ -253,7 +253,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): :: sage: phi.gen() # phi_T - t^2 + t + z + τ^2 + τ + z sage: phi.gen() == phi(T) True @@ -267,9 +267,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): 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 + To: Ore Polynomial Ring in τ over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) - Defn: T |--> t^2 + t + z + Defn: T |--> τ^2 + τ + z One can compute the rank and height:: @@ -309,9 +309,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi(T) in Hom(phi, phi) True - sage: t^6 in Hom(phi, phi) + sage: tau^6 in Hom(phi, phi) True - sage: t^5 + 2*t^3 + 1 in Hom(phi, phi) + sage: tau^5 + 2*tau^3 + 1 in Hom(phi, phi) False sage: 1 in Hom(phi, rho) False @@ -324,23 +324,23 @@ class DrinfeldModule(Parent, UniqueRepresentation): homset (``hom``):: sage: hom = Hom(phi, phi) - sage: frobenius_endomorphism = hom(t^6) + sage: frobenius_endomorphism = hom(tau^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 + Endomorphism of Drinfeld module defined by T |--> τ^2 + τ + z + Defn: τ^6 sage: identity_morphism - Identity morphism of Drinfeld module defined by T |--> t^2 + t + z + Identity morphism of Drinfeld module defined by T |--> τ^2 + τ + z sage: zero_morphism - Endomorphism of Drinfeld module defined by T |--> t^2 + t + z + Endomorphism of Drinfeld module defined by T |--> τ^2 + τ + z Defn: 0 The underlying Ore polynomial is retrieved with the method :meth:`ore_polynomial`:: sage: frobenius_endomorphism.ore_polynomial() - t^6 + τ^6 sage: identity_morphism.ore_polynomial() 1 @@ -366,11 +366,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): 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: P = (2*z^6 + z^3 + 2*z^2 + z + 2)*tau + 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 + Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*τ^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)*τ + z sage: P in Hom(phi, psi) True sage: P * phi(T) == psi(T) * P @@ -382,7 +382,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): Traceback (most recent call last): ... ValueError: the input does not define an isogeny - sage: phi.velu(t) + sage: phi.velu(tau) Traceback (most recent call last): ... ValueError: the input does not define an isogeny @@ -403,7 +403,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): 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 + induced by Drinfeld module defined by T |--> τ^2 + τ + z The action on elements is computed by calling the action object:: @@ -520,7 +520,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): """ @staticmethod - def __classcall_private__(cls, function_ring, gen, name='t'): + def __classcall_private__(cls, function_ring, gen, name='τ'): """ Check input validity and return a ``DrinfeldModule`` or ``DrinfeldModule_finite`` object accordingly. @@ -533,7 +533,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): - ``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 + - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial ring gen OUTPUT: a DrinfeldModule or DrinfeldModule_finite @@ -774,7 +774,7 @@ def _latex_(self): 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: latex(phi) - \phi: T \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12} + \phi: T \mapsto z_{12}^{5} τ^{2} + z_{12}^{3} τ + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12} :: @@ -801,7 +801,7 @@ def _repr_(self): 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 - 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 + Drinfeld module defined by T |--> z12^5*τ^2 + z12^3*τ + 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 """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ f'|--> {self._gen}' @@ -868,7 +868,7 @@ def action(self): 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 + induced by Drinfeld module defined by T |--> z12^5*τ^2 + z12^3*τ + 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:: @@ -1301,7 +1301,7 @@ def is_isomorphic(self, other, absolutely=False): sage: A. = Fq[] sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) - sage: t = phi.ore_variable() + sage: tau = phi.ore_variable() We create a second Drinfeld module, which is isomorphic to `\phi` and then check that they are indeed isomorphic:: @@ -1313,7 +1313,7 @@ def is_isomorphic(self, other, absolutely=False): In the example below, `\phi` and `\psi` are isogenous but not isomorphic:: - sage: psi = phi.velu(t + 1) + sage: psi = phi.velu(tau + 1) sage: phi.is_isomorphic(psi) False @@ -1783,9 +1783,9 @@ def morphism(self): 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 + To: Ore Polynomial Ring in τ over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) - Defn: T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + Defn: T |--> z12^5*τ^2 + z12^3*τ + 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) @@ -1809,7 +1809,7 @@ class the ``__call__`` method of this morphism:: 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^5*τ^2 + z12^3*τ + 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 @@ -1899,8 +1899,8 @@ def velu(self, isog): 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 + (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*τ^2 + + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*τ + 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 @@ -1963,7 +1963,7 @@ def hom(self, x, codomain=None): sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) sage: phi - Drinfeld module defined by T |--> z*t^3 + t^2 + z + Drinfeld module defined by T |--> z*τ^3 + τ^2 + z An important class of endomorphisms of a Drinfeld module `\phi` is given by scalar multiplications, that are endomorphisms @@ -1971,37 +1971,37 @@ def hom(self, x, codomain=None): 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 + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z*τ^3 + τ^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 + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z^2*τ^6 + (3*z^2 + z + 1)*τ^5 + τ^4 + 2*z^2*τ^3 + (3*z^2 + z + 1)*τ^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 + sage: tau = phi.ore_variable() + sage: phi.hom(tau^3) + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: τ^3 If the input Ore polynomial defines a morphism to another Drinfeld module, the latter is determined automatically:: - sage: phi.hom(t + 1) + sage: phi.hom(tau + 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 + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + Defn: τ + 1 TESTS:: - sage: phi.hom(t) + sage: phi.hom(tau) Traceback (most recent call last): ... ValueError: the input does not define an isogeny @@ -2015,7 +2015,7 @@ def hom(self, x, codomain=None): :: - sage: phi.hom(t + 1, codomain=phi) + sage: phi.hom(tau + 1, codomain=phi) Traceback (most recent call last): ... ValueError: Ore polynomial does not define a morphism @@ -2062,16 +2062,16 @@ def scalar_multiplication(self, x): sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) sage: phi - Drinfeld module defined by T |--> z*t^3 + t^2 + z + Drinfeld module defined by T |--> z*τ^3 + τ^2 + z sage: phi.hom(T) # indirect doctest - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: z*t^3 + t^2 + z + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z*τ^3 + τ^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 + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z^2*τ^6 + (3*z^2 + z + 1)*τ^5 + τ^4 + 2*z^2*τ^3 + (3*z^2 + z + 1)*τ^2 + z^2 + 1 """ if not self.function_ring().has_coerce_map_from(x.parent()): raise ValueError("%s is not element of the function ring" % x) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 38c93a70106..685d5b5c465 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -57,7 +57,7 @@ class DrinfeldModule_finite(DrinfeldModule): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, 0, 5]) sage: phi - Drinfeld module defined by T |--> 5*t^2 + z6 + Drinfeld module defined by T |--> 5*τ^2 + z6 :: @@ -84,8 +84,8 @@ class DrinfeldModule_finite(DrinfeldModule): sage: frobenius_endomorphism = phi.frobenius_endomorphism() sage: frobenius_endomorphism - Endomorphism of Drinfeld module defined by T |--> 5*t^2 + z6 - Defn: t^2 + Endomorphism of Drinfeld module defined by T |--> 5*τ^2 + z6 + Defn: τ^2 Its characteristic polynomial can be computed:: @@ -235,7 +235,7 @@ def frobenius_endomorphism(self): Let `q` be the order of the base field of the function ring. The *Frobenius endomorphism* is defined as the endomorphism whose - defining Ore polynomial is `t^q`. + defining Ore polynomial is `τ^q`. EXAMPLES:: @@ -244,8 +244,8 @@ def frobenius_endomorphism(self): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: phi.frobenius_endomorphism() - Endomorphism of Drinfeld module defined by T |--> z6*t^2 + 1 - Defn: t^2 + Endomorphism of Drinfeld module defined by T |--> z6*τ^2 + 1 + Defn: τ^2 TESTS:: @@ -273,14 +273,14 @@ def frobenius_charpoly(self, var='X', algorithm=None): Let `\chi = X^r + \sum_{i=0}^{r-1} A_{i}(T)X^{i}` be the characteristic polynomial of the Frobenius endomorphism, and - let `t^n` be the Ore polynomial that defines the Frobenius + let `τ^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by definition, `n` is the degree of `K` over the base field `\mathbb{F}_q`. Then we have .. MATH:: - \chi(t^n)(\phi(T)) - = t^{nr} + \sum_{i=1}^{r} \phi_{A_{i}}t^{n(i)} + \chi(τ^n)(\phi(T)) + = τ^{nr} + \sum_{i=1}^{r} \phi_{A_{i}}τ^{n(i)} = 0, with `\deg(A_i) \leq \frac{n(r-i)}{r}`. @@ -340,7 +340,7 @@ def frobenius_charpoly(self, var='X', algorithm=None): sage: chi(frob_pol, phi(T)) 0 sage: phi.frobenius_charpoly(algorithm='motive')(phi.frobenius_endomorphism()) - Endomorphism of Drinfeld module defined by T |--> z6*t^2 + 1 + Endomorphism of Drinfeld module defined by T |--> z6*τ^2 + 1 Defn: 0 :: @@ -935,15 +935,15 @@ def invert(self, ore_pol): When the input is not in the image of the Drinfeld module, an exception is raised:: - sage: t = phi.ore_polring().gen() - sage: phi.invert(t + 1) + sage: tau = phi.ore_variable() + sage: phi.invert(tau + 1) Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module :: - sage: phi.invert(t^4 + t^2 + 1) + sage: phi.invert(tau^4 + tau^2 + 1) Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module @@ -1091,7 +1091,7 @@ def is_supersingular(self): sage: phi.is_supersingular() True sage: phi(phi.characteristic()) # Purely inseparable - z6*t^2 + z6*τ^2 In rank two, a Drinfeld module is either ordinary or supersinguler. In higher ranks, it could be neither of diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index faf6b45c5b4..7dac2d93194 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -41,16 +41,16 @@ class DrinfeldModuleMorphismAction(Action): sage: phi = DrinfeldModule(A, [z, 1, z]) sage: psi = DrinfeldModule(A, [z, z^2 + 4*z + 3, 2*z^2 + 4*z + 4]) sage: H = Hom(phi, psi) - sage: t = phi.ore_variable() - sage: f = H(t + 2) + sage: tau = phi.ore_variable() + sage: f = H(tau + 2) Left action:: sage: (T + 1) * f Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^2 + t + z - To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*t^2 + (z^2 + 4*z + 3)*t + z - Defn: (2*z^2 + 4*z + 4)*t^3 + (2*z + 1)*t^2 + (2*z^2 + 4*z + 2)*t + 2*z + 2 + From: Drinfeld module defined by T |--> z*τ^2 + τ + z + To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^2 + (z^2 + 4*z + 3)*τ + z + Defn: (2*z^2 + 4*z + 4)*τ^3 + (2*z + 1)*τ^2 + (2*z^2 + 4*z + 2)*τ + 2*z + 2 Right action currently does not work (it is a known bug, due to an incompatibility between multiplication of morphisms and the coercion @@ -60,9 +60,9 @@ class DrinfeldModuleMorphismAction(Action): Traceback (most recent call last): ... TypeError: right (=T + 1) must be a map to multiply it by Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^2 + t + z - To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*t^2 + (z^2 + 4*z + 3)*t + z - Defn: t + 2 + From: Drinfeld module defined by T |--> z*τ^2 + τ + z + To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^2 + (z^2 + 4*z + 3)*τ + z + Defn: τ + 2 """ def __init__(self, A, H, is_left, op): r""" @@ -114,9 +114,9 @@ def _act_(self, a, f): sage: f = phi.hom(t + 1) sage: T*f # indirect doctest 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: (2*z^2 + 4*z + 4)*t^4 + (z + 1)*t^3 + t^2 + (2*z^2 + 4*z + 4)*t + z + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + Defn: (2*z^2 + 4*z + 4)*τ^4 + (z + 1)*τ^3 + τ^2 + (2*z^2 + 4*z + 4)*τ + z """ u = f.ore_polynomial() if self._is_left: @@ -147,8 +147,8 @@ class DrinfeldModuleHomset(Homset): sage: H = Hom(phi, psi) sage: H Set of Drinfeld module morphisms - from (gen) 2*t^2 + z6*t + z6 - to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + from (gen) 2*τ^2 + z6*τ + z6 + to (gen) 2*τ^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*τ + z6 :: @@ -160,7 +160,7 @@ class DrinfeldModuleHomset(Homset): sage: E = End(phi) sage: E - Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + z6*t + z6 + Set of Drinfeld module morphisms from (gen) 2*τ^2 + z6*τ + z6 to (gen) 2*τ^2 + z6*τ + z6 sage: E is Hom(phi, phi) True @@ -185,39 +185,39 @@ class DrinfeldModuleHomset(Homset): sage: identity_morphism = E(1) sage: identity_morphism - Identity morphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + Identity morphism of Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 :: - sage: t = phi.ore_polring().gen() - sage: frobenius_endomorphism = E(t^6) + sage: tau = phi.ore_variable() + sage: frobenius_endomorphism = E(tau^6) sage: frobenius_endomorphism - Endomorphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 - Defn: t^6 + Endomorphism of Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 + Defn: τ^6 :: - sage: isogeny = H(t + 1) + sage: isogeny = H(tau + 1) sage: isogeny Drinfeld Module morphism: - From: Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 - To: Drinfeld module defined by T |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 - Defn: t + 1 + From: Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 + To: Drinfeld module defined by T |--> 2*τ^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*τ + z6 + Defn: τ + 1 And one can test if an Ore polynomial defines a morphism using the ``in`` syntax:: sage: 1 in H False - sage: t^6 in H + sage: tau^6 in H False - sage: t + 1 in H + sage: tau + 1 in H True sage: 1 in E True - sage: t^6 in E + sage: tau^6 in E True - sage: t + 1 in E + sage: tau + 1 in E False This also works if the candidate is a morphism object:: @@ -293,7 +293,7 @@ def _latex_(self): sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: H = Hom(phi, psi) sage: latex(H) - \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from{ }(gen){ }}2 t^{2} + z_{6} t + z_{6}\text{{ }to{ }(gen){ }}2 t^{2} + \left(2 z_{6}^{5} + 2 z_{6}^{4} + 2 z_{6} + 1\right) t + z_{6} + \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from{ }(gen){ }}2 τ^{2} + z_{6} τ + z_{6}\text{{ }to{ }(gen){ }}2 τ^{2} + \left(2 z_{6}^{5} + 2 z_{6}^{4} + 2 z_{6} + 1\right) τ + z_{6} """ return f'\\text{{Set{{ }}of{{ }}Drinfeld{{ }}module{{ }}morphisms' \ f'{{ }}from{{ }}(gen){{ }}}}{latex(self.domain().gen())}' \ @@ -313,7 +313,7 @@ def _repr_(self): sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: H = Hom(phi, psi) sage: H - Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + Set of Drinfeld module morphisms from (gen) 2*τ^2 + z6*τ + z6 to (gen) 2*τ^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*τ + z6 """ return f'Set of Drinfeld module morphisms from (gen) '\ f'{self.domain().gen()} to (gen) {self.codomain().gen()}' @@ -337,23 +337,23 @@ def __contains__(self, x): sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: H = Hom(phi, psi) sage: E = End(phi) - sage: t = phi.ore_polring().gen() + sage: tau = phi.ore_variable() sage: 1 in H False - sage: t^6 in H + sage: tau^6 in H False - sage: t + 1 in H + sage: tau + 1 in H True sage: 1 in E True - sage: t^6 in E + sage: tau^6 in E True - sage: t + 1 in E + sage: tau + 1 in E False Whereas the input is now a Drinfeld module morphism:: - sage: isogeny = H(t + 1) + sage: isogeny = H(tau + 1) sage: isogeny in H True sage: E(0) in E @@ -385,33 +385,33 @@ def _element_constructor_(self, *args, **kwds): sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: H = Hom(phi, psi) sage: E = End(phi) - sage: t = phi.ore_polring().gen() + sage: tau = phi.ore_variable() sage: identity_morphism = E(1) sage: identity_morphism - Identity morphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + Identity morphism of Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 :: sage: scalar_multiplication = E(T) sage: scalar_multiplication - Endomorphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 - Defn: 2*t^2 + z6*t + z6 + Endomorphism of Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 + Defn: 2*τ^2 + z6*τ + z6 :: - sage: frobenius_endomorphism = E(t^6) + sage: frobenius_endomorphism = E(tau^6) sage: frobenius_endomorphism - Endomorphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 - Defn: t^6 + Endomorphism of Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 + Defn: τ^6 :: - sage: isogeny = H(t + 1) + sage: isogeny = H(tau + 1) sage: isogeny Drinfeld Module morphism: - From: Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 - To: Drinfeld module defined by T |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 - Defn: t + 1 + From: Drinfeld module defined by T |--> 2*τ^2 + z6*τ + z6 + To: Drinfeld module defined by T |--> 2*τ^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*τ + z6 + Defn: τ + 1 """ # NOTE: This used to be self.element_class(self, ...), but this # would call __init__ instead of __classcall_private__. This diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index beff948f980..2c7bf3b19c8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -48,15 +48,15 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: A. = Fq[] sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, z^2 + z, z^2 + z]) - sage: t = phi.ore_polring().gen() - sage: ore_pol = t + z^5 + z^3 + z + 1 + sage: tau = phi.ore_variable() + sage: ore_pol = tau + z^5 + z^3 + z + 1 sage: psi = phi.velu(ore_pol) sage: morphism = Hom(phi, psi)(ore_pol) sage: morphism Drinfeld Module morphism: - From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z)*t + z - To: Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*t^2 + (z^4 + z + 1)*t + z - Defn: t + z^5 + z^3 + z + 1 + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z)*τ + z + To: Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*τ^2 + (z^4 + z + 1)*τ + z + Defn: τ + z^5 + z^3 + z + 1 The given Ore polynomial must indeed define a morphism:: @@ -69,19 +69,19 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, One can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z)*t + z + Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z)*τ + z sage: morphism.domain() is phi True sage: morphism.codomain() - Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*t^2 + (z^4 + z + 1)*t + z + Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*τ^2 + (z^4 + z + 1)*τ + z sage: morphism.codomain() is psi True :: sage: morphism.ore_polynomial() - t + z^5 + z^3 + z + 1 + τ + z^5 + z^3 + z + 1 sage: morphism.ore_polynomial() is ore_pol True @@ -117,9 +117,9 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) Drinfeld Module morphism: - From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z)*t + z - To: Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*t^2 + (z^4 + z + 1)*t + z - Defn: t + z^5 + z^3 + z + 1 + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z)*τ + z + To: Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*τ^2 + (z^4 + z + 1)*τ + z + Defn: τ + z^5 + z^3 + z + 1 sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) is morphism True """ @@ -145,21 +145,21 @@ def __classcall_private__(cls, parent, x): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: End(phi)(T + 1) - Endomorphism of Drinfeld module defined by T |--> t^2 + t + z6 - Defn: t^2 + t + z6 + 1 + Endomorphism of Drinfeld module defined by T |--> τ^2 + τ + z6 + Defn: τ^2 + τ + z6 + 1 :: - sage: t = phi.ore_polring().gen() + sage: tau = phi.ore_variable() sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism is Hom(phi, psi)(morphism) True :: sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism - sage: morphism = DrinfeldModuleMorphism(Sets(), t + 1) + sage: morphism = DrinfeldModuleMorphism(Sets(), tau + 1) Traceback (most recent call last): ... TypeError: parent should be a DrinfeldModuleHomset @@ -203,13 +203,13 @@ def __init__(self, parent, ore_pol): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism._domain is phi True sage: morphism._codomain is psi True - sage: morphism._ore_polynomial == t + z6^5 + z6^2 + 1 + sage: morphism._ore_polynomial == tau + z6^5 + z6^2 + 1 True """ super().__init__(parent) @@ -228,10 +228,10 @@ def _latex_(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: latex(morphism) - t + z_{6}^{5} + z_{6}^{2} + 1 + τ + z_{6}^{5} + z_{6}^{2} + 1 """ return f'{latex(self._ore_polynomial)}' @@ -246,13 +246,13 @@ def _repr_(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism Drinfeld Module morphism: - From: Drinfeld module defined by T |--> t^2 + t + z6 - To: Drinfeld module defined by T |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 - Defn: t + z6^5 + z6^2 + 1 + From: Drinfeld module defined by T |--> τ^2 + τ + z6 + To: Drinfeld module defined by T |--> τ^2 + (z6^4 + z6^2 + 1)*τ + z6 + Defn: τ + z6^5 + z6^2 + 1 """ if self.is_identity(): return f'Identity morphism of {self._domain}' @@ -276,8 +276,8 @@ def __hash__(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: hash(morphism) # random -4214883752078138009 """ @@ -294,8 +294,8 @@ def is_zero(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism.is_zero() False @@ -324,8 +324,8 @@ def is_identity(self): :: sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism.is_identity() False """ @@ -342,8 +342,8 @@ def is_isogeny(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism.is_isogeny() True @@ -378,8 +378,8 @@ def is_isomorphism(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: morphism.is_isomorphism() False @@ -414,11 +414,11 @@ def ore_polynomial(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z6, 1, 1]) sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_polring().gen() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: tau = phi.ore_variable() + sage: morphism = Hom(phi, psi)(tau + z6^5 + z6^2 + 1) sage: ore_pol = morphism.ore_polynomial() sage: ore_pol - t + z6^5 + z6^2 + 1 + τ + z6^5 + z6^2 + 1 :: @@ -443,21 +443,21 @@ def __add__(self, other): sage: A. = Fq[] sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) - sage: t = phi.ore_variable() - sage: f = phi.hom(t + 1) + sage: tau = phi.ore_variable() + sage: f = phi.hom(tau + 1) sage: g = T * f sage: f + g # indirect doctest 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: (2*z^2 + 4*z + 4)*t^4 + (z + 1)*t^3 + t^2 + (2*z^2 + 4*z)*t + z + 1 + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + Defn: (2*z^2 + 4*z + 4)*τ^4 + (z + 1)*τ^3 + τ^2 + (2*z^2 + 4*z)*τ + z + 1 We check that coercion from the function ring works:: sage: F = phi.frobenius_endomorphism() sage: F + T - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: (z + 1)*t^3 + t^2 + z + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: (z + 1)*τ^3 + τ^2 + z """ return self.parent()(self.ore_polynomial() + other.ore_polynomial()) @@ -473,11 +473,11 @@ def _composition_(self, other, H): sage: phi = DrinfeldModule(A, [z, 1, z, z^2]) sage: f = phi.frobenius_endomorphism() sage: f - Endomorphism of Drinfeld module defined by T |--> z^2*t^3 + z*t^2 + t + z - Defn: t^3 + Endomorphism of Drinfeld module defined by T |--> z^2*τ^3 + z*τ^2 + τ + z + Defn: τ^3 sage: f * f # indirect doctest - Endomorphism of Drinfeld module defined by T |--> z^2*t^3 + z*t^2 + t + z - Defn: t^6 + Endomorphism of Drinfeld module defined by T |--> z^2*τ^3 + z*τ^2 + τ + z + Defn: τ^6 """ return H(self.ore_polynomial() * other.ore_polynomial()) @@ -495,10 +495,10 @@ def inverse(self): sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 1, z, z^2]) sage: f = phi.hom(2); f - Endomorphism of Drinfeld module defined by T |--> z^2*t^3 + z*t^2 + t + z + Endomorphism of Drinfeld module defined by T |--> z^2*τ^3 + z*τ^2 + τ + z Defn: 2 sage: f.inverse() - Endomorphism of Drinfeld module defined by T |--> z^2*t^3 + z*t^2 + t + z + Endomorphism of Drinfeld module defined by T |--> z^2*τ^3 + z*τ^2 + τ + z Defn: 3 Inversion of general isomorphisms between different Drinfeld modules @@ -506,13 +506,13 @@ def inverse(self): sage: g = phi.hom(z); g Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z^2*t^3 + z*t^2 + t + z - To: Drinfeld module defined by T |--> z^2*t^3 + (z^2 + 2*z + 3)*t^2 + (z^2 + 3*z)*t + z + From: Drinfeld module defined by T |--> z^2*τ^3 + z*τ^2 + τ + z + To: Drinfeld module defined by T |--> z^2*τ^3 + (z^2 + 2*z + 3)*τ^2 + (z^2 + 3*z)*τ + z Defn: z sage: g.inverse() Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z^2*t^3 + (z^2 + 2*z + 3)*t^2 + (z^2 + 3*z)*t + z - To: Drinfeld module defined by T |--> z^2*t^3 + z*t^2 + t + z + From: Drinfeld module defined by T |--> z^2*τ^3 + (z^2 + 2*z + 3)*τ^2 + (z^2 + 3*z)*τ + z + To: Drinfeld module defined by T |--> z^2*τ^3 + z*τ^2 + τ + z Defn: 3*z^2 + 4 When the morphism is not invertible, an error is raised:: @@ -564,8 +564,8 @@ def _motive_matrix(self): sage: A. = Fq[] sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1]) - sage: t = phi.ore_variable() - sage: u = t^2 + (2*z^2 + 3*z + 3)*t + (2*z + 3) + sage: tau = phi.ore_variable() + sage: u = tau^2 + (2*z^2 + 3*z + 3)*tau + (2*z + 3) sage: f = phi.hom(u) sage: f._motive_matrix() [ T + 3 + z 3 + 3*z + 2*z^2] @@ -621,8 +621,8 @@ def norm(self, ideal=True): sage: A. = Fq[] sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) - sage: t = phi.ore_variable() - sage: f = phi.hom(t + 1) + sage: tau = phi.ore_variable() + sage: f = phi.hom(tau + 1) sage: f.norm() Principal ideal (T + 4) of Univariate Polynomial Ring in T over Finite Field of size 5 @@ -682,19 +682,19 @@ def dual_isogeny(self): sage: A. = Fq[] sage: K. = Fq.extension(3) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) - sage: t = phi.ore_variable() - sage: f = phi.hom(t + 1) + sage: tau = phi.ore_variable() + sage: f = phi.hom(tau + 1) sage: f 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 + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + Defn: τ + 1 sage: g = f.dual_isogeny() sage: g Drinfeld Module morphism: - From: 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 - To: Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: z*t^2 + (4*z + 1)*t + z + 4 + From: Drinfeld module defined by T |--> (2*z^2 + 4*z + 4)*τ^3 + (3*z^2 + 2*z + 2)*τ^2 + (2*z^2 + 3*z + 4)*τ + z + To: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z*τ^2 + (4*z + 1)*τ + z + 4 We check that `f \circ g` (resp. `g \circ f`) is the multiplication by the norm of `f`:: @@ -760,8 +760,8 @@ def characteristic_polynomial(self, var='X'): TESTS:: - sage: t = phi.ore_variable() - sage: isog = phi.hom(t + 1) + sage: tau = phi.ore_variable() + sage: isog = phi.hom(tau + 1) sage: isog.characteristic_polynomial() Traceback (most recent call last): ... @@ -800,7 +800,7 @@ def charpoly(self, var='X'): morphism (Cayley-Hamilton's theorem):: sage: chi(f) - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z Defn: 0 We verify, on an example, that the characteristic polynomial From 27e890b5b1771607f0ad009cba2b010abf71ae46 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 17 Jul 2025 11:19:59 +0200 Subject: [PATCH 12/30] declare encoding --- src/sage/rings/function_field/drinfeld_modules/action.py | 1 + .../function_field/drinfeld_modules/charzero_drinfeld_module.py | 1 + .../rings/function_field/drinfeld_modules/drinfeld_module.py | 1 + .../function_field/drinfeld_modules/finite_drinfeld_module.py | 1 + src/sage/rings/function_field/drinfeld_modules/homset.py | 1 + src/sage/rings/function_field/drinfeld_modules/morphism.py | 1 + 6 files changed, 6 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 06f8897326d..7cb428452b1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" The module action induced by a Drinfeld module diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 99dd8dc9ce5..4c0c4b21437 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage.doctest: optional - sage.rings.finite_rings r""" Drinfeld modules over rings of characteristic zero diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 4d26afc2abb..1720d445ae2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Drinfeld modules diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 685d5b5c465..599aa775d72 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Finite Drinfeld modules diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 7dac2d93194..e1918686519 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Set of morphisms between two Drinfeld modules diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 2c7bf3b19c8..490adc634ea 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Drinfeld module morphisms From 227e9f5f8229e74dd49d61f137a53dcd60bc6d92 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 17 Jul 2025 23:27:47 +0200 Subject: [PATCH 13/30] remove the condition: nonzero constant coefficient --- .../drinfeld_modules/drinfeld_module.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index f65b6753831..cd39e3ca689 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -436,16 +436,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): ... ValueError: generator must have positive degree - The constant coefficient must be nonzero:: - - sage: Fq = GF(2) - sage: K. = Fq.extension(2) - sage: A. = Fq[] - sage: DrinfeldModule(A, [K(0), K(1)]) - Traceback (most recent call last): - ... - ValueError: constant coefficient must be nonzero - The coefficients of the generator must lie in an `\mathbb{F}_q[T]`-field, where `\mathbb{F}_q[T]` is the function ring of the Drinfeld module:: @@ -584,13 +574,10 @@ def __classcall_private__(cls, function_ring, gen, name='t'): elif isinstance(gen, (list, tuple)): ore_polring = None # Base ring without morphism structure: - base_field_noext = Sequence(gen).universe() + base_field_noext = Sequence(gen).universe().fraction_field() else: raise TypeError('generator must be list of coefficients or Ore ' 'polynomial') - # Constant coefficient must be nonzero: - if gen[0].is_zero(): - raise ValueError('constant coefficient must be nonzero') # The coefficients are in a base field that has coercion from Fq: if not (hasattr(base_field_noext, 'has_coerce_map_from') and base_field_noext.has_coerce_map_from(function_ring.base_ring())): From 281d9a686473197159b9e27a3abf2dbba0780548 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 18 Jul 2025 17:24:16 +0200 Subject: [PATCH 14/30] reorganize methods --- .../function_field/drinfeld_modules/homset.py | 533 +++++++++--------- 1 file changed, 257 insertions(+), 276 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 3c14f7fbece..c6171858cce 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -427,10 +427,85 @@ def _element_constructor_(self, *args, **kwds): # seems to work, but I don't know what I'm doing. return DrinfeldModuleMorphism(self, *args, **kwds) - def basis(self): + def an_element(self): + r""" + Return an element in this homset. + If the homset is not reduced to zero, then a nonzero + element is returned. + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: H = Hom(phi, psi) + sage: H.an_element() + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z + 1)*t + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*t^2 + (z + 1)*t + z + Defn: z^2*t^3 + + Below, `\phi` and `\psi` are not isogenous, so :meth:`an_element` + returns the zero morphism (which is the unique element in the + homset):: + + sage: psi = DrinfeldModule(A, [z, z + 1, 1]) + sage: H = Hom(phi, psi) + sage: H.an_element() + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z + 1)*t + z + To: Drinfeld module defined by T |--> t^2 + (z + 1)*t + z + Defn: (z + 1)*t^2 + (z^2 + z + 1)*t + z^2 + 1 + """ + basis = self._A_basis() + if len(basis) == 0: + return self(0) + return basis[0] + + def _frobenius_matrix(self, order=1, K_basis=None): + r""" + Internal method for computing the matrix of the Frobenius endomorphism + for K/Fq. This is a useful method for computing morphism ring bases so + we provide a helper method here. This should probably be part of the + Finite field implementation. + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: H = Hom(phi, psi) + sage: m = H._frobenius_matrix() + sage: e = K(z^2 + z + 1) + sage: frob = K.frobenius_endomorphism() + sage: frob(e) == K(m*vector(e.polynomial().coefficients(sparse=False))) + True + """ + Fq = self.domain()._Fq + K = self.domain().base_over_constants_field() + n = K.degree(Fq) + q = Fq.cardinality() + char = Fq.characteristic() + qorder = logb(q, char) + frob = K.frobenius_endomorphism(qorder*order) + if K_basis is None: + K_basis = K.basis_over(Fq) + pol_var = K_basis[0].polynomial().parent().gen() + pol_ring = PolynomialRing(Fq, str(pol_var)) + frob_matrix = Matrix(Fq, n, n) + for i, elem in enumerate(K_basis): + col = pol_ring(frob(elem).polynomial()).coefficients(sparse=False) + col += [0 for _ in range(n - len(col))] + for j in range(n): + frob_matrix[j, i] = col[j] + return frob_matrix + + def _A_basis(self): r""" Return a basis of this homset over the underlying - function ring `\mathbb F_q[T]`. + function ring. ALGORITHM: @@ -446,54 +521,17 @@ def basis(self): The algorithm consists in solving the above system viewed as a linear system over `\mathbb F_q[T]`. - EXAMPLES:: - - sage: Fq = GF(5) - sage: A. = Fq[] - sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(A, [z, 0, 1, z]) - sage: End(phi).basis() - [Identity morphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z, - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: t^2, - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: 2*t^4 + z*t^3 + z] - - :: - - sage: psi = DrinfeldModule(A, [z, 3*z + 1, 2*z, 4*z + 1]) - sage: H = Hom(phi, psi) - sage: H.basis() - [Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^3 + t^2 + z - To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: t + 1, - Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^3 + t^2 + z - To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: 3*t^3 + (z + 2)*t^2 + 4*z*t + z + 4, - Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^3 + t^2 + z - To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: (z + 4)*t^2 + 4*z*t + z + 4] - - When `\phi` and `\psi` are not isogenous, an empty list is returned:: - - sage: psi = DrinfeldModule(A, [z, 3*z, 2*z, 4*z]) - sage: Hom(phi, psi).basis() - [] + We refer to []_ for details. TESTS:: - sage: K. = Frac(A) + sage: A. = GF(5)[] sage: phi = DrinfeldModule(A, [T, 1]) sage: End(phi).basis() Traceback (most recent call last): ... NotImplementedError: computing basis of homsets are currently only implemented over finite fields """ - if self.base() not in FiniteFields(): - raise NotImplementedError("computing basis of homsets are currently only implemented over finite fields") phi = self.domain() psi = self.codomain() r = phi.rank() @@ -502,75 +540,73 @@ def basis(self): Fo = phi.base_over_constants_field() Fq = Fo.base() F = Fo.backend() + d = Fo.degree(Fq) q = Fq.cardinality() A = phi.function_ring() T = A.gen() - if self._basis is None: - AF = PolynomialRing(F, name='T') - TF = AF.gen() - - Frob = lambda x: x**q - FrobT = lambda P: P.map_coefficients(Frob) - - phiT = phi.gen() - psiT = psi.gen() - - # We compute the tau^i in M(phi) - lc = ~(phiT[r]) - xT = [-AF(lc*phiT[i]) for i in range(r)] - xT[0] += lc*TF - taus = [] - for i in range(r): - taui = r * [AF.zero()] - taui[i] = AF.one() - taus.append(taui) - for i in range(r): - s = FrobT(taui[-1]) - taui = [s*xT[0]] + [FrobT(taui[j-1]) + s*xT[j] for j in range(1,r)] - taus.append(taui) - - # We precompute the Frob^k(z^i) - d = Fo.degree(Fq) - z = F(Fo.gen()) - zs = [] - zq = z - for k in range(r+1): - x = F.one() - for i in range(d): - zs.append(x) - x *= zq - zq = zq ** q - - # We compute the linear system to solve - rows = [] + AF = PolynomialRing(F, name='T') + TF = AF.gen() + + Frob = lambda x: x**q + FrobT = lambda P: P.map_coefficients(Frob) + + phiT = phi.gen() + psiT = psi.gen() + + # We compute the tau^i in M(phi) + lc = ~(phiT[r]) + xT = [-AF(lc*phiT[i]) for i in range(r)] + xT[0] += lc*TF + taus = [] + for i in range(r): + taui = r * [AF.zero()] + taui[i] = AF.one() + taus.append(taui) + for i in range(r): + s = FrobT(taui[-1]) + taui = [s*xT[0]] + [FrobT(taui[j-1]) + s*xT[j] for j in range(1,r)] + taus.append(taui) + + # We precompute the Frob^k(z^i) + z = F(Fo.gen()) + zs = [] + zq = z + for k in range(r+1): + x = F.one() for i in range(d): - for j in range(r): - # For x = z^i * tau^j, we compute - # sum(g_k*tau^k(x), k=0..r) - T*x - # = sum(g_k*Frob^k(z^i)*tau^(k+j), k=0..r) - T*x - row = r * [AF.zero()] - for k in range(r+1): - s = psiT[k] * zs[k*d + i] - for l in range(r): - row[l] += s*taus[k+j][l] - row[j] -= zs[i] * TF - # We write it in the A-basis - rowFq = [] - for c in row: - c0 = Fo(c[0]).vector() - c1 = Fo(c[1]).vector() - rowFq += [c0[k] + T*c1[k] for k in range(d)] - rows.append(rowFq) - M = Matrix(rows) - - # We solve the linear system - self._basis = M.minimal_kernel_basis() + zs.append(x) + x *= zq + zq = zq ** q + + # We compute the linear system to solve + rows = [] + for i in range(d): + for j in range(r): + # For x = z^i * tau^j, we compute + # sum(g_k*tau^k(x), k=0..r) - T*x + # = sum(g_k*Frob^k(z^i)*tau^(k+j), k=0..r) - T*x + row = r * [AF.zero()] + for k in range(r+1): + s = psiT[k] * zs[k*d + i] + for l in range(r): + row[l] += s*taus[k+j][l] + row[j] -= zs[i] * TF + # We write it in the A-basis + rowFq = [] + for c in row: + c0 = Fo(c[0]).vector() + c1 = Fo(c[1]).vector() + rowFq += [c0[k] + T*c1[k] for k in range(d)] + rows.append(rowFq) + M = Matrix(rows) + + # We solve the linear system + ker = M.minimal_kernel_basis() # We reconstruct the isogenies isogenies = [] S = phi.ore_polring(); t = S.gen() - ker = self._basis for row in range(ker.nrows()): u = S.zero() for i in range(d): @@ -581,66 +617,22 @@ def basis(self): return isogenies - def an_element(self, degree): + def _Fq_basis(self, degree): r""" - Return a non-zero element of the space of morphisms between the domain - and codomain. By default, chooses an element of largest degree less - than or equal to the parameter `degree`. + Return a `\mathbb{F}_q`-basis of the space of morphisms in this + homset of degree at most `degree`. INPUT: - - ``degree`` -- the maximum degree of the morphism - - OUTPUT: a Drinfeld module morphism of degree at most ``degree`` - - EXAMPLES:: - - sage: Fq = GF(2) - sage: A. = Fq[] - sage: K. = Fq.extension(3) - sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) - sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) - sage: H = Hom(phi, psi) - sage: M = H.an_element(3) - sage: M_poly = M.ore_polynomial() - sage: M_poly*phi.gen() - psi.gen()*M_poly - 0 + - ``degree`` -- an integer ALGORITHM: - We scan the basis for the first element of maximal degree - and return it. - """ - basis = self._Fq_basis(degree) - if len(basis) == 0: - raise EmptySetError( - f'no possible morphisms of degree {degree} between the' - + 'domain and codomain') - elem = basis[0] - max_deg = elem.ore_polynomial().degree() - for basis_elem in basis: - if basis_elem.ore_polynomial().degree() > max_deg: - elem = basis_elem - max_deg = elem.ore_polynomial().degree() - return elem - - def _Fq_basis(self, degree): - r""" - Return a basis for the `\mathbb{F}_q`-space of morphisms from `phi` to - a Drinfeld module `\psi` of degree at most `degree`. - - A morphism `f: \phi \to psi` is an element `f \in K\{\tau\}` such that - `f \phi_T = \psi_T f`. The degree of a morphism is the - `\tau`-degree of `f`. This method currently only works for Drinfeld - modules over finite fields. - - INPUT: - - - ``degree`` -- the maximum degree of the morphisms in the span. - - OUTPUT: a list of Drinfeld module morphisms. + We return the basis of the kernel of a matrix derived from the + constraint that `f \phi_T = \psi_T f`. See [Wes2022]_ for + details on this algorithm. - EXAMPLES:: + TESTS:: sage: Fq = GF(2) sage: A. = Fq[] @@ -664,25 +656,6 @@ def _Fq_basis(self, degree): sage: hom = Hom(phi, psi) sage: hom._Fq_basis(4) [] - - TESTS:: - - sage: Fq = GF(4) - sage: A. = Fq[] - sage: K. = Fq.extension(3) - sage: psi = DrinfeldModule(A, [z, z^5 + z^3 + 1, 1]) - sage: phi = DrinfeldModule(A, [z, z^4 + z^3 + 1, 1]) - sage: H = Hom(phi, psi) - sage: basis = H._Fq_basis(2); basis - [...Defn: t^2 + (z^5 + z^4 + z^3 + z^2)*t + z^5 + z^4 + z^3 + 1, ...Defn: t^2 + (z^5 + z^4 + z)*t + z^5 + z^4 + z^3 + z] - - - - ALGORITHM: - - We return the basis of the kernel of a matrix derived from the - constraint that `f \phi_T = \psi_T f`. See [Wes2022]_ for - details on this algorithm. """ domain, codomain = self.domain(), self.codomain() Fq = domain._Fq @@ -730,27 +703,105 @@ def _Fq_basis(self, degree): raise ValueError('solution doesn\'t correspond to a morphism') return basis + def basis(self, degree=None): + r""" + Return a basis of this homset. + + INPUT: + + - ``degree`` -- an integer or ``None`` (default: ``None``) + + If ``degree`` is ``None``, a basis over the underlying + function ring is returned. + Otherwise, a `\FF_q`-basis of the set of morphisms of + degree at most ``degree`` is returned. + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, 1, z]) + sage: End(phi).basis() + [Identity morphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z, + Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z + Defn: t^2, + Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z + Defn: 2*t^4 + z*t^3 + z] + + If we specify a degree, a basis over `\FF_q` is computed:: + + sage: End(phi).basis(degree=5) + [] + + Here is another example where the domain and the codomain differ:: + + sage: psi = DrinfeldModule(A, [z, 3*z + 1, 2*z, 4*z + 1]) + sage: H = Hom(phi, psi) + sage: H.basis() + [Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*t^3 + t^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + Defn: t + 1, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*t^3 + t^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + Defn: 3*t^3 + (z + 2)*t^2 + 4*z*t + z + 4, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*t^3 + t^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + Defn: (z + 4)*t^2 + 4*z*t + z + 4] + + sage: H.basis(degree=2) + + We can check that `\phi` and `\psi` are not isomorphic by checking + that there is no isogeny of degree `0` between them:: + + sage: H.basis(degree=0) + [] + + When `\phi` and `\psi` are not isogenous, an empty list is returned:: + + sage: psi = DrinfeldModule(A, [z, 3*z, 2*z, 4*z]) + sage: Hom(phi, psi).basis() + [] + + TESTS:: + + sage: A. = GF(5)[] + sage: phi = DrinfeldModule(A, [T, 1]) + sage: End(phi).basis() + Traceback (most recent call last): + ... + NotImplementedError: computing basis of homsets are currently only implemented over finite fields + """ + if self.base() not in FiniteFields(): + raise NotImplementedError("computing basis of homsets are currently only implemented over finite fields") + if degree is None: + return self._A_basis() + else: + return self._Fq_basis(degree) + def basis_over_frobenius(self): r""" - Let `n = [K:\mathbb{F}_q]` and recall that `\tau^n` corresponds to the - Frobenius endomorphism on any Drinfeld module `\phi`. Return a basis - for the `\mathbb{F}_q[\tau^n]`-module of morphisms from the domain to - the codomain. This method currently only works for Drinfeld modules - over finite fields. + Return a basis of this homser over `\FF_q[\tau^n]` where + `n = [K:\FF_q]` (and thus `\tau^n` is to the Frobenius endomorphism). + + ALGORITHM: - OUTPUT: a list of Drinfeld module morphisms. + We return the basis of the kernel of a matrix derived from the + constraint that `\iota \phi_T = \psi_T \iota` for any morphism + `iota: \phi \to \psi`. EXAMPLES:: sage: Fq = GF(2) sage: A. = Fq[] sage: K. = Fq.extension(3) - sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: H = Hom(phi, psi) - sage: basis = H.basis() - sage: [b.ore_polynomial() for b in basis] - [(z^2 + 1)*t^2 + t + z + 1, (z^2 + 1)*t^5 + (z + 1)*t^4 + z*t^3 + t^2 + (z^2 + z)*t + z, z^2] + sage: H.basis_over_frobenius() :: @@ -760,7 +811,7 @@ def basis_over_frobenius(self): sage: phi = DrinfeldModule(A, [z, 3*z, 4*z]) sage: chi = DrinfeldModule(A, [z, 2*z^2 + 3, 4*z^2 + 4*z]) sage: H = Hom(phi, chi) - sage: H.basis() + sage: H.basis_over_frobenius() [] TESTS:: @@ -771,17 +822,24 @@ def basis_over_frobenius(self): sage: psi = DrinfeldModule(A, [z, z^5 + z^3 + 1, 1]) sage: phi = DrinfeldModule(A, [z, z^4 + z^3 + 1, 1]) sage: H = Hom(phi, psi) - sage: basis = H.basis(); basis - [... Defn: t^2 + (z^5 + z^4 + z^3 + z^2)*t + z^5 + z^4 + z^3 + 1, ... Defn: (z^3 + z^2 + z + 1)*t^2 + (z^5 + z^4 + z^3 + 1)*t + z^4 + z^3 + z, ... Defn: (z^3 + z^2 + z + 1)*t^5 + (z^5 + z^4 + z^3)*t^4 + z^2*t^3 + (z^3 + 1)*t^2 + (z^5 + z^4 + z^2 + 1)*t + z^2] + sage: H.basis_over_frobenius() + [... Defn: t^2 + (z^5 + z^4 + z^3 + z^2)*t + z^5 + z^4 + z^3 + 1, + ... Defn: (z^3 + z^2 + z + 1)*t^2 + (z^5 + z^4 + z^3 + 1)*t + z^4 + z^3 + z, + ... Defn: (z^3 + z^2 + z + 1)*t^5 + (z^5 + z^4 + z^3)*t^4 + z^2*t^3 + (z^3 + 1)*t^2 + (z^5 + z^4 + z^2 + 1)*t + z^2] sage: basis[2].ore_polynomial()*phi.gen() - psi.gen()*basis[2].ore_polynomial() 0 - ALGORITHM: + :: - We return the basis of the kernel of a matrix derived from the - constraint that `\iota \phi_T = \psi_T \iota` for any morphism - `iota: \phi \to \psi`. + sage: A. = GF(5)[] + sage: phi = DrinfeldModule(A, [T, 1]) + sage: End(phi).basis() + Traceback (most recent call last): + ... + ValueError: basis over Frobenius only makes sense for Drinfeld module defined over finite fields """ + if self.base() not in FiniteFields(): + raise ValueError("basis over Frobenius only makes sense for Drinfeld module defined over finite fields") Fq = self.domain()._Fq K = self.domain().base_over_constants_field() r = self.domain().rank() @@ -830,90 +888,7 @@ def basis_over_frobenius(self): basis.append(self(basis_poly)) return basis - def motive_power_decomposition(self, upper): - r""" - Computes coefficients a_0, .., a_{r-1} such that - \tau^t = a_{r-1}\tau^{r-1} + .... + a_{0} - with each a_i \in Fq[x] for each t < upper.. - """ - domain = self.domain() - r = domain.rank() - A = domain.function_ring() - x = A.gen() - Fq = domain._Fq - char, q = Fq.characteristic(), Fq.cardinality() - qord = char.log(q) - K = domain.base_over_constants_field() - coeff = domain.coefficients(sparse=False) - recurrence_relation = [K(coeff[i]/coeff[r]) for i in range(r)] - expansion_list = [[K(1) if i == j else K(0) for j in range(r)] for i in range(r)] - for i in range(0, upper - r + 1): - z = K.gen() - next_expansion = [-1*sum([recurrence_relation[j].frobenius(i*qord)*expansion_list[i+j][k] \ - for j in range(r)]) + (expansion_list[i][k]/K(coeff[r]).frobenius(i*qord))*x for k in range(r)] - expansion_list.append(next_expansion) - return expansion_list - - def motive_basis(self): - r""" - - """ - domain, codomain = self.domain(), self.codomain() - A = domain.function_ring() - Fq = domain._Fq - K = domain.base_over_constants_field() - q = Fq.cardinality() - char = Fq.characteristic() - r = domain.rank() - n = K.degree(Fq) - qorder = logb(q, char) - basis_expansions = self.motive_power_decomposition(2*r - 2) - - sys = Matrix(A, r*n) - for alpha in range(r-1): - for k in range(r-1): - for i in range(r-1): - pass - - def _frobenius_matrix(self, order=1, K_basis=None): - r""" - Internal method for computing the matrix of the Frobenius endomorphism - for K/Fq. This is a useful method for computing morphism ring bases so - we provide a helper method here. This should probably be part of the - Finite field implementation. - - sage: Fq = GF(5) - sage: A. = Fq[] - sage: K. = Fq.extension(3) - sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) - sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) - sage: H = Hom(phi, psi) - sage: m = H._frobenius_matrix() - sage: e = K(z^2 + z + 1) - sage: frob = K.frobenius_endomorphism() - sage: frob(e) == K(m*vector(e.polynomial().coefficients(sparse=False))) - True - """ - Fq = self.domain()._Fq - K = self.domain().base_over_constants_field() - n = K.degree(Fq) - q = Fq.cardinality() - char = Fq.characteristic() - qorder = logb(q, char) - frob = K.frobenius_endomorphism(qorder*order) - if K_basis is None: - K_basis = K.basis_over(Fq) - pol_var = K_basis[0].polynomial().parent().gen() - pol_ring = PolynomialRing(Fq, str(pol_var)) - frob_matrix = Matrix(Fq, n, n) - for i, elem in enumerate(K_basis): - col = pol_ring(frob(elem).polynomial()).coefficients(sparse=False) - col += [0 for _ in range(n - len(col))] - for j in range(n): - frob_matrix[j, i] = col[j] - return frob_matrix - - def random_element(self, degree, seed=None): + def random_element(self, degree=None): r""" Return a random morphism chosen uniformly from the space of morphisms of degree at most `degree`. @@ -937,6 +912,12 @@ def random_element(self, degree, seed=None): sage: M_poly*phi.gen() - psi.gen()*M_poly 0 """ - set_random_seed(seed) - return self(sum([self.domain()._Fq.random_element() - * elem.ore_polynomial() for elem in self._Fq_basis(degree)])) + domain = self.domain() + if degree is None: + A = domain._function_ring + return sum(A.random_element() * u + for u in self._A_basis()) + else: + Fq = domain._Fq + return sum(Fq.random_element() * u + for u in self._Fq_basis(degree)) From 96c4726f6bab4256b0f0cf6d637d72eaa769530f Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 18 Jul 2025 17:29:23 +0200 Subject: [PATCH 15/30] =?UTF-8?q?t=20->=20=CF=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../function_field/drinfeld_modules/homset.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index f4bde3b0604..c552eb1a264 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -444,9 +444,9 @@ def an_element(self): sage: H = Hom(phi, psi) sage: H.an_element() Drinfeld Module morphism: - From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z + 1)*t + z - To: Drinfeld module defined by T |--> (z^2 + z + 1)*t^2 + (z + 1)*t + z - Defn: z^2*t^3 + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: z^2*τ^3 Below, `\phi` and `\psi` are not isogenous, so :meth:`an_element` returns the zero morphism (which is the unique element in the @@ -456,9 +456,9 @@ def an_element(self): sage: H = Hom(phi, psi) sage: H.an_element() Drinfeld Module morphism: - From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z + 1)*t + z - To: Drinfeld module defined by T |--> t^2 + (z + 1)*t + z - Defn: (z + 1)*t^2 + (z^2 + z + 1)*t + z^2 + 1 + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> τ^2 + (z + 1)*τ + z + Defn: (z + 1)*τ^2 + (z^2 + z + 1)*τ + z^2 + 1 """ basis = self._A_basis() if len(basis) == 0: @@ -724,11 +724,11 @@ def basis(self, degree=None): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z, 0, 1, z]) sage: End(phi).basis() - [Identity morphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z, - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: t^2, - Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z - Defn: 2*t^4 + z*t^3 + z] + [Identity morphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z, + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: τ^2, + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: 2*τ^4 + z*τ^3 + z] If we specify a degree, a basis over `\FF_q` is computed:: @@ -741,17 +741,17 @@ def basis(self, degree=None): sage: H = Hom(phi, psi) sage: H.basis() [Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^3 + t^2 + z - To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z Defn: t + 1, Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^3 + t^2 + z - To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: 3*t^3 + (z + 2)*t^2 + 4*z*t + z + 4, + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z + Defn: 3*τ^3 + (z + 2)*τ^2 + 4*z*τ + z + 4, Drinfeld Module morphism: - From: Drinfeld module defined by T |--> z*t^3 + t^2 + z - To: Drinfeld module defined by T |--> (4*z + 1)*t^3 + 2*z*t^2 + (3*z + 1)*t + z - Defn: (z + 4)*t^2 + 4*z*t + z + 4] + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z + Defn: (z + 4)*τ^2 + 4*z*τ + z + 4] sage: H.basis(degree=2) @@ -824,9 +824,9 @@ def basis_over_frobenius(self): sage: phi = DrinfeldModule(A, [z, z^4 + z^3 + 1, 1]) sage: H = Hom(phi, psi) sage: H.basis_over_frobenius() - [... Defn: t^2 + (z^5 + z^4 + z^3 + z^2)*t + z^5 + z^4 + z^3 + 1, - ... Defn: (z^3 + z^2 + z + 1)*t^2 + (z^5 + z^4 + z^3 + 1)*t + z^4 + z^3 + z, - ... Defn: (z^3 + z^2 + z + 1)*t^5 + (z^5 + z^4 + z^3)*t^4 + z^2*t^3 + (z^3 + 1)*t^2 + (z^5 + z^4 + z^2 + 1)*t + z^2] + [... Defn: τ^2 + (z^5 + z^4 + z^3 + z^2)*τ + z^5 + z^4 + z^3 + 1, + ... Defn: (z^3 + z^2 + z + 1)*τ^2 + (z^5 + z^4 + z^3 + 1)*τ + z^4 + z^3 + z, + ... Defn: (z^3 + z^2 + z + 1)*τ^5 + (z^5 + z^4 + z^3)*τ^4 + z^2*τ^3 + (z^3 + 1)*τ^2 + (z^5 + z^4 + z^2 + 1)*τ + z^2] sage: basis[2].ore_polynomial()*phi.gen() - psi.gen()*basis[2].ore_polynomial() 0 From 81c37bc7afd26aaa1291499559641e61db804767 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 18 Jul 2025 17:51:07 +0200 Subject: [PATCH 16/30] small fixes and doctests --- .../function_field/drinfeld_modules/homset.py | 90 +++++++++++++++---- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index a0f1bcb4d45..9e20920063d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -462,9 +462,30 @@ def an_element(self): """ basis = self._A_basis() if len(basis) == 0: - return self(0) + return self.zero() return basis[0] + def zero(self): + r""" + Return the zero morphism is this homset. + + EXAMPLES:: + + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) + sage: H = Hom(phi, psi) + sage: H.zero() + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: 0 + """ + from sage.rings.integer_ring import ZZ + return self(ZZ(0)) + def _frobenius_matrix(self, order=1, K_basis=None): r""" Internal method for computing the matrix of the Frobenius endomorphism @@ -475,8 +496,8 @@ def _frobenius_matrix(self, order=1, K_basis=None): sage: Fq = GF(5) sage: A. = Fq[] sage: K. = Fq.extension(3) - sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) + sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: H = Hom(phi, psi) sage: m = H._frobenius_matrix() sage: e = K(z^2 + z + 1) @@ -695,7 +716,7 @@ def _Fq_basis(self, degree): basis = [] tau = domain.ore_polring().gen() for basis_elem in sol: - ore_poly = sum([sum([K_basis[j]*basis_elem[i*n + j] + ore_poly = sum([sum([K_basis[j].backend() * basis_elem[i*n + j] for j in range(n)])*(tau**i) for i in range(d + 1)]) try: @@ -733,7 +754,15 @@ def basis(self, degree=None): If we specify a degree, a basis over `\FF_q` is computed:: sage: End(phi).basis(degree=5) - [] + [Identity morphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z, + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z*τ^3 + z, + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: τ^2, + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: z*τ^5 + z*τ^2, + Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + Defn: τ^4] Here is another example where the domain and the codomain differ:: @@ -743,7 +772,7 @@ def basis(self, degree=None): [Drinfeld Module morphism: From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z - Defn: t + 1, + Defn: τ + 1, Drinfeld Module morphism: From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z @@ -754,6 +783,14 @@ def basis(self, degree=None): Defn: (z + 4)*τ^2 + 4*z*τ + z + 4] sage: H.basis(degree=2) + [Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z + Defn: τ + 1, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> z*τ^3 + τ^2 + z + To: Drinfeld module defined by T |--> (4*z + 1)*τ^3 + 2*z*τ^2 + (3*z + 1)*τ + z + Defn: (z + 4)*τ^2 + (4*z + 1)*τ + z] We can check that `\phi` and `\psi` are not isomorphic by checking that there is no isogeny of degree `0` between them:: @@ -803,6 +840,18 @@ def basis_over_frobenius(self): sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: H = Hom(phi, psi) sage: H.basis_over_frobenius() + [Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: (z^2 + 1)*τ^2 + τ + z + 1, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: (z^2 + 1)*τ^5 + (z + 1)*τ^4 + z*τ^3 + τ^2 + (z^2 + z)*τ + z, + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: z^2] :: @@ -823,7 +872,8 @@ def basis_over_frobenius(self): sage: psi = DrinfeldModule(A, [z, z^5 + z^3 + 1, 1]) sage: phi = DrinfeldModule(A, [z, z^4 + z^3 + 1, 1]) sage: H = Hom(phi, psi) - sage: H.basis_over_frobenius() + sage: basis = H.basis_over_frobenius() + sage: basis [... Defn: τ^2 + (z^5 + z^4 + z^3 + z^2)*τ + z^5 + z^4 + z^3 + 1, ... Defn: (z^3 + z^2 + z + 1)*τ^2 + (z^5 + z^4 + z^3 + 1)*τ + z^4 + z^3 + z, ... Defn: (z^3 + z^2 + z + 1)*τ^5 + (z^5 + z^4 + z^3)*τ^4 + z^2*τ^3 + (z^3 + 1)*τ^2 + (z^5 + z^4 + z^2 + 1)*τ + z^2] @@ -834,7 +884,7 @@ def basis_over_frobenius(self): sage: A. = GF(5)[] sage: phi = DrinfeldModule(A, [T, 1]) - sage: End(phi).basis() + sage: End(phi).basis_over_frobenius() Traceback (most recent call last): ... ValueError: basis over Frobenius only makes sense for Drinfeld module defined over finite fields @@ -884,8 +934,7 @@ def basis_over_frobenius(self): basis_poly = 0 for i in range(n): for j in range(n): - basis_poly += basis_vector[n*i + j] \ - .subs(tau**n)*K_basis[j]*tau**i + basis_poly += basis_vector[n*i + j].subs(tau**n) * K_basis[j].backend() * tau**i basis.append(self(basis_poly)) return basis @@ -908,17 +957,20 @@ def random_element(self, degree=None): sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) sage: H = Hom(phi, psi) - sage: M = H.random_element(3, seed=25) - sage: M_poly = M.ore_polynomial() - sage: M_poly*phi.gen() - psi.gen()*M_poly - 0 + sage: H.random_element(3) # random + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: (z^2 + 1)*τ^2 + τ + z + 1 """ domain = self.domain() if degree is None: - A = domain._function_ring - return sum(A.random_element() * u - for u in self._A_basis()) + scalars = domain._function_ring + basis = self._A_basis() else: - Fq = domain._Fq - return sum(Fq.random_element() * u - for u in self._Fq_basis(degree)) + scalars = domain._Fq + basis = self._Fq_basis(degree) + isog = self.zero() + for u in basis: + isog += scalars.random_element() * u + return isog From bffd26ef56bb52c78a67d87bee2757b0099a29b5 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 18 Jul 2025 18:02:48 +0200 Subject: [PATCH 17/30] reference to Musleh's thesis --- src/doc/en/reference/references/index.rst | 3 +++ .../function_field/drinfeld_modules/homset.py | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 949e5766167..c5a106f4d8b 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -5227,6 +5227,9 @@ REFERENCES: .. [Mur1983] \G. E. Murphy. *The idempotents of the symmetric group and Nakayama's conjecture*. J. Algebra **81** (1983). 258-265. +.. [Mus2023] Yossef Musleh. *Algorithms for Drinfeld Modules*. + PhD thesis, University of Waterloo, 2023. + .. [Muth2019] Robert Muth. *Super RSK correspondence with symmetry*. Electron. J. Combin. **26** (2019), no. 2, Paper 2.27, 29 pp. https://www.combinatorics.org/ojs/index.php/eljc/article/view/v26i2p27, diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 9e20920063d..1835b9bc164 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -543,7 +543,7 @@ def _A_basis(self): The algorithm consists in solving the above system viewed as a linear system over `\mathbb F_q[T]`. - We refer to []_ for details. + We refer to [Mus2023]_, Section 7.3 for more details. TESTS:: @@ -650,9 +650,9 @@ def _Fq_basis(self, degree): ALGORITHM: - We return the basis of the kernel of a matrix derived from the - constraint that `f \phi_T = \psi_T f`. See [Wes2022]_ for - details on this algorithm. + We return the basis of the kernel of a matrix derived from the + constraint that `f \phi_T = \psi_T f`. See [Wes2022]_ for + details on this algorithm. TESTS:: @@ -827,9 +827,10 @@ def basis_over_frobenius(self): ALGORITHM: - We return the basis of the kernel of a matrix derived from the - constraint that `\iota \phi_T = \psi_T \iota` for any morphism - `iota: \phi \to \psi`. + We return the basis of the kernel of a matrix derived from the + constraint that `\iota \phi_T = \psi_T \iota` for any morphism + `iota: \phi \to \psi`. + We refer to [Mus2023]_, Section 7.3 for more details. EXAMPLES:: From 2caec64f2344fbea9b2a16f2bebd5e37d30005b4 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 18 Jul 2025 21:37:20 +0200 Subject: [PATCH 18/30] some remaining t --- .../function_field/drinfeld_modules/drinfeld_module.py | 10 +++++----- .../drinfeld_modules/finite_drinfeld_module.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 1720d445ae2..7e2de8a4161 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -128,7 +128,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): - ``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 + - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial ring generator .. RUBRIC:: Construction @@ -534,8 +534,8 @@ def __classcall_private__(cls, function_ring, gen, name='τ'): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial - ring gen + - ``name`` -- (default: ``'τ'``) the name of the variable of + the Ore polynomial OUTPUT: a DrinfeldModule or DrinfeldModule_finite @@ -646,8 +646,8 @@ def __init__(self, gen, category): - ``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 gen + - ``name`` -- (default: ``'τ'``) the name of the variable of + the Ore polynomial ring TESTS:: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 599aa775d72..f0bf581caa2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -139,8 +139,8 @@ def __init__(self, gen, category): - ``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 gen + - ``name`` -- (default: ``'τ'``) the name of the variable of + the Ore polynomial ring TESTS:: From f8c02a0bdc254675f8c1d0f27e527956bdae52ce Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 19 Jul 2025 08:22:57 +0200 Subject: [PATCH 19/30] small fixes --- .../function_field/drinfeld_modules/homset.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 1835b9bc164..0c9ff6cbc68 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -27,15 +27,13 @@ from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.homset import Homset from sage.categories.action import Action +from sage.misc.cachefunc import cached_method from sage.misc.latex import latex from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.matrix.constructor import Matrix from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism from sage.structure.parent import Parent from sage.functions.log import logb -from sage.matrix.constructor import Matrix -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.misc.randstate import set_random_seed class DrinfeldModuleMorphismAction(Action): @@ -452,13 +450,13 @@ def an_element(self): returns the zero morphism (which is the unique element in the homset):: - sage: psi = DrinfeldModule(A, [z, z + 1, 1]) + sage: psi = DrinfeldModule(A, [z, z^2, z^3]) sage: H = Hom(phi, psi) sage: H.an_element() Drinfeld Module morphism: From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z - To: Drinfeld module defined by T |--> τ^2 + (z + 1)*τ + z - Defn: (z + 1)*τ^2 + (z^2 + z + 1)*τ + z^2 + 1 + To: Drinfeld module defined by T |--> (z + 1)*τ^2 + z^2*τ + z + Defn: 0 """ basis = self._A_basis() if len(basis) == 0: @@ -524,6 +522,7 @@ def _frobenius_matrix(self, order=1, K_basis=None): frob_matrix[j, i] = col[j] return frob_matrix + @cached_method def _A_basis(self): r""" Return a basis of this homset over the underlying @@ -628,7 +627,8 @@ def _A_basis(self): # We reconstruct the isogenies isogenies = [] - S = phi.ore_polring(); t = S.gen() + S = phi.ore_polring() + t = S.gen() for row in range(ker.nrows()): u = S.zero() for i in range(d): @@ -735,7 +735,7 @@ def basis(self, degree=None): If ``degree`` is ``None``, a basis over the underlying function ring is returned. - Otherwise, a `\FF_q`-basis of the set of morphisms of + Otherwise, a `\mathbb F_q`-basis of the set of morphisms of degree at most ``degree`` is returned. EXAMPLES:: @@ -751,7 +751,7 @@ def basis(self, degree=None): Endomorphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z Defn: 2*τ^4 + z*τ^3 + z] - If we specify a degree, a basis over `\FF_q` is computed:: + If we specify a degree, a basis over `\mathbb F_q` is computed:: sage: End(phi).basis(degree=5) [Identity morphism of Drinfeld module defined by T |--> z*τ^3 + τ^2 + z, @@ -822,8 +822,8 @@ def basis(self, degree=None): def basis_over_frobenius(self): r""" - Return a basis of this homser over `\FF_q[\tau^n]` where - `n = [K:\FF_q]` (and thus `\tau^n` is to the Frobenius endomorphism). + Return a basis of this homser over `\mathbb F_q[\tau^n]` where + `n = [K:\mathbb F_q]` (and thus `\tau^n` is to the Frobenius endomorphism). ALGORITHM: From 555cd699d57bed993c64b09ad7cf6d21ac0109ea Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 12 Aug 2025 08:10:47 +0200 Subject: [PATCH 20/30] shorten Joseph's code --- .../function_field/drinfeld_modules/homset.py | 117 ++++++------------ 1 file changed, 41 insertions(+), 76 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 0c9ff6cbc68..b6d3173f7cd 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -31,6 +31,7 @@ from sage.misc.latex import latex from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.matrix.constructor import Matrix +from sage.matrix.special import identity_matrix from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism from sage.structure.parent import Parent from sage.functions.log import logb @@ -481,46 +482,7 @@ def zero(self): To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z Defn: 0 """ - from sage.rings.integer_ring import ZZ - return self(ZZ(0)) - - def _frobenius_matrix(self, order=1, K_basis=None): - r""" - Internal method for computing the matrix of the Frobenius endomorphism - for K/Fq. This is a useful method for computing morphism ring bases so - we provide a helper method here. This should probably be part of the - Finite field implementation. - - sage: Fq = GF(5) - sage: A. = Fq[] - sage: K. = Fq.extension(3) - sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) - sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) - sage: H = Hom(phi, psi) - sage: m = H._frobenius_matrix() - sage: e = K(z^2 + z + 1) - sage: frob = K.frobenius_endomorphism() - sage: frob(e) == K(m*vector(e.polynomial().coefficients(sparse=False))) - True - """ - Fq = self.domain()._Fq - K = self.domain().base_over_constants_field() - n = K.degree(Fq) - q = Fq.cardinality() - char = Fq.characteristic() - qorder = logb(q, char) - frob = K.frobenius_endomorphism(qorder*order) - if K_basis is None: - K_basis = K.basis_over(Fq) - pol_var = K_basis[0].polynomial().parent().gen() - pol_ring = PolynomialRing(Fq, str(pol_var)) - frob_matrix = Matrix(Fq, n, n) - for i, elem in enumerate(K_basis): - col = pol_ring(frob(elem).polynomial()).coefficients(sparse=False) - col += [0 for _ in range(n - len(col))] - for j in range(n): - frob_matrix[j, i] = col[j] - return frob_matrix + return self(self.domain().ore_polring().zero()) @cached_method def _A_basis(self): @@ -614,12 +576,12 @@ def _A_basis(self): row[l] += s*taus[k+j][l] row[j] -= zs[i] * TF # We write it in the A-basis - rowFq = [] + rowA = [] for c in row: c0 = Fo(c[0]).vector() c1 = Fo(c[1]).vector() - rowFq += [c0[k] + T*c1[k] for k in range(d)] - rows.append(rowFq) + rowA += [c0[k] + T*c1[k] for k in range(d)] + rows.append(rowA) M = Matrix(rows) # We solve the linear system @@ -683,35 +645,40 @@ def _Fq_basis(self, degree): Fq = domain._Fq K = domain.base_over_constants_field() q = Fq.cardinality() - char = Fq.characteristic() r = domain.rank() if codomain.rank() != r: return [] n = K.degree(Fq) # shorten name for readability d = degree - qorder = logb(q, char) K_basis = K.basis_over(Fq) - dom_coeffs = domain.coefficients(sparse=False) - cod_coeffs = codomain.coefficients(sparse=False) + phiT = domain.coefficients(sparse=False) + psiT = codomain.coefficients(sparse=False) + # We precompute the matrices of the iterates of + # the Frobenius of K/Fq + frob_matrices = [identity_matrix(Fq, n)] + [Matrix(Fq, n) for _ in range(d + r)] + for i, elem in enumerate(K_basis): + for k in range(1, d+r+1): + elem = elem ** q + v = elem.vector() + for j in range(n): + frob_matrices[k][i,j] = v[j] + + # We write the linear system and solve it sys = Matrix(Fq, (d + r + 1)*n, (d + 1)*n) for k in range(0, d + r + 1): for i in range(max(0, k - r), min(k, d) + 1): # We represent multiplication and Frobenius # as operators acting on K as a vector space # over Fq - # Require matrices act on the right, so we - # take a transpose of operators here - oper = K(dom_coeffs[k-i] - .frobenius(qorder*i)).matrix().transpose() \ - - K(cod_coeffs[k-i]).matrix().transpose() \ - * self._frobenius_matrix(k - i, K_basis) + oper = K(phiT[k-i] ** (q**i)).matrix() \ + - frob_matrices[k-i] * K(psiT[k-i]).matrix() for j in range(n): for l in range(n): - sys[k*n + j, i*n + l] = oper[j, l] + sys[k*n + j, i*n + l] = oper[l, j] sol = sys.right_kernel().basis() - #print(f'sol:{sol}') + # Reconstruct the Ore polynomial from the coefficients basis = [] tau = domain.ore_polring().gen() @@ -719,10 +686,8 @@ def _Fq_basis(self, degree): ore_poly = sum([sum([K_basis[j].backend() * basis_elem[i*n + j] for j in range(n)])*(tau**i) for i in range(d + 1)]) - try: - basis.append(self(ore_poly)) - except ValueError: - raise ValueError('solution doesn\'t correspond to a morphism') + basis.append(self(ore_poly)) + return basis def basis(self, degree=None): @@ -896,18 +861,20 @@ def basis_over_frobenius(self): K = self.domain().base_over_constants_field() r = self.domain().rank() n = K.degree(Fq) - qorder = logb(Fq.cardinality(), Fq.characteristic()) - K_basis = [K.gen()**i for i in range(n)] - dom_coeffs = self.domain().coefficients(sparse=False) - cod_coeffs = self.codomain().coefficients(sparse=False) + q = Fq.cardinality() + K_basis = K.basis_over(Fq) + phiT = self.domain().coefficients(sparse=False) + psiT = self.codomain().coefficients(sparse=False) + # The commutative polynomial ring in tau^n. poly_taun = PolynomialRing(Fq, 'taun') + taun = poly_taun.gen() sys = Matrix(poly_taun, n**2, n**2) # Build a linear system over the commutative polynomial ring # Fq[tau^n]. The kernel of this system consists of all - # morphisms from domain -> codomain. + # morphisms from domain to codomain. for j in range(n): for k in range(n): for i in range(r+1): @@ -915,28 +882,26 @@ def basis_over_frobenius(self): # relation defining morphisms of Drinfeld modules # These are elements of K, expanded in terms of # K_basis. - base_poly = K(dom_coeffs[i].frobenius(qorder*k)*K_basis[j] - - cod_coeffs[i]*K_basis[j].frobenius(qorder*i)) \ - .polynomial() - c_tik = [0 for _ in range(n+1)] - for mono, coeff in zip(base_poly.monomials(), - base_poly.coefficients()): - c_tik[mono.degree()] = coeff - taudeg = i + k - for b in range(n): - sys[(taudeg % n)*n + b, k*n + j] += c_tik[b] * \ - poly_taun.gen()**(taudeg // n) + poly = K(phiT[i]**(q**k) * K_basis[j] + - psiT[i] * K_basis[j]**(q**i)).polynomial() + deg = (i + k) // n + row = n * (i + k - n*deg) + col = k*n + j + for b in range(poly.degree() + 1): + sys[row + b, col] += poly[b] * taun**deg sol = sys.right_kernel().basis() + # Reconstruct basis as skew polynomials. basis = [] - tau = self.domain().ore_polring().gen() + tau = self.domain().ore_variable() for basis_vector in sol: basis_poly = 0 for i in range(n): for j in range(n): basis_poly += basis_vector[n*i + j].subs(tau**n) * K_basis[j].backend() * tau**i basis.append(self(basis_poly)) + return basis def random_element(self, degree=None): From 55a374487f9fd60793dbbb79c3fe5da0aacdbe01 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 12 Aug 2025 08:17:13 +0200 Subject: [PATCH 21/30] address Antoine's comments --- .../function_field/drinfeld_modules/homset.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index b6d3173f7cd..f3895d3e9c8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -490,6 +490,11 @@ def _A_basis(self): Return a basis of this homset over the underlying function ring. + Currently, it only works over finite fields. + + This method should not be called directly; call + :meth:`basis` instead. + ALGORITHM: The isogenies `u : \phi \to \psi' correspond to @@ -603,8 +608,13 @@ def _A_basis(self): def _Fq_basis(self, degree): r""" - Return a `\mathbb{F}_q`-basis of the space of morphisms in this - homset of degree at most `degree`. + Return a `\mathbb{F}_q`-basis of the space of morphisms + in this homset of degree at most ``degree``. + + Currently, it only works over finite fields. + + This method should not be called directly; call + :meth:`basis` instead. INPUT: @@ -703,6 +713,12 @@ def basis(self, degree=None): Otherwise, a `\mathbb F_q`-basis of the set of morphisms of degree at most ``degree`` is returned. + ALGORITHM: + + We reformulate the problem as a linear system over `\mathbb F_q` + or `A = \mathbb F_q[T]` and solve it. + We refer to [Mus2023]_, Section 7.3 for more details. + EXAMPLES:: sage: Fq = GF(5) @@ -769,7 +785,7 @@ def basis(self, degree=None): sage: Hom(phi, psi).basis() [] - TESTS:: + Currently, this method only works over finite fields:: sage: A. = GF(5)[] sage: phi = DrinfeldModule(A, [T, 1]) From f2f6b47fa705cf5d3959614d34bb822cfef466a2 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 12 Aug 2025 08:19:27 +0200 Subject: [PATCH 22/30] add reference to Benjamin's paper --- src/sage/rings/function_field/drinfeld_modules/homset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index f3895d3e9c8..152e338e698 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -717,7 +717,7 @@ def basis(self, degree=None): We reformulate the problem as a linear system over `\mathbb F_q` or `A = \mathbb F_q[T]` and solve it. - We refer to [Mus2023]_, Section 7.3 for more details. + We refer to [Wes2022]_ and [Mus2023]_, Section 7.3 for more details. EXAMPLES:: From 563ff482065a89a74f26199f472a7938519dc174 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 12 Aug 2025 20:59:18 +0200 Subject: [PATCH 23/30] add authors --- src/sage/rings/function_field/drinfeld_modules/homset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 152e338e698..df70dfb0afe 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -9,6 +9,7 @@ AUTHORS: - Antoine Leudière (2022-04) +- Xavier Caruso, Yossef Musleh (2025-08): added computation of bases of homsets """ # ***************************************************************************** From 918bba2c3799a7aa427753c995b8ddd15fd0b607 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 13 Aug 2025 08:02:24 +0200 Subject: [PATCH 24/30] improve an_element and random_element --- .../function_field/drinfeld_modules/homset.py | 63 ++++++++++++++++--- .../drinfeld_modules/morphism.py | 20 ++++++ 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index df70dfb0afe..783af441eea 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -428,12 +428,17 @@ def _element_constructor_(self, *args, **kwds): # seems to work, but I don't know what I'm doing. return DrinfeldModuleMorphism(self, *args, **kwds) - def an_element(self): + def an_element(self, degree=None): r""" Return an element in this homset. If the homset is not reduced to zero, then a nonzero element is returned. + INPUT: + + - ``degree`` (default: ``None``) -- an integer; if given, + return an isogeny of this degree + EXAMPLES:: sage: Fq = GF(2) @@ -448,6 +453,21 @@ def an_element(self): To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z Defn: z^2*τ^3 + We can also ask for an isogeny with a required degree:: + + sage: H.an_element(degree=2) + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: (z^2 + 1)*τ^2 + τ + z + 1 + + If there is no isogeny with the required degree, an error is raised:: + + sage: H.an_element(degree=1) + Traceback (most recent call last): + ... + ValueError: no isogeny of given degree + Below, `\phi` and `\psi` are not isogenous, so :meth:`an_element` returns the zero morphism (which is the unique element in the homset):: @@ -460,10 +480,17 @@ def an_element(self): To: Drinfeld module defined by T |--> (z + 1)*τ^2 + z^2*τ + z Defn: 0 """ - basis = self._A_basis() - if len(basis) == 0: - return self.zero() - return basis[0] + if degree is None: + basis = self._A_basis() + if len(basis) == 0: + return self.zero() + return basis[0] + else: + basis = self._Fq_basis(degree=degree) + for isogeny in basis: + if isogeny.degree() == degree: + return isogeny + raise ValueError("no isogeny of given degree") def zero(self): r""" @@ -923,14 +950,12 @@ def basis_over_frobenius(self): def random_element(self, degree=None): r""" - Return a random morphism chosen uniformly from the space of morphisms - of degree at most `degree`. + Return a random morphism in this homset. INPUT: - - ``degree`` -- the maximum degree of the morphism - - OUTPUT: a univariate ore polynomials with coefficients in `K` + - ``degree`` (default: ``None``) -- the maximum degree of + the morphism EXAMPLES:: @@ -940,11 +965,29 @@ def random_element(self, degree=None): sage: psi = DrinfeldModule(A, [z, z + 1, z^2 + z + 1]) sage: phi = DrinfeldModule(A, [z, z^2 + z + 1, z^2 + z]) sage: H = Hom(phi, psi) + sage: H.random_element() # random + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: z*τ^7 + (z^2 + 1)*τ^6 + τ^5 + z^2*τ^4 + (z^2 + z + 1)*τ^3 + τ^2 + (z^2 + z)*τ + z + + When ``degree`` is given, a uniformly distributed random isogeny + of degree *at most* the given value is outputted:: + sage: H.random_element(3) # random Drinfeld Module morphism: From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z Defn: (z^2 + 1)*τ^2 + τ + z + 1 + + For producing a random isogeny with accurate degree, we can + proceed as follows:: + + sage: H.an_element(3) + H.random_element(2) # random + Drinfeld Module morphism: + From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z + To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z + Defn: z^2*τ^3 + (z^2 + 1)*τ^2 + τ + z^2 + z + 1 """ domain = self.domain() if degree is None: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 490adc634ea..620c6328fc7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -428,6 +428,26 @@ def ore_polynomial(self): """ return self._ore_polynomial + def degree(self): + r""" + Return the degree of this isogeny, that is, by definition, + the `\tau`-degree of its defining Ore polynomial. + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: f = phi.frobenius_endomorphism() + sage: f + Endomorphism of Drinfeld module defined by T |--> τ^2 + τ + z6 + Defn: τ^6 + sage: f.degree() + 6 + """ + return self._ore_polynomial.degree() + @coerce_binop def __add__(self, other): r""" From 38817eb6f8cc85f0f602b271b125c2bb481bf114 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 13 Aug 2025 08:15:24 +0200 Subject: [PATCH 25/30] handle the case of infinite fields --- .../function_field/drinfeld_modules/homset.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 783af441eea..6ff8d51c810 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -431,8 +431,6 @@ def _element_constructor_(self, *args, **kwds): def an_element(self, degree=None): r""" Return an element in this homset. - If the homset is not reduced to zero, then a nonzero - element is returned. INPUT: @@ -479,7 +477,24 @@ def an_element(self, degree=None): From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z To: Drinfeld module defined by T |--> (z + 1)*τ^2 + z^2*τ + z Defn: 0 + + When the base is not a finite field, computing isogenies is not + implemented so far, so this method always returns the zero morphism:: + + sage: phi = DrinfeldModule(A, [T, 1]) + sage: End(phi).an_element() + Endomorphism of Drinfeld module defined by T |--> τ + T + Defn: 0 + sage: End(phi).an_element(degree=1) + Traceback (most recent call last): + ... + NotImplementedError: computing isogenies are currently only implemented over finite fields """ + if self.base() not in FiniteFields(): + if degree is None: + return self.zero() + else: + raise NotImplementedError("computing isogenies are currently only implemented over finite fields") if degree is None: basis = self._A_basis() if len(basis) == 0: @@ -988,7 +1003,17 @@ def random_element(self, degree=None): From: Drinfeld module defined by T |--> (z^2 + z)*τ^2 + (z^2 + z + 1)*τ + z To: Drinfeld module defined by T |--> (z^2 + z + 1)*τ^2 + (z + 1)*τ + z Defn: z^2*τ^3 + (z^2 + 1)*τ^2 + τ + z^2 + z + 1 + + Currently, this method only works over finite fields:: + + sage: phi = DrinfeldModule(A, [T, 1]) + sage: End(phi).random_element() + Traceback (most recent call last): + ... + NotImplementedError: computing isogenies are currently only implemented over finite fields """ + if self.base() not in FiniteFields(): + raise NotImplementedError("computing isogenies are currently only implemented over finite fields") domain = self.domain() if degree is None: scalars = domain._function_ring From 15b399a7549334e781b37cc8c96a52eb2ff96e07 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 13 Aug 2025 17:40:23 +0200 Subject: [PATCH 26/30] remove coding utf8 --- src/sage/rings/function_field/drinfeld_modules/action.py | 1 - .../function_field/drinfeld_modules/charzero_drinfeld_module.py | 1 - .../rings/function_field/drinfeld_modules/drinfeld_module.py | 1 - .../function_field/drinfeld_modules/finite_drinfeld_module.py | 1 - src/sage/rings/function_field/drinfeld_modules/homset.py | 1 - src/sage/rings/function_field/drinfeld_modules/morphism.py | 1 - 6 files changed, 6 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 7cb428452b1..06f8897326d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" The module action induced by a Drinfeld module diff --git a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py index 4c0c4b21437..99dd8dc9ce5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/charzero_drinfeld_module.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage.doctest: optional - sage.rings.finite_rings r""" Drinfeld modules over rings of characteristic zero diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 7e2de8a4161..eaf958ff0ee 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Drinfeld modules diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 0cb6d60d265..15d616b5d2f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Finite Drinfeld modules diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index e1918686519..7dac2d93194 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Set of morphisms between two Drinfeld modules diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 490adc634ea..2c7bf3b19c8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage.doctest: needs sage.rings.finite_rings r""" Drinfeld module morphisms From 124db5bf4b685c6357fa4b574eb22fd126303585 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 15 Aug 2025 09:30:11 +0200 Subject: [PATCH 27/30] fix pdf documentation --- .../function_field/drinfeld_modules/drinfeld_module.py | 10 +++++----- .../drinfeld_modules/finite_drinfeld_module.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index eaf958ff0ee..c564cdd4935 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -127,8 +127,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial ring - generator + - ``name`` -- (default: `\tau`) the name of the Ore + polynomial ring generator .. RUBRIC:: Construction @@ -163,7 +163,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): False In those examples, we used a list of coefficients (``[z, 1, 1]``) to - represent the generator `\phi_T = z + τ + τ^2`. One can also use + represent the generator `\phi_T = z + \tau + \tau^2`. One can also use regular Ore polynomials:: sage: ore_polring = phi.ore_polring() @@ -533,7 +533,7 @@ def __classcall_private__(cls, function_ring, gen, name='τ'): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: ``'τ'``) the name of the variable of + - ``name`` -- (default: `\tau`) the name of the variable of the Ore polynomial OUTPUT: a DrinfeldModule or DrinfeldModule_finite @@ -645,7 +645,7 @@ def __init__(self, gen, category): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: ``'τ'``) the name of the variable of + - ``name`` -- (default: `\tau`) the name of the variable of the Ore polynomial ring TESTS:: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 15d616b5d2f..c18aa88664f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -138,7 +138,7 @@ def __init__(self, gen, category): - ``gen`` -- the generator of the Drinfeld module as a list of coefficients or an Ore polynomial - - ``name`` -- (default: ``'τ'``) the name of the variable of + - ``name`` -- (default: `\tau`) the name of the variable of the Ore polynomial ring TESTS:: @@ -235,7 +235,7 @@ def frobenius_endomorphism(self): Let `q` be the order of the base field of the function ring. The *Frobenius endomorphism* is defined as the endomorphism whose - defining Ore polynomial is `τ^q`. + defining Ore polynomial is `\tau^q`. EXAMPLES:: @@ -273,14 +273,14 @@ def frobenius_charpoly(self, var='X', algorithm=None): Let `\chi = X^r + \sum_{i=0}^{r-1} A_{i}(T)X^{i}` be the characteristic polynomial of the Frobenius endomorphism, and - let `τ^n` be the Ore polynomial that defines the Frobenius + let `\tau^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by definition, `n` is the degree of `K` over the base field `\mathbb{F}_q`. Then we have .. MATH:: - \chi(τ^n)(\phi(T)) - = τ^{nr} + \sum_{i=1}^{r} \phi_{A_{i}}τ^{n(i)} + \chi(\tau^n)(\phi(T)) + = \tau^{nr} + \sum_{i=1}^{r} \phi_{A_{i}}\tau^{n(i)} = 0, with `\deg(A_i) \leq \frac{n(r-i)}{r}`. From 9ae68f2df0e159e8282be439f54ca0f61fd00c11 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 15 Aug 2025 10:48:24 +0200 Subject: [PATCH 28/30] =?UTF-8?q?default:=20``'=CF=84'``?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../function_field/drinfeld_modules/drinfeld_module.py | 6 +++--- .../drinfeld_modules/finite_drinfeld_module.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index dec61d991ea..72b86ee1f78 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -126,7 +126,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the Ore + - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial ring generator .. RUBRIC:: Construction @@ -521,7 +521,7 @@ def __classcall_private__(cls, function_ring, gen, name='τ'): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the variable of + - ``name`` -- (default: ``'τ'``) the name of the variable of the Ore polynomial OUTPUT: a DrinfeldModule or DrinfeldModule_finite @@ -633,7 +633,7 @@ def __init__(self, gen, category): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the variable of + - ``name`` -- (default: ``'τ'``) the name of the variable of the Ore polynomial ring TESTS:: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index c18aa88664f..6a9fc657ab3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -138,7 +138,7 @@ def __init__(self, gen, category): - ``gen`` -- the generator of the Drinfeld module as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the variable of + - ``name`` -- (default: ``'τ'``) the name of the variable of the Ore polynomial ring TESTS:: From 5dc6b4588bbcbfc4642565ae953ab6867ed82152 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Fri, 15 Aug 2025 10:59:05 +0200 Subject: [PATCH 29/30] =?UTF-8?q?default:=20``'=CF=84'``?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../function_field/drinfeld_modules/drinfeld_module.py | 6 +++--- .../drinfeld_modules/finite_drinfeld_module.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c564cdd4935..1ab7e626232 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -127,7 +127,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the Ore + - ``name`` -- (default: ``'τ'``) the name of the Ore polynomial ring generator .. RUBRIC:: Construction @@ -533,7 +533,7 @@ def __classcall_private__(cls, function_ring, gen, name='τ'): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the variable of + - ``name`` -- (default: ``'τ'``) the name of the variable of the Ore polynomial OUTPUT: a DrinfeldModule or DrinfeldModule_finite @@ -645,7 +645,7 @@ def __init__(self, gen, category): - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the variable of + - ``name`` -- (default: ``'τ'``) the name of the variable of the Ore polynomial ring TESTS:: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index c18aa88664f..6a9fc657ab3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -138,7 +138,7 @@ def __init__(self, gen, category): - ``gen`` -- the generator of the Drinfeld module as a list of coefficients or an Ore polynomial - - ``name`` -- (default: `\tau`) the name of the variable of + - ``name`` -- (default: ``'τ'``) the name of the variable of the Ore polynomial ring TESTS:: From b4b4d901905e289e167b23c739363d97ad5a9607 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Sat, 16 Aug 2025 15:32:44 +0200 Subject: [PATCH 30/30] remove encoding declaration --- src/sage/categories/drinfeld_modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index aa80f8cc3b8..670ac3796b4 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # sage_setup: distribution = sagemath-categories # sage.doctest: needs sage.rings.finite_rings r"""