Showing posts with label Resolution Theorem Prover. Show all posts
Showing posts with label Resolution Theorem Prover. Show all posts

Monday, February 10, 2025

Pure ATPs: such a disappointment


---

I have always had a soft spot for automated theorem provers (ATPs). There is something both elegant and exciting about formalising your nontrivial problem in, say, a predicate calculus variant, then pressing the start-button and letting the machine solve it by the powers of deep deduction alone.

But ATPs have always disappointed. Doug Lenat, who spent his entire life handcrafting the general purpose intelligent system Cyc, commented at the last that they had originally encoded the facts and rules of the world in a Lisp-like formal language and then handed that knowledge base and the user-query to a powerful Resolution Theorem Prover to deduce an answer. To avoid the interminable waiting they added layer after layer of special-case heuristics. After decades of such aggregation they quietly turned off the theorem prover: it was never being used.

Edinburgh university was one of the centres of Prolog use and research in the 1980s, along with Oxford and Imperial College. Undergraduates hated it: the elegance of its specification capability was complemented by the opacity of its execution model. Debugging logic programs by mentally running a depth-first tree-search with backtracking-on-failure - well, that could tax the abstraction-powers of the keenest young minds. 

I was much happier with Lisp: you know where you are with β-reduction.

In the heady days of those applied ATPs known as Expert Systems the slogan was: “In the knowledge lies the power”; inferential capability was distinctly secondary.

The culmination of this insight was the success of the LLMs c. 2024. Enormous amounts of encoded knowledge - and no reasoning ability at all…

And yet, there are still those of us who value the elegance of inference... and now, in 2025, my happiness is complete: those LLMs can now reason!


Wednesday, March 14, 2018

A beta-level simulation

Amazon link

I'm just finishing "Elysium Fire" by Alastair Reynolds, his follow-up to "Aurora Rising: Previously published as The Prefect". It's a perfectly serviceable SF techno-thriller: the action moves along briskly enough.

I am most intrigued, though, by the concepts of alpha- and beta-level simulations.
"An alpha-level simulation was a digitised version of a scanned human mind, perfectly replicating its brain structure. It was fully capable of remembering past experiences, learning, adapting, and was fully self-aware.

Later technologies allowed individuals to survive the scanning process, leaving essentially two copies of the same person, one physical and one digital. ...

---

Beta-level simulations were sophisticated computer programs designed to mimic a person in appearance, mannerisms, and thought-patterns. While capable of successfully impersonating a human mind down to their most minute idiosyncrasies, they were not in fact self-aware -- they were just near-perfect imitations of life. As such, they enjoyed no legal rights or protections.

In Yellowstone society, and presumably other Demarchies, it was considered a most egregious social faux pas to allow someone to believe a beta-level simulation was in fact alpha-level."
A continuing theme is that after someone is murdered, they may well have had a shadowing beta-level simulation which the authorities then sequester for interrogation - a chance to interview the deceased victim, if you like. It's quite a shock when they're told they are dead.

The beta-levels generally don't know they're simulations and the hero, Inspector Dreyfus, is conflicted as to whether he should adopt the instrumental view ("I'm interviewing an expert system") or the intentional view ("I'm interviewing a digitalised person"). For the reader, this dilemma is never truly resolved.*

It does give me itchy fingers. I want to go back to my Lisp environment, to my 'Eliza' system and my automated theorem-prover. I'm sure I could knock up a fairly awesome beta-level myself, if we were talking, say, of a cat .. .

---

* I suppose a remote descendant of Replika will give us all beta-level simulations.

Tuesday, March 21, 2017

Improved resolution theorem prover (RTP v. 1.11)

[Update: March 26th 2017: small tweak for incremental release RTP v. 1.11. The change is to the order of new goals in 'binary-resolve', which adopts a faster-converging pure depth-first search. See the READ-ME file for details.]

===

You will have previously seen the announcement about the first (tutorial) version of my resolution theorem prover in Lisp.

Today I'm releasing version 1.1 which is easier to use and gives a clearer output.

Below are the "Who does Lady Sally Fowler like?" proofs (compare with the previous version).

The code is available at the AI code in Lisp Resource on the right of your (web view) screen.

Or follow these links for the program file and genealogical test data.

Retrieve program file: RTP-v1.11 and test-data:  Genealogical-etc-test-data.


---    To determine (likes sally ?who)    ---

 > (prove-it *g-mge1* *mge-axioms* 8)

Do you want to see all the proof steps y/n?  n
Conclusion = ((<- ((LIKES SALLY SALLY))))

(13)   ((LIKES ?X ?X) <-)
(14)   (<- ((LIKES SALLY ?WHO)))
(19 14 13)   +EMPTY-CLAUSE+
--------------------------------------

Conclusion = ((<- ((LIKES SALLY ROD))))

(5)   ((LIKES SALLY ROD) <-)
(14)   (<- ((LIKES SALLY ?WHO)))
(16 14 5)   +EMPTY-CLAUSE+
--------------------------------------

Conclusion = ((<- ((LIKES SALLY RENNER))))

(4)   ((LIKES SALLY RENNER) <-)
(14)   (<- ((LIKES SALLY ?WHO)))
(15 14 4)   +EMPTY-CLAUSE+
--------------------------------------

Conclusion = ((<- ((LIKES SALLY MOTIES))))

(13)   ((LIKES ?X ?X) <-)
(10)   ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES)))
(14)   (<- ((LIKES SALLY ?WHO)))
(17 14 10)   (<- ((LIKES ?WHO MOTIES)))
(24 17 13)   +EMPTY-CLAUSE+
--------------------------------------

Conclusion = ((<- ((LIKES SALLY HORVATH))))

(9)   ((LIKES HORVATH MOTIES) <-)
(10)   ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES)))
(14)   (<- ((LIKES SALLY ?WHO)))
(17 14 10)   (<- ((LIKES ?WHO MOTIES)))
(20 17 9)   +EMPTY-CLAUSE+
--------------------------------------

Conclusion = ((<- ((LIKES SALLY SALLY))))

(13)   ((LIKES ?X ?X) <-)
(10)   ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES)))
(10)   ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES)))
(14)   (<- ((LIKES SALLY ?WHO)))
(17 14 10)   (<- ((LIKES ?WHO MOTIES)))
(21 17 10)   (<- ((LIKES MOTIES MOTIES)))
(27 21 13)   +EMPTY-CLAUSE+
-----------------------------------
---

RTP Release 1.1 description.

Version 1.1: 21st March 2017

Use with test data file:  Genealogical-etc-test-data   ('load' statement at end of the file).

Version 1.01 continues to be best as a first tutorial to understand the program at code level. Plenty of comments there showing worked-execution. But the process for the user to invoke the program is clumsy.

This version has:
  1. Stripped out many of the comments for improved code clarity.
  2. Wrapped up execution in a new function, 'prove-it'.
  3. Improved functions to print the proofs created by the program. 
All new functions at the end of the file.

Only other change is altered search strategy order in 'prove1':

.       (next-igb-list     (append new-igb-list igb-list1)) ) ; search strategy

which seems to help convergence.

---

The main test data for the new version is the genealogical axiom set which you will find in the test data mentioned above (or for an early version scroll to the bottom here).

It does lead to some tortuous proofs: for example, this one.

---

