{-# LANGUAGE RankNTypes, RecordWildCards,ScopedTypeVariables #-} {-# OPTIONS -Wall #-} {-| Usage: (1) create an optimization problem of type `Config` by one of `minimize`, `minimizeIO` etc. (2) `run` it. Let's optimize the following function /f(xs)/. @xs@ is a list of Double and @f@ has its minimum at @xs !! i = sqrt(i)@. >>> import Test.DocTest.Prop >>> let f = sum . zipWith (\i x -> (x*abs x - i)**2) [0..] :: [Double] -> Double >>> let initXs = replicate 10 0 :: [Double] >>> bestXs <- run $ minimize f initXs >>> assert $ f bestXs < 1e-10 If your optimization is not working well, try: * Set `scaling` in the `Config` to the appropriate search range of each parameter. * Set `tolFun` in the `Config` to the appropriate scale of the function values. An example for scaling the function value: >>> let f2 xs = (/1e100) $ sum $ zipWith (\i x -> (x*abs x - i)**2) [0..] xs >>> bestXs <- run $ (minimize f2 $ replicate 10 0) {tolFun = Just 1e-111} >>> assert $ f2 bestXs < 1e-110 An example for scaling the input values: >>> let f3 xs = sum $ zipWith (\i x -> (x*abs x - i)**2) [0,1e100..] xs >>> let xs30 = replicate 10 0 :: [Double] >>> let m3 = (minimize f3 xs30) {scaling = Just (repeat 1e50)} >>> xs31 <- run $ m3 >>> assert $ f3 xs31 / f3 xs30 < 1e-10 Use `minimizeT` to optimize functions on traversable structures. >>> import qualified Data.Vector as V >>> let f4 = V.sum . V.imap (\i x -> (x*abs x - fromIntegral i)**2) >>> :t f4 f4 :: V.Vector Double -> Double >>> bestVx <- run $ minimizeT f4 $ V.replicate 10 0 >>> assert $ f4 bestVx < 1e-10 Or use `minimizeG` to optimize functions of any type that is Data and that contains `Double`s. Here is an example that deal with Triangles. >>> :set -XDeriveDataTypeable >>> import Data.Data >>> data Pt = Pt Double Double deriving (Typeable,Data) >>> let dist (Pt ax ay) (Pt bx by) = ((ax-bx)**2 + (ay-by)**2)**0.5 >>> data Triangle = Triangle Pt Pt Pt deriving (Typeable,Data) Let us create a triangle ABC so that AB = 3, AC = 4, BC = 5. >>> let f5 (Triangle a b c) = (dist a b - 3.0)**2 + (dist a c - 4.0)**2 + (dist b c - 5.0)**2 >>> let triangle0 = Triangle o o o where o = Pt 0 0 >>> :t f5 f5 :: Triangle -> Double >>> bestTriangle <- run $ (minimizeG f5 triangle0){tolFun = Just 1e-20} >>> assert $ f5 bestTriangle < 1e-10 Then the angle BAC should be orthogonal. >>> let (Triangle (Pt ax ay) (Pt bx by) (Pt cx cy)) = bestTriangle >>> assert $ abs ((bx-ax)*(cx-ax) + (by-ay)*(cy-ay)) < 1e-10 When optimizing noisy functions, set `noiseHandling` = @True@ (and increase `noiseReEvals`) for better results. >>> import System.Random >>> let noise = randomRIO (0,1e-2) >>> let f6Pure = sum . zipWith (\i x -> (x*abs x - i)**2) [0..] >>> let f6 xs = fmap (f6Pure xs +) noise >>> :t f6 f6 :: [Double] -> IO Double >>> xs60 <- run $ (minimizeIO f6 $ replicate 10 0) {noiseHandling = False} >>> xs61 <- run $ (minimizeIO f6 $ replicate 10 0) {noiseHandling = True,noiseReEvals=Just 10} >>> assert $ f6Pure xs61 < f6Pure xs60 (note : with the default value of `noiseReEvals` the test above failed 335 times out of 1111 trials. When noiseReEvals == 10 it passed consecutive 1111 trials.) -} module Numeric.Optimization.Algorithms.CMAES ( run, Config(..), defaultConfig, minimize, minimizeIO, minimizeT, minimizeTIO, minimizeG, minimizeGIO, getDoubles, putDoubles )where import Control.Applicative ((<|>)) import Control.Monad hiding (forM_, mapM) import qualified Control.Monad.State as State import Data.Data import Data.Generics import Data.List (isPrefixOf) import Data.Maybe import Data.Foldable import Data.Traversable import System.IO import System.Process import Prelude hiding (concat, mapM, sum) import Paths_cmaes -- | Optimizer configuration. @tgt@ is the type of the value to be -- optimized. data Config tgt = Config { funcIO :: tgt -> IO Double -- ^ The Function to be optimized. , projection :: tgt -> [Double] -- ^ Extract the parameters to be tuned from @tgt@. , embedding :: [Double] -> tgt -- ^ Create a value of type @tgt@ from the parameters. , initXs :: [Double] -- ^ An initial guess of the parameters. , sigma0 :: Double -- ^ The global scaling factor. , scaling :: Maybe [Double] -- ^ Typical deviation of each input parameters. -- The length of the list is adjusted to be the same as -- initXs, e.g. you can lazily use an infinite list here. , typicalXs :: Maybe [Double] -- ^ Typical mean of each input parameters. -- The length of this list too, is adjusted to be the same as -- initXs. , noiseHandling :: Bool -- ^ Assume the function to be rugged and/or noisy , noiseReEvals :: Maybe Int -- ^ How many re-evaluation to make to estimate the noise. , noiseEps :: Maybe Double -- ^ Perturb the parameters by this amount (relative to sigma) -- to estimate the noise , tolFacUpX :: Maybe Double -- ^ Terminate when one of the scaling grew too big -- (initial scaling was too small.) , tolUpSigma :: Maybe Double -- ^ Terminate when the global scaling grew too big. , tolFun :: Maybe Double -- ^ Terminate when the function value diversity in the current -- and last few generations is smaller than this value , tolStagnation :: Maybe Int -- ^ Terminate when the improvement is not seen for this number -- of iterations. , tolX :: Maybe Double -- ^ Terminate when the deviations in the solutions are smaller -- than this value. , verbose :: Bool -- ^ Repeat the CMA-ES output into stderr. , otherArgs :: [(String, String)] -- ^ Interfaces for passing other configuration arguments directly to -- @cma.py@ } -- | The default @Config@ values. Also consult the original document -- for default values -- of the parameters not listed here. defaultConfig :: Config a defaultConfig = Config { funcIO = error "funcIO undefined" , projection = error "projection undefined" , embedding = error "embedding undefined" , initXs = error "initXs undefined" , sigma0 = 0.25 , scaling = Nothing , typicalXs = Nothing , noiseHandling = False , noiseReEvals = Nothing , noiseEps = Just 1e-7 , tolFacUpX = Just 1e10 , tolUpSigma = Just 1e20 , tolFun = Just 1e-11 , tolStagnation = Nothing , tolX = Just 1e-11 , verbose = False , otherArgs = [] } -- | Create a minimizing problem, given a pure function and an initial guess. minimize :: ([Double]-> Double) -> [Double] -> Config [Double] minimize f xs = minimizeIO (return . f) xs -- | Create a minimizing problem, given an @IO@ function and an initial guess. minimizeIO :: ([Double]-> IO Double) -> [Double] -> Config [Double] minimizeIO fIO xs = defaultConfig { funcIO = fIO , initXs = xs , projection = id , embedding = id } -- | Create a minimizing problem for a function on traversable structure @t@. minimizeT :: (Traversable t) => (t Double-> Double) -> t Double -> Config (t Double) minimizeT f tx = minimizeTIO (return . f) tx -- | Create a minimizing problem for an effectful function on a traversable structure @t@. minimizeTIO :: (Traversable t) => (t Double-> IO Double) -> t Double -> Config (t Double) minimizeTIO fIO tx = defaultConfig { funcIO = fIO , initXs = proj tx , projection = proj , embedding = embd } where proj = toList embd = zipTWith (\_ y -> y) tx -- | Create a minimizing problem for a function on almost any type @a@ which contain Doubles. minimizeG :: (Data a) => (a -> Double) -> a -> Config a minimizeG f tx = minimizeGIO (return . f) tx -- | Create a minimizing problem for an effectful function of almost any type. minimizeGIO :: (Data a) => (a -> IO Double) -> a -> Config a minimizeGIO fIO initA = defaultConfig { funcIO = fIO , initXs = getDoubles initA , projection = getDoubles , embedding = flip putDoubles initA } -- | Execute the optimizer and get the solution. run :: forall tgt. Config tgt -> IO tgt run Config{..} = do fn <- getDataFileName wrapperFn (Just hin, Just hout, _, _) <- createProcess (proc "python2" [fn]) { std_in = CreatePipe, std_out = CreatePipe } sendLine hin $ unwords (map show initXs) sendLine hin $ show sigma0 sendLine hin $ show $ length options forM_ options $ \(key, val) -> do sendLine hin key sendLine hin val let loop = do str <- recvLine hout let ws = words str case ws!!0 of "a" -> do return $ embedding $ map read $ drop 1 ws "q" -> do ans <- funcIO . embedding $ map read $ drop 1 ws sendLine hin $ show ans loop _ -> do fail "ohmy god" loop where probDim :: Int probDim = length initXs adjustDim :: [a] -> [a] -> [a] adjustDim supply orig = take probDim $ catMaybes $ zipWith (<|>) (map Just orig ++ repeat Nothing) (map Just supply) options :: [(String, String)] options = concat $ map maybeToList [ "scaling_of_variables" `is` (fmap$adjustDim [1..] ) scaling , "typical_x" `is` (fmap$adjustDim initXs) typicalXs , "noise_handling" `is` Just noiseHandling , "noise_reevals" `is` noiseReEvals , "noise_eps" `is` noiseEps , "tolfacupx" `is` tolFacUpX , "tolupsigma" `is` tolUpSigma , "tolfunhist" `is` tolFun , "tolstagnation" `is` tolStagnation , "tolx" `is` tolX ] ++ [otherArgs] is :: Show a => String -> Maybe a -> Maybe (String,String) is key = fmap (\val -> (key, show val)) wrapperFn, commHeader :: String wrapperFn = "cmaes_wrapper.py" commHeader = "" recvLine :: Handle -> IO String recvLine h = do str <- hGetLine h when (verbose) $ hPutStrLn stderr str if commHeader `isPrefixOf` str then return $ unwords $ drop 1 $ words str else do recvLine h sendLine :: Handle -> String -> IO () sendLine h str = do hPutStrLn h str hFlush h zipTWith :: (Traversable t1, Traversable t2) => (a->b->c) -> (t1 a) -> (t2 b) -> (t1 c) zipTWith op xs0 ys0 = State.evalState (mapM zipper xs0) (toList ys0) where zipper x = do (y:ys) <- State.get State.put ys return (op x y) {-| getDoubles and putDoubles are generic functions used to put [Double] in and out of generic data types. Let's test them. >>> let d3 = (1,2,3) :: (Double,Int,Double) >>> getDoubles d3 [1.0,3.0] >>> putDoubles [4,5] d3 (4.0,2,5.0) >>> let complicated = ([0,1],(2,[3,4])) :: ([Double],(Double,[Double])) >>> getDoubles complicated [0.0,1.0,2.0,3.0,4.0] >>> putDoubles [5,6,7,8,9] complicated ([5.0,6.0],(7.0,[8.0,9.0])) Putting back the obtained values should not change the data. >>> import Test.DocTest.Prop >>> type Complicated = ([[Double]],(),(([(Double,String)]),[Double])) >>> prop ((\x -> putDoubles (getDoubles x) x == x) :: Complicated -> Bool) You can get the original list back after putting it. >>> let make3 xs = take 3 $ xs ++ [0..] >>> prop ((\xs' y -> let xs = make3 xs' in getDoubles (putDoubles xs y)==xs) :: [Double] -> (Double,Double,Double) -> Bool) -} getDoubles :: Data a => a -> [Double] getDoubles d = reverse $ State.execState (everywhereM getter d) [] where getter :: GenericM (State.State [Double]) getter a = do ys <- State.get let da = fmap (flip asTypeOf (head ys)) $ cast a case da of Nothing -> return a Just dd -> do State.put $ dd:ys return a putDoubles :: Data a => [Double] -> a -> a putDoubles ys0 d = State.evalState (everywhereM putter d) ys0 where putter :: GenericM (State.State [Double]) putter a0 = do ys <- State.get let ma1 = (cast =<<) $ fmap (asTypeOf (head ys)) $ cast a0 case ma1 of Nothing -> return a0 Just a1 -> do State.put $ tail ys return a1