initial commit of goal support

65 views
Skip to first unread message

denis.be...@gmail.com

unread,
Sep 7, 2023, 10:59:10 PM9/7/23
to CLIPSESG
Release r856 appears to be an "initial commit of goal support" for CLIPS 6.5x
Could you say more about it and explain the main lines of new version 6.5x? 

CLIPS Support

unread,
Sep 8, 2023, 5:50:27 PM9/8/23
to CLIPSESG
Here's an explanation of how goal generations works in a forward chaining system: http://haleyai.com/pdf/BackwardChaining.pdf

Ryan Johnston

unread,
Sep 8, 2023, 10:44:05 PM9/8/23
to CLIPSESG
Hey, Gary,

I read through the pdf you linked, and I'm trying to wrap my head around the example given at the end. Here's how I'm currently understanding the description:

(defrule cousins-may-inherit-trait
    (has ?x ?trait)
    (cousin ?x ?y)
    (has ?y ?trait)
    =>
    (assert (inherited (status possible) (trait ?trait))))

(defrule cousin
    (goal (cousin ?x ?y))
    (parent ?x ?parent-1)
    (sibling ?parent-1 ?parent-2)
    (parent ?y ?parent-2)
    =>
    (assert (cousin ?x ?y)))

(defrule sibling
    (goal (sibling ?x ?y))
    (parent ?x ?parent)
    (parent ?y ?parent)
    =>
    (assert (sibling ?x ?y)))

(assert (has John freckles))
==> f-1 (has John freckles)
==> g-1 (cousin John ?1)
;(defrule cousins-may-inherit-trait
;    (has John freckles)
;    (cousin John ?y)
;    (has ?y freckles)
;    =>
;    (assert (inherited (status possible) (trait freckles))))

(assert (parent John George))
==> f-2 (parent John George)
==> cousins g-1,f-2
==> g-2 (sibling George ?1)
;(defrule cousin
;   (goal (cousin John ?y))
;   (parent John George)
;   (sibling George ?parent-2)
;   (parent ?y ?parent-2)
;   =>
;   (assert (cousin John ?y)))
;(defrule sibling
;    (goal (sibling George ?y))
;    (parent George ?parent)
;    (parent ?y ?parent)
;    =>
;    (assert (sibling George ?y)))

(assert (parent George Adam))
==> f-3 (parent George Adam)
==> sibling g-2,f-3
;(defrule sibling
;    (goal (sibling George ?y))
;    (parent George Adam)
;    (parent ?y Adam)
;    =>
;    (assert (sibling George ?y)))

(assert (parent Sally Adam))
==> f-4 (parent Sally Adam)
==> sibling g-2,f-3,f-4
;(defrule sibling
;    (goal (sibling George Sally))
;    (parent George Adam)
;    (parent Sally Adam)
;    =>
;    (assert (sibling George Sally)))
==> activation sibling g-2,f-3,f-4
(run)
FIRE sibling g-2,f-3,f-4
==> f-5 (sibling George Sally)
==> cousin g-1,f-2,f-5
;(defrule cousin
;   (goal (cousin John ?y))
;   (parent John George)
;   (sibling George Sally)
;   (parent ?y Sally)
;   =>
;   (assert (cousin John ?y)))

(assert (parent Mary Sally))
==> f-6 (parent Mary Sally)
==> cousin g-1,f-2,f-5,f-6
;(defrule cousin
;   (goal (cousin John Mary))
;   (parent John George)
;   (sibling George Sally)
;   (parent Mary Sally)
;   =>
;   (assert (cousin John Mary)))
==> activation cousin g-1,f-2,f-5,f-6
(run)
FIRE cousin g-1,f-2,f-5,f-6
==> f-7 (cousin John Mary)
==> cousins-may-inherit-trait f-1,f-7
;(defrule cousins-may-inherit-trait
;    (has John freckles)
;    (cousin John Mary)
;    (has ?y freckles)
;    =>
;    (assert (inherited (status possible) (trait freckles))))

> From which point, asserting that Mary has freckles
> activates the rule, thereby leading to the assertion the freckles
> may be inherited.
(assert (has Mary freckles))

What is the value of goals in the above scenario? Since we have these "intermediary" facts to represent siblings and cousins, I'm not sure I understand the role of goals.

