resolution/app/Resolution.hs

212 lines
9.9 KiB
Haskell
Raw Normal View History

{-# LANGUAGE TupleSections #-}
2023-06-08 12:53:19 +02:00
{-# LANGUAGE InstanceSigs #-}
module Resolution where
import Data.Set (Set)
import qualified Data.Set as Set
import FOLSyntax
( Formula(Exists, Pred, Neg, Conj, Disj, Impl, All),
Term(..),
formulaVars,
termFreeVars,
termSubst,
findFresh )
import Data.Maybe ( mapMaybe )
2023-06-08 12:53:19 +02:00
import Data.List ( intercalate, intersect, permutations )
import Control.Monad (zipWithM)
type CNF = Set Clause
2023-06-08 12:53:19 +02:00
newtype Clause = Clause (Set Literal)
type Literal = Formula
type Unifier = [(Term, Term)]
2023-06-08 12:53:19 +02:00
instance Show Clause where
show :: Clause -> String
show = showClause
instance Eq Clause where
(==) :: Clause -> Clause -> Bool
(==) = alphaEq
instance Ord Clause where
(<=) :: Clause -> Clause -> Bool
(Clause c1) <= (Clause c2) = Clause c1 == Clause c2 || c1 <= c2
showCNF :: CNF -> String
showCNF set = "[" ++ intercalate ", " (map show $ Set.toList set) ++ "]"
-- alpha equality on clauses, a clause [S(x), P(y)] is equal to [S(y), P(x)]
-- 1. get all permutations of both clauses
-- 2. build the crossproduct of permutations of c1 and c2
-- 3. unify each clause pair
2023-06-08 12:53:19 +02:00
-- 4. check if any clause pair was unifiable, with a unificator that maps variables only to variables
alphaEq :: Clause -> Clause -> Bool
2023-06-08 12:53:19 +02:00
alphaEq (Clause c1) (Clause c2) = Set.size c1 == Set.size c2 && any (uncurry unifyLiteralList) permutationPairs
where
permutationPairs = [(c1', c2') | c1' <- permutations $ Set.toList c1, c2' <- permutations $ Set.toList c2]
-- unification algorithm of martelli montanari
unify :: [(Term, Term)] -> Maybe Unifier
unify [] = Just []
-- (delete)
unify ((Var x, Var y) : rest) | x == y = unify rest
-- (decomp)
unify ((Fun f es, Fun g ds) : rest) | f == g && length es == length ds = unify $ zip es ds ++ rest
-- (conflict)
unify ((Fun _ _, Fun _ _) : _) = Nothing
-- (orient)
unify ((Fun f ts, Var x) : rest) = unify $ (Var x, Fun f ts) : rest
-- (occurs)
unify ((Var x, t) : _) | x `elem` termFreeVars t && Var x /= t = Nothing
-- (elim)
unify ((Var x, t) : rest) | notElem x (termFreeVars t) && x `elem` concatMap (\(t1, t2) -> termFreeVars t1 ++ termFreeVars t2) rest = unify $ (Var x, t) : map (\(t1, t2) -> (termSubst t1 x t, termSubst t2 x t)) rest
-- decent
unify ((t, s) : rest) = do
rest' <- unify rest
return $ (t, s) : rest'
unifyLiteralList :: [Literal] -> [Literal] -> Bool
unifyLiteralList ls1 ls2 = case zipWithM unifyLiterals ls1 ls2 of
Nothing -> False
2023-06-08 12:53:19 +02:00
Just mgus -> all checkSimpleMgu mgus && checkValidMgu (concat mgus)
where
2023-06-08 12:53:19 +02:00
checkValidMgu mgu = case unify mgu of
Nothing -> False
_ -> True
checkSimpleMgu :: Unifier -> Bool
checkSimpleMgu [] = True
2023-06-08 12:53:19 +02:00
checkSimpleMgu ((Var _, Var _) : rest) = checkSimpleMgu rest
checkSimpleMgu _ = False
unifyLiterals :: Literal -> Literal -> Maybe Unifier
unifyLiterals (Neg f) (Neg g) = unifyLiterals f g
unifyLiterals (Pred p ts1) (Pred s ts2) | p == s && length ts1 == length ts2 = unify $ zip ts1 ts2
unifyLiterals _ _ = Nothing
-- unifies predicates, e.g. P(x,y) == P(f(a), z)
unifyPredicates :: Literal -> Literal -> Maybe Unifier
unifyPredicates (Pred p1 ts1) (Neg (Pred p2 ts2)) | p1 == p2 && length p1 == length p2 = unify $ zip ts1 ts2
unifyPredicates _ _ = Nothing
setConcat :: Ord a => [Set a] -> Set a
setConcat = foldr Set.union Set.empty
-- applies an mgu to a given formula, asserts that the formula contains no quantifiers
applyMgu :: Formula -> [(Term, Term)] -> Formula
applyMgu (Pred p ts) mgu = Pred p $ map (`applyMguTerm` mgu) ts
applyMgu (Neg f) mgu = Neg $ applyMgu f mgu
applyMgu (Conj f1 f2) mgu = Conj (applyMgu f1 mgu) (applyMgu f2 mgu)
applyMgu (Disj f1 f2) mgu = Disj (applyMgu f1 mgu) (applyMgu f2 mgu)
applyMgu (Impl f1 f2) mgu = Impl (applyMgu f1 mgu) (applyMgu f2 mgu)
applyMgu f _ = f
-- applies mgu to term
applyMguTerm :: Term -> [(Term, Term)] -> Term
applyMguTerm (Var x) [] = Var x
applyMguTerm (Var x) ((Var y, t) : rest) = if x == y then t else applyMguTerm (Var x) rest
applyMguTerm (Fun f ts) mgu = Fun f $ map (`applyMguTerm` mgu) ts
applyMguTerm t _ = t
-- takes two clauses and makes the variables of these clauses disjunct
makeVariablesDisjunct :: Clause -> Clause -> (Clause, Clause)
makeVariablesDisjunct c1 c2 = (c1', c2')
where
(used, c1') = makeClauseDisjunct [] c1
(_, c2') = makeClauseDisjunct used c2
-- takes a list of variable names and ensures that the clause does not contain these variables (by renaming), then returns all variables used in the clause + used before
makeClauseDisjunct :: [String] -> Clause -> ([String], Clause)
2023-06-08 12:53:19 +02:00
makeClauseDisjunct used (Clause clause) = (concatMap formulaVars newClause ++ newUsed, Clause newClause)
where
criticalVars = used `intersect` concatMap formulaVars clause
(newUsed, newClause) = foldr foldFun (used, clause) criticalVars
where
foldFun oldVar (used', clause') = (v' : used', Set.map (\f -> renameFormula f oldVar v') clause')
where
v' = findFresh used'
-- renames all occurences of variable v with v' in a term
renameTerm :: Term -> String -> String -> Term
renameTerm t@(Var x) v v' = if x == v then Var v' else t
renameTerm (Fun g ts) v v' = Fun g (map (\t -> renameTerm t v v') ts)
-- renames all occurences of free variable v with v' in a formula
renameFormula :: Formula -> String -> String -> Formula
renameFormula (Pred p ts) v v' = Pred p (map (\t -> renameTerm t v v') ts)
renameFormula (Neg f') v v' = Neg $ renameFormula f' v v'
renameFormula (Conj f1 f2) v v' = Conj (renameFormula f1 v v') (renameFormula f2 v v')
renameFormula (Disj f1 f2) v v' = Disj (renameFormula f1 v v') (renameFormula f2 v v')
renameFormula (Impl f1 f2) v v' = Impl (renameFormula f1 v v') (renameFormula f2 v v')
renameFormula (All y f') v v' | y /= v = All y $ renameFormula f' v v'
renameFormula (Exists y f') v v' | y /= v = Exists y $ renameFormula f' v v'
renameFormula f' _ _ = f'
-- takes two clauses and tries to unify every literal in a crossproduct, returns the set of all clauses resulting from this resolution step
resolveClauses :: Clause -> Clause -> Set Clause
2023-06-08 12:53:19 +02:00
resolveClauses (Clause c1) (Clause c2) = newClauses
where
-- first make variables in both clauses disjunct
2023-06-08 12:53:19 +02:00
(Clause c1', Clause c2') = makeVariablesDisjunct (Clause c1) (Clause c2)
zippedLiterals = [(lit1, lit2) | lit1 <- Set.toList c1', lit2 <- Set.toList c2']
newClauses = Set.fromList $ mapMaybe (\(l1, l2) -> do
-- first calculate mgu
mgu <- unifyPredicates l1 l2
-- now apply mgu to clauses without l1 and l2
let c1'' = Set.map (`applyMgu` mgu) (c1' `Set.difference` Set.singleton l1)
let c2'' = Set.map (`applyMgu` mgu) (c2' `Set.difference` Set.singleton l2)
2023-06-08 12:53:19 +02:00
return . Clause $ c1'' `Set.union` c2''
) zippedLiterals
2023-06-08 12:53:19 +02:00
cnfMember :: Set Literal -> Set Clause -> Bool
cnfMember set cnf = Set.member set $ Set.map (\(Clause c) -> c) cnf
-- a single resolution step as described in gloin
resolveStep :: CNF -> Either () CNF
2023-06-08 12:53:19 +02:00
resolveStep clauses = if Set.empty `cnfMember` clauses then Left () else Right $ clauses `Set.union` newClauses
where
-- cross product of clauses
zippedClauses = [(c1, c2) | c1 <- Set.toList clauses, c2 <- Set.toList clauses, c1 /= c2]
-- after resolveStep add every clause that can be gained through resolution to clauseSet
newClauses = setConcat $ map (uncurry resolveClauses) zippedClauses
resolveStepVerbose :: Set ([Clause], Clause) -> Either (Set ([Clause], Clause)) (Set ([Clause], Clause))
resolveStepVerbose clauses = if Set.empty `inRight` clauses then Left clauses else Right $ clauses `Set.union` newClauses
where
2023-06-08 12:53:19 +02:00
inRight clause clauses' = clause `cnfMember` Set.map snd clauses'
-- cross product of clauses
zippedClauses = [(c1, c2) | c1 <- Set.toList (Set.map snd clauses), c2 <- Set.toList (Set.map snd clauses), c1 /= c2]
-- after resolveStep add every clause that can be gained through resolution to clauseSet
newClauses = setConcat' $ map (\(c1, c2) -> ([c1, c2], resolveClauses c1 c2)) zippedClauses
setConcat' :: [([Clause], Set Clause)] -> Set ([Clause], Clause)
setConcat' [] = Set.empty
setConcat' ((cs, clauses) : rest) = Set.map (cs,) clauses `Set.union` setConcat' rest
-- do resolution until we have proven unfulfillability of formula set
doResolution :: CNF -> Either () CNF
doResolution f = do
f' <- resolveStep f
doResolution f'
doResolutionIO :: CNF -> IO (Either () (Set ([Clause], Clause)))
doResolutionIO cnf = do
let preparedCNF = Set.map ([], ) cnf
resolutionHelper preparedCNF 1
where
resolutionHelper :: Set ([Clause], Clause) -> Int -> IO (Either () (Set ([Clause], Clause)))
resolutionHelper set n = do
let setE = resolveStepVerbose set
case setE of
Left _ -> do
2023-06-08 12:53:19 +02:00
putStrLn "Resolved to empty clause!"
return $ Left ()
Right set' -> do
putStrLn $ "Resolution iteration " ++ show n ++ ":\n"
printResolvents (Set.toList $ set' `Set.difference` set)
2023-06-08 12:53:19 +02:00
putStrLn $ "Clauses after iteration:\n" ++ concatMap (\c -> showClause c ++ "\n") (Set.map snd set')
putStrLn ""
resolutionHelper set' (n + 1)
printResolvents :: [([Clause], Clause)] -> IO ()
printResolvents (([c1, c2], c) : rest) = do
putStrLn $ "Resolving " ++ showClause c1 ++ " and " ++ showClause c2 ++ "\n-> " ++ showClause c
printResolvents rest
printResolvents _ = return ()
showClause :: Clause -> String
2023-06-08 12:53:19 +02:00
showClause (Clause c) = "[" ++ intercalate ", " (map show (Set.toList c)) ++ "]"