packages feed

mini-1.6.3.0: src/Mini/Random/SplitMix.hs

-- | An implementation of SplitMix: <https://doi.org/10.1145/2660193.2660195>
module Mini.Random.SplitMix (
  -- * Type
  SplitMix,

  -- * Construction
  seed,

  -- * Operations
  nextWord32,
  nextWord64,
  split,
) where

import Data.Bits (
  popCount,
  shiftR,
  xor,
  (.|.),
 )
import Data.Word (
  Word32,
  Word64,
 )
import Mini.Data.Recursion (
  bool,
 )
import Prelude (
  Eq,
  Ord,
  Show,
  fromIntegral,
  ($),
  (*),
  (+),
  (<),
 )

-- Type

-- | Abstract representation of a SplitMix generator
data SplitMix = SplitMix Word64 Word64
  deriving (Eq, Ord, Show)

-- Construction

-- | Make a generator from a seed
seed :: Word64 -> SplitMix
seed s = SplitMix (mix64 s) (mixGamma $ s + 0x9e3779b97f4a7c15)

-- Operations

-- | Generate the next 32-bit word
nextWord32 :: SplitMix -> (Word32, SplitMix)
nextWord32 (SplitMix s g) =
  let s' = s + g
   in (mix32 s', SplitMix s' g)

-- | Generate the next 64-bit word
nextWord64 :: SplitMix -> (Word64, SplitMix)
nextWord64 (SplitMix s g) =
  let s' = s + g
   in (mix64 s', SplitMix s' g)

-- | Split a generator into two (seemingly) independent generators
split :: SplitMix -> (SplitMix, SplitMix)
split (SplitMix s g) =
  let s' = s + g
      s'' = s' + g
      sm = SplitMix s'' g
      sm' = SplitMix (mix64 s') (mixGamma s'')
   in (sm, sm')

-- Helpers

mix64 :: Word64 -> Word64
mix64 z0 =
  let z1 = z0 `xor` (z0 `shiftR` 33)
      z2 = z1 * 0xff51afd7ed558ccd
      z3 = z2 `xor` (z2 `shiftR` 33)
      z4 = z3 * 0xc4ceb9fe1a85ec53
      z5 = z4 `xor` (z4 `shiftR` 33)
   in z5

mix32 :: Word64 -> Word32
mix32 z0 =
  let z1 = z0 `xor` (z0 `shiftR` 33)
      z2 = z1 * 0xff51afd7ed558ccd
      z3 = z2 `xor` (z2 `shiftR` 33)
      z4 = z3 * 0xc4ceb9fe1a85ec53
      z5 = z4 `shiftR` 32
   in fromIntegral z5

mix64variant13 :: Word64 -> Word64
mix64variant13 z0 =
  let z1 = z0 `xor` (z0 `shiftR` 30)
      z2 = z1 * 0xbf58476d1ce4e5b9
      z3 = z2 `xor` (z2 `shiftR` 27)
      z4 = z3 * 0x94d049bb133111eb
      z5 = z4 `xor` (z4 `shiftR` 31)
   in z5

mixGamma :: Word64 -> Word64
mixGamma z0 =
  let z1 = mix64variant13 z0 .|. 1
      n = popCount $ z1 `xor` (z1 `shiftR` 1)
   in -- based on the text on p. 466, not the code with inverted logic on p. 465
      bool z1 (z1 `xor` 0xaaaaaaaaaaaaaaaa) $ n < 24