packages feed

GA 0.2 → 1.0

raw patch · 10 files changed

+483/−371 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ GA: hasConverged :: Entity e s d p m => [Archive e s] -> Bool
+ GA: showGeneration :: Entity e s d p m => Int -> Generation e s -> String
+ GA: type Archive e s = [ScoredEntity e s]
+ GA: type ScoredEntity e s = (Maybe s, e)
- GA: class (Eq e, Read e, Show e, Ord s, Read s, Show s, Monad m) => Entity e s d p m | e -> s, e -> d, e -> p, e -> m
+ GA: class (Eq e, Ord e, Read e, Show e, Ord s, Read s, Show s, Monad m) => Entity e s d p m | e -> s, e -> d, e -> p, e -> m
- GA: evolve :: Entity e s d p m => StdGen -> GAConfig -> p -> d -> m [ScoredEntity e s]
+ GA: evolve :: Entity e s d p m => StdGen -> GAConfig -> p -> d -> m (Archive e s)
- GA: evolveVerbose :: (Entity e s d p m, MonadIO m) => StdGen -> GAConfig -> p -> d -> m [ScoredEntity e s]
+ GA: evolveVerbose :: (Entity e s d p m, MonadIO m) => StdGen -> GAConfig -> p -> d -> m (Archive e s)
- GA: randomSearch :: Entity e s d p m => StdGen -> Int -> p -> d -> m [ScoredEntity e s]
+ GA: randomSearch :: Entity e s d p m => StdGen -> Int -> p -> d -> m (Archive e s)

Files

