All Manuals > KnowledgeWorks and Prolog User Guide > Appendix B: Examples

B.2 Explanation Facility

Below is the complete code implementing the simple explanation facility of 6.1.1.3 A Simple Explanation Facility. The implementation principle is exactly as described.

;;; ---------- A SIMPLE EXPLANATION FACILITY ---------
 
(in-package kw-user)
 
; connects rule to explanation definitions
(defvar *explanation-table* 
                (make-hash-table :test #'eq))
 
; explanation generated at run time
(defvar *explanation* nil)
 
;;; the next four definitions make up the defexplain 
;;; macro for each of the why, what and because
;;; definitions we create a function which we can call
;;; at run time on the bindings of the instantiation to
;;; generate the explanation text - this will be
;;; reasonably efficient
 
(defun is-var (expr)
  "is this a variable (i.e. starts with ?)"
  (and (symbolp expr)
    (eql (char (symbol-name expr) 0) #\?)))
 
(defun find-vars (expr)
  "returns a list of all the variables in expr"
  (if (consp expr)
      (append (find-vars (car expr)) 
              (find-vars (cdr expr)))
    (if (is-var expr) (list expr) nil)))
 
(defun make-explain-func (explain-stuff)
  "generates a function to generate explanation text at
   run time"
  (let* ((explain-string (car explain-stuff))
         (explain-args (cdr explain-stuff))
         (vars (remove-duplicates
                 (find-vars explain-args))))
    `#'(lambda (bindings)
         (let ,(mapcar
                 #'(lambda (v)
                   `(,v (cdr (assoc `,v bindings))))
                 vars)
           (format nil ,explain-string
                   ,@explain-args)))))
 
(defmacro defexplain (rulename &key why what because)
  "puts an entry for the rule in the explanation table"
  `(setf (gethash `,rulename *explanation-table*)
         (list ,(make-explain-func why)
               ,(make-explain-func what)
               ,(make-explain-func because))))
 
;;; next two definitions generate an explanation for
;;; each instantiation that fires and stores it away in
;;; *explanation*
 
(defun add-explanation (inst)
  "generate an explanation for firing this
   instantiation"
  (let ((explain-info
          (gethash (inst-rulename inst)
                   *explanation-table*)))
    (when explain-info
     (do-the-rest explain-info (inst-bindings inst)))))
 
(defun do-the-rest (explain-info bindings)
  "creates explanation text derived from explain
   functions and bindings"
  (let ((why-func (first explain-info))
        (what-func (second explain-info))
        (because-func (third explain-info)))
    (push `(,*cycle* ,(inst-rulename inst)
            ,(funcall why-func bindings)
            ,(funcall what-func bindings)
            ,(funcall because-func bindings))
          *explanation*)))))
 
;;; meta-interpreter for explanation contexts
;;; before firing the rule generate explanation for
;;; this cycle
 
(defrule explain-context :backward
  ((explain-context)
   <--
   (start-cycle)
   (instantiation ?inst)
   ((add-explanation ?inst))
   (fire-rule ?inst)
   (cut)
   (explain-context)))
 
;;; simple text output of the explanation
 
(defun explain (&optional cycle)
  "print out either the whole explanation or just for
   one cycle"
  (if cycle (explain-cycle (assoc cycle *explanation*))
    (dolist (cycle-entry (reverse *explanation*))
      (explain-cycle cycle-entry))))
 
(defun explain-cycle (entry)
  "print this explanation entry"
  (if entry
      (let ((cycle (first entry))
            (rulename (second entry))
            (why (third entry))
            (what (fourth entry))
            (because (fifth entry)))
        (format t "~2%~a: ~a~%~a~%~a~%~a"
                cycle rulename why what because))
    (format t "~2%No explanation for this cycle")))
 
;;; we could make a really smart tool here, but to give
;;; the general idea...
 
(defun explain-an-action ()
  (let ((item
          (tk:scrollable-menu
            (reverse *explanation*)
            :title "Which action do you want
                    explained?"
            :name-function #'(lambda (x) (fourth x)))))
    (if item (tk:send-a-message (fifth item)))))
 
;;; starting the rule interpreter should clear any old
;;; explanation
 
(defadvice (infer rest-explanation :before)
    (&rest args)
  (unless *in-interpreter* (setq *explanation* nil)))

Below are some example rules using the explanation facility. They are taken from the Monkey and Banana Example distributed with KnowledgeWorks. The classes used in the example are monkey, object and goal.

(defrule mb7 :forward
  :context mab
  (goal ?g status active type holds object ?w)
  (object ?o1 kb-name ?w at ?p on floor)
  (monkey ?m at ?p holds nil)
  -->
  ((format t "~%Grab ~s" ?w))
  (assert (monkey ?m holds ?w))
  (assert (goal ?g status satisfied)))
 
(defexplain mb7
  :why ("Monkey is at the ~s which is on the floor" ?w)
  :what ("Monkey grabs the ~s" ?w)
  :because ("Monkey needs the ~s somewhere else" ?w))
 
(defrule mb12 :forward
  :context mab
  :context mab
  (goal ?g status active type walk-to object ?p)
  (monkey ?m on floor at ?c holds nil)
  (test (not (eq ?c ?p)))
  -->
  ((format t "~%Walk to ~s" ?p))
  (assert (monkey ?m at ?p))
  (assert (goal ?g status satisfied)))
 
(defexplain mb12
  :why ("Monkey is on the floor holding nothing")
  :what ("Monkey walks to ~s" ?p)
  :because ("Monkey needs to do something with an
             object at ~s" ?p))
 
(defrule mb13 :forward
  :context mab
  (goal ?g status active type walk-to object ?p)
  (monkey ?m on floor at ?c holds ?w)
  (test (and ?w (not (eq ?c ?p))))
  (object ?o1 kb-name ?w)
  -->
  ((format t "~%Walk to ~s" ?p))
  (assert (monkey ?m at ?p))
  (assert (object ?o1 at ?p))
  (assert (goal ?g status satisfied)))
 
(defexplain mb13
  :why ("Monkey is on the floor and is holding the ~s"
        ?w)
  :what ("Monkey walks to ~s with the ~s" ?p ?w)
  :because ("Monkey wants the ~s to be at ~s" ?w ?p))
 
(defrule mb14 :forward
  :context mab
  (goal ?g status active type on object floor)
  (monkey ?m on ?x)
  (test (not (eq ?x `floor)))
  -->
  ((format t "~%Jump onto the floor"))
  (assert (monkey ?m on floor))
  (assert (goal ?g status satisfied)))
 
(defexplain mb14
  :why ("Monkey is on ~s" ?x)
  :what ("Monkey jumps onto the floor")
  :because ("Monkey needs to go somewhere"))
 
(defrule mb17 :forward
  :context mab
  (goal ?g status active type on object ?o)
  (object ?o1 kb-name ?o at ?p)
  (monkey ?m at ?p holds nil)
  -->
  ((format t "~%Climb onto ~s" ?o))
  (assert (monkey ?m on ?o))
  (assert (goal ?g status satisfied)))
 
(defexplain mb17
  :why ("Monkey is at the location of the ~s" ?o)
  :what ("Monkey climbs onto the ~s" ?o)
  :because ("Monkey wants to be on top of the ~s" ?o))
 
(defrule mb18 :forward
  :context mab
  (goal ?g status active type holds object nil)
  (monkey ?m holds ?x)
  (test ?x)
  -->
  ((format t "~%Drop ~s" ?x))
  (assert (monkey ?m holds nil))
  (assert (goal ?g status satisfied)))
 
(defexplain mb18
  :why ("Monkey is holding the ~s" ?x)
  :what ("Monkey drops the ~s" ?x)
  :because ("Monkey wants to do something for which he
             can't hold anything"))

KnowledgeWorks and Prolog User Guide (Windows version) - 01 Dec 2021 19:36:05