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

Discrete log #130

Merged
merged 40 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
df50d98
Add MultMod type
b-mehta Aug 21, 2018
e4f6fe7
Added PrimitiveRoot type and adjusted signatures
b-mehta Aug 21, 2018
1747210
Amend import structure
b-mehta Aug 22, 2018
73e79af
Adjusted tests to new types
b-mehta Aug 22, 2018
b81e174
Framework, test, bench for discrete logs
b-mehta Aug 22, 2018
75f4371
Added baby-step giant-step algorithm
b-mehta Aug 22, 2018
31abbf7
Removed redundant pragmas
b-mehta Aug 22, 2018
de81a94
MultMod stimes and improved error message
b-mehta Aug 22, 2018
d8e45bc
Import of Data.Semigroup for ghc>8.4 also
b-mehta Aug 22, 2018
ac3d84d
import Semigroup in DiscreteLog too
b-mehta Aug 22, 2018
3d35432
Merge branch 'master' into dirichlet-characters
b-mehta Aug 23, 2018
56b33ed
Additional tests for discrete logs [ci skip]
b-mehta Aug 23, 2018
6423c6a
Cosmetic changes
b-mehta Aug 25, 2018
8de36ca
More benchmarks for discrete logs
b-mehta Aug 25, 2018
e99b29c
Removed redundant comments and pragmas
b-mehta Aug 25, 2018
ec5fa71
Fix from review
b-mehta Aug 28, 2018
a6a1c1f
Basic refactoring of discrete log
b-mehta Aug 28, 2018
04a85cc
Refactor further, use Natural internally
b-mehta Aug 29, 2018
04119fe
Individual tests of large cases
b-mehta Aug 30, 2018
6dffb74
Handle prime power case better
b-mehta Aug 30, 2018
90102bb
Cosmetic changes and explicit import lists
b-mehta Aug 30, 2018
938d74f
Merge branch 'master' into dirichlet-characters
b-mehta Aug 30, 2018
0d78259
More careful imports to resolve cycle
b-mehta Aug 30, 2018
72239de
Remove redundant tests
b-mehta Aug 30, 2018
7251bf6
Remove redundant pragmas
b-mehta Aug 30, 2018
e77edcc
Cleaner benching output
b-mehta Aug 30, 2018
eaa5456
Fixed test suite
b-mehta Aug 30, 2018
b13f650
Cosmetic changes
b-mehta Aug 30, 2018
36edecc
Bench ranges of larger values
b-mehta Aug 31, 2018
dbe983a
(WIP) Pollard's rho for logarithms
b-mehta Aug 31, 2018
08d2481
Larger prime power benchmarks
b-mehta Sep 1, 2018
6df55dd
Some refactoring based on review
b-mehta Sep 1, 2018
6ec31ee
Merge remote-tracking branch 'upstream/master' into dirichlet-characters
b-mehta Sep 1, 2018
d9953ec
Changes from review
b-mehta Sep 1, 2018
3942bcc
Use BSGS for small moduli and pollard otherwise
b-mehta Sep 1, 2018
ed77e91
Share computation of p^k
b-mehta Sep 1, 2018
2ddd7e7
Probability of pollard failing is now much smaller
b-mehta Sep 2, 2018
f2386ab
Merge branch 'master' into dirichlet-characters
b-mehta Sep 10, 2018
ac1e46a
Changes from review
b-mehta Sep 10, 2018
1393fdf
Added todo comments
b-mehta Sep 12, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Math/NumberTheory/GaussianIntegers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import Data.Maybe (fromMaybe)
import Data.Ord (comparing)
import GHC.Generics

import qualified Math.NumberTheory.Moduli as Moduli
import qualified Math.NumberTheory.Moduli.Sqrt as Moduli
import Math.NumberTheory.Moduli.Sqrt (FieldCharacteristic(..))
import Math.NumberTheory.Powers (integerSquareRoot)
import Math.NumberTheory.Primes.Types (PrimeNat(..))
Expand Down
4 changes: 4 additions & 0 deletions Math/NumberTheory/Moduli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@
module Math.NumberTheory.Moduli
( module Math.NumberTheory.Moduli.Class
, module Math.NumberTheory.Moduli.Chinese
, module Math.NumberTheory.Moduli.DiscreteLogarithm
, module Math.NumberTheory.Moduli.Jacobi
, module Math.NumberTheory.Moduli.PrimitiveRoot
, module Math.NumberTheory.Moduli.Sqrt
) where

