diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index aaaf7a410c0..11a2e035f47 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1716,6 +1716,9 @@ REFERENCES: Yokonuma-Hecke algebras and the HOMFLYPT polynomial*. (2015) :arxiv:`1204.1871v4`. +.. [CL2023] Xavier Caruso and Antoine Leudière. + *Algorithms for computing norms and characteristic polynomials on general Drinfeld modules*, (2023) :arxiv:`2307.02879`. + .. [Cle1872] Alfred Clebsch, *Theorie der binären algebraischen Formen*, Teubner, 1872. @@ -2814,6 +2817,10 @@ REFERENCES: .. [Gek1991] \E.-U. Gekeler. On finite Drinfeld modules. Journal of algebra, 1(141):187–203, 1991. +.. [Gek2008] \E.-U. Gekeler. Frobenius Distributions of Drinfeld Modules over + Finite Fields. Transactions of the American Mathematical Society, + Volume 360 (2008), no. 4. + .. [Gek2017] \E.-U. Gekeler. On Drinfeld modular forms of higher rank. Journal de théorie des nombres de Bordeaux, Volume 29 (2017) no. 3, pp. 875-902. :doi:`10.5802/jtnb.1005` 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 15809061112..2269085539d 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 @@ -162,7 +162,7 @@ def __init__(self, gen, category): self._frobenius_trace = None self._frobenius_charpoly = None - def _frobenius_crystalline_matrix(self): + def _frobenius_matrix_crystalline(self): r""" Return the matrix representing the Frobenius endomorphism on the crystalline cohomology of the Drinfeld module. This is done up to @@ -180,7 +180,7 @@ def _frobenius_crystalline_matrix(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi._frobenius_crystalline_matrix() + sage: phi._frobenius_matrix_crystalline() [...((z2 + 3) + z12 + (4*z2 + 1)*z12^2 + (z2 + 4)*z12^3 + (z2 + 4)*z12^4 + (2*z2 + 3)*z12^5)*T^3 + (2*z2 + 2*z2*z12 + (2*z2 + 3)*z12^2 + ... + (3*z2 + 4)*z12^3 + (2*z2 + 3)*z12^4 + 3*z2*z12^5] ALGORITHM: @@ -257,7 +257,7 @@ def frobenius_endomorphism(self): deg = self.base_over_constants_field().degree_over() return self._Hom_(self, category=self.category())(t**deg) - def frobenius_charpoly(self, var='X', algorithm='crystalline'): + def frobenius_charpoly(self, var='X', algorithm=None): r""" Return the characteristic polynomial of the Frobenius endomorphism. @@ -290,10 +290,30 @@ def frobenius_charpoly(self, var='X', algorithm='crystalline'): INPUT: - - ``var`` -- (default: ``'X'``) the name of the second variable - - ``algorithm`` -- (default: ``'crystalline'``) the algorithm + - ``var`` (default: ``'X'``) -- the name of the second variable + - ``algorithm`` (default: ``None``) -- the algorithm used to compute the characteristic polynomial + .. NOTE: + + Available algorithms are: + + - ``'CSA'`` -- it exploits the fact that `K\{\tau\}` is a + central simple algebra (CSA) over `\mathbb + F_q[\text{Frob}_\phi]` (see Chapter 4 of [CL2023]_). + - ``'crystalline'`` -- it uses the action of the Frobenius + on the crystalline cohomology (see [MS2023]_). + - ``'gekeler'`` -- it tries to identify coefficients by + writing that the characteristic polynomial annihilates the + Frobenius endomorphism; this algorithm may fail is some + cases (see [Gek2008]_). + - ``'motive'`` -- it uses the action of the Frobenius on the + Anderson motive (see Chapter 2 of [CL2023]_). + + The method raises an exception if the user asks for an + unimplemented algorithm, even if the characteristic polynomial + has already been computed. + EXAMPLES:: sage: Fq = GF(25) @@ -310,7 +330,7 @@ def frobenius_charpoly(self, var='X', algorithm='crystalline'): sage: A. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [1, 0, z6]) - sage: chi = phi.frobenius_charpoly() + sage: chi = phi.frobenius_charpoly(algorithm='crystalline') sage: chi X^2 + ((3*z3^2 + z3 + 4)*T + 4*z3^2 + 6*z3 + 3)*X + (5*z3^2 + 2*z3)*T^2 + (4*z3^2 + 3*z3)*T + 5*z3^2 + 2*z3 @@ -319,6 +339,9 @@ def frobenius_charpoly(self, var='X', algorithm='crystalline'): sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() 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 + Defn: 0 :: @@ -329,33 +352,99 @@ def frobenius_charpoly(self, var='X', algorithm='crystalline'): ALGORITHM: - By default, this method uses the so-called *crystalline* - algorithm which computes the characteristic polynomial of the - Frobenius acting on the crystalline cohomology of the Drinfeld - module. For further details, see [Ang1997]_. + If the user specifies an algorithm, then the characteristic + polynomial is computed according to the user's input (see + the note above), even if it had already been computed. + + If no algorithm is given, then the function either returns a + cached value, or if no cached value is available, the + function computes the Frobenius characteristic polynomial + from scratch. In that case, if the rank `r` is less than the + extension degree `n`, then the ``crystalline`` algorithm is + used, while the ``CSA`` algorithm is used otherwise. + + TESTS:: + + sage: Fq = GF(9) + sage: A. = Fq[] + sage: k. = Fq.extension(2) + sage: K. = k.extension(3) + + :: + + sage: phi = DrinfeldModule(A, [K(zk), z^8, z^7]) + sage: phi.frobenius_charpoly(algorithm='CSA') + X^2 + (2*T^3 + (2*z2 + 2)*T^2 + (z2 + 1)*T + 2*z2)*X + z2*T^6 + (2*z2 + 2)*T^3 + 2 + sage: phi.frobenius_charpoly(algorithm='crystalline') + X^2 + (2*T^3 + (2*z2 + 2)*T^2 + (z2 + 1)*T + 2*z2)*X + z2*T^6 + (2*z2 + 2)*T^3 + 2 + sage: phi.frobenius_charpoly(algorithm='gekeler') + X^2 + (2*T^3 + (2*z2 + 2)*T^2 + (z2 + 1)*T + 2*z2)*X + z2*T^6 + (2*z2 + 2)*T^3 + 2 + sage: phi.frobenius_charpoly(algorithm='motive') + X^2 + (2*T^3 + (2*z2 + 2)*T^2 + (z2 + 1)*T + 2*z2)*X + z2*T^6 + (2*z2 + 2)*T^3 + 2 + sage: phi.frobenius_charpoly()(phi.frobenius_endomorphism()).ore_polynomial() + 0 - The available options for 'algorithm' are: + :: - - ``'crystalline'`` -- computes the characteristic polynomial of the - Frobenius endomorphism on the crystalline cohomology of a Drinfeld - module. + sage: phi = DrinfeldModule(A, [K(zk), z^2, z^3, z^4]) + sage: phi.frobenius_charpoly(algorithm='CSA') + X^3 + ((z2 + 1)*T^2 + (z2 + 1)*T + z2 + 2)*X^2 + ((z2 + 2)*T^4 + 2*T^3 + z2*T^2 + T + 2*z2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly(algorithm='crystalline') + X^3 + ((z2 + 1)*T^2 + (z2 + 1)*T + z2 + 2)*X^2 + ((z2 + 2)*T^4 + 2*T^3 + z2*T^2 + T + 2*z2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly(algorithm='gekeler') + X^3 + ((z2 + 1)*T^2 + (z2 + 1)*T + z2 + 2)*X^2 + ((z2 + 2)*T^4 + 2*T^3 + z2*T^2 + T + 2*z2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly(algorithm='motive') + X^3 + ((z2 + 1)*T^2 + (z2 + 1)*T + z2 + 2)*X^2 + ((z2 + 2)*T^4 + 2*T^3 + z2*T^2 + T + 2*z2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly()(phi.frobenius_endomorphism()).ore_polynomial() + 0 - - ``'motive'`` -- based on computing the characteristic polynomial of - the Frobenius endomorphism on the motive of a Drinfeld module. This - instantiates the Frobenius as a morphism object and calls its - ``'characteristic_polynomial'`` method. + :: + + sage: phi = DrinfeldModule(A, [K(zk), z^8, z^7, z^20]) + sage: phi.frobenius_charpoly(algorithm='CSA') + X^3 + (z2*T^2 + z2*T + z2 + 1)*X^2 + (T^4 + (2*z2 + 1)*T^3 + (z2 + 2)*T^2 + (2*z2 + 1)*T + z2 + 2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly(algorithm='crystalline') + X^3 + (z2*T^2 + z2*T + z2 + 1)*X^2 + (T^4 + (2*z2 + 1)*T^3 + (z2 + 2)*T^2 + (2*z2 + 1)*T + z2 + 2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly(algorithm='gekeler') + X^3 + (z2*T^2 + z2*T + z2 + 1)*X^2 + (T^4 + (2*z2 + 1)*T^3 + (z2 + 2)*T^2 + (2*z2 + 1)*T + z2 + 2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly(algorithm='motive') + X^3 + (z2*T^2 + z2*T + z2 + 1)*X^2 + (T^4 + (2*z2 + 1)*T^3 + (z2 + 2)*T^2 + (2*z2 + 1)*T + z2 + 2)*X + T^6 + 2*z2*T^3 + 2*z2 + 1 + sage: phi.frobenius_charpoly()(phi.frobenius_endomorphism()).ore_polynomial() + 0 + + Check that ``var`` inputs are taken into account for cached + characteristic polynomials:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, 1]) + sage: phi.frobenius_charpoly() + X^2 + X + T^2 + T + 1 + sage: phi.frobenius_charpoly(var='Foo') + Foo^2 + Foo + T^2 + T + 1 + sage: phi.frobenius_charpoly(var='Bar') + Bar^2 + Bar + T^2 + T + 1 """ - # Throw an error if the user asks for an unimplemented algorithm - # even if the char poly has already been computed - method_name = f'_frobenius_charpoly_{algorithm}' - if hasattr(self, method_name): + # If no algorithm is specified, return cached data (if it + # exists), or pick an algorithm: + if algorithm is None: if self._frobenius_charpoly is not None: - return self._frobenius_charpoly - self._frobenius_charpoly = getattr(self, method_name)(var) - return self._frobenius_charpoly - raise NotImplementedError(f'algorithm \"{algorithm}\" not implemented') + return self._frobenius_charpoly.change_variable_name(var) + else: + if self.rank() < self._base_degree_over_constants: + algorithm = 'crystalline' + else: + algorithm = 'CSA' + # If an algorithm is specified, do not use cached data, even + # if it is possible: + method_name = f'_frobenius_charpoly_{algorithm}' + if not hasattr(self, method_name): + raise NotImplementedError(f'algorithm "{algorithm}" not implemented') + self._frobenius_charpoly = getattr(self, method_name)() + return self._frobenius_charpoly.change_variable_name(var) - def _frobenius_charpoly_crystalline(self, var): + def _frobenius_charpoly_CSA(self): r""" Return the characteristic polynomial of the Frobenius endomorphism using Crystalline cohomology. @@ -365,11 +454,59 @@ def _frobenius_charpoly_crystalline(self, var): This method is private and should not be directly called. Instead, use :meth:`frobenius_charpoly` with the option - `algorithm='crystalline'`. + `algorithm='CSA'`. - INPUT: + EXAMPLES:: - - ``var`` -- the name of the second variable + sage: Fq = GF(5) + sage: A. = Fq[] + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z^i for i in range(1, 50)]) + sage: phi.frobenius_charpoly(algorithm="CSA") # indirect doctest + X^48 + 4*X^47 + 4*X^46 + X^45 + 3*X^44 + X^42 + 4*X^41 + 4*X^39 + 2*X^37 + 4*X^36 + 3*X^35 + 2*X^33 + (4*T + 2)*X^32 + (4*T + 1)*X^31 + (3*T + 1)*X^30 + 4*T*X^29 + 4*T*X^28 + 2*X^27 + X^26 + (3*T + 4)*X^25 + (4*T + 4)*X^24 + (T + 1)*X^23 + (T + 3)*X^22 + 4*T*X^21 + (2*T + 1)*X^20 + 4*X^19 + 4*T*X^18 + (T + 4)*X^17 + 4*T^2*X^16 + (T^2 + 3*T + 3)*X^15 + (4*T^2 + 3*T + 3)*X^14 + (3*T^2 + 3*T + 3)*X^13 + (3*T^2 + 4*T + 4)*X^12 + (T^2 + 2*T + 2)*X^11 + (3*T^2 + 4*T + 4)*X^10 + (3*T^2 + T + 1)*X^9 + (4*T + 4)*X^8 + (3*T + 3)*X^7 + (T^2 + 3*T + 3)*X^6 + (3*T^2 + T + 1)*X^5 + (2*T^2 + 3*T + 3)*X^4 + (2*T^2 + 3*T + 3)*X^3 + 3*T^2*X^2 + 4*T^2*X + 2*T^3 + T + 1 + + ALGORITHM: + + Compute the characteristic polynomial of the Frobenius from + the reduced characteristic polynomial of the Ore polynomial + `phi_T`. This algorithm is particularly interesting when the + rank of the Drinfeld module is large compared to the degree + of the extension `K/\mathbb F_q`. See [CL2023]_. + """ + E = self._base + EZ = PolynomialRing(E, name='Z') + n = self._base_degree_over_constants + f = self.gen() # phi_T, which is updated in the subsequent loop + t = self.ore_variable() + rows = [] + for i in range(n): + m = f.degree() + 1 + row = [EZ([f[jj] for jj in range(j, m, n)]) for j in range(n)] + rows.append(row) + f = t * f + chi = Matrix(rows).charpoly() + # Format the result + K = self.base_over_constants_field() + A = self.function_ring() + n = self._base_degree_over_constants + r = self.rank() + lc = chi[0][r] + coeffs = [A([K(chi[i][j]/lc).in_base() + for i in range((r-j)*n // r + 1)]) + for j in range(r+1)] + return PolynomialRing(A, name='X')(coeffs) + + def _frobenius_charpoly_crystalline(self): + r""" + Return the characteristic polynomial of the Frobenius + endomorphism using Crystalline cohomology. + + The algorithm works for Drinfeld `\mathbb{F}_q[T]`-modules of + any rank. + + This method is private and should not be directly called. + Instead, use :meth:`frobenius_charpoly` with the option + `algorithm='crystalline'`. OUTPUT: a univariate polynomial with coefficients in the function ring @@ -413,17 +550,96 @@ def _frobenius_charpoly_crystalline(self, var): of section 6.3 in [MS2023]_. """ A = self.function_ring() - K = self.base_over_constants_field() - charpoly_K = self._frobenius_crystalline_matrix() \ - .charpoly(var).coefficients(sparse=False) + charpoly_K = self._frobenius_matrix_crystalline().charpoly().list() # The above line obtains the char poly with coefficients in K[T] # This maps them into A = Fq[T] coeffs_A = [A([x.in_base() for x in coeff]) for coeff in charpoly_K] - return PolynomialRing(A, name=var)(coeffs_A) + return PolynomialRing(A, name='X')(coeffs_A) + + def _frobenius_charpoly_gekeler(self): + r""" + Return the characteristic polynomial of the Frobenius + endomorphism using Gekeler's algorithm. + + The algorithm works for Drinfeld `\mathbb{F}_q[T]`-modules of + any rank, provided that the constant coefficient is a generator + of the base field. + + This method is private and should not be directly called. + Instead, use :meth:`frobenius_charpoly` with the option + `algorithm='gekeler'`. + + .. WARNING: + + This algorithm only works in the generic case when the + corresponding linear system is invertible. Notable cases + where this fails include Drinfeld modules whose minimal + polynomial is not equal to the characteristic polynomial, + and rank 2 Drinfeld modules where the degree 1 coefficient + of `\phi_T` is 0. In that case, an exception is raised. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, 4, 1, z]) + sage: phi.frobenius_charpoly(algorithm='gekeler') # indirect doctest + X^3 + ((z2 + 2)*T^2 + (z2 + 2)*T + 4*z2 + 4)*X^2 + ... + (3*z2 + 2)*T^2 + (3*z2 + 3)*T + 4 + + :: + + sage: Fq = GF(125) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, z]) + sage: phi.frobenius_charpoly(algorithm='gekeler') # indirect doctest + Traceback (most recent call last): + NotImplementedError: 'gekeler' algorithm failed + + ALGORITHM: - def _frobenius_charpoly_motive(self, var): + Construct a linear system based on the requirement that the + Frobenius satisfies a degree r polynomial with coefficients in + the function ring. This generalizes the procedure from + [Gek2008]_ for the rank 2 case. + """ + K = self.base_over_constants_field() + A = self.function_ring() + r, n = self.rank(), self._base_degree_over_constants + # Compute constants that determine the block structure of the + # linear system. The system is prepared such that the solution + # vector has the form [a_0, a_1, ... a_{r-1}]^T with each a_i + # corresponding to a block of length (n*(r - i))//r + 1 + shifts = [(n*(r - i))//r + 1 for i in range(r)] + rows, cols = n*r + 1, sum(shifts) + block_shifts = [0] + for i in range(r-1): + block_shifts.append(block_shifts[-1] + shifts[i]) + # Compute the images \phi_T^i for i = 0 .. n. + gen_powers = [self(A.gen()**i).coefficients(sparse=False) + for i in range(0, n + 1)] + sys, vec = Matrix(K, rows, cols), vector(K, rows) + vec[rows - 1] = -1 + for j in range(r): + for k in range(shifts[j]): + for i in range(len(gen_powers[k])): + sys[i + n*j, block_shifts[j] + k] = gen_powers[k][i] + if sys.right_nullity() != 0: + raise NotImplementedError("'gekeler' algorithm failed") + sol = list(sys.solve_right(vec)) + # The system is solved over K, but the coefficients should all + # be in Fq We project back into Fq here. + sol_Fq = [K(x).vector()[0] for x in sol] + char_poly = [] + for i in range(r): + char_poly.append([sol_Fq[block_shifts[i] + j] + for j in range(shifts[i])]) + return PolynomialRing(A, name='X')(char_poly + [1]) + + def _frobenius_charpoly_motive(self): r""" Return the characteristic polynomial of the Frobenius endomorphism using Motivic cohomology. @@ -435,10 +651,6 @@ def _frobenius_charpoly_motive(self, var): Instead, use :meth:`frobenius_charpoly` with the option `algorithm='motive'`. - INPUT: - - - ``var`` -- the name of the second variable - OUTPUT: a univariate polynomial with coefficients in the function ring @@ -470,7 +682,7 @@ def _frobenius_charpoly_motive(self, var): sage: phi.frobenius_charpoly(algorithm='motive') # indirect doctest X^11 + (z3^2 + 2*z3)*X^10 + ((z3 + 1)*T + z3)*X^9 + ((2*z3^2 + z3 + 2)*T^2 + ... + (2*z3^2 + 2*z3 + 2)*T + z3^2 """ - return self.frobenius_endomorphism().characteristic_polynomial(var) + return self.frobenius_endomorphism().characteristic_polynomial('X') def frobenius_norm(self): r""" @@ -478,9 +690,25 @@ def frobenius_norm(self): Let `C(X) = \sum_{i=0}^r a_iX^{i}` denote the characteristic polynomial of the Frobenius endomorphism. The Frobenius norm - is `(-1)^r a_{0}`. This is an element of the regular function ring - and if `n` is the degree of the base field over `\mathbb{F}_q`, - then the Frobenius norm has degree `n`. + is `a_{0}`, and given by the formula + + .. MATH:: + + a_0 = (-1)^{nr - n -r} + \mathrm{Norm}_{K/\mathbb F_q}(\Delta)^{-1} + p^{n / \mathrm{deg}(p)}, + + where `K` is the ground field, which as degree `n` over + `\mathbb F_q`, `r` is the rank of the Drinfeld module, + and `\Delta` is the leading coefficient of the generator. + This formula is given in Theorem~4.2.7 of [Pap2023]_. + + Note that the Frobenius norm computed by this method may be + different than what is computed as the isogeny norm of the + Frobenius endomorphism (see :meth:`norm` on the Frobenius + endomorphism), which is an ideal defined of the function ring + given by its monic generator; the Frobenius norm may not be + monic. EXAMPLES:: @@ -488,19 +716,28 @@ def frobenius_norm(self): sage: A. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [1, 0, z6]) - sage: B = phi.frobenius_norm() - sage: B + sage: frobenius_norm = phi.frobenius_norm() + sage: frobenius_norm (5*z3^2 + 2*z3)*T^2 + (4*z3^2 + 3*z3)*T + 5*z3^2 + 2*z3 :: sage: n = 2 # Degree of the base field over Fq - sage: B.degree() == n + sage: frobenius_norm.degree() == n True :: - sage: B == phi.frobenius_charpoly()[0] + sage: frobenius_norm == phi.frobenius_charpoly()[0] + True + + :: + + sage: lc = frobenius_norm.leading_coefficient() + sage: isogeny_norm = phi.frobenius_endomorphism().norm() + sage: isogeny_norm.gen() == frobenius_norm / lc + True + sage: A.ideal(frobenius_norm) == isogeny_norm True ALGORITHM: @@ -512,12 +749,15 @@ def frobenius_norm(self): return self._frobenius_norm K = self.base_over_constants_field() n = K.degree(self._Fq) - char = self.characteristic() + r = self.rank() + p = self.characteristic() norm = K(self.coefficients()[-1]).norm() - self._frobenius_norm = ((-1)**n)*(char**(n/char.degree())) / norm + self._frobenius_norm = (-1) ** (n*r - n - r) \ + * norm**(-1) \ + * p ** (n//p.degree()) return self._frobenius_norm - def frobenius_trace(self): + def frobenius_trace(self, algorithm=None): r""" Return the Frobenius trace of the Drinfeld module. @@ -527,44 +767,145 @@ def frobenius_trace(self): and if `n` is the degree of the base field over `\mathbb{F}_q`, then the Frobenius trace has degree at most `\frac{n}{r}`. + INPUT: + + - ``algorithm`` (default: ``None``) -- the algorithm + used to compute the characteristic polynomial + + .. NOTE: + + Available algorithms are: + + - ``'CSA'`` -- it exploits the fact that `K\{\tau\}` is a + central simple algebra (CSA) over `\mathbb + F_q[\text{Frob}_\phi]` (see Chapter 4 of [CL2023]_). + - ``'crystalline'`` -- it uses the action of the Frobenius + on the crystalline cohomology (see [MS2023]_). + + The method raises an exception if the user asks for an + unimplemented algorithm, even if the characteristic has already + been computed. See :meth:`frobenius_charpoly` for more details. + EXAMPLES:: sage: Fq = GF(343) sage: A. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [1, 0, z6]) - sage: A = phi.frobenius_trace() - sage: A + sage: frobenius_trace = phi.frobenius_trace() + sage: frobenius_trace (4*z3^2 + 6*z3 + 3)*T + 3*z3^2 + z3 + 4 :: sage: n = 2 # Degree over Fq of the base codomain - sage: A.degree() <= n/2 + sage: frobenius_trace.degree() <= n/2 True :: - sage: A == -phi.frobenius_charpoly()[1] + sage: frobenius_trace == -phi.frobenius_charpoly()[1] True + One can specify an algorithm:: + + sage: psi = DrinfeldModule(A, [z6, 1, z6^3, z6 + 1]) + sage: psi.frobenius_trace(algorithm='crystalline') + z3^2 + 6*z3 + 2 + sage: psi.frobenius_trace(algorithm='CSA') + z3^2 + 6*z3 + 2 + ALGORITHM: - We extract the coefficient of `X^{r-1}` from the characteristic - polynomial if it has been previously computed, otherwise we compute - the trace of the matrix of the Frobenius acting on the crystalline - cohomology. + If the user specifies an algorithm, then the trace is + computed according to the user's input (see the note above), + even if the Frobenius trace or the Frobenius characteristic + polynomial had already been computed. + + If no algorithm is given, then the function either returns a + cached value, or if no cached value is available, the + function computes the Frobenius trace from scratch. In that + case, if the rank `r` is less than the extension degree `n`, + then the ``crystalline`` algorithm is used, while the + ``CSA`` algorithm is used otherwise. + + TESTS: + + These test values are taken from :meth:`frobenius_charpoly`:: + + sage: Fq = GF(9) + sage: A. = Fq[] + sage: k. = Fq.extension(2) + sage: K. = k.extension(3) + + :: + + sage: phi = DrinfeldModule(A, [K(zk), z^8, z^7]) + sage: phi.frobenius_trace(algorithm='CSA') + T^3 + (z2 + 1)*T^2 + (2*z2 + 2)*T + z2 + sage: phi.frobenius_trace(algorithm='crystalline') + T^3 + (z2 + 1)*T^2 + (2*z2 + 2)*T + z2 + sage: charpoly = phi.frobenius_charpoly() + sage: trace = phi.frobenius_trace() + sage: trace == -charpoly[1] + True + + :: + + sage: phi = DrinfeldModule(A, [K(zk), z^2, z^3, z^4]) + sage: phi.frobenius_trace(algorithm='CSA') + (2*z2 + 2)*T^2 + (2*z2 + 2)*T + 2*z2 + 1 + sage: phi.frobenius_trace(algorithm='crystalline') + (2*z2 + 2)*T^2 + (2*z2 + 2)*T + 2*z2 + 1 + sage: charpoly = phi.frobenius_charpoly() + sage: trace = phi.frobenius_trace() + sage: trace == -charpoly[2] + True + + :: + + sage: phi = DrinfeldModule(A, [K(zk), z^8, z^7, z^20]) + sage: phi.frobenius_trace(algorithm='CSA') + 2*z2*T^2 + 2*z2*T + 2*z2 + 2 + sage: phi.frobenius_trace(algorithm='crystalline') + 2*z2*T^2 + 2*z2*T + 2*z2 + 2 + sage: charpoly = phi.frobenius_charpoly() + sage: trace = phi.frobenius_trace() + sage: trace == -charpoly[2] + True """ - K = self.base_over_constants_field() - A = self.function_ring() - if self._frobenius_trace is not None: + if algorithm is None: + # If no algorithm is specified, return cached data (if it + # exists), or pick an algorithm: + # However, if an algorithm is specified, do not use cached + # data, even if it is possible + if self._frobenius_trace is not None: + return self._frobenius_trace + if self._frobenius_charpoly is not None: + self._frobenius_trace = -self._frobenius_charpoly \ + .coefficients(sparse=False)[-2] + return self._frobenius_trace + else: + if self.rank() < self._base_degree_over_constants: + algorithm = 'crystalline' + else: + algorithm = 'CSA' + # We first check if a matrix method is available + matrix_method_name = f'_frobenius_matrix_{algorithm}' + if hasattr(self, matrix_method_name): + matrix = getattr(self, matrix_method_name)() + trace = matrix.trace() + A = self.function_ring() + self._frobenius_trace = A([x.in_base() for x in trace]) + return self._frobenius_trace + # If it is not, we look for a charpoly method + charpoly_method_name = f'_frobenius_charpoly_{algorithm}' + if hasattr(self, charpoly_method_name): + charpoly = getattr(self, charpoly_method_name)() + self._frobenius_trace = -charpoly.list()[-2] return self._frobenius_trace - if self._frobenius_charpoly is not None: - self._frobenius_trace = -self._frobenius_charpoly \ - .coefficients(sparse=False)[-2] - self._frobenius_trace = self._frobenius_crystalline_matrix().trace() - self._frobenius_trace = A([x.in_base() for x in self._frobenius_trace]) - return self._frobenius_trace + # If all fail, we raise an error + raise NotImplementedError(f'algorithm "{algorithm}" not implemented') def invert(self, ore_pol): r""" @@ -657,7 +998,6 @@ def invert(self, ore_pol): # Write the system and solve it k = deg // r A = self._function_ring - T = A.gen() mat_rows = [[E.zero() for _ in range(k+1)] for _ in range(k+1)] mat_rows[0][0] = E.one() phiT = self.gen()