packages feed

metaheuristics-0.0.8: Control/Metaheuristics.hs

{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes #-}

module Control.Metaheuristics(
  HasQuality,quality,
  -- * Termination and post processing
  takeWhileProperty,
  -- * Execution of search processes
  openStreamMapArrow,
  executeMetaheuristic,
  -- * Simple lifting and injection
  mapArrow,
  inject,
  -- * General stream arrow combinators
  chunk,
  dechunk,
  stretch,
  doMany,
  delay,
  window,
  fanAll,
  newArrow,
  -- * Selection and filtering
  improvingFilter,
  improving,
  selectBest,
  selectRandom,
  diffFilter,
  -- * Escape Strategies
  escapeStrategy,
  replace,
  restart,
  uniformChoice,
  
  -- * SA cooling strategies
  logCooling,
  linCooling,
  geoCooling,
  
  -- * Metaheuristic algorithms
  iterativeImprover,
  stochasticIterativeImprover,
  simpleTABU,
  tabu,
  mergeStochasticAndTemperature,
  simulatedAnnealing,
  breed1,ga,
  populationPattern,degrade,aco
  ) where

import Control.Arrow
import Data.Stream (Stream(..),(<:>))
import qualified Data.Stream as S
import Control.Arrow.Transformer.Stream (StreamArrow(..),StreamMap(..))
import System.Random
import qualified Data.List as L

class Num v=>HasQuality f v where
  quality :: f->v

takeWhileProperty :: ([a]->Bool)->Int ->Stream a->[a]
takeWhileProperty term sz input = twi' input
  where
    -- twi' :: Stream a->[a]
    twi' xs = if term (S.take sz xs)
                then S.take sz xs
                else (S.head xs) : twi' (S.tail xs)


openStreamMapArrow :: StreamMap a b->Stream a->Stream b
openStreamMapArrow (StreamArrow f) = f

executeMetaheuristic :: StreamArrow (->) b b->[b]->Stream b 
executeMetaheuristic a x = let xs = S.prefix x (openStreamMapArrow a xs) in xs

newArrow :: (Stream a->Stream b)->StreamMap a b
newArrow = StreamArrow

mapArrow :: (a->b)->StreamMap a b
mapArrow f = StreamArrow (S.map f)

inject :: Stream a->(a->b->c)->StreamMap b c
inject s f = StreamArrow (S.zipWith f s)

chunk :: Int->StreamMap a [a]
chunk n = StreamArrow (f ) 
  where f ys = S.take n ys `S.Cons` (f $ S.drop n ys)


dechunk :: StreamMap [a] a
dechunk = StreamArrow (S.fromList . concat. S.toList )


stretch:: Int->StreamMap sol sol
stretch n = StreamArrow 
  (S.fromList  . concatMap (replicate n) .    S.toList )


doMany :: Int->StreamMap sol sol'->StreamMap sol sol'
doMany n f = stretch n >>> f

delay::x->StreamMap x x
delay x=StreamArrow(S.Cons x)

window :: Int->StreamMap sol [sol]
window n=StreamArrow(\xs->S.scan f [] xs)
  where f qu x=if length qu==n then f (tail qu) x else qu++ [x]

fanAll :: [StreamMap a b]->StreamMap a [b]
fanAll fs = StreamArrow (\xs->S.fromList $ L.transpose [S.toList (f xs) | f<-map openStreamMapArrow fs       ])

improvingFilter :: Ord a=>StreamMap ([a],a) [a] 
improvingFilter = StreamArrow $ S.map (\(xs,x)->filter (<x) xs)


improving :: Ord a=>StreamMap a [a]->StreamMap a [a]
improving nF = nF &&& (StreamArrow id)  >>> improvingFilter

historyFilter :: Eq a => StreamMap ([a],[a]) [a]
historyFilter = mapArrow (\(n,h)->n L.\\ h)

selectBest :: Ord a=>StreamMap  [a] a 
selectBest = StreamArrow $ S.map minimum

selectRandom :: RandomGen g=>g->StreamMap [a] a
selectRandom seedG = StreamArrow (f seedG)
  where f g xss = let xs = S.head xss
                      (i,g') = randomR (0,length xs-1) g
                  in xs !! i <:> f g' (S.tail xss)


replace :: Stream sol ->StreamMap sol sol
replace supply = StreamArrow (const supply)

escapeStrategy :: StreamMap sol (Either sol sol)->StreamMap sol sol->StreamMap sol sol->StreamMap sol sol
escapeStrategy escapeTest s1 s2 = escapeTest >>> (s1 ||| s2)

restart :: StreamMap sol (Either sol sol)->StreamMap sol sol->Stream sol->StreamMap sol sol
restart restartTest s1 solSupply = escapeStrategy restartTest s1 (replace solSupply)

iterativeImprover :: Ord sol=>StreamMap sol [sol]->StreamMap sol sol
iterativeImprover neighbourhoodArrow = improving neighbourhoodArrow >>> selectBest

stochasticIterativeImprover :: (Ord sol,RandomGen g)=>g->StreamMap sol [sol]->StreamMap sol sol
stochasticIterativeImprover g neighbourhoodArrow = improving neighbourhoodArrow >>> selectRandom g

simpleTABU :: Ord sol=>StreamMap sol [sol]->Int->StreamMap sol sol 
simpleTABU nF n = nF &&& (window n) >>> historyFilter >>> selectBest

tabu :: Ord sol=>StreamMap sol [sol]->Int->StreamMap sol sol 
tabu nF n = fanAll [ improving nF,
                     nF &&& window n >>> historyFilter,
                     nF] >>> mapArrow concat >>> mapArrow head

mergeStochasticAndTemperature:: Floating v=>S.Stream v -> S.Stream v -> S.Stream v
mergeStochasticAndTemperature = S.zipWith (\r t ->1.0/t - log r)

--diffChoice :: (Num a, Ord a, HasQuality f) => a -> (f, f) -> f
--diffChoice r (a,b) = if r >= quality a - quality b then b else a

--sa :: HasQuality sol => StreamMap sol sol->S.Stream Double->S.Stream Double -> StreamMap sol sol
--sa mutate rs ts = (StreamArrow id)&&&mutate >>> inject (mergeStochasticAndTemperature rs ts) diffChoice

diffFilter :: (Num a, Ord a, HasQuality f a) => a -> (f, [f]) -> [f]
diffFilter r (s,xs) = [x | x<-xs, r >= quality s - quality x]

simulatedAnnealing :: (Floating v,Num v,Ord v, Ord sol, HasQuality sol v) =>StreamMap sol [sol] -> S.Stream v -> S.Stream v -> StreamMap sol sol
simulatedAnnealing neighbourhood rs ts = fanAll [ improving neighbourhood,
                                                  (StreamArrow id)&&&neighbourhood >>> inject (mergeStochasticAndTemperature rs ts) diffFilter,
                                                  mapArrow (:[]) ] >>> mapArrow concat >>> mapArrow head

{-| A logarithmic cooling strategy intended for use within simulated annealing. Broadly the first value is 
    the starting temperature and the second a value between 0 and 1. -}
logCooling :: Floating b=>b->b->[b]
logCooling c d = map (\t->c / (log (t + d))) (iterate (+1) 1)

{-| The most common cooling strategy for simulated annealing, geometric. The first value is the starting temperature, 
    the second a value between 0 and 1, the cooling rate.  -}
geoCooling :: Floating b=>b->b->[b]
geoCooling startTemp tempChange = iterate (* tempChange) startTemp

{-| Included for completeness, this is a cooling strategy for simulated annealing that is usually not very effective,
    a linear changing strategy. The first value is the starting temperature the second is the value to increase it by 
    at each step. In order to have it reduce at each step, pass a negative value. 
-}
linCooling :: Floating b=>b->b->[b]
linCooling startTemp tempChange= iterate (+ tempChange) startTemp

breed1 :: RandomGen g=>StreamMap [sol] sol->g->StreamMap [sol] sol
breed1 recombine g1 = doMany 2 (selectRandom g1) >>> chunk 2 >>> recombine

ga :: Int ->StreamMap sol (Either sol sol)->StreamMap sol sol ->StreamMap [sol] sol->StreamMap sol sol
ga popSize mutateTest mutate breed = chunk popSize >>> doMany popSize breed >>> mutateTest >>> (StreamArrow id ||| mutate)


uniformChoice :: RandomGen g=>Float->g->StreamMap sol (Either sol sol)
uniformChoice chance g = StreamArrow (S.fromList . f (randoms g) . S.toList)
  where f (x:xs) (s:sols) | x<chance = Left s:f xs sols
                          | otherwise = Right s : f xs sols

populationPattern::Int->(sol->pat)->StreamMap [pat] pat-> StreamMap sol pat 
populationPattern popSize createPattern mergePattern=mapArrow createPattern>>>chunk popSize>>>mergePattern>>>stretch popSize

degrade :: StreamMap a a -> StreamMap (a,a) a->StreamMap a a 
degrade df mf = loop (second df >>> mf >>> (StreamArrow id &&& StreamArrow id))

aco :: pat->Int->(sol -> pat)->StreamMap [pat] pat ->StreamMap (pat, pat) pat->StreamMap pat pat ->StreamMap pat sol->StreamMap sol sol
aco defaultPattern popsize cfp mergePopulation mergePair df createsolution = mapArrow cfp >>> chunk popsize >>> mergePopulation >>> degrade (delay defaultPattern >>> df ) mergePair >>> stretch popsize >>> createsolution