import Math.NumberTheory.Moduli.Chinese
import Math.NumberTheory.Moduli.Class
import Math.NumberTheory.Moduli.DiscreteLogarithm
import Math.NumberTheory.Moduli.Jacobi
import Math.NumberTheory.Moduli.PrimitiveRoot
import Math.NumberTheory.Moduli.Sqrt
44 changes: 44 additions & 0 deletions Math/NumberTheory/Moduli/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ module Math.NumberTheory.Moduli.Class
, invertMod
, powMod
, (^%)
-- * Multiplicative group
, MultMod
, multElement
, isMultElement
, invertGroup
, powMultMod
-- * Unknown modulo
, SomeMod(..)
, modulo
Expand All @@ -38,6 +44,11 @@ module Math.NumberTheory.Moduli.Class
, KnownNat
) where

#if __GLASGOW_HASKELL__ < 803
import Data.Semigroup
#endif

import Data.Maybe
import Data.Proxy
import Data.Ratio
import Data.Type.Equality
Expand Down Expand Up @@ -180,6 +191,39 @@ infixr 8 ^%
-- of type classes in Core.
-- {-# RULES "^%Mod" forall (x :: KnownNat m => Mod m) p. x ^ p = x ^% p #-}

-- | This type represents elements of the multiplicative group mod m, i.e.
-- those elemets which are coprime to m. Use @toMultElement@ to construct.
Copy link
Owner

Choose a reason for hiding this comment

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

Typo, "elements".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for this catch!

newtype MultMod m = MultMod { multElement :: Mod m }
Copy link
Owner

Choose a reason for hiding this comment

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

I've been never good with names :)
Maybe MultGroupMod?

deriving (Eq, Ord, Show)

instance KnownNat m => Semigroup (MultMod m) where
MultMod a <> MultMod b = MultMod (a * b)

instance KnownNat m => Monoid (MultMod m) where
mempty = MultMod 1
mappend = (<>)

instance KnownNat m => Bounded (MultMod m) where
minBound = MultMod 1
maxBound = MultMod (-1)

-- | Attempt to construct a multiplicative group element.
isMultElement :: KnownNat m => Mod m -> Maybe (MultMod m)
isMultElement a = if getVal a `gcd` getMod a == 1
Copy link
Owner

Choose a reason for hiding this comment

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

It is better to use getNatVal and getNatMod respectively.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, but out of interest why are these preferable?

Copy link
Owner

Choose a reason for hiding this comment

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

The internal representation of Mod m uses Natural both for value and for modulo.

then Just $ MultMod a
else Nothing

-- | Powers can be taken without leaving the multiplicative group.
powMultMod :: (KnownNat m, Integral a) => MultMod m -> a -> MultMod m
Copy link
Owner

Choose a reason for hiding this comment

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

We'd rather implement Data.Semigroup.stimes method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, sounds reasonable.

powMultMod (MultMod a) k
| k >= 0 = MultMod (powMod a k)
| otherwise = invertGroup (MultMod (powMod a (-k)))

-- | For elements of the multiplicative group, we can safely perform the inverse
-- without needing to worry about failure.
invertGroup :: KnownNat m => MultMod m -> MultMod m
invertGroup = MultMod . fromJust . invertMod . multElement
Copy link
Owner

Choose a reason for hiding this comment

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

It should be safe, but still it is better to pattern-match by Nothing manually and throw a custom error, which explicitly mentions invertGroup as a source.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, sounds good, fixed.


-- | This type represents residues with unknown modulo and rational numbers.
-- One can freely combine them in arithmetic expressions, but each operation
-- will spend time on modulo's recalculation:
Expand Down
46 changes: 46 additions & 0 deletions Math/NumberTheory/Moduli/DiscreteLogarithm.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
-- |
-- Module: Math.NumberTheory.Moduli.DiscreteLogarithm
-- Copyright: (c) 2018 Bhavik Mehta
-- License: MIT
-- Maintainer: Andrew Lelechenko <[email protected]>
-- Stability: Provisional
-- Portability: Non-portable
--

{-# LANGUAGE CPP #-}

module Math.NumberTheory.Moduli.DiscreteLogarithm where

#if __GLASGOW_HASKELL__ < 803
import Data.Semigroup
#endif
import Data.Maybe
-- import Data.List
Copy link
Owner

Choose a reason for hiding this comment

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

Remove commented lines?

-- import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as M
import Numeric.Natural

import Math.NumberTheory.Moduli.Class
import Math.NumberTheory.Moduli.PrimitiveRoot
import Math.NumberTheory.Prefactored
import Math.NumberTheory.Powers.Squares

-- | Computes the discrete logarithm. Currently uses a naive search.
discreteLogarithm
:: KnownNat m
=> PrimitiveRoot m
-> MultMod m
-> Natural
-- discreteLogarithm a b = let n = prefValue . groupSize . getGroup $ a
-- a' = unPrimitiveRoot a
-- vals = genericTake n $ iterate (<> a') mempty
-- in fromIntegral $ fromJust $ elemIndex b vals
Copy link
Owner

Choose a reason for hiding this comment

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

Remove commented lines?


discreteLogarithm a b = let n = prefValue . groupSize . getGroup $ a
a' = unPrimitiveRoot a
m = integerSquareRoot (n - 1) + 1 -- simple way of ceiling . sqrt
babies = fromInteger . getVal . multElement <$> iterate (<> a') mempty
table = M.fromList $ zip babies [0..(m-1)]
bigGiant = powMultMod a' (- fromIntegral m)
giants = fromInteger . getVal . multElement <$> iterate (<> bigGiant) b
in head [i*m + j | (v,i) <- zip giants [0..(m-1)], j <- maybeToList $ M.lookup v table]
Copy link
Owner

Choose a reason for hiding this comment

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

Let us convert lets into a where-clause.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My hope is to replace this with a more space-efficient implementation with Pollard's rho, so hopefully this function should not be in the merge and so if it's okay I'll leave this as is for now.
In the future I'll make sure to use where clauses in similar scenarios though!

40 changes: 30 additions & 10 deletions Math/NumberTheory/Moduli/PrimitiveRoot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,36 @@
{-# LANGUAGE UndecidableInstances #-}

module Math.NumberTheory.Moduli.PrimitiveRoot
( isPrimitiveRoot
-- * Cyclic groups
, CyclicGroup(..)
( -- * Cyclic groups
CyclicGroup(..)
, cyclicGroupFromModulo
, cyclicGroupToModulo
, groupSize
-- * Primitive roots
, PrimitiveRoot
, unPrimitiveRoot
, getGroup
, isPrimitiveRoot
, isPrimitiveRoot'
) where

import Control.DeepSeq
#if __GLASGOW_HASKELL__ < 803
import Data.Semigroup
#endif

import Math.NumberTheory.ArithmeticFunctions (totient)
import Math.NumberTheory.GCD as Coprimes
import Math.NumberTheory.Moduli (Mod, getNatMod, getNatVal, KnownNat)
import Math.NumberTheory.Moduli.Class (getNatMod, getNatVal, KnownNat, Mod, MultMod, isMultElement)
import Math.NumberTheory.Powers.General (highestPower)
import Math.NumberTheory.Powers.Modular
import Math.NumberTheory.Prefactored
import Math.NumberTheory.UniqueFactorisation
import Math.NumberTheory.Utils.FromIntegral

import Control.DeepSeq
import Control.Monad (guard)
import GHC.Generics
import Numeric.Natural

-- | A multiplicative group of residues is called cyclic,
-- if there is a primitive root @g@,
Expand Down Expand Up @@ -112,6 +119,13 @@ cyclicGroupToModulo = fromFactors . \case
CGOddPrimePower p k -> Coprimes.singleton (unPrime p) k
CGDoubleOddPrimePower p k -> Coprimes.singleton 2 1 <> Coprimes.singleton (unPrime p) k

-- | 'PrimitiveRoot m' is a type which is only inhabited by primitive roots of n.
data PrimitiveRoot m =
PrimitiveRoot { unPrimitiveRoot :: MultMod m -- ^ Extract primitive root value.
, getGroup :: CyclicGroup Natural -- ^ Get cyclic group structure.
}
Copy link
Owner

Choose a reason for hiding this comment

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

My personal preference is to put constructor on the same line with data and list fields indented by two spaces.

deriving (Eq, Show)

-- | 'isPrimitiveRoot'' @cg@ @a@ checks whether @a@ is
-- a <https://en.wikipedia.org/wiki/Primitive_root_modulo_n primitive root>
-- of a given cyclic multiplicative group of residues @cg@.
Expand Down Expand Up @@ -153,15 +167,21 @@ isPrimitiveRoot' cg r =
--
-- Here is how to list all primitive roots:
--
-- >>> filter isPrimitiveRoot [minBound .. maxBound] :: [Mod 13]
-- >>> mapMaybe isPrimitiveRoot [minBound .. maxBound] :: [Mod 13]
-- [(2 `modulo` 13), (6 `modulo` 13), (7 `modulo` 13), (11 `modulo` 13)]
--
-- This function is a convenient wrapper around 'isPrimitiveRoot''. The latter
-- provides better control and performance, if you need them.
isPrimitiveRoot
:: KnownNat n
=> Mod n
-> Bool
isPrimitiveRoot r = case cyclicGroupFromModulo (getNatMod r) of
Nothing -> False
Just cg -> isPrimitiveRoot' cg (getNatVal r)
-> Maybe (PrimitiveRoot n)
isPrimitiveRoot r = do
r' <- isMultElement r
cg <- cyclicGroupFromModulo (getNatMod r)
guard $ isPrimitiveRoot' cg (getNatVal r)
return $ PrimitiveRoot r' cg

-- | Calculate the size of a given cyclic group.
groupSize :: (Integral a, UniqueFactorisation a) => CyclicGroup a -> Prefactored a
groupSize = totient . cyclicGroupToModulo
3 changes: 3 additions & 0 deletions arithmoi.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ library
Math.NumberTheory.Moduli
Math.NumberTheory.Moduli.Chinese
Math.NumberTheory.Moduli.Class
Math.NumberTheory.Moduli.DiscreteLogarithm
Math.NumberTheory.Moduli.Jacobi
Math.NumberTheory.Moduli.PrimitiveRoot
Math.NumberTheory.Moduli.Sqrt
Expand Down Expand Up @@ -141,6 +142,7 @@ test-suite spec
Math.NumberTheory.GaussianIntegersTests
Math.NumberTheory.GCDTests
Math.NumberTheory.Moduli.ChineseTests
Math.NumberTheory.Moduli.DiscreteLogarithmTests
Math.NumberTheory.Moduli.ClassTests
Math.NumberTheory.Moduli.JacobiTests
Math.NumberTheory.Moduli.PrimitiveRootTests
Expand Down Expand Up @@ -189,6 +191,7 @@ benchmark criterion
semigroups >=0.8
other-modules:
Math.NumberTheory.ArithmeticFunctionsBench
Math.NumberTheory.DiscreteLogarithmBench
Math.NumberTheory.GaussianIntegersBench
Math.NumberTheory.GCDBench
Math.NumberTheory.JacobiBench
Expand Down
2 changes: 2 additions & 0 deletions benchmark/Bench.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Main where
import Gauge.Main

import Math.NumberTheory.ArithmeticFunctionsBench as ArithmeticFunctions
import Math.NumberTheory.DiscreteLogarithmBench as DiscreteLogarithm
import Math.NumberTheory.GaussianIntegersBench as Gaussian
import Math.NumberTheory.GCDBench as GCD
import Math.NumberTheory.JacobiBench as Jacobi
Expand All @@ -17,6 +18,7 @@ import Math.NumberTheory.SmoothNumbersBench as SmoothNumbers
main :: IO ()
main = defaultMain
[ ArithmeticFunctions.benchSuite
, DiscreteLogarithm.benchSuite
, Gaussian.benchSuite
, GCD.benchSuite
, Jacobi.benchSuite
Expand Down
22 changes: 22 additions & 0 deletions benchmark/Math/NumberTheory/DiscreteLogarithmBench.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{-# LANGUAGE DataKinds #-}
module Math.NumberTheory.DiscreteLogarithmBench
( benchSuite
) where

import Gauge.Main
import Data.Maybe

-- import Math.NumberTheory.Moduli (Mod, discreteLogarithm, PrimitiveRoot, isPrimitiveRoot, MultMod, isMultElement)
Copy link
Owner

Choose a reason for hiding this comment

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

Remove commented lines?

import Math.NumberTheory.Moduli.Class (isMultElement)
import Math.NumberTheory.Moduli.PrimitiveRoot (PrimitiveRoot, isPrimitiveRoot)
import Math.NumberTheory.Moduli.DiscreteLogarithm (discreteLogarithm)

type Modulus = 1000000007

root :: PrimitiveRoot Modulus
root = fromJust $ isPrimitiveRoot 5

benchSuite :: Benchmark
benchSuite = bgroup "Discrete logarithm"
[ bench "5^x = 8 mod 10^9+7" $ nf (uncurry discreteLogarithm) (root,fromJust $ isMultElement 8)
]
31 changes: 31 additions & 0 deletions test-suite/Math/NumberTheory/Moduli/DiscreteLogarithmTests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
module Math.NumberTheory.Moduli.DiscreteLogarithmTests
( testSuite
) where

import Data.Maybe
import Numeric.Natural
import Test.Tasty
import Data.Proxy
import GHC.TypeNats.Compat

import Math.NumberTheory.Moduli.Class
import Math.NumberTheory.Moduli.PrimitiveRoot
import Math.NumberTheory.Moduli.DiscreteLogarithm
import Math.NumberTheory.TestUtils

-- | Check that 'discreteLogarithm' computes the logarithm
discreteLogarithmProperty :: Positive Natural -> Integer -> Integer -> Bool
discreteLogarithmProperty (Positive m) a b =
case someNatVal m of
SomeNat (_ :: Proxy m) -> fromMaybe True $ do
a' <- isPrimitiveRoot (fromInteger a :: Mod m)
b' <- isMultElement (fromInteger b :: Mod m)
let e = discreteLogarithm a' b'
return $ powMod (fromInteger a :: Mod m) e == (fromInteger b :: Mod m)

testSuite :: TestTree
testSuite = testGroup "Discrete logarithm"
[ testSmallAndQuick "discreteLogarithm" discreteLogarithmProperty
]
12 changes: 6 additions & 6 deletions test-suite/Math/NumberTheory/Moduli/PrimitiveRootTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Test.Tasty

import qualified Data.Set as S
import Data.List (genericTake, genericLength)
import Data.Maybe (isJust, isNothing)
import Data.Maybe (isJust, isNothing, mapMaybe)
import Control.Arrow (first)
import Numeric.Natural
import Data.Proxy
Expand Down Expand Up @@ -73,34 +73,34 @@ isPrimitiveRootProperty1 :: AnySign Integer -> Positive Natural -> Bool
isPrimitiveRootProperty1 (AnySign n) (Positive m)
= case n `modulo` m of
SomeMod n' -> gcd n (toInteger m) == 1
|| not (isPrimitiveRoot n')
|| isNothing (isPrimitiveRoot n')
InfMod{} -> False

isPrimitiveRootProperty2 :: Positive Natural -> Bool
isPrimitiveRootProperty2 (Positive m)
= isNothing (cyclicGroupFromModulo m)
|| case someNatVal m of
SomeNat (_ :: Proxy t) -> any isPrimitiveRoot [(minBound :: Mod t) .. maxBound]
SomeNat (_ :: Proxy t) -> not $ null $ mapMaybe isPrimitiveRoot [(minBound :: Mod t) .. maxBound]
Copy link
Owner

Choose a reason for hiding this comment

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

I'd rather write any (isJust . isPrimitiveRoot).


isPrimitiveRootProperty3 :: AnySign Integer -> Positive Natural -> Bool
isPrimitiveRootProperty3 (AnySign n) (Positive m)
= case n `modulo` m of
SomeMod n' -> not (isPrimitiveRoot n')
SomeMod n' -> isNothing (isPrimitiveRoot n')
|| allUnique (genericTake (totient m - 1) (iterate (* n') 1))
InfMod{} -> False

isPrimitiveRootProperty4 :: AnySign Integer -> Positive Natural -> Bool
isPrimitiveRootProperty4 (AnySign n) (Positive m)
= isJust (cyclicGroupFromModulo m)
|| case n `modulo` m of
SomeMod n' -> not (isPrimitiveRoot n')
SomeMod n' -> isNothing (isPrimitiveRoot n')
InfMod{} -> False

isPrimitiveRootProperty5 :: Positive Natural -> Bool
isPrimitiveRootProperty5 (Positive m)
= isNothing (cyclicGroupFromModulo m)
|| case someNatVal m of
SomeNat (_ :: Proxy t) -> genericLength (filter isPrimitiveRoot [(minBound :: Mod t) .. maxBound]) == totient (totient m)
SomeNat (_ :: Proxy t) -> genericLength (mapMaybe isPrimitiveRoot [(minBound :: Mod t) .. maxBound]) == totient (totient m)

testSuite :: TestTree
testSuite = testGroup "Primitive root"
Expand Down
2 changes: 2 additions & 0 deletions test-suite/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import qualified Math.NumberTheory.Recurrencies.LinearTests as RecurrenciesLinea

import qualified Math.NumberTheory.Moduli.ChineseTests as ModuliChinese
import qualified Math.NumberTheory.Moduli.ClassTests as ModuliClass
import qualified Math.NumberTheory.Moduli.DiscreteLogarithmTests as ModuliDiscreteLogarithm
import qualified Math.NumberTheory.Moduli.JacobiTests as ModuliJacobi
import qualified Math.NumberTheory.Moduli.PrimitiveRootTests as ModuliPrimitiveRoot
import qualified Math.NumberTheory.Moduli.SqrtTests as ModuliSqrt
Expand Down Expand Up @@ -62,6 +63,7 @@ tests = testGroup "All"
, testGroup "Moduli"
[ ModuliChinese.testSuite
, ModuliClass.testSuite
, ModuliDiscreteLogarithm.testSuite
, ModuliJacobi.testSuite
, ModuliPrimitiveRoot.testSuite
, ModuliSqrt.testSuite
Expand Down