;;; DEDUCE is a deductive retrieval system which uses generators to retrieve one ;;; answer at a time and also generates proofs. It currently supports only ;;; backward chaining. A depth bound in number of rules can be given. ;;;; Copyright (c) 1988 by Raymond Joseph Mooney. This program may be freely ;;;; copied, used, or modified provided that this copyright notice is included ;;;; in each copy of this code and parts thereof. ;;; Variables are represented with a leading "?". Backchaining rules are of ;;; the form (<- consequent . antecedents) and are indexed by predicate name ;;; of the consequent (i.e. "CAR indexing"). The function index-brules can be ;;; used to enter these rules. Facts are also indexed by predicate and ;;; the function index-facts can be used to enter them. ;;; The file CUP-DOMAIN is an example of a domain file which defines rules and facts ;;; The function clear-facts clears the database of facts and the function dump-facts ;;; returns all facts in the database. Similar functions (clear-brules dump-brules) ;;; exist for backchaining rules. ;;; After a set of rules and facts have been entered, a request can be retrieved by ;;; calling the function (retrieve
) (e.g. (retrieve '(cup obj1) 3)) ;;; Retrieve returns a generated list of answers which should be manipulated using only ;;; the functions gfirst, grest, and gnull which perform the obvious operations on ;;; generated lists. A generated list computes elements of the list only as they are ;;; needed, this allows the retriever to return one answer at a time like PROLOG. ;;; An answer from retrieve is of the form: ( ) where is a ;;; list of variable bindings which create an answer if applied to using ;;; the function (substitute-vars ). A is defined recursively ;;; as either a fact from the database which matches or a proof based on a rule: ;;; (rule-proof (( )( )...)) ;;; where and 's constitute a uniquely variablized rule. ;;; Each ( ) in a rule-proof is called an ante-proof. ;;; This form of proof constitutes an "explanation structure" and can be easily used to ;;; generate a generalized proof. A compact version of the specific proof for the given ;;; answer can be generated by calling the function (specific-proof ). ;------------------------------------------------------------------------------ ; GENERATORS ;------------------------------------------------------------------------------ ;;; Defines the function DELAY to create a generated list and the functions ;;; GFIRST, GREST, and GNULL for getting items from and testing a generated list. ;;; See AI Programming 2nd ed. for details (defstruct (generator (:print-function print-generator)) closure (value nil) (forced-p nil)) (defun print-generator (g stream depth) (declare (ignore g depth)) (format stream "#")) (defmacro delay (&body body) `(make-generator :closure #'(lambda () ,@body))) (defun force (x) (cond ((not (generator-p x)) x) ((generator-forced-p x)(generator-value x)) (t (prog1 (setf (generator-value x) (funcall (generator-closure x))) (setf (generator-forced-p x) t))))) (defun normalize (g) (cond ((null g) nil) ((not (generator-p (first g))) g) (t (let ((new-g (append (force (first g)) (rest g)))) (cond ((null new-g) nil) (t (setf (first g) (first new-g)) (setf (rest g) (rest new-g)) (normalize g))))))) (defun gfirst (x) (first (normalize x))) (defun grest (x) (normalize (rest (normalize x)))) (defun gnull (x) (null (normalize x))) ;------------------------------------------------------------------------------ ; UNIFICATION ;------------------------------------------------------------------------------ ;;; This section defines the unification pattern matcher. A variable must ;;; begin with a leading "?" which is used by the reader to create a structure. ;;; The unify function takes two patterns and a binding list and unifies the ;;; patterns in the context of the current bindings and returns an updated ;;; binding list. A binding list is of the form: ;;; (T ( )( )...) ;;; The leading T is used to distinguish the empty binding list: (T) from ;;; failure to unify for which NIL is returned. ;;; See AI Programming 2nd ed. for details (slightly different). (defstruct (pcvar (:print-function print-pcvar)) id) (defun print-pcvar (var stream depth) (declare (ignore depth)) (format stream "?~S" (PCVAR-ID var))) (set-macro-character #\? #'(lambda (stream char) (declare (ignore char)) (if (member (peek-char nil stream t nil t) '(#\space #\newline #\) #\( #\' #\" #\return #\linefeed #\tab #\` #\# #\,)) (quote \?) (make-pcvar :id (read stream t nil t)))) t) (defvar *occur-check* t) ; Performs an occur check if T (defvar *use-gensyms* t) ; Uses gensym to create unique variables if T ; otherwise uses copy-symbol (defun unify (a b &optional (bindings (list t))) ;;; Return a most general binding list which unifies a & b (cond ((eql a b) bindings) ((pcvar-p a) (var-unify a b bindings)) ((pcvar-p b) (var-unify b a bindings)) ((or (atom a)(atom b)) nil) ((setf bindings (unify (first a)(first b) bindings)) (unify (rest a) (rest b) bindings)))) (defun var-unify (var b bindings) ;;; Unify a variable with a wff, if must bind variable and *occur-check* ;;; flag is set then check for occur-check violation (if (and (pcvar-p b) (var-eq var b)) bindings (let ((binding (get-binding var bindings))) (cond (binding (unify (second binding) b bindings)) ((and (pcvar-p b)(bound-to-p b var bindings)) bindings) ((or (null *occur-check*) (free-in-p var b bindings)) (add-binding var b bindings)))))) (defun var-eq (var1 var2) ;;; Return T if the two variables are equal (eql (pcvar-id var1)(pcvar-id var2))) (defun get-binding (var bindings) ;;; Get the variable binding for var (assoc var (rest bindings) :test #'var-eq)) (defun add-binding (var val bindings) ;;; Add the binding of var to val to the existing set of bindings (setf (rest bindings) (cons (list var val) (rest bindings))) bindings) (defun bound-to-p (var1 var2 bindings) ;;; Check if var1 is eventually bound to var2 in the bindings (cond ((equalp var1 var2) t) ((let ((val (second (get-binding var1 bindings)))) (and val (pcvar-p val) (bound-to-p val var2 bindings)))))) (defun free-in-p (var b bindings) ;;; Return T if var does not occur in wff b (cond ((equalp var b) nil) ((pcvar-p b) (free-in-p var (second (get-binding b bindings)) bindings)) ((atom b) t) ((and (free-in-p var (first b) bindings) (free-in-p var (rest b) bindings))))) (defun substitute-vars (form bindings) ;;; Substitute the variables in form for their ultimate values specified ;;; in the bindings (if (rest bindings) (substitute1 form bindings) form)) (defun substitute1 (form bindings) (cond ((null form) nil) ((pcvar-p form) (let ((binding (get-binding form bindings))) (if binding (substitute1 (second binding) bindings) form))) ((atom form) form) (t (cons (substitute1 (first form) bindings) (substitute1 (rest form) bindings))))) (defun uniquify-variables (form) ;;; Make all the variables in form "unique" gensymed variables (let ((new-names (rename-list form nil))) (if (null new-names) form (rename-variables form new-names)))) (defun rename-list (form &optional new-names) (cond ((pcvar-p form) (let ((id (pcvar-id form))) (if (assoc id new-names) new-names (cons (list id (make-pcvar :id (if *use-gensyms* (gensym) (copy-symbol id)))) new-names)))) ((consp form) (rename-list (rest form) (rename-list (first form) new-names))) (t new-names))) (defun rename-variables (form new-names) (cond ((pcvar-p form) (let ((entry (assoc (pcvar-id form) new-names))) (if entry (second entry) form))) ((atom form) form) (t (cons (rename-variables (first form) new-names) (rename-variables (rest form) new-names))))) ;------------------------------------------------------------------------------ ; DEDUCTIVE RETRIEVER ;------------------------------------------------------------------------------ (defvar *rule-predicates* nil "List of predicates for known rule consequents") ;;; Define access functions for backchaining rules (defun brule-p (x) (and (consp x) (eq (first x) '<-))) (defun brule-consequent (brule) (second brule)) (defun brule-antecedents (brule) (cddr brule)) (defun index-brules (rules) ;;; Index a list of rules using "car indexing" ;;; Like PROLOG, rules are tried in the order presented. ;;; Each call to index-brules clears previous rules stored on the consequent ;;; predicates. (setf *rule-predicates* nil) (dolist (rule rules) (setf rule (uniquify-variables rule)) (let ((predicate (first (brule-consequent rule)))) (cond ((member predicate *rule-predicates*) (nconc (get predicate 'brules)(list rule))) (t (setf (get predicate 'brules) (list rule)) (push predicate *rule-predicates*)))))) (defun push-brule (rule) ;;; Push a backchain rule onto the front of the rules for that predicate ;;; so it is tried first in the future. (push rule (get (first (brule-consequent rule)) 'brules))) (defun dump-brules () ;;; Return a list of all known backchaining rules (mapcan #'(lambda (predicate) (copy-list (get predicate 'brules))) *rule-predicates*)) (defun clear-brules () (dolist (predicate *rule-predicates*) (setf (get predicate 'brules) nil)) (setf *rule-predicates* nil)) (defvar *fact-predicates* nil) ; Stores the list of all predicates which have facts ; indexed on their fact properties (defun index-facts (facts) ;;; Add facts to the database using "car indexing" (dolist (fact facts) (index-fact fact))) (defun index-fact (form) (let ((existing-facts (get (first form) 'facts))) (cond ((and existing-facts (not (member form existing-facts :test #'equal))) (nconc existing-facts (list form))) ((null existing-facts) (setf (get (first form) 'facts) (list form)) (push (first form) *fact-predicates*)))) form) (defun dump-facts () ;;; Return a list of all facts in the database. (mapcan #'(lambda (predicate) (copy-list (get predicate 'facts))) *fact-predicates*)) (defun clear-facts () ;;; Clear the database of facts (dolist (predicate *fact-predicates*) (setf (get predicate 'facts) nil)) (setf *fact-predicates* nil)) ;;; Fact and rule retrievers are explicitly defined so they can be changed as ;;; needed, e.g. to use a descrimination net instead. (defvar *fact-indexer* #'index-fact) (defvar *brule-retriever* #'(lambda (form) (get (first form) 'brules))) (defvar *fact-retriever* #'(lambda (form) (get (first form) 'facts))) (defstruct (rule-proof (:type list) :named) consequent ante-proofs) (defun retrieve (form &optional (depth-limit 100)) ;;; Return a generated list of answers but don't search more than ;;; depth-limit rules deep. (let ((from-database (mapcan #'(lambda (pos-answer) (let ((bindings (unify form pos-answer))) (if bindings (list (list bindings pos-answer))))) (funcall *fact-retriever* form)))) (cond ((zerop depth-limit) ; if exhausted depth limit then only try known facts ; and rules with out antecedents (nconc from-database (list (delay (try-rules form (remove-if #'brule-antecedents (funcall *brule-retriever* form)) depth-limit))))) (t (nconc from-database (list (delay (try-rules form (funcall *brule-retriever* form) depth-limit)))))))) (defun try-rules (form rules depth-limit) (cond ((null rules) nil) ((let ((bindings (unify (brule-consequent (first rules)) form))) (if bindings (let ((new-names (rename-list (first rules)))) (nconc (backchain (rename-variables (first rules) new-names) (rename-variables bindings new-names) depth-limit) (list (delay (try-rules form (rest rules) depth-limit))))) (try-rules form (rest rules) depth-limit)))))) (defun backchain (rule bindings depth-limit) (make-answers rule (retrieve-conjuncts (brule-antecedents rule) bindings depth-limit) bindings)) (defun make-answers (rule ante-answers bindings) (cond ((gnull ante-answers) nil) (t (list (list (first (gfirst ante-answers)) (make-rule-proof :consequent (brule-consequent rule) :ante-proofs (rest (gfirst ante-answers)))) (delay (make-answers rule (grest ante-answers) bindings)))))) (defun join-bindings (bindings1 bindings2) (append bindings1 (rest bindings2))) (defun retrieve-conjuncts (conjuncts bindings depth-limit) (cond ((null conjuncts) (list (list bindings))) (t (try-answers (first conjuncts) (retrieve (substitute-vars (first conjuncts) bindings) (1- depth-limit)) (rest conjuncts) bindings depth-limit)))) (defun try-answers (conjunct conj-answers remaining-conjuncts bindings depth-limit) (cond ((gnull conj-answers) nil) (t (let* ((conj-answer (gfirst conj-answers)) (remaining-answers (retrieve-conjuncts remaining-conjuncts (join-bindings (first conj-answer) bindings) depth-limit))) (nconc (join-answers conjunct conj-answer remaining-answers) (list (delay (try-answers conjunct (grest conj-answers) remaining-conjuncts bindings depth-limit)))))))) (defun join-answers (conjunct conj-answer remaining-answers) (cond ((gnull remaining-answers) nil) ((list (cons (first (gfirst remaining-answers)) (cons (list conjunct (second conj-answer)) (rest (gfirst remaining-answers)))) (delay (join-answers conjunct conj-answer (grest remaining-answers))))))) (defun specific-proof (answer) ;;; Return the specific proof for a particualr answer (instantiate-proof (second answer) (first answer))) (defun instantiate-proof (proof bindings) ;;; Return the proof created by instantiating the given proof structure ;;; with the given variable bindings (cond ((rule-proof-p proof) (cons (substitute-vars (rule-proof-consequent proof) bindings) (mapcar #'(lambda (ante-proof) (if (rule-proof-p (second ante-proof)) (instantiate-proof (second ante-proof) bindings) (substitute-vars (first ante-proof) bindings))) (rule-proof-ante-proofs proof)))) (t (substitute-vars proof bindings))))