{-# LANGUAGE FlexibleInstances #-} import Control.Exception (assert) import Control.Monad.Identity (Identity(..), runIdentity) import Control.SpaceProbe import Control.SpaceProbe.Internal.Optimize import Data.List (sort) import Data.Maybe (isJust) import Test.Framework (Test, defaultMain) import Test.Framework.Providers.QuickCheck2 (testProperty) import Test.QuickCheck validateSearchTree :: SearchTree t -> Bool validateSearchTree (SearchTree node xs) = assert (n == 0 || mu <= m) $ assert (not $ isNaN mu) $ assert (not $ isNaN m) $ assert (minimum [n, fromIntegral k, fromIntegral k'] >= 0) $ assert (fromIntegral k' <= n) $ assert (fromIntegral k == length xs) $ assert (fromIntegral k' == length explored) $ assert (n == sum playouts + if isJust x && n /= 0 then 1 else 0) $ assert (null xs || m >= maximum maxes) $ if n == 0 then True else assert (all validateSearchTree xs) True where SearchNode x mu m n k k' = node nodes = map _node xs fullyExplored (SearchNode _ _ _ n_ k_ k'_) = k_ == k'_ && (n_ /= 0) explored = filter fullyExplored nodes maxes = map _maximum nodes playouts = map _playouts nodes playout :: (t -> Float) -> Float -> Float -> SearchTree t -> PlayoutResult t playout eval l u = runIdentity . playoutM (Identity . eval) l u maximizationTree :: (t -> Float) -> Probe t -> Int -> SearchTree t maximizationTree eval p k = go k inf (-inf) $ searchTree p where inf = 1 / 0 :: Float go 0 _ _ t = t go k' l u t | b || (u' == inf) = t' | otherwise = go (k' - 1) l' u' t' where PlayoutResult t' _ _ l' u' b = playout eval l u t data Eval a = Eval String (a -> Float) instance Show (Eval a) where show (Eval s _) = s floatEvals :: [Eval Float] floatEvals = [ Eval "square" $ \x -> x ** 2, Eval "poly" $ \x -> x ** 3 + 5 * x ** 2 + 0.01 * x ** 7, Eval "case" $ \x -> if x < 2 then x else x + 4, Eval "sin" sin, Eval "floor log" $ \x -> (x - fromIntegral (floor x :: Integer)) + 5 + log (2 + abs x), Eval "sub" $ \x -> x - 2, Eval "exp" exp, Eval "const" $ const 0, Eval "sqrt" $ \x -> abs x ** 0.5] intEvals :: [Eval Int] intEvals = [Eval s (f . fromIntegral) | Eval s f <- floatEvals] instance Arbitrary (Eval Float) where arbitrary = oneof $ map return floatEvals instance Arbitrary (Eval Int) where arbitrary = oneof $ map return intEvals validProbe :: Probe t -> (t -> Float) -> Int -> Bool validProbe p eval k = validateSearchTree $ maximizationTree eval p k data DefaultProbe t = Exponential t | Normal t t | Uniform t t deriving (Show) makeFloatProbe :: DefaultProbe Float -> Probe Float makeFloatProbe (Exponential mu) = exponential mu makeFloatProbe (Normal mu sigma) = normal mu sigma makeFloatProbe (Uniform a b) = uniform a b makeIntProbe :: DefaultProbe Int -> Probe Int makeIntProbe (Exponential mu) = exponentialInt (fromIntegral mu) makeIntProbe (Normal mu sigma) = normalInt (fromIntegral mu) $ fromIntegral sigma makeIntProbe (Uniform a b) = uniformInt a b arbitraryExponential :: (Arbitrary t, Num t, Ord t) => Gen (DefaultProbe t) arbitraryExponential = do (NonNegative mu) <- arbitrary return $ Exponential mu arbitraryNormal :: (Arbitrary t, Num t, Ord t) => Gen (DefaultProbe t) arbitraryNormal = do mu <- arbitrary (NonNegative sigma) <- arbitrary return $ Normal mu sigma arbitraryUniform :: (Arbitrary t, Num t, Ord t) => Gen (DefaultProbe t) arbitraryUniform = do a <- arbitrary (NonNegative x) <- arbitrary return $ Uniform a x instance (Arbitrary t, Num t, Ord t) => Arbitrary (DefaultProbe t) where arbitrary = oneof [arbitraryExponential, arbitraryNormal, arbitraryUniform] prop_ValidFloat :: DefaultProbe Float -> Eval Float -> NonNegative Int -> Bool prop_ValidFloat p (Eval _ eval) (NonNegative k) = validProbe (makeFloatProbe p) eval k prop_ValidInt :: DefaultProbe Int -> Eval Int -> NonNegative Int -> Bool prop_ValidInt p (Eval _ eval) (NonNegative k) = validProbe (makeIntProbe p) eval k prop_ExponentialIntBounds :: NonNegative Float -> Positive Int -> Bool prop_ExponentialIntBounds (NonNegative mu) (Positive k) = all (>=(0 :: Int)) . take k . map fst . maximize (const 0) $ exponentialInt mu prop_UniformBounds :: Float -> Positive Float -> Positive Int -> Bool prop_UniformBounds a (Positive x) (Positive k) = all (\y -> a <= y && y < b) . take k . map fst . maximize (const 0) $ uniform a b where b = a + x prop_ExponentialBounds :: NonNegative Float -> Positive Int -> Bool prop_ExponentialBounds (NonNegative mu) (Positive k) = all (>=0) . take k . map fst . maximize (const 0) $ exponential mu strictMonotone :: Ord a => [a] -> Bool strictMonotone [] = True strictMonotone xs = all id $ zipWith (>) xs (tail xs) prop_LowestYet :: [(Int, Float)] -> Bool prop_LowestYet = strictMonotone . map snd . lowestYet prop_HighestYet :: [(Int, Float)] -> Bool prop_HighestYet = strictMonotone . reverse . map snd . highestYet tests :: [Test] tests = [testProperty "prop_ValidFloat" prop_ValidFloat, testProperty "prop_ValidInt" prop_ValidInt, testProperty "prop_UniformBounds" prop_UniformBounds, testProperty "prop_ExponentialBounds" prop_ExponentialBounds, testProperty "prop_LowestYet" prop_LowestYet, testProperty "prop_HighestYet" prop_HighestYet, testProperty "prop_ExponentialIntBounds" prop_ExponentialIntBounds] main :: IO () main = defaultMain tests