LAB 2
due midnight, Friday, Oct 18


For this assignment, you must take input from two separate files. The first file will contain a formula, and the second file will contain a list of variable assignments for that formula. You will output whether the variable assignments cause the formula to be satisfied (to be a tautology), falsified (to be a contradiction), or neither (to be a contingency). You will also output some other statistics about the formula, but I will tell you about that later. First, let me tell you more about your input files.

From cnf.formula you will take a Boolean formula in three-element Conjunctive normal form (3CNF), which is also known as a formula in product-of-sums form (POS)where each sum has no more than three elements. Each disjunction (or sum) is known as a clause. In this formula, the variables will be represented by numbers between 2 and 100 (we will reserve the varable one for a distinguished variable that is always true, and we will reserve 0 for an unbound variable). When a variable is negated, it will be prepended by a minus sign.

This is a sample cnf.formula file:

(2 + 5 + -3) * 
(3 + -4 + 2) * 
(3 + 2)
This is a 3CNF formula with three clauses and four variables. The first two clauses have three elements, and the last has only two. In the first clause, we have the disjunction of the variable represented by 2, the variable represented by 5, and the negation of the variable represented by 3.

Note that this formula is a contingency. We don't know if the formua is true or false until we have a set of assignments for the variables. This is where the second input file comes in. From var.bind you will take a set of assignments for the variables from 2 to 100. Not all the assignments will be specified, but those that are specified will be in order. We will call this set of assignments a binding.

Here is a sample var.bind file:

2 = F
3 = F
5 = T
Now that we have a formula and a binding, we can evaluate the truth value of the formula from our example. Now we want to print out the truth value, the number of clauses satisfied, the number falsified, and the number that are indeterminate. Here is a sample set of output:
Formula falsified.
Satisfied clauses: 1
Falsified clauses: 1
Indeterminate clauses: 1
Once again thinking in terms of Abstract Data Types, you will be implementing a formula ADT. The formula ADT will need a CNF clause ADT and a binding ADT. Make sure each ADT is implemented with a separate set of .c and .h files. Since we are going to use all this to build a Boolean satisfier, your solution to this problem has certain restrictions that would not be apparent just from the problem description I gave you in the first part of this handout. Read on and things will become more clear.

First, let me tell you what a Formula is for our purposes: A formula is a list of 3CNF clauses and a (possibly empty) binding. To be terribly anthropomorphic, one of the things that formulas do is to Evaluate their clauses under their given binding and report statistics on the evaluation. Next time we will add a Boolean satisfier ADT that will operate on a formula with an empty binding and set the binding to an interesting value, but we will learn all about that for the next assignment. Among the operations your formula ADT will need are Create, which returns an empty formula with an empty binding, Delete, which destroys the formula on which it is called, and the previously mentioned Evaluate. Your Evaluate operator will go through its list of clauses and ask each one to Evaluate itself and return the appropriate information. If every clause Evaluates to true, the formula is satisfied. If any clause Evaluates to false, the formula is falsified, and if any clause is indeterminate (and non is false), so is the whole formula.

For our purposes, a binding is a set of variable assignments. I would suggest that you implement a binding as an array of 100 elements (I picked the upper bound to make your life easier). Among the operations your binding ADT will need are Create, which returns an empty binding, and Delete, which destroys the binding on which it is called. An empty binding has every variable unbound. I suspect you will also want an operation that Binds a variable to a given value, and one that Unbinds a variable, but the necessity for these operations may not be really clear to you until the next assignment.

In the suggested implementation, that would mean that the contents of each array element will be 0. When the variable is eventually bound to true, you will put a 1 in the array, and when it is bound to false, you will put a -1 in the array. Yes, I know it is odd to use -1 for false, but we really do need three values: true, false, and unbound. When we build our satisfiers, we will probably find it is useful to put integers other than -1, 0, and 1 in the array, but once again I will leave that until the next assignment.

Now we come to the ADT that has the strange restrictions: the clause ADT. For crucial reasons that will become very clear in the next assignment, you need to be able to able to remove a given clause from a formula in constant time---no matter where it is in the formula data structure. In effect, this means that you can't just implement your list of clauses with a standard list ADT. I require that you you use this definition of a clause ADT: A clause contains three literals (though one or more of the literals may be false, which is represented by -1 in our world), a right neighbor, and a left neighbor. This means that your formula will contain a ring of clauses. Eventually, we will actually want four separate rings of clauses in a formula, but we won't need that until we have a satisfier (the rings will contain clauses with the same number of unbound variables, so Ring 3 would have only clauses with 3 unbound variables, Ring 2 would have clauses with 2 unbound variables, Ring 1 would have clauses with 1 unbound variables, and Ring 0 would have clauses with no unbound variables).

We are using doublely-linked lists here, and they are nasty items if you don't treat them with respect. The first thing you do is you write an operator that will Link in a new clause, and an operator that, given a pointer to a given clause, will Delink the clause, and once you write those procedures you never ever modify the left neighbor or right neighbor links outside of those operators. In order to Delink, you need to:

  1. Go to your left neighbor, set its right neighbor pointer to your right neighbor pointer.
  2. Go to your right neighbor, set its left neighbor pointer to your left neighbor pointer.
  3. Finally, and optionally, set your left and right neighbor pointers to NIL.
You now have a clause that is Delinked: if you want to save it, you better Link it up somewhere else. If you want to throw it on the floor, you better call your trusty Delete operator. You will also need a Create operator that takes three literals and returns a pointer to the new clause. Once you create it, you call Link to put it into the ring of clauses. For now, you can just put it anywhere in the ring, but eventually Link will put it in the "right place."

Now let's talk about your main program. Your main program will open cnf.formula, Create a formula with the appropriate clauses, close cnf.formula, open var.bind, Create an empty binding and then Bind every variable that is assigned in var.bind, close var.bind, and then have the formula Evaluate itself. Once you output the result of all that binding, you are done.

It is crucial for this assignment that your program be well structured. You will have files prog.c, formula.c, formula.h, clause.c, clause.h, binding.c, binding.h and an appropriate makefile; if you want to have a separate doubly-linked-list ADT, that's fine too. Make sure you make a new directory that will contain all these files. By the way, if your implementation leads to your segmenting your files even further, that is just fine (as long as you have a logical organization), but you must have, in addition to your main program, a formula ADT, a clause ADT, and a binding ADT.



larrabee@soe.ucsc.edu