------------------------------------------------------------------------ -- | -- Module : ALife.Creatur.Genetics.Recombination -- Copyright : (c) Amy de Buitléir 2011-2014 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- Provides a mechanism to break apart and rejoin sequences of data. -- Inspired by DNA recombination in biology, this technique can be used -- to recombine \"genetic\" instructions for building artificial life. -- ------------------------------------------------------------------------ module ALife.Creatur.Genetics.Recombination ( crossover, cutAndSplice, mutateList, mutatePairedLists, randomOneOfList, randomOneOfPair, randomCrossover, randomCutAndSplice, repeatWithProbability, withProbability ) where import ALife.Creatur.Util (safeReplaceElement) import System.Random (Random) import Control.Monad.Random (Rand, RandomGen, getRandom, getRandomR) -- | Cuts two lists at the specified locations, swaps the ends, and -- splices them. The resulting lists will be: -- @ -- a[0..n-1] ++ b[m..] -- b[0..m-1] ++ a[n..] -- @ -- Here are some examples. -- @ -- /Expression/ /Result/ -- 'cutAndSplice' 2 5 (\"abcdef\", \"ABCDEF\") (\"abF\",\"ABCDEcdef\") -- 'cutAndSplice' 3 1 (\"abcd\", \"ABCDEFG\") (\"abcBCDEFG\",\"Ad\") -- 'cutAndSplice' 4 4 (\"abcdef\", \"ABCDEF\") (\"abcdEF\",\"ABCDef\") -- @ -- If n <= 0 or m <= 0, the corresponding input list will be completely -- transferred to the other. -- @ -- /Expression/ /Result/ -- 'cutAndSplice' 0 4 (\"abcdef\", \"ABCDEF\") (\"EF\",\"ABCDabcdef\") -- 'cutAndSplice' (-2) 4 (\"abcd\", \"ABCDEFGH\") (\"EFGH\",\"ABCDabcd\") -- 'cutAndSplice' 5 0 (\"abcdef\", \"ABCDEF\") (\"abcdeABCDEF\",\"f\") -- @ -- If n or m are greater than or equal to length of the corresponding list, -- that list will not be transferred. -- @ -- /Expression/ /Result/ -- 'cutAndSplice' 10 0 (\"abcdef\", \"ABCDEF\") (\"abcdefABCDEF\",\"\") -- 'cutAndSplice' 0 0 (\"\", \"ABCDEF\") (\"ABCDEF\",\"\") -- @ cutAndSplice :: Int -> Int -> ([a], [a]) -> ([a], [a]) cutAndSplice n m (as, bs) = (cs, ds) where cs = as1 ++ bs2 ds = bs1 ++ as2 (as1, as2) = splitAt n as (bs1, bs2) = splitAt m bs -- | Same as @'cutAndSplice'@, except that the two locations are -- chosen at random. randomCutAndSplice :: RandomGen g => ([a], [a]) -> Rand g ([a], [a]) randomCutAndSplice (as, bs) = do n <- getRandomR (0,length as - 1) m <- getRandomR (0,length bs - 1) return (cutAndSplice n m (as, bs)) -- | Cuts two lists at the specified location, swaps the ends, and -- splices them. This is a variation of 'cutAndSplice' where n == m. crossover :: Int -> ([a], [a]) -> ([a], [a]) crossover n = cutAndSplice n n -- | Same as @'crossover'@, except that the location is chosen at -- random. randomCrossover :: RandomGen g => ([a], [a]) -> Rand g ([a], [a]) randomCrossover (as, bs) = do n <- getRandomR (0,length as - 1) return (crossover n (as, bs)) -- | Mutates a random element in the list. mutateList :: (Random n, RandomGen g) => [n] -> Rand g [n] mutateList xs = do (i, _) <- randomListSelection xs x <- getRandom return (safeReplaceElement xs i x) -- | Mutates a random element in one list in a pair. mutatePairedLists :: (Random n, RandomGen g) => ([n], [n]) -> Rand g ([n], [n]) mutatePairedLists (xs,ys) = do chooseFst <- weightedRandomBoolean 0.5 if chooseFst then do xs' <- mutateList xs return (xs', ys) else do ys' <- mutateList ys return (xs, ys') -- | Performs an operation with the specified probability. withProbability :: RandomGen g => Double -> (b -> Rand g b) -> b -> Rand g b withProbability p op genes = do doOp <- weightedRandomBoolean p if doOp then op genes else return genes -- | Performs an operation a random number of times. -- The probability of repeating the operation @n@ times is @p^n@. repeatWithProbability :: RandomGen g => Double -> (b -> Rand g b) -> b -> Rand g b repeatWithProbability p op genes = do doOp <- weightedRandomBoolean p if doOp then do genes' <- op genes repeatWithProbability p op genes' else return genes -- :m + ALife.Creatur.Genetics.Gene -- let g = (replicate 10 A, replicate 10 C) -- evalRandIO (withProbability 0.1 randomCrossover g >>= withProbability 0.01 randomCutAndSplice >>= withProbability 0.001 mutatePairedLists) -- Any mixing of As and Cs will be the result of crossover (if the lengths are the same) or cut-and-splice (if the lengths are different). -- Any Gs or Ts that show up are the result of mutation. -- evalRandIO (withProbability 0.5 randomCrossover g >>= withProbability 0.05 randomCutAndSplice >>= withProbability 0.5 mutatePairedLists >>= randomOneOfPair) -- | Randomly select a boolean, but weighted to return True with probability -- p. weightedRandomBoolean :: RandomGen g => Double -> Rand g Bool weightedRandomBoolean p = do x <- getRandomR (0.0,1.0) return (x < p) randomOneOfPair :: RandomGen g => (a, a) -> Rand g a randomOneOfPair pair = do chooseFst <- weightedRandomBoolean 0.5 if chooseFst then return $ fst pair else return $ snd pair randomOneOfList :: RandomGen g => [a] -> Rand g a randomOneOfList xs = do (_, z) <- randomListSelection xs return z ---- | Sample a random element from a weighted list. ---- The total weight of all elements must not be 0. ---- Adapted from the code in MonadRandom --randomWeightedChoice :: RandomGen g => [(a, Double)] -> Rand g a --randomWeightedChoice [] = error "randomFromList called with empty list" --randomWeightedChoice [(x,_)] = return x --randomWeightedChoice xs = do -- let s = sum $ map snd xs -- total weight -- let cs = scanl1 (\(_,q) (y,s') -> (y, s'+q)) xs -- cumulative weight -- p <- getRandomR (0.0,s) -- return (fst (head (dropWhile (\(_,q) -> q < p) cs))) -- | Choose an element at random from a list and return the element and its -- index randomListSelection :: RandomGen g => [a] -> Rand g (Int, a) randomListSelection xs = do i <- getRandomR (0,length xs - 1) return (i, xs !! i)