splitmix 0.0.2 → 0.0.3
raw patch · 12 files changed
+787/−64 lines, 12 filesdep +HUnitdep +clockdep ~basedep ~randomdep ~time
Dependencies added: HUnit, clock
Dependency ranges changed: base, random, time
Files
- Changelog.md +8/−0
- bench/Bench.hs +29/−12
- bench/Range.hs +109/−0
- bench/SimpleSum.hs +43/−0
- splitmix.cabal +72/−9
- src-compat/Data/Bits/Compat.hs +45/−0
- src/System/Random/SplitMix.hs +92/−26
- src/System/Random/SplitMix32.hs +311/−0
- tests/Dieharder.hs +35/−15
- tests/Examples.hs +15/−0
- tests/SplitMixPi.hs +1/−2
- tests/SplitMixPi32.hs +27/−0
Changelog.md view
@@ -1,3 +1,11 @@+# 0.0.3++- Add `System.Random.SplitMix32` module+- Add `bitmaskWithRejection32` and `bitmaskWithRejection64` module+- Add `nextWord32`, `nextTwoWord32` and `nextFloat`+- Add `random` flag, dropping dependency on `random`+ (breaks things, e.g. `QuickCheck`, when disabled).+ # 0.0.2 - Support back to GHC-7.0
bench/Bench.hs view
@@ -9,6 +9,7 @@ import qualified System.Random.TF as TF import qualified System.Random.TF.Instances as TF import qualified System.Random.SplitMix as SM+import qualified System.Random.SplitMix32 as SM32 ------------------------------------------------------------------------------- -- List@@ -31,6 +32,9 @@ splitMixList :: Word64 -> [Int] splitMixList w64 = genListN $ SM.mkSMGen w64 +splitMix32List :: Word64 -> [Int]+splitMix32List w64 = genListN $ SM32.mkSMGen $ fromIntegral w64+ ------------------------------------------------------------------------------- -- Tree -------------------------------------------------------------------------------@@ -57,6 +61,9 @@ splitMixTree :: Word64 -> T.Tree Int splitMixTree w64 = genTreeN $ SM.mkSMGen w64 +splitMix32Tree :: Word64 -> T.Tree Int+splitMix32Tree w64 = genTreeN $ SM32.mkSMGen $ fromIntegral w64+ ------------------------------------------------------------------------------- -- List Word64 -------------------------------------------------------------------------------@@ -78,6 +85,9 @@ splitMixList64 :: Word64 -> [Word64] splitMixList64 w64 = genListN64 SM.nextWord64 $ SM.mkSMGen w64 +splitMix32List64 :: Word64 -> [Word64]+splitMix32List64 w64 = genListN64 SM32.nextWord64 $ SM32.mkSMGen $ fromIntegral w64+ ------------------------------------------------------------------------------- -- Tree Word64 -------------------------------------------------------------------------------@@ -105,6 +115,9 @@ splitMixTree64 :: Word64 -> T.Tree Word64 splitMixTree64 w64 = genTreeN64 SM.nextWord64 $ SM.mkSMGen w64 +splitMix32Tree64 :: Word64 -> T.Tree Word64+splitMix32Tree64 w64 = genTreeN64 SM32.nextWord64 $ SM32.mkSMGen $ fromIntegral w64+ ------------------------------------------------------------------------------- -- Main -------------------------------------------------------------------------------@@ -112,23 +125,27 @@ main :: IO () main = defaultMain [ bgroup "list"- [ bench "random" $ nf randomList 42- , bench "tf-random" $ nf tfRandomList 42- , bench "splitmix" $ nf splitMixList 42+ [ bench "random" $ nf randomList 42+ , bench "tf-random" $ nf tfRandomList 42+ , bench "splitmix" $ nf splitMixList 42+ , bench "splitmix32" $ nf splitMix32List 42 ] , bgroup "tree"- [ bench "random" $ nf randomTree 42- , bench "tf-random" $ nf tfRandomTree 42- , bench "splitmix" $ nf splitMixTree 42+ [ bench "random" $ nf randomTree 42+ , bench "tf-random" $ nf tfRandomTree 42+ , bench "splitmix" $ nf splitMixTree 42+ , bench "splitmix32" $ nf splitMix32Tree 42 ] , bgroup "list 64"- [ bench "random" $ nf randomList64 42- , bench "tf-random" $ nf tfRandomList64 42- , bench "splitmix" $ nf splitMixList64 42+ [ bench "random" $ nf randomList64 42+ , bench "tf-random" $ nf tfRandomList64 42+ , bench "splitmix" $ nf splitMixList64 42+ , bench "splitmix32" $ nf splitMix32List64 42 ] , bgroup "tree 64"- [ bench "random" $ nf randomTree64 42- , bench "tf-random" $ nf tfRandomTree64 42- , bench "splitmix" $ nf splitMixTree64 42+ [ bench "random" $ nf randomTree64 42+ , bench "tf-random" $ nf tfRandomTree64 42+ , bench "splitmix" $ nf splitMixTree64 42+ , bench "splitmix32" $ nf splitMix32Tree64 42 ] ]
+ bench/Range.hs view
@@ -0,0 +1,109 @@+-- http://www.pcg-random.org/posts/bounded-rands.html+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+module Main where++import Data.Bits+import Data.Bits.Compat+import Data.List (unfoldr)+import Data.Word (Word32, Word64)++import qualified System.Random as R+import qualified System.Random.SplitMix32 as SM++#if defined(__GHCJS__)+#else+import System.Clock (Clock (Monotonic), getTime, toNanoSecs)+import Text.Printf (printf)+#endif++main :: IO ()+main = do+ gen <- SM.newSMGen++ bench gen (\g h -> R.randomR (0, pred h) g)+ bench gen classicMod+ bench gen intMult+ bench gen bitmaskWithRejection++bench :: g -> (g -> Word32 -> (Word32, g)) -> IO ()+bench gen next = do+ print $ take 70 $ unfoldr (\g -> Just (next g 10)) gen+ clocked $ do+ let x = sumOf next gen+ print x++sumOf :: (g -> Word32 -> (Word32, g)) -> g -> Word32+sumOf next = go 0 2+ where+ go !acc !n g | n > 0xfffff = acc+ | otherwise = let (w, g') = next g n in go (acc + w) (succ n) g'++classicMod :: SM.SMGen -> Word32 -> (Word32, SM.SMGen)+classicMod g h =+ let (w32, g') = SM.nextWord32 g in (w32 `mod` h, g')+++-- @+-- uint32_t bounded_rand(rng_t& rng, uint32_t range) {+-- uint32_t x = rng();+-- uint64_t m = uint64_t(x) * uint64_t(range);+-- return m >> 32;+-- }+-- @+--+intMult :: SM.SMGen -> Word32 -> (Word32, SM.SMGen)+intMult g h =+ (fromIntegral $ (fromIntegral w32 * fromIntegral h :: Word64) `shiftR` 32, g')+ where+ (w32, g') = SM.nextWord32 g++-- @+-- uint32_t bounded_rand(rng_t& rng, uint32_t range) {+-- uint32_t mask = ~uint32_t(0);+-- --range;+-- mask >>= __builtin_clz(range|1);+-- uint32_t x;+-- do {+-- x = rng() & mask;+-- } while (x > range);+-- return x;+-- }+-- @@+bitmaskWithRejection :: SM.SMGen -> Word32 -> (Word32, SM.SMGen)+bitmaskWithRejection g0 range = go g0+ where+ mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1)+ go g = let (x, g') = SM.nextWord32 g+ x' = x .&. mask+ in if x' >= range+ then go g'+ else (x', g')++-------------------------------------------------------------------------------+-- Poor man benchmarking with GHC and GHCJS+-------------------------------------------------------------------------------++clocked :: IO () -> IO ()+#if defined(__GHCJS__)+clocked action = do+ start+ action+ stop++foreign import javascript unsafe+ "console.time('loop');"+ start :: IO ()++foreign import javascript unsafe+ "console.timeEnd('loop');"+ stop :: IO ()+#else+clocked action = do+ start <- getTime Monotonic+ action+ end <- getTime Monotonic+ printf "loop: %.03fms\n"+ $ fromIntegral (toNanoSecs (end - start))+ / (1e6 :: Double)+#endif
+ bench/SimpleSum.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE CPP #-}+module Main (main) where++import System.Environment (getArgs)+import Data.List (foldl')+import Data.Word (Word32)++import qualified System.Random as R+import qualified System.Random.SplitMix as SM+import qualified System.Random.SplitMix32 as SM32++newGen :: a -> (a -> g) -> IO g -> IO g+#if 0+newGen _ _ new = new+#else+newGen seed mk _ = return (mk seed)+#endif++main :: IO ()+main = do+ putStrLn "Summing randoms..."+ getArgs >>= \args -> case args of+ "splitmix" : _ -> newGen 33 SM.mkSMGen SM.newSMGen >>= \g -> print $ benchSum g SM.nextTwoWord32+ "splitmix32" : _ -> newGen 33 SM32.mkSMGen SM32.newSMGen >>= \g -> print $ benchSum g SM32.nextTwoWord32+ "random" : _ -> R.newStdGen >>= \g -> print $ benchSum g randomNextTwoWord32++ -- after Closure Compiler getArgs return [] always?+ -- _ -> newGen 33 SM.mkSMGen SM.newSMGen >>= \g -> print $ benchSum g SM.nextTwoWord32+ _ -> newGen 33 SM32.mkSMGen SM32.newSMGen >>= \g -> print $ benchSum g SM32.nextTwoWord32+++benchSum :: g -> (g -> (Word32, Word32, g)) -> Word32+benchSum g next = foldl' (+) 0 $ take 10000000 $ unfoldr2 next g++-- | Infinite unfoldr with two element generator+unfoldr2 :: (s -> (a, a, s)) -> s -> [a]+unfoldr2 f = go where+ go s = let (x, y, s') = f s in x : y : go s'++randomNextTwoWord32 :: R.StdGen -> (Word32, Word32, R.StdGen)+randomNextTwoWord32 s0 = (x, y, s2) where+ (x, s1) = R.random s0+ (y, s2) = R.random s1
splitmix.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: splitmix-version: 0.0.2+version: 0.0.3 synopsis: Fast Splittable PRNG description: Pure Haskell implementation of SplitMix described in@@ -30,31 +30,49 @@ license-file: LICENSE maintainer: Oleg Grenrus <oleg.grenrus@iki.fi> bug-reports: https://github.com/phadej/splitmix#issues-category: System+category: System, Random build-type: Simple tested-with:- GHC ==8.6.4 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 || ==7.8.4 || ==7.6.3 || ==7.4.2 || ==7.2.2 || ==7.0.4+ GHC ==8.8.1 || ==8.6.5 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 || ==7.8.4 || ==7.6.3 || ==7.4.2 || ==7.2.2 || ==7.0.4 extra-source-files: README.md Changelog.md +flag optimised-mixer+ description: Use JavaScript for mix32+ manual: True+ default: False++flag random+ description: Providen RandomGen SMGen instance+ manual: True+ default: True+ library default-language: Haskell2010 ghc-options: -Wall- hs-source-dirs: src- exposed-modules: System.Random.SplitMix+ hs-source-dirs: src src-compat+ other-modules: Data.Bits.Compat+ exposed-modules:+ System.Random.SplitMix+ System.Random.SplitMix32 -- dump-core -- build-depends: dump-core -- ghc-options: -fplugin=DumpCore -fplugin-opt DumpCore:core-html build-depends:- base >=4.3 && <4.13+ base >=4.3 && <4.14 , deepseq >=1.3.0.0 && <1.5- , random >=1.0 && <1.2- , time >=1.2.0.3 && <1.9+ , time >=1.2.0.3 && <1.10 + if flag(random)+ build-depends: random >=1.0 && <1.2++ if flag(optimised-mixer)+ cpp-options: -DOPTIMISED_MIX32=1+ source-repository head type: git location: https://github.com/phadej/splitmix.git@@ -73,6 +91,41 @@ , splitmix , tf-random >=0.5 && <0.6 +benchmark simple-sum+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ ghc-options: -Wall+ hs-source-dirs: bench+ main-is: SimpleSum.hs+ build-depends:+ base+ , random+ , splitmix++benchmark range+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ ghc-options: -Wall+ hs-source-dirs: bench src-compat+ main-is: Range.hs+ other-modules: Data.Bits.Compat+ build-depends:+ base+ , clock >=0.8 && <0.9+ , random+ , splitmix++test-suite examples+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ ghc-options: -Wall+ hs-source-dirs: tests+ main-is: Examples.hs+ build-depends:+ base+ , HUnit ==1.3.1.2 || >=1.6.0.0 && <1.7+ , splitmix+ test-suite montecarlo-pi type: exitcode-stdio-1.0 default-language: Haskell2010@@ -83,9 +136,19 @@ base , splitmix -test-suite splitmix-dieharder+test-suite montecarlo-pi-32 type: exitcode-stdio-1.0 default-language: Haskell2010+ ghc-options: -Wall+ hs-source-dirs: tests+ main-is: SplitMixPi32.hs+ build-depends:+ base+ , splitmix++test-suite splitmix-dieharder+ default-language: Haskell2010+ type: exitcode-stdio-1.0 ghc-options: -Wall -threaded -rtsopts hs-source-dirs: tests main-is: Dieharder.hs
+ src-compat/Data/Bits/Compat.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-}+module Data.Bits.Compat (+ popCount,+ zeroBits,+ finiteBitSize,+ countLeadingZeros,+ ) where++import Data.Bits++#if !MIN_VERSION_base(4,7,0)+#define FiniteBits Bits+#endif++#if !MIN_VERSION_base(4,5,0)+popCount :: Bits a => a -> Int+popCount = go 0+ where+ go !c 0 = c+ go c w = go (c+1) (w .&. (w - 1)) -- clear the least significant+{-# INLINE popCount #-}+#endif++#if !MIN_VERSION_base(4,7,0)+zeroBits :: Bits a => a+zeroBits = clearBit (bit 0) 0+{-# INLINE zeroBits #-}++finiteBitSize :: Bits a => a -> Int+finiteBitSize = bitSize+{-# INLINE finiteBitSize #-}+#endif++#if !MIN_VERSION_base(4,8,0)+countLeadingZeros :: FiniteBits b => b -> Int+countLeadingZeros x = (w-1) - go (w-1)+ where+ go i | i < 0 = i -- no bit set+ | testBit x i = i+ | otherwise = go (i-1)++ w = finiteBitSize x+{-# INLINE countLeadingZeros #-}+#endif
src/System/Random/SplitMix.hs view
@@ -1,8 +1,6 @@ -- | -- /SplitMix/ is a splittable pseudorandom number generator (PRNG) that is quite fast. ----- Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast splittable pseudorandom number generators. /In Proceedings of the 2014 ACM International Conference on Object Oriented Programming Systems Languages & Applications/ (OOPSLA '13). ACM, New York, NY, USA, 453-472. DOI: <https://doi.org/10.1145/2660193.2660195>--- -- Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. -- Fast splittable pseudorandom number generators. In Proceedings -- of the 2014 ACM International Conference on Object Oriented@@ -28,17 +26,22 @@ -- but GHC-7.0 and GHC-7.2 have slow implementation, as there -- are no native 'popCount'. ---{-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} #if __GLASGOW_HASKELL__ >= 702-{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE Trustworthy #-} #endif module System.Random.SplitMix ( SMGen, nextWord64,+ nextWord32,+ nextTwoWord32, nextInt, nextDouble,+ nextFloat, splitSMGen,+ -- * Generation+ bitmaskWithRejection32,+ bitmaskWithRejection64, -- * Initialisation mkSMGen, initSMGen,@@ -49,24 +52,19 @@ ) where import Control.DeepSeq (NFData (..))-import Data.Bits (shiftL, shiftR, xor, (.|.))+import Data.Bits (complement, shiftL, shiftR, xor, (.&.), (.|.))+import Data.Bits.Compat (countLeadingZeros, popCount, zeroBits) import Data.IORef (IORef, atomicModifyIORef, newIORef) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Word (Word32, Word64)-import System.CPUTime (cpuTimePrecision, getCPUTime) import System.IO.Unsafe (unsafePerformIO) +#ifdef MIN_VERSION_random import qualified System.Random as R+#endif -#if MIN_VERSION_base(4,5,0)-import Data.Bits (popCount)-#else-import Data.Bits ((.&.))-popCount :: Word64 -> Int-popCount = go 0- where- go !c 0 = c- go c w = go (c+1) (w .&. (w - 1)) -- clear the least significant+#if !__GHCJS__+import System.CPUTime (cpuTimePrecision, getCPUTime) #endif -- $setup@@ -98,12 +96,12 @@ -- instance Read SMGen where readsPrec d r = readParen (d > 10) (\r0 ->- [ (SMGen seed gamma, r3)- | ("SMGen", r1) <- lex r0- , (seed, r2) <- readsPrec 11 r1- , (gamma, r3) <- readsPrec 11 r2- , odd gamma- ]) r+ [ (SMGen seed gamma, r3)+ | ("SMGen", r1) <- lex r0+ , (seed, r2) <- readsPrec 11 r1+ , (gamma, r3) <- readsPrec 11 r2+ , odd gamma+ ]) r ------------------------------------------------------------------------------- -- Operations@@ -119,6 +117,20 @@ where seed' = seed + gamma +-- | Generate 'Word32' by truncating 'nextWord64'.+--+-- @since 0.0.3+nextWord32 :: SMGen -> (Word32, SMGen)+nextWord32 g = (fromIntegral w64, g') where+ (w64, g') = nextWord64 g++-- | Generate two 'Word32'.+--+-- @since 0.0.3+nextTwoWord32 :: SMGen -> (Word32, Word32, SMGen)+nextTwoWord32 g = (fromIntegral $ w64 `shiftR` 32, fromIntegral w64, g') where+ (w64, g') = nextWord64 g+ -- | Generate an 'Int'. nextInt :: SMGen -> (Int, SMGen) nextInt g = case nextWord64 g of@@ -133,6 +145,16 @@ nextDouble g = case nextWord64 g of (w64, g') -> (fromIntegral (w64 `shiftR` 11) * doubleUlp, g') +-- | Generate a 'Float' in @[0, 1)@ range.+--+-- >>> take 8 $ map (printf "%0.3f") $ unfoldr (Just . nextFloat) (mkSMGen 1337) :: [String]+-- ["0.057","0.089","0.237","0.383","0.680","0.320","0.826","0.007"]+--+-- @since 0.0.3+nextFloat :: SMGen -> (Float, SMGen)+nextFloat g = case nextWord32 g of+ (w32, g') -> (fromIntegral (w32 `shiftR` 8) * floatUlp, g')+ -- | Split a generator into a two uncorrelated generators. splitSMGen :: SMGen -> (SMGen, SMGen) splitSMGen (SMGen seed gamma) =@@ -148,13 +170,14 @@ goldenGamma :: Word64 goldenGamma = 0x9e3779b97f4a7c15 +floatUlp :: Float+floatUlp = 1.0 / fromIntegral (1 `shiftL` 24 :: Word32)+ doubleUlp :: Double doubleUlp = 1.0 / fromIntegral (1 `shiftL` 53 :: Word64) -- Note: in JDK implementations the mix64 and mix64variant13 -- (which is inlined into mixGamma) are swapped.------ I have no idea if swapping them affects statistical properties. mix64 :: Word64 -> Word64 mix64 z0 = -- MurmurHash3Mixer@@ -168,6 +191,8 @@ mix64variant13 z0 = -- Better Bit Mixing - Improving on MurmurHash3's 64-bit Finalizer -- http://zimbry.blogspot.fi/2011/09/better-bit-mixing-improving-on.html+ --+ -- Stafford's Mix13 let z1 = shiftXorMultiply 30 0xbf58476d1ce4e5b9 z0 -- MurmurHash3 mix constants z2 = shiftXorMultiply 27 0x94d049bb133111eb z1 z3 = shiftXor 31 z2@@ -190,6 +215,41 @@ shiftXorMultiply n k w = shiftXor n w * k -------------------------------------------------------------------------------+-- Generation+-------------------------------------------------------------------------------++-- | /Bitmask with rejection/ method of generating subrange of 'Word32'.+--+-- @since 0.0.3+bitmaskWithRejection32 :: Word32 -> SMGen -> (Word32, SMGen)+bitmaskWithRejection32 range = go where+ mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1)+ go g = let (x, g') = nextWord32 g+ x' = x .&. mask+ in if x' >= range+ then go g'+ else (x', g')++-- | /Bitmask with rejection/ method of generating subrange of 'Word64'.+--+-- @bitmaskWithRejection64 w64@ generates random numbers in closed-open+-- range of @[0, w64)@.+--+-- >>> take 20 $ unfoldr (Just . bitmaskWithRejection64 5) (mkSMGen 1337)+-- [3,1,4,1,2,3,1,1,0,3,4,2,3,0,2,3,3,4,1,0]+--+-- @since 0.0.3+bitmaskWithRejection64 :: Word64 -> SMGen -> (Word64, SMGen)+bitmaskWithRejection64 range = go where+ mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1)+ go g = let (x, g') = nextWord64 g+ x' = x .&. mask+ in if x' >= range+ then go g'+ else (x', g')+++------------------------------------------------------------------------------- -- Initialisation ------------------------------------------------------------------------------- @@ -235,15 +295,21 @@ mkSeedTime :: IO Word64 mkSeedTime = do now <- getPOSIXTime- cpu <- getCPUTime let lo = truncate now :: Word32- hi = fromIntegral (cpu `div` cpuTimePrecision) :: Word32+#if __GHCJS__+ let hi = lo+#else+ cpu <- getCPUTime+ let hi = fromIntegral (cpu `div` cpuTimePrecision) :: Word32+#endif return $ fromIntegral hi `shiftL` 32 .|. fromIntegral lo ------------------------------------------------------------------------------- -- System.Random ------------------------------------------------------------------------------- +#ifdef MIN_VERSION_random instance R.RandomGen SMGen where next = nextInt split = splitSMGen+#endif
+ src/System/Random/SplitMix32.hs view
@@ -0,0 +1,311 @@+-- |+-- /SplitMix/ is a splittable pseudorandom number generator (PRNG) that is quite fast.+--+-- This is 32bit variant (original one is 32 bit).+--+-- You __really don't want to use this one__.+--+-- Note: This module supports all GHCs since GHC-7.0.4,+-- but GHC-7.0 and GHC-7.2 have slow implementation, as there+-- are no native 'popCount'.+--+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 702+{-# LANGUAGE Trustworthy #-}+#endif+module System.Random.SplitMix32 (+ SMGen,+ nextWord32,+ nextWord64,+ nextTwoWord32,+ nextInt,+ nextDouble,+ nextFloat,+ splitSMGen,+ -- * Generation+ bitmaskWithRejection32,+ bitmaskWithRejection64,+ -- * Initialisation+ mkSMGen,+ initSMGen,+ newSMGen,+ seedSMGen,+ seedSMGen',+ unseedSMGen,+ ) where++import Control.DeepSeq (NFData (..))+import Data.Bits (complement, shiftL, shiftR, xor, (.&.), (.|.))+import Data.Bits.Compat+ (countLeadingZeros, finiteBitSize, popCount, zeroBits)+import Data.IORef (IORef, atomicModifyIORef, newIORef)+import Data.Time.Clock.POSIX (getPOSIXTime)+import Data.Word (Word32, Word64)+import System.IO.Unsafe (unsafePerformIO)++#ifdef MIN_VERSION_random+import qualified System.Random as R+#endif++#if !__GHCJS__+import System.CPUTime (cpuTimePrecision, getCPUTime)+#endif++-- $setup+-- >>> import Text.Read (readMaybe)+-- >>> import Data.List (unfoldr)+-- >>> import Text.Printf (printf)++-------------------------------------------------------------------------------+-- Generator+-------------------------------------------------------------------------------++-- | SplitMix generator state.+data SMGen = SMGen {-# UNPACK #-} !Word32 {-# UNPACK #-} !Word32 -- seed and gamma; gamma is odd+ deriving Show++instance NFData SMGen where+ rnf (SMGen _ _) = ()++-- |+--+-- >>> readMaybe "SMGen 1 1" :: Maybe SMGen+-- Just (SMGen 1 1)+--+-- >>> readMaybe "SMGen 1 2" :: Maybe SMGen+-- Nothing+--+-- >>> readMaybe (show (mkSMGen 42)) :: Maybe SMGen+-- Just (SMGen 142593372 1604540297)+--+instance Read SMGen where+ readsPrec d r = readParen (d > 10) (\r0 ->+ [ (SMGen seed gamma, r3)+ | ("SMGen", r1) <- lex r0+ , (seed, r2) <- readsPrec 11 r1+ , (gamma, r3) <- readsPrec 11 r2+ , odd gamma+ ]) r++-------------------------------------------------------------------------------+-- Operations+-------------------------------------------------------------------------------++-- | Generate a 'Word32'.+--+-- >>> take 3 $ map (printf "%x") $ unfoldr (Just . nextWord32) (mkSMGen 1337) :: [String]+-- ["e0cfe722","a6ced0f0","c3a6d889"]+--+nextWord32 :: SMGen -> (Word32, SMGen)+nextWord32 (SMGen seed gamma) = (mix32 seed', SMGen seed' gamma)+ where+ seed' = seed + gamma++-- | Generate a 'Word64', by generating to 'Word32's.+nextWord64 :: SMGen -> (Word64, SMGen)+nextWord64 s0 = (fromIntegral w0 `shiftL` 32 .|. fromIntegral w1, s2)+ where+ (w0, s1) = nextWord32 s0+ (w1, s2) = nextWord32 s1++-- | Generate two 'Word32'.+nextTwoWord32 :: SMGen -> (Word32, Word32, SMGen)+nextTwoWord32 s0 = (w0, w1, s2) where+ (w0, s1) = nextWord32 s0+ (w1, s2) = nextWord32 s1++-- | Generate an 'Int'.+nextInt :: SMGen -> (Int, SMGen)+nextInt g | isBigInt = int64+ | otherwise = int32+ where+ int32 = case nextWord32 g of+ (w, g') -> (fromIntegral w, g')+ int64 = case nextWord64 g of+ (w, g') -> (fromIntegral w, g')++isBigInt :: Bool+isBigInt = finiteBitSize (undefined :: Int) > 32++-- | Generate a 'Double' in @[0, 1)@ range.+--+-- >>> take 8 $ map (printf "%0.3f") $ unfoldr (Just . nextDouble) (mkSMGen 1337) :: [String]+-- ["0.878","0.764","0.063","0.845","0.262","0.490","0.176","0.544"]+--+nextDouble :: SMGen -> (Double, SMGen)+nextDouble g = case nextWord64 g of+ (w64, g') -> (fromIntegral (w64 `shiftR` 11) * doubleUlp, g')++-- | Generate a 'Float' in @[0, 1)@ range.+--+-- >>> take 8 $ map (printf "%0.3f") $ unfoldr (Just . nextFloat) (mkSMGen 1337) :: [String]+-- ["0.878","0.652","0.764","0.631","0.063","0.180","0.845","0.645"]+--+nextFloat :: SMGen -> (Float, SMGen)+nextFloat g = case nextWord32 g of+ (w32, g') -> (fromIntegral (w32 `shiftR` 8) * floatUlp, g')++-- | Split a generator into a two uncorrelated generators.+splitSMGen :: SMGen -> (SMGen, SMGen)+splitSMGen (SMGen seed gamma) =+ (SMGen seed'' gamma, SMGen (mix32 seed') (mixGamma seed''))+ where+ seed' = seed + gamma+ seed'' = seed' + gamma++-------------------------------------------------------------------------------+-- Algorithm+-------------------------------------------------------------------------------++-- | (1 + sqrt 5) / 2 * (2 ^^ bits)+goldenGamma :: Word32+goldenGamma = 0x9e3779b9++floatUlp :: Float+floatUlp = 1.0 / fromIntegral (1 `shiftL` 24 :: Word32)++doubleUlp :: Double+doubleUlp = 1.0 / fromIntegral (1 `shiftL` 53 :: Word64)++#if defined(__GHCJS__) && defined(OPTIMISED_MIX32)+-- JavaScript Foreign Function Interface+-- https://github.com/ghcjs/ghcjs/blob/master/doc/foreign-function-interface.md++foreign import javascript unsafe+ "var x0 = $1 ^ $1 >>> 16; var x1 = x0 & 0xffff; var x2 = (((x0 >>> 16 & 0xffff) * 0x0000ca6b + x1 * 0x000085eb & 0xffff) << 16) + x1 * 0x0000ca6b; var x3 = x2 ^ x2 >>> 13; var x4 = x3 & 0xffff; var x5 = (((x3 >>> 16 & 0xffff) * 0x0000ae35 + x4 * 0x0000c2b2 & 0xffff) << 16) + x4 * 0x0000ae35; $r = (x5 ^ x5 >>> 16) | 0;"+ mix32 :: Word32 -> Word32++foreign import javascript unsafe+ "var x0 = $1 ^ $1 >>> 16; var x1 = x0 & 0xffff; var x2 = (((x0 >>> 16 & 0xffff) * 0x00006ccb + x1 * 0x000069ad & 0xffff) << 16) + x1 * 0x00006ccb; var x3 = x2 ^ x2 >>> 13; var x4 = x3 & 0xffff; var x5 = (((x3 >>> 16 & 0xffff) * 0x0000b5b3 + x4 * 0x0000cd9a & 0xffff) << 16) + x4 * 0x0000b5b3; $r = (x5 ^ x5 >>> 16) | 0;"+ mix32variant13 :: Word32 -> Word32++#else+mix32 :: Word32 -> Word32+mix32 z0 =+ -- MurmurHash3Mixer 32bit+ let z1 = shiftXorMultiply 16 0x85ebca6b z0+ z2 = shiftXorMultiply 13 0xc2b2ae35 z1+ z3 = shiftXor 16 z2+ in z3++-- used only in mixGamma+mix32variant13 :: Word32 -> Word32+mix32variant13 z0 =+ -- See avalanche "executable"+ let z1 = shiftXorMultiply 16 0x69ad6ccb z0+ z2 = shiftXorMultiply 13 0xcd9ab5b3 z1+ z3 = shiftXor 16 z2+ in z3++shiftXor :: Int -> Word32 -> Word32+shiftXor n w = w `xor` (w `shiftR` n)++shiftXorMultiply :: Int -> Word32 -> Word32 -> Word32+shiftXorMultiply n k w = shiftXor n w * k+#endif++mixGamma :: Word32 -> Word32+mixGamma z0 =+ let z1 = mix32variant13 z0 .|. 1 -- force to be odd+ n = popCount (z1 `xor` (z1 `shiftR` 1))+ -- see: http://www.pcg-random.org/posts/bugs-in-splitmix.html+ -- let's trust the text of the paper, not the code.+ in if n >= 12+ then z1+ else z1 `xor` 0xaaaaaaaa++-------------------------------------------------------------------------------+-- Generation+-------------------------------------------------------------------------------++-- | /Bitmask with rejection/ method of generating subrange of 'Word32'.+bitmaskWithRejection32 :: Word32 -> SMGen -> (Word32, SMGen)+bitmaskWithRejection32 range = go where+ mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1)+ go g = let (x, g') = nextWord32 g+ x' = x .&. mask+ in if x' >= range+ then go g'+ else (x', g')++-- | /Bitmask with rejection/ method of generating subrange of 'Word64'.+--+-- @bitmaskWithRejection64 w64@ generates random numbers in closed-open+-- range of @[0, w64)@.+--+-- >>> take 20 $ unfoldr (Just . bitmaskWithRejection64 5) (mkSMGen 1337)+-- [0,2,4,2,1,4,2,4,2,2,3,0,3,2,2,2,3,1,2,2]+--+bitmaskWithRejection64 :: Word64 -> SMGen -> (Word64, SMGen)+bitmaskWithRejection64 range = go where+ mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1)+ go g = let (x, g') = nextWord64 g+ x' = x .&. mask+ in if x' >= range+ then go g'+ else (x', g')++-------------------------------------------------------------------------------+-- Initialisation+-------------------------------------------------------------------------------++-- | Create 'SMGen' using seed and gamma.+--+-- >>> seedSMGen 2 2+-- SMGen 2 3+--+seedSMGen+ :: Word32 -- ^ seed+ -> Word32 -- ^ gamma+ -> SMGen+seedSMGen seed gamma = SMGen seed (gamma .|. 1)++-- | Like 'seedSMGen' but takes a pair.+seedSMGen' :: (Word32, Word32) -> SMGen+seedSMGen' = uncurry seedSMGen++-- | Extract current state of 'SMGen'.+unseedSMGen :: SMGen -> (Word32, Word32)+unseedSMGen (SMGen seed gamma) = (seed, gamma)++-- | Preferred way to deterministically construct 'SMGen'.+--+-- >>> mkSMGen 42+-- SMGen 142593372 1604540297+--+mkSMGen :: Word32 -> SMGen+mkSMGen s = SMGen (mix32 s) (mixGamma (s + goldenGamma))++-- | Initialize 'SMGen' using system time.+initSMGen :: IO SMGen+initSMGen = fmap mkSMGen mkSeedTime++-- | Derive a new generator instance from the global 'SMGen' using 'splitSMGen'.+newSMGen :: IO SMGen+newSMGen = atomicModifyIORef theSMGen splitSMGen++theSMGen :: IORef SMGen+theSMGen = unsafePerformIO $ initSMGen >>= newIORef+{-# NOINLINE theSMGen #-}++mkSeedTime :: IO Word32+mkSeedTime = do+ now <- getPOSIXTime+ let lo = truncate now :: Word32+#if __GHCJS__+ let hi = lo+#else+ cpu <- getCPUTime+ let hi = fromIntegral (cpu `div` cpuTimePrecision) :: Word32+#endif+ return $ fromIntegral hi `shiftL` 32 .|. fromIntegral lo++-------------------------------------------------------------------------------+-- System.Random+-------------------------------------------------------------------------------++#ifdef MIN_VERSION_random+instance R.RandomGen SMGen where+ next = nextInt+ split = splitSMGen+#endif
tests/Dieharder.hs view
@@ -8,6 +8,7 @@ import Control.Concurrent.QSem import Control.DeepSeq (force)+import Control.Monad (when) import Data.Bits (shiftL, (.|.)) import Data.Char (isSpace) import Data.List (isInfixOf, unfoldr)@@ -17,7 +18,7 @@ import Foreign.Ptr (castPtr) import GHC.IO.Exception (IOErrorType (..), IOException (..)) import System.Environment (getArgs)-import System.IO (Handle, hGetContents)+import System.IO (Handle, hGetContents, stdout) import Text.Printf (printf) import qualified Control.Concurrent.Async as A@@ -27,6 +28,7 @@ import qualified Data.Vector.Storable.Mutable as MSV import qualified System.Process as Proc import qualified System.Random.SplitMix as SM+import qualified System.Random.SplitMix32 as SM32 import qualified System.Random.TF as TF import qualified System.Random.TF.Gen as TF import qualified System.Random.TF.Init as TF@@ -37,18 +39,26 @@ if null args then return () else do- (cmd, runs, conc, seed, test, _help) <- parseArgsIO args $ (,,,,,)+ (cmd, runs, conc, seed, test, raw, _help) <- parseArgsIO args $ (,,,,,,) <$> arg <*> optDef "-n" 1 <*> optDef "-j" 1 <*> opt "-s" <*> opt "-d"+ <*> flag "-r" <*> flag "-h" + let run :: RunType g+ run | raw = runRaw+ | otherwise = runManaged+ case cmd of "splitmix" -> do g <- maybe SM.initSMGen (return . SM.mkSMGen) seed run test runs conc SM.splitSMGen SM.nextWord64 g+ "splitmix32" -> do+ g <- maybe SM32.initSMGen (return . SM32.mkSMGen) (fmap fromIntegral seed)+ run test runs conc SM32.splitSMGen SM32.nextWord64 g "tfrandom" -> do g <- TF.initTFGen run test runs conc TF.split tfNext64 g@@ -64,19 +74,26 @@ -- Dieharder ------------------------------------------------------------------------------- -run :: Maybe Int+type RunType g =+ Maybe Int -> Int -> Int -> (g -> (g, g)) -> (g -> (Word64, g)) -> g- -> IO ()-run test runs conc split word gen = do+ -> IO () ++runRaw :: RunType g+runRaw _test _runs _conc split word gen =+ generate word split gen stdout++runManaged :: RunType g+runManaged test runs conc split word gen = do qsem <- newQSem conc rs <- A.forConcurrently (take runs $ unfoldr (Just . split) gen) $ \g -> E.bracket_ (waitQSem qsem) (signalQSem qsem) $- dieharder test (generate word g)+ dieharder test (generate word split g) case mconcat rs of Result p w f -> do@@ -84,7 +101,7 @@ printf "PASSED %4d %6.02f%%\n" p (fromIntegral p / total * 100) printf "WEAK %4d %6.02f%%\n" w (fromIntegral w / total * 100) printf "FAILED %4d %6.02f%%\n" f (fromIntegral f / total * 100)-{-# INLINE run #-}+{-# INLINE runManaged #-} dieharder :: Maybe Int -> (Handle -> IO ()) -> IO Result dieharder test gen = do@@ -144,26 +161,29 @@ size :: Int size = 512 -generate :: forall g. (g -> (Word64, g)) -> g -> Handle -> IO ()-generate word gen0 h = do+generate+ :: forall g. (g -> (Word64, g))+ -> (g -> (g, g))+ -> g -> Handle -> IO ()+generate word split gen0 h = do vec <- MSV.new size go gen0 vec where go :: g -> MSV.IOVector Word64 -> IO () go gen vec = do- gen' <- write gen vec 0+ let (g1, g2) = split gen+ write g1 vec 0 MSV.unsafeWith vec $ \ptr -> do bs <- BS.unsafePackCStringLen (castPtr ptr, size * 8) BS.hPutStr h bs- go gen' vec+ go g2 vec - write :: g -> MSV.IOVector Word64 -> Int -> IO g+ write :: g -> MSV.IOVector Word64 -> Int -> IO () write !gen !vec !i = do let (w64, gen') = word gen MSV.unsafeWrite vec i w64- if i < size- then write gen' vec (i + 1)- else return gen'+ when (i < size) $+ write gen' vec (i + 1) {-# INLINE generate #-} -------------------------------------------------------------------------------
+ tests/Examples.hs view
@@ -0,0 +1,15 @@+module Main (main) where++import Test.HUnit ((@?=))++import qualified System.Random.SplitMix32 as SM32++main :: IO ()+main = do+ let g = SM32.mkSMGen 42+ show g @?= "SMGen 142593372 1604540297"+ print g++ let (w32, g') = SM32.nextWord32 g+ w32 @?= 1296549791+ show g' @?= "SMGen 1747133669 1604540297"
tests/SplitMixPi.hs view
@@ -7,7 +7,7 @@ doubles = unfoldr (Just . nextDouble) monteCarloPi :: SMGen -> Double-monteCarloPi = (4 *) . calc . foldl' accum (P 0 0) . take 10000000 . pairs . doubles+monteCarloPi = (4 *) . calc . foldl' accum (P 0 0) . take 50000000 . pairs . doubles where calc (P n m) = fromIntegral n / fromIntegral m @@ -25,4 +25,3 @@ print (pi :: Double) print pi' print (pi - pi')-
+ tests/SplitMixPi32.hs view
@@ -0,0 +1,27 @@+module Main (main) where++import Data.List (unfoldr, foldl')+import System.Random.SplitMix32++doubles :: SMGen -> [Float]+doubles = unfoldr (Just . nextFloat)++monteCarloPi :: SMGen -> Float+monteCarloPi = (4 *) . calc . foldl' accum (P 0 0) . take 50000000 . pairs . doubles+ where+ calc (P n m) = fromIntegral n / fromIntegral m++ pairs (x : y : xs) = (x, y) : pairs xs+ pairs _ = []++ accum (P n m) (x, y) | x * x + y * y >= 1 = P n (m + 1)+ | otherwise = P (n + 1) (m + 1)++data P = P !Int !Int++main :: IO ()+main = do+ pi' <- fmap monteCarloPi newSMGen+ print (pi :: Float)+ print pi'+ print (pi - pi')