Professional Documents
Culture Documents
(clear) Removes all rules and facts from memory. Equivalent to shutting down and
restarting CLIPS.
(reset) Removes facts information from memory (but not rules) and resets the
agenda.
(run)
The above commands can also be executed from the CLIPS menu bar.
CLIPS>(retract 0)
CLIPS>(facts)
f-1 (colour red)
For a total of 1 fact.
There are two things to note here: firstly, to retract a fact you must specify a
number (the fact-index), not the fact itself, and secondly, fact-indices are not
reused. Once fact 0 has been retracted, the next fact asserted will have the index 2,
not 0.
Facts on their own are of only limited use. The application of rules is necessary to
develop a program capable of some useful function. In general, a rule is expressed in
the form IF something is true THEN do some action. This kind of rule is known as a
production. For this reason, rule-based expert systems are often known as
production systems (CLIPS actually stands for C Language Integrated Production
System). In CLIPS, a typical rule looks like this:
(defrule duck
(animal-is duck)
=>
(assert (sound-is quack)))
The rule consists of three parts. The first part, (defrule duck, simply gives the rule
a unique name. The second part, (animal-is duck), is the pattern (the IF part) of
the rule and the last part, (assert (sound-is quack)), is the action (the THEN
part). In plain language, this rule means if there is a fact (animal-is duck) on the
fact database, then assert another fact, (sound-is quack), onto the fact database.
Try it. Clear the system, then type in the rule exactly as printed above. Typing
(rules) will give you a list of rules (just the one, in this case) present in the system.
At this point, there are no facts present. Now, type (assert (animal-is duck)).
Check the fact list - theres one fact. To trigger your rule, type (run). Although
nothing appears to happen, if you check the fact list again youll see that there is a
new fact, (sound-is quack), which has been inferred by the rule. This is the power
of rule-based programming - the ability to make inferences from data, particularly as
the results of one rule can be used as the pattern for another. Add the rule
(defrule is-it-a-duck
(animal-has webbed-feet)
(animal-has feathers)
=>
(assert (animal-is duck)))
Then type (reset) to clear the facts (the rules will be untouched). Note that this rule
has two patterns. Both must be satisfied for the action to be taken. This translates to
IF the animal has webbed feet AND the animal has feathers THEN the animal is a
duck (taxonomists and pedants may disagree with this rule). If you now assert the
facts (animal-has webbed-feet) and (animal-has feathers) there will be two
facts present. (run) the rules, and suddenly there are four. Firstly, rule is-it-aduck has fired, asserting the fact (animal-is duck). This fact has then triggered
rule duck, which has asserted the fact (sound-is quack). Very powerful systems
can be built using this ability to chain rules.
Asserting facts is a rather unsatisfactory way of presenting results. Type in the first
rule again, this time with the multiple actions as shown below:
(defrule duck
(animal-is duck)
=>
(assert (sound-is quack))
(printout t "its a duck" crlf))
Next time you run the rules, you'll get a message on screen as well as the asserted
quack fact.
Its rather inefficient having to type all your rules in each time you run
CLIPS. Fortunately, you can load them from a file using the Load
Constructs.. command on the file menu. CLIPS will expect a file with the
extension .CLP, and theres a handy editor to help you create them. You
cant put facts in a .CLP file in the same way as you can from the command prompt,
so for now youll
still enter them as
before.
Heres a more
complex example
of rules and facts.
The decision tree
opposite
represents a small
section of the
diagnosis of a
cars failure to
start. Each
rounded box is a
recommended
remedy. Each
rectangular box is
piece of evidence,
which might be
represented by a
fact such as
(lights-working no) or (petrol yes). Each connecting path to a remedy represents
a rule, for example IF starter is turning AND there is no petrol THEN buy some
petrol.
Matching things
So far, the patterns used to match rules against facts have been very simple and
rather restrictive. Each pattern has matched one specific fact. By using wildcards, it
is possible to make rules match multiple facts, executing their actions repeatedly.
For instance, the rule:
(defrule animal
(animal ?)
=>
(printout t "animal found" crlf))
Produces the following results when run:
CLIPS>(run)
Animal found
Animal found
Animal found
Animal found
CLIPS>
Which shows that it has triggered four times, once for each fact matching the
(animal ?) pattern. In this pattern, the ? symbol is a wildcard. It will match any
symbol. You can use as many wildcards as you like in a pattern, but the first symbol
may not be one. So (child-of ? ?) is legal and will match four facts, but
(? ? hatchling) is illegal.
Variables in patterns
Simple wildcards are only mildly useful. Variables make them indispensable. If we
use something like ?var instead of ? on its own, we can use the value of ?var each
time the rule is fired. Try this example:
(defrule list-animals
(animal ?name)
=>
(printout t ?name " found" crlf))
This will produce the following results:
CLIPS>(run)
turtle found
duck found
cat found
dog found
CLIPS>
The rule has matched four facts, and each time the variable ?name has taken the
value of the symbol it represents in the pattern, so that in the action part of the rule
it can be printed. The real power of this feature is apparent when two or more
patterns are used, as in the next example:
(defrule mammal
(animal ?name)
(warm-blooded ?name)
(not (lays-eggs ?name))
=>
(assert (mammal ?name))
(printout t ?name " is a mammal" crlf))
You may notice the not function sneaked in there. The purpose of this should be
self-evident. This rule gives the results
CLIPS>(run)
cat is a mammal
dog is a mammal
CLIPS>
When you are satisfied that you understand how this works, try the next step:
(defrule mammal2
(mammal ?name)
(child-of ?name ?young)
=>
(assert (mammal ?young))
(printout t ?young " is a mammal" crlf))
After you have run this rule, look at the fact list
CLIPS>(run)
kitten is a mammal
puppy is a mammal
CLIPS>
CLIPS>(+ 5 7)
12
CLIPS>(- 5 7)
-2
CLIPS>(* 5 7)
35
CLIPS>(/ 5 7)
0.7142857142857143
CLIPS>
Rewrite the expression 10+4*19-35/12 in CLIPS notation and verify that you get the
result 83.0833. (Answer at bottom of page).
CLIPS Tutorial 3
More about wildcard patterns
In the last tutorial, you were introduced to the concept of wildcard pattern matching,
in which the symbol ? is used to take the place of a symbol on the left hand side of a
rule. Suppose we have the facts
?member
matches
last $? matches
paul_mccartney
george_harrison ringo_starr
john_lennon
paul_mccartney george_harrison ringo_starr
john_lennon paul_mccartney george_harrison ringo_starr
john_lennon paul_mccartney
ringo_starr
nothing
george_harrison
nothing
john_lennon
Functions
Rules and facts, while offering great flexibility, are not suited to all tasks.
CLIPS offers a full range of procedural programming functions as well.
other part of Andrew's personal data (weight, for example) was changed the birthday
rule would be fired again, causing rapid ageing!
Template-based facts can be used in rules just as ordinary facts, thus:
(defrule lardy-bugger
(personal-data (name ?name) (weight ?weight))
(test (> ?weight 100))
=>
(printout t ?name " weighs " ?weight " kg - the fat sod." crlf)
)
But, wait! I hear you cry - what's all this (test (> ?weight 100)) business? That's
what is known as a conditional element. It allows a rule to do a certain amount of
expression evaluation on the left hand side of the rule. In this case, it will match
facts where the value of the weight slot is greater than 100 kg. There are several
ways of employing conditional elements in rules. The first is the logical and which is
implied simply by using two patterns in the left hand side of a rule, thus:
(defrule print-ages
(personal-data (name ?name) (age ?age))
=>
(printout t ?name " is " ?age " years old." crlf)
)
This rule is really saying "if there is a fact with a name and an age, then print it out".
The and connective can also be used explicitly if needed, thus:
(defrule print-ages
(and
(personal-data (name ?name) (age ?age))
(personal-data (name ?name) (weight ?weight))
)
=>
(printout t ?name " weighs " ?weight " at " ?age " years old."
crlf)
)
Although the and is superfluous in the above example, it is very useful in more
complex logical constructs, as we shall see. We can also use the or connective, thus:
(defrule take-an-umbrella
(or
(weather raining)
(weather snowing)
)
=>
(printout t "Take an umbrella" crlf)
)
Which of course means "if it's snowing or raining, take an umbrella". Notice that both
or and and are prefix operators, just like addition or subtraction, so you write (or
(thing1) (thing2)), not ((thing) or (thing2)). The other conditional element
recognisable from traditional logic is not, which simply negates the truth of a
predicate, thus:
(defrule not-birthday
(personal-data (name ?name) (weight ?weight))
(not (birthday ?name))
=>
(printout t "It's not " ?name "'s birthday" crlf)
)
Now, back to that test element. Using test allows you to check anything in the left
hand side of a rule, not just facts. So, we could write a rule which (for no readily
apparent reason) checks to see if six is greater than five:
(defrule pointless
(test (> 6 5))
=>
(printout t "Six is indeed greater than five" crlf)
)
The final two conditional elements are exists and forall. exists is satisfied if
there are one or more facts which match its predicate; the rule containing it is fired
only once regardless of how many facts it matches. forall, on the other hand,
triggers once if every fact matches its pattern. Make sure you have the personal data
of a few people on the fact base, then try the following two rules:
(defrule person-exists
(personal-data (name ?name))
=>
(printout t "Rule person exists reports there is a person called
" ?name crlf)
)
(defrule are-there-people
(exists (personal-data (name ?name)))
=>
(printout t "Rule are-there-people reports there is at least one
person" crlf)
)
(defrule check-each-person
(forall (personal-data (name ?name))
(
)
=>
(printout t "Rule check-each-person reports that all persons
have a name" crlf)
)
Now try creating a new person without a name, and run them again. Rule checkeach-person will not fire because it is not true for all facts which match its pattern.
Summary
This rule tests whether a person is overweight and a smoker. If he is, then it asserts
a warning that his heart is at risk. Now what happens if the person gives up
smoking, or loses weight? The warning fact will still be present, because it is not
linked in any way to the rule which created it. This means that the fact base is no
longer consistent with reality. We can overcome the problem by using the logical
element. Modify the rule you have just entered so that it looks like this:
(defrule cardiac-risk
(logical (person (name ?name) (smoker yes) (weight ?weight)))
(logical (test (> ?weight 100)))
=>
(assert (cardiac-risk ?name))
)
When we run this rule, the results are identical to those generated by the first
version (i.e. the facts (cardiac-risk brenda) and (cardiac-risk charles) are
asserted). The difference occurs if we change the initial data. Make sure the fact
window is open, then having run the rule, locate the fact-index of Brenda's personal
data (on my system, it's 2) and type at the command line
CLIPS> (modify 2 (weight 80))
As if by magic, the fact (cardiac-risk brenda) disappears from the fact list. By
using the logical keyword, we have created a link between the fact asserted and
the premises on which it was based. When the premises changed, their results were
no longer valid, so they were removed. So why not make everything logical in this
way? Firstly, it increases the memory and processing time needed - in a complex
system, there can be many links to check. Secondly, it's not always appropriate - for
most applications, the standard facts are quite sufficient.
As you are now aware, the order in which rules are triggered in CLIPS is not easily
controlled. Any rule which matches a fact may be placed on the agenda in any
position. How, then, are we to establish any sort of control over our expert systems?
There are two primary solutions to this problem - by using control facts or salience.
We'll come back to salience later. A control fact is one whose only purpose is to
direct program operation rather than to express knowledge about the problem
domain. By including these control facts in the left hand sides of rules, we can
control when the rules will fire. For example, suppose we wish to check all our
personal data records to see whose birthday it is today, and update their ages
accordingly. If we have a fact of the form (date 18 6 2000) in the fact list
representing today's date, then the rule
(defrule birthdays-today
?person <- (personal-data (age ?age) (date-of-birth ?day ?month
?))
(date ?day ?month ?)
=>
(modify ?person (age (+ ?age 1)))
)
might seem a reasonable way of doing this. Try it, and see what happens. You might
want to know that pressing ctrl-break will stop a CLIPS program. Why doesn't this
work? The reason it gets into an infinite loop is that the modify function actually
retracts the personal-data fact and asserts a new one with the updated data in it.
This new fact then matches the same rule, which causes it to be modified, and so on
until the end of time. The way around this problem is to divide the task into discrete
phases and use control facts to execute them in order. there are really two phases to
this problem - identifying all the people who have birthdays today, and then updating
their ages. We can implement this using the four rules below:
(defrule birthdays-today
(check-birthdays)
(personal-data (name ?name) (date-of-birth ?day ?month ?))
(date ?day ?month ?)
=>
(assert (birthday ?name))
)
(defrule done-checking-birthdays
?check-birthday-fact <- (check-birthdays)
(forall (and (personal-data (name ?name) (date-of-birth ?day
?month ?))
(date ?day ?month ?)
)
(birthday ?name)
)
=>
(retract ?check-birthday-fact)
(assert (update-ages))
)
(defrule update-ages
?person<-(personal-data (name ?name) (age ?age) (date-of-birth
?day ?month ?year))
?birthday-fact<-(birthday ?name)
(update-ages)
=>
(modify ?person (age (+ ?age 1)))
(retract ?birthday-fact)
)
(defrule done-updating-ages
?update-age-fact<-(update-ages)
(not (birthday ?))
=>
(retract ?update-age-fact)
)
The phases are controlled by two facts: (check-birthdays) and (update-ages). In
phase 1, which is triggered by the appearance of the fact (check-birthdays), rule
birthdays-today asserts a new fact for each person who has a birthday today. Rule
done-checking-birthdays detects when this process is finished, retracts (checkbirthdays) and asserts (update-ages). The second phase now begins, and rule
update-ages modifies each person in turn, retracting the birthday facts in turn as it
does so. When there are no more birthday facts, rule done-updating-ages fires and
retracts (update-ages) for good housekeeping purposes. This may all seem a little
complex for what should be a simple task, and indeed it is. But it does illustrate the
use of control facts.
The other way of controlling rule firing is by using the salience property of rules.
When a rule is declared, it may be given a salience value between -10000 and
10000, the default value being 0. All other things being equal, rules with a higher
salience are fired before rules with a lower salience. Consider the following two rules:
(defrule poke-fun-at-smokers
(personal-data (name ?name) (smoker yes))
=>
(printout t ?name " is a fool." crlf)
)
(defrule worry-about-thin-people
(personal-data (name ?name) (weight ?weight))
(test (< ?weight 80))
=>
(printout t ?name " is looking a bit thin." crlf)
)
As things stand, if you run those rules then they will both fire, but the order of their
firing will depend only upon the order in which the facts on which they depend were
created. If, however, you modify them thus:
(defrule poke-fun-at-smokers
(declare (salience 10))
(personal-data (name ?name) (smoker yes))
=>
(printout t ?name " is a fool." crlf)
)
(defrule worry-about-thin-people
(declare (salience 20))
(personal-data (name ?name) (weight ?weight))
(test (< ?weight 80))
=>
(printout t ?name " is looking a bit thin." crlf)
)
then the higher salience of rule worry-about-thin-people will ensure that it fires
first. As a general rule, try to use salience sparingly. If you find you need many
levels of salience (more than four is a good rule of thumb) then you should probably
consider either (a) writing your program in a language which gives you the level of
control you desire, or (b) rewriting your program to suit the production system
paradigm. There is a third way of controlling the execution of rules or groups of
rules, and that is by collecting them into modules, only one of which is active at any
given time.
In addition to facts and rules, CLIPS also offers a full set of the programming
functions associated with 'normal' procedural languages. We're now going to
demonstrate some of them by programming CLIPS badly to set up the cards for a
game of pontoon. We'll use the following snippet of code to get started:
(defglobal ?*shuffleswaps* = 150)
(deffacts cards
(card-names ace two three four five six seven eight nine ten
jack queen king)
(card-values 1 2 3 4 5 6 7 8 9 10 10 10 10)
(card-suits hearts clubs diamonds spades)
)
The first job is to set up the pack of cards we will us to play the game. This is done
with a two rules, thus:
(defrule go
(initial-fact)
=>
(assert (state create-pack))
)
(defrule create-cards
?old-state <- (state create-pack)
(card-names $?names)
(card-suits $?suits)
=>
(bind ?number 0)
(loop-for-count (?suit 1 4) do
(loop-for-count (?name 1 13) do
(bind ?number (+ ?number 1))
(assert (draw-pile (nth$ ?name ?names) (nth$
?suit ?suits) ?number))
)
)
(assert (top-card 1))
(retract ?old-state)
(assert (state shuffle-pack))
)
The first rule is of course only a control rule; the second does the work. It uses a
construct you've not seen before - the loop-for-count iterator. This is very similar
to a for loop in C or BASIC. You give it a variable, a lower bound and an upper
bound, and it repeats everything between the do and the closing bracket the
appropriate number of times, incrementing the variable from the lower to the upper
bounds. You might also want to look at the while function, which is similar. Another
new function in this rule is nth$, which returns a single value at a given position in a
multifield variable. So, if we had a variable ?a which had the multiple value (dog cat
fish), then (nth$ 2 ?a) would return the value (fish). So, by the time this rule
has finished, we have a pack of cards, but they are in a perfect order. We need to
shuffle them. The following two rules do just that:
(defrule start-shuffle
(state shuffle-pack)
(not (swap-count ?))
=>
(seed (round (time)))
(assert (swap-count 1))
(assert (swap-position1 (round (+ (* (/ (random) 32767) 51)
1))))
(assert (swap-position2 (round (+ (* (/ (random) 32767) 51)
1))))
)
(defrule shuffle-pack
(state shuffle-pack)
?swapcard1 <- (swap-position1 ?cp1)
?swapcard2 <- (swap-position2 ?cp2)
?swapcount <- (swap-count ?cc)
(test (< ?cc ?*shuffleswaps*))
?card1 <- (draw-pile ?name1 ?suit1 ?cp1)
?card2 <- (draw-pile ?name2 ?suit2 ?cp2)
=>
(retract ?card1)
(retract ?card2)
(retract ?swapcount)
(retract ?swapcard1)
(retract ?swapcard2)
(assert (draw-pile ?name1 ?suit1 ?cp2))
(assert (draw-pile ?name2 ?suit2 ?cp1))
(assert (swap-count (+ ?cc 1)))
(assert (swap-position1 (round (+ (* (/ (random) 32767) 51)
1))))
(assert (swap-position2 (round (+ (* (/ (random) 32767) 51)
1))))
)
(defrule pack-shuffled
?state <- (state shuffle-pack)
?swapcount <- (swap-count ?cc)
(test (= ?cc ?*shuffleswaps*))
=>
(retract ?swapcount)
(retract ?state)
(assert (state print-deck))
)
The shuffling algorithm isn't brilliant. All it does is pick random pairs of cards and
swap their places in the pack. There is no guarantee that the pack will be well
shuffled. (How could you do this better?) The only new things in this rule are (seed
(round (time))) and (random).
The next part of the program prints out the deck of cards in the order they would be
dealt.
(defrule start-printdeck
(state print-deck)
This rule uses a function (card-value) which is not part of CLIPS, but is userdefined. The definition is below:
(deffunction card-value
(?card-name)
(switch ?card-name
(case ace then (bind ?return-value 1))
(case two then (bind ?return-value 2))
(case three then (bind ?return-value 3))
(case four then (bind ?return-value 4))
(case five then (bind ?return-value 5))
(case six then (bind ?return-value 6))
(case seven then (bind ?return-value 7))
(case eight then (bind ?return-value 8))
(case nine then (bind ?return-value 9))
(case ten then (bind ?return-value 10))
(case jack then (bind ?return-value 10))
(case queen then (bind ?return-value 10))
(case king then (bind ?return-value 10))
(default (bind ?return-value 0))
)
(return ?return-value)
)
The function takes the name of a card (ace, two, king etc) and returns its numeric
value. It uses the switch statement to do this.