moo-1.0: Moo/GeneticAlgorithm/Random.hs
{-# LANGUAGE BangPatterns #-}
{- | Some extra facilities to work with 'Rand' monad and 'PureMT'
random number generator.
-}
module Moo.GeneticAlgorithm.Random
(
-- * Random numbers from given range
getRandomR
, getRandom
-- * Probability distributions
, getNormal2
, getNormal
-- * Random samples and shuffles
, randomSample
, shuffle
-- * Building blocks
, withProbability
-- * Re-exports from random number generator packages
, getBool, getInt, getWord, getInt64, getWord64, getDouble
, runRandom, evalRandom, newPureMT
, Rand, Random, PureMT
) where
import Control.Monad (liftM)
import Control.Monad.Mersenne.Random
import Data.Complex (Complex (..))
import System.Random (RandomGen, Random(..))
import System.Random.Mersenne.Pure64
import qualified System.Random.Shuffle as S
-- | Yield a new randomly selected value of type @a@ in the range @(lo, hi)@.
-- See 'System.Random.randomR' for details.
getRandomR :: Random a => (a, a) -> Rand a
getRandomR range = Rand $ \s -> let (r, s') = randomR range s in R r s'
-- | Yield a new randomly selected value of type @a@.
-- See 'System.Random.random' for details.
getRandom :: Random a => Rand a
getRandom = Rand $ \g -> let (r, g') = random g in R r g'
-- | Yield two randomly selected values which follow standard
-- normal distribution.
getNormal2 :: Rand (Double, Double)
getNormal2 = do
-- Box-Muller method
u <- getDouble
v <- getDouble
let (c :+ s) = exp (0 :+ (2*pi*v))
let r = sqrt $ (-2) * log u
return (r*c, r*s)
-- | Yield one randomly selected value from standard normal distribution.
getNormal :: Rand Double
getNormal = fst `liftM` getNormal2
-- | Take at most n random elements from the list. Preserve order.
randomSample :: Int -> [a] -> Rand [a]
randomSample n xs =
Rand $ \g -> case select g n (length xs) xs [] of (xs', g') -> R xs' g'
where
select rng _ _ [] acc = (reverse acc, rng)
select rng n m xs acc
| n <= 0 = (reverse acc, rng)
| otherwise =
let (k, rng') = randomR (0, m - n) rng
(x:rest) = drop k xs
in select rng' (n-1) (m-k-1) rest (x:acc)
-- | Randomly reorder the list.
shuffle :: [a] -> Rand [a]
shuffle xs = Rand $ \g ->
let (xs', g') = randomShuffle xs (length xs) g in R xs' g'
-- | Given a sequence (e1,...en) to shuffle, its length, and a random
-- generator, compute the corresponding permutation of the input
-- sequence, return the permutation and the new state of the
-- random generator.
randomShuffle :: RandomGen gen => [a] -> Int -> gen -> ([a], gen)
randomShuffle elements len g =
let (rs, g') = rseq len g
in (S.shuffle elements rs, g')
where
-- | The sequence (r1,...r[n-1]) of numbers such that r[i] is an
-- independent sample from a uniform random distribution
-- [0..n-i]
rseq :: RandomGen gen => Int -> gen -> ([Int], gen)
rseq n g = second lastGen . unzip $ rseq' (n - 1) g
where
rseq' :: RandomGen gen => Int -> gen -> [(Int, gen)]
rseq' i gen
| i <= 0 = []
| otherwise = let (j, gen') = randomR (0, i) gen
in (j, gen') : rseq' (i - 1) gen'
-- apply a function on the second element of a pair
second :: (b -> c) -> (a, b) -> (a, c)
second f (x,y) = (x, f y)
-- the last returned random number generator
lastGen [] = g -- didn't use the generator yet
lastGen (lst:[]) = lst
lastGen gens = lastGen (drop 1 gens)
-- |Modify value with probability @p@. Return the unchanged value with probability @1-p@.
withProbability :: Double -> (a -> Rand a) -> (a -> Rand a)
withProbability p modify x = do
t <- getDouble
if t < p
then modify x
else return x