Safe Haskell | None |
---|---|
Language | Haskell2010 |
Fifth assignment for IB016, semester spring 2019.
Effective Transformation of a Logical Formula to Conjunctive Normal Form
In this homework you work will be to implement an effective tranformation of a propositional logic formula to Conjunctive Normal Form (CNF). You probably know from the basic logic course that for every propositional formula there is an equivalent formula in CNF. The problem with the basic transformation is that there can be an exponential blowup, i.e. the size of the resulting formula can be exponential compared to the size of the original formula. This is very impractical for example for SAT solvers (which are algorithms which aim at checking if a formula is satisfiable and try to be efficient for large classes of propositional formulas): these solvers usually require input formula to be in CNF and their runtime can be up to exponential in the size of the input formula. Therefore a SAT solver with naive conversion to CNF would have double exponential blowup.
There is another approach to conversion to CNF: we can create a formula in CNF which is satisfiable if and only if original formula was satisfiable (i.e. is equisatisfiable) and which has only a linear overhead. The trick is to use auxiliary variables in the transformation to represent subformulas of the original formula. This transformation is called Tseytin transformation and is described for example on Wikipedia: https://en.wikipedia.org/wiki/Tseytin_transformation. We do not require that you use Tseytin transformation in particular, however, your transformation must guarantee that the size of the resulting formula is linear with respect to the size of the original formula.
Your goal will be to implement functions eval
and getVariables
for boolean
formulas represented by a tree data type, as well as those represented by CNF
(which is basically a list of lists of literals). Furthermore, you will be
required to implement toCNF
which converts a tree-represented formula to CNF.
Finally there is a small helper function eval'
which you should also
implement. Your implementation should use monad transformers, see later.
There are some tests prepared for you, feel free to write your own tests on top
of these.
Monad Transformers
In implementing the required functionality you are strongly advised to use monad transformers.
eval
: this function operates withinMonadReader
andMonadError
and therefore you must use their functionality in the implementation.getVariables
in the version forFormulaTree
: think about a good use ofMonadWriter
/Writer
in this function. You are strongly advised to use it (and it will be required to get full points).toCNF
: think about which monads/monad transformers will be useful for this task. There are several aspects of the conversion to CNF which map well to different monads/transformers shown in the class. You will still get some points if you don't use any of them, but your work will probably be harder then if you choose your monads well.
Tips & Tricks
- Think before implementing. Especially about monads and transformers shown on the 10th & 11th lecture.
- Have a look at the mtl package.
- Use ghcid (or an IDE with similar functionality) for type checking & testing:
ghcid -c 'ghci -Wall' -T testAll HW05.hs
-- this will re-compile your solution every time you change it, and show you either compilation errors/warnings or test results. Ghcid can be installed either from your distribution's package if you have it, or from cabal. - Use typed holes when you don't know the type of an subexpression which you need to write.
- Don't forget to use
hlint
& check compilation on aisa/ghc 8.6.
Notes on Used Language Extensions
There are several language extensions enabled in this homework. If you want to enable some more, please ask in discussion forum.
FlexibleContexts
-- this allows usage of concrete types (with kind*
) as parts of the context (e.g. inMonadReader
Valuation
mValuation
cannot be used without this extension).InstanceSigs
-- allows you to specify explicitly a type of a function in an instance definition.ScopedTypeVariables
-- this, together withExplicitForAll
which it implies, allows you to use type variables from the functions' type in its nested functions, e.g.:foo :: forall m a. Monad m => (a -> m a) -> m [a] foo = ... where bar :: a -> m a bar = ...
Here
m
anda
in definition ofbar
represent the same type as infoo
(while normally, this would define a polymorphicbar
and thereforem
would not need to be a monad).TemplateHaskell
is used for generation oftestAll
function.GeneralizedNewtypeDeriving
allows us to lift any instance available on some type tonewtype
s over this type (see e.g.CNF
,Clause
).
Modules and packages
You can use any module from packages base, containers, and mtl (which includes transformers). If you wish so, you can also use Unicode syntax from unicode-prelude.
Synopsis
- class Formula f where
- eval :: (MonadError String m, MonadReader Valuation m) => f -> m Bool
- getVariables :: f -> Set Varname
- data Valuation
- insert :: Varname -> Bool -> Valuation -> Valuation
- type Varname = String
- eval' :: Formula f => f -> Valuation -> Either String Bool
- class Pretty a where
- data FormulaTree
- data Op
- data Literal
- newtype Clause = Clause {
- getLiterals :: [Literal]
- newtype CNF = CNF {
- getClauses :: [Clause]
- toCNF :: FormulaTree -> CNF
- testAll :: IO Bool
- anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool
- foldMapM :: (Monad m, Monoid b) => (a -> m b) -> [a] -> m b
Documentation
class Formula f where Source #
Class for formulas which can have different representation.
eval :: (MonadError String m, MonadReader Valuation m) => f -> m Bool Source #
A formula can be evaluated in a monad which allows reading of valuations and reporting of string errors.
getVariables :: f -> Set Varname Source #
Get all free variables used in the formula.
Instances
Formula CNF Source # | |
Formula FormulaTree Source # | Evaluation should be strict in the sense that it should produce an error
if any free variable in the formula is not defined in the valuation. This
should be true even for variables used in definition of unused Hint: you can use |
Defined in HW05 eval :: (MonadError String m, MonadReader Valuation m) => FormulaTree -> m Bool Source # getVariables :: FormulaTree -> Set Varname Source # |
A valuation is a (partial) mapping from variable names to boolean values.
eval' :: Formula f => f -> Valuation -> Either String Bool Source #
Evaluate a formula, returning either error message or result.
This function is derived from eval'
. It is mostly a helper to for
interactive evaluation of formulas.
Note: pay attention to instances of MonadError
, find the one best suited
for this.
Things which have nice human-readable representation.
Instances
Pretty Valuation Source # | |
Pretty CNF Source # | |
Pretty FormulaTree Source # | A pretty-printer for formulas, the scope of |
Pretty Op Source # | Human-readable representation of binary operators. |
data FormulaTree Source #
A tree representation of formula.
Let
can be used to name a subtree and re-use this name multiple times.
We say that variable v
is free in the formula if it is used in it, and
there is at least one use which is not inside a scope of a let
subexpression. E.g. in formula (a && let x = b || c in x && !b)
variables
a
, b
, c
are free, while x
is not free. Note that the scope of
a let
-bound variable is only in the in
part of let
. E.g. in the
formula (let x = x in x)
is equivalent to (let y = x in y)
, i.e. there
are two distinct variables named x
.
Instances
Eq FormulaTree Source # | |
Defined in HW05 (==) :: FormulaTree -> FormulaTree -> Bool # (/=) :: FormulaTree -> FormulaTree -> Bool # | |
Read FormulaTree Source # | |
Defined in HW05 readsPrec :: Int -> ReadS FormulaTree # readList :: ReadS [FormulaTree] # readPrec :: ReadPrec FormulaTree # readListPrec :: ReadPrec [FormulaTree] # | |
Show FormulaTree Source # | |
Defined in HW05 showsPrec :: Int -> FormulaTree -> ShowS # show :: FormulaTree -> String # showList :: [FormulaTree] -> ShowS # | |
Arbitrary FormulaTree Source # | |
Defined in HW05 arbitrary :: Gen FormulaTree shrink :: FormulaTree -> [FormulaTree] | |
Pretty FormulaTree Source # | A pretty-printer for formulas, the scope of |
Formula FormulaTree Source # | Evaluation should be strict in the sense that it should produce an error
if any free variable in the formula is not defined in the valuation. This
should be true even for variables used in definition of unused Hint: you can use |
Defined in HW05 eval :: (MonadError String m, MonadReader Valuation m) => FormulaTree -> m Bool Source # getVariables :: FormulaTree -> Set Varname Source # |
Boolean binary operations used in formulas.
Note that the Enum
instance is useful for QuickCheck.
A literal is either a (positive) variable, or negation of variable.
A Clause is a disjunction of literals.
Note that we can derive monoid for newtypes over monoids thanks to
the GeneralizedNewtypeDeriving
extension (it allows us to "lift" any
instance from a base type to a newtype over the given type).
Clause | |
|
A CNF formula is a conjunction of Clauses.
CNF | |
|
toCNF :: FormulaTree -> CNF Source #
Convert a formula to CNF (Conjunctive Normal Form) with linear overhead (i.e. the length of the result must have linear size with respect to the length of the input). Use for example https://en.wikipedia.org/wiki/Tseytin_transformation.
You should use an appropriate (combination of) monads/monad transformers in the solution. You can expect some points for solution without reasonable use of them, but not too much.
The runtime of the conversion should be in O(n log n)
where n
is the
length of the input formula (hint: think about let
bindings).
You can assume that the original formula does not contain any variables
with aux
prefix.
Tests
Run all tests named prop_*
. Hint: install ghcid (either from your distro,
or using cabal) and run ghcid -c 'ghci -Wall' -T testAll HW05.hs
.
It will show compiler errors and warnings if there are any, and test results
otherwise, automatically rerunning it if you change this source.