clipsexpert-system

CLIPS programming


I am attempting to create a CLIPS program that asserts three facts as ingredients for a cake recipe. The goal is to determine the name of the cake recipe and the minimum number of cakes that can be made based on the quantity of ingredients. The program assumes that each recipe requires one of it's each ingredient.

I may have made a mistake with the "count-initial-quantity" function. Here is the code

(deftemplate Recipe
   (slot name))

(deftemplate RecipeName
   (multislot recipe-names))

(deftemplate Ingredient
   (slot name))

(deftemplate InitialQuantity
   (slot ingredient)
   (slot quantity))

(deffacts initial-quantities
   (InitialQuantity (ingredient chocolate) (quantity 200))
   (InitialQuantity (ingredient butter) (quantity 150))
   (InitialQuantity (ingredient sugar) (quantity 250))
   (InitialQuantity (ingredient flour) (quantity 300))
   (InitialQuantity (ingredient strawberry) (quantity 2))
)

(deffunction count-initial-quantity (?ingredient)
   (bind ?quantity -1)
   (bind ?facts (find-all-facts ((?f InitialQuantity)) (eq ?f:ingredient ?ingredient)))
   (if (> (length$ ?facts) 0)
      then (bind ?quantity (fact-slot-value (nth$ 1 ?facts) quantity))
   )
   ?quantity
)

(defrule chocolatecake
  (Ingredient (name "chocolate"))
  (Ingredient (name "butter"))
  (Ingredient (name "sugar"))
  (not (RecipeName (recipe-names $? "Chocolate Cake")))
  =>
  (bind ?num-cakes (min (count-initial-quantity "chocolate") (count-initial-quantity "butter") (count-initial-quantity "sugar")))
  (if (>= ?num-cakes 1)
    then
    (printout t "Recipe Name: Chocolate Cake" crlf)
    (printout t "Minimum number of cakes that can be made: " ?num-cakes crlf)
    (assert (RecipeName (recipe-names "Chocolate Cake")))
    )
    else
    (printout t "Insufficient ingredients to make Chocolate Cake." crlf)
)

(defrule vanillacake
  (Ingredient (name "flour"))
  (Ingredient (name "butter"))
  (Ingredient (name "sugar"))
  (not (RecipeName (recipe-names $? "Vanilla Cake")))
  =>
  (bind ?num-cakes (min (count-initial-quantity "flour") (count-initial-quantity "butter") (count-initial-quantity "sugar")))
  (if (>= ?num-cakes 1)
    then
    (printout t "Recipe Name: Vanilla Cake" crlf)
    (printout t "Minimum number of cakes that can be made: " ?num-cakes crlf)
    (assert (RecipeName (recipe-names "Vanilla Cake")))
    )
    else
    (printout t "Insufficient ingredients to make Vanilla Cake." crlf)
)

(defrule strawberryCake
  (Ingredient (name "strawberry"))
  (Ingredient (name "butter"))
  (Ingredient (name "sugar"))
  (not (RecipeName (recipe-names $? "Strawberry Cake")))
  =>
  (bind ?num-cakes (min (count-initial-quantity "strawberry") (count-initial-quantity "butter") (count-initial-quantity "sugar")))
  (if (>= ?num-cakes 1)
    then
    (printout t "Recipe Name: Strawberry Cake" crlf)
    (printout t "Minimum number of cakes that can be made: " ?num-cakes crlf)
    (assert (RecipeName (recipe-names "Strawberry Cake")))
    )
    else
    (printout t "Insufficient ingredients to make Strawberry Cake." crlf)
)

(defrule norecipe
  (not (RecipeName (recipe-names $?)))
  =>
  (printout t "No matching recipe found." crlf)
)

Getting a result like this:

CLIPS> (assert (Ingredient (name "sugar")))
<Fact-1>
CLIPS> (assert (Ingredient (name "butter")))
<Fact-2>
CLIPS> (assert (Ingredient (name "chocolate")))
<Fact-3>
CLIPS> (run)
Insufficient ingredients to make Chocolate Cake.
No matching recipe found.

The result should be:

Recipe Name: Chocolate Cake
Minimum number of cakes that can be made: 150

Solution

  • Strings and symbols are different data types. You use strings (such as "chocolate") in some places and symbols (such as chocolate) in others. In your count-initial-quantity function, the comparison (eq ?f:ingredient ?ingredient) fails because a string is compared to a symbol. Change the symbols to strings in your initial-quantities deffacts and the comparison in your function will work properly:

    (deffacts initial-quantities
       (InitialQuantity (ingredient "chocolate") (quantity 200))
       (InitialQuantity (ingredient "butter") (quantity 150))
       (InitialQuantity (ingredient "sugar") (quantity 250))
       (InitialQuantity (ingredient "flour") (quantity 300))
       (InitialQuantity (ingredient "strawberry") (quantity 2))
    )
    

    There's also a misplaced parenthesis in the if statement of your rule. The right parenthesis before the else should be moved after the following printout statement:

    (defrule chocolatecake
      (Ingredient (name "chocolate"))
      (Ingredient (name "butter"))
      (Ingredient (name "sugar"))
      (not (RecipeName (recipe-names $? "Chocolate Cake")))
      =>
      (bind ?num-cakes (min (count-initial-quantity "chocolate") (count-initial-quantity "butter") (count-initial-quantity "sugar")))
      (if (>= ?num-cakes 1)
        then
        (printout t "Recipe Name: Chocolate Cake" crlf)
        (printout t "Minimum number of cakes that can be made: " ?num-cakes crlf)
        (assert (RecipeName (recipe-names "Chocolate Cake")))
        else
        (printout t "Insufficient ingredients to make Chocolate Cake." crlf))
    )