-
-
Notifications
You must be signed in to change notification settings - Fork 641
Implement Square Roots to FiniteFields Category #40401
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
base: develop
Are you sure you want to change the base?
Conversation
Added the following methods to the finite field category - quadratic_non_residue - square_root and sqrt - _tonelli and _cipolla: the algorithm for finding a square root - is_square Moved is_square and sqrt methods from element.pyx to their proper categories.
Brian did this work as part of an undergrad mentorship program at the University of Calgary, and I was the grad student mentoring him. I'll review this for mathematical correctness/style/documentation/etc., but since I worked closely with him on this I'd like a second person to review it as well (especially for the refactoring that was done to move the default square root implementation out of element.pyx and into the appropriate category). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass, mostly style things and wording in the documentation.
Co-authored-by: Vincent Macri <[email protected]>
Co-authored-by: Vincent Macri <[email protected]>
Co-authored-by: Vincent Macri <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring fixes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstring formatting
Co-authored-by: Vincent Macri <[email protected]>
Not completely related to this change: @tobiasdiez another |
do fix the lints. Also, build the HTML files locally (the CI for HTML preview is currently broken) and check that they're rendered correctly. They're incorrect in many places. Check some existing docstring for the formatting. I think first word should be in infinitive form, first sentence has full stop, and no bullet point in output if only one output. (Do verify that.) Refer to #39798 . First character of error message should not be capitalized. What's the algorithm for computing square root in e.g. |
Yes, ruff is not supporting cython. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docstrings, cleanup.
Curious, did you approve the CI run? I don't have permission to approve CI runs was wondering who did (so I can ping them to do it again).
That's annoying that the CI is broken. The process to build the docs with Meson is not very well-documented (I should get around to filing a bug report about that eventually...), but I eventually figured it out and sent the instructions to Brian.
I'm not too sure what you mean by wanting to distinguish with the naming, but I don't think we need to worry about it. In terms of what algorithm That said, I do think that at least for |
- Changed order mod 2 call to characteristic equal in is_square() - Capitalization in docstrings - Correct chapter in docstring for sqrt - Co-authored-by: Vincent Macri <[email protected]>
Fixed Latex documentation and error message capitalization. Instead of picking randomly for a quadratic non residue we now iterate through the field until we find one, making sqrt and _tonelli deterministic, _cipolla is still random pick.
Yes, I know. But that is a breakage of Liskov substitution principle — in general, elements of parents in In practice, yes, the user probably wouldn't care and just not pass
Bad idea. Methods with name start with underscore should not be called by the user. |
I meant Sage itself could call that, not the user. |
Sage's current behaviour doesn't respect this principle. The implementation of
|
I consider that a bug. While the behavior remains, we could at least not introduce more of it. |
So the options to deal with this are
|
Fair enough. For this PR I think not breaking the Liskov substitution principle with respect to the
Using a heuristic to decide what algorithm to use is only a good option if we have an accurate heuristic for which algorithm is better for all cases, and I don't think we do. If there are any cases where Cipolla is meaningfully faster than Tonelli but the heuristic still decides to use Tonelli then we'd still want to have an optional I don't like the idea of adding a public In theory my preference would be to add the Given those issues, I think the only realistic solution is to add the @user202729 What's your preference for fixing this? Any other ideas? |
I was thinking of something like class FiniteFieldGeneric:
_default_sqrt_algorithm = "sage_tonelli"
def sqrt(algorithm=None):
if algorithm is None:
algorithm = self._default_sqrt_algorithm
if algorithm == "sage_tonelli":
return self._sqrt_sage_tonelli()
elif algorithm == "pari":
if hasattr(self, '_sqrt_pari'): # alternatively can also provide a default implementation of `_sqrt_pari` that raise NotImplementedError or ValueError
return self._sqrt_pari() # else fallthrough
elif ...:
...
raise NotImplementedError(f"algorithm {algorithm} not supported for elements of {self.parent()}")
def _sqrt_sage_tonelli():
...
class FiniteFieldPari(FiniteFieldGeneric):
def _sqrt_pari(self):
...
_default_sqrt_algorithm = "pari"
class FiniteFieldGivaro(FiniteFieldGeneric):
def _sqrt_givaro(self):
...
_default_sqrt_algorithm = "givaro" but then there's While it ought to be possible to deduplicate/clean up things, depends on how difficult it is to do so, it may be reasonable to just get the current pull request in in the current situation anyway. In that case, my only remaining complaint is the naming of the
|
I think it is definitely possible but it would be large enough to warrant its own PR. I would be in favour of getting this PR in in its current state and opening a separate issue to clean up the finite field square root code so its less likely to be forgotten about. So I think we're in agreement that this is ready to merge, pending the CI run. I do disagree with the suggestion to call the algorithm
Sage doesn't make any guarantees on what algorithm external libraries use internally, so I don't think it would be confusing if Givaro uses Tonelli internally (it doesn't) because nowhere would Sage document that fact or guarantee it to be true if your Givaro version changes. Additionally, I don't see a reason to make the Tonelli implementation added in this PR available for the Givaro/NTL/Flint implementations of finite fields, whatever they have is likely faster as it's written in a lower-level language like C. So that's another reason why there shouldn't be risk of confusion. Indeed, this PR does not allow one to use either the Tonelli or Cipolla implementations with any of the existing finite field implementations (at least not through public methods). The only way to run either of the square roots algorithms implemented in this PR is irreducible polynomial quotient rings over a finite field (or call the private
For what it's worth, Givaro represents elements as If Givaro is slower then that would indicate to me that it needs to be optimized (our Givaro implementation does some arithmetic mod 2 and division by 2 which could probably be replaced by bit operations). If you've measured them and don't see a substantial difference in speed then that's probably explained by Givaro only allowing finite fields of size up to In fact, Tonelli (very broadly speaking, skipping a lot of details here) computes
I think prefixing the algorithm name with While obviously this is subjective, I'd actually find it more confusing if it were called Basically, Tonelli(-Shanks) seems to be the fastest algorithm for most cases, although one can certainly construct finite fields where Cipolla is faster (as @Brian-Heckel shows in one of the examples in the In terms of your code snippet, this is how I think it should work (after refactoring, not for this PR). This is just in terms of the logic, the categories framework and the fact that the GF implementations don't get most of their behaviours from it complicates what the real code would look like of course. class FiniteFieldElementGeneric:
def sqrt(algorithm=None):
if algorithm == 'cipolla':
return self._cipolla()
return self._sqrt() # By defaulting to a sensible algorithm rather than throwing an error, it's easier for us to add/remove square root algorithms without breaking changes.
def _tonelli(self):
...
_sqrt = _tonelli
def _cipola(self):
...
class FiniteFieldElementPari(FiniteFieldElementGeneric):
def _sqrt_pari(self):
...
# I haven't checked what PARI does, but for this example let's assume it uses Tonelli, in which case there is no reason to offer the Sage Tonelli implementation when we have a C implementation available
_sqrt = _sqrt_pari
class FiniteFieldElementGivaro(FiniteFieldElementGeneric):
def _sqrt_givaro(self):
...
def sqrt(self, algorithm=None):
return self._sqrt_givaro() # The Givaro implementation is optimized enough that it doesn't make sense to use other algorithms, although it may be hard to see that in timings since Givaro only works for small finite fields and all algorithms are fast for small finite fields. To make this a lot shorter: I don't think every finite field implementation needs to support every square root algorithm, only the ones that someone would reasonably want to use. For the default category implementation this is Tonelli and Cipolla. For other implementations it would be whatever they currently use and maybe also Cipolla (with the exception of Givaro which I think should only use the algorithm it has now). If you still find the |
Fixes #39688
Added the following methods to the finite field category
quadratic_non_residue()
square_root()
andsqrt()
_tonelli()
and_cipolla()
: the algorithms for finding a square rootis_square()
Movedis_square()
andsqrt()
methods from element.pyx to their proper categories.Most profiling tests showed that Tonelli's algorithm outperforms Cipolla. However a profile with a Finite Field with order of a Cullen prime resulted in Cipolla outperforming Tonelli.
I'm running the meson build and currently cannot find the way to build the html documentation.
📝 Checklist
⌛ Dependencies