{-# LANGUAGE TypeOperators #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} -- | -- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier -- License : GPL v3 (see LICENSE) -- -- Maintainer : Simon Meier -- Portability : GHC only -- -- A monad for writing constraint reduction steps together with basic steps -- for inserting nodes, edges, actions, and equations and applying -- substitutions. module Theory.Constraint.Solver.Reduction ( -- * The constraint 'Reduction' monad Reduction , execReduction , runReduction -- ** Change management , ChangeIndicator(..) , whenChanged , applyChangeList , whileChanging -- ** Accessing the 'ProofContext' , getProofContext , getMaudeHandle -- ** Inserting nodes, edges, and atoms , labelNodeId , insertFreshNode , insertFreshNodeConc , insertGoal , insertAtom , insertEdges , insertChain , insertAction , insertLess , insertFormula , reducibleFormula -- ** Goal management , markGoalAsSolved , removeSolvedSplitGoals -- ** Substitution application , substSystem , substNodes , substEdges , substLastAtom , substLessAtoms , substFormulas , substSolvedFormulas -- ** Solving equalities , SplitStrategy(..) , solveNodeIdEqs , solveTermEqs , solveFactEqs , solveRuleEqs , solveSubstEqs -- ** Conjunction with another constraint 'System' , conjoinSystem -- ** Convenience export , module Logic.Connectives ) where import Debug.Trace import Prelude hiding (id, (.)) import qualified Data.Foldable as F import qualified Data.Map as M import qualified Data.Set as S import Data.List (mapAccumL) import Safe import Control.Basics import Control.Category import Control.Monad.Bind import Control.Monad.Disj import Control.Monad.Reader import Control.Monad.State (StateT, execStateT, gets, runStateT) import Text.PrettyPrint.Class import Extension.Data.Label import Extension.Data.Monoid (Monoid(..)) import Extension.Prelude import Logic.Connectives import Theory.Constraint.Solver.Contradictions import Theory.Constraint.Solver.Types import Theory.Constraint.System import Theory.Model ------------------------------------------------------------------------------ -- The constraint reduction monad ------------------------------------------------------------------------------ -- | A constraint reduction step. Its state is the current constraint system, -- it can generate fresh names, split over cases, and access the proof -- context. type Reduction = StateT System (FreshT (DisjT (Reader ProofContext))) -- Executing reductions ----------------------- -- | Run a constraint reduction. Returns a list of constraint systems whose -- combined solutions are equal to the solutions of the given system. This -- property is obviously not enforced, but it must be respected by all -- functions of type 'Reduction'. runReduction :: Reduction a -> ProofContext -> System -> FreshState -> Disj ((a, System), FreshState) runReduction m ctxt se fs = Disj $ (`runReader` ctxt) $ runDisjT $ (`runFreshT` fs) $ runStateT m se -- | Run a constraint reduction returning only the updated constraint systems -- and the new freshness states. execReduction :: Reduction a -> ProofContext -> System -> FreshState -> Disj (System, FreshState) execReduction m ctxt se fs = Disj $ (`runReader` ctxt) . runDisjT . (`runFreshT` fs) $ execStateT m se -- Change management -------------------- -- | Indicate whether the constraint system was changed or not. data ChangeIndicator = Unchanged | Changed deriving( Eq, Ord, Show ) instance Monoid ChangeIndicator where mempty = Unchanged Changed `mappend` _ = Changed _ `mappend` Changed = Changed Unchanged `mappend` Unchanged = Unchanged -- | Return 'True' iff there was a change. wasChanged :: ChangeIndicator -> Bool wasChanged Changed = True wasChanged Unchanged = False -- | Only apply a monadic action, if there has been a change. whenChanged :: Monad m => ChangeIndicator -> m () -> m () whenChanged = when . wasChanged -- | Apply a list of changes to the proof state. applyChangeList :: [Reduction ()] -> Reduction ChangeIndicator applyChangeList [] = return Unchanged applyChangeList changes = sequence_ changes >> return Changed -- | Execute a 'Reduction' as long as it results in changes. Indicate whether -- at least one change was performed. whileChanging :: Reduction ChangeIndicator -> Reduction ChangeIndicator whileChanging reduction = go Unchanged where go indicator = do indicator' <- reduction case indicator' of Unchanged -> return indicator Changed -> go indicator' -- Accessing the proof context ------------------------------ -- | Retrieve the 'ProofContext'. getProofContext :: Reduction ProofContext getProofContext = ask -- | Retrieve the 'MaudeHandle' from the 'ProofContext'. getMaudeHandle :: Reduction MaudeHandle getMaudeHandle = askM pcMaudeHandle -- Inserting (fresh) nodes into the constraint system ----------------------------------------------------- -- | Insert a fresh rule node labelled with a fresh instance of one of the -- rules and return one of the conclusions. insertFreshNodeConc :: [RuleAC] -> Reduction (RuleACInst, NodeConc, LNFact) insertFreshNodeConc rules = do (i, ru) <- insertFreshNode rules (v, fa) <- disjunctionOfList $ enumConcs ru return (ru, (i, v), fa) -- | Insert a fresh rule node labelled with a fresh instance of one of the rules -- and solve it's 'Fr', 'In', and 'KU' premises immediatly. insertFreshNode :: [RuleAC] -> Reduction (NodeId, RuleACInst) insertFreshNode rules = do i <- freshLVar "vr" LSortNode (,) i <$> labelNodeId i rules -- | Label a node-id with a fresh instance of one of the rules and -- solve it's 'Fr', 'In', and 'KU' premises immediatly. -- -- PRE: Node must not yet be labelled with a rule. labelNodeId :: NodeId -> [RuleAC] -> Reduction RuleACInst labelNodeId = \i rules -> do (ru, mrconstrs) <- importRule =<< disjunctionOfList rules solveRuleConstraints mrconstrs modM sNodes (M.insert i ru) exploitPrems i ru return ru where -- | Import a rule with all its variables renamed to fresh variables. importRule ru = someRuleACInst ru `evalBindT` noBindings mkISendRuleAC m = return $ Rule (IntrInfo (ISendRule)) [kuFact m] [inFact m] [kLogFact m] mkFreshRuleAC m = Rule (ProtoInfo (ProtoRuleACInstInfo FreshRule [])) [] [freshFact m] [] exploitPrems i ru = mapM_ (exploitPrem i ru) (enumPrems ru) exploitPrem i ru (v, fa) = case fa of -- CR-rule *DG2_2* specialized for *In* facts. Fact InFact [m] -> do j <- freshLVar "vf" LSortNode ruKnows <- mkISendRuleAC m modM sNodes (M.insert j ruKnows) modM sEdges (S.insert $ Edge (j, ConcIdx 0) (i, v)) exploitPrems j ruKnows -- CR-rule *DG2_2* specialized for *Fr* facts. Fact FreshFact [m] -> do j <- freshLVar "vf" LSortNode modM sNodes (M.insert j (mkFreshRuleAC m)) unless (isFreshVar m) $ do -- 'm' must be of sort fresh ==> enforce via unification n <- varTerm <$> freshLVar "n" LSortFresh void (solveTermEqs SplitNow [Equal m n]) modM sEdges (S.insert $ Edge (j, ConcIdx 0) (i,v)) -- CR-rule *DG2_{2,u}*: solve a KU-premise by inserting the -- corresponding KU-actions before this node. _ | isKUFact fa -> do j <- freshLVar "vk" LSortNode insertLess j i void (insertAction j fa) -- Store premise goal for later processing using CR-rule *DG2_2* | otherwise -> insertGoal (PremiseG (i,v) fa) (v `elem` breakers) where breakers = ruleInfo (get praciLoopBreakers) (const []) $ get rInfo ru -- | Insert a chain constrain. insertChain :: NodeConc -> NodePrem -> Reduction () insertChain c p = insertGoal (ChainG c p) False -- | Insert an edge constraint. CR-rule *DG1_2* is enforced automatically, -- i.e., the fact equalities are enforced. insertEdges :: [(NodeConc, LNFact, LNFact, NodePrem)] -> Reduction () insertEdges edges = do void (solveFactEqs SplitNow [ Equal fa1 fa2 | (_, fa1, fa2, _) <- edges ]) modM sEdges (\es -> foldr S.insert es [ Edge c p | (c,_,_,p) <- edges]) -- | Insert an 'Action' atom. Ensures that (almost all) trivial *KU* actions -- are solved immediately using rule *S_{at,u,triv}*. We currently avoid -- adding intermediate products. Indicates whether nodes other than the given -- action have been added to the constraint system. -- -- FIXME: Ensure that intermediate products are also solved before stating -- that no rule is applicable. insertAction :: NodeId -> LNFact -> Reduction ChangeIndicator insertAction i fa = do present <- (goal `M.member`) <$> getM sGoals if present then do return Unchanged else do insertGoal goal False case kFactView fa of Just (UpK, viewTerm2 -> FPair m1 m2) -> requiresKU m1 *> requiresKU m2 *> return Changed Just (UpK, viewTerm2 -> FInv m) -> requiresKU m *> return Changed Just (UpK, viewTerm2 -> FMult ms) -> mapM_ requiresKU ms *> return Changed Just (UpK, viewTerm2 -> FUnion ms) -> mapM_ requiresKU ms *> return Changed _ -> return Unchanged where goal = ActionG i fa -- Here we rely on the fact that the action is new. Otherwise, we might -- loop due to generating new KU-nodes that are merged immediately. requiresKU t = do j <- freshLVar "vk" LSortNode let faKU = kuFact t insertLess j i void (insertAction j faKU) -- | Insert a 'Less' atom. @insertLess i j@ means that *i < j* is added. insertLess :: NodeId -> NodeId -> Reduction () insertLess i j = modM sLessAtoms (S.insert (i, j)) -- | Insert a 'Last' atom and ensure their uniqueness. insertLast :: NodeId -> Reduction ChangeIndicator insertLast i = do lst <- getM sLastAtom case lst of Nothing -> setM sLastAtom (Just i) >> return Unchanged Just j -> solveNodeIdEqs [Equal i j] -- | Insert an atom. Returns 'Changed' if another part of the constraint -- system than the set of actions was changed. insertAtom :: LNAtom -> Reduction ChangeIndicator insertAtom ato = case ato of EqE x y -> solveTermEqs SplitNow [Equal x y] Action i fa -> insertAction (ltermNodeId' i) fa Less i j -> do insertLess (ltermNodeId' i) (ltermNodeId' j) return Unchanged Last i -> insertLast (ltermNodeId' i) -- | Insert a 'Guarded' formula. Ensures that existentials, conjunctions, negated -- last atoms, and negated less atoms, are immediately solved using the rules -- *S_exists*, *S_and*, *S_not,last*, and *S_not,less*. Only the inserted -- formula is marked as solved. Other intermediate formulas are not marked. insertFormula :: LNGuarded -> Reduction () insertFormula = do insert True where insert mark fm = do formulas <- getM sFormulas solvedFormulas <- getM sSolvedFormulas insert' mark formulas solvedFormulas fm insert' mark formulas solvedFormulas fm | fm `S.member` formulas = return () | fm `S.member` solvedFormulas = return () | otherwise = case fm of GAto ato -> do markAsSolved void (insertAtom (bvarToLVar ato)) -- CR-rule *S_∧* GConj fms -> do markAsSolved mapM_ (insert False) (getConj fms) -- Store for later applications of CR-rule *S_∨* GDisj disj -> do modM sFormulas (S.insert fm) insertGoal (DisjG disj) False -- CR-rule *S_∃* GGuarded Ex ss as gf -> do -- must always mark as solved, as we otherwise may repeatedly -- introduce fresh variables. modM sSolvedFormulas $ S.insert fm xs <- mapM (uncurry freshLVar) ss let body = gconj (map GAto as ++ [gf]) insert False (substBound (zip [0..] (reverse xs)) body) -- CR-rule *S_{¬,⋖}* GGuarded All [] [Less i j] gf | gf == gfalse -> do markAsSolved insert False (gdisj [GAto (EqE i j), GAto (Less j i)]) -- CR-rule: FIXME add this rule to paper GGuarded All [] [EqE i@(bltermNodeId -> Just _) j@(bltermNodeId -> Just _) ] gf | gf == gfalse -> do markAsSolved insert False (gdisj [GAto (Less i j), GAto (Less j i)]) -- CR-rule *S_{¬,last}* GGuarded All [] [Last i] gf | gf == gfalse -> do markAsSolved lst <- getM sLastAtom j <- case lst of Nothing -> do j <- freshLVar "last" LSortNode void (insertLast j) return (varTerm (Free j)) Just j -> return (varTerm (Free j)) insert False $ gdisj [ GAto (Less j i), GAto (Less i j) ] -- Guarded All quantification: store for saturation GGuarded All _ _ _ -> modM sFormulas (S.insert fm) where markAsSolved = when mark $ modM sSolvedFormulas $ S.insert fm -- | 'True' iff the formula can be reduced by one of the rules implemented in -- 'insertFormula'. reducibleFormula :: LNGuarded -> Bool reducibleFormula fm = case fm of GAto _ -> True GConj _ -> True GGuarded Ex _ _ _ -> True GGuarded All [] [Less _ _] gf -> gf == gfalse GGuarded All [] [Last _] gf -> gf == gfalse _ -> False -- Goal management ------------------ -- | Combine the status of two goals. combineGoalStatus :: GoalStatus -> GoalStatus -> GoalStatus combineGoalStatus (GoalStatus solved1 age1 loops1) (GoalStatus solved2 age2 loops2) = GoalStatus (solved1 || solved2) (min age1 age2) (loops1 || loops2) -- | Insert a goal and its status with a new age. Merge status if goal exists. insertGoalStatus :: Goal -> GoalStatus -> Reduction () insertGoalStatus goal status = do age <- getM sNextGoalNr modM sGoals $ M.insertWith' combineGoalStatus goal (set gsNr age status) sNextGoalNr =: succ age -- | Insert a 'Goal' and store its age. insertGoal :: Goal -> Bool -> Reduction () insertGoal goal looping = insertGoalStatus goal (GoalStatus False 0 looping) -- | Mark the given goal as solved. markGoalAsSolved :: String -> Goal -> Reduction () markGoalAsSolved how goal = case goal of ActionG _ _ -> updateStatus PremiseG _ fa | isKDFact fa -> modM sGoals $ M.delete goal | otherwise -> updateStatus ChainG _ _ -> modM sGoals $ M.delete goal SplitG _ -> updateStatus DisjG disj -> modM sFormulas (S.delete $ GDisj disj) >> modM sSolvedFormulas (S.insert $ GDisj disj) >> updateStatus where updateStatus = do mayStatus <- M.lookup goal <$> getM sGoals case mayStatus of Just status -> trace (msg status) $ modM sGoals $ M.insert goal $ set gsSolved True status Nothing -> trace ("markGoalAsSolved: inexistent goal " ++ show goal) $ return () msg status = render $ nest 2 $ fsep $ [ text ("solved goal nr. "++ show (get gsNr status)) <-> parens (text how) <> colon , nest 2 (prettyGoal goal) ] removeSolvedSplitGoals :: Reduction () removeSolvedSplitGoals = do goals <- getM sGoals existent <- splitExists <$> getM sEqStore sequence_ [ modM sGoals $ M.delete goal | goal@(SplitG i) <- M.keys goals, not (existent i) ] -- Substitution --------------- -- | Apply the current substitution of the equation store to the remainder of -- the sequent. substSystem :: Reduction ChangeIndicator substSystem = do c1 <- substNodes substEdges substLastAtom substLessAtoms substFormulas substSolvedFormulas substLemmas c2 <- substGoals substNextGoalNr return (c1 <> c2) -- no invariants to maintain here substEdges, substLessAtoms, substLastAtom, substFormulas, substSolvedFormulas, substLemmas, substNextGoalNr :: Reduction () substEdges = substPart sEdges substLessAtoms = substPart sLessAtoms substLastAtom = substPart sLastAtom substFormulas = substPart sFormulas substSolvedFormulas = substPart sSolvedFormulas substLemmas = substPart sLemmas substNextGoalNr = return () -- | Apply the current substitution of the equation store to a part of the -- sequent. This is an internal function. substPart :: Apply a => (System :-> a) -> Reduction () substPart l = do subst <- getM sSubst modM l (apply subst) -- | Apply the current substitution of the equation store the nodes of the -- constraint system. Indicates whether additional equalities were added to -- the equations store. substNodes :: Reduction ChangeIndicator substNodes = substNodeIds <* ((modM sNodes . M.map . apply) =<< getM sSubst) -- | @setNodes nodes@ normalizes the @nodes@ such that node ids are unique and -- then updates the @sNodes@ field of the proof state to the corresponding map. -- Return @True@ iff new equalities have been added to the equation store. setNodes :: [(NodeId, RuleACInst)] -> Reduction ChangeIndicator setNodes nodes0 = do sNodes =: M.fromList nodes if null ruleEqs then return Unchanged else solveRuleEqs SplitLater ruleEqs >> return Changed where -- merge nodes with equal node id (ruleEqs, nodes) = first concat $ unzip $ map merge $ groupSortOn fst nodes0 merge [] = unreachable "setNodes" merge (keep:remove) = (map (Equal (snd keep) . snd) remove, keep) -- | Apply the current substitution of the equation store to the node ids and -- ensure uniqueness of the labels, as required by rule *U_lbl*. Indicates -- whether there where new equalities added to the equations store. substNodeIds :: Reduction ChangeIndicator substNodeIds = whileChanging $ do subst <- getM sSubst nodes <- gets (map (first (apply subst)) . M.toList . get sNodes) setNodes nodes -- | Substitute all goals. Keep the ones with the lower nr. substGoals :: Reduction ChangeIndicator substGoals = do subst <- getM sSubst goals <- M.toList <$> getM sGoals sGoals =: M.empty changes <- forM goals $ \(goal, status) -> case goal of -- Look out for KU-actions that might need to be solved again. ActionG i fa@(kFactView -> Just (UpK, m)) | (isMsgVar m || isProduct m || isUnion m) && (apply subst m /= m) -> insertAction i (apply subst fa) _ -> do modM sGoals $ M.insertWith' combineGoalStatus (apply subst goal) status return Unchanged return (mconcat changes) -- Conjoining two constraint systems ------------------------------------ -- | @conjoinSystem se@ conjoins the logical information in @se@ to the -- constraint system. It assumes that the free variables in @se@ are shared -- with the free variables in the proof state. conjoinSystem :: System -> Reduction () conjoinSystem sys = do kind <- getM sCaseDistKind unless (kind == get sCaseDistKind sys) $ error "conjoinSystem: typing-kind mismatch" joinSets sSolvedFormulas joinSets sLemmas joinSets sEdges F.mapM_ insertLast $ get sLastAtom sys F.mapM_ (uncurry insertLess) $ get sLessAtoms sys -- split-goals are not valid anymore mapM_ (uncurry insertGoalStatus) $ filter (not . isSplitGoal . fst) $ M.toList $ get sGoals sys F.mapM_ insertFormula $ get sFormulas sys -- update nodes _ <- (setNodes . (M.toList (get sNodes sys) ++) . M.toList) =<< getM sNodes -- conjoin equation store eqs <- getM sEqStore let (eqs',splitIds) = (mapAccumL addDisj eqs (map snd . getConj $ get sConjDisjEqs sys)) setM sEqStore eqs' -- add split-goals for all disjunctions of sys mapM_ (`insertGoal` False) $ SplitG <$> splitIds void (solveSubstEqs SplitNow $ get sSubst sys) -- Propagate substitution changes. Ignore change indicator, as it is -- assumed to be 'Changed' by default. void substSystem where joinSets :: Ord a => (System :-> S.Set a) -> Reduction () joinSets proj = modM proj (`S.union` get proj sys) -- Unification via the equation store ------------------------------------- -- | 'SplitStrategy' denotes if the equation store should be split into -- multiple equation stores. data SplitStrategy = SplitNow | SplitLater -- The 'ChangeIndicator' indicates whether at least one non-trivial equality -- was solved. -- | @noContradictoryEqStore@ suceeds iff the equation store is not -- contradictory. noContradictoryEqStore :: Reduction () noContradictoryEqStore = (contradictoryIf . eqsIsFalse) =<< getM sEqStore -- | Add a list of term equalities to the equation store. And -- split resulting disjunction of equations according -- to given split strategy. -- -- Note that updating the remaining parts of the constraint system with the -- substitution has to be performed using a separate call to 'substSystem'. solveTermEqs :: SplitStrategy -> [Equal LNTerm] -> Reduction ChangeIndicator solveTermEqs splitStrat eqs0 = case filter (not . evalEqual) eqs0 of [] -> do return Unchanged eqs1 -> do hnd <- getMaudeHandle se <- gets id (eqs2, maySplitId) <- addEqs hnd eqs1 =<< getM sEqStore setM sEqStore =<< simp hnd (substCreatesNonNormalTerms hnd se) =<< case (maySplitId, splitStrat) of (Just splitId, SplitNow) -> disjunctionOfList $ fromJustNote "solveTermEqs" $ performSplit eqs2 splitId (Just splitId, SplitLater) -> do insertGoal (SplitG splitId) False return eqs2 _ -> return eqs2 noContradictoryEqStore return Changed -- | Add a list of equalities in substitution form to the equation store solveSubstEqs :: SplitStrategy -> LNSubst -> Reduction ChangeIndicator solveSubstEqs split subst = solveTermEqs split [Equal (varTerm v) t | (v, t) <- substToList subst] -- | Add a list of node equalities to the equation store. solveNodeIdEqs :: [Equal NodeId] -> Reduction ChangeIndicator solveNodeIdEqs = solveTermEqs SplitNow . map (fmap varTerm) -- | Add a list of fact equalities to the equation store, if possible. solveFactEqs :: SplitStrategy -> [Equal LNFact] -> Reduction ChangeIndicator solveFactEqs split eqs = do contradictoryIf (not $ all evalEqual $ map (fmap factTag) eqs) solveListEqs (solveTermEqs split) $ map (fmap factTerms) eqs -- | Add a list of rule equalities to the equation store, if possible. solveRuleEqs :: SplitStrategy -> [Equal RuleACInst] -> Reduction ChangeIndicator solveRuleEqs split eqs = do contradictoryIf (not $ all evalEqual $ map (fmap (get rInfo)) eqs) solveListEqs (solveFactEqs split) $ map (fmap (get rConcs)) eqs ++ map (fmap (get rPrems)) eqs ++ map (fmap (get rActs)) eqs -- | Solve a number of equalities between lists interpreted as free terms -- using the given solver for solving the entailed per-element equalities. solveListEqs :: ([Equal a] -> Reduction b) -> [(Equal [a])] -> Reduction b solveListEqs solver eqs = do contradictoryIf (not $ all evalEqual $ map (fmap length) eqs) solver $ concatMap flatten eqs where flatten (Equal l r) = zipWith Equal l r -- | Solve the constraints associated with a rule. solveRuleConstraints :: Maybe RuleACConstrs -> Reduction () solveRuleConstraints (Just eqConstr) = do hnd <- getMaudeHandle (eqs, splitId) <- addRuleVariants eqConstr <$> getM sEqStore insertGoal (SplitG splitId) False -- do not use expensive substCreatesNonNormalTerms here setM sEqStore =<< simp hnd (const (const False)) eqs noContradictoryEqStore solveRuleConstraints Nothing = return ()