Skip to content

Instantly share code, notes, and snippets.

@ahefner
Created December 1, 2013 17:51
Show Gist options
  • Save ahefner/7738300 to your computer and use it in GitHub Desktop.
Save ahefner/7738300 to your computer and use it in GitHub Desktop.
Stand-alone version of PAIP "krep", extracted from paiprolog.
;;; -*- 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