CLIPS Tutorial 1

advertisement
CLIPS Tutorial 1
Working with CLIPS
To start CLIPS, double-click on the CLIPSWin.exe file. You’ll get a window
with nothing in it but the command prompt CLIPS>. For the time being, this is
where you type in your commands and programs. To exit CLIPS, type (exit)
or shut down the program like any other windows application. Note that
CLIPS commands are always encased in brackets thus: (assert (foo)). Here is a
list of some important commands:
(exit)
Shuts down CLIPS
(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)
Starts executing a CLIPS program.
The above commands can also be executed from the CLIPS menu bar.
Facts and rules
At its most basic, CLIPS operates by maintaining a list of facts and a set of rules
which operate on them. A fact is a piece of information such as (colour green) or
(parent_of John Susan). Facts are created by asserting them onto the fact
database using the assert command. Here’s an example, complete with the
response from CLIPS:
CLIPS>(assert (colour green))
<Fact-0>
The <Fact-0> part is the response from CLIPS to say that a new fact (fact number 0)
has been placed on the fact database. The (facts) command will list all current
facts. Try it, and you’ll get the following:
CLIPS>(facts)
f-0 (colour green)
For a total of 1 fact.
Facts may also be retracted (removed) from the fact database by using the retract
command. As an example, assert two facts as shown:
CLIPS>(assert (colour green))
<Fact-0>
CLIPS>(assert (colour red))
<Fact-1>
Then retract the first fact and display the fact list:
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 - there’s one fact. To trigger your rule, type (run). Although
nothing appears to happen, if you check the fact list again you’ll 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 "it’s a duck" crlf))
Next time you run the rules, you'll get a message on screen as well as the asserted
quack fact.
It’s 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 there’s a handy editor to help you create them. You
can’t put facts in a .CLP file in the same way as you can from the command prompt,
so for now you’ll
still enter them as
before.
Here’s a more
complex example
of rules and facts.
The decision tree
opposite
represents a small
section of the
diagnosis of a
car’s 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’.
CLIPS Tutorial 2 - Patterns and actions
Persistent facts
For the following exercises, you will need to use the same set of facts several times.
Rather than type them in repeatedly, you should use the deffacts structure. This is
a way of specifying facts which are recreated every time a (reset) is executed. For
example, the code
(deffacts startup (animal dog) (animal duck) (animal haddock))
will assert three facts onto the database every time the system is reset. Once they
are asserted, the facts are the same as any others - they can be retracted or used in
rule patterns - but even if they are retracted they will reappear after a (reset).
Below is the list of facts you will need to use - use the CLIPS editor to enter them in
a deffacts structure then reset CLIPS and look at the fact list to check that they are
present.
(animal dog) (animal cat) (animal duck) (animal turtle) (warm-blooded
dog) (warm-blooded cat) (warm-blooded duck) (lays-eggs duck) (lays-eggs
turtle) (child-of dog puppy) (child-of cat kitten) (child-of turtle
hatchling)
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>
Facts and rules
In order to retract a fact, you need to know its fact-index. You can retract facts from
within rules by binding them to variables, like this:
(defrule remove-mammals
?fact <- (mammal ?)
=>
(printout t "retracting " ?fact crlf)
(retract ?fact))
In the pattern part of this rule, the variable ?fact is given the fact-index of each fact
matching the pattern (mammal ?) in turn. That's what the leftwards arrow (<-)
symbol means. When you run it, this is what happens (the fact numbers may be
different):
CLIPS>(run)
retracting <Fact-13>
retracting <Fact-14>
retracting <Fact-15>
retracting <Fact-16>
CLIPS>
All the mammal facts have been retracted.
Logic and Math Operators
You've already seen that two or more patterns in a rule are automatically connected
with a logical AND, which means that both must be true for the rule to fire. You've
also seen the not function, which leaves OR as the only boolean function missing for
pattern specification. CLIPS has an or function, which is used as shown:
(defrule take-umbrella
(or (weather raining)
(weather snowing))
=>
(assert (umbrella required)))
Which means "if it is raining or it is snowing, then take an umbrella". Notice the way
the or comes before the two arguments, rather than between them. this is known as
prefix notation, and all CLIPS operators work this way. For example, to express a
sum of two numbers in most computer languages, you would use something like 5 +
7 (this is known as infix notation). In CLIPS, the expression would be written (+ 5
7). Examine the following examples which show the addition, subtraction,
multiplication and division operators:
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).
Getting data from the user
CLIPS gets information from the user by means of the (read) function. Wherever
(read) is encountered, the program waits for the user to type something in, then
substitutes that response. To demonstrate this, type (assert (user-input
(read))) at the CLIPS prompt. After you press return, you'll need to type something
else (anything) before the command will complete. When you look at the facts, you'll
see a new fact with your input as the second item. Try the rule
(defrule what-is-child
(animal ?name)
(not (child-of ?name ?))
=>
(printout t "What do you call the child of a " ?name "?")
(assert (child-of ?name (read))))
When you run it, the system will now prompt you for the name of the young of any
animal it doesn't know about. It will use the data you enter to assert a fact, which
could then be used by other rules. In the car diagnostic example of the previous
tutorial, you could have used a rule such as
(defrule are-lights-working
(not (lights-working ?))
=>
(printout t "Are the car's lights working (yes or no)?")
(assert (lights-working (read))))
The correct CLIPS representation for the expression 10+4*19-35/12 is (+ 10 (- (* 4
19) (/ 35 12))).
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-of beatles john_lennon paul_mccartney george_harrison
ringo_starr)
(member-of who roger_daltrey pete_townsend keith_moon)
(member-of ebtg tracey_thorn ben_watt)
If we wish to write a rule which will be triggered by all three of these facts, we can't
easily use the ? wildcard, because it will match only one symbol. Instead, we will use
the multi-field wildcard, $?, which will match zero or more symbols, thus:
(defrule bands
(member-of ?band $?)
=>
(printout t "there is a band called " ?band crlf))
Produces the results:
CLIPS>(run)
there is a band called ebtg
there is a band called beatles
there is a band called who
Taking this one step further, we can get a list of all members of all bands:
(defrule band-members
(member-of ?band $? ?member $?)
=>
(printout t ?member " is a member of " ?band crlf))
In the left hand side of this rule, the multi-field wildcard, $?, will match zero or more
symbols. The wildcard ?member will only match one symbol at a time. Thus, the
?member wildcard will match once for each individual member of a band, while the
$? wildcards match to the other preceding and following members. For example, the
following table shows all the different ways this rule would match the facts for the
Beatles:
Match# first $? matches
1
2
3
4
?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
You can also use multi-field wildcard variables:
(defrule band-members
(member-of ?band $?members)
=>
(printout t "The members of " ?band " are " $?members crlf))
More about variables
So far, the only way we've seen of incorporating a variable in a rule is to create it as
part of a pattern on the left hand side. Using the bind function, it's also possible to
create a temporary variable in the right hand side of a rule:
(defrule addup
(number ?x)
(number ?y)
=>
(bind ?total (+ ?x ?y))
(printout t ?x " + " ?y " = " ?total crlf)
(assert (total ?total)))
You should be aware that the temporary variable only exists inside the rule - don't
expect to be able to use the value elsewhere. If you need variables which can be
used by more than one rule or function without losing their values, you need to
declare them using the defglobal construct in a file, like in this example:
(defglobal
?*var1* = 17
?*oranges* = "seven"
)
After a (reset), there will be two global variables, ?*var1* and ?*oranges* (the
asterisks are necessary) with the values 17 and "seven" respectively, which may be
accessed by any rule. To change their values, the bind function can be used.
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.
CLIPS Tutorial 4 - Templates and conditions
Although you can accomplish a lot with simple facts such as the ones we have
already used, in many cases it is desirable to link bits of information together.
Consider, for example, an application which is to try to determine the general fitness
of a number of people. Several indicators will be used, so each person will have a
different value for each one. We could express the information for two people as
follows:
(age Andrew 20)
(weight Andrew 80)
(height Andrew 188)
(blood-pressure Andrew 130 80)
(age brenda 23)
(weight brenda 50)
(height brenda 140)
(blood-pressure brenda 120 60)
But this involves lots of separate facts, with nothing to link them together other than
a single field bearing the person's name. A better way to do this is by using the
deftemplate structure, thus:
(deftemplate personal-data
(slot name)
(slot age)
(slot weight)
(slot height)
(multislot blood-pressure)
)
deftemplate does not create any facts, but rather the form which facts can take.
Every time a fact of this type is created, it contains the slots specified in its
definition, each of which can contain a value and can be accessed by name. Each
person's data can now be asserted thus:
(assert (personal-data (name Andrew) (age 20) (weight 80)
(height 188) (blood-pressure 130 80)))
or, in a deffacts structure thus:
(deffacts people
(personal-data (name Andrew) (age 20) (weight 80)
(height 188) (blood-pressure 130 80))
(personal-data (name Cyril) (age 63) (weight 70)
(height 1678) (blood-pressure 180 90)))
Although you don't have to specify all the information, so
(assert (personal-data (weight 150) (age 23) (name Brenda)))
is perfectly valid. The order in which you access the slots does not matter, as you are
referring to them by name (this also allows you to set as few of them as you wish).
Template facts can be altered without the need to retract them and assert a new
version. The function modify allows you to change the value of one or more slots on
a fact. Suppose it's Andrew's birthday. If we define the rule
(defrule birthday
?birthday <- (birthday ?name)
?data-fact <- (personal-data (name ?name) (age ?age))
=>
(modify ?data-fact (age (+ ?age 1)))
(retract ?birthday)
)
then asserting the fact (birthday Andrew) will modify Andrew's age while leaving all
his other personal data intact. Incidentally, the reason we retract the birthday fact is
for the purposes of truth maintenance. It's only Andrew's birthday once a year, and
if we left the fact lying around it would soon become false. Further, every time any
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
Elements introduced in this tutorial: template facts, conditional elements
(deftemplate (slot slotname1) (slot slotname1))
(and (predicate1) (predicate2))
(or (predicate1) (predicate2))
(not (predicate1))
(test (predicate1))
(exists (pattern))
(forall (pattern1)
(pattern2)
CLIPS Tutorial 5 - Truth and control
This tutorial uses the following data. If you're reading this on screen, you can cut it
and paste it into the CLIPS editor. If you're reading it on paper, you can still do this
but it will make a mess of your monitor.
(deftemplate personal-data
(slot name)
(slot age)
(slot weight)
(slot smoker)
(multislot date_of_birth)
)
(deffacts people
(personal-data (name adam) (weight 60) (age 30)
(smoker no) (date-of-birth 18 06 1970))
(personal-data (name brenda) (weight 120) (age 45)
(smoker yes) (date-of-birth 18 06 1955))
(personal-data (name charles) (weight 120) (age 60)
(smoker yes)(date-of-birth 18 06 1940))
)
(deffacts data
(date 18 06 2000)
)
In the last tutorial, we learned about several conditional elements: and, or, not,
test, forall and exists. There is still one more: logical. The logical conditional
element assists with the perennial problem of truth maintenance. Enter and run the
following rule, using the facts defined above.
(defrule cardiac-risk
(person (name ?name) (smoker yes) (weight ?weight))
(test (> ?weight 100))
=>
(assert (cardiac-risk ?name))
)
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.
CLIPS Tutorial 6 - All the other stuff
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)
(not (next-card ?))
(top-card ?topcard)
=>
(assert (next-card ?topcard))
)
(defrule printdeck
(state print-deck)
?nextcard <- (next-card ?number)
(draw-pile ?name ?suit ?number)
=>
(printout t ?number ": " ?name " of " ?suit " has value " (cardvalue ?name) crlf)
(retract ?nextcard)
(assert (next-card (+ ?number 1)))
)
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.
Download