Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement characteristic polynomial computation for Drinfeld modules of any Rank #35269

Merged
merged 15 commits into from Nov 5, 2023

Conversation

ymusleh
Copy link
Contributor

@ymusleh ymusleh commented Mar 13, 2023

📚 Description

The goal of this PR is to implement methods for computing the characteristic polynomial (of the Frobenius endomorphism) of a Drinfeld Fq[t]-module. This largely closes #35028, though opportunities to implement other algorithms, as well as the question of computing characteristic polynomials for arbitrary morphisms, remain open.

Some further reading on characteristic polynomials of Drinfeld modules:

Gekeler, E.-U. (2008). Frobenius Distributions of Drinfeld Modules over Finite Fields. Transactions of the American Mathematical Society, 360(4), 1695–1721. http://www.jstor.org/stable/20161942

Angles, B. On some characteristic polynomials attached to finite Drinfeld modules. Manuscripta Math 93, 369–379 (1997). https://doi.org/10.1007/BF02677478

@kryzar @xcaruso @DavidAyotte @schost

📝 Checklist

  • I have made sure that the title is self-explanatory and the description concisely explains the PR.
  • I have linked an issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation accordingly.

⌛ Dependencies

None as it only requires the base implementation of Drinfeld modules which has been merged.

@DavidAyotte DavidAyotte self-requested a review March 13, 2023 03:27
@ymusleh ymusleh marked this pull request as draft March 13, 2023 04:45
@codecov-commenter
Copy link

codecov-commenter commented Mar 13, 2023

Codecov Report

Patch coverage: 100.00% and project coverage change: -0.02 ⚠️

Comparison is base (c00e6c2) 88.62% compared to head (987d661) 88.61%.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #35269      +/-   ##
===========================================
- Coverage    88.62%   88.61%   -0.02%     
===========================================
  Files         2148     2148              
  Lines       398855   398915      +60     
===========================================
+ Hits        353480   353493      +13     
- Misses       45375    45422      +47     
Impacted Files Coverage Δ
...n_field/drinfeld_modules/finite_drinfeld_module.py 97.01% <100.00%> (+1.06%) ⬆️

... and 29 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@kryzar
Copy link
Contributor

kryzar commented Mar 13, 2023

Thanks Joseph! I will read the code and docs this week, and I'd be happy to be a reviewer!

@DavidAyotte
Copy link
Member

Thank you very much Joseph, that's a wonderful contribution. I made a couple of minor docstring typesetting suggestions that follows the Sage development guidelines, but overall from a first glance the code looks very good!

@xcaruso xcaruso requested review from kryzar and xcaruso March 13, 2023 13:54
Copy link
Contributor

@kryzar kryzar left a comment

Choose a reason for hiding this comment

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

Quick suggestion before going into more in-depth review this week. I think the methods frobenius_charpoly_something should be private. Consequently, they should also be alphabetically reordered in the file.

Anyway, your contribution looks impressive.

algorithms = {'gekeler', 'crystalline'}
if algorithm in algorithms:
return getattr(self, \
f'{self.frobenius_charpoly.__name__}_{algorithm}')(var)
Copy link
Contributor

@kryzar kryzar Mar 13, 2023

Choose a reason for hiding this comment

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

Suggested change
f'{self.frobenius_charpoly.__name__}_{algorithm}')(var)
f'{self._frobenius_charpoly.__name__}_{algorithm}')(var)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ugh... I have trouble adding the necessary spaces, be careful here!

@ymusleh ymusleh marked this pull request as ready for review March 14, 2023 10:06
moduli = [S([c**(q**(-i*nstar % n)) for c in mu_coeffs]) \
for i in range(1, n1) ]

def reduce_and_frobenius(order, modulus):
Copy link
Contributor

Choose a reason for hiding this comment

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

This nested function is only called once; do we really need to have it?

Copy link
Contributor

@xcaruso xcaruso left a comment

Choose a reason for hiding this comment

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

My first comments...

