I am using CLIPS, and I want to use control flags to trigger events. In this case, I am mimicking a simple traffic light system.
When the light is red (i.e. when (light red)
is asserted), then the rule will print 'Go' and if the fact was previously green (i.e. (light green)
fact exists), then the (light green)
fact is retracted.
The purpose of this is a kind poor man's truth maintenance scheme - but more importantly, and of more practical use, it means that CLIPS responds to facts it has seen before (after they have been retracted by another rule).
Here is CLIPS pseudocode that tries to implement this functionality:
(defrule go
(light green)
=>
(if (fact-existsp (light red))
then
(retract (light red)))
(printout t "Go" crlf))
(defrule stop
(light red)
=>
(if (fact-existsp (light green))
then
(retract (light green)))
(printout t "Stop" crlf))
Note: fact-existsp
is a function that returns a boolean value as to whether a fact exists or not.
This is the kind of response I want:
CLIPS> (assert(light green))
CLIPS> (run)
Go
CLIPS> (assert(light red))
CLIPS> (run)
Stop
CLIPS> (assert(light green))
CLIPS> (run)
Go
Note that I didn't have to (reset)
.
How do I modify the two rules above to get this behaviour from CLIPS?
[[Edit]]
Following on from Gary's answer, I wanted to extend my question a little - whatabout if instead of using a single field variable, light was a multifield variable, and I wanted to do the toggling on a specific field (or fields?).
Here is my extended code, showing this scenario - where I want to toggle the flag based on geolocation field match (i.e. color changed):
(deftemplate light
(slot color (type SYMBOL) (allowed-symbols red green yellow))
(multislot geolocation )
)
(defrule go
(light (color green))
=>
(printout t "Go" crlf))
(defrule stop
(light (color red))
=>
(printout t "Stop" crlf))
(defrule light-changed
?f1 <- (light (color ?c1) (geolocation ?g))
?f2 <- (light (color ?c2) (geolocation ?g))
(test (< (fact-index ?f1) (fact-index ?f2)))
=>
(retract ?f1))
CLIPS> (facts)
f-1 (light (color green) (geolocation 12.34 56.78))
f-2 (light (color red) (geolocation 23.45 69.69))
For a total of 2 facts.
CLIPS> ( assert(light (color yellow) (geolocation 23.45 69.69)))
==> f-3 (light (color yellow) (geolocation 23.45 69.69))
<Fact-3>
CLIPS> (run)
CLIPS> (assert(light (color green) (geolocation 23.45 69.69)))
==> f-4 (light (color green) (geolocation 23.45 69.69))
==> Activation 0 go: f-4
<Fact-4>
CLIPS> (run)
Go
CLIPS>
Here, the color was changing from yellow to green, and the geolocation wasn't changing, so I was expecting the light-changed
rule to fire, but it didn't. Why? - and how do I fix the code to get the "correct" behaviour (i.e. enforce logic criteria on the fields of the variable in my light-changed
rule)?
I'd suggest adding a rule which deletes the older fact using the fact-index to determine which fact is older:
CLIPS (6.4 2/9/21)
CLIPS>
(defrule go
(light green)
=>
(printout t "Go" crlf))
CLIPS>
(defrule stop
(light red)
=>
(printout t "Stop" crlf))
CLIPS>
(defrule light-changed
?f1 <- (light ?)
?f2 <- (light ?)
(test (< (fact-index ?f1) (fact-index ?f2)))
=>
(retract ?f1))
CLIPS> (assert (light green))
<Fact-1>
CLIPS> (run)
Go
CLIPS> (assert (light red))
<Fact-2>
CLIPS> (run)
Stop
CLIPS> (assert (light green))
<Fact-3>
CLIPS> (run)
Go
CLIPS> (facts)
f-3 (light green)
For a total of 1 fact.
CLIPS>
In your extended example, you're using the single field variable ?g in the geolocation slot in the pattern. This will only match a fact that has one value in its geolocation slot. Since you have two values that must match, use the multifield variable $?g. You also don't need to include the color slot in the pattern.
(defrule light-changed
?f1 <- (light (geolocation $?g))
?f2 <- (light (geolocation $?g))
(test (< (fact-index ?f1) (fact-index ?f2)))
=>
(retract ?f1))