> (defvar *gg6* (mk-goal-clause '((grandfather gerry james))))

> (prove-it *gg6* *family-tree-axioms* 12)
Conclusion = ((<- ((GRANDFATHER GERRY JAMES))))

(13)   ((CHILD-OF PETER JANE JAMES) <-)
(2)   ((MALE PETER) <-)
(11)   ((CHILD-OF GERRY MARY PETER) <-)
(1)   ((MALE GERRY) <-)
(23)   ((PARENT ?X ?Z) <- ((MALE ?X) (CHILD-OF ?X ? ?Z)))
(23)   ((PARENT ?X ?Z) <- ((MALE ?X) (CHILD-OF ?X ? ?Z)))
(1)   ((MALE GERRY) <-)
(32)   ((GRANDFATHER ?X ?Z) <- ((MALE ?X) (PARENT ?X ?Y) (PARENT ?Y ?Z)))
(39)   (<- ((GRANDFATHER GERRY JAMES)))
(40 39 32)   (<- ((MALE GERRY) (PARENT GERRY ?Y1425) (PARENT ?Y1425 JAMES)))
(41 40 1)   (<- ((PARENT GERRY ?Y1425) (PARENT ?Y1425 JAMES)))
(42 41 23)   (<- ((PARENT ?Y1425 JAMES) (MALE GERRY) (CHILD-OF GERRY ?1498 ?Y1425)))
(44 42 23)   (<- ((MALE GERRY) (CHILD-OF GERRY ?1498 ?Y1425) (MALE ?Y1425) (CHILD-OF ?Y1425 ?1547 JAMES)))
(46 44 1)   (<- ((CHILD-OF GERRY ?1498 ?Y1425) (MALE ?Y1425) (CHILD-OF ?Y1425 ?1547 JAMES)))
(47 46 11)   (<- ((MALE PETER) (CHILD-OF PETER ?1547 JAMES)))
(49 47 2)   (<- ((CHILD-OF PETER ?1547 JAMES)))
(50 49 13)   +EMPTY-CLAUSE+
-------------------------------------- 

So that means it's already a superhuman reasoner, right?

Monday, March 20, 2017

AI code in Lisp: new resource here

New resource on the sidebar to your right. This should please readers wondering why vast screeds of Lisp code randomly appear here, interrupting more erudite essays on this & that.

Except as I write this, 134 of you have visited: Description: Theorem Prover in Lisp.

Here's what the READ ME at the new sidebar says.

READ ME
=======

These Common Lisp files contain AI programs which are organised around the theme of building a chatbot.

They will all run independently and were developed in LispWorks free Personal Edition.

You can use the code as you like. It's not supported and there will certainly be bugs I haven't spotted.

I think of the code as a toolkit, there to be modified.

---

To come:

1. Upgrades to the resolution theorem-prover improving the display of proofs + any bug fixes.

2. An AI planner, oriented both towards a toy, virtual, physical world and speech acts for conversation planning.

3. A design for 'internal emotional states' to create some 'point' for the chatbot's autonomous behaviour; we need something more interesting than a natural language interface to Wikipedia-style queries.

Plus integration of all the above.

Thursday, March 16, 2017

Description: Theorem Prover in Lisp

[Update: RTP release 1.1 is now available.]
---

As I mentioned, I've now completed basic testing of my (very, very simple) resolution theorem prover in Common Lisp: the code is ready to load. But read the stuff below first.

1. How to run the system

Once you have the code in front of you, go to Part 2: clauses and extended clauses where you will find the test-data definitions as follows:
(defvar *c1* (mk-fact-clause '(member ?item (?item . ?rest ))))
(defvar *c2* (mk-rule-clause '(member ?item (? . ?rest)) '((member ?item ?rest))))
(defvar *c3* (mk-fact-clause '(likes rod horvath)))
(defvar *c4* (mk-fact-clause '(likes sally renner)))
(defvar *c5* (mk-fact-clause '(likes sally rod)))
(defvar *c6* (mk-fact-clause '(likes hardy renner)))
(defvar *c7* (mk-fact-clause '(likes hardy rod)))
(defvar *c8* (mk-fact-clause '(amusing hardy)))
(defvar *c9* (mk-fact-clause '(likes horvath moties)))
(defvar *c10* (mk-rule-clause '(likes sally ?x) '((likes ?x moties)) ) )
(defvar *c11* (mk-rule-clause '(likes rod ?x) '((likes ?x renner) (likes ?x rod))))
(defvar *c12* (mk-rule-clause '(likes ?x ?y) '((amusing ?y) (likes rod ?y)) ) )
(defvar *c13* (mk-fact-clause '(likes ?x ?x)))

(defvar *g1* (mk-goal-clause '((likes sally ?who))))
(defvar *g2* (mk-goal-clause '((member sally (rod renner horvath sally . moties)))))

(setf *axioms* (list *c1* *c2* *c3* *c4* *c5* *c6* *c7* *c8*
                 *c9* *c10* *c11* *c12* *c13*))

(setf *goals* (list *g1*))
Input your own facts, rules and goals and assign them to variables *axioms* and *goals* as shown above. Or just use my test data to check the system out in the first instance.

Now go to Part 4:  TEST DATA and execute the following commands as shown there.
(defvar *igb-list-ia-list* (initialise-databases *goals* *axioms*))

(defvar *igb-list* (first *igb-list-ia-list*))  ; extended goal clause(s)

(defvar *ia-list*   (second *igb-list-ia-list*))  ; extended axiom clauses

(setf archived-goals-and-proofs (prove *goals* *axioms* 6))    ; depth 6 here

(pprint (setf all-proofs (show-all-proofs archived-goals-and-proofs *ia-list*)))
The search depth (eg 6) is arbitrary - try 4 or 8 etc.

With *1step* set to true,
(defvar *1step* t)        ; If t stops proof loop each iteration and
;                                      prints archived-goals, archived proofs & next goals
the theorem-prover will print its workings on each inference cycle and wait for input before proceeding. If you just want the thing to run to completion, set *1step* to nil.

---

2. In-code documentation

Most of the code is heavily documented with runtime examples - but read this post first.

---

3. Data Structures

The data structures are as follows.

1. We start with literals like (LIKES ?X RENNER), (LIKES SALLY ?WHO) - which the function 'unify' tries to unify to create bindings like this:
((?WHO . RENNER) (#:?X1113 . SALLY)).

2. At the level of basic binary resolution we have clauses, which look like this:

A fact is a clause (a list of literals) which contains only one (positive) literal followed by <- .
(defvar *c1* '( (likes horvath moties) <- )                          ; a fact clause
A rule is a clause (a list) which is a (positive) literal (the clause head) followed by <- and then a list of literals - the body of the clause.

We use <- with value nil as a spacer only for human readability.
(defvar *c2* '((likes sally ?who) <- ((likes ?who ?y) (likes ?y moties)))     ;  rule
A goal is a clause (a list) of one or more negative literals. First element is <- for human readability.
(defvar *g* '(<- ((likes ?x renner) (likes ?x rod)) ) )          ;  a goal clause

3. At the level of running the whole proof procedure we have extended-clauses, sometimes written as igb-list and ia-list for extended goal clauses and extended axiom clauses. The letter 'i' stands for index, and 'b' stands for binding.

An extended clause is a triple: index,  clause, then (if a goal) binding. It is implemented as a list like this: (index clause binding). Example:
(defvar *icb* '((16 (4 (nil nil)) (2 (nil nil)))                   ; Index
                     (<- ((likes ?x renner) (amusing ?x)))       ; Clause
                              ((T . T))))                                    ; Binding
An index is a binary tree of integers, with leaves nil.
Example: (1 (nil nil))   or   (16 (12 (7 (nil nil)) (4 (nil nil)) ) (5 (nil nil)) )
Each leading integer is the current clause number; the two immediate children are the two resolution parent clauses .. and so on. Initial goals, and axioms have no parents, thus nil.

The reason for extended clauses is that we need to keep track of the inference steps to reconstruct the entire proofs afterwards. The bindings are kept to allow us to instantiate variables in the original query which we need as the answer!

---

4. FAQs (reprinted from here).

4.1. Isn't this just Prolog?

Prolog is encountered as a black box. You provide a program and a query (as above) and you get back bindings, like this:
?- likes(sally, W).

W = sally;
W = rod;
... and so on

If you want proofs, to add the 'occurs check' or to change the order in which resolution steps are tried (the search strategy) - well tough: all those things are both fixed and opaque in Prolog. To make them explicit for modification, you have to write an explicit theorem-prover in Prolog (which can of course be done).

4.2. You are using Horn clauses?

Yes. I initially thought to implement a general clausal prover, but Horn clauses make the resolution step particularly simple (just one positive literal to match) and you lose neither generality nor expressive power. But since everything in the code is both modular and explicit, it would be easy to extend the program.

4.3. The style is functional?

Yes. Resolution theorem provers on the Internet are heavily optimised with clever, imperative algorithms and direct access data structures such as hash tables. This makes the code efficient but obscure - very hard to understand.

I didn't want to do that. This is a tool-kit and I'm not trying to create thousands of logical inferences per second. My intended application area (simple inference over an ever-changing knowledge-base for a chatbot) never requires massive inferential chains so clarity and easy modifiability was my objective.

4.4. What was hard?

The program is architected at three levels.

(a) Unification

This is basically already quite modular and straightforward. I re-used Peter Norvig's code.

(b) Resolution

Binary resolution itself - specially with Horn clauses - is a straightforward procedure - as you will see in the code. There are some subtleties with bindings and substitutions, but once you realise that resolution is fundamentally a local operation it's not too difficult.

(c) Control and proof construction

The process of creating new goals and resolving them with the axioms is somewhat complex although again, sticking with Horn clauses makes it a lot easier: just think of a naive Prolog execution model.

However, if you want to return proofs, you need to number the axioms and number-and-archive goals as you process them, capturing the resulting tree of inferences. At the end, for each empty-clause = successful proof, you need to trace that index-tree backwards to recover the proof steps. I found it a bit intricate.

4.5. What's next?

In theory you can do anything with a theorem-prover (Prolog being the existence proof) but it's not necessarily the best architecture. For a planner, where state changes are part of the problem definition, I need to adapt the tool-kit to a design centred around actions with pre- and post-condition in the context of a goal-state and an updating world-state. Such a dynamic model can be used both for actions in a (virtual) world and conversation planning via speech acts.

The theorem-prover remains optimal as a separate module, managing the crystallized knowledge base using inference to draw conclusions - for example to drive questions and answers.

I'm thinking of using the already-programmed Eliza front-end as a robust conversational interface. Doesn't matter if it's bluffing some of the time if it can use learning, inference and planning often enough.

Onwards to the GOFAI smart chatbot ...

---

5. Miscellaneous

As usual, code is provided for free use, unsupported and with no guarantees at all that there aren't bugs I've missed. Feel free to tell me in the comments.

Other code you may find useful:
  1. A resolution theorem prover in Lisp (as described here)
  2. Prolog in Lisp
  3. Minimax (noughts-and-crosses) in Lisp
  4. Eliza in Lisp
 An example of input and output of the prover.

---

Update: Saturday March 18th 2017.

1. Code here slightly updated (to v. 1.01) with change to 'show-proof' (and 'show-all-proofs') to add an additional 'top level goals' parameter, thereby getting rid of the previous embedded global variable. This makes the code completely functional now.

2. With further testing (on the genealogical axiom set below) .. with deeper inferences .. I have noticed that naive pprint doesn't do a good job of showing the output - the 'found proofs'. Over the next few days I'll write a dedicated display function which can handle more deeply-nested proofs and let you know when it's done. I'll also provide you with proper version of the genealogical test data below: (not completed testing, so it's illustrative only at this point).

; ---  Test data: family tree : axioms and goal ---
;   https://jameskulkarni17.wordpress.com/2011/09/26/family-tree-using-prolog/
;   with anglicised names and modified rules.

(defvar *g1* (mk-fact-clause '(male gerry)))
(defvar *g2* (mk-fact-clause '(male peter)))
(defvar *g3* (mk-fact-clause '(male john)))
(defvar *g4* (mk-fact-clause '(male mike)))
(defvar *g5* (mk-fact-clause '(male james)))
(defvar *g6* (mk-fact-clause '(male hugo)))

(defvar *g7* (mk-fact-clause '(female mary)))
(defvar *g8* (mk-fact-clause '(female jane)))
(defvar *g9* (mk-fact-clause '(female sarah)))
(defvar *g10* (mk-fact-clause '(female christine)))


(defvar *g11* (mk-fact-clause '(child-of gerry mary peter)))
(defvar *g12* (mk-fact-clause '(child-of gerry mary john)))
(defvar *g13* (mk-fact-clause '(child-of peter jane james)))
(defvar *g14* (mk-fact-clause '(child-of peter jane mike)))
(defvar *g15* (mk-fact-clause '(child-of john sarah christine)))
(defvar *g16* (mk-fact-clause '(child-of john sarah hugo)))


(defvar *g17* (mk-fact-clause '(brother peter john)))
(defvar *g18* (mk-fact-clause '(brother john peter)))
(defvar *g19* (mk-fact-clause '(brother james mike)))
(defvar *g20* (mk-fact-clause '(brother mike james)))

(defvar *g21* (mk-fact-clause '(brother hugo christine)))
(defvar *g22* (mk-fact-clause '(sister christine hugo)))

(defvar *g23* (mk-rule-clause '(parent ?X ?Y) '((child-of ?X ? ?Y))))
(defvar *g24* (mk-rule-clause '(parent ?X ?Y) '((child-of ? ?X ?Y))))

(defvar *g25* (mk-rule-clause '(sibling ?X ?Y) '((brother ?X ?Y))))
(defvar *g26* (mk-rule-clause '(sibling ?X ?Y) '((sister ?X ?Y))))

(defvar *g27* (mk-rule-clause '(father ?X ?Z) '((child-of ?X ? ?Z))))

(defvar *g28* (mk-rule-clause '(mother ?Y ?Z) '((child-of ? ?Y ?Z))))

(defvar *g29* (mk-rule-clause '(son ?X ?Y ?Z)
                              '((male ?X) (father ?Y ?X) (mother ?Z ?X))))

(defvar *g30* (mk-rule-clause '(daughter ?X ?Y ?Z)
                              '((female ?X) (father ?Y ?X) (mother ?Z ?X))))

(defvar *g31* (mk-rule-clause '(wife ?X ?Y) '((female ?X) (child-of ?Y ?X ?))))

(defvar *g32* (mk-rule-clause '(grandfather ?X ?Z)
                              '((male ?X) (parent ?X ?Y) (parent ?Y ?Z))))

(defvar *g33* (mk-rule-clause '(grandmother?X ?Z)
                              '((female ?X) (parent ?X ?Y) (parent ?Y ?Z))))

(defvar *g34* (mk-rule-clause '(uncle ?X ?Z)
                              '((male ?X)   (sibling ?X ?Y) (parent ?Y ?Z))))

(defvar *g35* (mk-rule-clause '(aunt ?X ?Z)
                              '((female ?X) (sibling ?X ?Y) (parent ?Y ?Z))))

(defvar *g36* (mk-rule-clause '(cousin ?X ?Y)
                              '((parent ?U ?X) (sibling ?U ?W) (parent ?W ?Y))))

(defvar *g37* (mk-rule-clause '(ancestor ?X ?Y ?Z) '((child-of ?X ?Y ?Z))))
(defvar *g38* (mk-rule-clause '(ancestor ?X ?Y ?Z)
                              '((child-of ?X ?Y ?W)  (ancestor ?W ? ?Z))))

(defvar *family-tree-axioms* (list *g1* *g2* *g3* *g4* *g5* *g6* *g7*
                                 *g8* *g9* *g10* *g11* *g12* *g13*
                                 *g14* *g15* *g16* *g17* *g18* *g19*
                                 *g20* *g21* *g22* *g23* *g24* *g25*
                                 *g26* *g27* *g28* *g29* *g30* *g31*
                                 *g32* *g33* *g34* *g35* *g36* *g37* *g38*))

(defvar *gg1* (mk-goal-clause '((father ?X ?Y))))
(defvar *gg2* (mk-goal-clause '((mother ?X ?Y))))
(defvar *gg3* (mk-goal-clause '((child-of ?X ?Y ?Z))))
(defvar *gg4* (mk-goal-clause '((son james ?X ?Y))))
(defvar *gg5* (mk-goal-clause '((daughter christine ?X ?Y))))
(defvar *gg6* (mk-goal-clause '((grandfather ?X ?Y))))
(defvar *gg7* (mk-goal-clause '((aunt ?X ?Y))))
(defvar *gg8* (mk-goal-clause '((uncle peter ?X))))
(defvar *gg9* (mk-goal-clause '((cousin ?X ?Y))))
(defvar *gg10* (mk-goal-clause '((ancestor ?X ?Y james))))

(defvar *family-tree-goal* (list *gg1* ))   ; start testing with the first goal

;;; A function, 'prove-it', to save copy-and-paste of the long commands below

(defun prove-it (goal depth)
    (setf *igb-list-ia-list*
        (initialise-databases *family-tree-goal* *family-tree-axioms*))
    (setf *igb-list* (first *igb-list-ia-list*))
    (setf  *ia-list*  (second *igb-list-ia-list*))
     (setf archived-goals-and-proofs (prove (list goal) *family-tree-axioms*  depth))
     (pprint (setf all-proofs
                           (show-all-proofs archived-goals-and-proofs *ia-list* (list goal))))
 nil)

#|   --- Commands to run the test data ---

(defvar *igb-list-ia-list*
        (initialise-databases *family-tree-goal* *family-tree-axioms*))
(defvar *igb-list* (first *igb-list-ia-list*))
(defvar *ia-list*  (second *igb-list-ia-list*))

(setf archived-goals-and-proofs (prove *family-tree-goal* *family-tree-axioms*  6))

(pprint (setf all-proofs (show-all-proofs archived-goals-and-proofs *ia-list* *family-tree-

goal*)))

or type

(prove-it *gg1* 6)    ; but currently doesn't display well

----------------------------------------------------------------------------------
|#

#|
**********************   Intended Output   *************************

(defvar *gg1* (mk-goal-clause '((father ?X ?Y))))

1 ?- father(X,Y).

X = gerry
Y = peter ;

X = gerry
Y = john ;

X = peter
Y = james ;

X = peter
Y = mike ;

X = john
Y = christine ;

X = john
Y = hugo ;

No
******************************************************
(defvar *gg2* (mk-goal-clause '((mother ?X ?Y))))

2 ?- mother(X,Y).

X = mary
Y = peter ;

X = mary
Y = john ;

X = jane
Y = james ;

X = jane
Y = mike ;

X = sarah
Y = christine ;

X = sarah
Y = hugh ;

No
******************************************************
(defvar *gg3* (mk-goal-clause '((child-of ?X ?Y ?Z))))

3 ?- child-of(X,Y,Z).

X = gerry
Y = mary
Z = peter ;

X = gerry
Y = mary
Z = john ;

X = peter
Y = jane
Z = james ;

X = peter
Y = jane
Z = mike ;

X = john
Y = sarah
Z = christine ;

X = john
Y = sarah
Z = hugo ;

No
******************************************************
(defvar *gg4* (mk-goal-clause '((son james ?X ?Y))))

4 ?- son(james,X,Y).

X = peter
Y = jane

Yes
******************************************************
(defvar *gg5* (mk-goal-clause '((daughter christine ?X ?Y))))

5 ?- daughter(christine,X,Y).

X = john
Y = sarah

Yes
******************************************************
(defvar *gg6* (mk-goal-clause '((grandfather ?X ?Y))))

6 ?- grandfather(X,Y).

X = gerry
Y = james ;

X = gerry
Y = mike ;

X = gerry
Y = christine ;

X = gerry
Y = hugo ;

No
******************************************************
(defvar *gg7* (mk-goal-clause '((aunt ?X ?Y))))

7 ?- aunt(X,Y).

X = jane
Y = christine;

X = jane
Y = hugo ;

X = sarah
Y = james ;

X = sarah
Y = mike ;

No
******************************************************
(defvar *gg8* (mk-goal-clause '((uncle peter ?X))))

8 ?- uncle(peter,X).

X = christine;

X = hugo;

No
******************************************************
(defvar *gg9* (mk-goal-clause '((paternal-cousin ?X ?Y))))

9 ?- cousin(X,Y).

X = james
Y = christine ;

X = james
Y = hugo ;

X = mike
Y = christine ;

X = mike
Y = hugo;

X = christine
Y = james ;

X = christine
Y = mike ;

X = hugo
Y = james ;

X = hugo
Y = mike ;

No
******************************************************
(defvar *gg10* (mk-goal-clause '((paternal-ancestor ?X ?Y james))))

10 ?- ancestor(X,Y,james).

X = peter
Y = jane ;

X = gerry
Y = mary ;

|#


A Horn clause resolution theorem prover in Lisp

;;; A Horn-Clause Resolution Theorem Prover in Lisp:
;;; February 24th 2017 - March 16th 2017:   v. 1.0 -- initial release
;;;
;;; v. 1.01  March 18th 2017  -- remove state variable *goals* from show-proof, now functional
;;; ---
;;; [Update: (March 21st 2017): RTP release 1.1 is now available. See:
; http://interweave-consulting.blogspot.co.uk/2017/03/improved-resolution-theorem-prover-rtp.html
;;; ---
;;; Based on code from From Peter Norvig's book: Chapter 11
;;; "Paradigms of Artificial Intelligence Programming" - initially.
;;;
;;; Unification, Prolog, Resolution, inference control strategies
;;; generation of all proofs, extraction of proofs.
;;;
;;; Posted here:
;
; http://interweave-consulting.blogspot.co.uk/2017/03/a-horn-clause-resolution-theorem-prover.html
;;;
;;;  For documentation see here (you really need to read this first!):
;
; http://interweave-consulting.blogspot.co.uk/2017/03/description-theorem-prover-in-lisp.html
;;;
; (load  "C:\\Users\\HP Owner\\Google Drive\\Lisp\\Prog-RTP\\RTP.lisp")
;
;;; ----------------------------------------------------------------------
;;;
;;; Part 1: Unification
;;;
;;;  unify works on literals: eg (LIKES SALLY RENNER), (LIKES SALLY ?WHO)
;;;
;;;  --- Bindings: a list of dotted pairs created by unify: like this ---
;;;
;;;   ((?WHO . SALLY) (#:?X1113 . SALLY))

(defconstant +fail+ nil "Indicates unification failure")

(defconstant +no-bindings+ '((t . t))
"Indicates pat-match success but with no variables.")

(defun variable-p (x)    ; Symbol -> Bool
  "Is x a variable, a symbol beginning with '?'"
  (and (symbolp x) (equal (char (symbol-name x) 0) #\? )))

(defun get-binding (var bindings)
  "Find a (variable . value) pair in a binding list."
  (assoc var bindings))

(defun binding-val (binding)
  "Get the value part of a single binding."
  (cdr binding))

(defun lookup (var bindings)
  "Get the value part (for var) from a binding list."
  (binding-val (get-binding var bindings) ))

(defun extend-bindings (var val bindings)
  "Add a (var . value) pair to a binding list, remove (t . t)"
  (cons (cons var val) (if (equal bindings +no-bindings+) nil bindings)))

; --- Unification -------------------------------------------------------

(defparameter *occurs-check* t "Should we do the occurs check? If yes, t")

(defun unify (term-1 term-2 &optional (bindings +no-bindings+))
;  Lit x Lit -> Bindings
  "See if term-1 and term-2 match with given bindings"
  (cond ((equal bindings '+fail+) '+fail+)
        ((equal term-1 term-2) bindings)
        ((variable-p term-1) (unify-variable term-1 term-2 bindings))
        ((variable-p term-2) (unify-variable term-2 term-1 bindings))
        ((and (consp term-1) (consp term-2))
            (unify (rest term-1) (rest term-2)
                (unify (first term-1) (first term-2) bindings) ) )
        (t '+fail+) ) )

(defun unify-variable (var term bindings)     ; Var x Lit -> Bindings
  "Unify var with term, using (and maybe extending) bindings"
  (cond ((get-binding var bindings)
             (unify (lookup var bindings) term bindings))
        ((and (variable-p term) (get-binding term bindings))
             (unify var (lookup term bindings) bindings))
        ((and *occurs-check* (occurs-check var term bindings))
             '+fail+)
        (t (extend-bindings var term bindings))))

; > (unify '(likes sally renner) '(likes sally ?who)  +no-bindings+)
; ((?WHO . RENNER))

(defun occurs-check (var literal bindings)  ; Var x Lit -> Bool
  "Does var occur anywhere 'inside literal'? Returns t if it does (so fail)"
  (cond ((eq var literal) t)
        ((and (variable-p literal) (get-binding literal bindings))
         (occurs-check var (lookup literal bindings) bindings))
        ((consp literal) (or (occurs-check var (first literal) bindings)
                       (occurs-check var (rest literal) bindings)))
        (t nil)))

(defun unifier (literal literal-1)  ; ; Lit x Lit -> Lit
  "Return something that unifies with both literal and y (or +fail+)"
  (subst-bindings (unify literal literal-1) literal) )

(defun subst-bindings (bindings literal) ; Bindings x Lit ->Lit
  "Substitute the value of variables in bindings into literal,
taking recursively bound variables into account. Also works with clauses."
  (cond ((equal bindings '+fail+) '+fail+ )
        ((equal bindings +no-bindings+) literal)
        ((and (variable-p literal) (get-binding literal bindings))
              (subst-bindings bindings (lookup literal bindings) ))
        ((atom literal) literal)
        ( t (cons (subst-bindings bindings (car literal))
                        (subst-bindings bindings (cdr literal )) ) ) ) )

; --- Replace anonymous variables and rename variables in clause --------

(defun replace-?-vars (exp)
  "Replace any anonymous variable ? within exp with a var of the form ?123"
  (cond ((eq exp '?) (gensym "?"))
        ((atom exp) exp)
        (t (cons (replace-?-vars (first exp))
                 (replace-?-vars (rest exp))) ))  )

(defun rename-variables (x)       ; clause -> clause (with vars renamed)
  "Replace all variables in x with new ones"
  (sublis (mapcar #'(lambda (var) (cons var (gensym (string var))))
                  (variables-in x))
          x))

(defun variables-in (exp)
  "Return a list of all the variables in exp"
  (unique-find-anywhere-if #'variable-p exp))

(defun unique-find-anywhere-if (predicate tree &optional found-so-far)
  "Return a list of leaves of tree satisfying predicate,
    with duplicates removed"
  (if (atom tree)
      (if (funcall predicate tree) (adjoin tree found-so-far) found-so-far)
      (unique-find-anywhere-if predicate (first tree)
                (unique-find-anywhere-if predicate (rest tree) found-so-far)
 )))

;;; ----------------------------------------------------------------------
;;;
;;; Part 2: clauses and extended clauses
;;;
;;; --- Constants, variables and parameters ---

(defconstant <- nil   "pos/neg literals separator")
(defvar *axioms* nil)               ; list of all 'program' clauses
(defvar *goals* nil)                ; list of goal clauses

(defconstant +empty-clause+ t
    "indicates a proof: we resolved to the empty clause")
(defconstant +no-resolvant+ t
    "indicates clauses don't resolve")

; -----------------------------------------------------------------------
; EXAMPLE CLAUSES - WE HAVE AN EXTRA LEVEL OF PARENS FOR <-  .. REMEMBER!
;
; A fact is a clause (a list of literals) which has one (positive) literal
; followed by <- (which has value nil though this is not currently used).
; (defvar *c1* '( (likes horvath moties) <- )    ; a fact clause

; A rule is a clause (a list) which is a (positive) literal (the clause head)
; followed by <- and then a list of literals - the body of the clause.
;
; We use <- with value nil as a spacer only for human readability.
; (defvar *c2* '((likes sally ?who) <- ((likes ?who ?y) (likes ?y moties))) ;rule
;
; A goal is a clause (a list) of one or more negative literals.
; First element is <- for human readability.
; (defvar *g* '(<- ((likes ?x renner) (likes ?x rod)) ) )   ; a goal clause
;
; An index is a binary tree of integers, with leaves nil.
; Example: (1 (nil nil)) or (16 (12 (7 (nil nil)) (4 (nil nil)) ) (5 (nil nil)) )
; Each leading integer is the current clause number;
; the two immediate children are the two parent clauses .. and so on.
; Initial goals and axioms have no parents, thus nil.
;
; An extended clause is a triple: index,  clause then (if a goal) binding.
; Implemented as a list like this: (index clause binding). Example:
;
; (defvar *icb* '((13 (4 (nil nil)) (2 (nil nil)))             ; Index
;                      (<- ((likes ?x renner) (amusing ?x)))   ; Clause
;                               ((T . T))))                    ; Binding
;
; I don't use constructors/projectors for extended clauses:
; just remember their structure as triples.

; --- Clause constructors and projectors ---

(defun mk-fact-clause (literal)              ; Lit -> fact clause
   (list literal '<-))

(defun mk-rule-clause (literal literal-list) ; Lit x Lit-list ->rule clause
   (list literal '<- literal-list))

(defun mk-goal-clause (literal-list)         ; Lit-list -> goal clause
   (list '<- literal-list))

; --- test data (Mote in God's Eye): axioms and goal ---

(defvar *c1* (mk-fact-clause '(member ?item (?item . ?rest ))))
(defvar *c2* (mk-rule-clause '(member ?item (? . ?rest)) '((member ?item ?rest))))
(defvar *c3* (mk-fact-clause '(likes rod horvath)))
(defvar *c4* (mk-fact-clause '(likes sally renner)))
(defvar *c5* (mk-fact-clause '(likes sally rod)))
(defvar *c6* (mk-fact-clause '(likes hardy renner)))
(defvar *c7* (mk-fact-clause '(likes hardy rod)))
(defvar *c8* (mk-fact-clause '(amusing hardy)))
(defvar *c9* (mk-fact-clause '(likes horvath moties)))
(defvar *c10* (mk-rule-clause '(likes sally ?x) '((likes ?x moties)) ) )
(defvar *c11* (mk-rule-clause '(likes rod ?x) '((likes ?x renner) (likes ?x rod))))
(defvar *c12* (mk-rule-clause '(likes ?x ?y) '((amusing ?y) (likes rod ?y)) ) )
(defvar *c13* (mk-fact-clause '(likes ?x ?x)))

(defvar *g1* (mk-goal-clause '((likes sally ?who))))
(defvar *g2* (mk-goal-clause '((member sally (rod renner horvath sally . moties)))))

(setf *axioms* (list *c1* *c2* *c3* *c4* *c5* *c6* *c7* *c8*
                 *c9* *c10* *c11* *c12* *c13*))

(setf *goals* (list *g1*))

; -----Datatype operators for clauses -----------------------------------

(defun is-fact-clause (clause)
   (and (= (length clause) 2) (equal (cadr clause) '<-)) )

(defun is-rule-clause (clause)
   (and (= (length clause) 3) (equal (cadr clause) '<-)) )

(defun is-goal-clause (clause)
   (and (= (length clause) 2) (equal (car clause) '<-)) )

(defun axiom-head-literal (clause)           ; Clause -> Lit (remove <-)
    (if (or (is-fact-clause clause) (is-rule-clause clause))
        (first clause)
        "error - not a fact or rule clause" ) )

(defun axiom-body-literal-list (clause)      ; Clause -> Lit-list (remove <-)
    (if (is-rule-clause clause) (third clause) nil))

(defun goal-literal-list (goal-clause)    ; Clause -> Lit-list (remove <-)
    (if (is-goal-clause goal-clause)
            (second goal-clause)
            "error - not goal clause"))

(defun pred (literal) (car literal))    ; Lit -> Symbol (predicate)

; ----------------------------------------------------------------------
#|
; get-clauses: Pred-symbol x Clause-list -> Clause-list where head matches
; This is an optimisation not currently used.

(defun get-clauses (p cl)
  "Get those clauses (facts and rules) from cl (the kb) which match
   the (goal) predicate, and which are therefore candidates for unification"
  (remove-if-not #'(lambda (c) (equalp p (pred (axiom-head-literal c)))) cl) )
|#

; initialise-databases: Clause-list x Clause-list -> Extended-Clause versions
; note - args are clause lists (from user), not extended clauses

(defun initialise-databases (goal-list axiom-list)  
   "Takes the goal and the program and creates extended clause list versions"
  (let* ((index     (list 0 (list nil nil)))
         (ia-list   (mk-extended-clauses index axiom-list nil))
         (new-index (list (apply #'max (mapcar #'caar ia-list))(list nil nil)))
         (igb-list  (mk-extended-clauses new-index goal-list +no-bindings+)))
      (list igb-list ia-list) ) )

(defun mk-extended-clauses (index clause-list bindings) ; -> ig(b)-list
  (cond ((null clause-list) nil)
         (bindings (let ((index1 (incr-index index)) )
                     (cons (list index1 (car clause-list) bindings)  ; goal list
                        (mk-extended-clauses index1 (cdr clause-list) bindings))))
         (t     (let ((index1 (incr-index index)) )
                   (cons (list index1 (car clause-list))           ; axiom list
                      (mk-extended-clauses index1 (cdr clause-list) bindings))))))

(defun incr-index (i) (list (+ (car i) 1) (cadr i)) ) ; Just ups the first number

;   (incr-index '(19 ((17 ((14 (nil nil)) (10 (nil nil))))
;   (20 ((17 ((14 (nil nil)) (10 (nil nil)))

;;; ----------------------------------------------------------------------
;;;
;;;   Part 3: the theorem prover
;;;
;;;  ---  Binary Resolution
;;;
;;;   L1 <= M1,M2,M3      <= L1,G2
;;;   --------------------------------------------   L1 is resolved upon giving binding b
;;;          <= G2,M1,M2,M3                         The binding b is applied to the new goals
;;;

;  binary-resolve: Goal x Axiom x Bindings -> new-Goal x Bindings (after substs)
;  note that these are all clauses, not extended clauses

(defun binary-resolve (goal axiom1 b1)
  "goal =  a goal clause (previously extracted from extended-goal-list)
    axiom = a fact or rule clause (extracted from the extended-axiom-list)  
    b1 =    bindings stored with the goal clause"
  (let* ((axiom                (rename-variables axiom1)) ;rename vars in axiom
         (axiom-head-lit       (axiom-head-literal axiom))       ; remove '<-
         (axiom-body-lit-list  (axiom-body-literal-list axiom))  ; remove '<-
         (goal-lit-list        (goal-literal-list goal))         ; remove '<-
         (head-goal-lit        (car goal-lit-list))
         (other-goal-lits      (cdr goal-lit-list))
         (b                    (unify axiom-head-lit head-goal-lit b1)) )
    (cond ((equal b '+fail+)
           (list '+no-resolvant+ '+fail+))
          ( t (if (and (null axiom-body-lit-list) (null other-goal-lits))
                  (list '+empty-clause+ b)
                (list (mk-goal-clause
                       (mapcar #'(lambda (literal)
                                   (subst-bindings b literal))
                               (append other-goal-lits axiom-body-lit-list) ))
                      b))))) )

; Example:
;  (binary-resolve  *g1*  *c12* +no-bindings+)
;  ((<- ((AMUSING ?WHO) (LIKES ROD ?WHO))) ((#:?Y871 . ?WHO) (#:?X870 . SALLY)))

;  one-step-prove:  Nat x ext-goal-list x axioms -> ext-goal-list
(defun one-step-prove (max-i igb-list ia-list)
  "Takes max index so far,  extended goal list,
   returns new extended goal list, (no +fail+s)"
  (let* ((igb       (car igb-list))   ; igb = extended goal clause, head of list
         (g-index   (first igb))      ; extract the goal clause index
         (goal      (second igb))     ; extract the goal clause itself
         (b1        (third igb))      ; extract the previously stored binding
         (a-indexes (mapcar #'first  ia-list))   ; list of indexes of axioms
         (a-list    (mapcar #'second ia-list))   ; list of axiom clauses
         (new-gb-list (mapcar #'(lambda (x) (binary-resolve goal x b1)) a-list)) )
    (mk-new-igb-list max-i g-index a-indexes new-gb-list) ) )

; Example:
; A detailed walkthrough of one-step-prove starting with the arguments ----

(defvar *igb-list-ia-list* (initialise-databases *goals* *axioms*))
(defvar *igb-list* (car *igb-list-ia-list*))
(defvar *ia-list* (cadr *igb-list-ia-list*))


;    *igb-list*                                           ; extended goals
; (((14 (nil nil)) (<- ((LIKES SALLY ?WHO))) ((T . T))))
;
;     (pprint *ia-list*)                                  ; extended axioms
#|
(((1 (NIL NIL)) ((MEMBER ?ITEM (?ITEM . ?REST)) <-))
 ((2 (NIL NIL)) ((MEMBER ?ITEM (? . ?REST)) <- ((MEMBER ?ITEM ?REST))))
 ((3 (NIL NIL)) ((LIKES ROD HORVATH) <-))
 ((4 (NIL NIL)) ((LIKES SALLY RENNER) <-))
 ((5 (NIL NIL)) ((LIKES SALLY ROD) <-))
 ((6 (NIL NIL)) ((LIKES HARDY RENNER) <-))
 ((7 (NIL NIL)) ((LIKES HARDY ROD) <-))
 ((8 (NIL NIL)) ((AMUSING HARDY) <-))
 ((9 (NIL NIL)) ((LIKES HORVATH MOTIES) <-))
 ((10 (NIL NIL)) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
 ((11 (NIL NIL)) ((LIKES ROD ?X) <- ((LIKES ?X RENNER) (LIKES ?X ROD))))
 ((12 (NIL NIL)) ((LIKES ?X ?Y) <- ((AMUSING ?Y) (LIKES ROD ?Y))))
 ((13 (NIL NIL)) ((LIKES ?X ?X) <-)))
|#
;
;    (setf igb (car *igb-list*))    ; curr extended goal clause
; ((14 (NIL NIL)) (<- ((LIKES SALLY ?WHO))) ((T . T)))
;
;    (setf g-index (first igb))
; (14 (NIL NIL))
;
;    (setf goal (second igb))
; (<- ((LIKES SALLY ?WHO)))
;
;    (setf b1 (third igb))
; ((T . T))
;
;    (pprint (setf a-indexes (mapcar #'first  *ia-list*)) )
#|
((1 (NIL NIL))
 (2 (NIL NIL))
 (3 (NIL NIL))
 (4 (NIL NIL))
 (5 (NIL NIL))
 (6 (NIL NIL))
 (7 (NIL NIL))
 (8 (NIL NIL))
 (9 (NIL NIL))
 (10 (NIL NIL))
 (11 (NIL NIL))
 (12 (NIL NIL))
 (13 (NIL NIL)))
|#
;
;    (pprint (setf a-list (mapcar #'second *ia-list*)))
#|
(((MEMBER ?ITEM (?ITEM . ?REST)) <-)
 ((MEMBER ?ITEM (? . ?REST)) <- ((MEMBER ?ITEM ?REST)))
 ((LIKES ROD HORVATH) <-)
 ((LIKES SALLY RENNER) <-)
 ((LIKES SALLY ROD) <-)
 ((LIKES HARDY RENNER) <-)
 ((LIKES HARDY ROD) <-)
 ((AMUSING HARDY) <-)
 ((LIKES HORVATH MOTIES) <-)
 ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES)))
 ((LIKES ROD ?X) <- ((LIKES ?X RENNER) (LIKES ?X ROD)))
 ((LIKES ?X ?Y) <- ((AMUSING ?Y) (LIKES ROD ?Y)))
 ((LIKES ?X ?X) <-))
|#
;
; - one-step-prove maps binary-resolve across axioms -> resolvants (if any):
;
; (pprint (setf new-gb-list (mapcar #'(lambda (x) (binary-resolve goal x b1)) a-list)))
;
#|
((+NO-RESOLVANT+ +FAIL+)
 (+NO-RESOLVANT+ +FAIL+)
 (+NO-RESOLVANT+ +FAIL+)
 (+EMPTY-CLAUSE+ ((?WHO . RENNER)))
 (+EMPTY-CLAUSE+ ((?WHO . ROD)))
 (+NO-RESOLVANT+ +FAIL+)
 (+NO-RESOLVANT+ +FAIL+)
 (+NO-RESOLVANT+ +FAIL+)
 (+NO-RESOLVANT+ +FAIL+)
 ((<- (#)) ((#:?X1003 . ?WHO)))
 (+NO-RESOLVANT+ +FAIL+)
 ((<- (# #)) ((#:?Y1006 . ?WHO) (#:?X1005 . SALLY)))
 (+EMPTY-CLAUSE+ ((?WHO . SALLY) (#:?X1007 . SALLY))))
|#

; and finally we remove the +FAIL+ values and update the clause indexes for the
; genuine new clauses - to return the list of additional igb clauses.
;
; That's the job of mk-new-igb-list.
;
;   (setf new-igb-list1 (mk-new-igb-list 14 g-index a-indexes new-gb-list) ))
;   (mapcar #'pprint new-igb-list1)
#|
((15 ((14 (NIL NIL)) (4 (NIL NIL)))) +EMPTY-CLAUSE+
     ((?WHO . RENNER)))
((16 ((14 (NIL NIL)) (5 (NIL NIL)))) +EMPTY-CLAUSE+
     ((?WHO . ROD)))
((17 ((14 (NIL NIL)) (10 (NIL NIL)))) (<- ((LIKES ?WHO MOTIES)))
     ((#:?X1003 . ?WHO)))
((18 ((14 (NIL NIL)) (12 (NIL NIL)))) (<- ((AMUSING ?WHO) (LIKES ROD ?WHO)))
     ((#:?Y1006 . ?WHO) (#:?X1005 . SALLY)))
((19 ((14 (NIL NIL)) (13 (NIL NIL)))) +EMPTY-CLAUSE+
      ((?WHO . SALLY) (#:?X1007 . SALLY)))


|#
;
; mk-new-igb-list: max-index x g-index x a-indexes x new-gb-list -> igb-list
; Read as 'make additional igb extended clauses' - to be added to the pot
; Takes the new resolvants from the latest goal and creates additional igb
; extended clauses to be added to the overall igb-list.

; mk-new-igb-list arguments:
; max-i is just a number, the highest clause number yet allocated, eg 7
; a-index: eg (3 (nil nil))
; g-index: eg  (15 ((3 (nil nil)) (2 (nil nil))))
; a-indexes: eg ((1 (nil nil)) (2 (nil nil)) ...  (n (nil nil)) )
; new-gb-list: eg ((+no-resolvant+ +fail+) (goal b1) (+empty-clause+ b2) ..)
; the task here is to remove +fail+s and update the indexes.

(defun mk-new-igb-list (max-i g-index a-indexes new-gb-list)
  (if (null new-gb-list)
      nil
    (let* ((a-index        (car a-indexes))  ;(i (nil nil)) index for axiom
           (a-rest-indexes (cdr a-indexes))  ;remaining axiom indexes
           (gb             (car new-gb-list))
           (gb-rest        (cdr new-gb-list))
           (g              (first  gb))  
           (b              (second gb)) )
      (cond ((equal b '+fail+)
                (mk-new-igb-list max-i g-index a-rest-indexes gb-rest))
            (t  (let ((new-index (list (+ max-i 1) (list g-index a-index))))
                  (cons (list new-index g b)
                        (mk-new-igb-list (+ max-i 1) g-index
                                          a-rest-indexes gb-rest))))))))


;   (pprint (setf new-igb-list (one-step-prove 14 *igb-list* *ia-list*)))
;
; [Same result as from (mapcar #'pprint new-igb-list1) above].

;;; -- Now we look to the top-level loop which iterates until termination --

(defvar *1step* t)  ; If t stops proof loop each iteration and
;                          prints archived-proofs & goals

(defun empty-clause-p (igb-clause)              ;  -> Bool
   (equal '+empty-clause+ (second igb-clause)))

(defun prove1 (archived-goals archived-proofs igb-list ia-list depth)
 ; -> archived-proofs
  "'prove1' iterates, finally returning archived-goals and archived-proofs
   for subsequent proof extraction"
  (if (or (zerop depth) (null igb-list))
      (if *1step* (progn (terpri)
                         (princ "Done: archived-goals = ")
                         (pprint archived-goals) (terpri)
                         (princ "Archived-proofs = ")
                         (pprint archived-proofs) (terpri)
                         (list archived-goals archived-proofs))
                   (list archived-goals archived-proofs))
    (let* ((max-i (apply #'max (mapcar 'caar (append archived-proofs igb-list))))
           (new-igb-list1     (one-step-prove max-i igb-list ia-list))
           (new-empty-clauses (remove-if-not #'empty-clause-p new-igb-list1))
           (new-igb-list      (remove-if #'empty-clause-p new-igb-list1))
           (archived-goals1   (cons (car igb-list) archived-goals))
           (archived-proofs1  (append new-empty-clauses archived-proofs))
           (igb-list1         (cdr igb-list))
           (next-igb-list     (append igb-list1 new-igb-list)) )
           ; depth first: reverse append order for breadth first
       (if *1step* (progn (terpri)
                          (princ "Depth = ") (princ depth)
                          (princ ". Archived-goals1 = ")
                          (pprint archived-goals1) (terpri) (terpri)
                          (princ "Archived-proofs1 = ")
                          (pprint archived-proofs1) (terpri) (terpri)
                          (princ "next-igb-list = ")
                          (pprint next-igb-list)  (terpri)
                          (princ "type return: ") (terpri)
                          (read-char) (princ "-----") ))
      (prove1 archived-goals1 archived-proofs1
                  next-igb-list ia-list (- depth 1)))))

(defun prove (goals axioms depth) ;goals and axioms are input clause-lists
  "prove turns initial clauses into extended-clauses then calls prove1.
   Returns a 2 member list of extended clauses:(archived-goals archived-proofs)"
   (let* ((igb-ia (initialise-databases goals axioms))
          (igb-list (first igb-ia))
          (ia-list (second igb-ia))
          (initial-archived-goals  nil)
          (initial-archived-proofs nil) )
     (prove1 initial-archived-goals initial-archived-proofs igb-list ia-list depth)))

; 'archived-proofs' is a list of igb extended clauses
; (setf *archived-proofs* (prove *goals* *axioms* 4))
;
; --- Transform archived-proofs into a readable collection of proofs --
; Note that 'archived-goals-and-proofs' =  a two-member list:
;     (archived-goals archived-proofs)
; is returned by 'prove'.

(defun show-all-proofs (archived-goals-and-proofs ia-list goals)
  (let* ((archived-goals  (first  archived-goals-and-proofs))
         (archived-proofs (second archived-goals-and-proofs))
         (all-igbs  (append archived-goals archived-proofs ia-list))
         (proofs (mapcar #'(lambda (x) (show-proof x all-igbs goals))
                              archived-proofs)))
     (reverse proofs)))

(defun show-proof (empty-igb all-igbs top-level-goals)
 "empty-igb is the root of the proof tree we construct here"
       (let* ((index     (first empty-igb))
              (answer    (subst-bindings (third empty-igb) top-level-goals))
              (proof     (mk-proof-list index all-igbs)) )
          (list answer (reverse proof))) )

(defun mk-proof-list (i all-igbs)
  "Traverses index i as a tree pulling out the matching clauses"
   (if (null i)
        nil
      (let* ((current-igb-# (first i))
             (current-igb (get-igb-by-n current-igb-# all-igbs))
             (current-clause (second current-igb))
             (left-ancestor  (first (second i)))
             (right-ancestor (second (second i)))
             (tg (list (car i) (first (first (second i)))
                               (first (second (second i))))))
         (cons (list tg current-clause)
               (append (mk-proof-list left-ancestor  all-igbs)
                       (mk-proof-list right-ancestor all-igbs) ))) ) )

(defun get-igb-by-n (n igb-list)  ; nat x igb-list -> igb
  " example (n=3) returns ((3 (nil nil)) ((LIKES ROD HORVATH) <-)) "
   (find-if #'(lambda (igb) (= n (caar igb))) igb-list))


;;; ----------------------------------------------------------------------
;;;
;;;  Part 4:  TEST DATA

#|

With goal: (setf *goals* (list *g1*))

   ((<- ((LIKES SALLY ?WHO))))

   (defvar *igb-list-ia-list* (initialise-databases *goals* *axioms*))
   (defvar *igb-list* (car *igb-list-ia-list*))  ; ext. goal clause(s)
   (defvar *ia-list* (cadr *igb-list-ia-list*))  ; ext. axiom clauses

; The above three assignments were eval'ed above so already bound.
; Prove itself calls 'initialise-databases' to set up extended clauses

 (setf archived-goals-and-proofs (prove *goals* *axioms* 6))   ; depth 6 here

(pprint (setf all-proofs (show-all-proofs archived-goals-and-proofs *ia-list* *goals*)))

Result: (with a few added line-breaks)

((((<- ((LIKES SALLY SALLY))))
  (((13 NIL NIL) ((LIKES ?X ?X) <-))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((19 14 13) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY ROD))))
  (((5 NIL NIL) ((LIKES SALLY ROD) <-))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((16 14 5) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY RENNER))))
  (((4 NIL NIL) ((LIKES SALLY RENNER) <-))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((15 14 4) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY MOTIES))))
  (((13 NIL NIL) ((LIKES ?X ?X) <-))
   ((10 NIL NIL) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((17 14 10) (<- ((LIKES ?WHO MOTIES))))
   ((24 17 13) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY HORVATH))))
  (((9 NIL NIL) ((LIKES HORVATH MOTIES) <-))
   ((10 NIL NIL) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((17 14 10) (<- ((LIKES ?WHO MOTIES))))
   ((20 17 9) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY SALLY))))
  (((13 NIL NIL) ((LIKES ?X ?X) <-))
   ((10 NIL NIL) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
   ((10 NIL NIL) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((17 14 10) (<- ((LIKES ?WHO MOTIES))))
   ((21 17 10) (<- ((LIKES MOTIES MOTIES))))
   ((27 21 13) +EMPTY-CLAUSE+))))

; -------------------------------------------------------------------------

With goal: (setf *goals* (list *g2*))

   ((<- ((MEMBER SALLY (ROD RENNER HORVATH SALLY . MOTIES)))))


(pprint (setf all-proofs (show-all-proofs archived-goals-and-proofs *ia-list* *goals*)))

((((<- ((MEMBER SALLY (ROD RENNER HORVATH SALLY . MOTIES)))))
  (((1 NIL NIL) ((MEMBER ?ITEM (?ITEM . ?REST)) <-))
   ((2 NIL NIL) ((MEMBER ?ITEM (? . ?REST)) <- ((MEMBER ?ITEM ?REST))))
   ((2 NIL NIL) ((MEMBER ?ITEM (? . ?REST)) <- ((MEMBER ?ITEM ?REST))))
   ((2 NIL NIL) ((MEMBER ?ITEM (? . ?REST)) <- ((MEMBER ?ITEM ?REST))))
   ((14 NIL NIL) (<- ((MEMBER SALLY (ROD RENNER HORVATH SALLY . MOTIES)))))
   ((15 14 2) (<- ((MEMBER SALLY (RENNER HORVATH SALLY . MOTIES)))))
   ((16 15 2) (<- ((MEMBER SALLY (HORVATH SALLY . MOTIES)))))
   ((17 16 2) (<- ((MEMBER SALLY (SALLY . MOTIES)))))
   ((18 17 1) +EMPTY-CLAUSE+))))

|#

; --------------------------- End of program -------------------------------

My theorem-prover is (basically) working

My resolution theorem prover is working: find the documentation and code here.

My initial, rather simple test data concerns Sally Fowler's situation aboard the battleship MacArthur ('The Mote in God's Eye').



Who does Sally like: ←  (LIKES SALLY ?WHO)

Well, here are the Prolog-style facts.
 (LIKES ROD HORVATH) ←
 (LIKES SALLY RENNER) ←
 (LIKES SALLY ROD) ←
 (LIKES HARDY RENNER) ←
 (LIKES HARDY ROD) ←
 (AMUSING HARDY) ←
 (LIKES HORVATH MOTIES) ←
 (LIKES SALLY ?X) ← (LIKES ?X MOTIES)
 (LIKES ROD ?X) ← (LIKES ?X RENNER) (LIKES ?X ROD)
 (LIKES ?X ?Y) ← (AMUSING ?Y) (LIKES ROD ?Y)
 (LIKES ?X ?X) ←
Based on the above, the theorem-prover offers the following five responses. I doubt you are overwhelmed ... but then, test data is designed to be easy to understand.
1, Because everyone likes themselves, Sally likes Sally.

2. Because it's mentioned Sally likes Rod, she does.

3. Because it's mentioned Sally likes Renner, she does.

4. Sally likes those who like Moties. Moties like Moties because everyone likes themselves. So Sally likes Moties.

5. Sally likes people who like Moties. Horvath like Moties.  So Sally likes Horvath.
More precisely, after a little editing (inserted line-breaks), this is what was printed out.
((((<- ((LIKES SALLY SALLY))))
  (((13 NIL NIL) ((LIKES ?X ?X) <-))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((19 14 13) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY ROD))))
  (((5 NIL NIL) ((LIKES SALLY ROD) <-))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((16 14 5) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY RENNER))))
  (((4 NIL NIL) ((LIKES SALLY RENNER) <-))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((15 14 4) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY MOTIES))))
  (((13 NIL NIL) ((LIKES ?X ?X) <-))
   ((10 NIL NIL) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((17 14 10) (<- ((LIKES ?WHO MOTIES))))
   ((24 17 13) +EMPTY-CLAUSE+)))

 (((<- ((LIKES SALLY HORVATH))))
  (((9 NIL NIL) ((LIKES HORVATH MOTIES) <-))
   ((10 NIL NIL) ((LIKES SALLY ?X) <- ((LIKES ?X MOTIES))))
   ((14 NIL NIL) (<- ((LIKES SALLY ?WHO))))
   ((17 14 10) (<- ((LIKES ?WHO MOTIES))))
   ((20 17 9) +EMPTY-CLAUSE+)))

Figure 1
---

Q&A.

1. Isn't this just Prolog?

Prolog is encountered as a black box. You provide a program and a query (as above) and you get back bindings, like this:
?- likes(sally, W).

W = sally;
W = rod;
... and so on

If you want proofs, to add the 'occurs check' or to change the order in which resolution steps are tried (the search strategy) - well tough: all those things are both fixed and opaque in Prolog. To make them explicit for modification, you have to write an explicit theorem-prover in Prolog (which can of course be done).

2. You are using Horn clauses?

Yes. I initially thought to implement a general clausal prover, but Horn clauses make the resolution step particularly simple (just one positive literal to match) and you lose neither generality nor expressive power. But since everything in the code is both modular and explicit, it would be easy to extend the program.

3. The style is functional?

Yes. Resolution theorem provers on the Internet are heavily optimised with clever, imperative algorithms and direct access data structures such as hash tables. This makes the code efficient but obscure - very hard to understand.

I didn't want to do that. This is a tool-kit and I'm not trying to create thousands of logical inferences per second. My intended application area (simple inference over an ever-changing knowledge-base for a chatbot) never requires massive inferential chains so clarity and easy modifiability was my objective.

4. What was hard?

The program is architected at three levels.

(a) Unification

This is basically already quite modular and straightforward. I re-used Peter Norvig's code.

(b) Resolution

Binary resolution itself - specially with Horn clauses - is a straightforward procedure - as you will see when I publish the code. There are some subtleties with bindings and substitutions, but once you realise that resolution is fundamentally a local operation it's not too difficult.

(c) Control and proof construction

The process of creating new goals and resolving them with the axioms is somewhat complex although again, sticking with Horn clauses makes it a lot easier: just think of a naive Prolog execution model.

However, if you want to return proofs, you need to number the axioms and number-and-archive goals as you process them, capturing the resulting tree of inferences. At the end, for each empty-clause = successful proof, you need to trace that index-tree backwards to recover the proof steps. That's what created figure 1 above. I found it a bit intricate.

5. What's next?

In theory you can do anything with a theorem-prover (Prolog being the existence proof) but it's not necessarily the best architecture. For a planner, where state changes are part of the problem definition, I need to adapt the tool-kit to a design centred around actions with pre- and post-condition in the context of a goal-state and an updating world-state. Such a dynamic model can be used both for actions in a (virtual) world and conversation planning via speech acts.

The theorem-prover remains optimal as a separate module, managing the crystallized knowledge base using inference to draw conclusions - for example to drive questions and answers.

I'm thinking of using the already-programmed Eliza front-end as a robust conversational interface. Doesn't matter if it's bluffing some of the time if it can use learning, inference and planning often enough.

Onwards to the GOFAI smart chatbot ...

---

Here is the documentation and code.

Friday, March 03, 2017

Diary: programming is hard!

I remember a post by James Thompson (can't find it - sorry) describing the joy of giving IQ tests to bright students. Their smug condescension as they breeze through the early items; their horror as they hit their cognitive limits and start to sweat: "Wait a mo, .., just can't seem to see it ... ."

In artificial intelligence textbooks it's standard to cover the coding of theorem provers for propositional-calculus. Hidden away in the advanced exercises is the suggestion to extend the program to cover the full predicate-calculus. Inevitably, this is flagged as hard.

Tell me about it. Today I got basic binary resolution to work. Great! I can do inference. And yet this is not the central mystery of a predicate-calculus resolution theorem prover (RTP).

The conceptual problems arise from the layers of abstraction around the proof procedure. At the risk of boring you even further, binary resolution is driven by the unification of complementary literals (basic formulae). These two literals occur in the two clauses offered to the resolution function (hence binary resolution).

A  problem addressed by an RTP comprises: assumptions  = the axioms, a set of clauses, plus the problem itself, a goal clause.

Here's my working example. First come the axiom clauses:
(defvar *c3*   (mk-fact-clause '(likes rod horvath)))
(defvar *c4*   (mk-fact-clause '(likes sally renner)))
(defvar *c5*   (mk-fact-clause '(likes sally rod)))
(defvar *c6*   (mk-fact-clause '(likes hardy renner)))
(defvar *c7*   (mk-fact-clause '(likes hardy rod)))
(defvar *c8*   (mk-fact-clause '(amusing hardy)))
(defvar *c9*   (mk-fact-clause '(likes horvath moties)))
(defvar *c10* (mk-rule-clause '(likes sally ?x) '((likes ?x moties)) ) )
(defvar *c11* (mk-rule-clause '(likes rod ?x) '((likes ?x renner) (likes ?x rod)) ) )
(defvar *c12* (mk-rule-clause '(likes ?x ?y) '((amusing ?y) (likes rod ?y)) ) )
(defvar *c13* (mk-fact-clause '(likes ?x ?x)))

(defvar *g1* (mk-goal-clause '((likes sally ?who)) ) )  ; this is the problem clause
You may notice a "Mote in God's Eye" theme here.

Despite the simplicity of these facts and rules, you'll struggle a bit to figure out the complete list of who, exactly, Sally Fowler likes.

I expect better from my program - once completed.

---

So a clause might look like this (which you will recognise as a Prolog-style Horn-clause):
'( (likes sally ?x) ← ((likes ?x ?y) (likes ?y moties)) )   ; a rule clause
which is already a bit complicated.

Then we have to set up structures for the knowledge-base (facts and rules), the goals which emerge from the resolution process, and the pointers and bindings which allow a reconstruction of proofs.

---

In telling you this, my intention is not to document the program, my point is more psychological.

You need to keep a very great deal of abstract structure in your head simultaneously if you hope to put this not-entirely-trivial program together. Plus lots of functions.

It's very g-loaded, much like doing mathematics.
"It's quite hard getting figures but ... it appears that the average IQ of students applying for PhD programmes at good universities in computer science is around 128.

"That sounds right to me. Most software development companies don't take people without good first degrees or a higher degree in a STEM subject - and that's just the pool we're talking about here."
---

Parenthetically, thank God for Lisp, a gift from the deity for clear, conceptual, exploratory thinking.

---

I read endless texts in the media from innumerate journalists about how unemployed or newly-redundant workers are going to retrain as software developers. Those writers couldn't hack it themselves and sadly, neither will most of those no-longer-required workers.
"Wait a mo, ..., just can't seem to see it ... ."
At an IQ threshold of 128, we're talking about 2.5% of a population normed at 100.

A caveat. It's intellectually hard writing machine learning programs or configuring recalcitrant artificial neural nets on novel problem-domains .. or programming RTPs. Yet for everyone doing those things, there are ten people paid to use graphical web design tools or load databases, or do a bit of scripting. Things which are not that hard.

Like many occupations, there has been mission-creep in the definition of software developer.

---

And why am I making such heavy weather of this wretched theorem-prover? This.

Wednesday, March 01, 2017

Diary: chronicles of wasted time

There were several things I toyed with writing about today.

1. China Miéville on the Nazis

The Nazis (short for National Socialists, lest anyone forget) are uniquely demonised in contemporary culture. It's not so much that the Nazis did uniquely bad things - most of their heinous deeds recapitulated acts previously implemented by many other powers: in fact the Nazi edge in atrocity tended to be greater efficiency. Nazis are demons mostly because they took such pride in their murderous policies. They were the paradigmatic nasty party. They flagrantly and consistently failed to show generalised compassion.

Readers familiar with Jonathan Haidt's Moral Foundations Theory will recognise the red-flag value-atrocity for liberals here. By promoting the 'Aryan race' and classifying Jews, homosexuals, the disabled and non-'Aryans' in general as sub-human (and therefore worthy of extermination), Nazism was xenophobia personified.

You can see why nationalists like Trump, Le Pen and the rest are so easily compared to Hitler. Shockingly, they explicitly disavow liberal globalism and human universalism.

We know the 'downsides' to Nazism, strongly amplified by the fact they lost. But the Pollyanna world of Liberalism - where everyone is 'nice' all the time - doesn't always reflect reality either.

Especially when you engage with people who don't tend - or intend - to play nice at all.

Ordinary folk are generally not liberals, and once the liberal elite makes a hash of it they get somewhat agitated. How agitated? Depends on how bad things get.

The Nazis were quite popular in the grim thirties, and not just amongst ordinary Germans.

Somehow these deeper truths about human nature - and what humans will do to survive in extremis - appear to have evaded China Miéville. The root causes of fascist movements aren't really manifest through the metaphor of shallow, lifeless Nazi art.

Anyway, I decided, meta-fictionally, not to write about that, because writing anything at all about the Nazis gets you into trouble.


2.  Who's going to win the French Presidential Election 2017?

The first round is Sunday, 23 April and the second round is two weeks later, 7 May 2017. The Media is gushingly excited that Emmanuel Macron of En Marche! will win the second round decisively against Marine Le Pen of the Front National.

Take that, you fascists!

---

*Eyebrows raised*.

Macron is somewhere between the Tony Blair and Nick Clegg of the French governing elite. And about as popular as either amongst the non-bubble masses.

In The Times today, British neocon op-ed writer Roger Boyes calls it for Le Pen, citing subterranean popular discontent.
"Le Pen has kept momentum; she has (unlike Ukip) a proper party machine behind her which is able to guarantee that declared voting intentions turn into actual votes. She is polling at 45 per cent if she runs against François Fillon in the second round; not many on the left will be able to vote for a man who promises to cut half a million public service jobs.

Against Emmanuel Macron she is polling at 42 per cent in the run-off; it doesn’t take much more to give her victory. That’s Macron, the pro-European, elite-educated, former investment banker Blairite who once served as economics minister under François Hollande — how desperate does a country have to be to imagine that he could be a saviour, rather than a prettifier of the old politics?"
I am not quite so sure. I suspect the French media will do a pretty good job on Le Pen, accusing her of plotting to break up the EU - something which the French masses still find scary, with all those old Nazi occupation memories and a resurgent Germany.

If I had to call it, I'd agree that Macron is toast, even froth. But this Penelopegate thing is perhaps forgivable by a cynical electorate; if so, François Fillon's traditionalist nationalism will strike a chord in the second round.

If he gets that far, it's winnable for Les Républicains.

Anyway, I was going to write about that but .. what do I know about French politics?


3. And what about your theorem prover?

Yes, hmm, shuffles papers. You know, I thought and thought about the control architecture. I'm good with unification and binary resolution - all those functions are well-understood and work. But generating the proof space (which Prolog does by depth-first search with backtracking) is a bit intricate and involves quite complex clausal data structures, particularly if you want to number clauses, and keep track of previous goals and bindings to generate an explicit proof tree.

So it's a bit, well, .. boring - and I feel under-motivated today. Which is why I'm wasting my time telling you all this.