ALGORITHM:
def _frobenius_charpoly_crystalline(self, var='X'):
r"""
Method to compute the characteristic polynomial of the
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the first line of the doctest has to be of the form Return [...]

sage: K.<z> = Fq.extension(8)
sage: phi = DrinfeldModule(A, [z, 4, 1, z, z+1, 2, z+2, 1, 1, 3, 1])
sage: phi._frobenius_charpoly_crystalline()
X^10 + X^9 + (3*T + z2 + 1)*X^8 + (4*T^2 + z2*T + 2*z2 + 1)*X^7 + (4*T^3 + (z2 + 2)*T^2 + (4*z2 + 2)*T + 4)*X^6 + (3*T^4 + T^3 + 3*z2*T + 3*z2 + 3)*X^5 + ((4*z2 + 2)*T^4 + (3*z2 + 4)*T^3 + 3*T^2 + (2*z2 + 4)*T + 4*z2 + 1)*X^4 + (3*T^5 + (2*z2 + 3)*T^4 + 4*T^3 + (2*z2 + 2)*T^2 + (z2 + 3)*T + 4*z2)*X^3 + (3*T^6 + (3*z2 + 2)*T^5 + (4*z2 + 1)*T^4 + z2*T^3 + (4*z2 + 4)*T^2 + 4*z2*T)*X^2 + (2*T^7 + 3*T^6 + 2*z2*T^5 + (2*z2 + 3)*T^4 + (4*z2 + 3)*T^3 + (z2 + 2)*T^2 + (z2 + 4)*T + 2*z2 + 2)*X + T^8 + (4*z2 + 3)*T^6 + (4*z2 + 4)*T^4 + 4*z2*T^2 + (z2 + 2)*T + z2
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, the complete output is not really meaningful; maybe, you should cut it using Ellipsis (...);

sage: K.<z> = Fq.extension(10)
sage: phi = DrinfeldModule(A, [z, z^2 + z, 2, 1, z, z+1, 2, z+2, 0, 1, 1, z^2 + z])
sage: phi._frobenius_charpoly_crystalline()
X^11 + (z3^2 + 2*z3)*X^10 + ((z3 + 1)*T + z3)*X^9 + ((2*z3^2 + z3 + 2)*T^2 + (2*z3^2 + z3 + 1)*T + 2*z3^2 + z3 + 2)*X^8 + ((z3^2 + z3)*T^3 + 2*z3^2*T^2 + (2*z3^2 + z3 + 2)*T + 1)*X^7 + ((z3^2 + 2*z3)*T^4 + (2*z3^2 + 2*z3)*T^3 + (z3^2 + 1)*T^2 + (z3^2 + 1)*T + z3^2 + z3 + 2)*X^6 + ((z3^2 + z3)*T^5 + (z3^2 + 1)*T^4 + (z3^2 + 1)*T^3 + (z3^2 + 2*z3 + 1)*T^2 + (z3^2 + 2)*T + z3^2 + 2*z3 + 1)*X^5 + ((z3^2 + z3 + 1)*T^6 + (z3^2 + 2*z3 + 1)*T^5 + (2*z3^2 + z3)*T^4 + (z3^2 + 2)*T^2 + (2*z3^2 + 2*z3)*T + z3^2 + 1)*X^4 + (2*z3*T^7 + 2*z3^2*T^6 + (z3^2 + z3)*T^5 + (2*z3 + 2)*T^4 + 2*z3*T^3 + (z3 + 2)*T^2 + z3^2*T + z3^2 + 2*z3 + 1)*X^3 + (2*z3*T^8 + (z3^2 + z3)*T^7 + (2*z3^2 + z3 + 2)*T^6 + (z3^2 + 2)*T^5 + T^4 + (2*z3^2 + 2)*T^3 + (z3^2 + z3 + 1)*T^2 + (z3 + 2)*T + 2*z3^2 + z3 + 1)*X^2 + (2*z3*T^9 + z3*T^8 + (z3^2 + z3 + 1)*T^7 + T^6 + (z3^2 + 2*z3 + 2)*T^5 + (z3^2 + 2*z3 + 1)*T^4 + T^3 + (2*z3^2 + z3 + 2)*T^2 + T + 2*z3^2 + z3)*X + z3*T^10 + (z3^2 + z3)*T^9 + (2*z3^2 + 1)*T^8 + (2*z3^2 + 2*z3)*T^7 + z3*T^6 + (z3^2 + 2*z3 + 2)*T^5 + T^4 + z3*T^3 + (z3^2 + 1)*T^2 + (2*z3^2 + 2*z3 + 2)*T + z3^2
Copy link
Contributor

Choose a reason for hiding this comment

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

Same remark as above.

Comment on lines 339 to 345
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting is not correct. It should be:

.. 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.

sage: K.<z> = Fq.extension(6)
sage: phi = DrinfeldModule(A, [z, 4, 1, z])
sage: phi._frobenius_charpoly_gekeler()
X^3 + ((z2 + 2)*T^2 + (z2 + 2)*T + 4*z2 + 4)*X^2 + (4*z2*T^3 + (2*z2 + 3)*T^2 + (2*z2 + 2)*T + z2 + 3)*X + (3*z2 + 2)*T^6 + (4*z2 + 2)*T^5 + (3*z2 + 2)*T^4 + (3*z2 + 4)*T^3 + (3*z2 + 2)*T^2 + (3*z2 + 3)*T + 4
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, maybe truncate output.

sage: phi = DrinfeldModule(A, [z, 0, z])
sage: phi._frobenius_charpoly_gekeler()
Traceback (most recent call last):
ValueError: Can't solve system for characteristic polynomial
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe NotImplementedError instead of ValueError

@xcaruso
Copy link
Contributor

xcaruso commented Mar 17, 2023

I would like to add the algorithm azumaya (or another better name) for the computation of the charpoly of the Frobenius. Would we prefer that I include this in this PR or that I open another PR once this one will be merged?

@kryzar
Copy link
Contributor

kryzar commented Mar 17, 2023

Would we prefer that I include this in this PR or that I open another PR once this one will be merged?

A separate PR would be better, no?

@ymusleh
Copy link
Contributor Author

ymusleh commented Mar 17, 2023

I would like to add the algorithm azumaya (or another better name) for the computation of the charpoly of the Frobenius. Would we prefer that I include this in this PR or that I open another PR once this one will be merged?

Either approach is fine with me, but it seems separate PRs are preferred so I think creating one and just referencing this as a dependency is probably best.

@xcaruso
Copy link
Contributor

xcaruso commented Mar 17, 2023

OK, I will open a new PR soon.

Copy link
Contributor

@kryzar kryzar left a comment

Choose a reason for hiding this comment

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

I did my review! Thank you Joseph for this contribution, which seems quite technical. The core is very good. Yet, I think the code could definitely be cleaner.

Also, a few general remarks:

  • Generally, variable names — especially those that are only one character — can be a little cryptic.
  • I would remove the default variable name on the _frobenius_charpoly_... methods.
  • I would suggest adding comments that outline / split the rough different steps in your algorithms. You may use blank lines, sporadically and wisely, if necessary.
  • Be careful that your paragraphs are correctly flowed (I use gqip or gwip on vim to achieve the result).

For the Gekeler algorithm, should we check from the start the the constant coefficient generates the base field? If not, we should highlight in the doctest that the computation may fail, and raise an exception. @DavidAyotte, @xcaruso, what do you think?

Can you update methods frobenius_trace and frobenius_norm?

Also, you can use a linter to check that your code respects code-writing conventions. For Python, the convention is PEP8, and flake8 is the most used linter. In the terminal, you can run flake8 finite_drinfeld_module.py. Of course, there are some errors that you have to ignore (typically, sage: lines often exceed the character limit).

from sage.modules.free_module_element import vector
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule

from sage.functions.other import ceil, sqrt
from sage.all import prod

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a blank line here? I forgot to do this in the first place (PEP8)...

is defined in [Gek1991]_. An important feature of this
polynomial is that it is a monic univariate polynomial with
coefficients in the function ring. As in our case the function
Copy link
Contributor

Choose a reason for hiding this comment

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

What about:

An important feature of this polynomial is that it is monic, univariate, and his coefficients live in the function ring.


ALGORITHM:
"""
algorithms = {'gekeler', 'crystalline'}
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't there a way to automatically create this set from the list of methods?


