{-# OPTIONS_GHC -fglasgow-exts #-} -- | Genetic Algorithms module GA (Config (..), PopulationConfig (..), ChromosomeConfig (..), defaultConfig, GAState, bestChromosome, gaRand, run, rouletteM, mutateM, crossM, tournamentM, isDone) where import Maybe import Random import Control.Monad.State.Strict type GAState c p = State (Config c p) data Config c p = Config { -- |The config for the chromosome model cConfig :: ChromosomeConfig c p, -- |The config for the population model pConfig :: PopulationConfig c p, -- |The function that transforms a population into the next generation newPopulation :: p -> (GAState c p) p, -- |The fitness at which to stop the GA maxFitness :: Maybe Double, -- |The generation at which to stop the GA maxGeneration :: Maybe Int, -- |The number of generations elapsed. defaultConfig sets this to 0 currentGeneration :: Int, -- |The random number generator gen :: StdGen } data ChromosomeConfig c p = ChromosomeConfig { -- |The fitness function for the chromosome model fitness :: c -> Double, -- |The mutation operator for the chromosome model mutate :: c -> (GAState c p) c, -- |The crossover operator for the chromosome model cross :: c -> c -> (GAState c p) (c,c) } data PopulationConfig c p = PopulationConfig { bestChromosomePop :: p -> (GAState c p) c, roulettePop :: p -> (GAState c p) p, tournamentPop :: p -> (GAState c p) p, applyCrossoverPop :: p -> (GAState c p) p, applyMutationPop :: p -> (GAState c p) p } -- |defaultConfig acts as a blank slate for genetic algorithms. -- cConfig, pConfig, gen, and maxFitness or maxGeneration must be defined defaultConfig :: Config c p defaultConfig = Config { cConfig = undefined, pConfig = undefined, newPopulation = undefined, maxFitness = Nothing, maxGeneration = Nothing, currentGeneration = 0, gen = undefined } -- |Wrapper function which returns the best chromosome of a population bestChromosome :: p -> (GAState c p) c bestChromosome pop = do config <- get bestChromosomePop (pConfig config) pop -- |Wrapper function which returns the highest-fitness member of a population highestFitness :: p -> (GAState c p) Double highestFitness pop = do fitFunc <- (fitness . cConfig) `liftM` get best <- bestChromosome pop return $ fitFunc best -- |A wrapper function for use in newPopulation for roulette selection rouletteM :: p -> (GAState c p) p rouletteM pop = (roulettePop . pConfig) `liftM` get >>= ($pop) -- |A wrapper function for use in newPopulation for tournament selection tournamentM :: p -> (GAState c p) p tournamentM pop = (tournamentPop . pConfig) `liftM` get >>= ($pop) -- |A wrapper function for use in newPopulation for mutating the population mutateM :: p -> (GAState c p) p mutateM pop = do (applyMutationPop . pConfig) `liftM` get >>= ($pop) -- |A wrapper function for use in newPopulation for applying crossover to the population crossM :: p -> (GAState c p) p crossM pop = (applyCrossoverPop . pConfig) `liftM` get >>= ($pop) newPopulationM :: p -> (GAState c p) p newPopulationM pop = incGA >> newPopulation `liftM` get >>= ($pop) incGA :: (GAState c p) () incGA = modify (\c@Config { currentGeneration = g} -> c { currentGeneration = g + 1}) untilM :: (Monad m) => (a -> m Bool) -> (a -> m a) -> a -> m a untilM p f x = do test <- p x if test then return x else f x >>= untilM p f -- |Runs the specified GA config until the termination condition is reached run :: p -> (GAState c p) p run = untilM isDone newPopulationM -- |Returns true if the given population satisfies the termination condition for the GA config isDone :: p -> (GAState c p) Bool isDone population = do c <- get f <- highestFitness population let generationsDone = maybe False (<(currentGeneration c)) $ maxGeneration c let fitnessDone = maybe False (>f) $ maxFitness c return $ generationsDone || fitnessDone -- |Generates a random number which updating the random number generator for the config gaRand :: (Random a) => (a,a) -> (GAState c p) a gaRand range = do config <- get let g = gen config let (x, g') = randomR range g put $ config { gen = g' } return x