Updated: 10/21/03; 12:58pm
In this assignment, you are going to design and program a theorem prover that uses the resolution inference rule. Your theorem prover will run until it finds the empty clause or until it can generate no more new clauses. You are to use the Python language to code your theorem prover.
The Task
Your input will comprise fully-parenthesized First-Order logic clauses in conjunctive normal form (CNF). In CNF, the "and"s are suppressed, and only disjunctions and negations appear. The specific knowledge-base (KB) your program will be using is as follows:
The statement to be proved is (S A). In its CNF form, with the negated (S A) inserted (for a proof by contradiction), the KB is:
((not (P ?x)) (Q ?x))
((P ?y) (R ?y))
((not (Q ?z)) (S ?z))
((not (R ?t)) (S ?t))
((not (S A)))
While this is the specific KB we will be using, your program must be written so as it can handle any arbitrary KB in CNF.
Variables in the first-order logic statements (y, z, and t) are converted (by hand) by placing "?" in front of them. This should make it easier for your program to detect variables in the clauses. Distinct clauses will always contain variables with distinct variable names, variable names not used in other clauses.
Each line (wff) in the original KB is implicitly conjoined to the other lines (wffs). Note also that the disjunctions have been suppressed within the lines. Because we can (and will) program our theorem prover to implicitly know that (not (P ?x)) and (Q ?x) are disjoined (or'ed) together in the first line of the KB above, we do not have to explicitly write an "or". This will make processing these KB's easier. In general, each clause of the KB will contain a list:
(t1 t2 t3 ... tK1), where each of the t's are the terms of the clause (items that are implicitly disjoined), and where K1 is the number of disjoined terms in the particular clause. Each of the terms will be a predicate usage or a negated predicate usage.
Since we are doing proof by contradiction, your KB will also contain the negated form of the wff you are trying to prove. For example, our KB also contains the clause:
((not (S A)))
The output of your program should indicate whether or not the proof could be found. That is, if a contradiction was found. In this case, we should be able to conclude that the non-negated statement was true. The following is an example output from my program, obtained by running my theorem prover with the KB as given above. Note that the proof is found-- the last clause obtained is the empty clause.
Output
chris% python resolution.py
KB Contents:
(0, [['not', ['P', '?x']], ['Q', '?x']])
(1, [['P', '?y'], ['R', '?y']])
(2, [['not', ['Q', '?z']], ['S', '?z']])
(3, [['not', ['R', '?t']], ['S', '?t']])
(4, [['not', ['S', 'A']]])
resolve: 0), 1): new clause= (5, [['Q', '?0'], ['R', '?0']]); binding set= {'?y': '?x'}
resolve: 0), 2): new clause= (6, [['S', '?1'], ['not', ['P', '?1']]]); binding set= {'?x': '?z'}
resolve: 1), 3): new clause= (7, [['P', '?3'], ['S', '?3']]); binding set= {'?y': '?t'}
resolve: 2), 4): new clause= (8, [['not', ['Q', 'A']]]); binding set= {'?z': 'A'}
resolve: 3), 4): new clause= (9, [['not', ['R', 'A']]]); binding set= {'?t': 'A'}
resolve: 0), 7): new clause= (10, [['Q', '?8'], ['S', '?8']]); binding set= {'?3': '?x'}
resolve: 0), 8): new clause= (11, [['not', ['P', 'A']]]); binding set= {'?x': 'A'}
resolve: 1), 6): new clause= (12, [['R', '?11'], ['S', '?11']]); binding set= {'?y': '?1'}
resolve: 1), 9): new clause= (13, [['P', 'A']]); binding set= {'?y': 'A'}
resolve: 5), 8): new clause= (14, [['R', 'A']]); binding set= {'?0': 'A'}
resolve: 5), 9): new clause= (15, [['Q', 'A']]); binding set= {'?0': 'A'}
resolve: 6), 7): new clause= (16, [['S', '?19'], ['S', '?19']]); binding set= {'?3': '?1'}
resolve: 2), 15): new clause= (17, [['S', 'A']]); binding set= {'?z': 'A'}
resolve: 8), 15): new clause= []; binding set= {}
Found contradiction; made proof!
The output of your program should include the information in the example above. When your program starts, it should output the current contents of the KB. That is, the contents of the KB prior to starting the resolution process. For convenience in reading and comprehending the proof, your clauses should be numbered. I have represented my clauses as pairs (two-tuples) in Python:
(clause#, termList)
I also have a function addToKB(termList) which accepts a term list as an argument, creates a new clause number, and adds this new clause into the KB. Probably the easiest way to access the KB is as a global variable. I also access the current (next) clause number as a global variable. There should only be a few functions (e.g., addToKB) in which you need to access these global variables. Try to minimize the number of functions in which you access these global variables. At each step in the proof, when your theorem prover finds two clauses that it can resolve (and ensures the resolvent clause is novel; see below), it should output:
a) the clause numbers of the two clauses involved,
b) the new clause generated by the resolution (i.e., the resolvent clause), and
c) the binding set that results from unification of the terms that are involved in the resolution.
Representation, Algorithms, and Design
It is probably best to represent the term list of each clause as a Python list. Each variable, predicate name, constant name, or function name will be a string. This makes typing the clauses by hand somewhat tedious, but accessing elements of the term lists becomes relatively straightforward in Python.
This program is reasonably complex. The main components are:
1) Variable and binding set operations including three functions:
a) Unify(wff1, wff2): unification
b) Substitute(bindingSet, wff): substitution using binding sets
c) RenameVariables(wff): variable renaming
2) Resolution
As your first step, you are required in this assignment to design the algorithms that are going to be used. A unification algorithm is described on page 278 (Figure 9.1) of the course text. Your main algorithm design challenge is to structure the resolution algorithm. At each step of processing in the theorm prover, your resolution algorithm should attempt to resolve the KB with itself. That is, it should attempt to resolve each clause of the KB with every clause of the KB (including itself; clauses may resolve with themselves-- note that in this case, variable names will be the same across formulas when you attempt unification-- that is OK in this case).
This program is harder than the previous programs. Get started on it early!
When your resolution algorithm finds two clauses that resolve together, and before you add a new clause to the KB, you need to check if the term list that results from this resolution is already present in another clause in the KB. Additionally, in order to ensure that clauses are represented in standard order, every time your program generates a new term list (from a resolution) the terms within the term list should be sorted (list sort method). Don't sort the predicates, functions etc. within a term, but sort the terms within term lists for new clauses. In general, two clauses are equivalent if (when they are in a standard, sorted order), they are of the same length and the term lists of the two clauses unify.
Distinct clauses need to have independent sets of variable names. Therefore, when you resolve two clauses, after performing the substitution, you will need to rename the variables within clauses. In order to do this, you will need to generate new, unique, variable names. It is likely that you will have to use a global variable -- to create new, unique variable names. E.g., create unique variable names of the form "?<n>" where n is an integer from 0, 1, ... etc. A global variable will be needed to keep track of the current "n".
Your unification algorithm needs to discriminate between three possible results of unifying two logic sentences: (1) the sentences resolve and have a non-empty binding set, (2) the sentences resolve and have an empty binding set, and (3) the sentences don't resolve. I return 'nix from my unification function when the sentences don't resolve. A good way to represent a binding set in Python is with a dictionary. E.g., {'?3': '?1'} is a binding set that says that the variable "?3" should be replaced with "?1".
You need to test each of your functions incrementally as you write the Python code for this problem. For example, you need to turn in the results of testing your functions: Unify, Substitute, RenameVariables.
You should run your theorem prover on a few KB's aside from the main task. For example:
chris% python resolution.py KB Contents: (0, [[['not', ['P', '?m']]], [['P', '?m']]]) Could not prove, no novel resolvent clauses generated.(Note that the above KB does generate resolvent clauses, but they are not novel and thus are not added to the KB or displayed.)Of course, you also need to turn in results of testing your program on the main task as specified above.
Don't bother trying to check the syntax of the clauses for correctness. Assume that the clauses you are using as input to your program have correct syntax. (Of course, when you are typing the clauses, make sure that you have typed them correctly!).
1) Design. You must submit a 2-4 page (typewritten, double spaced) document describing the algorithms and design of your program. Diagrams can be included in this document.
2) Testing. You must submit the results of testing (hardcopy) at least your Unify, Substitute, RenameVariables, and theorem prover functions on the main task.
3) Proof of main task. Draw the proof tree (by hand is fine) for the main task, as solved by your program. You don't need to write down all the resolutions, only the ones that actually lead to the contradiction.
4) Documentation. This is the most complex program that we've had in this class. More points than previously will be assigned to documenting of functions, and documenting program code in this assignment. Write your documentation before you write your code, or as you are writing your code. Learn the mantra: Commenting before helps me understand my own program!
Turn In & Demo
Your design must be submitted by 4 November 2003.
For the final due date (11 November 2003), you need to turn in a revised design document (that updates it according to the final design of your algorithms), hard copy of your testing, the proof tree, and hard copy listings of your program. You also need to do a demo of your program to the class TA on or before the due date. You should demonstrate that your program finds the proof to the problem. You should also email your program file(s) to the course TA (Prashant Jain).