"""
if not isinstance(psi, DrinfeldModule):
raise TypeError("Input must be a Drinfeld module")
Copy link
Contributor

Choose a reason for hiding this comment

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

truedat

raise TypeError("Input must be a Drinfeld module")
if self.category() != psi.category():
raise TypeError("Drinfeld modules are not in the same category")
return self.rank() == psi.rank() and\
Copy link
Contributor

Choose a reason for hiding this comment

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

Space before backslash.

@ymusleh
Copy link
Contributor Author

ymusleh commented Mar 24, 2023

Thanks for the very detailed and thorough review; I will try to address the requested changes shortly. To reply to a couple of your points.

  • Generally, variable names — especially those that are only one character — can be a little cryptic.

I'm thinking for q and n in particular there isn't a great alternative and their usage should hopefully be clear from the documentation.

For the Gekeler algorithm, should we check from the start the the constant coefficient generates the base field? If not, we should highlight in the doctest that the computation may fail, and raise an exception. @DavidAyotte, @xcaruso, what do you think?

The Gekeler algorithm can return the correct result in the non-prime field case, so I think it should be fine as is.

@kryzar
Copy link
Contributor

kryzar commented Mar 24, 2023

I will try to address the requested changes shortly.

No rush!

I'm thinking for q and n in particular there isn't a great alternative and their usage should hopefully be clear from the documentation.

For those, I agree. I was thinking about c, and maybe S, SM C, C0.

For the Gekeler algorithm, should we check from the start the the constant coefficient generates the base field? If not, we should highlight in the doctest that the computation may fail, and raise an exception. @DavidAyotte, @xcaruso, what do you think?

The Gekeler algorithm can return the correct result in the non-prime field case, so I think it should be fine as is.

I'm okay with this!

@ymusleh
Copy link
Contributor Author

ymusleh commented Mar 28, 2023

Can you update methods frobenius_trace and frobenius_norm?

I'm mostly done with the updates, and I think this is the last major point to address. I'm unsure what the best approach is here, as I find it a bit excessive to have methods merely to access a single coefficient of the characteristic polynomial. However, for the Frobenius norm at least, there is some justification for such a method as it has a simple formula for the prime field case. I think there are 3 possibilities, and am happy for input on which makes the most sense:

  1. Keep the methods as rank 2 exclusive.
  2. Remove the methods entirely.
  3. Implement the methods for general rank Drinfeld modules, shortcutting the full char poly computation when possible.

@xcaruso
Copy link
Contributor

xcaruso commented Mar 28, 2023

IMO, it's better to keep these methods, even if they look redundant.

@kryzar
Copy link
Contributor

kryzar commented Mar 29, 2023

Can you update methods frobenius_trace and frobenius_norm?

I'm mostly done with the updates, and I think this is the last major point to address. I'm unsure what the best approach is here, as I find it a bit excessive to have methods merely to access a single coefficient of the characteristic polynomial. However, for the Frobenius norm at least, there is some justification for such a method as it has a simple formula for the prime field case. I think there are 3 possibilities, and am happy for input on which makes the most sense:

1. Keep the methods as rank 2 exclusive.

2. Remove the methods entirely.

3. Implement the methods for general rank Drinfeld modules, shortcutting the full char poly computation when possible.

I would keep them both. Removing them would require a formal deprecation. Obviously the third option is the best. I haven't looked at algorithms that compute the trace without computing the full characteristic polynomial. And I will not have time in the short term. For the time being, we can compute the full polynomial and return the coefficient, while insisting in the documentation that this is inefficient.

EDIT. Let $\mathfrak{p}$ be the $\mathbb{F}_q[X]$ characteristic of the base field $K$. Let $\Delta$ be the coefficient of index $r$ of the Drinfeld module, and let $\omega$ be its constant coefficient. Let $d = [K: \mathbb{F}_q]$ and $e = [\mathbb{F}_q(\omega): \mathbb{F}_q]$. Don't we always have the following formula?

$$ \mathrm{FrobeniusNorm}(\phi) = \frac{(-1)^d}{\mathrm{Norm}_{K/\mathbb{F}_q}(\Delta)} \mathfrak{p}^{d / e} $$

In that case, we could use it for the method frobenius_norm. I also noticed that you deleted the lines

        self._frobenius_norm = None
        self._frobenius_trace = None

from the constructor. If we implement frobenius_norm with a formula, we should at least keep self._frobenius_norm = None.

@xcaruso
Copy link
Contributor

xcaruso commented Mar 29, 2023

I haven't looked at algorithms that compute the trace without computing the full characteristic polynomial.

For the motivic algorithm (and I would say that it's the same for the crystalline algorithm), I think it suffices to return the trace/determinant of the intermediate matrix we compute, instead of its characteristic polynomial.

Copy link
Contributor

@kryzar kryzar left a comment

Choose a reason for hiding this comment

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

Thank you very much Joseph for all the changes you did, as it was probably a bit tedious!

Comment on lines 266 to 340
methods = [method for method in dir(self)
if method.startswith('_frobenius_charpoly_')]
method_name = f'_frobenius_charpoly_{algorithm}'
Copy link
Contributor

Choose a reason for hiding this comment

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

Marvelous!

sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5])
sage: phi.frobenius_charpoly()
X^2 + ((4*z2 + 4)*T^3 + (z2 + 3)*T^2 + 3*T + 2*z2 + 3)*X + 3*z2*T^6 + (4*z2 + 3)*T^5 + (4*z2 + 4)*T^4 + 2*T^3 + (3*z2 + 3)*T^2 + (z2 + 2)*T + 4*z2

Copy link
Contributor

Choose a reason for hiding this comment

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

Missing :::

Suggested change
::

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')

def _frobenius_charpoly_crystalline(self, var='X'):
Copy link
Contributor

Choose a reason for hiding this comment

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

My only comment for this method is the following: I think you did not address my previous comment on the var='X' for _frobenius_charpoly_... methods. Shouldn't we remove it here? It seems redundant.

The rest of the method is perfect, thank you for the changes!

any rank.

This method is private and should not be directly called.
Instead, use :meth:`frobenius_charpoly`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Instead, use :meth:`frobenius_charpoly`.
Instead, use :meth:`frobenius_charpoly` with the option
`algorithm='crystalline'`.

Comment on lines 348 to 351
companion_initial = prod([companion(i)
for i in range(nrem, 0, -1)])
companion_step = prod([companion(i)
for i in range(nstar + nrem, nrem, -1)])
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
companion_initial = prod([companion(i)
for i in range(nrem, 0, -1)])
companion_step = prod([companion(i)
for i in range(nstar + nrem, nrem, -1)])
companion_initial = prod([companion(i) for i in range(nrem, 0, -1)])
companion_step = prod([companion(i)
for i in range(nstar + nrem, nrem, -1)])

of the base field.

This method is private and should not be directly called.
Instead, use :meth:`frobenius_charpoly`.

.. WARNING:
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know why I can't select the whole block, but here is a suggestion:

            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.

@@ -369,18 +413,19 @@ def _frobenius_charpoly_gekeler(self, var='X'):
sage: phi = DrinfeldModule(A, [z, 0, z])
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you replace Line 109 with


            ::

Copy link
Contributor

Choose a reason for hiding this comment

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

The Rubric line? Not sure if this is what you want so I'll leave this for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I apologize, this was not clear at all by me. Apparently, what I meant was Line 418. I tagued you in the relevant comment.


Let `n` be the degree of the base field over `\mathbb{F}_q` Then the
Frobenius norm has degree `n`.
Let `C(X) = \sum_{i=0}^r a_iX^{r-i}` denote the characteristic
Copy link
Contributor

Choose a reason for hiding this comment

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

Whare are you using r-i instead of i?
Also, I would use is defined as instead of simply is.

Same remark for frobenius_trace.

Copy link
Contributor

Choose a reason for hiding this comment

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

Notationally it can actually be a bit cleaner to do it this way, but to ensure consistency with documentation elsewhere I'll revert it.

Copy link
Contributor

@kryzar kryzar Mar 30, 2023

Choose a reason for hiding this comment

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

Ok. I don't have a strong opinion on this. At first glance it looks like i is a bit easier for the user that won't go into details. That being said, I believe you. @xcaruso, @DavidAyotte: any recommendation?

EDIT. That being said, it makes more sense to me that a norm would be the degree-$0$ coefficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The main gain is that the degree constraint looks a little nicer, and the sum ranges from 1 to r instead of 0 to r - 1, and reflects the "order" of the term, with the norm being an order r object and the trace being an order 1 object. Not really a huge issue so I'll leave it with the standard convention.

Comment on lines 686 to 687
return self.rank() == psi.rank() and \
self.frobenius_charpoly() == psi.frobenius_charpoly()
Copy link
Contributor

Choose a reason for hiding this comment

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

After all it looks better with the and at the begining of the line:

Suggested change
return self.rank() == psi.rank() and \
self.frobenius_charpoly() == psi.frobenius_charpoly()
return self.rank() == psi.rank() \
and self.frobenius_charpoly() == psi.frobenius_charpoly()

@@ -706,7 +720,6 @@ def is_ordinary(self):
`\mathbb{F}_q[T]`-characteristic under the Drinfeld module
is purely inseparable; see [Gek1991]_, Proposition 4.1.
"""
self._check_rank_two()
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you, I had forgotten about this!

