Created
December 1, 2013 17:51
-
-
Save ahefner/7738300 to your computer and use it in GitHub Desktop.
Stand-alone version of PAIP "krep", extracted from paiprolog.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- | |
;;; Code from Paradigms of Artificial Intelligence Programming | |
;;; Copyright (c) 1991 Peter Norvig | |
;;; krep.lisp: Knowledge representation code; final version. | |
;;; Adds support for worlds and attached functions. | |
;;; Stand-alone version. | |
(defpackage :krep | |
(:use :common-lisp) | |
(:export | |
;; ?????? | |
)) | |
(in-package :krep) | |
;;; Auxfns: | |
(defconstant fail nil) | |
(defconstant no-bindings (if (boundp 'no-bindings) | |
(symbol-value 'no-bindings) | |
'((t . t)))) | |
(defun length=1 (x) | |
"Is x a list of length 1?" | |
(and (consp x) (null (cdr x)))) | |
(defun maybe-add (op exps &optional if-nil) | |
"For example, (maybe-add 'and exps t) returns | |
t if exps is nil, exps if there is only one, | |
and (and exp1 exp2...) if there are several exps." | |
(cond ((null exps) if-nil) | |
((length=1 exps) (first exps)) | |
(t (cons op exps)))) | |
(defun reuse-cons (x y x-y) | |
"Return (cons x y), or reuse x-y if it is equal to (cons x y)" | |
(if (and (eql x (car x-y)) (eql y (cdr x-y))) | |
x-y | |
(cons x y))) | |
(defun make-binding (var val) (cons var val)) | |
(defun binding-var (binding) | |
"Get the variable part of a single binding." | |
(car binding)) | |
(defun binding-val (binding) | |
"Get the value part of a single binding." | |
(cdr binding)) | |
(defun get-binding (var bindings) | |
"Find a (variable . value) pair in a binding list." | |
(assoc var bindings)) | |
(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." | |
(cons (cons var val) | |
;; Once we add a "real" binding, | |
;; we can get rid of the dummy no-bindings | |
(if (eq bindings no-bindings) | |
nil | |
bindings))) | |
(defun variable-p (x) | |
"Is x a variable (a symbol beginning with `?')?" | |
(and (symbolp x) (equal (elt (symbol-name x) 0) #\?))) | |
;;;; File unify.lisp: Unification functions | |
(defparameter *occurs-check* t "Should we do the occurs check?") | |
(defun unify (x y &optional (bindings no-bindings)) | |
"See if x and y match with given bindings." | |
(cond ((eq bindings fail) fail) | |
((eql x y) bindings) | |
((variable-p x) (unify-variable x y bindings)) | |
((variable-p y) (unify-variable y x bindings)) | |
((and (consp x) (consp y)) | |
(unify (rest x) (rest y) | |
(unify (first x) (first y) bindings))) | |
(t fail))) | |
(defun unify-variable (var x bindings) | |
"Unify var with x, using (and maybe extending) bindings." | |
(cond ((get-binding var bindings) | |
(unify (lookup var bindings) x bindings)) | |
((and (variable-p x) (get-binding x bindings)) | |
(unify var (lookup x bindings) bindings)) | |
((and *occurs-check* (occurs-check var x bindings)) | |
fail) | |
(t (extend-bindings var x bindings)))) | |
(defun occurs-check (var x bindings) | |
"Does var occur anywhere inside x?" | |
(cond ((eq var x) t) | |
((and (variable-p x) (get-binding x bindings)) | |
(occurs-check var (lookup x bindings) bindings)) | |
((consp x) (or (occurs-check var (first x) bindings) | |
(occurs-check var (rest x) bindings))) | |
(t nil))) | |
;;; ============================== | |
(defun subst-bindings (bindings x) | |
"Substitute the value of variables in bindings into x, | |
taking recursively bound variables into account." | |
(cond ((eq bindings fail) fail) | |
((eq bindings no-bindings) x) | |
((and (variable-p x) (get-binding x bindings)) | |
(subst-bindings bindings (lookup x bindings))) | |
((atom x) x) | |
(t (reuse-cons (subst-bindings bindings (car x)) | |
(subst-bindings bindings (cdr x)) | |
x)))) | |
;;; ============================== | |
(defun unifier (x y) | |
"Return something that unifies with both x and y (or fail)." | |
(subst-bindings (unify x y) x)) | |
;;; Etc. | |
;; clauses are stored on the predicate's plist | |
(defun get-clauses (pred) (get pred 'clauses)) | |
(defun predicate (relation) | |
(if (atom relation) | |
relation | |
(first relation))) | |
(defun args (x) | |
"The arguments of a relation" | |
(if (atom x) | |
nil | |
(rest x))) | |
;; An nlist is implemented as a (count . elements) pair: | |
(defun make-empty-nlist () | |
"Create a new, empty nlist." | |
(cons 0 nil)) | |
(defun nlist-n (x) "The number of elements in an nlist." (car x)) | |
(defun nlist-list (x) "The elements in an nlist." (cdr x)) | |
(defun nlist-push (item nlist) | |
"Add a new element to an nlist." | |
(incf (car nlist)) | |
(push item (cdr nlist)) | |
nlist) | |
;;; Utils from prolog implementation | |
(defun rename-variables (x) | |
"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 #'non-anon-variable-p exp)) | |
(defun non-anon-variable-p (x) | |
(and (variable-p x) (not (eq x '?)))) | |
(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)))) | |
;;; ============================== | |
(defstruct (dtree (:type vector)) | |
(first nil) (rest nil) (atoms nil) (var (make-empty-nlist))) | |
;;; ============================== | |
;; Not all Lisps handle the closure properly, so change the local PREDICATES | |
;; to a global *predicates* - norvig Jun 11 1996 | |
(defvar *predicates* nil) | |
(defun get-dtree (predicate) | |
"Fetch (or make) the dtree for this predicate." | |
(cond ((get predicate 'dtree)) | |
(t (push predicate *predicates*) | |
(setf (get predicate 'dtree) (make-dtree))))) | |
(defun clear-dtrees () | |
"Remove all the dtrees for all the predicates." | |
(dolist (predicate *predicates*) | |
(setf (get predicate 'dtree) nil)) | |
(setf *predicates* nil)) | |
(defun index (key) | |
"Store key in a dtree node. Key must be (predicate . args); | |
it is stored in the predicate's dtree." | |
(dtree-index key (rename-variables key) ; store unique vars | |
(get-dtree (predicate key)))) | |
(defun dtree-index (key value dtree) | |
"Index value under all atoms of key in dtree." | |
(cond | |
((consp key) ; index on both first and rest | |
(dtree-index (first key) value | |
(or (dtree-first dtree) | |
(setf (dtree-first dtree) (make-dtree)))) | |
(dtree-index (rest key) value | |
(or (dtree-rest dtree) | |
(setf (dtree-rest dtree) (make-dtree))))) | |
((null key)) ; don't index on nil | |
((variable-p key) ; index a variable | |
(nlist-push value (dtree-var dtree))) | |
(t ;; Make sure there is an nlist for this atom, and add to it | |
(nlist-push value (lookup-atom key dtree))))) | |
(defun lookup-atom (atom dtree) | |
"Return (or create) the nlist for this atom in dtree." | |
(or (lookup atom (dtree-atoms dtree)) | |
(let ((new (make-empty-nlist))) | |
(push (cons atom new) (dtree-atoms dtree)) | |
new))) | |
;;; ============================== | |
(defun mapc-retrieve (fn query &optional (bindings no-bindings)) | |
"For every fact that matches the query, | |
apply the function to the binding list." | |
(dolist (bucket (fetch query)) | |
(dolist (answer bucket) | |
(let ((new-bindings (unify query answer bindings))) | |
(unless (eq new-bindings fail) | |
(funcall fn new-bindings)))))) | |
(defun retrieve (query &optional (bindings no-bindings)) | |
"Find all facts that match query. Return a list of bindings." | |
(let ((answers nil)) | |
(mapc-retrieve #'(lambda (bindings) (push bindings answers)) | |
query bindings) | |
answers)) | |
;;; ============================== | |
(defun fetch (query) | |
"Return a list of buckets potentially matching the query, | |
which must be a relation of form (predicate . args)." | |
(dtree-fetch query (get-dtree (predicate query)) | |
nil 0 nil most-positive-fixnum)) | |
;;; ============================== | |
(defun dtree-fetch (pat dtree var-list-in var-n-in best-list best-n) | |
"Return two values: a list-of-lists of possible matches to pat, | |
and the number of elements in the list-of-lists." | |
(if (or (null dtree) (null pat) (variable-p pat)) | |
(values best-list best-n) | |
(let* ((var-nlist (dtree-var dtree)) | |
(var-n (+ var-n-in (nlist-n var-nlist))) | |
(var-list (if (null (nlist-list var-nlist)) | |
var-list-in | |
(cons (nlist-list var-nlist) | |
var-list-in)))) | |
(cond | |
((>= var-n best-n) (values best-list best-n)) | |
((atom pat) (dtree-atom-fetch pat dtree var-list var-n | |
best-list best-n)) | |
(t (multiple-value-bind (list1 n1) | |
(dtree-fetch (first pat) (dtree-first dtree) | |
var-list var-n best-list best-n) | |
(dtree-fetch (rest pat) (dtree-rest dtree) | |
var-list var-n list1 n1))))))) | |
(defun dtree-atom-fetch (atom dtree var-list var-n best-list best-n) | |
"Return the answers indexed at this atom (along with the vars), | |
or return the previous best answer, if it is better." | |
(let ((atom-nlist (lookup atom (dtree-atoms dtree)))) | |
(cond | |
((or (null atom-nlist) (null (nlist-list atom-nlist))) | |
(values var-list var-n)) | |
((and atom-nlist (< (incf var-n (nlist-n atom-nlist)) best-n)) | |
(values (cons (nlist-list atom-nlist) var-list) var-n)) | |
(t (values best-list best-n))))) | |
;;; ============================== | |
;;; Krep: | |
(defparameter *primitives* '(and sub ind rel val)) | |
(defun add-fact (fact) | |
"Add the fact to the data base." | |
(cond ((eq (predicate fact) 'and) | |
(mapc #'add-fact (args fact))) | |
((or (not (every #'atom (args fact))) | |
(some #'variable-p (args fact)) | |
(not (member (predicate fact) *primitives*))) | |
(error "Ill-formed fact: ~a" fact)) | |
((not (fact-present-p fact)) | |
(index fact) | |
(run-attached-fn fact))) | |
t) | |
(defun fact-present-p (fact) | |
"Is this fact present in the data base?" | |
(retrieve fact)) | |
(defun retrieve-fact (query &optional (bindings no-bindings)) | |
"Find all facts that match query. Return a list of bindings." | |
(if (eq (predicate query) 'and) | |
(retrieve-conjunction (args query) (list bindings)) | |
(retrieve query bindings))) | |
(defun retrieve-conjunction (conjuncts bindings-lists) | |
"Return a list of binding lists satisfying the conjuncts." | |
(mapcan | |
#'(lambda (bindings) | |
(cond ((eq bindings fail) nil) | |
((null conjuncts) (list bindings)) | |
(t (retrieve-conjunction | |
(rest conjuncts) | |
(retrieve-fact | |
(subst-bindings bindings (first conjuncts)) | |
bindings))))) | |
bindings-lists)) | |
;;; ============================== | |
(defun index-new-fact (fact) | |
"Index the fact in the data base unless it is already there." | |
(unless (fact-present-p fact) | |
(index fact))) | |
;;; ============================== | |
(defun test-bears () | |
(clear-dtrees) | |
(mapc #'add-fact | |
'((sub animal living-thing) | |
(sub living-thing thing) (sub polar-bear bear) | |
(sub grizzly bear) (ind Yogi bear) (ind Lars polar-bear) | |
(ind Helga grizzly))) | |
(trace index) | |
(add-fact '(sub bear animal)) | |
(untrace index)) | |
(defmacro a (&rest args) | |
"Define a new individual and assert facts about it in the data base." | |
`(add-fact ',(translate-exp (cons 'a args)))) | |
(defmacro each (&rest args) | |
"Define a new category and assert facts about it in the data base." | |
`(add-fact ',(translate-exp (cons 'each args)))) | |
(defmacro ?? (&rest queries) | |
"Return a list of answers satisfying the query or queries." | |
`(retrieve-setof | |
',(translate-exp (maybe-add 'and (replace-?-vars queries)) | |
:query))) | |
;;; ============================== | |
(defun translate-exp (exp &optional query-mode-p) | |
"Translate exp into a conjunction of the four primitives." | |
(let ((conjuncts nil)) | |
(labels | |
((collect-fact (&rest terms) (push terms conjuncts)) | |
(translate (exp) | |
;; Figure out what kind of expression this is | |
(cond | |
((atom exp) exp) | |
((eq (first exp) 'a) (translate-a (rest exp))) | |
((eq (first exp) 'each) (translate-each (rest exp))) | |
(t (apply #'collect-fact exp) exp))) | |
(translate-a (args) | |
;; translate (A category [ind] (rel filler)*) | |
(let* ((category (pop args)) | |
(self (cond ((and args (atom (first args))) | |
(pop args)) | |
(query-mode-p (gentemp "?")) | |
(t (gentemp (string category)))))) | |
(collect-fact 'ind self category) | |
(dolist (slot args) | |
(translate-slot 'val self slot)) | |
self)) | |
(translate-each (args) | |
;; translate (EACH category [(isa cat*)] (slot cat)*) | |
(let* ((category (pop args))) | |
(when (eq (predicate (first args)) 'isa) | |
(dolist (super (rest (pop args))) | |
(collect-fact 'sub category super))) | |
(dolist (slot args) | |
(translate-slot 'rel category slot)) | |
category)) | |
(translate-slot (primitive self slot) | |
;; translate (relation value) into a REL or SUB | |
(assert (= (length slot) 2)) | |
(collect-fact primitive (first slot) self | |
(translate (second slot))))) | |
;; Body of translate-exp: | |
(translate exp) ;; Build up the list of conjuncts | |
(maybe-add 'and (nreverse conjuncts))))) | |
;;; ============================== | |
(defun replace-?-vars (exp) | |
"Replace each ? in exp with a temporary var: ?123" | |
(cond ((eq exp '?) (gentemp "?")) | |
((atom exp) exp) | |
(t (reuse-cons (replace-?-vars (first exp)) | |
(replace-?-vars (rest exp)) | |
exp)))) | |
;;;; Support for Multiple Worlds | |
;; In the book, we redefine index, but that screws up other things, | |
;; so we'll define index-in-world instead of index. | |
(defvar *world* 'W0 "The current world used by index and fetch.") | |
(defun index-in-world (key &optional (world *world*)) | |
"Store key in a dtree node. Key must be (predicate . args); | |
it is stored in the dtree, indexed by the world." | |
(dtree-index-in-world key key world (get-dtree (predicate key)))) | |
(defun dtree-index-in-world (key value world dtree) | |
"Index value under all atoms of key in dtree." | |
(cond | |
((consp key) ; index on both first and rest | |
(dtree-index-in-world (first key) value world | |
(or (dtree-first dtree) | |
(setf (dtree-first dtree) (make-dtree)))) | |
(dtree-index-in-world (rest key) value world | |
(or (dtree-rest dtree) | |
(setf (dtree-rest dtree) (make-dtree))))) | |
((null key)) ; don't index on nil | |
((variable-p key) ; index a variable | |
(nalist-push world value (dtree-var dtree))) | |
(t ;; Make sure there is an nlist for this atom, and add to it | |
(nalist-push world value (lookup-atom key dtree))))) | |
;;; ============================== | |
(defun nalist-push (key val nalist) | |
"Index val under key in a numbered alist." | |
;; An nalist is of the form (count (key val*)*) | |
;; Ex: (6 (nums 1 2 3) (letters a b c)) | |
(incf (car nalist)) | |
(let ((pair (assoc key (cdr nalist)))) | |
(if pair | |
(push val (cdr pair)) | |
(push (list key val) (cdr nalist))))) | |
;;; ============================== | |
(defstruct (world (:print-function print-world)) | |
name parents current) | |
;;; ============================== | |
(defun get-world (name &optional current (parents (list *world*))) | |
"Look up or create the world with this name. | |
If the world is new, give it the list of parents." | |
(cond ((world-p name) name) ; ok if it already is a world | |
((get name 'world)) | |
(t (setf (get name 'world) | |
(make-world :name name :parents parents | |
:current current))))) | |
(setf *world* (get-world 'W0 nil nil)) | |
;;; ============================== | |
(defun use-world (world) | |
"Make this world current." | |
;; If passed a name, look up the world it names | |
(setf world (get-world world)) | |
(unless (eq world *world*) | |
;; Turn the old world(s) off and the new one(s) on, | |
;; unless we are already using the new world | |
(set-world-current *world* nil) | |
(set-world-current world t) | |
(setf *world* world))) | |
(defun use-new-world () | |
"Make up a new world and use it. | |
The world inherits from the current world." | |
(setf *world* (get-world (gensym "W"))) | |
(setf (world-current *world*) t) | |
*world*) | |
(defun set-world-current (world on/off) | |
"Set the current field of world and its parents on or off." | |
;; nil is off, anything else is on. | |
(setf (world-current world) on/off) | |
(dolist (parent (world-parents world)) | |
(set-world-current parent on/off))) | |
;;; ============================== | |
(defun print-world (world &optional (stream t) depth) | |
(declare (ignorable depth)) | |
(prin1 (world-name world) stream)) | |
;;; ============================== | |
(defun mapc-retrieve-in-world (fn query) | |
"For every fact in the current world that matches the query, | |
apply the function to the binding list." | |
(dolist (bucket (fetch query)) | |
(dolist (world/entries bucket) | |
(when (world-current (first world/entries)) | |
(dolist (answer (rest world/entries)) | |
(let ((bindings (unify query answer))) | |
(unless (eq bindings fail) | |
(funcall fn bindings)))))))) | |
(defun retrieve-in-world (query) | |
"Find all facts that match query. Return a list of bindings." | |
(let ((answers nil)) | |
(mapc-retrieve-in-world | |
#'(lambda (bindings) (push bindings answers)) | |
query) | |
answers)) | |
(defun retrieve-bagof-in-world (query) | |
"Find all facts in the current world that match query. | |
Return a list of queries with bindings filled in." | |
(mapcar #'(lambda (bindings) (subst-bindings bindings query)) | |
(retrieve-in-world query))) | |
;;; ============================== | |
(defun nlist-delete (item nlist) | |
"Remove an element from an nlist. | |
Assumes that item is present exactly once." | |
(decf (car nlist)) | |
(setf (cdr nlist) (delete item (cdr nlist) :count 1)) | |
nlist) | |
;;; ============================== | |
(defmacro query-bind (variables query &body body) | |
"Execute the body for each match to the query. | |
Within the body, bind each variable." | |
(let* ((bindings (gensym "BINDINGS")) | |
(vars-and-vals | |
(mapcar | |
#'(lambda (var) | |
(list var `(subst-bindings ,bindings ',var))) | |
variables))) | |
`(mapc-retrieve | |
#'(lambda (,bindings) | |
(let ,vars-and-vals | |
,@body)) | |
,query))) | |
;;; ============================== | |
(defun retrieve-bagof (query) | |
"Find all facts that match query. | |
Return a list of queries with bindings filled in." | |
(mapcar #'(lambda (bindings) (subst-bindings bindings query)) | |
(retrieve-fact query))) | |
(defun retrieve-setof (query) | |
"Find all facts that match query. | |
Return a list of unique queries with bindings filled in." | |
(remove-duplicates (retrieve-bagof query) :test #'equal)) | |
;;; ============================== | |
(defmacro def-attached-fn (pred args &body body) | |
"Define the attached function for a primitive." | |
`(setf (get ',pred 'attached-fn) | |
#'(lambda ,args | |
(declare (ignorable ,@args)) | |
,@body))) | |
(defun run-attached-fn (fact) | |
"Run the function associated with the predicate of this fact." | |
(apply (get (predicate fact) 'attached-fn) (args fact))) | |
;;;; The attached functions: | |
(def-attached-fn ind (individual category) | |
;; Cache facts about inherited categories | |
(query-bind (?super) `(sub ,category ?super) | |
(add-fact `(ind ,individual ,?super)))) | |
(def-attached-fn val (relation ind1 ind2) | |
;; Make sure the individuals are the right kinds | |
(query-bind (?cat1 ?cat2) `(rel ,relation ?cat1 ?cat2) | |
(add-fact `(ind ,ind1 ,?cat1)) | |
(add-fact `(ind ,ind2 ,?cat2)))) | |
(def-attached-fn rel (relation cat1 cat2) | |
;; Run attached function for any IND's of this relation | |
(query-bind (?a ?b) `(ind ,relation ?a ?b) | |
(run-attached-fn `(ind ,relation ,?a ,?b)))) | |
(def-attached-fn sub (subcat supercat) | |
;; Cache SUB facts | |
(query-bind (?super-super) `(sub ,supercat ?super-super) | |
(index-new-fact `(sub ,subcat ,?super-super)) | |
(query-bind (?sub-sub) `(sub ?sub-sub ,subcat) | |
(index-new-fact `(sub ,?sub-sub ,?super-super)))) | |
(query-bind (?sub-sub) `(sub ?sub-sub ,subcat) | |
(index-new-fact `(sub ,?sub-sub ,supercat))) | |
;; Cache IND facts | |
(query-bind (?super-super) `(sub ,subcat ?super-super) | |
(query-bind (?sub-sub) `(sub ?sub-sub ,supercat) | |
(query-bind (?ind) `(ind ?ind ,?sub-sub) | |
(index-new-fact `(ind ,?ind ,?super-super)))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment