Introduction. For this homework, you will implement an entire forward-chaining logical inference system from scratch. This project can be very engaging and enjoyable -- hopefully once you start you'll get involved and won't want to stop until you get it working!
In any case, I highly recommend you read through this entire assignment at least once immediately. Then, before our next class, you should spend at least a few hours starting the implementation. In general, plan to spend at least 5-7 hours a week for the next couple weeks so you do not have to cram it all in at the last moment. This will be the largest implemented project of the semester.
If you program it in a high-level language like Java or LISP I think you'll find progress pretty smooth. C will be more difficult. Since I have broken down the design of the system for you, below, you are in a good position to do a nice, systematic job at this relatively large project. By the way, the next (and last) homework is likely to make use of the system you are implementing for this one, so this makes it even more important that your system is clean (e.g., you may need to do further debugging when you do the next assignment, although probably no expansion).
Reading:
Implement the Forward-Chain algorithm in Figure 9.1 of the text in a language of your choice. This is a complete inference algorithm over Horn sentences. It simply applies Generalized Modus Ponens (GMP) as much as possible. For this homework project, we will use the term rule to refer to an implication in Horn form, each of which has a left-hand side (LHS - a conjunction of non-negated atoms) and a right-hand side (RHS - one non-negated atom). Also, we will use fact to refer to a single atom (which is also legitimate in Horn form). We will say applying or firing a rule to refer to a legal application of GMP to that rule, thus deriving a new fact. Note that in general, an implication could also be considered a "fact", so this lingo-convention applies only to these pages.
For the purposes of this assignment, there are several simplifying assumptions to make your job easier:
The first thing you should do is understand the algorithm in Figure 9.1 to a great degree of detail, so you see what is is doing and why it makes sense.
When you implement the algorithm, pay heed to the following items, which are addendum to the the text's Back-Chain algorithm in Figure 9.1, and are due to the above assumptions and to my making a few things more explicit for your convenience:
In general, there is a set of rules, and a separate set of facts. Each time a new fact comes in, the Forward-Chain algorithm is invoked to set off all possible rule firing, like the "domino effect", adding new facts to the database. In addition to implementing Forward-Chain, you must write code to invoke it for each new incoming fact.
Unification algorithm. Furthermore, due to the above simplifying assumptions, the unification algorithm can be much simpler than that given in Figure 10.3 (part of the optional reading). Here is the unification algorithm you should implement:
Unify(f1, f2) takes two atoms as input, the second of which has no variables (a fact). If they can be matched, it returns a list of variable bindings. Otherwise, it returns "fail". 0. Initialize substitutions (a local variable) to the empty list. 1. If f1 and f2 have different predicate names or different numbers of arguments, Then return "fail". 2. For each argument, a1, in f1, with corresponding argument a2 in f2: If a1 is a variable Then If a1 is bound to something other than a2 in substitutions Then return "fail" Else add the binding a1/a2 to substitutions Else If (constant) a1 doesn't equal (constant) a2 Then return "fail" 3. return substitutions
Text-based I/O. Your system should be able to handle (piped as standard input) input such as that shown below in Part II. As in the text, variables are lower-case, and constants are capitalized. You need not check for errors in the input -- you can assume the input is syntactically correct. You may expect the input to first list all the rules, and then all the facts.
Implementation guideline.
You should make separate data structures (classes, if OOP, e.g., Java) for each of the following (these are general guidelines -- you can stray from them as you like):
Parsing the input. You can assume no spaces within each predicate-argument structure, but spaces everywhere else. If you are using Java, you can use that stringbuffer(?) class (I can't remember what it's called), which allows you to get each token one at a time.
Debugging hints: Get a little to work at a time. For example, first, get it to successfully input rules (only), including the use of the symbol table, and then print them back out. In general, have a "trace" mode that can be turned on or off easily (for debugging only -- turn it off when capturing your system's performance for your homework submission).
American(x) ^ Weapon(y) ^ Nation(z) ^ Hostile(z) ^ Sells(x,z,y) -> Criminal(x) Owns(Nono,x) ^ Missile(x) -> Sells(West,Nono,x) Missile(x) -> Weapon(x) Enemy(x,America) -> Hostile(x) American(West) Nation(Nono) Enemy(Nono,America) Owns(Nono,M1) Missile(M1) Nation(America)From this, it should derive Criminal(West).
TooBig(x) ^ GoodSize(y) -> BetterPet(y,x) Giraffe(x) -> TooBig(x) Dog(x) -> GoodSize(x) Barks(x) ^ WagsTail(x) -> Dog(x) Giraffe(Bob) Barks(Sally) WagsTail(Sally)From this, it should derive BetterPet(Sally,Bob)
Instrument(y) ^ Musician(x) -> Plays(x,y) Instrument(y) ^ Plays(x,y) -> NotToneDeaf(x) Musician(Grace) Instrument(I1)From this, it should derive that Grace is not tonedeaf.
Parent(x,y) ^ Parent(x,z) ^ Distinct(y,z) -> Sibling(y,z) Parent(x,y) ^ Sibling(x,z) ^ Parent(z,w) -> Cousin(y,w) Distinct(x,y) -> Distinct(y,x) Parent(Lisa,Eric) Parent(Lisa,Rachel) Parent(Speed,Lisa) Parent(Speed,Jay) Parent(Jay,Frances) Distinct(Eric,Rachel) Distinct(Lisa,Jay)After testing this input, leave the two rules as is, but replace the rest with your own family or a made-up family. Only assert parental relationships, and allow it to ascertain sibling and cousin relationships. Your example family input need not be any larger than the example shown above. Make sure to clarify that everyone is distinct from one another (unless two names both refer to the same person). Read (but don't do) exercise 9.6 to understand why we need "Distinct".
email: evs at cs dot columbia dot edu