{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE UndecidableInstances #-}
module Math.NumberTheory.Moduli.PrimitiveRoot
(
CyclicGroup(..)
, cyclicGroupFromModulo
, cyclicGroupToModulo
, groupSize
, PrimitiveRoot
, unPrimitiveRoot
, getGroup
, isPrimitiveRoot
, isPrimitiveRoot'
) where
#if __GLASGOW_HASKELL__ < 803
import Data.Semigroup
#endif
import Math.NumberTheory.ArithmeticFunctions (totient)
import qualified Math.NumberTheory.Euclidean as E
import Math.NumberTheory.Euclidean.Coprimes as Coprimes (singleton)
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.Primes
import Control.DeepSeq
import Control.Monad (guard)
import GHC.Generics
import Numeric.Natural
data CyclicGroup a
= CG2
| CG4
| CGOddPrimePower (Prime a) Word
| CGDoubleOddPrimePower (Prime a) Word
deriving (Eq, Show, Generic)
instance NFData a => NFData (CyclicGroup a)
cyclicGroupFromModulo
:: (Ord a, Integral a, UniqueFactorisation a)
=> a
-> Maybe (CyclicGroup a)
cyclicGroupFromModulo = \case
2 -> Just CG2
4 -> Just CG4
n
| n <= 1 -> Nothing
| odd n -> uncurry CGOddPrimePower <$> isPrimePower n
| odd halfN -> uncurry CGDoubleOddPrimePower <$> isPrimePower halfN
| otherwise -> Nothing
where
halfN = n `quot` 2
isPrimePower
:: (Integral a, UniqueFactorisation a)
=> a
-> Maybe (Prime a, Word)
isPrimePower n = (, k) <$> isPrime m
where
(m, k) = highestPower n
cyclicGroupToModulo
:: E.Euclidean a
=> CyclicGroup a
-> Prefactored a
cyclicGroupToModulo = fromFactors . \case
CG2 -> Coprimes.singleton 2 1
CG4 -> Coprimes.singleton 2 2
CGOddPrimePower p k -> Coprimes.singleton (unPrime p) k
CGDoubleOddPrimePower p k -> Coprimes.singleton 2 1
<> Coprimes.singleton (unPrime p) k
data PrimitiveRoot m = PrimitiveRoot
{ unPrimitiveRoot :: MultMod m
, getGroup :: CyclicGroup Natural
}
deriving (Eq, Show)
isPrimitiveRoot'
:: (Integral a, UniqueFactorisation a)
=> CyclicGroup a
-> a
-> Bool
isPrimitiveRoot' cg r =
case cg of
CG2 -> r == 1
CG4 -> r == 3
CGOddPrimePower p k -> oddPrimePowerTest (unPrime p) k r
CGDoubleOddPrimePower p k -> doubleOddPrimePowerTest (unPrime p) k r
where
oddPrimeTest p g = let phi = totient p
pows = map (\pk -> phi `quot` unPrime (fst pk)) (factorise phi)
exps = map (\x -> powMod g x p) pows
in g /= 0 && gcd g p == 1 && all (/= 1) exps
oddPrimePowerTest p 1 g = oddPrimeTest p (g `mod` p)
oddPrimePowerTest p _ g = oddPrimeTest p (g `mod` p) && powMod g (p-1) (p*p) /= 1
doubleOddPrimePowerTest p k g = odd g && oddPrimePowerTest p k g
isPrimitiveRoot
:: KnownNat n
=> Mod n
-> Maybe (PrimitiveRoot n)
isPrimitiveRoot r = do
r' <- isMultElement r
cg <- cyclicGroupFromModulo (getNatMod r)
guard $ isPrimitiveRoot' cg (getNatVal r)
return $ PrimitiveRoot r' cg
groupSize :: (E.Euclidean a, UniqueFactorisation a) => CyclicGroup a -> Prefactored a
groupSize = totient . cyclicGroupToModulo