moo-1.2: examples/rosenbrock.hs
{- Minimize Rosenbrock function using real-valued genetic algorithm.
Optimal value x* = (1,...,1). F(x*) = 0.
It is a real-values genetic algorithm. The user may choose a
mutation and crossover operators. This example uses hooks to save
evolution history.
To run:
ghc --make rosenbrock.hs
./rosenbrock gm undx > output.txt
To visualize the output in gnuplot:
% gnuplot
> set logscale y ; set xlabel 'generation' ;
> plot 'output.txt' u 1:2 w l t 'median', '' u 1:3 w l t 'best' lt 3
-}
import Moo.GeneticAlgorithm.Continuous
import Control.Monad
import Data.List
import System.Environment (getArgs)
import System.Exit (exitWith, ExitCode(..))
import Text.Printf (printf)
rosenbrock :: [Double] -> Double
rosenbrock xs = sum . map f $ zip xs (drop 1 xs)
where
f (x1, x2) = 100.0 * (x2 - x1^(2::Int))^(2::Int) + (x1 - 1)^(2::Int)
nvariables = 3
xrange = (-30.0, 30.0)
popsize = 100
precision = 1e-5
maxiters = 4000 :: Int
elitesize = 10
-- Rosenbrock function is minimized
objective :: [Double] -> Objective
objective xs = rosenbrock xs
-- selection: tournament selection
select = tournamentSelect Minimizing 3 (popsize-elitesize)
-- Gaussian mutation, mutate fraction @genomeschanged@ of the population
gm genomeschanged =
let p = 1.0 - (1.0 - genomeschanged)**(1.0 / fromIntegral nvariables)
s = 0.01*(snd xrange - fst xrange)
in gaussianMutate p s
mutationOps = [ ("gm", gm 0.33) ]
-- BLX-0.5 crossover
blxa = blendCrossover 0.5
-- UNDX crossover
undx = unimodalCrossoverRP
-- SBX crossover
sbx = simulatedBinaryCrossover 2
crossoverOps = [ ("blxa", blxa), ("undx", undx), ("sbx", sbx) ]
printUsage = do
putStrLn usage
exitWith (ExitFailure 1)
where
usage = intercalate " " [ "rosenbrock", mops, xops ]
mops = intercalate "|" (map fst mutationOps)
xops = intercalate "|" (map fst crossoverOps)
logStats = WriteEvery 10 $ \iterno pop ->
let pop' = bestFirst Minimizing pop
bestobjval = takeObjectiveValue $ head pop'
medianobjval = takeObjectiveValue $ pop' !! (length pop' `div` 2)
in [(iterno, medianobjval, bestobjval)]
printStats :: [(Int, Objective, Objective)] -> IO ()
printStats stats = do
printf "# %-10s %15s %15s\n" "generation" "median" "best"
flip mapM_ stats $ \(iterno, median, best) ->
printf "%12d %15.3g %15.3g\n" iterno median best
geneticAlgorithm mutate crossover = do
-- initial population
let initialize = replicateM popsize $ replicateM nvariables (getRandomR xrange)
let stop = IfObjective ((<= precision) . minimum) `Or` Generations maxiters
let step = nextGeneration Minimizing objective select elitesize crossover mutate
--
let ga = loopWithLog logStats stop step
runGA initialize ga
printBest :: Population Double -> IO ()
printBest pop = do
let bestGenome = takeGenome . head $ bestFirst Minimizing pop
let vals = map (\x -> printf "%.5f" x) bestGenome
putStrLn $ "# best solution: " ++ (intercalate ", " vals)
-- usage: rosenbrock mutationOperator crossoverOperator
main = do
args <- getArgs
conf <- case args of
[] -> return (lookup "gm" mutationOps, lookup "undx" crossoverOps)
(m:x:[]) -> return (lookup m mutationOps, lookup x crossoverOps)
_ -> printUsage
case conf of
(Just mutate, Just crossover) -> do
(pop, stats) <- geneticAlgorithm mutate crossover
printStats stats
printBest pop
-- exit status depends on convergence
let bestF = takeObjectiveValue . head $ bestFirst Minimizing pop
if (abs bestF <= precision)
then exitWith ExitSuccess
else do
printf "# failed to converge: best residual=%.5g, target=%g\n" bestF precision
exitWith (ExitFailure 2) -- failed to find a solution
_ -> printUsage