;;;====================================================== ;;; 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)) (defmodule SEND (import MAIN ?ALL)) ;;;************************* ;;;* DEFGLOBAL DEFINITIONS * ;;;************************* (defglobal MAIN ?*rule-index* = 1 ?*validate* = TRUE) ;;;*************************** ;;;* 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::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) (slot prefix (default "")) (slot postfix (default ""))) (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::displayanswers (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 middle))) (deftemplate MAIN::state-list (slot current) (multislot sequence)) (deftemplate MAIN::next (slot id) (slot value-set (default FALSE) (allowed-values TRUE FALSE)) (slot value)) (deftemplate MAIN::prev (slot id)) (deffacts MAIN::startup (state-list)) ;;;************************** ;;;* INFERENCE ENGINE RULES * ;;;************************** (defrule MAIN::startup (welcome (message ?message)) => (assert (UI-state (display ?message) (relation-asserted start) (state initial) (valid-answers))) (focus VALIDATE SEND)) (defrule MAIN::continue (declare (salience -10)) ?f <- (activity) => (retract ?f) (focus CHAIN ASK SEND)) (defrule MAIN::goal-satified "" (goal (variable ?goal)) (variable (name ?goal) (value ?value)) (answer (prefix ?prefix) (variable ?goal) (postfix ?postfix)) => (assert (UI-state (display ;(format nil "%s%s%s%n" ?prefix ?value ?postfix) ?value) (state final))) (focus SEND)) ;;; ################## ;;; CHAIN MODULE RULES ;;; ################## (defrule CHAIN::propagate-goal "" (logical (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 "" (logical (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))) => (retract ?f1) (assert (UI-state (display ?text) (relation-asserted ?variable) (response No) (valid-answers No Yes)))) (defrule ASK::ask-question-legalvalues-displayanswers "" (legalanswers (values $?answers)) (displayanswers (values $?display)) ?f1 <- (goal (variable ?variable)) (question (variable ?variable) (query ?text)) (not (variable (name ?variable))) => (retract ?f1) (assert (UI-state (display ?text) (relation-asserted ?variable) (response (nth$ 1 ?answers)) (valid-answers ?answers) (display-answers ?display)))) (defrule ASK::ask-question-legalvalues-no-displayanswers "" (legalanswers (values $?answers)) (not (displayanswers)) ?f1 <- (goal (variable ?variable)) (question (variable ?variable) (query ?text)) (not (variable (name ?variable))) => (retract ?f1) (assert (UI-state (display ?text) (relation-asserted ?variable) (response (nth$ 1 ?answers)) (valid-answers ?answers) (display-answers ?answers)))) ;;; ################# ;;; SEND MODULE RULES ;;; ################# (defrule SEND::send-question (UI-state (id ?id)) ?f <- (state-list (sequence $?s&:(not (member$ ?id ?s)))) => (modify ?f (current ?id) (sequence ?id ?s)) (halt)) ;;; ################# ;;; MAIN MODULE RULES ;;; ################# (defrule MAIN::handle-next-no-change-none-middle-of-chain ?f1 <- (next (id ?id) (value-set FALSE)) ?f2 <- (state-list (current ?id) (sequence $? ?nid ?id $?)) => (retract ?f1) (modify ?f2 (current ?nid)) (halt)) (defrule MAIN::handle-next-response-none-end-of-chain ?f <- (next (id ?id) (value-set FALSE)) (state-list (sequence ?id $?)) (UI-state (id ?id) (relation-asserted ?relation)) => (assert (activity)) (retract ?f)) (defrule MAIN::handle-next-no-change-middle-of-chain ?f1 <- (next (id ?id) (value-set TRUE) (value ?response)) ?f2 <- (state-list (current ?id) (sequence $? ?nid ?id $?)) (UI-state (id ?id) (response ?response)) => (assert (activity)) (retract ?f1) (modify ?f2 (current ?nid)) (halt)) (defrule MAIN::Handle-next-change-middle-of-chain (next (id ?id) (value-set TRUE) (value ?response)) ?f1 <- (state-list (current ?id) (sequence ?nid $?b ?id $?e)) (UI-state (id ?id) (response ~?response)) ?f2 <- (UI-state (id ?nid)) => (assert (activity)) (modify ?f1 (sequence ?b ?id ?e)) (retract ?f2)) (defrule MAIN::handle-next-response-end-of-chain ?f1 <- (next (id ?id) (value-set TRUE) (value ?response)) (state-list (sequence ?id $?)) ?f2 <- (UI-state (id ?id) (response ?expected) (relation-asserted ?relation)) => (retract ?f1) (if (neq ?response ?expected) then (modify ?f2 (response ?response))) (assert (add-response ?id ?response))) (defrule MAIN::handle-add-response (logical (UI-state (id ?id) (relation-asserted ?relation))) ?f1 <- (add-response ?id ?response) => (assert (activity)) (bind ?response (lowcase ?response)) (str-assert (str-cat "(variable (name " ?relation ") (value " ?response "))")) (retract ?f1)) (defrule MAIN::handle-prev ?f1 <- (prev (id ?id)) ?f2 <- (state-list (sequence $?b ?id ?p $?e)) => (retract ?f1) (modify ?f2 (current ?p)) (halt)) (defrule MAIN::Restore-Rule (declare (salience 10)) ?f <- (rule (if $?if) (processed $?begin ?variable ?relation ?value ?end)) (not (variable (name ?variable))) => (assert (activity)) (if (eq ?end #) then (modify ?f (if ?variable ?relation ?value) (processed $?begin)) else (modify ?f (if ?variable ?relation ?value and ?if) (processed $?begin)))) ;;; ##################### ;;; 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))