home *** CD-ROM | disk | FTP | other *** search
- Chapter 8 Powerful Patterns
-
-
-
-
- In this chapter, you will learn more of the powerful pattern matching
- capabilities of CLIPS. By effectively using this capability you can greatly
- reduce the number of rules required by programs and also make them more easily
- understandable.
-
- Stronger Than Logic
-
-
- In the previous chapter, you learned about one type of field constraint, the
- logical constraint which uses "&", "|", or "~". The second type
- of field constraint is called a predicate function, and is commonly used for
- more complex pattern matching of a field. You'll find the predicate
- function very useful with numeric patterns.
- A predicate function uses a special form of the "&" constraint called the
- AND function and symbolized by "&:". The general form is
-
- ?variable&:(<fun> <<arg>>)
-
- where
- ?variable is any legal variable name
- <fun> is a predefined or user defined function
- <<arg>> are zero or more arguments required by the function
-
- Any predefined or user defined function can be used as a function. The
- predefined functions are those shown in the section on the test function
- in the section "Control Your Loop" of Chapter 6, and also in Appendix A of the
- Reference Manual. The math functions can also be used if you
- have a version of CLIPS which has been linked to the math library.
- The "&:" function operates by initially binding the value in the field to
- ?variable and then performing the test (<fun> <<arg>>). If the
- test is succesful, the conditional element is true.
- As a very simple example, consider the following.
-
- (defrule compare
- (?x&:(> 2 1))
- =>
- (printout "answer " ?x crlf))
-
- Enter and assert a 1. When you run, the same fact will be printed. In
- fact, the rule will always print out whatever facts are asserted because
- the predicate function test is always true. That is, since 2 > 1 is always
- true, any fact meets the criteria of the predicate function and
- will be bound to ?x.
- A more useful form of the predicate function will use the variable as one of
- the arguments. For example, suppose you wanted to test which
- numbers are greater than 1. The modified rule would now be
-
- (defrule compare
- (?x&:(> ?x 1))
- =>
- (printout "answer " ?x crlf))
-
- Before you enter this rule, do not have an (initial-facts) or other
- non-number in the fact-list. The reason is that CLIPS will try to match
- the conditional element on (initial-fact). So give an error message because
- (initial-fact) is not a number and the test (> ?x 1) is invalid.
- If there is a non-numeric fact such as (initial-fact), you'll see an error
- message
-
- Non-float argument #1 found in function > in rfloat call
-
- With no non-numeric fact present, enter the rule and assert the numbers 0, 1,
- 2, 3, and 4. When you run, you'll see that the numbers greater
- than 1 are indeed printed out.
- In order to prevent error messages like the above, you can either try never
- to have a non-numeric fact or tighten up the constraints on the
- facts to be matched. For example, the rule could be changed to
-
- (defrule compare
- (number ?x&:(> ?x 1))
- =>
- (printout "answer " ?x crlf))
-
- and then you would assert facts like (number 0), (number 1), and so forth. Now
- (initial-fact) cannot match the conditional element and the error
- message does not occur.
- Notice that the "&:" constraint now applies to the second field in the
- conditional element. Using constraints, wildcards, and tests gives
- you a very powerful pattern matching capability.
- The presence of the extra "number" pattern in the first field means that
- CLIPS must do a little more matching to trigger a rule. However,
- the extra matching time is insignificant. The important thing is that extra
- fields aid in debugging a program because they explicitly show
- what the facts are supposed to be.
- You may be wondering why the second version of the program involving ?x
- produced the error message and the first with (> 2 1) did not. The
- reason is that CLIPS did not have to test ?x in (> 2 1) and so did not find an
- error in comparing initial-fact to 1.
- Now suppose you want those numbers which are greater than 1 and less than 4.
- No sweat. Just add the following conditional element to check
- if ?x is < 4.
-
- (defrule compare
- (number ?x&:(> ?x 1))
- (number ?x&:(< ?x 4)) ; add this conditional element
- =>
- (printout "answer " ?x crlf))
-
- After you change the rule, check the agenda. Notice that the facts for the
- numbers 2 and 3 are listed twice since they match the two conditional
- elements of the rule. If nothing is on the agenda, you probably didn't assert
- the facts with a "number" in the first field. When you run,
- the rule will print out the correct numbers 2 and 3.
- Is there any way to write the rule using only one conditional element? There
- is, as shown by the following rule.
-
- (defrule compare
- (number ?x&:(&& (> ?x 1)) (< ?x 4))
- =>
- (printout "answer " ?x crlf))
-
- The one conditional element says that ?x must be greater than 1 and less than
- 4. Notice that the logical AND, "&&", must be used as the connector
- for the two comparisons (> ?x 1) and (< ?x 4).
-
-
- The Truth Of The Matter
-
-
- A function that returns a value of 0 is considered false, while non-zero is
- true. As an simple example, consider the following.
-
- (defrule and-1
- (number ?x&:1)
- =>
- (printout "true" crlf))
-
- (defrule and-0
- (number ?x&:0)
- =>
- (printout "false" crlf))
-
- Notice that there is a number after the "&:" rather than a function and
- arguments in parentheses. The number alone indicates the value that
- would have been returned by a function. An alternative is a function that is
- always true like (= 1 1) or one that is always false like (= 1
- 0).
- Enter these rules and then do a (reset). As expected, the and-1 is on the
- agenda while the and-0 is not. If you replace the 1 and 0 after
- the "&:" with the always true and always false function, you'll see the same
- results.
- How would you invert the logic of the function result to make a false become
- true? The answer is shown in the following modification of the
- and-0 rule. The logical not, "!", is used to invert the 0. Enter this rule
- and (reset). You'll see that it is on the agenda.
-
- (defrule and-0-true
- (number ?x&:(! 0))
- =>
- (printout "false" crlf))
-
- The same considerations of true and false apply to test conditions. For
- example, enter the following, (reset), and check the agenda.
-
- (defrule test-1
- (number ?x)
- (test 1)
- =>
- (printout "true" crlf))
-
- (defrule test-0
- (number ?x)
- (test 0)
- =>
- (printout "false" crlf))
-
- Normally you'd never intentionally use constant functions like this since
- they are not of practical use. However, you might accidentally
- write one involving variables such as
-
- (defrule error
- (number ?x&:(> ?x ?x))
- =>
- (printout "false" crlf))
-
- The programmer probably meant to write a conditional element like (number
- ?x&:(> ?x ?y)) but accidentally typed a ?x. Since a number cannot
- be greater than itself, the function is always false and the rule is never
- triggered.
- If you write your own functions, they must return 0 for false and a nonzero
- value for true. Any nonzero number will do.
-
-
- You Just Can't Get Enough
-
-
- Besides the predefined functions, there are some other predicate functions that
- are very useful, especially with "&:". These predicate functions
- are used to test strings and numbers. As the old saying goes, you just can't
- get enough of a good thing. Notice that there is a "p" after
- each function. The reason for the "p" is that these functions were originally
- defined in LISP and that's how LISP defines them.
-
- Function Purpose
-
- (numberp <arg>) Check if <arg> is a number
- (stringp <arg>) Check if <arg> is a string
- (evenp <arg>) Check if <arg> is even
- (oddp <arg>) Check if <arg> is odd
-
- As an example, consider the following rules to print out the odd and even
- numbers in the fact-list. Note that there may also be non-numbers,
- but only the numbers will be printed out.
-
- (defrule odd-number
- (?x&:(&& (numberp ?x) (oddp ?x)))
- =>
- (printout ?x " is an odd number" crlf))
-
- (defrule even-number
- (?x&:(&& (numberp ?x) (evenp ?x)))
- =>
- (printout ?x " is an even number" crlf))
-
- The "number" field is not necessary now because the "numberp" checks to see if
- the fact is a number.
- Enter and assert the facts duck, cat, -2, -1, 0, 1, and 2. Check the agenda
- and you'll see that the rules do match the correct numbers.
-
-
- Pushing CLIPS
-
-
- Let's take a look at another example. This one will really push CLIPS by
- straining its pattern matching and arithmetic capability.
- Suppose you want a program that will determine if three numbers are perfect
- squares. That is, what integer numbers x, y, and z will satisfy
- the equality
-
- z * z = x * x + y * y
-
- First of all, we'll have to agree on some limits to the problem. Since there
- are infinitely many numbers that satisfy the equality, we'll
- let the user pick a certain range of numbers for CLIPS to test.
- The program will be written as three rules. The input-limits rule will let
- the user specify the initial and final values of numbers. The
- make-numbers rule will then assert the integers from initial to final value.
- The perfect-squares rule will then find the perfect squares from
- the numbers asserted by the make-numbers rule. Shown following are the first
- two rules of the program.
-
- (defrule input-limits
- (initial-fact)
- =>
- (printout "Initial number ? " crlf)
- (bind ?initial (read))
- (printout "Final number ? " crlf)
- (assert (limits =(read) ?initial))) ;final and initial limits
-
- (defrule make-numbers
- (declare (salience 10))
- ?limits <- (limits ?final ?initial&:(<= ?initial ?final))
- =>
- (retract ?limits)
- (printout "number " ?initial " asserted" crlf)
- (assert (number ?initial))
- (bind ?initial (+ ?initial 1))
- (assert (limits ?final ?initial)))
-
- Enter these two rules and run for an initial limit of 1 and a final limit of
- 7. Notice how quickly CLIPS prints the asserted numbers. Now
- enter a (reset) command. Then enter the third rule to find numbers which are
- perfect squares.
-
- (defrule perfect-squares
- (number ?x)
- (number ?y)
- (number ?z&:(= (* ?z ?z) (+ (* ?x ?x) (* ?y ?y))))
- =>
- (printout "x = " ?x " y = " ?y " z = " ?z crlf))
-
- The salience conditional element in the make-numbers rule is to insure that
- all the asserted numbers are asserted before the perfect-squares
- rule is triggered. Now run the program for the same limits as before of 1 and
- 7.
- The third conditional element in perfect squares is the equality to be
- tested. It checks if the sum of the square of z is the sum of the
- squares of x and y. Enter and run the program as before for initial and
- final numbers of 1 and 7. Notice that it takes CLIPS longer and longer
- to assert the numbers now that the perfect-squares rule is present.
- The reason for the delays in asserting numbers is that CLIPS also checks
- which rules are triggered every time a new fact is asserted. Since
- there are more facts, there are more comparisons to check. Try to calculate
- how many checks of facts against conditional elements there are
- as each new fact is added.
- As soon as a fact is asserted, CLIPS starts checking for matches against
- rules. In other languages such as FORTAN, Pascal, Ada, and C, nothing
- happens until you issue a run command. But in CLIPS and LISP, the interpreter
- is always running. The situation is analogous to BASIC in which
- the interpreter is always running and will immediately execute a command
- without a line number. In CLIPS, the interpreter automatically matches
- rules against facts, even before your program starts executing.
- Now you can see why you were asked to enter and run the first two rules
- before the third rule. This way, you have gained a better understanding
- of how CLIPS operates than if you had just typed in all three rules at once.
- A more efficient method of writing the program is to eliminate the salience
- conditional element in the make-numbers rule. The salience was
- included to just illustrate the difference in execution speed before and after
- the perfect-squares rule was added. Try running the program
- without salience also.
- .PA
- Problems
-
-
- 1. Write a program that will first assert the numbers from 1 to 24 and then
- determine which are factorials.
-
-
- 2. Write a program to give directions to a robot on how to go from point 1 to
- point 2 by the shortest path. The directions will tell how to
- move one square at a time in the form
-
- move north
- move south
- move east
- move west
-
- Different traffic lights and buildings will be asserted from a (deffacts),
- whose facts are in the form
-
- (building x y)
-
- (light red x y)
-
- (light green x y)
-
- and so forth for the other light conditions. The x and y coordinates are shown
- followed by a diagram. Use the x and y coordinates for the buildings
- and lights shown. The robot starts at x = 0, y = 0. The goal is at x = 9, y
- = 9. Ideally, the robot will travel in a straight line to the
- goal.
- For each move, print out the direction to travel and update the diagram.
- The symbols on the diagram represent the following.
-
- B building
- O robot
- X goal
- r red light
- y yellow light
- g green
- G blinking green - The light is broke. Cannot go in this square
- Y blinking yellow
- R blinking red
-
- The robot must be in a square to know what is in the immediately surrounding
- squares. That is, the robot can only look at the immediately
- adjacent eight squares. It cannot go past the borders, into a building, or
- into a broken light.
- Hint: If you can't go in a straight line, then always go in one consistent
- direction, such as right or left.
-
-
-
- ------------
- 9| BB X|
- 8| GB r |
- N 7| BBBB |
- W E 6| R YBB |
- S Y 5| g BrBBB |
- 4| B y B |
- 3|BBB B B|
- 2| BBBg BBB|
- 1| Br B |
- 0|O |
- ------------
- 0123456789
-
- X
-
-
-