-
Notifications
You must be signed in to change notification settings - Fork 40
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
Faster primitive root #127
Conversation
AFAIU neither tests nor arbitrary instances were changed, so they run through the same range of values as before. And if they succeeded before and fail now, then something went wrong. A wild guess is to add {-# SPECIALIZE isPrimitiveRoot' :: CyclicGroup Int -> Int -> Bool #-}
{-# SPECIALIZE isPrimitiveRoot' :: CyclicGroup Word -> Word -> Bool #-} |
My suspicion is that overflow occurred in both the implementation and the tests - it's notable that the Integer and Natural versions pass. In addition, the test cases that fail always seem to be those which have overflow, and the new implementation has no chance of internal overflow since prefValue is never used.
|
Confirming my suspicion, the test which fails ( |
Yes, you are right: previously both implementation and test were incorrect. Thanks for noticing this. I propose to amend the test, avoiding overflow in arguments of import qualified Math.NumberTheory.GCD as GCD
isPrimitiveRoot'Property1
:: (Eq a, Integral a, UniqueFactorisation a)
=> AnySign a -> CyclicGroup a -> Bool
isPrimitiveRoot'Property1 (AnySign n) cg
= gcd (toInteger n) (prefValue (castPrefactored (cyclicGroupToModulo cg))) == 1
|| not (isPrimitiveRoot' cg n)
castPrefactored :: Integral a => Prefactored a -> Prefactored Integer
castPrefactored = fromFactors . GCD.splitIntoCoprimes . map (first toInteger) . GCD.toList . prefFactors
Sounds good. It is also worth to add a benchmark, showing the achieved performance improvement. |
Should be all completed now. Benchmarking: I've added benchmarks for The new version of |
forceCyclic (Just CG2) = -2 | ||
forceCyclic (Just CG4) = -1 | ||
forceCyclic (Just (CGOddPrimePower !_p !_k)) = 1 | ||
forceCyclic (Just (CGDoubleOddPrimePower !_p !_k)) = 2 |
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.
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.
Fixed.
EDIT: maybe not. The problem is that this requires NFData PrimeNat
(or something analogous). This can't be done as a standalone/orphan instance since PrimeNat
is in Math.NumberTheory.Primes.Types
, which is a hidden module. I'm hesitant to include the instance in the hidden module since this involves including deepseq
in the library dependencies.
, testSmallAndQuick "Int" (isPrimitiveRoot'Property1 :: AnySign Int -> CyclicGroup Int -> Bool) | ||
, testSmallAndQuick "Word" (isPrimitiveRoot'Property1 :: AnySign Word -> CyclicGroup Word -> Bool) | ||
, testSmallAndQuick "Int" (isPrimitiveRoot'Property1 :: AnySign Int -> CyclicGroup Int -> Bool) -- dubious test | ||
, testSmallAndQuick "Word" (isPrimitiveRoot'Property1 :: AnySign Word -> CyclicGroup Word -> Bool) -- dubious test |
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.
They are not dubious anymore, right?
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.
Fixed.
|| case 0 `modulo` m of | ||
SomeMod (_ :: Mod t) -> genericLength (filter isPrimitiveRoot [(minBound :: Mod t) .. maxBound]) == totient (totient m) | ||
InfMod{} -> False | ||
|
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.
If you need to promote m
to type level, there is a shorter approach:
case someNatVal m of
SomeNat (_ :: Proxy t) -> ...
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.
Fixed.
@@ -90,6 +95,13 @@ isPrimitiveRootProperty4 (AnySign n) (Positive m) | |||
SomeMod n' -> not (isPrimitiveRoot n') | |||
InfMod{} -> False | |||
|
|||
isPrimitiveRootProperty5 :: Positive Natural -> Bool | |||
isPrimitiveRootProperty5 (Positive m) |
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.
Let us use Test.QuickCheck.Small
newtype here. Positive
does not mean much for Natural
and I am worried that if the Arbitrary
instance of Natural
gets changed, this test may take a really long time.
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.
I'm not entirely sure what your proposed fix here is, could you elaborate? Positive Natural
seems to be used fairly often across the test suites here, including in isPrimitiveRootProperty2
.
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.
My proposal was to write
isPrimitiveRootProperty5 :: Small Natural -> Bool
isPrimitiveRootProperty5 (Smalll m) = ...
But now I realised that there is no Small
newtype in smallcheck
and my approach actually would not work. Nevermind.
Wow! This is impressive. |
Overall looks good to me, but Travis build fails with
I do not want this module to be publicly exposed. |
Everything should be working now! |
Thanks for your contribution! Merged. |
(As mentioned in #119)
Unfinished so far, but coprimality tests for Int and Word fail seemingly due to overflow: Is it okay to just remove these tests? I think the logic in the new
isPrimitiveRoot'
is correct, but sanity checks would be appreciated also; the idea is that to check if g is a primitive root mod p^2 it suffices to check that g is a primitive root mod p and that g^(p-1) mod p^2 is not 1. In addition, these same conditions are exactly the conditions for g to be a primitive root mod p^k for any k. I've kept the method for testing primitive roots mod p as wikipedia describes. Similarly to check primitive roots mod 2p^k it suffices to check g is a primitive root mod p^k and that g mod 2p^k is odd, so just that g is odd.I'd also like to add a test counting the number of primitive roots and possibly laziness tests. Are there any other appropriate tests?