Changelog view
@@ -1,13 +1,15 @@ Changelog for GA, a Haskell library for working with genetic algorithms: ------------------------------------------------------------------------ -v0.1 (Aug. 31st 2011):+v1.0 (Sept. 27th 2011): -* initial release-* support for:-	- evolution of arbitrary entities (see Entity type class)-	- checkpointing between generations with automatic restore from checkpoint-* two toy examples+* reorganize examples+* minor code cleanup+* support for user-defined:+  - checking of progress (mustContinue)+  - print progress per generation (showProgress)+*  bug fixes:+  - double pop/archive entities  v0.2 (Sept. 19th 2011): @@ -19,3 +21,12 @@   progress to stdout (requires liftIO) * implemented random search * code cleanup and reorganization++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
@@ -1,5 +1,5 @@ Name:                GA-Version:             0.2+Version:             1.0 Synopsis:            Genetic algorithm library License:             BSD3 License-file:        LICENSE
GA.hs view
@@ -1,12 +1,155 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} --- |GA, a Haskell library for working with genetic algoritms+-- | GA, a Haskell library for working with genetic algoritms. -- -- Aug. 2011 - Sept. 2011, by Kenneth Hoste ----- version: 0.2-module GA (Entity(..), +-- version: 1.0+--+-- Major features:+--+--  * flexible user-friendly API for working with genetic algorithms+--+--  * Entity type class to let user define entity definition, scoring, etc.+--+--  * abstraction over monad, resulting in a powerful yet simple interface+--+--  * support for scoring entire population at once+--+--  * support for checkpointing each generation, +--    and restoring from last checkpoint+--+--  * convergence detection, as defined by user+--+--  * also available: random searching, user-defined progress output+--+--  * illustrative toy examples included+--+-- Hello world example:+--+-- > -- Example for GA package+-- > -- see http://hackage.haskell.org/package/GA+-- > --+-- > -- Evolve the string "Hello World!"+-- >+-- >{-# LANGUAGE FlexibleInstances #-}+-- >{-# LANGUAGE MultiParamTypeClasses #-}+-- >{-# LANGUAGE TypeSynonymInstances #-}+-- >+-- >import Data.Char (chr,ord)+-- >import Data.List (foldl')+-- >import System.Random (mkStdGen, random, randoms)+-- >import System.IO(IOMode(..), hClose, hGetContents, openFile)+-- >+-- >import GA (Entity(..), GAConfig(..), +-- >           evolveVerbose, randomSearch)+-- >+-- >-- efficient sum+-- >sum' :: (Num a) => [a] -> a+-- >sum' = foldl' (+) 0+-- >+-- >--+-- >-- GA TYPE CLASS IMPLEMENTATION+-- >--+-- >+-- >type Sentence = String+-- >type Target = String+-- >type Letter = Char+-- >+-- >instance Entity Sentence Double Target [Letter] IO where+-- > +-- >  -- generate a random entity, i.e. a random string+-- >  -- assumption: max. 100 chars, only 'printable' ASCII (first 128)+-- >  genRandom pool seed = return $ 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 = return $ 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 = return $ 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 fn e = do+-- >    h <- openFile fn ReadMode+-- >    x <- hGetContents h+-- >    length x `seq` hClose h+-- >    let e' = map ord e+-- >        x' = map ord x+-- >        d = sum' $ map abs $ zipWith (-) e' x'+-- >        l = abs $ (length x) - (length e)+-- >    return $ Just $ fromIntegral $ d + 100*l+-- >+-- >  -- whether or not a scored entity is perfect+-- >  isPerfect (_,s) = s == 0.0+-- >+-- >+-- >main :: IO() +-- >main = do+-- >        let cfg = GAConfig +-- >                    100 -- population size+-- >                    25 -- archive size (best entities to keep track of)+-- >                    300 -- maximum number of generations+-- >                    0.8 -- crossover rate (% of entities by crossover)+-- >                    0.2 -- mutation rate (% of entities by mutation)+-- >                    0.0 -- parameter for crossover (not used here)+-- >                    0.2 -- parameter for mutation (% of replaced letters)+-- >                    False -- whether or not to use checkpointing+-- >                    False -- don't rescore archive in each generation+-- >+-- >            g = mkStdGen 0 -- random generator+-- >+-- >            -- pool of characters to pick from: printable ASCII characters+-- >            charsPool = map chr [32..126]+-- >+-- >            fileName = "goal.txt"+-- >+-- >        -- write string to file, pretend that we don't know what it is+-- >        -- goal is to let genetic algorithm evolve this string+-- >        writeFile fileName "Hello World!"+-- >+-- >        -- Do the evolution!+-- >        -- Note: if either of the last two arguments is unused, just use () as a value+-- >        es <- evolveVerbose g cfg charsPool fileName+-- >        let e = snd $ head es :: String+-- >        +-- >        putStrLn $ "best entity (GA): " ++ (show e)+-- >+-- >        -- Compare with random search with large budget+-- >        -- 100k random entities, equivalent to 1000 generations of GA+-- >        es' <- randomSearch g 100000 charsPool fileName+-- >        let e' = snd $ head es' :: String+-- >       +-- >        putStrLn $ "best entity (random search): " ++ (show e')+--++module GA (Entity(..),+           ScoredEntity, +           Archive,             GAConfig(..),             evolve,             evolveVerbose,@@ -14,7 +157,7 @@  import Control.Monad (zipWithM) import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.List (sortBy, nub)+import Data.List (sortBy, nub, nubBy) import Data.Maybe (catMaybes, fromJust, isJust) import Data.Ord (comparing) import System.Directory (createDirectoryIfMissing, doesFileExist)@@ -36,6 +179,18 @@                    in (head xs:hs, ts)     | otherwise = ([],xs) +-- |A scored entity.+type ScoredEntity e s = (Maybe s, e)++-- |Archive of scored entities.+type Archive e s = [ScoredEntity e s]++-- |Scored generation (population and archive).+type Generation e s = ([e], Archive e s)++-- |Universe of entities.+type Universe e = [e]+ -- |Configuration for genetic algorithm. data GAConfig = GAConfig {     -- |population size@@ -73,10 +228,12 @@ -- -- * monad to operate in (m) ----- Minimal implementation includes genRandom, crossover, mutation, --- and either score', score or scorePop.+-- Minimal implementation should include 'genRandom', 'crossover', 'mutation', +-- and either 'score'', 'score' or 'scorePop'. ---class (Eq e, Read e, Show e, +-- The 'isPerfect', 'showGeneration' and 'hasConverged' functions are optional.+--+class (Eq e, Ord e, Read e, Show e,         Ord s, Read s, Show s,         Monad m)    => Entity e s d p m @@ -138,15 +295,27 @@                -> Bool -- ^ whether or not scored entity is perfect   isPerfect _ = False ---- |A possibly scored entity.-type ScoredEntity e s = (Maybe s, e)---- |Scored generation (population and archive).-type Generation e s = ([e],[ScoredEntity e s])+  -- |Show progress made in this generation.+  --+  -- Default implementation shows best entity.+  showGeneration :: Int -- ^ generation index+               -> Generation e s -- ^ generation (population and archive)+               -> String -- ^ string describing this generation+  showGeneration gi (_,archive) = "best entity (gen. " +                                ++ show gi ++ "): " ++ (show e) +                                ++ " [fitness: " ++ show fitness ++ "]"+    where+      (Just fitness, e) = head archive --- |Universe of entities.-type Universe e = [e]+  -- |Determine whether evolution should continue or not, +  --  based on lists of archive fitnesses of previous generations.+  --+  --  Note: most recent archives are at the head of the list.+  --+  --  Default implementation always returns False.+  hasConverged :: [Archive e s] -- ^ archives so far+               -> Bool -- ^ whether or not convergence was detected+  hasConverged _ = False  -- |Initialize: generate initial population. initPop :: (Entity e s d p m) => p -- ^ pool for generating random entities@@ -265,13 +434,16 @@     let -- new population: crossovered + mutated entities         newPop = crossEnts ++ mutEnts         -- new archive: best entities so far-        newArchive = take an $ nub $ sortBy (comparing fst) $ combo+        newArchive = take an +                   $ nubBy (\x y -> comparing snd x y == EQ) +                   $ sortBy (comparing fst) combo         newUniverse = nub $ universe ++ pop     return (newUniverse, (newPop,newArchive))  -- |Evolution: evaluate generation and continue. evolution :: (Entity e s d p m) => GAConfig -- ^ configuration for GA                                 -> Universe e -- ^ known entities +                                -> [Archive e s] -- ^ previous archives                                 -> Generation e s -- ^ current generation                                 -> (   Universe e                                     -> Generation e s @@ -280,14 +452,15 @@                                    ) -- ^ function that evolves a generation                                 -> [(Int,Int)] -- ^ gen indicies and seeds                                 -> m (Generation e s) -- ^evolved generation-evolution cfg universe gen step ((_,seed):gss) = do+evolution cfg universe pastArchives gen step ((_,seed):gss) = do     (universe',nextGen) <- step universe gen seed      let (Just fitness, e) = (head $ snd nextGen)-    if isPerfect (e,fitness)+        newArchive = snd nextGen+    if hasConverged pastArchives || isPerfect (e,fitness)       then return nextGen-      else evolution cfg universe' nextGen step gss+      else evolution cfg universe' (newArchive:pastArchives) nextGen step gss -- no more gen. indices/seeds => quit-evolution _ _ gen _              []    = return gen+evolution _ _ _ gen _ [] = return gen  -- |Generate file name for checkpoint. chkptFileName :: GAConfig -- ^ configuration for generation algorithm@@ -307,10 +480,10 @@  -- |Checkpoint a single generation. checkpointGen :: (Entity e s d p m) => GAConfig -- ^ configuraton for GA-                                  -> Int -- ^ generation index-                                  -> Int -- ^ random seed for generation-                                  -> Generation e s -- ^ current generation-                                  -> IO() -- ^ writes to file+                                    -> Int -- ^ generation index+                                    -> Int -- ^ random seed for generation+                                    -> Generation e s -- ^ current generation+                                    -> IO() -- ^ writes to file checkpointGen cfg index seed (pop,archive) = do     let txt = show $ (pop,archive)         fn = chkptFileName cfg (index,seed)@@ -320,9 +493,10 @@     writeFile fn txt  -- |Evolution: evaluate generation, (maybe) checkpoint, continue.-evolutionChkpt :: (Entity e s d p m, +evolutionVerbose :: (Entity e s d p m,                     MonadIO m) => GAConfig -- ^ configuration for GA                               -> Universe e -- ^ universe of known entities+                              -> [Archive e s] -- ^ previous archives                               -> Generation e s -- ^ current generation                               -> (   Universe e                                    -> Generation e s @@ -331,27 +505,29 @@                                  ) -- ^ function that evolves a generation                               -> [(Int,Int)] -- ^ gen indicies and seeds                               -> m (Generation e s) -- ^ evolved generation-evolutionChkpt cfg universe gen step ((gi,seed):gss) = do+evolutionVerbose cfg universe pastArchives gen step ((gi,seed):gss) = do     (universe',newPa@(_,archive')) <- step universe gen seed     let (Just fitness, e) = head archive'     -- checkpoint generation if desired     liftIO $ if (getWithCheckpointing cfg)       then checkpointGen cfg gi seed newPa       else return () -- skip checkpoint-    liftIO $ putStrLn $ "best entity (gen. " -                     ++ show gi ++ "): " ++ (show e) -                     ++ " [fitness: " ++ show fitness ++ "]"+    liftIO $ putStrLn $ showGeneration gi newPa     -- check for perfect entity-    if isPerfect (e, fitness)+    if hasConverged pastArchives || isPerfect (e,fitness)        then do -               liftIO $ putStrLn $ "perfect entity found, "-                                ++ "finished after " ++ show gi -                                ++ " generations!"+               liftIO $ putStrLn $ if isPerfect (e,fitness)+                                     then    "perfect entity found, "+                                          ++ "finished after " ++ show gi +                                          ++ " generations!"+                                     else    "no progress for 3 generations, "+                                          ++ "stopping after " ++ show gi+                                          ++ " generations!"                return newPa-       else evolutionChkpt cfg universe' newPa step gss+       else evolutionVerbose cfg universe' (archive':pastArchives) newPa step gss  -- no more gen. indices/seeds => quit-evolutionChkpt _ _ gen _ [] = do +evolutionVerbose _ _ _ gen _ [] = do      liftIO $ putStrLn $ "done evolving!"     return gen @@ -387,7 +563,7 @@                              -> GAConfig -- ^ configuration for GA                              -> p -- ^ random entities pool                              -> d -- ^ dataset required to score entities-                             -> m [ScoredEntity e s] -- ^ best entities+                             -> m (Archive e s) -- ^ best entities evolve g cfg pool dataset = do     -- initialize     (pop, cCnt, mCnt, aSize, @@ -398,7 +574,7 @@     -- do the evolution     let rescoreArchive = getRescoreArchive cfg     (_,resArchive) <- evolution -                       cfg [] (pop,[]) +                       cfg [] [] (pop,[])                         (evolutionStep pool dataset                                        (cCnt,mCnt,aSize)                                        (crossPar,mutPar) @@ -425,15 +601,17 @@     fn = chkptFileName cfg (gi,seed) restoreFromChkpt _ [] = return Nothing --- |Do the evolution (supports checkpointing). +-- |Do the evolution, verbosely. ----- Requires support for liftIO in monad used.-evolveVerbose :: (Entity e s d p m, -                  MonadIO m) => StdGen -- ^ random generator+-- Prints progress to stdout, and supports checkpointing. +--+-- Note: requires support for liftIO in monad used.+evolveVerbose :: (Entity e s d p m, MonadIO m) +                             => StdGen -- ^ random generator                              -> GAConfig -- ^ configuration for GA                              -> p -- ^ random entities pool                              -> d -- ^ dataset required to score entities-                             -> m [ScoredEntity e s] -- ^ best entities+                             -> m (Archive e s) -- ^ best entities evolveVerbose g cfg pool dataset = do     -- initialize     (pop, cCnt, mCnt, aSize, @@ -452,8 +630,8 @@         genSeeds' = filter ((>gi) . fst) genSeeds         rescoreArchive = getRescoreArchive cfg     -- do the evolution-    (_,resArchive) <- evolutionChkpt -                        cfg [] gen +    (_,resArchive) <- evolutionVerbose +                        cfg [] [] gen                          (evolutionStep pool dataset                                         (cCnt,mCnt,aSize)                                         (crossPar,mutPar) @@ -462,16 +640,18 @@     -- return best entity      return resArchive --- |Random search.+-- |Random searching. -- -- Useful to compare with results from genetic algorithm. randomSearch :: (Entity e s d p m) => StdGen -- ^ random generator                                    -> Int -- ^ number of random entities                                    -> p -- ^ random entity pool                                    -> d -- ^ scoring dataset-                                   -> m [ScoredEntity e s] -- ^ best ents+                                   -> m (Archive e s) -- ^ scored entities (sorted) randomSearch g n pool dataset = do     let seed = fst $ random g :: Int     es <- initPop pool n seed     scores <- scoreAll dataset [] es-    return $ zip scores es+    return $ nubBy (\x y -> comparing snd x y == EQ) +           $ sortBy (comparing fst)+           $ zip scores es
README view
@@ -1,7 +1,7 @@ GA, a Haskell library for working with genetic algorithms --------------------------------------------------------- -version 0.2, Sept. 2011, written by Kenneth Hoste (kenneth.hoste@gmail.com)+version 1.0, Sept. 2011, written by Kenneth Hoste (kenneth.hoste@gmail.com) see http://hackage.haskell.org/package/GA  * DESCRIPTION@@ -30,29 +30,35 @@  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!").--The second example (see example2.hs) evolves an integer number that has+The first example (see theNumber.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.+one by one starting from e.g. 8), but 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.+We use the score' function in this example, because the scoring itself+doesn't operate in a monad. -The third example reimplements the first example, but inside the IO monad.+A second example evolves the string "Hello World!". The string that the+genetic algorithm should generate is supplied by the user in this example,+and is printed to a file where the GA will read it from during scoring.+This is of course not representative of a real world problem that could +be solved using genetic algorithms, but again, it does serve well as a toy +example.++The code in hello.hs illustrates how you can define the "genRandom", +"crossover", "mutation" and "score" functions that are required to run +the genetic algorithm using the 'evolveVerbose' function. It also shows+an example of defining the "isPerfect" function to determine whether a+perfect entity was observed (and thus evolution can stop).++This example demonstrates 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 name of the file where the target string was written to).++It also shows how the GA module support operating in a monad, in this case +the IO monad, and illustrates the usefulness of the 'randomSearch' function.
examples/Makefile view
@@ -1,7 +1,7 @@-all: example1 example2 example3+all: theNumber hello  %: %.hs 	ghc --make -Wall $@  clean:-	rm -f *.hi *.o example1 example2 example3+	rm -f *.hi *.o theNumber hello
− examples/example1.hs
@@ -1,101 +0,0 @@-{--- - Example for GA package- - see http://hackage.haskell.org/package/GA- -- - Evolve the string "Hello World!"---}--{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances #-}--import Control.Monad.Identity (Identity(..))-import Data.Char (chr,ord)-import Data.List (foldl')-import System.Random (mkStdGen, random, randoms)--import GA (Entity(..), GAConfig(..), evolve)---- efficient sum-sum' :: (Num a) => [a] -> a-sum' = foldl' (+) 0------- GA TYPE CLASS IMPLEMENTATION-----type Sentence = String-type Target = String-type Letter = Char--instance Entity Sentence Double Target [Letter] Identity where- -  -- generate a random entity, i.e. a random string-  -- assumption: max. 100 chars, only 'printable' ASCII (first 128)-  genRandom pool seed = return $ 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 = return $ 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 = return $ 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' x e = Just $ 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)--  -- whether or not a scored entity is perfect-  isPerfect (_,s) = s == 0.0--main :: IO() -main = do-        let cfg = GAConfig -                    100 -- population size-                    25 -- archive size (best entities to keep track of)-                    300 -- maximum number of generations-                    0.8 -- crossover rate (% of entities by crossover)-                    0.2 -- mutation rate (% of entities by mutation)-                    0.0 -- parameter for crossover (not used here)-                    0.2 -- parameter for mutation (% of replaced letters)-                    False -- whether or not to use checkpointing-                    False -- don't rescore archive in each generation--            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-            (Identity es) = evolve g cfg charsPool "Hello World!"-            e = snd $ head es :: String-        -        putStrLn $ "best entity: " ++ (show e)
− examples/example2.hs
@@ -1,95 +0,0 @@-{--- - 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 #-}-{-# LANGUAGE TypeSynonymInstances #-}--import Control.Monad.Identity (Identity(..))-import Data.List (foldl')-import System.Random (mkStdGen, random)--import GA (Entity(..), GAConfig(..), evolve)------- HELPER FUNCTIONS------- find all divisors of a number-divisors :: Int -> [Int]-divisors n = concat $ map divsFor [1..(sqrt' n)]-  where-    divsFor x = if n `mod` x == 0-                     then [x, n `div` x]-                     else []---- "integer" square root-sqrt' :: Int -> Int-sqrt' n = floor (sqrt $ fromIntegral n :: Float)---- efficient sum-sum' :: (Num a) => [a] -> a-sum' = foldl' (+) 0------- GA TYPE CLASS IMPLEMENTATION-----type Number = Int--instance Entity Number Double () () Identity where- -  -- generate a random entity, i.e. a random integer value -  genRandom _ seed = return $ (fst $ random $ mkStdGen seed) `mod` 10000--  -- crossover operator: sum, (abs value of) difference or (rounded) mean-  crossover _ _ seed e1 e2 = return $ Just $ case seed `mod` 3 of-                                                  0 -> e1+e2-                                                  1 -> abs (e1-e2)-                                                  2 -> (e1+e2) `div` 2-                                                  _ -> error "crossover: unknown case"--  -- mutation operator: add or subtract random value (max. 10)-  mutation _ _ seed e = return $ 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 = Just $ fromIntegral $ s + n-    where-      ds = divisors e-      s = abs $ (-) 96 $ sum' ds-      n = abs $ (-) 8 $ length ds--  -- whether or not a scored entity is perfect-  isPerfect (_,s) = s == 0.0---main :: IO() -main = do-        let cfg = GAConfig -                    20 -- population size-                    10 -- archive size (best entities to keep track of)-                    100 -- maximum number of generations-                    0.8 -- crossover rate (% of entities by crossover)-                    0.2 -- mutation rate (% of entities by mutation)-                    0.0 -- parameter for crossover (not used here)-                    0.2 -- parameter for mutation (% of replaced letters)-                    False -- whether or not to use checkpointing-                    False -- don't rescore archive in each generation--            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-            (Identity es) = evolve g cfg () ()-            e = snd $ head es :: Int-        -        putStrLn $ "best entity: " ++ (show e)
− examples/example3.hs
@@ -1,100 +0,0 @@-{--- - Example for GA package- - see http://hackage.haskell.org/package/GA- -- - Evolve the string "Hello World!"---}--{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances #-}--import Data.Char (chr,ord)-import Data.List (foldl')-import System.Random (mkStdGen, random, randoms)--import GA (Entity(..), GAConfig(..), evolveVerbose)---- efficient sum-sum' :: (Num a) => [a] -> a-sum' = foldl' (+) 0------- GA TYPE CLASS IMPLEMENTATION-----type Sentence = String-type Target = String-type Letter = Char--instance Entity Sentence Double Target [Letter] IO where- -  -- generate a random entity, i.e. a random string-  -- assumption: max. 100 chars, only 'printable' ASCII (first 128)-  genRandom pool seed = return $ 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 = return $ 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 = return $ 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 x e = return $ Just $ 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)--  -- whether or not a scored entity is perfect-  isPerfect (_,s) = s == 0.0---main :: IO() -main = do-        let cfg = GAConfig -                    100 -- population size-                    25 -- archive size (best entities to keep track of)-                    300 -- maximum number of generations-                    0.8 -- crossover rate (% of entities by crossover)-                    0.2 -- mutation rate (% of entities by mutation)-                    0.0 -- parameter for crossover (not used here)-                    0.2 -- parameter for mutation (% of replaced letters)-                    False -- whether or not to use checkpointing-                    False -- don't rescore archive in each generation--            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-        es <- evolveVerbose g cfg charsPool "Hello World!"-        let e = snd $ head es :: String-        -        putStrLn $ "best entity: " ++ (show e)
+ examples/hello.hs view
@@ -0,0 +1,119 @@+{--+ - Example for GA package+ - see http://hackage.haskell.org/package/GA+ -+ - Evolve the string "Hello World!"+--}++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances #-}++import Data.Char (chr,ord)+import Data.List (foldl')+import System.Random (mkStdGen, random, randoms)+import System.IO(IOMode(..), hClose, hGetContents, openFile)++import GA (Entity(..), GAConfig(..), +           evolveVerbose, randomSearch)++-- efficient sum+sum' :: (Num a) => [a] -> a+sum' = foldl' (+) 0++--+-- GA TYPE CLASS IMPLEMENTATION+--++type Sentence = String+type Target = String+type Letter = Char++instance Entity Sentence Double Target [Letter] IO where+ +  -- generate a random entity, i.e. a random string+  -- assumption: max. 100 chars, only 'printable' ASCII (first 128)+  genRandom pool seed = return $ 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 = return $ 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 = return $ 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 fn e = do+    h <- openFile fn ReadMode+    x <- hGetContents h+    length x `seq` hClose h+    let e' = map ord e+        x' = map ord x+        d = sum' $ map abs $ zipWith (-) e' x'+        l = abs $ (length x) - (length e)+    return $ Just $ fromIntegral $ d + 100*l++  -- whether or not a scored entity is perfect+  isPerfect (_,s) = s == 0.0+++main :: IO() +main = do+        let cfg = GAConfig +                    100 -- population size+                    25 -- archive size (best entities to keep track of)+                    300 -- maximum number of generations+                    0.8 -- crossover rate (% of entities by crossover)+                    0.2 -- mutation rate (% of entities by mutation)+                    0.0 -- parameter for crossover (not used here)+                    0.2 -- parameter for mutation (% of replaced letters)+                    False -- whether or not to use checkpointing+                    False -- don't rescore archive in each generation++            g = mkStdGen 0 -- random generator++            -- pool of characters to pick from: printable ASCII characters+            charsPool = map chr [32..126]++            fileName = "goal.txt"++        -- write string to file, pretend that we don't know what it is+        -- goal is to let genetic algorithm evolve this string+        writeFile fileName "Hello World!"++        -- Do the evolution!+        -- Note: if either of the last two arguments is unused, just use () as a value+        es <- evolveVerbose g cfg charsPool fileName+        let e = snd $ head es :: String+        +        putStrLn $ "best entity (GA): " ++ (show e)++        -- Compare with random search with large budget+        -- 100k random entities, equivalent to 1000 generations of GA+        es' <- randomSearch g 100000 charsPool fileName+        let e' = snd $ head es' :: String+        +        putStrLn $ "best entity (random search): " ++ (show e')
+ examples/theNumber.hs view
@@ -0,0 +1,92 @@+{--+ - 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 #-}+{-# LANGUAGE TypeSynonymInstances #-}++import Control.Monad.Identity (Identity(..))+import Data.List (foldl')+import System.Random (mkStdGen, random)++import GA (Entity(..), GAConfig(..), evolve)++--+-- HELPER FUNCTIONS+--++-- find all divisors of a number+divisors :: Int -> [Int]+divisors n = concat $ map divsFor [1..(sqrt' n)]+  where+    divsFor x = if n `mod` x == 0+                     then [x, n `div` x]+                     else []++-- "integer" square root+sqrt' :: Int -> Int+sqrt' n = floor (sqrt $ fromIntegral n :: Float)++-- efficient sum+sum' :: (Num a) => [a] -> a+sum' = foldl' (+) 0++--+-- GA TYPE CLASS IMPLEMENTATION+--++type Number = Int++instance Entity Number Double () () Identity where+ +  -- generate a random entity, i.e. a random integer value +  genRandom _ seed = return $ (fst $ random $ mkStdGen seed) `mod` 10000++  -- crossover operator: sum, (abs value of) difference or (rounded) mean+  crossover _ _ seed e1 e2 = return $ Just $ case seed `mod` 3 of+                                                  0 -> e1+e2+                                                  1 -> abs (e1-e2)+                                                  2 -> (e1+e2) `div` 2+                                                  _ -> error "crossover: unknown case"++  -- mutation operator: add or subtract random value (max. 10)+  mutation _ _ seed e = return $ 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 = Just $ fromIntegral $ s + n+    where+      ds = divisors e+      s = abs $ (-) 96 $ sum' ds+      n = abs $ (-) 8 $ length ds+++main :: IO() +main = do+        let cfg = GAConfig +                    20 -- population size+                    10 -- archive size (best entities to keep track of)+                    100 -- maximum number of generations+                    0.8 -- crossover rate (% of entities by crossover)+                    0.2 -- mutation rate (% of entities by mutation)+                    0.0 -- parameter for crossover (not used here)+                    0.2 -- parameter for mutation (% of replaced letters)+                    False -- whether or not to use checkpointing+                    False -- don't rescore archive in each generation++            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+            (Identity es) = evolve g cfg () ()+            e = snd $ head es :: Int+        +        putStrLn $ "best entity: " ++ (show e)