packages feed

huff-0.1.0.0: src/Huff/ConnGraph.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecursiveDo #-}

module Huff.ConnGraph (
    -- * Connection Graph
    ConnGraph()
  , resetConnGraph
  , buildConnGraph
  , IsNode(..)

    -- ** Facts
  , Fact()
  , markTrue, isTrue
  , markGoal, isGoal
  , requiresFact
  , addsFact
  , delsFact

    -- ** Effects
  , Effect()
  , isInPlan, markInPlan
  , activatePrecondition
  , effectAdds
  , effectDels
  , effectPre
  , effectOp

  , Level

  , State, applyEffect
  , Goals
  , Facts
  , Effects

    -- * Debugging
  , printConnGraph
  , printEffects, printEffect
  , printFacts, printFact
  ) where

import qualified Huff.Input as I

import           Control.Monad (zipWithM,foldM,unless)
import           Data.Function (on)
import           Data.Hashable (Hashable(..))
import           Data.IORef
                     (IORef,newIORef,readIORef,writeIORef,atomicModifyIORef')
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as T


-- Connection Graph ------------------------------------------------------------

type Facts   a = Set.Set (Fact a)
type Goals   a = Set.Set (Fact a)
type State   a = Set.Set (Fact a)
type Effects a = Set.Set (Effect a)

type Level = Int

data ConnGraph a = ConnGraph { cgFacts        :: !(Facts a)
                             , cgEffects      :: !(Effects a)
                             , cgDirtyFacts   :: !(IORef [Fact a])
                             , cgDirtyEffects :: !(IORef [Effect a])
                             }


data Fact a = Fact { fId    :: !Int
                   , fProp  :: !I.Fact
                   , fLevel :: !(IORef (Maybe Level))

                   , fIsTrue:: !(IORef (Maybe Level))
                   , fIsGoal:: !(IORef Bool)

                   , fDirty     :: !(IORef Bool)
                   , fDirtyList :: !(IORef [Fact a])

                   , fPreCond :: Effects a
                     -- ^ Effects that require this fact
                   , fAdd   :: Effects a
                     -- ^ Effects that add this fact
                   , fDel   :: Effects a
                     -- ^ Effects that delete this fact
                   }

instance Show (Fact a) where
  showsPrec p Fact { .. } = showParen (p >= 10)
    $ showString "Fact "
    . shows fId
    . showString " "
    . showParen True (shows fProp)

instance Hashable (Fact a) where
  hashWithSalt s Fact { .. } = hashWithSalt s fId

markTrue :: Fact a -> Level -> IO ()
markTrue f l =
  do dirty f
     writeIORef (fIsTrue f) (Just l)

isTrue :: Fact a -> IO (Maybe Level)
isTrue Fact { .. } = readIORef fIsTrue


markGoal :: Fact a -> IO ()
markGoal f =
  do dirty f
     writeIORef (fIsGoal f) True

isGoal :: Fact a -> IO Bool
isGoal Fact { .. } = readIORef fIsGoal


instance Eq (Fact a) where
  (==) = (==) `on` fId
  (/=) = (/=) `on` fId
  {-# INLINE (==) #-}
  {-# INLINE (/=) #-}

instance Ord (Fact a) where
  compare = compare `on` fId
  {-# INLINE compare #-}

data Effect a = Effect { eId        :: !Int
                       , ePre       :: Facts a
                       , eNumPre    :: !Int
                       , eAdds      :: Facts a
                       , eDels      :: Facts a
                       , eOp        :: !(I.Operator a)
                         -- ^ The operator that this effect came from

                       , eDirty     :: !(IORef Bool)
                       , eDirtyList :: !(IORef [Effect a])

                       , eInPlan    :: !(IORef Bool)
                         -- ^ Whether or not this effect is a member of the
                         -- current relaxed plan

                       , eIsInH     :: !(IORef Bool)
                         -- ^ If this action is part of the helpful action set

                       , eLevel     :: !(IORef (Maybe Level))
                         -- ^ Membership level for this effect
                       , eActivePre :: !(IORef Level)
                         -- ^ Active preconditions for this effect
                       }

instance Show (Effect a) where
  showsPrec p Effect { .. } = showParen (p >= 10)
    $ showString "Effect: "
    . showParen True (shows eId)


markInPlan :: Effect a -> IO ()
markInPlan e =
  do dirty e
     writeIORef (eInPlan e) True

isInPlan :: Effect a -> IO Bool
isInPlan Effect { .. } = readIORef eInPlan

effectOp :: Effect a -> I.Operator a
effectOp Effect { .. } = eOp


instance Eq (Effect a) where
  (==) = (==) `on` eId
  (/=) = (/=) `on` eId
  {-# INLINE (==) #-}
  {-# INLINE (/=) #-}

instance Ord (Effect a) where
  compare = compare `on` eId
  {-# INLINE compare #-}


-- Node Operations -------------------------------------------------------------

class IsNode node where
  dirty    :: node a -> IO ()
  activate :: node a -> Level -> IO ()
  getLevel :: node a -> IO (Maybe Level)

instance IsNode Fact where
  dirty f =
    do isDirty <- readIORef (fDirty f)
       unless isDirty (atomicModifyIORef' (fDirtyList f) (\fs -> (f:fs, ())))

  activate f l =
    do dirty f
       writeIORef (fLevel f) (Just l)

  getLevel Fact { .. } = readIORef fLevel

instance IsNode Effect where
  dirty e =
    do isDirty <- readIORef (eDirty e)
       unless isDirty (atomicModifyIORef' (eDirtyList e) (\xs -> (e:xs, ())))

  activate e l =
    do dirty e
       writeIORef (eLevel e) (Just l)

  getLevel Effect { .. } = readIORef eLevel


-- | The effects that have this fact as a precondition.
requiresFact :: Fact a -> Effects a
requiresFact Fact { .. } = fPreCond

-- | The effects that add this fact to the state.
addsFact :: Fact a -> Effects a
addsFact Fact { .. } = fAdd

-- | The effects that delete this fact from the state.
delsFact :: Fact a -> Effects a
delsFact Fact { .. } = fDel

-- | Increment the number of activated preconditions, and return a boolean that
-- indicates whether or not the effect has been activated.
activatePrecondition :: Effect a -> IO Bool
activatePrecondition eff =
  do dirty eff
     atomicModifyIORef' (eActivePre eff) $ \ n ->
       let n' = n + 1
        in (n', n' >= eNumPre eff)

effectAdds :: Effect a -> Facts a
effectAdds Effect { .. } = eAdds
{-# INLINE effectAdds #-}

effectDels :: Effect a -> Facts a
effectDels Effect { .. } = eDels
{-# INLINE effectDels #-}

effectPre :: Effect a -> Facts a
effectPre Effect { .. } = ePre
{-# INLINE effectPre #-}


-- Utility Functions -----------------------------------------------------------

-- | Apply an effect to the state given, returning a new state.
applyEffect :: Effect a -> State a -> State a
applyEffect Effect { .. } s = (s Set.\\ eDels) `Set.union` eAdds


-- Input Processing ------------------------------------------------------------

-- | Translate a domain and problem into a description of the initial state, the
-- goal state, and the connection graph.  The translation process includes
-- adding a special empty fact that all effects with no preconditions will have
-- as a precondition.  The empty fact is also added to the initial state, in the
-- event that the problem has an empty initial state.
buildConnGraph :: I.Domain a -> I.Problem -> IO (State a,Goals a,ConnGraph a)
buildConnGraph dom prob =
  do cgDirtyFacts   <- newIORef []
     cgDirtyEffects <- newIORef []

     rec (pres,adds,dels,effs) <-
             foldM (mkEffect cgDirtyEffects emptyFact factMap)
                 (Map.empty,Map.empty,Map.empty,[]) allEffs

         emptyFact <- mkFact cgDirtyFacts pres adds dels 0 (I.Fact "<empty>" [])

         facts <- zipWithM (mkFact cgDirtyFacts pres adds dels) [1 ..] allFacts
         let factMap = Map.fromList (zip allFacts facts)


     let resolveFacts fs = Set.fromList (map (factMap Map.!) fs)

         -- translate the initial state and goal
         state = Set.insert emptyFact (resolveFacts (I.probInit prob))
         goal  =                       resolveFacts (I.probGoal prob)

         cgFacts   = Set.fromList facts
         cgEffects = Set.fromList effs


     return (state, goal, ConnGraph { .. })

  where

  -- all ground facts
  allFacts = Set.toList (I.probFacts prob `Set.union` I.domFacts dom)

  -- all ground effects, extended with the preconditions from their operators
  allEffs = [ (ix, op, eff) | ix  <- [0 .. ]
                            | op  <- I.domOperators dom
                            , eff <- I.expandEffects op ]


  mkFact fDirtyList pres adds dels fId fProp =
    do fLevel  <- newIORef Nothing
       fIsTrue <- newIORef Nothing
       fIsGoal <- newIORef False
       fDirty  <- newIORef False

       return Fact { fPreCond = Map.findWithDefault Set.empty fProp pres
                   , fAdd     = Map.findWithDefault Set.empty fProp adds
                   , fDel     = Map.findWithDefault Set.empty fProp dels
                   , .. }

  mkEffect eDirtyList emptyFact facts (pres,adds,dels,effs) (eId,op,e) =
    do eLevel     <- newIORef Nothing
       eActivePre <- newIORef 0
       eInPlan    <- newIORef False
       eIsInH     <- newIORef False

       eDirty     <- newIORef False

       let refs fs = Set.fromList (map (facts Map.!) fs)

           -- When the preconditions for this fact are empty, make it depend on
           -- the special empty fact.
           ePre | null (I.ePre e) = Set.singleton emptyFact
                | otherwise       = refs (I.ePre e)

           eff     =  Effect { eNumPre = length (I.ePre e)
                             , eAdds   = refs (I.eAdd e)
                             , eDels   = refs (I.eDel e)
                             , eOp     = op
                             , .. }

           eff'      = Set.singleton eff
           merge f m = Map.insertWith Set.union f eff' m


       return ( foldr merge pres (I.ePre e)
              , foldr merge adds (I.eAdd e)
              , foldr merge dels (I.eDel e)
              , eff : effs )


-- Resetting -------------------------------------------------------------------

-- | Reset dirty references in the plan graph to their initial state.
resetConnGraph :: ConnGraph a -> IO ()
resetConnGraph ConnGraph { .. } =
  do facts <- atomicModifyIORef' cgDirtyFacts (\xs -> ([], xs))
     mapM_ resetFact facts

     effs <- atomicModifyIORef' cgDirtyEffects (\xs -> ([], xs))
     mapM_ resetEffect effs

resetFact :: Fact a -> IO ()
resetFact Fact { .. } =
  do writeIORef fLevel  Nothing
     writeIORef fIsTrue Nothing
     writeIORef fIsGoal False
     writeIORef fDirty  False

resetEffect :: Effect a -> IO ()
resetEffect Effect { .. } =
  do writeIORef eLevel Nothing
     writeIORef eActivePre 0
     writeIORef eInPlan False
     writeIORef eIsInH False
     writeIORef eDirty False


-- Utilities -------------------------------------------------------------------

printConnGraph :: ConnGraph a -> IO ()
printConnGraph cg =
  do printFacts cg
     printEffects cg

printFacts :: ConnGraph a -> IO ()
printFacts ConnGraph { .. } = mapM_ printFact cgFacts

printFact :: Fact a -> IO ()
printFact Fact { .. } =
  do putStrLn ("Fact: (" ++ show fId ++ ") " ++ show fProp)

     lev  <- readIORef fLevel
     true <- readIORef fIsTrue
     goal <- readIORef fIsGoal

     putStr $ unlines
       [ "  level:      " ++ show lev
       , "  is true:    " ++ show true
       , "  is goal:    " ++ show goal
       , "  required by:" ++ show (map eId (Set.toList fPreCond))
       , "  added by:   " ++ show (map eId (Set.toList fAdd))
       , "  deleted by: " ++ show (map eId (Set.toList fDel))
       ]

printEffects :: ConnGraph a -> IO ()
printEffects cg = mapM_ printEffect (cgEffects cg)

printEffect :: Effect a -> IO ()
printEffect Effect { .. } =
  do let I.Operator { .. } = eOp
     putStrLn ("Effect (" ++ show eId ++ ") " ++ T.unpack opName)

     lev <- readIORef eLevel

     putStr $ unlines
       [ " level:    " ++ show lev
       , " requires: " ++ show (map fProp (Set.toList ePre))
       , " adds:     " ++ show (map fProp (Set.toList eAdds))
       , " dels:     " ++ show (map fProp (Set.toList eDels))
       ]