clips tutorial

23
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:

Upload: parisa-khosravi

Post on 26-Dec-2015

34 views

Category:

Documents


2 download

DESCRIPTION

CLIPS Tutorial

TRANSCRIPT

Page 1: Clips Tutorial

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:

Page 2: Clips Tutorial

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-a-duck 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:

Page 3: Clips Tutorial

(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’.

Page 4: Clips Tutorial

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 foundAnimal foundAnimal foundAnimal foundCLIPS>

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.

Page 5: Clips Tutorial

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 foundduck foundcat founddog foundCLIPS>

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 mammaldog is a mammalCLIPS>

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

Page 6: Clips Tutorial

CLIPS>(run)kitten is a mammalpuppy is a mammalCLIPS>

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)

Page 7: Clips Tutorial

12CLIPS>(- 5 7)-2CLIPS>(* 5 7)35CLIPS>(/ 5 7)0.7142857142857143CLIPS>

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

Page 8: Clips Tutorial

(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 ebtgthere is a band called beatlesthere 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 ?member matches

last $? matches

1 nothing john_lennon paul_mccartney george_harrison ringo_starr

2 john_lennon paul_mccartney george_harrison ringo_starr

3 john_lennon paul_mccartney george_harrison ringo_starr

4 john_lennon paul_mccartney george_harrison

ringo_starr nothing

 

You can also use multi-field wildcard variables:

(defrule band-members (member-of ?band $?members)=> (printout t "The members of " ?band " are " $?members crlf))

Page 9: Clips Tutorial

 

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)

Page 10: Clips Tutorial

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!

Page 11: Clips Tutorial

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

Page 12: Clips Tutorial

(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 check-each-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))

Page 13: Clips Tutorial

(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 sets

(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

Page 14: Clips Tutorial

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

Page 15: Clips Tutorial

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 (check-birthdays) 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,

Page 16: Clips Tutorial

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:

Page 17: Clips Tutorial

(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))

Page 18: Clips Tutorial

(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))

)

Page 19: Clips Tutorial

(defrule printdeck(state print-deck)?nextcard <- (next-card ?number)(draw-pile ?name ?suit ?number)

=>(printout t ?number ": " ?name " of " ?suit " has value " (card-

value ?name) crlf)(retract ?nextcard)(assert (next-card (+ ?number 1)))

)

This rule uses a function (card-value) which is not part of CLIPS, but is user-defined. 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.