denis.be...@gmail.com

unread,
Sep 9, 2023, 12:33:07 AM9/9/23
to CLIPSESG
My question was more general. Sure, I asked about goals, but I also asked about the future of CLIPS. Which new features do you plan to implement? Goals as presented don't seem to bring much. What about e.g. joins from the right and the possibility to write explicit JOIN conditions? 
Could you provide us some general ideas about what you plan to do in e.g. the next 5 years?

CLIPS Support

unread,
Sep 9, 2023, 3:44:08 PM9/9/23
to CLIPSESG
My current interest is writing books on programming with CLIPS. The next one I'm working on will focus on building some of the common types of expert systems. If you look at the types of CLIPS projects uploaded to GitHub, the majority fall into the categories of diagnosis, classification, or decision trees. Typically all of these programs ask the user questions and it's preferable to limit the questions asked to the fewest number needed to make a conclusion. If you're strictly using forward chaining it's possible to do this, but the burden of analyzing the dependencies and encoding them falls on the programmer. In the current version of CLIPS, that means you end up creating a lot of rules that look like this (from the auto.clp example):

(defrule determine-engine-state ""
   (not (engine-starts ?))
   (not (repair ?))
   =>
   (assert (engine-starts (yes-or-no-p "Does the engine start (yes/no)? "))))
   
(defrule determine-runs-normally ""
   (engine-starts yes)
   (not (repair ?))
   =>
   (assert (runs-normally (yes-or-no-p "Does the engine run normally (yes/no)? "))))

(defrule determine-sluggishness ""
   (runs-normally no)
   (not (repair ?))
   =>
   (assert (engine-sluggish (yes-or-no-p "Is the engine sluggish (yes/no)? "))))

   .
   .
   .

(defrule engine-sluggish ""
   (engine-sluggish yes)
   (not (repair ?))
   =>
   (assert (repair "Clean the fuel line."))) 

Using goals, your rule can be changed to this:

(defrule engine-sluggish
   (av (attribute engine-starts) (value yes))
   (av (attribute runs-normally) (value no))
   (av (attribute engine-sluggish) (value yes))
   =>
   (assert (repair "Clean the fuel line."))) 


And you have a single rule for asking all the questions:

(defrule determine-attribute
   (declare (salience -10))
   (goal (av (attribute ?attribute)))
   (not (av (attribute ?attribute)))
   (question (attribute ?attribute)
             (text ?text)
             (valid-responses $?responses))
   (not (repair ?))
   =>
   (bind ?value (ask-question ?text ?responses))
   (assert (av (attribute ?attribute) (value ?value))))


Here's a smaller example which shows when the goals are generated as they are needed for the case-1 rule:

CLIPS>
(deftemplate av
   (slot attribute)
   (slot value))
CLIPS>
(defrule ask
   (goal (av (attribute ?a)))
   (not (av (attribute ?a)))
   =>
   (print "What is the value for " ?a "? ")
   (bind ?v (read))
   (assert (av (attribute ?a) (value ?v))))
CLIPS>
(defrule case-1
   (av (attribute weather) (value sunny))
   (av (attribute humidity) (value high))
   =>
   (println "Today is not a good day to play outside."))
CLIPS> (reset)
CLIPS> (agenda)
0      ask: g-1,*
For a total of 1 activation.
CLIPS> (goals)
g-1     (av (attribute weather) (value sunny))
For a total of 1 goal.
CLIPS> (run 1)
What is the value for weather? sunny
CLIPS> (agenda)
0      ask: g-2,*
For a total of 1 activation.
CLIPS> (goals)
g-2     (av (attribute humidity) (value high))
For a total of 1 goal.
CLIPS> (run)
What is the value for humidity? high
Today is not a good day to play outside.
CLIPS> 


One of the other things I'd like to add for 6.5 is inheritance for deftemplates as well as support for a true modify operation (rather than the current retract/assert) and triggering of fact patterns only when slot specified in the pattern change.

