Open 4c814734-baf9-4879-ba94-57f68b74985f opened 4 years ago
Branch: u/gh-theHawke/finite_field_roots
Commit: 533fd5d
Description changed:
---
+++
@@ -1 +1 @@
-
+Implemented a "new" algorithm for nth root in finite fields/rings (adapted from the paper "On taking roots in finite fields" by Adelman et al. 1977). My tests show similar or slightly better performance at small prime moduli and significantly better performance in many cases at large prime moduli. This performance increase is mainly due to not relying on K.multiplicative_generator() which needs to factor (p-1) and leads to erratic performance.
Author: Hauke Neitzel
The branch points to 9.0.beta0; did you perhaps forget to push your branch?
Replying to @pjbruin:
The branch points to 9.0.beta0; did you perhaps forget to push your branch?
Apologies, I didn't realize git trac create
forks from develop instead of the current branch.
EDIT: Also apologies that it is not compiling, "it was working fine yesterday" and I have no idea what Cython is trying to tell me.
EDIT2: it was unicode quotes in the citation of the paper, ~600 lines down
Branch pushed to git repo; I updated commit sha1. New commits:
a10ba65 | it compiles again |
Hi,
Please post your benchmarks instead of writing My tests show similar or slightly better performance at small prime moduli and significantly better performance in many cases at large prime moduli.
The option algorithm="AMM"
should be documented in the INPUT
section somewhere.
There musts be tests associated to your new algorithm in the section EXAMPLES:
and possibly TESTS:
.
It is often better to avoid coercion (ie comparison, operation, etc of elements with distinct parents). For example
g**((q-1)/r) == 1
-> (g**((q-1)/r)).is_one()
A*G**lam != 1
-> not (A*G**lam).is_one()
J = 0
-> J = K.zero()
J == 0
-> J.is_zero()
g += 1
-> g += one
where one
would have been initialized to K.one()
Branch pushed to git repo; I updated commit sha1. New commits:
495b65b | suggested changes |
Branch pushed to git repo; I updated commit sha1. New commits:
18d1b1a | expanded doctests |
Replying to @videlec:
It is often better to avoid coercion (ie comparison, operation, etc of elements with distinct parents). For example
g**((q-1)/r) == 1
->(g**((q-1)/r)).is_one()
A*G**lam != 1
->not (A*G**lam).is_one()
J = 0
->J = K.zero()
J == 0
->J.is_zero()
g += 1
->g += one
whereone
would have been initialized toK.one()
Thank you for the feedback!
I have now included the algorithm in the INPUT
, EXAMPLES
and TEST
sections and I have attached my small benchmark script to this ticket.
I have also integrated your suggestions with regard to coercion, however since J
is an exponent like k
and v
, I believe it should always be an integer and shouldn't be an element of K
.
Lastly, I have expanded the algorithm to include the self.is_one()
case.
Attachment: root_test.sage.gz
small benchmark comparing the two algorithms
sample output of the benchmark
Attachment: root_test_output.txt
Branch pushed to git repo; I updated commit sha1. New commits:
9d6a86e | fixed failing FinitePolyExtElement test |
You introduced this doctest
sage: a._nth_root_common(4, True, "AMM", cunningham = True) # optional - cunningham
with no output!
It would be better to not duplicate
if all:
nthroot = g**q1overn
L = [self]
for i in range(1,n):
self *= nthroot
L.append(self)
return L
(you could move that out of the if/else
)
Branch pushed to git repo; I updated commit sha1. New commits:
3d4fcb6 | moved common code out of the algorithms and fixed doctest |
There are trailing whitespaces in several place (ie space sign before line break). Please remove them. They appear in red if you do git diff YOUR_BRANCH MAIN_BRANCH
.
This is ugly
if y.nth_root(r)**r != y: raise RuntimeError
if y.nth_root(r, algorithm='AMM')**r != y: raise RuntimeError
if (y^41).nth_root(41*r)**(41*r) != y^41: raise RuntimeError
if (y^41).nth_root(41*r, algorithm='AMM')**(41*r) != y^41: raise RuntimeError
if (y^307).nth_root(307*r)**(307*r) != y^307: raise RuntimeError
if (y^307).nth_root(307*r, algorithm='AMM')**(307*r) != y^307: raise RuntimeError
What about
for s, algo in product([1,41,307], ['Johnston', 'AMM']):
assert (y^s).nth_root(s*r, algorithm=algo)**(s*r) == y^s
(you need a from itertools import product
)
More interestingly, if AMM is faster for larger moduli why not make it the default in these cases? Would this dispatch be reasonable:
Replying to @videlec:
More interestingly, if AMM is faster for larger moduli why not make it the default in these cases? Would this dispatch be reasonable:
- if moduli is small just use Johnston
- if moduli is large
- if a multiplicative generator is already available use Johnston
- if not use AMM
Since AMM is actually slightly faster even at small moduli, it could also be made the default in general unless the multiplicative generator is available.
That said, I am not sure how to check whether the generator is available. There does seem to be some caching but I can't see a way to explicitly check it.
There is some caching
sage: R = Zmod(next_prime(2**10))
sage: r = R.multiplicative_generator()
sage: R.multiplicative_generator() is r
True
or finite fields
sage: R = GF(next_prime(2**10)**2)
sage: r = R.multiplicative_generator()
sage: R.multiplicative_generator() is r
True
The cache is implemented via the decorator @cached_method
. To access the cache here is what could be done
gen = R.multiplictative_generator.cache
if gen is not None:
# gen is the multiplicative generator
...
else:
# do not trigger multiplicative generator computation
...
You should also check timings over very small moduli, e.g things like GF(2^5)
or GF(3^10)
.
Attachment: root_test.2.sage.gz
Benchmark script for comparing the two algortihms in various situations
sample output of the benchmark (new version)
Attachment: root_test_output.2.txt
I added a couple new tests to the benchmarks. Unless you want to set a threshold at something like 11 I don't think it is worth using Johnston for small values.
Whether a particular finite field is faster with Johnston or AMM seems to be fairly randomly distributed with AMM's advantage getting slightly larger as the fields order increases (goes up to 85% AMM better for orders below 10000).
I would suggest making the default simply depend on the availability of a cached multiplicative generator.
Branch pushed to git repo; I updated commit sha1. New commits:
5845925 | Made AMM the default algorithm unless a multiplicative generator is available cached |
Ticket retargeted after milestone closed
Batch modifying tickets that will likely not be ready for 9.1, based on a review of the ticket title, branch/review status, and last modification date.
The reference would better go in the dedicated file $SAGE_ROOT/src/doc/en/reference/references/index.rst
. You can make citations using ReST citations.
Setting new milestone based on a cursory review of ticket status, priority, and last modification date.
Setting a new milestone for this ticket based on a cursory review.
no longer applies
Implemented a "new" algorithm for nth root in finite fields/rings (adapted from the paper "On taking roots in finite fields" by Adelman et al. 1977). My tests show similar or slightly better performance at small prime moduli and significantly better performance in many cases at large prime moduli. This performance increase is mainly due to not relying on K.multiplicative_generator() which needs to factor (p-1) and leads to erratic performance.
Component: finite rings
Author: Hauke Neitzel
Branch/Commit: u/gh-theHawke/finite_field_roots @
5845925
Issue created by migration from https://trac.sagemath.org/ticket/28585