packages feed

huff-0.1.0.1: src/Huff/FF/Extract.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiWayIf #-}

module Huff.FF.Extract where

import           Huff.ConnGraph

import           Control.Monad ( foldM, filterM, guard )
import           Data.IORef ( readIORef, writeIORef )
import qualified Data.IntMap.Strict as IM
import           Data.Maybe ( isNothing )
import           Data.Monoid ( mappend )
import qualified Data.Set as Set


-- | A map from fact level to the goals that appear there.
type GoalSet a = IM.IntMap (Goals a)

-- | Construct the initial goal set for a set of presumed solved goals in the
-- connection graph.  If the goals have not been solved, the level returned will
-- be Nothing.
--
-- NOTE: in fast-forward, when a goal with level INFINITY is encountered, this
-- process returns immediately the value INFINITY, and doesn't complete the goal
-- set.
goalSet :: Goals a -> IO (Maybe (Level,GoalSet a))
goalSet goals = go 0 IM.empty (Set.toList goals)
  where
  go !m !gs (fact:fs) =
    do mb <- getLevel fact
       case mb of

         Nothing ->
              return Nothing

         Just i  ->
           do markGoal fact
              go (max m i) (IM.insertWith mappend i (Set.singleton fact) gs) fs

  go !m !gs [] = return (Just (m,gs))


-- | The difficulty heuristic for an effect: the lowest level where one of the
-- effect's preconditions appears.
difficulty :: Effect a -> IO Level
difficulty eff = foldM minPrecondLevel maxBound (Set.toList (effectPre eff))
  where
  minPrecondLevel l fact =
    do mb <- getLevel fact
       case mb of
         Just l' -> return $! min l l'
         Nothing -> return l

-- | Extract a plan from a fixed connection graph.
extractPlan :: Goals a -> IO (Maybe (Int,GoalSet a))
extractPlan goals0 =
  do mb <- goalSet goals0
     case mb of
       Just (m,gs) -> solveGoals 0 m gs
       Nothing     -> return Nothing
  where

  -- solve goals that are added at this fact level.
  solveGoals plan level gs
    | level > 0 =
      do (plan',gs') <-
           case IM.lookup level gs of
             Just goals -> foldM (solveGoal level) (plan,gs) (Set.toList goals)
             Nothing    -> return (plan,gs)

         solveGoals plan' (level - 1) gs'

    | otherwise =
         return (Just (plan,gs))

  solveGoal level acc@(plan,gs) f =
    do -- the goal was solved by something else at this level
       mb <- isTrue f
       case mb of
         Just trueLevel | trueLevel == level ->
              return acc

         _ ->
           do eff <- pickBest (level - 1) (Set.toList (addsFact f))
              markInPlan eff
              gs' <- foldM (filterGoals level) gs (Set.toList (effectPre eff))
              mapM_ (markAdd level) (Set.toList (effectAdds eff))
              let plan' = 1 + plan
              plan' `seq` return (plan',gs')

  -- insert goals into the goal set for the level where they become true
  filterGoals level gs fact =
    do true   <- isTrue   fact
       goal   <- isGoal   fact
       Just l <- getLevel fact

       let existingGoal =
             or [ maybe False (== level) true
                  -- the fact was added by something else at this level

                , goal
                  -- the fact is already a goal

                , l == 0
                  -- the fact exists in the initial layer
                ]

       if existingGoal
          then    return gs
          else do markGoal fact
                  return (IM.insertWith mappend l (Set.singleton fact) gs)


  -- mark the fact as being added at level i
  markAdd i fact = markTrue fact i


  -- pick the best effect that achieved this goal in the given layer, using the
  -- difficulty heuristic
  pickBest _ []     = fail "extractPlan: invalid connection graph"
  pickBest _ [e]    = return e
  pickBest level es = snd `fmap` foldM check (maxBound,error "pickBest") es
    where
    check acc@(d,_) eff =
      do mb <- getLevel eff
         case mb of

           Just l | l == level ->
             do d' <- difficulty eff
                let acc' | d' < d    = (d',eff)
                         | otherwise = acc
                return $! acc'

           _ -> return acc


-- Helpful Actions -------------------------------------------------------------

-- | All applicable actions from the state.
allActions :: State a -> IO (Effects a)
allActions s = foldM enabledEffects Set.empty (Set.toList s)
  where
  enabledEffects effs fact =
       foldM checkEffect effs (Set.toList (requiresFact fact))

  checkEffect effs eff =
    do mb <- getLevel eff
       case mb of
         Just 0 -> return $! Set.insert eff effs
         _      -> return effs

-- | Helpful actions are those in the first layer of the relaxed plan, that
-- contribute something directly to the next layer.
helpfulActions :: Effects a -> Goals a -> IO [Effect a]
helpfulActions effs goals
  | Set.null goals = return (Set.toList effs)
  | otherwise      = filterM isHelpful (Set.toList effs)
  where
  isHelpful eff =
    do inPlan <- isInPlan eff
       return (inPlan && not (Set.null (Set.intersection goals (effectAdds eff))))


-- Added Goal Deletion ---------------------------------------------------------

-- | True when the plan currently represented in the graph deletes a goal along
-- the way.
addedGoalDeletion :: Goals a -> IO Bool
addedGoalDeletion goals = go Set.empty (Set.toList goals)
  where
  go seen (fact : gs) =
    do (seen',mb) <- foldM checkDels (seen,Just Set.empty)
                                     (Set.toList (addsFact fact))
       case mb of
         Just gs' -> go seen' (Set.toList gs' ++ gs)
         Nothing  -> return True

  go _ [] = return False

  checkDels acc@(seen,next) eff

    | isNothing next || eff `Set.member` seen =
         return acc

    | otherwise =
      do inPlan <- isInPlan eff

         let next'
               | inPlan =
                 do guard (Set.null (goals `Set.intersection` effectDels eff))
                    facts <- next
                    return (effectPre eff `Set.union` facts)

               | otherwise =
                    next


         next' `seq` return (Set.insert eff seen, next')