-- | Simple parallel genetic algorithm implementation. module GA.Simple ( Chromosome(..), runGA, runGAIO, zeroGeneration, nextGeneration ) where import System.Random import qualified Data.List as L import Control.Parallel.Strategies -- | Chromosome representation class NFData a => Chromosome a where -- | Crossover function crossover :: RandomGen g => g -> a -> a -> ([a],g) -- | Mutation function mutation :: RandomGen g => g -> a -> (a,g) -- | Fitness function. fitness x > fitness y means that x is better than y fitness :: a -> Double -- | Pure GA implementation runGA :: (RandomGen g, Chromosome a) => g -- ^ Random number generator -> Int -- ^ Population size -> Double -- ^ Mutation probability [0, 1] -> (g -> (a, g)) -- ^ Random chromosome generator (hint: use closures) -> (a -> Int -> Bool) -- ^ Stopping criteria, 1st arg - best chromosome, 2nd arg - generation number -> a -- ^ Best chromosome runGA gen ps mp rnd stopf = let (pop, gen') = zeroGeneration gen rnd ps in runGA' gen' pop ps mp stopf 0 runGA' gen pop ps mp stopf gnum = let best = head pop in if stopf best gnum then best else let (pop', gen') = nextGeneration gen pop ps mp in runGA' gen' pop' ps mp stopf (gnum+1) -- | Non-pure GA implementation runGAIO :: Chromosome a => Int -- ^ Population size -> Double -- ^ Mutation probability [0, 1] -> (StdGen -> (a, StdGen)) -- ^ Random chromosome generator (hint: use closures) -> (a -> Int -> IO Bool) -- ^ Stopping criteria, 1st arg - best chromosome, 2nd arg - generation number -> IO a -- ^ Best chromosome runGAIO ps mp rnd stopf = do gen <- newStdGen let (pop, gen') = zeroGeneration gen rnd ps runGAIO' gen' pop ps mp stopf 0 runGAIO' gen pop ps mp stopf gnum = do let best = head pop stop <- stopf best gnum if stop then return best else do let (pop', gen') = nextGeneration gen pop ps mp runGAIO' gen' pop' ps mp stopf (gnum+1) -- | Generate zero generation. Use this function only if you are going to implement your own runGA. zeroGeneration :: (RandomGen g) => g -- ^ Random number generator -> (g -> (a, g)) -- ^ Random chromosome generator (hint: use closures) -> Int -- ^ Population size -> ([a],g) -- ^ Zero generation and new RNG zeroGeneration initGen rnd ps = L.foldl' (\(xs,gen) _ -> let (c, gen') = rnd gen in ((c:xs),gen')) ([], initGen) [1..ps] -- | Generate next generation (in parallel) using mutation and crossover. -- Use this function only if you are going to implement your own runGA. nextGeneration :: (RandomGen g, Chromosome a) => g -- ^ Random number generator -> [a] -- ^ Current generation -> Int -- ^ Population size -> Double -- ^ Mutation probability -> ([a], g) -- ^ Next generation ordered by fitness (best - first) and new RNG nextGeneration gen pop ps mp = let (gen':gens) = L.unfoldr (Just . split) gen chunks = L.zip gens $ init $ L.tails pop results = map (\(g, (x:ys)) -> [ (t, fitness t) | t <- nextGeneration' [ (x, y) | y <- ys ] g mp [] ]) chunks `using` parList rdeepseq lst = take ps $ L.sortBy (\(_, fx) (_, fy) -> fy `compare` fx) $ concat results in ( map fst lst, gen' ) nextGeneration' [] _ _ acc = acc nextGeneration' ((p1,p2):ps) g0 mp acc = let (children0, g1) = crossover g0 p1 p2 (children1, g2) = L.foldl' (\(xs, g) x -> let (x', g') = mutate g x mp in (x':xs, g')) ([],g1) children0 in nextGeneration' ps g2 mp (children1 ++ acc) mutate :: (RandomGen g, Chromosome a) => g -> a -> Double -> (a, g) mutate gen x mp = let (r, gen') = randomR (0.0, 1.0) gen in if r <= mp then mutation gen' x else (x, gen')