As for explicit joins, I don't know if/when that will ever get added. To be honest, all of that code is very tedious/complex to work with so I don't know when if ever I would be motivated enough to dig into it again if there was something else of greater interest to me. If I recall correctly, explicit joins were added to ART programming language decades ago as an enhancement for their object system. Objects were presented to the user as a single object, but they were represented internally as object/attribute/values, so they really needed the ability to use explicit joins to allow their object system to work well. Allowing the programmer to place them around patterns came as a freebie.

CLIPS Support

unread,
Sep 9, 2023, 6:02:15 PM9/9/23
to CLIPSESG
So the idea of a goal is that if you have a pattern in a rule that's not satisfied, you can set up your program so that a goal will be generated so that another rule can assert a fact to satisfy that pattern. The simplest use case for goals is applications like diagnosis, classification, and decision trees. Here's a simple set of cases for determining whether to play:

Case 1 is a scenario where:
  The weather is sunny;
  The humidity is high;
  The recommendation is "Don't play.".

Case 2 is a scenario where:
  The weather is sunny;
  The humidity is normal;
  The recommendation is "Play.".

Case 3 is a scenario where:
  The weather is cloudy;
  The recommendation is "Play.".

Case 4 is a scenario where:
  The weather is rainy;
  The wind is weak;
  The recommendation is "Play.".

Case 5 is a scenario where:
  The weather is rainy;
  The wind is strong;
  The recommendation is "Don't play.".


If we were encoding this in CLIPS just using forward chaining rules, we'd have to analyze our cases and encode the correct order to ask questions. One rule would ask whether the weather was sunny, cloudy, or rainy. If the weather was sunny, another rule would ask about the humidity, and if the weather was rainy, another rule would ask about the wind. 

In a system that automatically generates goals we wouldn't have to do that. The system would automatically determine that the first pattern in all of the rules was unsatisfied and then generate a goal to determine the weather. Once the weather is determined, the system would automatically generate a goal to determine either the humidity or wind if necessary to satisfy the second pattern in these rules. Because the rules for processing goals can be generalized, we can usually have a single simple rule process the goal, whereas without that we would have to multiple rules to determine which question to ask.

Another use case for goals is when we have a piece of information that can be computed, but we don't want to compute that information until we know it's needed (either because it's expensive to do so or there would be a lot of unnecessary information generated).

Another use case I can think of is for planning problems like the monkey and bananas problem or towers of Hanoi where you have a long series of goals being generated to accomplish a task.

So the paper is making a lot of points about how goal generation should work for some situations which are a little more complex than just having goals without any unknown values. The point of the intermediary facts might have been easier to understand if there were a number of parent facts unrelated to John and his family. The fact (has John freckles) begins the generation of goals. The rules generating cousin and sibling facts only generate facts based on goals. So cousin and sibling facts will only be created for John's family through his parents. Cousins and siblings will not be generated for other families.

denis.berthier

unread,
Sep 9, 2023, 11:02:40 PM9/9/23
to clip...@googlegroups.com
>
> One of the other things I'd like to add for 6.5 is inheritance for deftemplates as well as support for a true modify operation (rather than the current retract/assert) and triggering of fact patterns only when slot specified in the pattern change.

IMO, the strongest problem with the current "modify" is, it doesn’t keep the logical dependencies of the original fact. Note that it may be the right thing to do in some cases, but not always. It depends on application-specific semantics. As a result, there should be a parameter specifying which dependencies should be assigned to the modified fact: original, new (from the "logical" of the current rule) or addition of both - possibly with some default value for upwards consistency with the current modify.
As an artificial example where addition of both seems the right thing to do, suppose a first rule creates a fact (car brand no_colour) meaning the making of a car with no painting yet. Then a modify "paint-red" rule means it has been processed through painting and its colour is now red. One will want the modified fact (car brand red) to depend on the logicals of the two rules. [Sure, there are other ways to write the rules; that’s just a contextless example.]

Now, if you ask me, there’s a much worse and more elementary problem: the globals. More precisely, the confusion between globals and constants. This makes the writing of rules very error prone. Even after 20 years of using CLIPS, I can make the error.
If you had separate defglobal and defconstant, you could check at compile time that no global is allowed in rules at places where they will be considered as constants.
More precisely, they needn’t be real constants, but compile-time constants and they should have a special syntax, e.g. ?!*my_constant*.
Reply all
Reply to author
Forward
0 new messages