Lab01
Unicode input: type "\varphi" to obtain φ, "\rho" for ρ, and similarly for other mathematical characters.
Imports and utility functions
import Data.Listimport Control.Monadimport Test.QuickCheckimport System.IO.Unsafe-- updating a functionupdate :: Eq a => (a -> b) -> a -> b -> a -> bupdate ρ x v = \y -> if x == y then v else ρ y-- useful for debuggingdebug :: Show a => String -> a -> adebug str x = seq (unsafePerformIO $ do putStr "<"; putStr str; putStr ": "; print x; putStr ">") xtodo :: atodo = undefinedSyntax
We define an inductive type for formulas of first-order logic.
For propositional logic we will only use atomic formulas of the form "Rel RelName []", i.e., zero-ary relations, and no quantifiers. It's convenient to have here a more general definition since later we will use it for first-order logic.
-- propositional variable names are just stringstype PropName = Stringdata Formula = T | F | Prop PropName -- atomic formulas | Not Formula | And Formula Formula | Or Formula Formula | Implies Formula Formula | Iff Formula Formula deriving (Eq, Show)We introduce a binary operator syntax:
infixr 8 /\, ∧(/\) = And(∧) = And -- input with "\and"infixr 5 \/, ∨, ==>(\/) = Or(∨) = Or -- input with "\or"(==>) = Impliesinfixr 4 <==>, ⇔(<==>) = Iff(⇔) = Iff -- input with "\lr"Example formulas:
p, q, r, s, t :: Formulap = Prop "p"q = Prop "q"r = Prop "r"s = Prop "s"t = Prop "t"satisfiable_formulas = [ p ∧ q ∧ s ∧ p, T, p, Not p, (p ∨ q ∧ r) ∧ (Not p ∨ Not r), (p ∨ q) ∧ (Not p ∨ Not q) ]unsatisfiable_formulas = [ p ∧ q ∧ s ∧ Not p, T ∧ F, F, (p ∨ q ∧ r) ∧ Not p ∧ Not r, (p ⇔ q) ∧ (q ⇔ r) ∧ (r ⇔ s) ∧ (s ⇔ Not p) ]Random generation of propositional formulas (for testing)
instance Arbitrary Formula where arbitrary = sized f where f 0 = oneof $ map return $ [p, q, r, s, t] ++ [T, F] f size = frequency [ (1, liftM Not (f (size - 1))), (4, do size' <- choose (0, size - 1) conn <- oneof $ map return [And, Or, Implies, Iff] left <- f size' right <- f $ size - size' - 1 return $ conn left right) ]Semantics (EXERCISE)
Define the semantic function for propositional logic. Hint: proceed by structural induction on formulas (ignoring the cases corresponding to quantifiers and relations of >0 arity).
-- truth valuationstype Valuation = PropName -> Bool-- the evaluation functioneval :: Formula -> Valuation -> Booleval T _ = Trueeval F _ = todoeval (Prop p) ρ = todoeval (Not φ) ρ = not (eval φ ρ)eval (And φ ψ) ρ = todoeval (Or φ ψ) ρ = todoeval (Implies φ ψ) ρ = todoeval (Iff φ ψ) ρ = todoeval _ _ = error "not a propositional formula"Tests for the evaluation function:
ρ0 = const Trueρ1 = const Falseρ2 = update ρ0 "p" Falsetest_eval = eval (p ∧ Not p) ρ0 == False && eval (p ∧ Not p) ρ1 == False && eval (p ∨ q) ρ2 == TruequickCheck test_eval-- check that the eval function is efficient-- ifformula 3 == Iff (Iff (Iff T T) T) Tifformula :: Int -> Formulaifformula 0 = Tifformula n = Iff (ifformula (n-1)) T-- this should evaluate within a fraction of secondtest_eval2 = eval (ifformula 23) (const True) == TruequickCheck test_eval2Satisfiable formulas
List of variables appearing in a formula:
variables :: Formula -> [PropName]variables = nub . go where go T = [] go F = [] go (Prop p) = [p] go (Not φ) = go φ go (And φ ψ) = go φ ++ go ψ go (Or φ ψ) = go φ ++ go ψ go (Implies φ ψ) = go φ ++ go ψ go (Iff φ ψ) = go φ ++ go ψ go _ = error "not a propositional formula"A trivial SAT solver based on truth tables:
type SATSolver = Formula -> Bool-- the list of all valuations on a given list of variablesvaluations :: [PropName] -> [Valuation]valuations [] = [undefined]valuations (x : xs) = concat [[update ρ x True, update ρ x False] | ρ <- valuations xs]satisfiable :: SATSolversatisfiable φ = or [eval φ ρ | ρ <- valuations (variables φ)]Tautologies (EXERCISE)
Write a program that checks whether a given input formula is a tautology. Hint: Reduce to satisfiability.
tautology :: Formula -> Booltautology φ = todoNormal forms
Negation normal form (EXERCISE)
A formula of propositional logic is in negation normal form (NNF) if the only connectives are true (T), false (F), conjunction (And), disjunction (Or), and negation (Not), and moreover negation is only applied to atomic formulas:
is_nnf :: Formula -> Boolis_nnf T = Trueis_nnf F = Trueis_nnf (Prop _) = Trueis_nnf (Not (Prop _)) = Trueis_nnf (And phi psi) = is_nnf phi && is_nnf psiis_nnf (Or phi psi) = is_nnf phi && is_nnf psiis_nnf (Implies phi psi) = Falseis_nnf (Iff phi psi) = Falseis_nnf (Not _) = Falseis_nnf _ = error "not a propositional formula"quickCheck $ is_nnf (Not p ∧ (q ∨ (r ∧ s))) -- NNF example && (not $ is_nnf $ Not (p ∨ q)) -- NNF non-exampleWrite a function that turns an arbitrary formula to a logically equivalent one in NNF. What is the complexity of the NNF translation? Hint: Proceed by structural induction on formulas:
express Implies in terms of Not and Or,
express Iff in terms of And, Or, and Not, and
push negation inside the formula with De Morgan's laws.
nnf :: Formula -> Formulannf = todoTests:
prop_nnf :: Formula -> Boolprop_nnf φ = let ψ = nnf φ in is_nnf ψ && (tautology $ φ ⇔ ψ)quickCheck prop_nnfDisjunctive normal form (EXERCISE)
A literal is either a propositional variable, or the negation of a propositional variable:
data Literal = Pos PropName | Neg PropName deriving (Eq, Show, Ord)literal2var :: Literal -> PropNameliteral2var (Pos p) = pliteral2var (Neg p) = pA clause is a conjunction of literals. A formula of propositional logic is in disjunctive normal form (DNF) if it is a disjunction of clauses. It is customary to represent DNF formulas as lists of lists of literals:
type DNFClause = [Literal]type DNF = [DNFClause]dnf2formula :: [[Literal]] -> Formuladnf2formula [] = Fdnf2formula lss = foldr1 Or (map go lss) where go [] = T go ls = foldr1 And (map go2 ls) go2 (Pos p) = Prop p go2 (Neg p) = Not (Prop p)Write a function that turns an arbitrary formula to a logically equivalent one in DNF. What is the complexity of the DNF translation? Hint: Convert the formula to NNF first, then proceed by structural induction on NNF formulas (analogy with polynomial multiplication).
dnf :: Formula -> DNFdnf = todoTests:
test_dnf :: Formula -> Booltest_dnf φ = tautology $ φ ⇔ (dnf2formula (dnf φ))quickCheckWith (stdArgs {maxSize = 18}) test_dnfConjunctive normal form
The conjunctive normal form (CNF) is entirely dual to the DNF. In the next lab we will see how to convert a propositional formula to an equisatisfiable one in CNF, which is sufficient for SAT solving.
DNF-based SAT solver (EXERCISE)
Write a SAT solver based on the following recipe:
Convert the input formula to DNF.
Find whether there exists a satisfiable clause.
Hint: A clause is satisfiable iff it does not contain the same literal both positively and negatively.
What is the complexity of this SAT solver compared to the one based on truth tables? Is it potentially more efficient on satisfiable or on unsatisfiable instances?
sat_dnf :: SATSolversat_dnf = todoTests:
prop_sat_dnf :: Formula -> Boolprop_sat_dnf phi = sat_dnf phi == satisfiable phiquickCheckWith (stdArgs {maxSize = 20}) prop_sat_dnftest_solver :: SATSolver -> Booltest_solver solver = and $ map solver satisfiable_formulas ++ map (not . solver) unsatisfiable_formulasquickCheck (test_solver sat_dnf)stack install QuickCheck