I am trying to use rules for an agriculture based system
So for example,
based on location --> list crops --> based on crop selected --> select seed
This is a forward chaining problem
I am only able to define the rules statically. Meaning, defining rules for every possible scenario
Is there a way to code, say if I select a location, I get a list of all crops, and when the user selects the crop , I get the list of seeds
How do I make sure, the rules are fired based on the output of the previous rule?
One approach you can take is to represent the questions as facts and then write general rules for processing those facts. First define some deftemplates to represent the questions, the branching from one question to another based on the user's response, and the user's response.
(deftemplate question
(slot name)
(slot text)
(slot display-answers (allowed-values yes no))
(slot last-question (default none)))
(deftemplate branch
(slot question)
(slot answer)
(slot next-question)
(multislot next-answers))
(deftemplate response
(slot question)
(slot answer))
Next, define your questions and the branches between them:
(deffacts questions
(question (name location)
(text "Country")
(display-answers no)
(last-question none))
(question (name crop-type)
(text "Crop Type")
(display-answers yes)
(last-question location))
(question (name seed)
(text "Seed")
(display-answers yes)
(last-question crop-type)))
(deffacts locations
(branch (question location)
(answer "United States")
(next-question crop-type)
(next-answers food fiber))
(branch (question location)
(answer "India")
(next-question crop-type)
(next-answers food fiber))
(branch (question location)
(answer "China")
(next-question crop-type)
(next-answers food fiber))
(branch (question location)
(answer "Brazil")
(next-question crop-type)
(next-answers food fiber))
(branch (question location)
(answer "Pakistan")
(next-question crop-type)
(next-answers fiber)))
(deffacts crop-types
(branch (question crop-type)
(answer fiber)
(next-question seed)
(next-answers cotton hemp flax))
(branch (question crop-type)
(answer food)
(next-question seed)
(next-answers corn wheat rice)))
Define some utility deffunctions for processing user responses. These will allow the program to ignore differences in alphabetic case in the user response.
(deffunction lenient-eq (?v1 ?v2)
(if (eq ?v1 ?v2)
then
(return TRUE))
(if (eq (lowcase (str-cat ?v1)) (lowcase (str-cat ?v2)))
then
(return TRUE))
(return FALSE))
(deffunction lenient-member$ (?value $?allowed-values)
(loop-for-count (?i (length$ ?allowed-values))
(bind ?v (nth$ ?i ?allowed-values))
(if (lenient-eq ?value ?v)
then
(return ?i)))
(return FALSE))
(deffunction ask-question (?question $?allowed-values)
(printout t ?question)
(bind ?answer (lowcase (readline)))
(while (not (lenient-member$ ?answer ?allowed-values)) do
(printout t ?question)
(bind ?answer (lowcase (readline))))
?answer)
Add some rules to handle the case where the list of valid answers are not displayed when the question is asked (because there may be a large number of them).
;;; Ask question without valid answers displayed or checked
(defrule ask-question-without-answers
;; There is a question that should be
;; displayed without valid answers.
(question (name ?question)
(text ?text)
(display-answers no)
(last-question ?last-question))
;; There is no prior question or
;; the prior question has a response.
(or (test (eq ?last-question none))
(response (question ?last-question)))
;; There is no response to the question.
(not (response (question ?question)))
=>
;; Ask the question
(printout t ?text ": ")
;; Assert a response with the question and answer.
(assert (response (question ?question)
(answer (lowcase (readline))))))
;;; Check for valid response to a question
(defrule bad-answer-to-question
;; There is a question that should be
;; displayed without valid answers.
(question (name ?question)
(display-answers no))
;; There is a response to the question.
?r <- (response (question ?question)
(answer ?answer))
;; The response to the question does
;; not branch to another question.
(not (branch (question ?question)
(answer ?a&:(lenient-eq ?a ?answer))))
=>
;; Print the list of valid answers for the question.
(printout t "Valid answers are:" crlf)
(do-for-all-facts ((?b branch))
(eq ?b:question ?question)
(printout t " " ?b:answer crlf))
;; Retract the response so that the
;; question will be asked again.
(retract ?r))
Finally, add a rule to handle the case where the question is asked with the list of valid answers displayed and is immediately check by the ask-question deffunction.
;;; Ask questions with valid answers displayed and checked
(defrule ask-question-with-answers
;; There is a question that should be
;; displayed including valid answers.
(question (name ?question)
(text ?text)
(display-answers yes)
(last-question ?last-question))
;; The preceding question has been answered.
(response (question ?last-question)
(answer ?last-answer))
;; There is a branch from the preceding question
;; and its answer to this question and the allowed
;; values for the answer.
(branch (answer ?a&:(lenient-eq ?a ?last-answer))
(next-question ?question)
(next-answers $?next-answers))
=>
;; Construct the question text including the possible answers.
(bind ?text (str-cat ?text " [" (implode$ ?next-answers) "]: "))
;; Ask the question.
(bind ?answer (ask-question ?text ?next-answers))
;; Assert a response fact with the question and answer.
(assert (response (question ?question) (answer ?answer))))
The output when this program is run:
CLIPS (6.31 6/12/19)
CLIPS> (load "seeds.clp")
%%%$$$!!!***
TRUE
CLIPS> (reset)
CLIPS> (run)
Country: Sweden
Valid answers are:
United States
India
China
Brazil
Pakistan
Country: China
Crop Type [food fiber]: food
Seed [corn wheat rice]: wheat
CLIPS>
To allow the first question to display the valid responses, redefine the questions deffacts to include an initial question that has already been answered:
(deffacts questions
(question (name location)
(text "Country")
(display-answers yes)
(last-question start-program))
(question (name crop-type)
(text "Crop Type")
(display-answers yes)
(last-question location))
(question (name seed)
(text "Seed")
(display-answers yes)
(last-question crop-type))
(response (question start-program)
(answer yes))
(branch (question start-program)
(answer yes)
(next-question location)
(next-answers "United States" "India" "China" "Brazil" "Pakistan")))
The output will then look like this:
CLIPS> (run)
Country ["United States" "India" "China" "Brazil" "Pakistan"]: Sweden
Country ["United States" "India" "China" "Brazil" "Pakistan"]: China
Crop Type [food fiber]: food
Seed [corn wheat rice]: wheat
CLIPS>