;;;====================================================== ;;; Animal Identification Expert System ;;; ;;; A simple expert system which attempts to identify ;;; an animal based on its characteristics. ;;; The knowledge base in this example is a ;;; collection of facts which represent backward ;;; chaining rules. CLIPS forward chaining rules are ;;; then used to simulate a backward chaining inference ;;; engine. ;;; ;;; CLIPS Version 6.3 Example ;;; ;;; For use with the Animal Demo Example ;;;====================================================== (defmodule MAIN (export ?ALL)) (defmodule VALIDATE (import MAIN ?ALL)) (defmodule CHAIN (import MAIN ?ALL)) (defmodule ASK (import MAIN ?ALL)) ;;;***************** ;;;* Configuration * ;;;***************** (deffacts MAIN::Configuration (validate no) (target console)) ; console, clipsjni, gui, or cgi ;;;************************* ;;;* DEFGLOBAL DEFINITIONS * ;;;************************* (defglobal MAIN ?*rule-index* = 1) ;;;*************************** ;;;* DEFFUNCTION DEFINITIONS * ;;;*************************** (deffunction generate-rule-name () (bind ?name (sym-cat rule- ?*rule-index*)) (bind ?*rule-index* (+ ?*rule-index* 1)) (return ?name)) ;;;*************************** ;;;* DEFTEMPLATE DEFINITIONS * ;;;*************************** (deftemplate MAIN::text-for-id (slot id) (slot text)) (deftemplate MAIN::rule (slot name (default-dynamic (generate-rule-name))) (slot validate (default no)) (multislot if) (multislot then) (multislot processed)) (deftemplate MAIN::question (multislot valid-answers) (multislot display-answers) (slot variable) (slot query)) (deftemplate MAIN::answer (slot variable)) (deftemplate MAIN::goal (slot variable)) (deftemplate MAIN::variable (slot name) (slot value)) (deftemplate MAIN::activity) (deftemplate MAIN::welcome (slot message)) (deftemplate MAIN::legalanswers (multislot values)) (deftemplate MAIN::UI-state (slot id (default-dynamic (gensym*))) (slot display) (slot relation-asserted (default none)) (slot response (default none)) (multislot valid-answers) (multislot display-answers) (slot state (default interview))) ;;;*************************** ;;;* DEFFUNCTION DEFINITIONS * ;;;*************************** (deffunction MAIN::find-text-for-id (?id) ;; Search for the text-for-id fact ;; with the same id as ?id (bind ?fact (find-fact ((?f text-for-id)) (eq ?f:id ?id))) (if ?fact then (fact-slot-value (nth$ 1 ?fact) text) else ?id)) (deffunction MAIN::translate-av (?values) ;; Create the return value (bind ?result (create$)) ;; Iterate over each of the allowed-values (progn$ (?v ?values) ;; Find the associated text-for-id fact (bind ?nv (find-text-for-id ?v)) ;; Add the text to the return value (bind ?result (create$ ?result ?nv))) ;; Return the return value ?result) (deffunction MAIN::replace-spaces (?str) (bind ?len (str-length ?str)) (bind ?i (str-index " " ?str)) (while (neq ?i FALSE) (bind ?str (str-cat (sub-string 1 (- ?i 1) ?str) "-" (sub-string (+ ?i 1) ?len ?str))) (bind ?i (str-index " " ?str))) ?str) (deffunction MAIN::sym-cat-multifield (?values) (bind ?rv (create$)) (progn$ (?v ?values) (bind ?rv (create$ ?rv (sym-cat (replace-spaces ?v))))) ?rv) (deffunction MAIN::multifield-to-delimited-string (?mv ?delimiter) (bind ?rv "") (bind ?first TRUE) (progn$ (?v ?mv) (if ?first then (bind ?first FALSE) (bind ?rv (str-cat ?v)) else (bind ?rv (str-cat ?rv ?delimiter ?v)))) ?rv) ;;;***************** ;;;* STATE METHODS * ;;;***************** ;;; Console target (defmethod handle-state ((?state SYMBOL (eq ?state greeting)) (?target SYMBOL (eq ?target console)) (?display LEXEME) (?relation-asserted SYMBOL) (?valid-answers MULTIFIELD)) (printout t ?display crlf) (assert (variable (name greeting) (value yes)))) (defmethod handle-state ((?state SYMBOL (eq ?state interview)) (?target SYMBOL (eq ?target console)) (?question LEXEME) (?variable SYMBOL) (?response PRIMITIVE) ; default (?valid-answers MULTIFIELD) (?display-answers MULTIFIELD)) (bind ?display-answers (sym-cat-multifield ?display-answers)) (format t "%s " ?question) (printout t ?display-answers " ") (bind ?answer (read)) (if (lexemep ?answer) then (bind ?answer (lowcase ?answer))) (while (not (member$ ?answer ?display-answers)) do (format t "%s " ?question) (printout t ?display-answers " ") (bind ?answer (read)) (if (lexemep ?answer) then (bind ?answer (lowcase ?answer)))) (bind ?pos (member$ ?answer ?display-answers)) (bind ?answer (nth$ ?pos ?valid-answers)) (assert (variable (name ?variable) (value ?answer)))) (defmethod handle-state ((?state SYMBOL (eq ?state conclusion)) (?target SYMBOL (eq ?target console)) (?display LEXEME)) (printout t ?display crlf)) ;;; CGI target (defmethod handle-state ((?state SYMBOL (eq ?state greeting)) (?target SYMBOL (eq ?target cgi)) (?display LEXEME) (?relation-asserted SYMBOL) (?valid-answers MULTIFIELD)) (printout t "state=greeting" crlf) (printout t "display=" ?display crlf) (printout t "variable=greeting" crlf) (printout t "validAnswers=yes" crlf) (printout t "displayAnswers=yes" crlf) (printout t "prevLabel=" (find-text-for-id Prev) crlf) (printout t "nextLabel=" (find-text-for-id Next) crlf) (printout t "restartLabel=" (find-text-for-id Restart) crlf) (printout t "animalDemoLabel=" (find-text-for-id AnimalDemo) crlf) (halt)) (defmethod handle-state ((?state SYMBOL (eq ?state interview)) (?target SYMBOL (eq ?target cgi)) (?message LEXEME) (?relation-asserted SYMBOL) (?response PRIMITIVE) (?valid-answers MULTIFIELD) (?display-answers MULTIFIELD)) (printout t "state=interview" crlf) (printout t "display=" ?message crlf) (printout t "variable=" ?relation-asserted crlf) (printout t "validAnswers=" (multifield-to-delimited-string ?valid-answers ":") crlf) (printout t "displayAnswers=" (multifield-to-delimited-string ?display-answers ":") crlf) (printout t "prevLabel=" (find-text-for-id Prev) crlf) (printout t "nextLabel=" (find-text-for-id Next) crlf) (printout t "restartLabel=" (find-text-for-id Restart) crlf) (printout t "animalDemoLabel=" (find-text-for-id AnimalDemo) crlf) (halt)) (defmethod handle-state ((?state SYMBOL (eq ?state conclusion)) (?target SYMBOL (eq ?target cgi)) (?display LEXEME)) (printout t "state=conclusion" crlf) (printout t "display=" ?display crlf) (printout t "prevLabel=" (find-text-for-id Prev) crlf) (printout t "nextLabel=" (find-text-for-id Next) crlf) (printout t "restartLabel=" (find-text-for-id Restart) crlf) (printout t "animalDemoLabel=" (find-text-for-id AnimalDemo) crlf) (halt)) ;;; CLIPSJNI target (defmethod handle-state ((?state SYMBOL (eq ?state greeting)) (?target SYMBOL (eq ?target clipsjni)) (?message LEXEME) (?relation-asserted SYMBOL) (?valid-answers MULTIFIELD)) (assert (UI-state (display ?message) (relation-asserted ?relation-asserted) (state ?state) (valid-answers $?valid-answers))) (halt)) (defmethod handle-state ((?state SYMBOL (eq ?state interview)) (?target SYMBOL (eq ?target clipsjni)) (?message LEXEME) (?relation-asserted SYMBOL) (?response PRIMITIVE) (?valid-answers MULTIFIELD) (?display-answers MULTIFIELD)) (assert (UI-state (display ?message) (relation-asserted ?relation-asserted) (response ?response) (valid-answers ?valid-answers) (display-answers ?display-answers))) (halt)) ;;; GUI target (defmethod handle-state ((?state SYMBOL (eq ?state greeting)) (?target SYMBOL (eq ?target gui)) (?message LEXEME) (?relation-asserted SYMBOL) (?valid-answers MULTIFIELD)) (assert (UI-state (display ?message) (relation-asserted greeting) (state ?state) (valid-answers yes) (display-answers yes))) (halt)) (defmethod handle-state ((?state SYMBOL (eq ?state interview)) (?target SYMBOL (eq ?target gui)) (?message LEXEME) (?relation-asserted SYMBOL) (?response PRIMITIVE) (?valid-answers MULTIFIELD) (?display-answers MULTIFIELD)) (assert (UI-state (display ?message) (relation-asserted ?relation-asserted) (state ?state) (response ?response) (valid-answers ?valid-answers) (display-answers ?display-answers))) (halt)) (defmethod handle-state ((?state SYMBOL (eq ?state conclusion)) (?target SYMBOL (eq ?target gui)) (?display LEXEME)) (assert (UI-state (display ?display) (state ?state) (valid-answers) (display-answers))) (halt)) ;;;************************** ;;;* INFERENCE ENGINE RULES * ;;;************************** (defrule MAIN::validate (declare (salience 10)) (validate yes) => (focus VALIDATE)) (defrule MAIN::startup (welcome (message ?message-id)) (not (variable (name greeting))) (target ?target) => (handle-state greeting ?target (find-text-for-id ?message-id) greeting (create$))) (deffacts MAIN::continue-interview (continue-interview)) (defrule MAIN::continue-interview (declare (salience -10)) ?f <- (continue-interview) (not (and (goal (variable ?goal)) (variable (name ?goal) (value ?value)) (answer (variable ?goal)))) => (retract ?f) (focus CHAIN ASK)) (defrule MAIN::goal-satified "" (goal (variable ?goal)) (variable (name ?goal) (value ?value)) (answer (variable ?goal)) (target ?target) => (handle-state conclusion ?target (find-text-for-id ?value))) ;;; ################## ;;; CHAIN MODULE RULES ;;; ################## (defrule CHAIN::propagate-goal "" (goal (variable ?goal)) (rule (if ?variable $?) (then ?goal ? ?value)) => (assert (goal (variable ?variable)))) (defrule CHAIN::modify-rule-match-is "" (variable (name ?variable) (value ?value)) ?f <- (rule (if ?variable is ?value and $?rest) (processed $?p)) => (modify ?f (if ?rest) (processed ?p ?variable is ?value and))) (defrule CHAIN::rule-satisfied-is "" (variable (name ?variable) (value ?value)) ?f <- (rule (if ?variable is ?value) (then ?goal ? ?goal-value) (processed $?p)) => (modify ?f (if) (processed ?p ?variable is ?value #))) (defrule CHAIN::apply-rule "" (rule (if) (then ?goal ? ?goal-value)) => (assert (variable (name ?goal) (value ?goal-value)))) ;;; ################ ;;; ASK MODULE RULES ;;; ################ ;;;(defrule ASK::ask-question-no-legalvalues "" ;;; (not (legalanswers)) ;;; ?f1 <- (goal (variable ?variable)) ;;; (question (variable ?variable) (query ?text)) ;;; (not (variable (name ?variable))) ;;; (target ?target) ;;; => ;;; (retract ?f1) ;;; (assert (start)) ;;; (assert (UI-state (display ?text) ;;; (relation-asserted ?variable) ;;; (response No) ;;; (valid-answers No Yes)))) (defrule ASK::ask-question-legalvalues "" (legalanswers (values $?answers)) ?f1 <- (goal (variable ?variable)) (question (variable ?variable) (query ?query-id)) (not (variable (name ?variable))) (target ?target) => (assert (continue-interview)) (retract ?f1) (handle-state interview ?target (find-text-for-id ?query-id) ?variable (nth$ 1 ?answers) ?answers (translate-av ?answers))) ;;; ##################### ;;; VALIDATE MODULE RULES ;;; ##################### (defrule VALIDATE::copy-rule (declare (salience 10)) ?f <- (rule (validate no)) => (duplicate ?f (validate yes)) (modify ?f (validate done))) (defrule VALIDATE::next-condition (declare (salience -10)) ?f <- (rule (name ?name) (validate yes) (if ?a ?c ?v and $?rest)) => (modify ?f (if ?rest))) (defrule VALIDATE::validation-complete (declare (salience -10)) ?f <- (rule (validate yes) (if ? ? ?)) => (retract ?f)) ;;; ******************* ;;; Validation - Syntax ;;; ******************* (defrule VALIDATE::and-connector ?f <- (rule (name ?name) (validate yes) (if ?a ?c ?v ?connector&~and $?)) => (retract ?f) (printout t "In rule " ?name ", if conditions must be connected using and:" crlf " " ?a " " ?c " " ?v " *" ?connector "*" crlf)) (defrule VALIDATE::and-requires-additional-condition ?f <- (rule (name ?name) (validate yes) (if ?a ?c ?v and)) => (retract ?f) (printout t "In rule " ?name ", an additional condition should follow the final and:" crlf " " ?a " " ?c " " ?v " and " crlf)) (defrule VALIDATE::incorrect-number-of-then-terms ?f <- (rule (name ?name) (validate yes) (then $?terms&:(<> (length$ ?terms) 3))) => (retract ?f) (printout t "In rule " ?name ", then portion should be of the form is :" crlf " " (implode$ ?terms) crlf)) (defrule VALIDATE::incorrect-number-of-if-terms ?f <- (rule (name ?name) (validate yes) (if $?terms&:(< (length$ ?terms) 3))) => (retract ?f) (printout t "In rule " ?name ", if portion contains an incomplete condition:" crlf " " (implode$ ?terms) crlf)) (defrule VALIDATE::incorrect-then-term-syntax ?f <- (rule (name ?name) (validate yes) (then ?a ?c&~is ?v)) => (retract ?f) (printout t "In rule " ?name ", then portion should be of the form is :" crlf " " ?a " " ?c " " ?v " " crlf)) (defrule VALIDATE::incorrect-if-term-syntax ?f <- (rule (name ?name) (validate yes) (if ?a ?c&~is ?v $?)) => (retract ?f) (printout t "In rule " ?name ", if portion comparator should be \"is\"" crlf " " ?a " " ?c " " ?v " " crlf)) (defrule VALIDATE::illegal-variable-value ?f <- (rule (name ?name) (validate yes) (if ?a ?c ?v $?)) (question (variable ?a) (valid-answers)) (legalanswers (values $?values)) (test (not (member$ ?v ?values))) => (retract ?f) (printout t "In rule " ?name ", the value " ?v " is not legal for variable " ?a ":" crlf " " ?a " " ?c " " ?v crlf)) (defrule VALIDATE::reachable (rule (name ?name) (validate yes) (if ?a ?c ?v $?)) (not (question (variable ?a))) (not (rule (then ?a $?))) => (printout t "In rule " ?name " no question or rule could be found " "that can supply a value for the variable " ?a ":" crlf " " ?a " " ?c " " ?v crlf)) (defrule VALIDATE::used "TBD lower salience" ?f <- (rule (name ?name) (validate yes) (then ?a is ?v)) (not (goal (variable ?a))) (not (rule (if ?a ? ?v $?))) => (retract ?f) (printout t "In rule " ?name " the conclusion for variable " ?a " is neither referenced by any rules nor the primary goal" crlf " " ?a " is " ?v crlf)) (defrule VALIDATE::variable-in-both-if-and-then ?f <- (rule (name ?name) (validate yes) (if ?a $?) (then ?a is ?v)) => (retract ?f) (printout t "In rule " ?name " the variable " ?a " is used in both the if and then sections" crlf)) (defrule VALIDATE::question-variable-unreferenced (question (variable ?a) (query ?q)) (not (rule (validate done) (if $? ?a is ?v $?))) => (printout t "The question \"" ?q "\", assigns a value to the variable " ?a " which is not referenced by any rules" crlf))