2023-06-08 11:30:41 +02:00
{- # LANGUAGE TupleSections # -}
2023-06-07 17:36:14 +02:00
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 11:30:41 +02:00
import Data.List
import Control.Monad ( zipWithM )
2023-06-07 17:36:14 +02:00
type CNF = Set Clause
type Clause = Set Literal
type Literal = Formula
type Unifier = [ ( Term , Term ) ]
2023-06-08 11:30:41 +02:00
-- 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
-- 4. collect mgus where variables are only mapped to variables (meaning they are alpha eq)
-- 5. check if any such mgu exists
alphaEq :: Clause -> Clause -> Bool
alphaEq c1 c2 = any ( uncurry unifyLiteralList ) permutationPairs
where
permutationPairs = [ ( c1' , c2' ) | c1' <- permutations $ Set . toList c1 , c2' <- permutations $ Set . toList c2 ]
set1 :: Clause
set1 = Set . fromList [ Pred " S " [ Var " x " ] , Pred " P " [ Var " y " ] ]
set2 :: Clause
set2 = Set . fromList [ Pred " P " [ Var " z " ] , Pred " S " [ Var " y " ] ]
2023-06-07 17:36:14 +02:00
-- unification algorithm of martelli montanari
2023-06-08 11:30:41 +02:00
unify :: [ ( Term , Term ) ] -> Maybe Unifier
2023-06-07 17:36:14 +02:00
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'
2023-06-08 11:30:41 +02:00
unifyLiteralList :: [ Literal ] -> [ Literal ] -> Bool
unifyLiteralList ls1 ls2 = case zipWithM unifyLiterals ls1 ls2 of
Nothing -> False
Just mgus -> all checkSimpleMgu mgus
where
checkSimpleMgu :: Unifier -> Bool
checkSimpleMgu [] = True
checkSimpleMgu ( ( Var _ , Var _ ) : _ ) = True
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
2023-06-07 17:36:14 +02:00
-- 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
2023-06-08 11:30:41 +02:00
-- takes two clauses and makes the variables of these clauses disjunct
2023-06-07 17:36:14 +02:00
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 )
makeClauseDisjunct used clause = ( concatMap formulaVars newClause ++ newUsed , 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 11:30:41 +02:00
resolveClauses c1 c2 = newClauses
2023-06-07 17:36:14 +02:00
where
-- first make variables in both clauses disjunct
( c1' , c2' ) = makeVariablesDisjunct c1 c2
zippedLiterals = [ ( lit1 , lit2 ) | lit1 <- Set . toList c1' , lit2 <- Set . toList c2' ]
2023-06-08 11:30:41 +02:00
newClauses = Set . fromList $ mapMaybe ( \ ( l1 , l2 ) -> do
2023-06-07 17:36:14 +02:00
-- 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 11:30:41 +02:00
return $ c1'' ` Set . union ` c2''
2023-06-07 17:36:14 +02:00
) zippedLiterals
2023-06-08 11:30:41 +02:00
-- a single resolution step as described in gloin
resolveStep :: CNF -> Either () CNF
resolveStep clauses = if Set . empty ` Set . member ` 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
inRight clause set = clause ` Set . member ` Set . map snd set
-- 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
2023-06-07 17:36:14 +02:00
-- do resolution until we have proven unfulfillability of formula set
doResolution :: CNF -> Either () CNF
doResolution f = do
f' <- resolveStep f
2023-06-08 11:30:41 +02:00
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
putStrLn " Resolved to empty clause, so formula is proven! "
return $ Left ()
Right set' -> do
putStrLn $ " Resolution iteration " ++ show n ++ " : \ n "
printResolvents ( Set . toList $ set' ` Set . difference ` 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
showClause c = " [ " ++ intercalate " , " ( map show ( Set . toList c ) ) ++ " ] "