home *** CD-ROM | disk | FTP | other *** search
- Chapter 6 How To Be In Control
-
-
-
-
- Up to this point, you've been learning the basic syntax of CLIPS. Now you'll
- see how to apply the syntax you've learned to more powerful and
- complex programs. You'll also learn some new syntax for keyboard input, and
- see how to compare values and generate loops.
-
-
- Let's Start Reading
-
-
- Besides matching a conditional element, there is another way that a rule can
- get information. CLIPS can read information you type from the keyboard
- using the read function.
- The following example shows how (read) is used to calculate the square of a
- number. Note that no extra (crlf) is needed after the (read)
- to put the cursor on a new line. The (read) automatically resets the cursor
- to a new line.
-
- (defrule square-number
- (initial-fact)
- =>
- (printout "number? " crlf)
- (bind ?num (read))
- (bind ?square (* ?num ?num))
- (printout "square of " ?num " is " ?square crlf))
-
- Since the rule is designed to use keyboard input on the RHS, it's convenient
- to trigger the rule with (initial-fact). Otherwise, you'd have
- to make up some dummy fact to trigger the rule.
- The (read) function is not a general purpose function that will read
- anything you type on the keyboard. One limitation is that (read) will
- only read one atom. So if you try to read
-
- this is a duck
-
- only the first atom, "this" will be read. To (read) all of the input. you must
- enclose the input within double quotes. Of course, once the
- input is within double quotes, it is a single literal atom. You can't access
- the individual words "this", "is", "a", and "duck" with CLIPS
- functions since an atom can't be split up in CLIPS. To split up an atom, you
- must create and call an external function.
- The second limitation of (read) is that you can't input parentheses unless
- they are within double quotes. Just as you can't assert a fact
- with parentheses, you can't (read) parentheses.
- Even if (read) could read multiple atoms, there would be a limitation.
- Because (bind) can only bind one variable at a time, you can't read
- multiple variables in a single (bind). To read multiple values and bind them,
- you'll have to use multiple (bind) actions which each contain
- a (read). Also, since CLIPS does not have a concatenation operator, you
- cannot combine separate facts.
-
-
- Reading Your Assertions
-
-
- You can read an atom into an (assert) action. For example, suppose you wanted
- to modify the square program to give the user a choice of running
- the program again. This is a useful feature in many programs because
- otherwise the program ends after one calculation.
- The following version of the square program shows how to run the program
- over again by retracting and then asserting a dummy fact called start-fact
- again in the do-another rule. Although it is possible to use initial-fact
- instead of start-fact, it is poor programming because other rules
- may depend on initial-fact to trigger them. As long as your programs don't
- retract (initial-fact), it's safe to use it. Otherwise, use your
- own fact. For convenience, you may wish to keep a (deffacts) of (start-fact)
- in a file and load it in after your rules.
-
- (defrule square-number
- (start-fact)
- =>
- (printout "number? " crlf)
- (bind ?num (read))
- (bind ?square (* ?num ?num))
- (printout "square of " ?num " is " ?square crlf)
- (printout "Do another calculation? (y or n) " crlf)
- (assert (response =(read))))
-
- (defrule do-another
- (response y)
- ?initial <- (start-fact)
- =>
- (retract ?initial)
- (assert (start-fact)))
-
- Sample output of the program is as follows. Try some other numbers,
- especially very large and very small numbers to see how your computer
- handles their precision.
-
- number?
- 2
- square of 2 is 4
- Do another calculation? (y or n)
- y
- number?
- 4
- square of 4 is 16
- Do another calculation? (y or n)
-
- Notice how the two rules work together. This is a common method in expert
- systems to generate a loop where one rule triggers another. Since
- the flow of control in a rule-based system is not sequential, you must program
- the appropriate interactions between rules.
-
-
- Control Your Loop
-
-
- There are many cases in which it's useful to repeat a calculation or other
- information processing. The common way of doing this is by setting
- up a loop. In the previous example, you've seen how a loop is set up until
- the user responds no to a question. But there are also many situations
- in which you want a loop to terminate automatically as the result of some
- test. In the previous example, the test was asking the user whether
- they wanted to do another calculation. In a more general sense, the test of
- loop termination will be a comparison of values.
- The test function provides a very powerful way to compare numbers,
- variables, and strings on the LHS. The basic syntax of test is
-
- (test (<fun> <<arg>>))
-
- where <fun> is a predefined function of CLIPS and <<arg>> stands for one or
- more arguments required by the function. The (test) is used as a
- conditional element on the LHS. So a rule will only be triggered if the
- (test) is satisfied.
- As a simple example of a (test), consider the problem of writing a program
- to generate the squares of numbers up to a maximum number. The
- following program uses (test) in a rule to decide when to stop printing the
- squares.
-
- (defrule input-max
- (start-fact)
- =>
- (printout "number of loops? " crlf)
- (bind ?max (read))
- (assert (loop 0 ?max))) ;initialize ?count to 0
-
- (defrule print-squares
- (loop ?count ?max)
- (test (<= ?count ?max)) ;test if ?count is <= ?max
- =>
- (bind ?square (* ?count ?count))
- (printout "square of " ?count " is " ?square crlf)
- (assert (loop =(+ ?count 1) ?max))) ;?count = ?count + 1
-
- The fact (loop) contains information about how many numbers have been
- printed in ?count, and the maximum number, ?max, to be printed. Notice
- that the maximum number must be included in a fact and input to print-squares
- for every iteration of the loop. While the program works, it
- is wasteful of memory. How would you change it to reduce all the (loop) facts
- generated?
- The (test) checks if ?count is less than or equal to ?max using the less
- than or equal function, "<=". On the last iteration of the loop,
- ?count = ?max and so the rule will not be triggered again. The arguments of
- "<=" are ?count and ?max. Look again at the syntax of (test) described
- before, and you can now see how (test (<= ?count ?max)) matches (test (<fun>
- <<arg>>).
- In general, you can understand how the predefined function acts on what
- follows by thinking of it following the first argument. For example,
-
- (<= ?count ?max)
-
- in prefix form is like saying
-
- (?count <= ?max)
-
- in customary infix.
- There are many predefined function provided for you by CLIPS. The opposite
- of the predefined function is the external function or user-defined
- function. An external function is a function that you write in C and link to
- CLIPS. For more information, see the CLIPS Reference Manual.
- The following table shows the predefined functions.
-
- Symbol Predefined Function
-
- Logical Predefined Functions
-
- ! not(inverse) function
- && and function
- || or function
-
-
- Comparison Predefined Functions
-
- = equal(numeric) function
- eq equal(strings or numbers) function
- != not equal function
- >= greater than or equal to function
- > greater than function
- <= less than or equal to function
- < less than function
-
-
- Arithmetic Predefined Functions
-
- / division function
- * multiplication function
- ** exponentiation function
- + addition function
- - subtraction function
-
- All of the Comparison Functions except "eq" will give an error message if
- used to compare a number and non-numbers, called strings. The "eq"
- function should be used for checking items whose types are not known in
- advance.
- The (test) can be used for more complex arguments. However, you'll have to
- get used to the prefix notation to use (test) effectively.
- For example, suppose you want to see if two points have a positive slope.
- In the customary infix way, we can write this as
-
- (y2 - y1) / (x2 - x1) > 0
-
- In order to write this in prefix form, let's start off by thinking of the
- numerator as (Y) and the denominator as (X). So we can write the above
- as
-
- (Y) / (X) > 0
- The prefix form for division is then
-
- (/ (Y) (X))
-
- since in prefix the operator comes before the argument. Now we want to see if
- the division is greater than 0. So the prefix form is used with
- (test) as follows.
-
- (test (> (/ (Y) (X)) 0))
-
- In infix form, Y = y2 - y1. But we want the prefix form since we're making
- a prefix expression. So (- y2 y1) will be used for (Y) and (-
- x2 x1) for (X). Now just replace (Y) and (X) by their prefix forms to get the
- final expression of whether the two points have a positive slope.
-
- (test (> (/ (- y2 y1) (- x2 x1)) 0))
-
- As you become more proficient in prefix form, you'll be able to write the
- prefix form automatically. At first, it's probably best if you
- write down the steps in converting from infix to prefix until you get the hang
- of it.
-
-
- Let's Be Logical
-
-
- The logical functions of (test) are very useful in expressing multiple
- relationships. For example, suppose you wanted to check for valid entry
- of numbers, such as dates. If the month is entered as a number, it must be
- greater than or equal to 1 and less than or equal to 12. The "and"
- function "&&" can be used to express this relationship in the test
-
- (test (&& (>= ?month 1) (<= ?month 12)))
-
- where ?month would contain the month number. The "&&" is formed by pressing
- the "&" key twice.
- Likewise an invalid month number would be one that is less than 1 or greater
- than 12. This can be expressed using the logical "or" function,
- "||", formed by pressing the "|" key twice.
-
- (test (|| (< ?month 1) (> ?month 12)
-
- A program to tell whether a month is valid or invalid is shown following.
- Enter and run it for some different month numbers.
-
- (defrule ask-month-number
- (start-fact)
- =>
- (printout "Number of month? " crlf)
- (bind ?number (read))
- (assert (month ?number)))
-
- (defrule valid-month
- ?start-fact <- (start-fact)
- ?month <- (month ?number)
- (test (&& (>= ?number 1) (<= ?number 12)))
- =>
- (printout "Valid month" crlf)
- (retract ?start-fact ?month)
- (assert (start-fact)))
-
- (defrule invalid-month
- ?start-fact <- (start-fact)
- ?month <- (month ?number)
- (test (|| (< ?number 1) (> ?number 12)))
- =>
- (printout "Invalid month" crlf)
- (retract ?start-fact ?month)
- (assert (start-fact)))
-
- Note that after facts are no longer needed, they are retracted. This
- prevents a lot of facts from building up in memory and slowing down
- execution because CLIPS tries to match them to rules.
-
-
- Name That Month
-
-
- While it's nice to know if a month is valid, it would be even nicer if the
- program would name the month. Following is a modification of the
- program which does this. Enter and run for some month numbers and you'll see
- the month names printed out.
-
- (defrule ask-month-number
- (start-fact)
- =>
- (printout "Number of month? " crlf)
- (bind ?number (read))
- (assert (month ?number)))
-
- (defrule valid-month
- ?month <- (month ?number)
- (test (&& (>= ?number 1) (<= ?number 12)))
- =>
- (printout "Valid month" crlf)
- (retract ?month)
- (assert (valid-month ?number)))
-
- (defrule invalid-month
- ?start-fact <- (start-fact)
- ?month <- (month ?number)
- (test (|| (< ?number 1) (> ?number 12)))
- =>
- (printout "Invalid month" crlf)
- (retract ?start-fact ?month)
- (assert (start-fact)))
-
- (defrule print-month
- ?valid <- (valid-month ?number)
- (name ?month ?number)
- ?start-fact <- (start-fact)
- =>
- (printout "Month is " ?month crlf)
- (retract ?start-fact ?valid)
- (assert (start-fact)))
-
- (deffacts month-names
- (name January 1)
- (name February 2)
- (name March 3)
- (name April 4)
- (name May 5)
- (name June 6)
- (name July 7)
- (name August 8)
- (name September 9)
- (name October 10)
- (name November 11)
- (name December 12))
-
- The data for the month names and numbers are stored in the facts of the
- (deffacts) statement. The rule print-month is triggered when there
- is a fact (valid-month ?number) made by the rule valid-month. Notice how the
- rule valid-month was modified to assert a fact (valid-month ?number)
- which is a conditional element of the rule print-month. The second
- conditional element is the fact which matches the month name and number.
- The third conditional element is just to match the fact start-fact so that
- the program does not end.
- Let's talk a little now about efficiency. A minor point, but one worth
- mentioning, is the choice of names in facts. Consider the difference
- between (valid-month ?number) and an alternative (valid month ?number). The
- (valid-month ?number) pattern tries to match two atoms, "valid-month"
- and "?number". In contrast, the pattern (valid month ?number) must try to
- match three atoms to the patterns, "valid", "month", and "?number".
- So if the pattern (valid month ?number) had been used, CLIPS would have to
- do more work in matching, which decreases its efficiency. Although
- this choice of names is not a big problem in this program, it can become
- aggravated if you use a lot of patterns unnecessarily.
- Another factor that affects efficiency is the order in which conditional
- elements are listed affects the efficiency of a program. If the
- order was reversed like this
-
- (name ?month ?number)
- ?valid <- (valid month ?number)
- ?start-fact <- (start-fact)
-
- then CLIPS would waste time in checking to see if the rule should be put on the
- agenda.
- The reason for this inefficiency is that CLIPS would always find 12 matches
- to the first conditional element. Then CLIPS would go on to the
- second conditional element and find one match of the valid-month number.
- Finally, it would find 1 match for the (start-fact). So a total of
- 12 x 1 x 1 = 12 matches would have to be made before the rule is triggered.
- Consider now the order of conditional elements shown in the program.
- The first conditional element is (valid month ?number) and so one match is
- made. Now when CLIPS checks the second conditional element, (name
- ?month ?number), only 1 match is possible because CLIPS knows what ?number is.
- Contrast this with the case described before in which CLIPS
- found 12 matches to (name ?month ?number) because it didn't know what ?number
- was and so all 12 months matched. Finally, since there's 1 match
- to the third conditional element, (start-fact), there is a total of 1 x 1 x 1
- = 1 matches required compared to the 12 matches of the other version.
- For more information on efficiency, see Appendix C of the CLIPS Reference
- Manual.
- .PA
-
- Problems
-
-
- 1. Write a program that will generate a table of squares and cubes of numbers.
- The user should be asked the math function such as square or
- cube, the minimum and maximum numbers, and the step size for incrementing
- numbers. The program should then print out the table. A sample format
- for an input of math function square, min = 1, max = 5, and step size = 1 is
- shown below.
-
- Number Square
- 1 1
- 2 4
- 3 9
- 4 16
- 5 25
-
-
- 2. Write a program which will test if three points are colinear. That is,
- whether all three points fall on a straight line. The user should
- be asked to specify the x and y coordinates of the three points in the form
-
- Point 1 x1 ?
- Point 2 y1 ?
-
- and so forth.
-
- After telling the user whether the points are colinear, the user should be
- asked if they want to do another.
-
-
- 3. Write a program to compute compound interest on a loan using the infix
- formula
-
- Interest=Principal*(1+Interest-rate/Periods/100)**(Periods*Years)
-
- where
- Principal : amount invested
- Periods : number of times a year
- that the money is compounded
- Interest-rate : yearly percent rate
- Run the program for
- Principal = 20
- Periods = 12
- Years = 366
- Interest rate = 8
-
- to see how much money the Dutch would have made if they had invested their
- money rather than buying Manhatten Island for $20 in 1620.
-
-
- 4. Write a program that tells the day of the week, i.e., Sunday, Monday, and
- so forth when a user inputs the month, day, and year. The program
- should also check for invalid entry. For example, months should be in the
- range 1 - 12. The day of month will depend on the month. So a day
- of 31 will be valid if the month is 1 but not if the month is 2. The program
- should also account for leap years.
-
-
-