COMP 590-096 Fall 2010
Assignment 3: Logic programming
Due date: Monday, November 22, 11:59 PM
Submission Instructions
The goal of this assignment is to implement a very basic Prolog interpreter
and apply it to several logic programs. You will need to turn in the following:
- A report in PDF or Word Document format.
The report should describe your implementation, including pseudocode or figures if
necessary for understanding what you did. As in the previous assignment, your report
should focus on the most "interesting" aspects of your solution, i.e., any non-obvious
implementation choices and what you had to do to get the interpreter to work.
Your report should be self-contained and it should make it possible for us to
evaluate your solution without having to run your source code. Please don't forget
to include in your report all the results that are requested in the individual
problem instructions below. You will not get credit for results
not included in the report.
The name of the report file should be lastname_firstname_assignment3.pdf or lastname_firstname_assignment3.doc.
- Your source code and execution traces
(see below for instructions) compressed to a single ZIP file. As before,
your code should be well commented, and it should be easy to see the correspondence between
what's in the code and what's in the report. You don't need to include executables
or various supporting files (e.g., utility libraries) whose content is irrelevant to the
assignment. If we find it necessary to run your code in order to evaluate
your solution, we will get in touch with you.
The name of the code archive should be lastname_firstname_assignment3.zip.
Submit your report and code files through Blackboard using the same instructions as in
Assignment 1.
Late policy: You lose 25% of the points for every day the assignment is late.
If you have a compelling reason for not being able to submit the assignment on time
and would like to make a special arrangement, you must let me know ahead of the due date.
Policy on outside sources: Feel free to consult the
Web in order to learn more about Prolog and to see examples of Prolog programs or
advice on implementing a Prolog interpreter. However, DO NOT submit somebody else's
code wholesale as your own work. As usual, whenever you use outside code or references,
please be sure to acknowledge them in your report.
Assignment Instructions
As stated above, the goal of this assignment is to implement an interpreter with
backward chaining inference for a bare-bones
subset of Prolog (see this lecture and Chapter 9
of the textbook for an introduction).
Here is an example of the kind of input your interpreter should be able to handle:
Program (KB):
evil(X) :- king(X), greedy(X).
king(john).
greedy(Y).
Query:
evil(john).
The following is a more detailed description of the language syntax:
- The program consists of clauses and atomic sentences.
Each clause has the format
head_pred(head_arg_list) :- pred_1(arg_list_1), ..., pred_n(arg_list_n).
and each atomic sentence has the format
pred(arg_list).
Each arg_list is a comma-separated list of constants,
variables, or lists (see below for details).
- We will use the convention that variable names start with uppercase letters
(X, Y11, Z222, etc.), while predicate names and constants
are lowercase strings (king, john, etc.).
- A list is enclosed in square brackets and the elements are separated by commas.
For simplicity, we will assume that list elements can only be numbers, e.g., [1,2,3,4].
The empty list is denoted []. A single-element list is denoted [H]. A list with first element
H and tail T is denoted by [H|T]. For example, if you're trying to unify
[1,2,3,4] with [H|T], then H = 1 and T = [2,3,4].
Detailed Implementation Instructions
- Your interpreter should begin by reading in the program from a file or command line,
parsing it, and storing it into a KB data structure. Then it should read in each query
in turn, parse it, run backward chaining to find all the answers, and display them
at the output.
See this file and this file
for example traces of queries from Program 1 and Program 3 below.
- Your first step should be to write a parser for Prolog clauses.
Use whatever data structures you see fit. Please note that
your parser does not need to be robust to syntax errors. That is, you
can assume that every sentence given to your interpreter is well-formed. You can even
make restrictive assumptions about the format of the strings down to
the number of spaces between predicates and arguments, if it makes your life easier.
There is no need to implement any features of Prolog not present in the
following programs.
- For the overall structure of the backward chaining algorithm, follow the pseudocode from
the lecture notes.
- Standardizing apart: when you take a rule from the KB and want to apply it to one of
your goals, you need to standardize it apart, or rename all the variables to
previously unused names. For example, if you want to prove evil(john), you would
take the rule evil(X) :- king(X), greedy(X) and standardize it apart by renaming
the variable X into, say, X1, to get evil(X1) :- king(X1), greedy(X1)
The simplest way of generating new variable names is to keep a numeric global "index" variable.
Every time you take a new rule from the KB, you would append that index to its variable names,
and increment it for next time.
- Unification: In order to apply a rule from the KB to one of your goals, you need
to unify that rule with the goal, or generate a substitution of variables that makes
the rule and the goal the same. To implement unification, you can follow the pseudocode given
in Figure 9.1 of the textbook (p. 328 in the 3rd edition and p. 278 in the 2nd edition).
However, keep in mind that you do not need to implement a fully general unification routine.
In particular, you do not need to include the Occur-Check (in fact, most Prolog interpreters
omit it).
- Substitution: When applying a substitution to a goal, you should be able to deal
with composition. For example, a substitution may look
someting like X = [H1|L1], H1 = 1, L1 = [H2|L2], H2 = 2, L2 = [H3|L3], H3 = 3, L3 = [].
Your substitution routine should be able to figure out that X = [1,2,3].
General Suggestions
- To get a better understanding of backward chaning, unification, and substitution,
you may want to manually work through a few simple examples, like Program 1 below.
- Most of your effort will go not into implementing backward chaining, but into
the various subroutines, including parsing, substitution, and unification.
Implementing these operations may be bug-prone, so you should take care, give yourself
plenty of time, and approach debugging systematically.
- To help with debugging, you should tackle the programs in the order listed below. Make
sure you get the correct answer to the simpler programs before moving on to the more
complex ones. For example, begin by implementing an interpreter without list
functionality and get it to run on programs 1 and 2 before tacking program 3.
- Don't be afraid of recursion!
Programs
Test your interpreter on each of the following programs by executing each query in turn.
For each query, include in your report all the answers returned by your program.
In the zip file, include complete traces of your program for every query following the
examples in this file and this file.
In particular, the trace should show every recursive call to the backwards chaining routine,
every successful unification and the resulting substitution, and every answer (substitution)
found. Feel free to add your own queries and expand the programs.
Program 1
This is the simple program from the lecture notes to help you debug.
parent(abraham,ishmael).
parent(abraham,isaac).
parent(isaac,esau).
parent(isaac,jacob).
grandparent(X,Y) :- parent(X,Z), parent(Z,Y).
descendant(X,Y) :- parent(Y,X).
descendant(X,Y) :- parent(Z,X), descendant(Z,Y).
Queries:
parent(isaac,X).
parent(X,abraham).
grandparent(X,Y).
descendant(X,Y).
grandparent(X,Y), descendant(X,Y).
Example output:
? parent(isaac,X).
yes
X = esau
X = jacob
? parent(X,abraham).
no
See this file for a sample trace of the query grandparent(X,Y).
Program 2: Graph coloring
This is the Australia problem:
colorable(Wa,Nt,Sa,Q,Nsw,V) :- diff(Wa,Nt), diff(Wa,Sa), diff(Nt,Q), diff(Nt,Sa), diff(Q,Nsw), diff(Q,Sa), diff(Nsw,V), diff(Nsw,Sa), diff(V,Sa).
diff(red,blue).
diff(red,green).
diff(green,red).
diff(green,blue).
diff(blue,red).
diff(blue,green).
Query:
colorable(Wa,Nt,Sa,Q,Nsw,V).
Be sure to find and display all the solutions to the query.
Program 3: Lists and Sets
This problem requires you to implement list functionality for your interpreter.
The append function below was discussed in the lecture notes.
Also, see this file for a sample trace of the query append([1,2,3],[4,5,6],X).
append([],Y,Y).
append([X|L],Y,[X|Z]) :- append(L,Y,Z).
union([],X,X).
union([X|R],Y,Z) :- member(X,Y), union(R,Y,Z).
union([X|R],Y,[X|Z]) :- union(R,Y,Z).
foo(X,L,[X|L]).
foo(X,[L|H],[L|R]) :- foo(X,H,R).
bar([],[]).
bar([L|H],R) :- bar(H,R1), foo(L,R1,R).
To complete the above program, you should write the definitions for the following functions yourself
(and include these definitions in your report):
- member(X,Y): this function should give the answer
"yes" if element X is a member of list Y.
- reverse(X,Y): X is the input, and Y is the output (reversed list).
For example, reverse([1,2,3,4],Y) should give the answer Y = [4,3,2,1].
- intersect(X,Y,Z): Z should be the result of intersecting lists X and Y.
Queries:
append([1,2,3],[4,5,6],X).
append([1,2,3],X,[1,2,3,4,5,6]).
append(X,Y,[1,2,3,4]).
member(4,[1,2,3,4]).
member(3,[1,2,4]).
member(X,[1,2,3,4]).
reverse([1,2,3,4],[3,4,2,1]).
reverse([1,2,3,4],R).
foo(1,[2,3,4],X).
bar([1,2,3,4],X).
union([1,2],[3,4],X).
union([1,2],[1,2,3,4],X).
intersect([1,2,3,4],[5,1,3,6],X).
In your report, be sure to explain what the functions foo and bar do (and how they do it),
and why the union and intersect queries produce the results that they do.
Program 4
Write your own program and your own queries. Try to push the limits of your interpreter
by giving it more and more complex queries. For this part, you can use snippets of Prolog
code found on the Web (but be sure to cite your sources).
Extra Credit
Research and implement additional Prolog functionality not listed above, for example,
arithmetic operations, negation, or cuts. As before, the amount of credit you will
receive will vary based on the amount of extra work you did and on the thoroughness
of your discussion/analysis. If you implement anything for extra credit,
please be sure to include a clearly marked "Extra Credit" section in your report.
|