{-# language FlexibleContexts #-}
{-# language GADTs #-}
{-# language GeneralizedNewtypeDeriving #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}
{-# language RecordWildCards #-}
module FastDownward
(
Problem
, Var
, newVar
, readVar
, writeVar
, modifyVar
, resetInitial
, Effect
, Test
, (?=)
, FastDownward.any
, solve
, SolveResult(..)
, Solution
, runProblem
, totallyOrderedPlan
, partiallyOrderedPlan
)
where
import Control.Applicative ( Alternative )
import Control.Monad ( MonadPlus, mzero )
import Control.Monad.IO.Class ( MonadIO, liftIO )
import Control.Monad.State.Class ( get, gets, modify )
import Control.Monad.Trans.Class ( lift )
import Control.Monad.Trans.State.Strict ( StateT(..), evalStateT, runStateT )
import qualified Data.Foldable
import qualified Data.Graph
import Data.IORef
import qualified Data.IntMap.Strict as IntMap
import Data.List ( inits, intersect )
import Data.Map.Strict ( Map )
import qualified Data.Map.Strict as Map
import Data.Maybe ( mapMaybe )
import Data.Sequence ( Seq )
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import Data.String ( fromString )
import qualified Data.Text.Lazy
import qualified Data.Text.Lazy.IO
import Data.Traversable ( for )
import qualified FastDownward.Exec as Exec
import qualified FastDownward.SAS
import qualified FastDownward.SAS.Axiom
import qualified FastDownward.SAS.Effect
import qualified FastDownward.SAS.Operator
import qualified FastDownward.SAS.Plan
import qualified FastDownward.SAS.Variable
import GHC.Exts ( Any )
import ListT ( ListT, fromFoldable, toList )
import Prelude hiding ( reads )
import System.Exit
import System.IO.Temp
import Unsafe.Coerce ( unsafeCoerce )
data Var a =
Var
{ variableIndex :: {-# UNPACK #-} !FastDownward.SAS.VariableIndex
, values :: {-# UNPACK #-} !( IORef ( Map a FastDownward.SAS.DomainIndex ) )
}
newtype Problem a =
Problem { unProblem :: StateT ProblemState IO a }
deriving
( Functor, Applicative, Monad, MonadIO )
data VariableDeclaration =
VariableDeclaration
{ initial :: {-# UNPACK #-} !FastDownward.SAS.DomainIndex
, _enumerateDomain :: IO [ FastDownward.SAS.DomainIndex ]
, _axiomLayer :: {-# UNPACK #-} !Int
}
data ProblemState =
ProblemState
{ initialState :: !( Map FastDownward.SAS.VariableIndex VariableDeclaration )
, axioms :: !( Seq FastDownward.SAS.Axiom )
}
observeValue :: ( Ord a, MonadIO m ) => Var a -> a -> m FastDownward.SAS.DomainIndex
observeValue var a = liftIO $ do
vs <-
readIORef ( values var )
case Map.lookup a vs of
Just i ->
return i
Nothing -> do
let
i =
FastDownward.SAS.DomainIndex ( Map.size vs )
i <$ modifyIORef' ( values var ) ( Map.insert a i )
newVar :: Ord a => a -> Problem ( Var a )
newVar =
newVarAt (-1)
newVarAt :: Ord a => Int -> a -> Problem ( Var a )
newVarAt axiomLayer initialValue = do
values <-
liftIO ( newIORef mempty )
variableIndex <-
freshIndex
let
enumerate =
Map.elems <$> readIORef values
var =
Var{..}
initialI <-
liftIO ( observeValue var initialValue )
Problem
( modify
( \ps ->
ps
{ initialState =
Map.insert
variableIndex
( VariableDeclaration initialI enumerate axiomLayer )
( initialState ps )
}
)
)
return var
freshIndex :: Problem FastDownward.SAS.VariableIndex
freshIndex =
FastDownward.SAS.VariableIndex <$> Problem ( gets ( Map.size . initialState ) )
writeVar :: Ord a => Var a -> a -> Effect ()
writeVar var a = Effect $ do
currentValues <-
liftIO ( readIORef ( values var ) )
domainIndex <-
maybe ( observeValue var a ) return ( Map.lookup a currentValues )
modify
( \es ->
es
{ writes =
Map.insert ( variableIndex var ) domainIndex ( writes es )
}
)
return ()
readVar :: Ord a => Var a -> Effect a
readVar var = Effect $ do
currentValues <-
liftIO ( readIORef ( values var ) )
mPrevRead <-
gets ( Map.lookup ( variableIndex var ) . reads )
case mPrevRead of
Just ( _, prevRead ) ->
return ( unsafeCoerce prevRead )
Nothing -> do
( value, domainIndex ) <-
lift ( fromFoldable ( Map.toList currentValues ) )
modify
( \es ->
es
{ reads =
Map.insert
( variableIndex var )
( domainIndex, unsafeCoerce value )
( reads es )
}
)
return value
modifyVar :: Ord a => Var a -> ( a -> a ) -> Effect ()
modifyVar v f =
readVar v >>= writeVar v . f
newtype Effect a =
Effect { runEffect :: StateT EffectState ( ListT IO ) a }
deriving
( Functor, Applicative, Alternative, MonadPlus )
instance Monad Effect where
return =
pure
Effect a >>= f =
Effect ( a >>= runEffect . f )
fail _ =
Effect ( lift mzero )
data EffectState =
EffectState
{ reads :: !( Map FastDownward.SAS.VariableIndex ( FastDownward.SAS.DomainIndex, Any ) )
, writes :: !( Map FastDownward.SAS.VariableIndex FastDownward.SAS.DomainIndex )
}
data SolveResult a
= Unsolvable
| Crashed String String ExitCode
| Solved ( Solution a )
data Solution a =
Solution
{ sas :: FastDownward.SAS.Plan
, operators :: IntMap.IntMap a
, stepIndices :: [ IntMap.Key ]
}
totallyOrderedPlan :: Solution a -> [ a ]
totallyOrderedPlan Solution{..} =
map ( operators IntMap.! ) stepIndices
solve
:: Show a
=> Exec.SearchEngine
-> [ Effect a ]
-> [ Test ]
-> Problem ( SolveResult a )
solve cfg ops tests = do
s0 <-
Problem get
Problem $ liftIO $ flip evalStateT s0 $ do
goal <-
unProblem ( Prelude.traverse testToVariableAssignment tests )
operators <-
liftIO ( fixEffects ops )
initialState <-
gets initialState
axioms <-
gets axioms
variables <-
for
( Map.toAscList initialState )
( \( FastDownward.SAS.VariableIndex i
, VariableDeclaration _ enumerate axiomLayer
) -> do
domain <-
liftIO enumerate
return
FastDownward.SAS.Variable
{ name =
fromString ( "var-" <> show i )
, domain =
map
( \( FastDownward.SAS.DomainIndex d ) ->
fromString
( "Atom var-" <> show i <> "(" <> show d <> ")" )
)
domain
++ [ "Atom dummy(dummy)" ]
, axiomLayer = axiomLayer
}
)
let
plan =
FastDownward.SAS.Plan
{ version =
FastDownward.SAS.SAS3
, useCosts =
FastDownward.SAS.NoCosts
, variables =
variables
, mutexGroups =
[]
, initialState =
FastDownward.SAS.State
( map ( initial . snd ) ( Map.toAscList initialState ) )
, goal =
FastDownward.SAS.Goal goal
, operators =
zipWith
( \i ( _, EffectState{ reads, writes } ) ->
FastDownward.SAS.Operator
{ name = fromString ( "op" <> show i )
, prevail =
map
( uncurry FastDownward.SAS.VariableAssignment )
( Map.toList
( fst <$> Map.difference reads writes )
)
, effects =
map
( \( v, post ) -> FastDownward.SAS.Effect v Nothing post )
( Map.toList ( Map.difference writes reads ) )
++
Map.elems
( Map.intersectionWithKey
( \v pre post ->
FastDownward.SAS.Effect v ( Just pre ) post
)
( fst <$> reads )
writes
)
}
)
[ 0 :: Int .. ]
operators
, axioms =
Data.Foldable.toList axioms
}
planFilePath <-
liftIO ( emptySystemTempFile "sas_plan" )
( exitCode, stdout, stderr ) <-
liftIO
( Exec.callFastDownward
Exec.Options
{ fastDownward = "downward"
, problem = Data.Text.Lazy.unpack ( FastDownward.SAS.Plan.toSAS plan )
, planFilePath = planFilePath
, search = cfg
}
)
case exitCode of
ExitFailure 11 ->
return Unsolvable
ExitFailure 12 ->
return Unsolvable
ExitFailure other ->
return ( Crashed stdout stderr ( ExitFailure other ) )
ExitSuccess -> liftIO $ do
planText <-
Data.Text.Lazy.IO.readFile planFilePath
let
stepIndices =
map
( read
. Data.Text.Lazy.unpack
. Data.Text.Lazy.init
. Data.Text.Lazy.drop 3
)
( takeWhile
( "(" `Data.Text.Lazy.isPrefixOf` )
( Data.Text.Lazy.lines planText )
)
return
( Solved
Solution
{ sas = plan
, operators = IntMap.fromList ( zip [0..] ( map fst operators ) )
, ..
}
)
fixEffects
:: Traversable t
=> t ( Effect a )
-> IO [ ( a, EffectState ) ]
fixEffects ops =
go mempty
where
go previousWrites = do
es <-
fmap
concat
( for ops
( \effect ->
toList
( runStateT
( runEffect effect )
( EffectState mempty mempty )
)
)
)
let
newWrites =
Map.unionsWith
Set.union
( map ( fmap Set.singleton . writes . snd ) es )
if previousWrites == newWrites
then return es
else go newWrites
runProblem :: MonadIO m => Problem a -> m a
runProblem p = liftIO $
evalStateT
( unProblem p )
ProblemState { initialState = mempty , axioms = mempty }
(?=) :: Ord a => Var a -> a -> Test
(?=) =
TestEq
data Test where
TestEq :: Ord a => {-# UNPACK #-} !( Var a ) -> !a -> Test
Any :: ![ Test ] -> Test
resetInitial :: Ord a => Var a -> a -> Problem ()
resetInitial var a = do
i <-
liftIO ( observeValue var a )
Problem $ modify $ \ps ->
ps
{ initialState =
Map.adjust
( \decl -> decl { initial = i } )
( variableIndex var )
( initialState ps )
}
any :: [ Test ] -> Test
any =
Any
testToVariableAssignment :: Test -> Problem FastDownward.SAS.VariableAssignment
testToVariableAssignment ( TestEq var a ) =
FastDownward.SAS.VariableAssignment ( variableIndex var )
<$> liftIO ( observeValue var a )
testToVariableAssignment ( Any tests ) = do
axiom <-
newVarAt 0 False
falseI <-
liftIO ( observeValue axiom False )
trueI <-
liftIO ( observeValue axiom True )
assigns <-
Prelude.traverse testToVariableAssignment tests
Problem $ modify $ \ps ->
ps
{ axioms =
Seq.fromList
[ FastDownward.SAS.Axiom
{ variable = variableIndex axiom
, conditions = [ va ]
, pre = falseI
, post = trueI
}
| va <- assigns
]
<> axioms ps
}
return ( FastDownward.SAS.VariableAssignment ( variableIndex axiom ) trueI )
partiallyOrderedPlan
:: Ord a
=> Solution a
-> ( Data.Graph.Graph
, Data.Graph.Vertex -> ( a, IntMap.Key, [ IntMap.Key ] )
, IntMap.Key -> Maybe Data.Graph.Vertex
)
partiallyOrderedPlan Solution{..} =
let
ops =
IntMap.fromList ( zip [0..] ( FastDownward.SAS.Plan.operators sas ) )
operation i =
ops IntMap.! i
g = do
( i, o ) : priorReversed <-
map
reverse
( tail ( inits ( map ( \i -> ( i, operation i ) ) stepIndices ) ) )
let
priors =
reverse priorReversed
return
( operators IntMap.! i
, i
, mapMaybe
( \( j, x ) -> if o `after` x then Just j else Nothing )
priors
)
( gr, fromVertex, toVertex ) =
Data.Graph.graphFromEdges g
in
( Data.Graph.transposeG gr, fromVertex, toVertex )
assignments :: FastDownward.SAS.Operator -> [ FastDownward.SAS.VariableAssignment ]
assignments o =
[ FastDownward.SAS.VariableAssignment
( FastDownward.SAS.Effect.variable e )
( FastDownward.SAS.Effect.post e )
| e <- FastDownward.SAS.Operator.effects o
]
requirements :: FastDownward.SAS.Operator -> [ FastDownward.SAS.VariableAssignment ]
requirements o =
FastDownward.SAS.Operator.prevail o ++ original o
original :: FastDownward.SAS.Operator -> [ FastDownward.SAS.VariableAssignment ]
original o =
mapMaybe
( \e ->
FastDownward.SAS.VariableAssignment ( FastDownward.SAS.Effect.variable e )
<$> FastDownward.SAS.Effect.pre e
)
( FastDownward.SAS.Operator.effects o )
after
:: FastDownward.SAS.Operator.Operator
-> FastDownward.SAS.Operator.Operator
-> Bool
o `after` x =
not ( null ( requirements o `intersect` assignments x ) ) ||
not ( null ( requirements x `intersect` original o ) )