GA (empty) → 0.1
raw patch · 9 files changed
+619/−0 lines, 9 filesdep +basedep +directorydep +randomsetup-changed
Dependencies added: base, directory, random
Files
- Changelog +11/−0
- GA.cabal +37/−0
- GA.hs +262/−0
- LICENSE +28/−0
- Makefile +7/−0
- README +63/−0
- Setup.hs +2/−0
- example1.hs +107/−0
- example2.hs +102/−0
+ Changelog view
@@ -0,0 +1,11 @@+Changelog for GA, a Haskell library for working with genetic algorithms:+------------------------------------------------------------------------++v0.1 (Aug. 31st 2011):++* initial release+* support for:+ - evolution of arbitrary entities (see Entity type class)+ - checkpointing between generations with automatic restore from checkpoint+* two toy examples+
+ GA.cabal view
@@ -0,0 +1,37 @@+Name: GA+Version: 0.1+Synopsis: Genetic algorithm library+License: BSD3+License-file: LICENSE+Author: Kenneth Hoste+Maintainer: kenneth.hoste@gmail.com+Copyright: (c) 2011 Kenneth Hoste+Homepage: http://boegel.kejo.be+Bug-reports: mailto:kenneth.hoste@gmail.com+Category: AI, Algorithms, Optimisation+Stability: Experimental+Build-type: Simple+Cabal-version: >= 1.6+Description:+ This package provides a framework for working with genetic+ algorithms. A genetic algorithm is an evolutionary technique, + inspired by biological evolution, to evolve entities that perform+ as good as possible in terms of a predefined criterion (the scoring + function). Note: lower scores are assumed to indicate better entities.+ The GA module provides a type class for defining entities and the+ functions that are required by the genetic algorithm.+ Checkpointing in between generations is available, as is automatic+ restoring from the last available checkpoint. + +Extra-source-files: example1.hs, example2.hs, Makefile+Tested-with: GHC==6.12.1++Library+ exposed-modules: GA+ extensions: FunctionalDependencies, MultiParamTypeClasses+ ghc-options: -Wall+ build-depends: base >= 4 && < 5, directory, random++source-repository head+ type: git+ location: git://github.com/boegel/GA.git
+ GA.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |GA, a Haskell library for working with genetic algoritms+--+-- Aug. 2011, by Kenneth Hoste+--+-- version: 0.1+module GA (Entity(..), + GAConfig(..), + ShowEntity(..), + evolve) where++import Data.List (intersperse, sortBy, nub)+import Data.Maybe (fromJust, isJust)+import Data.Ord (comparing)+import Debug.Trace (trace)+import System.Directory (createDirectoryIfMissing, doesFileExist)+import System.Random (StdGen, mkStdGen, randoms)++-- DEBUGGING++-- |Enable/disable debugging output (hard coded).+debug :: Bool+debug = False++-- |Return value with debugging output if debugging is enabled.+dbg :: String -> a -> a+dbg str x = if debug+ then trace str x+ else x ++-- |Currify a list of elements into tuples.+currify :: [a] -> [(a,a)]+currify (x:y:xs) = (x,y):currify xs+currify [] = []+currify [_] = error "(currify) ERROR: only one element left?!?"++-- |Take and drop elements of a list in a single pass.+takeAndDrop :: Int -> [a] -> ([a],[a])+takeAndDrop n xs+ | n > 0 = let (hs,ts) = takeAndDrop (n-1) (tail xs) in (head xs:hs, ts)+ | otherwise = ([],xs)+++-- |Configuration for genetic algorithm.+data GAConfig = GAConfig {+ -- |population size+ popSize :: Int, + -- |size of archive (best entities so far)+ archiveSize :: Int, + -- |maximum number of generations to evolve+ maxGenerations :: Int, + -- |fraction of entities generated by crossover (tip: >= 0.80)+ crossoverRate :: Float, + -- |fraction of entities generated by mutation (tip: <= 0.20)+ mutationRate :: Float, + -- |parameter for crossover (semantics depend on actual crossover operator)+ crossoverParam :: Float, + -- |parameter for mutation (semantics depend on actual mutation operator)+ mutationParam :: Float, + -- |enable/disable built-in checkpointing mechanism+ withCheckpointing :: Bool + }++-- |Type class for entities that represent a candidate solution.+--+-- Three parameters:+--+-- * data structure representing an entity (a)+--+-- * data used to score an entity, e.g. a list of numbers (b)+--+-- * some kind of pool used to generate random entities, e.g. a Hoogle database (c)+--+class (Eq a, Read a, Show a, ShowEntity a) => Entity a b c | a -> b, a -> c where+ -- |Generate a random entity.+ genRandom :: c -> Int -> a+ -- |Crossover operator: combine two entities into a new entity.+ crossover :: c -> Float -> Int -> a -> a -> Maybe a+ -- |Mutation operator: mutate an entity into a new entity.+ mutation :: c -> Float -> Int -> a -> Maybe a+ -- |Score an entity (lower is better).+ score :: a -> b -> Double++-- |A possibly scored entity.+type ScoredEntity a = (Maybe Double, a)++-- |Scored generation (population and archive).+type ScoredGen a = ([ScoredEntity a],[ScoredEntity a])++-- |Type class for pretty printing an entity instead of just using the default show implementation.+class ShowEntity a where+ -- |Show an entity.+ showEntity :: a -> String++-- |Show a scored entity.+showScoredEntity :: ShowEntity a => ScoredEntity a -> String+showScoredEntity (score,e) = "(" ++ show score ++ ", " ++ showEntity e ++ ")"++-- |Show a list of scored entities.+showScoredEntities :: ShowEntity a => [ScoredEntity a] -> String+showScoredEntities es = ("["++) . (++"]") . concat . intersperse "," $ map showScoredEntity es++-- |Initialize: generate initial population.+initPop :: (Entity a b c) => c -> Int -> [Int] -> ([Int],[a])+initPop src n seeds = (seeds'', entities)+ where+ (seeds',seeds'') = takeAndDrop n seeds+ entities = map (genRandom src) seeds'++-- |Score an entity (if it hasn't been already).+scoreEnt :: (Entity a b c) => b -> ScoredEntity a -> ScoredEntity a+scoreEnt d e@(Just _,_) = e+scoreEnt d (Nothing,x) = (Just $ score x d, x)++-- |Binary tournament selection operator.+tournamentSelection :: [ScoredEntity a] -> Int -> a+tournamentSelection xs seed = if s1 < s2 then x1 else x2+ where+ len = length xs+ g = mkStdGen seed+ is = take 2 $ map (flip mod len) $ randoms g+ [(s1,x1),(s2,x2)] = map ((!!) xs) is++-- |Function to perform a single evolution step:+--+-- * score all entities+--+-- * combine with best entities so far+--+-- * sort by fitness+--+-- * create new population using crossover/mutation+evolutionStep :: (Entity a b c) => c -> b -> (Int,Int,Int) -> (Float,Float) -> ScoredGen a -> (Int,Int) -> ScoredGen a+evolutionStep src d (cn,mn,an) (crossPar,mutPar) (pop,archive) (gi,seed) = dbg ( "\n\ngeneration " ++ (show gi) ++ ": \n\n" + ++ " scored population: " ++ (showScoredEntities scoredPop) ++ "\n\n"+ ++ " archive: " ++ (showScoredEntities archive') ++ "\n\n"+ ++ " archive fitnesses: " ++ (show $ map fst archive') ++ "\n\n"+ ++ " generated " ++ show (length pop') ++ " entities\n\n"+ ++ (replicate 150 '='))+ (pop',archive')+ where+ -- score population+ scoredPop = map (scoreEnt d) pop+ -- combine with archive for selection+ combo = scoredPop ++ archive+ -- split seeds for crossover selection/seeds, mutation selection/seeds+ seeds = randoms (mkStdGen seed) :: [Int]+ -- generate twice as many crossover/mutation entities as needed, because crossover/mutation can fail+ (crossSelSeeds,seeds') = takeAndDrop (2*2*cn) seeds+ (crossSeeds ,seeds'') = takeAndDrop (2*cn) seeds'+ (mutSelSeeds ,seeds''') = takeAndDrop (2*mn) seeds''+ (mutSeeds ,_) = takeAndDrop (2*mn) seeds'''+ -- crossover entities+ crossSel = currify $ map (tournamentSelection combo) crossSelSeeds+ crossEnts = take cn $ map fromJust $ filter isJust $ zipWith ($) (map (uncurry . (crossover src crossPar)) crossSeeds) crossSel+ -- mutation entities+ mutSel = map (tournamentSelection combo) mutSelSeeds+ mutEnts = take cn $ map fromJust $ filter isJust $ zipWith ($) (map (mutation src mutPar) mutSeeds) mutSel+ -- new population: crossovered + mutated entities+ pop' = zip (repeat Nothing) $ crossEnts ++ mutEnts+ -- new archive: best entities so far+ archive' = take an $ nub $ sortBy (comparing fst) $ filter (isJust . fst) combo++-- |Generate file name for checkpoint.+chkptFileName :: GAConfig -> (Int,Int) -> FilePath+chkptFileName cfg (gi,seed) = dbg fn fn+ where+ cfgTxt = (show $ popSize cfg) ++ "-" ++ + (show $ archiveSize cfg) ++ "-" +++ (show $ crossoverRate cfg) ++ "-" +++ (show $ mutationRate cfg) ++ "-" +++ (show $ crossoverParam cfg) ++ "-" +++ (show $ mutationParam cfg)+ fn = "checkpoints/GA-" ++ cfgTxt ++ "-gen" ++ (show gi) ++ "-seed-" ++ (show seed) ++ ".chk"++-- |Try to restore from checkpoint: first checkpoint for which a checkpoint file is found is restored.+restoreFromCheckpoint :: (Entity a b c) => GAConfig -> [(Int,Int)] -> IO (Maybe (Int,ScoredGen a))+restoreFromCheckpoint cfg ((gi,seed):genSeeds) = do+ chkptFound <- doesFileExist fn+ if chkptFound + then do+ txt <- dbg ("chk for gen. " ++ (show gi) ++ " found") readFile fn+ return $ Just (gi, read txt)+ else restoreFromCheckpoint cfg genSeeds+ where+ fn = chkptFileName cfg (gi,seed)+restoreFromCheckpoint cfg [] = return Nothing++-- |Checkpoint a single generation.+checkpointGen :: (Entity a b c) => GAConfig -> Int -> Int -> ScoredGen a -> IO()+checkpointGen cfg index seed (pop,archive) = do+ let txt = show $ (pop,archive)+ fn = chkptFileName cfg (index,seed)+ if debug + then putStrLn $ "writing checkpoint for gen " ++ (show index) ++ " to " ++ fn+ else return ()+ createDirectoryIfMissing True "checkpoints"+ writeFile fn txt++-- |Evolution: evaluate generation, (maybe) checkpoint, continue.+evolution :: (Entity a b c) => GAConfig -> ScoredGen a -> (ScoredGen a -> (Int,Int) -> ScoredGen a) -> [(Int,Int)] -> IO (ScoredGen a)+evolution cfg (pop,archive) step ((gi,seed):gss) = do+ let newPa@(_,archive') = step (pop,archive) (gi,seed)+ (Just fitness, e) = head archive'+ -- checkpoint generation if desired+ if (withCheckpointing cfg)+ then checkpointGen cfg gi seed newPa+ else return () -- skip checkpoint+ putStrLn $ "best entity (gen. " ++ show gi ++ "): " ++ (show e) ++ " [fitness: " ++ show fitness ++ "]"+ -- check for perfect entity+ if (fromJust $ fst $ head archive') == 0.0+ then do + putStrLn $ "perfect entity found, finished after " ++ show gi ++ " generations!"+ return newPa+ else evolution cfg newPa step gss+-- no more gen. indices/seeds => quit+evolution cfg (pop,archive) _ [] = do + putStrLn $ "done evolving!"+ return (pop,archive)+ +-- |Do the evolution!+evolve :: (Entity a b c) => StdGen -> GAConfig -> c -> b -> IO a+evolve g cfg src dataset = do+ -- generate list of random integers+ let rs = randoms g :: [Int]++ -- initial population+ let (rs',pop) = initPop src (popSize cfg) rs++ let ps = popSize cfg+ -- number of entities generated by crossover/mutation+ cCnt = round $ (crossoverRate cfg) * (fromIntegral ps)+ mCnt = round $ (mutationRate cfg) * (fromIntegral ps)+ -- archive size+ aSize = archiveSize cfg+ -- crossover/mutation parameters+ crossPar = crossoverParam cfg+ mutPar = mutationParam cfg+ -- seeds for evolution+ seeds = take (maxGenerations cfg) rs'+ -- seeds per generation+ genSeeds = zip [0..] seeds+ -- checkpoint?+ checkpointing = withCheckpointing cfg+ -- do the evolution+ restored <- if checkpointing+ then restoreFromCheckpoint cfg (reverse genSeeds) :: (Entity a b c) => IO (Maybe (Int,ScoredGen a))+ else return Nothing+ let (gi,(pop',archive')) = if isJust restored+ -- restored pop/archive from checkpoint+ then dbg ("restored from checkpoint!\n\n") $ fromJust restored + -- restore failed, new population and empty archive+ else dbg (if checkpointing then "no checkpoint found...\n\n"+ else "checkpoints ignored...\n\n") + (-1, (zip (repeat Nothing) pop, []))+ (resPop,resArchive) <- evolution cfg (pop',archive') (evolutionStep src dataset (cCnt,mCnt,aSize) (crossPar,mutPar)) (filter ((>gi) . fst) genSeeds)+ + if null resArchive+ then error $ "(evolve) empty archive!"+ else return $ snd $ head resArchive
+ LICENSE view
@@ -0,0 +1,28 @@+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Makefile view
@@ -0,0 +1,7 @@+all: example1 example2++%: GA.hs %.hs+ ghc --make $@++clean:+ rm -f *.hi *.o example1 example2
+ README view
@@ -0,0 +1,63 @@+GA, a Haskell library for working with genetic algorithms+---------------------------------------------------------++version 0.1, Aug. 2011, written by Kenneth Hoste (kenneth.hsote@gmail.com)+see http://hackage.haskell.org/package/GA++* DESCRIPTION++This package provides a framework for working with genetic+algorithms. A genetic algorithm is an evolutionary technique, +inspired by biological evolution, to evolve entities that perform+as good as possible in terms of a predefined criterion (the scoring +function). +Note: lower scores are assumed to indicate better entities.++The GA module provides a type class for defining entities and the+functions that are required by the genetic algorithm.++Checkpointing in between generations is available, as is automatic+restoring from the last available checkpoint. ++* BUILDING AND USING++Building the GA module and supplied examples can be done by running 'make'.++Using the GA module should be clear after studying the examples.++* EXAMPLES++This release includes two toy examples that show how to use the GA module.++A first example evolves the string "Hello World!". The string that the+genetic algorithm should generate is supplied by the user in this example,+which is of course not representative of a real world problem that could +be solved using genetic algorithms. However, it does serve well as a toy +example.++The code in example1.hs illustrates how you can define the 'genRandom', +'crossover', 'mutation' and 'score' functions that are required to run +the genetic algorithm using the 'evolve' function.++It also shows the use of a 'pool' that can be used to generate random+entities (a list of characters, in this particular case), and user-supplied+data that can be used to evaluate the fitness of entities (in this case,+the string "Hello World!").++Example command line (with checkpointing enabled):++ ./example1 100 25 200 0.8 0.2 0.0 0.2 True +RTS -M1G++The second example (see example2.hs) evolves an integer number that has+8 integer divisors, and for which the sum of its divisors equals 96.+Although using a genetic algorithm is probably not the best way to find +such an integer (it would be easier/faster to just go over integer values+one by one starting from e.g. 8), but again, it serves well as a toy example.++This example shows how the pool and score data do not have to be used; it+suffices to supply '()' as values to the evolve function, and to simply ignore+the respective arguments passed to the Entity typeclass functions.++Example command line:++ ./example2 20 10 100 0.8 0.2 0.0 0.2 False +RTS -M1G
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ example1.hs view
@@ -0,0 +1,107 @@+{--+ - Example for GA package+ - see http://hackage.haskell.org/package/GA+ -+ - Evolve the string "Hello World!"+--}++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances #-}++import GA (Entity(..), GAConfig(..), ShowEntity(..), evolve)+import Data.Char (chr,ord)+import Data.List (foldl')+import System (getArgs,getProgName)+import System.Random (mkStdGen, random, randoms)++-- efficient sum+sum' :: (Num a) => [a] -> a+sum' = foldl' (+) 0++--+-- GA TYPE CLASS IMPLEMENTATION+--++instance Entity String String [Char] where+ + -- generate a random entity, i.e. a random string+ -- assumption: max. 100 chars, only 'printable' ASCII (first 128)+ genRandom pool seed = take n $ map ((!!) pool) is+ where+ g = mkStdGen seed+ n = (fst $ random g) `mod` 101+ k = length pool+ is = map (flip mod k) $ randoms g++ -- crossover operator: mix (and trim to shortest entity)+ crossover _ _ seed e1 e2 = Just e+ where+ g = mkStdGen seed+ cps = zipWith (\x y -> [x,y]) e1 e2+ picks = map (flip mod 2) $ randoms g+ e = zipWith (!!) cps picks++ -- mutation operator: use next or previous letter randomly and add random characters (max. 9)+ mutation pool p seed e = Just $ (zipWith replace tweaks e) ++ addChars+ where+ g = mkStdGen seed+ k = round (1 / p) :: Int+ tweaks = randoms g :: [Int]+ replace i x = if (i `mod` k) == 0+ then if even i+ then if x > (minBound :: Char) then pred x else succ x+ else if x < (maxBound :: Char) then succ x else pred x+ else x+ is = map (flip mod $ length pool) $ randoms g+ addChars = take (seed `mod` 10) $ map ((!!) pool) is++ -- score: distance between current string and target+ -- sum of 'distances' between letters, large penalty for additional/short letters+ -- NOTE: lower is better+ score e x = fromIntegral $ d + 100*l+ where+ e' = map ord e+ x' = map ord x+ d = sum' $ map abs $ zipWith (-) e' x'+ l = abs $ (length x) - (length e)++instance ShowEntity String where + showEntity = show+ +main = do+ args <- getArgs+ progName <- getProgName+ if length args /= 8 + then error $ "Usage: <pop. size> <archive size> <max. # generations> " +++ "<crossover rate> <mutation rate> " +++ "<crossover parameter> <mutation parameter> " +++ "<enable checkpointing (bool)>"+ else return ()+ let popSize = read $ args !! 0+ archiveSize = read $ args !! 1+ maxGens = read $ args !! 2+ crossoverRate = read $ args !! 3+ mutationRate = read $ args !! 4+ crossoverPar = read $ args !! 5+ mutationPar = read $ args !! 6+ checkpointing = read $ args !! 7+ let cfg = GAConfig + popSize -- population size+ archiveSize -- archive size (best entities to keep track of)+ maxGens -- maximum number of generations+ crossoverRate -- crossover rate (% of new entities generated with crossover)+ mutationRate -- mutation rate (% of new entities generated with mutation)+ crossoverPar -- parameter for crossover operator (not used here)+ mutationPar -- parameter for mutation operator (ratio of replaced letters)+ checkpointing -- whether or not to use checkpointing++ g = mkStdGen 0 -- random generator++ -- pool of characters to pick from+ charsPool = map chr [32..126]+ -- Do the evolution!+ -- Note: if either of the last two arguments is unused, just use () as a value+ e <- evolve g cfg charsPool "Hello World!" :: IO String+ + putStrLn $ "best entity: " ++ (show e)
+ example2.hs view
@@ -0,0 +1,102 @@+{--+ - Example for GA package+ - see http://hackage.haskell.org/package/GA+ -+ - Evolve a single integer number to match the following features as closely as possible+ - * 8 integer divisors+ - * sum of divisors is 96+--}++{-# LANGUAGE MultiParamTypeClasses #-}++import GA (Entity(..), GAConfig(..), ShowEntity(..), evolve)+import Data.List (foldl')+import Debug.Trace+import System (getArgs,getProgName)+import System.Random (mkStdGen, random)++--+-- HELPER FUNCTIONS+--++-- find all divisors of a number+divisors :: Int -> [Int]+divisors n = concat $ map (divsFor n) [1..(sqrt' n)]+ where+ divsFor n x = if n `mod` x == 0+ then [x, n `div` x]+ else []++-- "integer" square root+sqrt' :: Int -> Int+sqrt' n = floor $ sqrt $ fromIntegral n++-- efficient sum+sum' :: (Num a) => [a] -> a+sum' = foldl' (+) 0++--+-- GA TYPE CLASS IMPLEMENTATION+--++instance Entity Int () () where+ + -- generate a random entity, i.e. a random integer value + genRandom _ seed = (fst $ random $ mkStdGen seed) `mod` 10000++ -- crossover operator: sum, (abs value of) difference or (rounded) mean+ crossover _ _ seed e1 e2 = Just $ case seed `mod` 3 of+ 0 -> e1+e2+ 1 -> abs (e1-e2)+ 2 -> (e1+e2) `div` 2++ -- mutation operator: add or subtract random value (max. 10)+ mutation _ _ seed e = Just $ if seed `mod` 2 == 0+ then e +(1 + seed `mod` 10)+ else abs (e - (1 + seed `mod` 10))++ -- score: how closely does the given number match the criteria?+ -- NOTE: lower is better+ score e _ = fromIntegral $ s + n+ where+ ds = divisors e+ s = abs $ (-) 96 $ sum' ds+ n = abs $ (-) 8 $ length ds++instance ShowEntity Int where + showEntity = show+ +main = do+ args <- getArgs+ progName <- getProgName+ if length args /= 8 + then error $ "Usage: <pop. size> <archive size> <max. # generations> " +++ "<crossover rate> <mutation rate> " +++ "<crossover parameter> <mutation parameter> " +++ "<enable checkpointing (bool)>"+ else return ()+ let popSize = read $ args !! 0+ archiveSize = read $ args !! 1+ maxGens = read $ args !! 2+ crossoverRate = read $ args !! 3+ mutationRate = read $ args !! 4+ crossoverPar = read $ args !! 5+ mutationPar = read $ args !! 6+ checkpointing = read $ args !! 7+ let cfg = GAConfig + popSize -- population size+ archiveSize -- archive size (best entities to keep track of)+ maxGens -- maximum number of generations+ crossoverRate -- crossover rate (% of new entities generated with crossover)+ mutationRate -- mutation rate (% of new entities generated with mutation)+ crossoverPar -- parameter for crossover operator (not used here)+ mutationPar -- parameter for mutation operator (ratio of replaced letters)+ checkpointing -- whether or not to use checkpointing++ g = mkStdGen 0 -- random generator++ -- Do the evolution!+ -- two last parameters (pool for generating new entities and extra data to score an entity) are unused in this example+ e <- evolve g cfg () () :: IO Int+ + putStrLn $ "best entity: " ++ (show e)