@ymusleh
Copy link
Contributor Author

ymusleh commented Oct 21, 2023

This request should be ready for review now.

Comment on lines 648 to 649
Traceback (most recent call last):
TypeError: Drinfeld modules are not in the same category
Copy link
Contributor

Choose a reason for hiding this comment

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

Ellipsis is missing

Comment on lines 655 to 656
Traceback (most recent call last):
TypeError: input must be a Drinfeld module
Copy link
Contributor

Choose a reason for hiding this comment

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

Ellipsis is missing

K = self.base_over_constants_field()
n = K.degree(self._Fq)
char = self.characteristic()
norm = K(self.coefficients()[-1]).norm()
Copy link
Contributor

Choose a reason for hiding this comment

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

So, should we implement it instead?

@DavidAyotte
Copy link
Member

Hi Joseph, most of this PR looks good for me. If you want, you can add your name to the author list at the top of the file, and mention what you implemented.

@xcaruso
Copy link
Contributor

xcaruso commented Oct 25, 2023

Thanks.
It looks good to me except maybe this question of the computation of the norm of the Frobenius for which, I think, there is an explicit formula (it's basically the characteristic to the rank) which should be much most efficient to evaluate. Am I wrong?

@ymusleh
Copy link
Contributor Author

ymusleh commented Oct 25, 2023

Thanks. It looks good to me except maybe this question of the computation of the norm of the Frobenius for which, I think, there is an explicit formula (it's basically the characteristic to the rank) which should be much most efficient to evaluate. Am I wrong?

Sorry, I meant to reply to the previous comment directly but forgot. So I believe it is computed via the formula in frobenius_norm. It isn't done this way for the characteristic polynomial method as I don't think it improves the efficiency by enough relative to the amount of extra code that would be introduced to compute the norm coefficient separately.

@xcaruso
Copy link
Contributor

xcaruso commented Oct 25, 2023

Oh sorry, you're right, I misread the code.
So everything's fine with me. I'm ready to give a positive review if nobody complains.

if algorithm in algorithms:
return getattr(self, \
f'{self.frobenius_charpoly.__name__}_{algorithm}')(var)
raise NotImplementedError(f'Algorithm \"{algorithm}\" not implemented')
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
raise NotImplementedError(f'Algorithm \"{algorithm}\" not implemented')
raise NotImplementedError(f'algorithm \"{algorithm}\" not implemented')

Comment on lines 266 to 269
Return the characteristic polynomial of the Frobenius
endomorphism for any rank if the minimal polynomial is
equal to the characteristic polynomial. Currently only
works for Drinfeld modules defined over Fq[T].
Copy link
Member

Choose a reason for hiding this comment

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

The description is usually formatted in this way: a one sentence description of the form "Return..." then, on a second paragraph, a longer description.

Suggested change
Return the characteristic polynomial of the Frobenius
endomorphism for any rank if the minimal polynomial is
equal to the characteristic polynomial. Currently only
works for Drinfeld modules defined over Fq[T].
Return the characteristic polynomial of the Frobenius
endomorphism for any rank if the minimal polynomial is
equal to the characteristic polynomial.
Currently only works for Drinfeld `\mathbb{F}_q[T]`-modules.

Comment on lines 271 to 277
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.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.
.. 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.


INPUT:

- ``var`` (default: ``'X'``) -- the name of the second variable
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- ``var`` (default: ``'X'``) -- the name of the second variable
- ``var`` (string, default: ``'X'``) -- the name of the second variable

Also, I would change var to name.

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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
modules where the degree 1 coefficient of \phi_T is
modules where the degree 1 coefficient of `\phi_T` is

if sys.right_nullity() == 0:
sol = list(sys.solve_right(rhs))
else:
raise ValueError("Can't solve system for characteristic polynomial")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
raise ValueError("Can't solve system for characteristic polynomial")
raise ValueError("can't solve system for characteristic polynomial")

Comment on lines 347 to 352
Method to compute the characteristic polynomial of the
Frobenius endomorphism using Crystalline cohomology.
Currently only works for Drinfeld modules defined over
Fq[T], but otherwise does not impose any other constraints,
including on the rank, minimal polynomial, or that the Drinfeld
module is defined over the prime field.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Method to compute the characteristic polynomial of the
Frobenius endomorphism using Crystalline cohomology.
Currently only works for Drinfeld modules defined over
Fq[T], but otherwise does not impose any other constraints,
including on the rank, minimal polynomial, or that the Drinfeld
module is defined over the prime field.
Return the characteristic polynomial of the
Frobenius endomorphism using Crystalline cohomology.
Currently only works for Drinfeld `\mathbb{F}_q[T]`-modules,
but otherwise does not impose any other constraints,
including on the rank, minimal polynomial, or that the Drinfeld
module is defined over the prime field.

Comment on lines 374 to 399
ALGORITHM:

Compute the characteristic polynomial of the endomorphism
acting on the crystalline cohomology of a Drinfeld module.
A recurrence on elements of the cohomology allows us to
compute a matrix representation of the Frobenius endomorphism
efficiently using a companion matrix method.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ALGORITHM:
Compute the characteristic polynomial of the endomorphism
acting on the crystalline cohomology of a Drinfeld module.
A recurrence on elements of the cohomology allows us to
compute a matrix representation of the Frobenius endomorphism
efficiently using a companion matrix method.
ALGORITHM:
Compute the characteristic polynomial of the endomorphism
acting on the crystalline cohomology of a Drinfeld module.
A recurrence on elements of the cohomology allows us to
compute a matrix representation of the Frobenius endomorphism
efficiently using a companion matrix method.

Comment on lines 403 to 404
raise NotImplementedError(\
"Can't solve system for characteristic polynomial")
Copy link
Member

Choose a reason for hiding this comment

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

I prefer the first error message which is more explicative. I would either keep what Joseph wrote or merge the two:

            raise NotImplementedError(\
                    "Algorithm failed: cannot solve system for characteristic polynomial")

@@ -470,6 +632,61 @@ def invert(self, ore_pol):
raise ValueError('input must be in the image of the Drinfeld '
'module')

def is_isogenous(self, psi):
r"""
Return ``True`` whethere ``self`` is isogenous to the other
Copy link
Member

Choose a reason for hiding this comment

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

There is a small typo: "whethere" should be "whether"

@DavidAyotte
Copy link
Member

I'm also ok for a positive review. Maybe @kryzar wants to go over the changes one last time.

@kryzar kryzar self-requested a review October 25, 2023 18:40
@@ -183,46 +257,62 @@ 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'):
def frobenius_charpoly(self, var='X', algorithm='crystalline'):
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't 'motive' be an available option for the algorithm flag? This is implemented in DrinfeldModuleMorphism.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I assumed that would get added when you and Xavier implement the other algorithms specialized for the frobenius case, but I can add a wrapper for the morphism method if you'd like.

Copy link
Contributor

@xcaruso xcaruso Oct 26, 2023

Choose a reason for hiding this comment

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

I'm actually fine with both solutions: adding it here or in a separate PR.
However, I think that if you add it, you should document it a bit in the generic method frobenius_charpoly.

Copy link
Contributor

@kryzar kryzar Oct 26, 2023

Choose a reason for hiding this comment

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

I think this should be implemented. However, I don't really care if it is here or in a separate PR. Maybe a separate PR would be easier for Joseph tho, so we can do that!

EDIT.: Well Joseph did it. Thanks a lot! But I agree that in that case the documentation needs to explain that we can use algorithm to choose between different algorithms, etc.

K = self.base_over_constants_field()
n = K.degree(self._Fq)
char = self.characteristic()
norm = K(self.coefficients()[-1]).norm()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be a good idea!

ymusleh and others added 2 commits November 3, 2023 12:13
…_module.py

Co-authored-by: Antoine Leudière <clapped.hesitancy332@anonaddy.me>
Copy link

github-actions bot commented Nov 3, 2023

Documentation preview for this PR (built with commit 28b0705; changes) is ready! 🎉

@kryzar
Copy link
Contributor

kryzar commented Nov 3, 2023

Thanks a lot @ymusleh!

@vbraun vbraun merged commit a901ccc into sagemath:develop Nov 5, 2023
11 of 17 checks passed
@mkoeppe mkoeppe added this to the sage-10.2 milestone Nov 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compute the characteristic polynomial of the Frobenius endomorphism of a Drinfeld module
8 participants