tamarin-prover-0.1.0.0: src/Theory/Proof/EquationStore.hs
{-# LANGUAGE TypeOperators, TemplateHaskell, DeriveDataTypeable, ScopedTypeVariables, TupleSections #-}
-- |
-- Copyright : (c) 2010-2012 Benedikt Schmidt
-- License : GPL v3 (see LICENSE)
--
-- Maintainer : Benedikt Schmidt <beschmi@gmail.com>
-- Portability : GHC only
--
-- Support for reasoning with and about disjunctions of substitutions.
module Theory.Proof.EquationStore (
-- * Transformation
simp
, addEqs
, addRuleVariants
, splitAtPos
, eqSplits
, constrainedVarsPos
, SplitStrategy(..)
-- * simplify a disjunction
, simpDisjunction
) where
import Term.Unification
import Logic.Connectives
import Theory.Proof.Types
import Control.Monad.Reader
import Control.Monad.Fresh
import Utils.Misc
import Extension.Prelude
-- import qualified Debug.Trace as DT
import Debug.Trace.Ignore
import Data.List
import Data.Label hiding ( for )
import Data.Maybe
import Data.Monoid
import Data.Traversable hiding ( mapM )
import qualified Data.Foldable as F
import Control.Basics
import Control.Monad.State hiding (get, modify)
import qualified Control.Monad.State as MS
-- Equation Store
----------------------------------------------------------------------
-- | We use an empty disjunction to denote false.
falseDisj :: Disj (LNSubstVFresh)
falseDisj = Disj []
data SplitStrategy = SplitNow | SplitLater
-- Dealing with equations
----------------------------------------------------------------------
-- | Returns the list of all @SplitId@s corresponding equation disjunctions.
eqSplits :: EqStore -> [SplitId]
eqSplits eqs = [0.. length (getConj . get eqsConj $ eqs) -1 ]
-- | Add a list of term equalities to the equation store.
-- Returns the resulting equation store(s) depending
-- on the split strategy.
addEqs :: MonadFresh m => SplitStrategy -> MaudeHandle
-> [Equal LNTerm] -> EqStore -> m [EqStore]
addEqs splitStrat hnd eqs0 eqStore =
case unifyLNTermFactored eqs `runReader` hnd of
(_, []) -> return [set eqsConj falseEqConstrConj eqStore]
(subst, substs) ->
case splitStrat of
SplitLater ->
return $ [addDisj (applyEqStore hnd subst eqStore) (Disj substs)]
SplitNow ->
addEqsAC (modify eqsSubst (compose subst) eqStore)
<$> simpDisjunction hnd (Disj substs)
where
eqs = apply (get eqsSubst eqStore) $ trace (unlines ["addEqs: ", show eqs0]) $ eqs0
addEqsAC eqSt (sfree, Nothing) = [applyEqStore hnd sfree eqSt]
addEqsAC eqSt (sfree, Just disj) =
fromMaybe (error "addEqsSplit: impossible, splitAtPos failed")
(splitAtPos (applyEqStore hnd sfree (addDisj eqSt (Disj disj))) 0)
-- | Apply a substitution to an equation store and bring resulting equations into
-- normal form again by using unification.
applyEqStore :: MaudeHandle -> LNSubst -> EqStore -> EqStore
applyEqStore hnd asubst eqStore
| dom asubst `intersect` varsRange asubst /= [] || trace (show ("applyEqStore", asubst, eqStore)) False
= error $ "applyS2EqStore: dom and vrange not disjoint for `"++show asubst++"'"
| otherwise
= modify eqsConj (fmap ((Disj . concatMap applyBound . getDisj))) $
set eqsSubst newsubst eqStore
where
newsubst = asubst `compose` get eqsSubst eqStore
applyBound s = map (restrictVFresh (varsRange newsubst ++ domVFresh s)) $
(`runReader` hnd) $ unifyLNTerm
[ Equal (apply newsubst (varTerm $ lv)) t
| let slist = substToListVFresh s,
-- variables in the range are fresh, so we have to rename
-- them away from all other variables in unification problem
-- NOTE: these variables never enter the global context
let ran = renameAvoiding (map snd slist)
(domVFresh s ++ varsRange newsubst),
(lv,t) <- zip (map fst slist) ran
]
{- NOTES for @applyEqStore tau@ to a fresh substitution sigma:
[ FIXME: extend explanation to multiple unifiers ]
Let dom(sigma) = x1,..,xk, vrange(sigma) = y1, .. yl, vrange(tau) = z1,..,zn
Fresh substitution denotes formula
exists #y1, .., #yl. x1 = t1 /\ .. /\ xk = tk
for variables #yi that do not clash with xi and zi [renameAwayFrom]
and with vars(ti) `subsetOf` [#y1, .. #yl].
We apply tau with vrange(tau) = z1,..,zn to the formula to obtain
exists ##y1, .., ##yl. tau(x1) = t1 /\ .. /\ tau(xk) = tk
unification then yields a lemma
forall xi zi #yi.
tau(x1) = t1 /\ .. /\ tau(xk) = tk
<-> exists vars(s1,..sm). x1 = .. /\ z1 = .. /\ #y1 = ..
So we have
exists #y1, .., #yl.
exists vars(s1,..sm). x1 = .. /\ z1 = .. /\ #y1 = ..
<=>
exists vars(s1,..sm). x1 = .. /\ z1 = ..
/\ (exists #y1, .., #yl. #y1 = ..)
<=> [restric]
exists vars(s1,..sm). x1 = .. /\ z1 = .. /\ True
-}
-- | Add the given rule variants.
addRuleVariants :: (Disj (LNSubstVFresh)) -> EqStore -> EqStore
addRuleVariants (Disj substs) eqStore
| dom freeSubst `intersect` concatMap domVFresh substs /= []
= error $ "addRuleVariants: Nonempty intersection between domain of variants and free substitution. "
++"This case has not been implemented, add rule variants earlier."
| otherwise = addDisj eqStore (Disj substs)
where
freeSubst = get eqsSubst eqStore
-- | Return the set of variables that is constrained by disjunction at give position.
constrainedVarsPos :: EqStore -> Int -> [LVar]
constrainedVarsPos eqStore k
| k < length conj = frees (conj!!k)
| otherwise = []
where
conj = getConj . get eqsConj $ eqStore
-- Internal functions
----------------------------------------------------------------------
-- | Add a disjunction to the equation store at the beginning
addDisj :: EqStore -> (Disj (LNSubstVFresh)) -> EqStore
addDisj eqStore disj = modify eqsConj ((Conj [disj]) `mappend`) eqStore
-- | @splitEqStoreAt eqs i@ takes the disjunction at position @i@ in @eqs@
-- and returns a list of resulting substitutions and the equality store
-- with the remaining equations.
splitAtPos :: EqStore -> Int -> Maybe [EqStore]
splitAtPos eqStore i
| i `notElem` eqSplits eqStore = Nothing
| otherwise = Just $ map (\d -> set eqsConj (conjNew d) eqStore) disj
where
conj = getConj $ get eqsConj eqStore
disj = getDisj $ conj !! i
conjNew d = Conj $ take i conj ++ [Disj [d]] ++ drop (i+1) conj
-- Simplifying disjunctions
----------------------------------------------------------------------
-- | Simplify given disjunction via EqStore simplification. Obtains fresh
-- names for variables from the underlying 'MonadFresh'.
simpDisjunction :: MonadFresh m
=> MaudeHandle
-> Disj (LNSubstVFresh)
-> m (LNSubst, Maybe [LNSubstVFresh])
simpDisjunction hnd disj0 = do
eqStore' <- simp hnd eqStore
return (get eqsSubst eqStore', wrap $ get eqsConj eqStore')
where
eqStore = set eqsConj (Conj [disj0]) $ emptyEqStore
wrap (Conj []) = Nothing
wrap (Conj [Disj disj]) = Just $ disj
wrap conj =
error ("simplifyDisjunction: imposible, unexpected conjuction `"
++ show conj ++ "'")
-- Simplification
----------------------------------------------------------------------
-- | @simp eqStore@ simplifies the equation store.
simp :: MonadFresh m => MaudeHandle -> EqStore -> m EqStore
simp hnd eqStore = (`execStateT` (trace (show ("eqStore", eqStore)) eqStore)) $ whileTrue (simp1 hnd)
-- | @simp1@ tries to execute one simplification step
-- for the equation store. It returns @True@ if
-- the equation store was modified.
simp1 :: MonadFresh m => MaudeHandle -> StateT EqStore m Bool
simp1 hnd = do
s <- MS.get
b1 <- simpMinimize
b2 <- simpRemoveRenamings
b3 <- simpEmptyDisj
b4 <- foreachDisj hnd simpSingleton
b5 <- foreachDisj hnd simpAbstractSortedVar
b6 <- foreachDisj hnd simpIdentify
b7 <- foreachDisj hnd simpAbstractFun
b8 <- foreachDisj hnd simpAbstractName
s' <- MS.get
(trace (show ("simp:", [b1, b2, b3, b4, b5, b6, b7, b8], s, s'))) $ return $ (or [b1, b2, b3, b4, b5, b6, b7, b8])
-- | Remove variable renamings in fresh substitutions.
simpRemoveRenamings :: MonadFresh m => StateT EqStore m Bool
simpRemoveRenamings = do
conj <- gets (get eqsConj)
let (conj',changed) =
runState (traverse (traverse rmRenamings) conj) False
when changed $ MS.modify (set eqsConj conj')
return changed
where
rmRenamings :: LNSubstVFresh -> State Bool LNSubstVFresh
rmRenamings subst = do
let subst' = removeRenamings subst
when (domVFresh subst /= domVFresh subst') $ put True
return subst'
-- | If empty disjunction is found, the whole conjunct
-- can be simplified to False.
simpEmptyDisj :: MonadFresh m => StateT EqStore m Bool
simpEmptyDisj = do
conj <- gets (get eqsConj)
if (F.any (==falseDisj) conj && conj /= falseEqConstrConj)
then MS.modify (set eqsConj falseEqConstrConj) >> return True
else return False
-- | If there is a singleton disjunction, it can be
-- composed with the free substitution.
simpSingleton :: MonadFresh m => Disj LNSubstVFresh
-> m (Maybe (Maybe LNSubst, [Disj LNSubstVFresh]))
simpSingleton (Disj [subst0]) = do
subst <- freshToFree subst0
return (Just (Just subst, []))
simpSingleton _ = return Nothing
-- | If all substitutions @si@ map a variable @v@ to terms with the same
-- outermost function symbol @f@, then they all contain the common factor
-- @{v |-> f(x1,..,xk)}@ for fresh variables xi and we can replace
-- @x |-> ..@ by @{x1 |-> ti1, x2 |-> ti2, ..}@ in all substitutions @si@.
simpAbstractFun :: MonadFresh m => Disj LNSubstVFresh
-> m (Maybe (Maybe LNSubst, [Disj LNSubstVFresh]))
simpAbstractFun (Disj []) = return Nothing
simpAbstractFun (Disj (subst:others)) = case commonOperators of
[] -> return Nothing
-- abstract all arguments
(v, o, argss@(args:_)):_ | all ((==length args) . length) argss -> do
fvars <- mapM (\_ -> freshLVar "x" LSortMsg) args
let substs' = zipWith (abstractAll v fvars) (subst:others) argss
fsubst = substFromList [(v, FApp o (map varTerm fvars))]
return $ Just (Just $ fsubst, [Disj substs'])
-- abstract first two arguments
(v, o@(AC _), argss):_ -> do
fv1 <- freshLVar "x" LSortMsg
fv2 <- freshLVar "x" LSortMsg
let substs' = zipWith (abstractTwo o v fv1 fv2) (subst:others) argss
fsubst = substFromList [(v, FApp o (map varTerm [fv1,fv2]))]
return $ Just (Just $ fsubst, [Disj substs'])
(_, _ ,_):_ ->
error "simpAbstract: impossible, invalid arities or List operator encountered."
where
commonOperators = do
(v, FApp o args) <- substToListVFresh subst
let images = map (\s -> imageOfVFresh s v) others
argss = [ args' | Just (FApp o' args') <- images, o' == o ]
guard (length argss == length others)
return (v, o, args:argss)
abstractAll v freshVars s args = substFromListVFresh $
filter ((/= v) . fst) (substToListVFresh s) ++ zip freshVars args
abstractTwo o v fv1 fv2 s args = substFromListVFresh $
filter ((/= v) . fst) (substToListVFresh s) ++ newMappings args
where
newMappings [] =
error "simpAbstract: impossible, AC symbols must have arity >= 2."
newMappings [a1,a2] = [(fv1, a1), (fv2, a2)]
-- here we always abstract from left to right and do not
-- take advantage of the AC property of o
newMappings (a:as) = [(fv1, a), (fv2, FApp o as)]
-- | If all substitutions @si@ map a variable @v@ to the same name @n@,
-- then they all contain the common factor
-- @{v |-> n}@ and we can remove @{v -> n} from all substitutions @si@
simpAbstractName :: MonadFresh m => Disj LNSubstVFresh
-> m (Maybe (Maybe LNSubst, [Disj LNSubstVFresh]))
simpAbstractName (Disj []) = return Nothing
simpAbstractName (Disj (subst:others)) = case commonNames of
[] -> return Nothing
(v, c):_ ->
return $ Just (Just $ substFromList [(v, c)]
, [Disj (map (\s -> restrictVFresh (delete v (domVFresh s)) s) (subst:others))])
where
commonNames = do
(v, c@(Lit (Con _))) <- substToListVFresh subst
let images = map (\s -> imageOfVFresh s v) others
guard (length images == length [ () | Just c' <- images, c' == c])
return (v, c)
-- | If all substitutions @si@ map a variable @v@ to variables @xi@ of the same
-- sort @s@ then they all contain the common factor
-- @{v |-> y}@ for a fresh variable of sort @s@
-- and we can replace @{v -> xi}@ by @{y -> xi} in all substitutions @si@
simpAbstractSortedVar :: MonadFresh m => Disj LNSubstVFresh
-> m (Maybe (Maybe LNSubst, [Disj LNSubstVFresh]))
simpAbstractSortedVar (Disj []) = return Nothing
simpAbstractSortedVar (Disj (subst:others)) = case commonSortedVar of
[] -> return Nothing
(v, s, lvs):_ -> do
fv <- freshLVar (lvarName v) s
return $ Just (Just $ substFromList [(v, varTerm fv)]
, [Disj (zipWith (replaceMapping v fv) lvs (subst:others))])
where
commonSortedVar = do
(v, (Lit (Var lx))) <- substToListVFresh subst
guard (sortCompare (lvarSort v) (lvarSort lx) == Just GT)
let images = map (\s -> imageOfVFresh s v) others
-- FIXME: could be generalized to choose topsort s of all images if s < sortOf v
-- could also be generalized to terms of a given sort
goodImages = [ ly | Just (Lit (Var ly)) <- images, lvarSort lx == lvarSort ly]
guard (length images == length goodImages)
return (v, lvarSort lx, (lx:goodImages))
replaceMapping v fv lv sigma =
substFromListVFresh $ (filter ((/=v) . fst) $ substToListVFresh sigma) ++ [(fv, varTerm lv)]
-- | If all substitutions @si@ map two variables @x@ and @y@ to identical terms @ti@,
-- then they all contain the common factor @{x |-> y} for a fresh variable @z@
-- and we can remove @{x |-> ti}@ from all @si@.
simpIdentify :: MonadFresh m => Disj (LNSubstVFresh)
-> m (Maybe (Maybe LNSubst, [Disj LNSubstVFresh]))
simpIdentify (Disj []) = return Nothing
simpIdentify (Disj (subst:others)) = case equalImgPairs of
[] -> return Nothing
((v,v'):_) -> do
let (vkeep, vremove) = case sortCompare (lvarSort v) (lvarSort v') of
Just GT -> (v', v)
Just _ -> (v, v')
Nothing -> error $ "EquationStore.simpIdentify: impossible, variables with incomparable sorts: "
++ show v ++" and "++ show v'
return $ Just (Just (substFromList [(vremove, varTerm vkeep)]),
[Disj (map (removeMappings [vkeep]) (subst:others))])
where
equalImgPairs = do
(v,t) <- substToListVFresh subst
(v', t') <- substToListVFresh subst
guard (t == t' && v < v' && all (agrees_on v v') others)
return (v,v')
agrees_on v v' s =
imageOfVFresh s v == imageOfVFresh s v' && isJust (imageOfVFresh s v)
removeMappings vs s = restrictVFresh (domVFresh s \\ vs) s
-- | Traverse disjunctions without msgBefore fact in conjunction and
-- execute @f@ until it returns @Just (mfreeSubst, disjs)@.
-- Then the @disjs@ is inserted at the current position, if @mfreeSubst@ is
-- @Just freesubst@, then it is applied to the equation store. @True@ is
-- returned if any modifications took place.
foreachDisj :: MonadFresh m
=> MaudeHandle
-> (Disj (LNSubstVFresh) -> m (Maybe (Maybe LNSubst, [Disj LNSubstVFresh])))
-> StateT EqStore m Bool
foreachDisj hnd f = do
conj <- gets (get eqsConj)
go [] (getConj conj)
where
go _ [] = return False
go lefts (d:rights) = do
b <- lift $ f d
case b of
Nothing -> go (d:lefts) rights
Just (msubst, disjs) -> do
MS.modify (set eqsConj (Conj (reverse lefts ++ disjs ++ rights)))
maybe (return ()) (\s -> MS.modify (applyEqStore hnd s)) msubst
return True
-- Renaming and subsumption
----------------------------------------------------------------------
-- | Simplify by removing substitutions that occur twice in a disjunct.
-- We could generalize this function by using AC-equality or subsumption.
simpMinimize :: MonadFresh m => StateT EqStore m Bool
simpMinimize = do
eqs <- MS.get
let eqs' = modify eqsConj (fmap (Disj . sortednub . getDisj)) eqs
MS.put eqs'
return (eqs /= eqs')
{-
t2 = simpAbstract (Disj (map substFromListVFresh [s1,s2])) `evalFresh` nothingUsed
where s1 = [(lx1,pair(y1,y2))]
s2 = [(lx1,pair(inv(y1),inv(y2)))]
t3 = simpAbstract (Disj (map substFromListVFresh [s1,s2,s3])) `evalFresh` nothingUsed
where s1 = [(lx1, mult [y1,y2] )]
s2 = [(lx1, mult [inv(y1), inv(y2), inv(y3)])]
s3 = [(lx1, mult[y5, y6, y7, y8])]
t4 = simpIdentify (Disj (map substFromListVFresh [s1,s2])) `evalFresh` nothingUsed
where s1 = [(lx1, mult [y1,y2,y3] ), (lx2, mult [y1,y2,y3] )]
s2 = [(lx1, mult [inv(y1), inv(y2), inv(y3)]), (lx2, mult [inv(y1), inv(y2), inv(y3)])]
-}
{-
t5 = simpAbstractFun (Disj (map substFromListVFresh [s1,s2,s3])) `evalFresh` nothingUsed
where s1 = [(lx1, mult [y1,y2] )]
s2 = [(lx1, x3)]
s3 = [(lx1, mult[y5, y6, y7, y8])]
t6 = simpIdentify (Disj (map substFromListVFresh [s1,s2,s3])) `evalFresh` nothingUsed
where s1 = [(lx1, mult [y1,y2,y3] ), (lx2, mult [y1,y2,y3] )]
s2 = [(lx1, mult [inv(y1), inv(y2), inv(y3)]), (lx2, mult [inv(y1), inv(y2), inv(y3)])]
s3 = [(lx1, y1), (lx2, y2)]
-}