module AI.MEP.Operators (
Config (..)
, defaultConfig
, LossFunction
, initialize
, evaluateGeneration
, evolve
, phenotype
, binaryTournament
, crossover
, mutation3
, smoothMutation
, newChromosome
) where
import Data.Vector ( Vector )
import qualified Data.Vector as V
import Data.List
( nub
, sortBy
)
import Data.Ord ( comparing )
import qualified Control.Monad as CM
import Control.Monad.Primitive ( PrimMonad )
import AI.MEP.Random
import AI.MEP.Types
import AI.MEP.Run ( evaluate )
data Config a = Config
{
p'const :: Double
, p'var :: Double
, p'mutation :: Double
, p'crossover :: Double
, c'length :: Int
, c'popSize :: Int
, c'popN :: Int
, c'ops :: Vector (F a)
, c'vars :: Int
}
defaultConfig :: Config Double
defaultConfig = Config
{
p'const = 0.1
, p'var = 0.4
, p'mutation = 0.1
, p'crossover = 0.9
, c'length = 50
, c'popSize = 100
, c'popN = 1
, c'ops = V.empty
, c'vars = 1
}
type LossFunction a =
((V.Vector a -> V.Vector a) -> (V.Vector Int, Double))
phenotype
:: Num a =>
LossFunction a
-> Chromosome a
-> Phenotype a
phenotype loss chr = let (is, val) = loss (evaluate chr)
in (val, chr, is)
initialize :: PrimMonad m => Config Double -> RandT m (Population Double)
initialize c@Config { c'popSize = size } = mapM (\_ -> newChromosome c) [1..size]
evaluateGeneration
:: Num a =>
LossFunction a
-> Population a
-> Generation a
evaluateGeneration loss = map (phenotype loss)
evolve
:: PrimMonad m =>
Config Double
-> LossFunction Double
-> (Chromosome Double -> RandT m (Chromosome Double))
-> (Chromosome Double -> Chromosome Double -> RandT m (Chromosome Double, Chromosome Double))
-> (Generation Double -> RandT m (Chromosome Double))
-> Generation Double
-> RandT m (Generation Double)
evolve c loss mut cross select phenotypes = do
let pc = p'crossover c
pm = p'mutation c
sort' = sortBy (comparing (\(val, _, _) -> negate val))
ev phen0 _ = do
chr1 <- select phen0
chr2 <- select phen0
(of1, of2) <- withProbability pc (uncurry cross) (chr1, chr2)
of1' <- withProbability pm mut of1
of2' <- withProbability pm mut of2
let r1@(val1, _, _) = phenotype loss of1'
r2@(val2, _, _) = phenotype loss of2'
(worstVal, _, _) = head phen0
phen' | val1 < worstVal = r1 : tail phen0
| val2 < worstVal = r2 : tail phen0
| otherwise = phen0
let phen1 = sort' phen'
return phen1
CM.foldM ev (sort' phenotypes) [1..c'popSize c `div` 2]
binaryTournament :: (PrimMonad m, Ord a) => Generation a -> RandT m (Chromosome a)
binaryTournament phen = do
(val1, cand1, _) <- drawFrom $ V.fromList phen
(val2, cand2, _) <- drawFrom $ V.fromList phen
if val1 < val2
then return cand1
else return cand2
crossover :: PrimMonad m =>
Chromosome a
-> Chromosome a
-> RandT m (Chromosome a, Chromosome a)
crossover ca cb = do
r <- V.zipWithM (curry (swap 0.5)) ca cb
return $ V.unzip r
swap :: PrimMonad m => Double -> (t, t) -> RandT m (t, t)
swap p = withProbability p (\(a, b) -> return (b, a))
replaceAt :: Int -> a -> Vector a -> Vector a
replaceAt i gene chr0 =
let (c1, c2) = V.splitAt i chr0
in c1 V.++ V.singleton gene V.++ V.tail c2
mutation3 :: PrimMonad m =>
Config Double
-> Chromosome Double
-> RandT m (Chromosome Double)
mutation3 c chr = do
is <- nub <$> CM.replicateM k (uniformIn_ (0, chrLen 1))
genes <- mapM new' is
let chr' = foldr (uncurry replaceAt)
chr
(zip is genes)
return chr'
where chrLen = V.length chr
k = 3
new' = new (p'const c) (p'var c) (c'vars c) (c'ops c)
smoothMutation
:: PrimMonad m =>
Double
-> Config Double
-> Chromosome Double
-> RandT m (Chromosome Double)
smoothMutation p c chr =
let new' = new (p'const c) (p'var c) (c'vars c) (c'ops c)
mutate i = withProbability p (\_ -> new' i)
in V.zipWithM mutate (V.enumFromN 0 (V.length chr)) chr
newChromosome :: PrimMonad m =>
Config Double
-> RandT m (Chromosome Double)
newChromosome c = do
let pConst = p'const c
pVar = p'var c
V.mapM (new pConst pVar (c'vars c) (c'ops c)) $ V.enumFromN 0 (c'length c)
new :: PrimMonad m =>
Double
-> Double
-> Int
-> Vector (F Double)
-> Int
-> RandT m (Gene Double Int)
new p1 p2 vars ops maxIndex = if maxIndex == 0
then let p1' = 0.5 * (1 + p1 p2)
in newTerminal p1' vars
else do
p' <- double
let sel | p' < p1 = newC
| p' < (p1 + p2) = newVar vars
| otherwise = newOp ops maxIndex
sel
newTerminal :: (PrimMonad m, Floating a, Variate a) =>
Double
-> Int
-> RandT m (Gene a i)
newTerminal p vars = do
p' <- double
if p' < p
then newC
else newVar vars
newVar :: PrimMonad m => Int -> RandT m (Gene a i)
newVar vars = do
var <- drawFrom $ V.enumFromN 0 vars
return $ Var var
newOp :: PrimMonad m =>
Vector (F a)
-> Int
-> RandT m (Gene a Int)
newOp ops maxIndex = do
op <- drawFrom ops
i1 <- uniformIn_ (0, maxIndex)
i2 <- uniformIn_ (0, maxIndex)
return $ Op op i1 i2
newC :: (PrimMonad m, Floating a, Variate a) => RandT m (Gene a i)
newC = C <$> uniformIn (0.5, 0.5)