{-# LANGUAGE ScopedTypeVariables, ViewPatterns, BangPatterns, DeriveDataTypeable, RecordWildCards, GeneralizedNewtypeDeriving, MultiParamTypeClasses, RankNTypes, ImpredicativeTypes, TypeFamilies, UndecidableInstances, TemplateHaskell,TypeOperators #-} -- |Module : Numeric.Optimization.Algorithms.DifferentialEvolution -- Copyright : (c) 2011 Ville Tirronen -- License : MIT -- -- Maintainer : ville.tirronen@jyu.fi -- -- This module implements basic version of Differential Evolution algorithm -- for finding minimum of possibly multimodal and non-differentiable real valued -- functions. -- -- Example -- >>>import Data.Vector.Unboxed as VUB -- -- >>>import Numeric.Optimization.Algorithms.DifferentialEvolution -- -- >>>let fitness = VUB.sum . VUB.map (*2) -- -- >>>de (defaultParams fitness ((VUB.replicate 60 0), (VUB.replicate 60 0))) -- (0.12486060253695,fromList [2.481036288296201e-3, ... ] module Numeric.Optimization.Algorithms.DifferentialEvolution( -- * Basic Types Vector, Bounds, Fitness, Budget, DeMonad, -- * Control Parameters DEArgs(..), Strategy(..), strategy, defaultParams, -- * Accessing internal state of the algorithm evaluationCount, population, optimizationTrace, -- * Executing the algorithm runDE, de, deStep) where import qualified Control.Parallel.Strategies as CPS import Control.DeepSeq import qualified Data.Vector as V import Data.Vector ((!)) import qualified Data.Vector.Unboxed as VUB import qualified Data.Vector.Unboxed.Mutable as MUB import Data.Function import Data.Record.Label import Control.Monad import Control.Arrow ((&&&)) import Control.Monad.ST import Control.Monad.State import Control.Monad.Primitive import System.Random.MWC import Data.Word -- import Test.QuickCheck hiding (Gen) -- |Vector type for storing trial points type Vector = VUB.Vector Double -- |Type for storing function domain orthope. (Structure of arrays seems -- more efficient than array of structures) type Bounds = (VUB.Vector Double, VUB.Vector Double) -- |Fitness function type type Fitness = Vector -> Double -- |Termination condition type. (Currently just a hard limit on evaluation count) type Budget = Int data DEParams s = DEParams {_gen :: GenST s ,_ec :: Int ,_pop :: V.Vector (Double,Vector) ,_trace :: [(Int,Double,String)] } $(mkLabels [''DEParams]) -- |The current number of fitness evaluations evaluationCount :: forall s. DEParams s :-> Int evaluationCount = ec -- |The current set of active trial points population :: forall s. DEParams s :-> V.Vector (Double,Vector) population = pop -- |The execution trace of current run optimizationTrace :: forall s. DEParams s :-> [(Int,Double,String)] optimizationTrace = trace -- |Monad for storing optimization trace and random number generator newtype DeMonad s a = DE (StateT (DEParams s) (ST s) a) deriving (Monad) instance MonadState (DEParams s) (DeMonad s) where get = DE $ get put a = DE $ put a instance HasPRNG (DeMonad s) where type S (DeMonad s) = s withGen op = get >>= \x -> op (_gen x) liftST :: ST s a -> DeMonad s a liftST op = DE $ lift op -- |Extract values from the DeMonad. runDE :: (forall s. DeMonad s a) -> a runDE de = runST (let (DE a ) = de in evalStateT a $ DEParams (error "Generator uninitialized") 0 (error "Population unitialized") []) logPoint :: String -> DeMonad s () logPoint tx = do (cb,_) <- getM pop >>= return . V.minimumBy (compare`on`fst) e <- getM ec modM trace ((e,cb,tx):) -- HasPRNG related class HasPRNG m where type S m :: * withGen :: (Gen (S m) -> m b) -> m b selectRandom :: (PrimMonad m) => Gen (PrimState m) -> Int -> V.Vector a -> m [a] selectRandom !gen !n vec = do let idx = replicate n 0 --idx <- replicateM n (randomIndex (V.length vec) gen) return $ map (V.unsafeIndex vec) idx {-#INLINE randomIndex#-} randomIndex !ub gen = uni >>= return . floor . (fromIntegral ub*) where uni = do x <- (uniform gen) return (x::Float) expVariate !lambda gen = do u :: Float <- uniform gen return . round $ (- log (u))/(1-lambda) expVariate' !lambda gen = work 0 where work !n = do x :: Float <- uniform gen if x),(<->) :: Vector -> Vector -> Vector a <+> b = VUB.zipWith (+) a b a <-> b = VUB.zipWith (-) a b (*|) :: Double -> Vector -> Vector a *| b = VUB.map (a*) b -- -- -- -- |Different strategies for optimization data Strategy = Rand1Bin {cr ::{-# UNPACK #-} !Float, f ::{-# UNPACK #-} !Double} | Rand2Bin {cr ::{-# UNPACK #-} !Float, f ::{-# UNPACK #-} !Double} | Rand1Exp {cr ::{-# UNPACK #-} !Float, f ::{-# UNPACK #-} !Double} | Rand2Exp {cr ::{-# UNPACK #-} !Float, f ::{-# UNPACK #-} !Double} deriving (Show) type DEStrategy s = GenST s -> Int -> Vector -> V.Vector (Double, Vector) -> DeMonad s Vector -- |Convert a Showable strategy into executable one strategy :: Strategy -> DEStrategy s strategy (Rand1Bin{..}) = strat' f cr rand1 binCrossover strategy (Rand2Bin{..}) = strat' f cr rand2 binCrossover strategy (Rand1Exp{..}) = strat' f cr rand1 expCrossover strategy (Rand2Exp{..}) = strat' f cr rand2 expCrossover strat' f cr m co = \gen l parent pop -> liftST (m gen f pop >>= \x -> co l cr parent x gen) {-# INLINE strategy #-} rand1 :: GenST s -> Double -> V.Vector (Double,Vector) -> ST s Vector rand1 gen f pop = do [x1,x2,x3] <- selectRandom gen 3 $ V.map snd pop return $ x1 <+> (f *| (x2 <-> x3)) rand2 :: GenST s -> Double -> V.Vector (a, Vector) -> ST s Vector rand2 gen f pop = do [x1,x2,x3,x4,x5] <- selectRandom gen 5 $ V.map snd pop return $ x1 <+> (f *| (x2 <-> x3)) <+> (f *| (x4 <-> x5)) expCrossover :: (PrimMonad m, VUB.Unbox t) => Int -> Float -> VUB.Vector t -> VUB.Vector t -> Gen (PrimState m) -> m (VUB.Vector t) expCrossover l cr a b gen = do n' :: Int <- expVariate cr gen index <- randomIndex l gen let n = min n' (l-1) m = index + n - l nmax = min n (l-index) overflow = index+n-l end = min l (index+n) return (moduloReplacement1 index n l a b) -- return $ VUB.modify (\v -> do -- -- MUB.write v index (b VUB.! index) -- forM_ ([0..overflow-1]++[index..end-1]) $ \i -> (MUB.write v i (b VUB.! i))) -- a {- prop_r1_len s' l' = VUB.length (moduloReplacement1 s l dim a b) == dim where s = abs s' `mod` dim l = abs l' `mod` dim dim = 100 a = VUB.replicate dim (0::Int) b = VUB.replicate dim (1::Int) prop_r1_cs s' l' = (VUB.length $ VUB.filter (==1) (moduloReplacement1 s l tdim tva tvb)) == max 1 l where s = abs s' `mod` tdim l = abs l' `mod` tdim prop_r1_eq2 s' l' = (moduloReplacement1 s l tdim tva tvb) == (moduloReplacement2 s l tdim tva tvb) where s = abs s' `mod` tdim l = abs l' `mod` tdim prop_r1_eq3 s' l' = (moduloReplacement1 s l tdim tva tvb) == (moduloReplacement3 s l tdim tva tvb) where s = abs s' `mod` tdim l = abs l' `mod` tdim tdim = 100 tva = VUB.replicate tdim (0::Int) tvb = VUB.replicate tdim (1::Int) -} moduloReplacement1 start length dim a b = VUB.modify (\v -> do MUB.write v start (b VUB.! start) forM_ ([0..overflow-1]++[start..end-1]) $ \i -> (MUB.write v i (b VUB.! i))) a where overflow = start+length-dim end = min dim $ start+length {-#INLINE moduloReplacement2 #-} moduloReplacement2 start length dim a b = VUB.generate dim (\i -> if (i>=start && i < end) || i < overflow || i==start then b VUB.! i else a VUB.! i ) where overflow = start+length-dim end = min dim $ start+length {-#INLINE moduloReplacement3 #-} moduloReplacement3 start length dim a b = VUB.map (\(e1,e2,i) -> if (i>=start && i if i>=index && i < (index+n) || i < m -- then a VUB.! i else b VUB.! i ) -- return $ VUB.map (\(e1,e2,i) -> if (i>=index && i<(index+n)) || i Int -> Float -> VUB.Vector t -> VUB.Vector t -> Gen (PrimState m) -> m (VUB.Vector t) binCrossover l cr a b gen = do randoms :: VUB.Vector Float <- VUB.replicateM l (uniform gen) index :: Int <- randomIndex l gen return $ VUB.map (\(x,e1,e2,i) -> if x>=save) dimension = VUB.length . fst $ bounds saturateVector :: Bounds -> VUB.Vector Double -> VUB.Vector Double saturateVector (mn,mx) x = VUB.modify (\m -> go m (MUB.length m-1)) x where go :: MUB.MVector s Double -> Int -> ST s () go x 0 = return () go !x !i = do xi <- MUB.read x i when (xi < (mn VUB.! i)) $ MUB.write x i (mn VUB.! i) when (xi > (mn VUB.! i)) $ MUB.write x i (mx VUB.! i) --saturateVector (mn,mx) x = VUB.zipWith max mn $ VUB.zipWith min mx x --saturate (lb,ub) x = min (max x lb) ub -- | Create a Differential Evolution process de :: DEArgs -> DeMonad s (Double,Vector) de DEArgs{..} = do liftST (restore seed) >>= setM gen init <- withGen $ \g -> liftST (V.replicateM spop (uniformVector g dim >>= return.scale)) pop =: V.map (fitness &&& id) init work where (lb,ub) = (fst bounds, snd bounds) scale x = VUB.zipWith3 (\l u x -> l+x*(u-l)) lb ub x strat = strategy destrategy work = do logPoint "" e <- getM ec if e > budget then getM pop >>= return . V.minimumBy (compare`on`fst) else getM pop >>= deStep strat bounds fitness >>= setM pop >> work -- | Single iteration of Differential Evolution. Could be an useful building block -- for other algorithms as well. deStep :: DEStrategy s -> Bounds -> Fitness -> V.Vector (Double,Vector) -> DeMonad s (V.Vector (Double,Vector)) deStep strat bounds fitness pop = do modM ec (+V.length pop) withGen $ \g -> (V.mapM (candidate g) pop) where l = VUB.length . snd . V.head $ pop candidate gen orig@(ft,a) = do w <- strat gen l a pop return (select orig (postProcess w)) select (fa,a) b@(fitness -> fb) = if fa < fb then (fa,a) else (fb,b) postProcess x = saturateVector bounds x