Skip to content
Snippets Groups Projects
Commit 6e284015 authored by chrg's avatar chrg
Browse files

Add addaptive algorithm

parent ea34006f
No related branches found
No related tags found
No related merge requests found
Showing with 274 additions and 165 deletions
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-} {-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
module Control.Monad.IRTree ( module Control.Monad.IRTree (
-- * IRTree -- * IRTree
...@@ -11,16 +14,18 @@ module Control.Monad.IRTree ( ...@@ -11,16 +14,18 @@ module Control.Monad.IRTree (
extract, extract,
probe, probe,
reduce, reduce,
reduceExp, reduceAdaptive,
reduceFib,
-- * IRTreeT -- * IRTreeT
IRTreeT, IRTreeT,
probeT, probeT,
extractT, extractT,
reduceT, reduceT,
reduceFibT, reduceAdaptiveT,
reduceExpT,
-- * helpers
binsearch,
expsearch,
-- * Re-exports -- * Re-exports
module Control.Monad.Reader, module Control.Monad.Reader,
...@@ -32,6 +37,7 @@ import Control.Monad.Reader ...@@ -32,6 +37,7 @@ import Control.Monad.Reader
import Control.Monad.Reduce import Control.Monad.Reduce
import Data.Bits import Data.Bits
import Data.Foldable import Data.Foldable
import Data.Function
import Data.Functor import Data.Functor
import Data.Functor.Identity import Data.Functor.Identity
import Data.RPath import Data.RPath
...@@ -56,123 +62,104 @@ extractT :: (Functor m) => IRTreeT l m i -> m i ...@@ -56,123 +62,104 @@ extractT :: (Functor m) => IRTreeT l m i -> m i
extractT (IRTreeT m) = fmap (\(i, _, _) -> i) (runRWST m "" 0) extractT (IRTreeT m) = fmap (\(i, _, _) -> i) (runRWST m "" 0)
{-# INLINE extractT #-} {-# INLINE extractT #-}
probe :: IRTree l i -> RPath -> (i, [(Bool, l)]) probe :: IRTree l i -> RPath -> (i, [AnnotatedChoice l], Int)
probe t pth = runIdentity $ probeT t pth probe t pth = runIdentity $ probeT t pth
{-# INLINE probe #-} {-# INLINE probe #-}
probeT :: (Functor m) => IRTreeT l m i -> RPath -> m (i, [(Bool, l)]) {- | Probe the IRTree at a path RPath, and return the input, a list of annotated choices,
and the number of guesses made (can be negative to indicate that not all choices in
the path was used.
-}
probeT :: (Functor m) => IRTreeT l m i -> RPath -> m (i, [AnnotatedChoice l], Int)
probeT (IRTreeT m) pth = probeT (IRTreeT m) pth =
runRWST m pth 0 <&> \(i, _, l) -> runRWST m pth 0 <&> \(i, x, l) ->
(i, zip (toChoiceList pth <> repeat False) (appEndo l [])) (i, annotateChoices pth (appEndo l []), x - numberOfChoices pth)
{-# INLINE probeT #-} {-# INLINE probeT #-}
reduce reduce
:: (Monad m) :: (Monad m, m ~ IO, Show l)
=> ([(Bool, l)] -> i -> m Bool) => ([AnnotatedChoice l] -> i -> m Bool)
-> IRTree l i -> IRTree l i
-> m (Maybe i) -> m i
reduce = reduceT (pure . runIdentity) reduce = reduceT (pure . runIdentity)
{-# INLINE reduce #-} {-# INLINE reduce #-}
-- | Interpreted reduction with an m base monad -- | Interpreted reduction with an m base monad
reduceT reduceT
:: (Monad m, Functor t) :: (Monad m, Functor t, m ~ IO, Show l)
=> (forall a. t a -> m a) => (forall a. t a -> m a)
-> ([(Bool, l)] -> i -> m Bool) -> ([AnnotatedChoice l] -> i -> m Bool)
-> IRTreeT l t i -> IRTreeT l t i
-> m (Maybe i) -> m i
reduceT lift_ p rt = do reduceT lift_ p rt = do
(i, l) <- lift_ (probeT rt "") Seq.empty & fix \rec sq -> do
t <- p l i
if t
then Just <$> go Seq.empty
else pure Nothing
where
go pth = do
-- Try to run the true branch. -- Try to run the true branch.
let pth' = fromChoiceList (toList (pth Seq.|> True)) (i, l, left) <- _probe (sq Seq.|> True)
(i, l) <- lift_ $ probeT rt pth' p l i >>= \case
if length l >= numberOfChoices pth' -- If predicate is true, and there is choices left
then do True | left > 0 -> rec (sq Seq.|> True)
t <- p l i -- If predicate is false (and stable)
go (pth Seq.|> t) False | left >= 0 -> rec (sq Seq.|> False)
else pure i _ow -> pure i
where
_probe sq = lift_ . probeT rt . fromChoiceList $ toList sq
{-# INLINE reduceT #-} {-# INLINE reduceT #-}
reduceExp reduceAdaptive
:: (Monad m) :: (Monad m, m ~ IO, Show i, Show l)
=> ([(Bool, l)] -> i -> m Bool) => ([AnnotatedChoice l] -> i -> m Bool)
-> IRTree l i
-> m (Maybe i)
reduceExp = reduceExpT (pure . runIdentity)
{-# INLINE reduceExp #-}
reduceFib
:: (Monad m)
=> ([(Bool, l)] -> i -> m Bool)
-> IRTree l i -> IRTree l i
-> m (Maybe i) -> m i
reduceFib = reduceFibT (pure . runIdentity) reduceAdaptive = reduceAdaptiveT (pure . runIdentity)
{-# INLINE reduceFib #-} {-# INLINE reduceAdaptive #-}
-- | Interpreted reduction with an m base monad, and running in expoential mode. -- | Interpreted reduction with an m base monad, but using exponential search.
reduceExpT reduceAdaptiveT
:: (Monad m, Functor t) :: (Monad m, Functor t, IO ~ m, Show i, Show l)
=> (forall a. t a -> m a) => (forall a. t a -> m a)
-- ^ a lift of monad m into t (normally @id@ or @lift@) -- ^ a lift of monad m into t (normally @id@ or @lift@)
-> ([(Bool, l)] -> i -> m Bool) -> ([AnnotatedChoice l] -> i -> m Bool)
-> IRTreeT l t i -> IRTreeT l t i
-> m (Maybe i) -> m i
reduceExpT lift_ p rt = do reduceAdaptiveT lift_ p rt = do
(i, l) <- lift_ (probeT rt "") Seq.empty & fix \rec !sq -> do
t <- p l i (d', c) <- expsearch \d -> do
if t let sq' = sq <> Seq.replicate d True
then Just <$> go 0 Seq.empty (i, l, left) <- _probe sq'
else pure Nothing t <- p l i
pure (t, left)
if c
then rec (sq <> Seq.replicate d' True <> Seq.singleton False)
else do
(i, _, _) <- _probe (sq <> Seq.replicate d' True)
pure i
where where
go n sq = do _probe sq = lift_ . probeT rt . fromChoiceList $ toList sq
let depth = shiftL 1 n
let sq' = sq <> Seq.replicate depth True expsearch :: (Monad m) => (Int -> m (Bool, Int)) -> m (Int, Bool)
let pth' = fromChoiceList (toList sq') expsearch p =
(i, l) <- lift_ (probeT rt pth') 1 & fix \rec !d -> do
if length l >= numberOfChoices pth' - depth + 1 p d >>= \case
then do (True, left)
t <- p l i | left < 0 -> pure (d + left, False)
if t | otherwise -> rec (d `shiftL` 1)
then go (n + 1) sq' (False, left) -> do
else case n of d' <-
0 -> go 0 (sq Seq.|> False) binsearch
n' -> go (n' - 1) sq (fmap (fmap fst) p)
else pure i (d `shiftR` 1)
{-# INLINE reduceExpT #-} (min d (d + left))
pure (d', d' < d + left - 1)
-- | Interpreted reduction with an m base monad, and running in fibonacci mode.
reduceFibT binsearch :: (Monad m) => (Int -> m Bool) -> Int -> Int -> m Int
:: (Monad m, Functor t) binsearch p = go
=> (forall a. t a -> m a)
-- ^ a lift of monad m into t (normally @id@ or @lift@)
-> ([(Bool, l)] -> i -> m Bool)
-> IRTreeT l t i
-> m (Maybe i)
reduceFibT lift_ p rt = do
(i, l) <- lift_ (probeT rt "")
t <- p l i
if t
then Just <$> go 1 1 Seq.empty
else pure Nothing
where where
go n m sq = do go !m !h
let depth = m | m >= pivot = do
let sq' = sq <> Seq.replicate depth True pure m
let pth' = fromChoiceList (toList sq') | otherwise = do
(i, l) <- lift_ (probeT rt pth') p pivot >>= \case
if length l >= numberOfChoices pth' - depth + 1 True -> go pivot h
then do False -> go m pivot
t <- p l i where
if t pivot = (m + h) `shiftR` 1
then go m (n + m) sq'
else case m of
1 -> go 1 1 (sq Seq.|> False)
m' -> go (m' - n) n sq
else pure i
{-# INLINE reduceFibT #-}
...@@ -33,12 +33,9 @@ module Control.Monad.RTree ( ...@@ -33,12 +33,9 @@ module Control.Monad.RTree (
module Data.RPath, module Data.RPath,
) where ) where
import Control.Applicative
import Control.Monad import Control.Monad
import Control.Monad.Identity import Control.Monad.Identity
import Control.Monad.Reduce import Control.Monad.Reduce
import Control.Monad.Trans
import Control.Monad.Trans.Maybe (MaybeT (runMaybeT))
import Data.Foldable import Data.Foldable
import Data.Foldable.WithIndex import Data.Foldable.WithIndex
import Data.Function ((&)) import Data.Function ((&))
...@@ -123,15 +120,15 @@ reduce ...@@ -123,15 +120,15 @@ reduce
:: (Monad m) :: (Monad m)
=> (i -> m Bool) => (i -> m Bool)
-> RTree l i -> RTree l i
-> m (Maybe i) -> m i
reduce p = runMaybeT . checkgo reduce p = go
where where
checkgo r = do
t <- lift $ p (extract r)
guard t *> go r
go = \case go = \case
Done i -> pure i Done i -> pure i
Split _ lhs rhs -> checkgo lhs <|> go rhs Split _ lhs rhs -> do
p (extract lhs) >>= \case
True -> go lhs
False -> go rhs
{-# INLINE reduce #-} {-# INLINE reduce #-}
-- | An RTreeT Node -- | An RTreeT Node
...@@ -180,14 +177,11 @@ reduceT ...@@ -180,14 +177,11 @@ reduceT
-> (i -> n Bool) -> (i -> n Bool)
-> RTreeT l m i -> RTreeT l m i
-> n i -> n i
reduceT lift_ p = checkgo reduceT lift_ p = fix \rec x ->
where lift_ (unRTreeT x) >>= \case
checkgo (RTreeT r) = do
r' <- lift_ r
t <- p =<< lift_ (extractN r')
unless t mzero
go r'
go = \case
DoneN i -> pure i DoneN i -> pure i
SplitN _ lhs rhs -> checkgo lhs <|> (lift_ (unRTreeT rhs) >>= go) SplitN _ lhs rhs -> do
lift_ (extractT lhs) >>= p >>= \case
True -> rec lhs
False -> rec rhs
{-# INLINE reduceT #-} {-# INLINE reduceT #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
module Data.RPath ( module Data.RPath (
-- * RPath -- * RPath
RPath, RPath,
...@@ -6,6 +9,13 @@ module Data.RPath ( ...@@ -6,6 +9,13 @@ module Data.RPath (
numberOfChoices, numberOfChoices,
indexChoice, indexChoice,
-- * Annotated
annotateChoices,
AnnotatedChoice (..),
Choice (..),
toDecision,
numberOfUndecided,
-- * As a predicate -- * As a predicate
toPredicate, toPredicate,
toPredicateDebug, toPredicateDebug,
...@@ -16,6 +26,8 @@ module Data.RPath ( ...@@ -16,6 +26,8 @@ module Data.RPath (
) where ) where
import Data.Bool import Data.Bool
import Data.Foldable
import Data.Function
import Data.IORef (atomicModifyIORef, newIORef) import Data.IORef (atomicModifyIORef, newIORef)
import Data.Maybe import Data.Maybe
import Data.String import Data.String
...@@ -32,16 +44,16 @@ the rpath points to. ...@@ -32,16 +44,16 @@ the rpath points to.
-} -}
toPredicate :: RPath -> IO (i -> IO Bool) toPredicate :: RPath -> IO (i -> IO Bool)
toPredicate rp = do toPredicate rp = do
idx <- newIORef (-1) idx <- newIORef 0
pure . const $ do pure . const $ do
idx' <- atomicModifyIORef idx (\n -> (n + 1, n)) idx' <- atomicModifyIORef idx (\n -> (n + 1, n))
pure $ indexChoice rp idx' pure $ indexChoice rp idx'
-- | Like @toPredicate@, but with debugging information -- | Like @toPredicate@, but with debugging information
toPredicateDebug :: (Show i) => RPath -> IO (i -> IO Bool) toPredicateDebug :: (Show i, Show l) => RPath -> IO ([AnnotatedChoice l] -> i -> IO Bool)
toPredicateDebug rp = do toPredicateDebug rp = do
idx <- newIORef (-1) idx <- newIORef 0
pure $ \i -> do pure $ \_ i -> do
idx' <- atomicModifyIORef idx (\n -> (n + 1, n)) idx' <- atomicModifyIORef idx (\n -> (n + 1, n))
putStr (debugShowWithDepth rp idx') putStr (debugShowWithDepth rp idx')
putStr ": " putStr ": "
...@@ -52,9 +64,7 @@ toPredicateDebug rp = do ...@@ -52,9 +64,7 @@ toPredicateDebug rp = do
default to false. Indexing with a negative number gives True. default to false. Indexing with a negative number gives True.
-} -}
indexChoice :: RPath -> Int -> Bool indexChoice :: RPath -> Int -> Bool
indexChoice (RPath v) idx indexChoice (RPath v) idx = fromMaybe False (v VU.!? idx)
| idx < 0 = True
| otherwise = fromMaybe False (v VU.!? idx)
{-# INLINE indexChoice #-} {-# INLINE indexChoice #-}
-- | Get the number of choices in the RPath. -- | Get the number of choices in the RPath.
...@@ -77,6 +87,18 @@ debugShowWithDepth rp i = ...@@ -77,6 +87,18 @@ debugShowWithDepth rp i =
(map (bool '0' '1') . take (i + 1) . toChoiceList $ rp) (map (bool '0' '1') . take (i + 1) . toChoiceList $ rp)
<> replicate (numberOfChoices rp - i - 1) '*' <> replicate (numberOfChoices rp - i - 1) '*'
-- | Get a list of annotated choices.
annotateChoices :: RPath -> [l] -> [AnnotatedChoice l]
annotateChoices rp =
zipWith
AnnotatedChoice
(map (bool No Yes) (toChoiceList rp) <> repeat Undecided)
numberOfUndecided :: [AnnotatedChoice l] -> Int
numberOfUndecided =
0 & foldl' \a ->
(\case Undecided -> 1 + a; _ow -> a) . choice
debugShow :: RPath -> String debugShow :: RPath -> String
debugShow = map (bool '0' '1') . toChoiceList debugShow = map (bool '0' '1') . toChoiceList
...@@ -88,3 +110,20 @@ instance Read RPath where ...@@ -88,3 +110,20 @@ instance Read RPath where
instance IsString RPath where instance IsString RPath where
fromString = fromChoiceList . map (== '1') fromString = fromChoiceList . map (== '1')
-- | A Choice
data Choice = Yes | No | Undecided
deriving (Show, Read, Eq, Ord, Enum, Bounded)
-- | An annotated choice
data AnnotatedChoice l = AnnotatedChoice
{ choice :: !Choice
, label :: l
}
deriving (Show, Eq, Ord)
toDecision :: Choice -> Maybe Bool
toDecision = \case
Yes -> Just True
No -> Just False
Undecided -> Nothing
1***: y := 2; 1 + y True
11***: 1 + 2 True
111: 1 False
1101: 2 False
11001: 3 False
11000: 1 + 2 True
****: x := 1; y := 2; x + y True
1***: y := 2; 1 + y True
11***: 1 + 2 True
111: 1 False
1***: x := 2; x + x True
11***: 2 + 2 True
111: 2 False
1101: 2 False
11001: 4 False
11000: 2 + 2 True
****: x := 1; x := 2; x + x True
1***: x := 2; x + x True
11***: 2 + 2 True
111: 2 False
1***: 2 + 1 True
11: 2 False
101: 1 False
1001: 3 False
1000: 2 + 1 True
***: x := 1; 2 + x True
1***: 2 + 1 True
11: 2 False
1: 1 False
01: 2 False
001: 3 False
000: 1 + 2 True
***: 1 + 2 True
1: 1 False
{-# LANGUAGE BlockArguments #-} {-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Control.Monad.IRTreeSpec where module Control.Monad.IRTreeSpec where
import Control.Monad.IRTree import Control.Monad.IRTree
import qualified Control.Monad.IRTree as IRTree import qualified Control.Monad.IRTree as IRTree
import qualified Control.Monad.RTree as RTree import qualified Control.Monad.RTree as RTree
import Control.Monad.Writer.Strict
import Data.Bool import Data.Bool
import Data.Expr as Expr import Data.Expr as Expr
import Data.List.NonEmpty (nonEmpty) import Data.List.NonEmpty (nonEmpty)
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import System.IO
import Test.Hspec import Test.Hspec
import Test.Hspec.Glitter (onGlitter)
spec :: Spec spec :: Spec
spec = describe "examples" do spec = do
binsearchSpec
expsearchSpec
examplesSpec
binsearchSpec :: Spec
binsearchSpec = describe "binsearch" do
it "should guess a number between 0 and 10" do
binsearch (\a -> do pure (a <= 6)) 0 10 `shouldReturn` 6
binsearch (\a -> do pure (a <= 3)) 0 10 `shouldReturn` 3
expsearchSpec :: Spec
expsearchSpec = describe "expsearch" do
it "should guess a number between 0 and 10" do
expsearch (\a -> do pure (a <= 6, 10 - a)) `shouldReturn` (6, True)
expsearch (\a -> do pure (a <= 3, 10 - a)) `shouldReturn` (3, True)
expsearch (\a -> do pure (a <= 9, 10 - a)) `shouldReturn` (9, False)
expsearch (\a -> do pure (a <= 12, 10 - a)) `shouldReturn` (9, False)
examplesSpec :: Spec
examplesSpec = describe "examples" do
handle "small-opr-expr" $ handle "small-opr-expr" $
Opr (Cnt 1) (Cnt 2) Opr (Cnt 1) (Cnt 2)
...@@ -35,53 +58,85 @@ spec = describe "examples" do ...@@ -35,53 +58,85 @@ spec = describe "examples" do
let re = runReaderT (Expr.rExpr e) Map.empty let re = runReaderT (Expr.rExpr e) Map.empty
it "should reduce like iinputs" do
forM_ (RTree.iinputs re) \(ii, e') -> do
p <- toPredicate ii
IRTree.reduce (const p) me `shouldReturn` e'
let let
predicate :: Expr -> IO Bool predicate :: Expr -> IO Bool
predicate = pure . contains isOpr predicate = pure . contains isOpr
rex <- runIO $ RTree.reduce predicate re rex <- runIO $ RTree.reduce predicate re
-- onGlitterWith onGlitter
-- ("test/expected/" <> str <> "-red") ("test/expected/" <> str <> "-ired")
-- ( \fp () -> do ( \f -> do
-- (mex, result) <- runWriterT (IRTree.reduce (debugPredicate showString (prettyExprS 0) predicate) me) withFile f WriteMode \h -> do
-- writeFile fp (appEndo result "") let p = debugPredicate' h predicate
-- pure mex IRTree.reduce p me `shouldReturn` rex
-- ) )
-- do do
-- it "should produce the same results as the RTree" \mex -> do it "should find an opr normally" \_ -> do
-- rex `shouldBe` mex IRTree.reduce (const predicate) me `shouldReturn` rex
it "should find an opr exponentially" do onGlitter
(mex, _) <- runWriterT (IRTree.reduceExp (debugPredicate showString (prettyExprS 0) predicate) me) ("test/expected/" <> str <> "-ireda")
rex `shouldBe` mex ( \f -> do
withFile f WriteMode \h -> do
it "should find an opr fibonacci" do let p = debugPredicate' h predicate
(mex, _) <- runWriterT (IRTree.reduceFib (debugPredicate showString (prettyExprS 0) predicate) me) let (i, l, _) = IRTree.probe me ""
rex `shouldBe` mex _ <- p l i
IRTree.reduceAdaptive p me `shouldReturn` rex
it "should reduce like iinputs" do )
forM_ (RTree.iinputs re) \(ii, e') -> do do
p <- toPredicate ii it "should find an opr addabtively" \_ -> do
IRTree.reduce (const p) me `shouldReturn` Just e' IRTree.reduceAdaptive (const predicate) me `shouldReturn` rex
debugPredicate'
:: (Show l)
=> Handle
-> (Expr -> IO Bool)
-> [AnnotatedChoice l]
-> Expr
-> IO Bool
debugPredicate' h predicate lst i = do
x <- predicate i
hPutStr
h
( map (debugChoice . choice) lst
<> ": "
<> prettyExprS 0 i ""
<> " "
<> show x
<> "\n"
)
pure x
where
debugChoice = \case
Yes -> '1'
No -> '0'
Undecided -> '*'
debugPredicate debugPredicate
:: (Monad m) :: Handle
=> (l -> ShowS) -> (l -> ShowS)
-> (i -> ShowS) -> (i -> ShowS)
-> (i -> m Bool) -> (i -> IO Bool)
-> [(Bool, l)] -> [AnnotatedChoice l]
-> i -> i
-> WriterT (Endo String) m Bool -> IO Bool
debugPredicate ppl ppi predicate lst i = do debugPredicate h ppl ppi predicate lst i = do
x <- lift (predicate i) x <- predicate i
tell . Endo $ hPutStr
showString (bool "0" "1" x) h
. showString ": " ( showString (bool "0" "1" x)
. ppi i . showString ": "
. showString "\n" . ppi i
. case nonEmpty lst of . showString "\n"
Nothing -> showString "initial\n" . case nonEmpty lst of
Just lst' -> ppl (snd $ NE.last lst') . showString "\n" Nothing -> showString "initial\n"
Just lst' -> ppl (label $ NE.last lst') . showString "\n"
$ ""
)
pure x pure x
...@@ -115,4 +115,4 @@ examplesSpec = describe "example" do ...@@ -115,4 +115,4 @@ examplesSpec = describe "example" do
it "should reduce like iinputs" do it "should reduce like iinputs" do
forM_ (iinputs re) \(ii, e') -> do forM_ (iinputs re) \(ii, e') -> do
p <- toPredicate ii p <- toPredicate ii
reduce p re `shouldReturn` Just e' reduce p re `shouldReturn` e'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment