home *** CD-ROM | disk | FTP | other *** search
/ Crawly Crypt Collection 2 / crawlyvol2.bin / program / compiler / clips / clip_doc / clips10.man < prev    next >
Encoding:
Text File  |  1993-09-01  |  22.0 KB  |  585 lines

  1.          Chapter 10   Adding Structure
  2.  
  3.  
  4.  
  5.  
  6. In this chapter you'll see alternate ways to write loops and other program 
  7. structures than the standard method of multiple rules.  While these 
  8.  method do work, there is also an inherent danger because your program design 
  9. can become very inefficient.
  10.  
  11.  
  12. If This Goes On
  13.  
  14.  
  15. CLIPS provides some programming structures that can be used on the RHS.  These 
  16. structures are the while and if then else that are also found 
  17.  in modern high-level languages such as Ada and Pascal.
  18.    The basic (if then else) has the form
  19.  
  20. (if (<fun> <<arg>>)
  21.  then
  22.    (<<actions>>)
  23.  else
  24.    (<<actions>>)
  25.  
  26. where <fun> stands for a predefined or user-defined function, and <<arg>> and 
  27. <<actions>> stand for one or more arguments and actions.
  28.    If the condition (<fun> <<arg>>) is true, then the actions in the (then) are 
  29. executed. Else, if the condition is false, the actions in the 
  30.  (else) are executed.  Actually, the (else) is optional and so you can also 
  31. just write an (if then) rule with no (else) part.  Once the (if then 
  32.  else) completes execution, CLIPS continues with the next action, if any, on 
  33. the RHS.
  34.    As a simple example of (if then else), enter the following rule.  Then 
  35. assert (light green), then (light red) and run.
  36.  
  37. (defrule traffic-light
  38.    (light ?color)
  39. =>
  40.    (if (eq ?color green)
  41.     then
  42.        (printout "Go" crlf)
  43.     else
  44.        (printout "light not green" crlf)))
  45.  
  46.    If the light is green, the (then) part will be executed and "Go" will be 
  47. printed out.  If the light is not green, the message "light not green" 
  48.  will be printed out.
  49.    The (if then else) can be nested.  That is, one (if then else) can be 
  50. included within another.  The following example shows how one rule can 
  51.  handle all three cases of the traffic light.  The indentation of the (if) 
  52. parts is to aid readability.  Enter and run this for the facts (light 
  53.  green), (light red), and (light yellow).
  54.  
  55. (defrule traffic-light
  56.    (light ?color)
  57. =>
  58.    (if (eq ?color green)
  59.     then 
  60.        (printout "Go" crlf)
  61.     else
  62.        (if (eq ?color yellow)
  63.         then
  64.            (printout "Slow down" crlf)
  65.         else
  66.            (if (eq ?color red)
  67.             then
  68.                (printout "Stop" crlf)
  69.            )
  70.        )
  71.    )
  72. )
  73.  
  74.    Notice that the closing right parentheses are lined up under their 
  75. corresponding left parentheses.  When you have a lot of nesting, it's very 
  76.  helpful to someone reading the program to see the parentheses enclosing the 
  77. different parts of the rule.  It's also easier on the programmer 
  78.  writing the code to tell when all the parentheses are balanced.
  79.    Although the above rule does work, it is shown just as an example of (if 
  80. then else) syntax.  The program should really be written as three 
  81.  rules to make efficient use of CLIPS.  The danger of using an (if then else) 
  82. is that you tend to think in a procedural fashion and so write 
  83.  your programs that way.  However, CLIPS is not a procedural language.  To make 
  84. the best use of CLIPS, you should think in terms of rules and 
  85.  not try to structure a CLIPS program in a procedural way.
  86.  
  87.  
  88. Meanwhile
  89.  
  90.  
  91. The second type of structure provided by CLIPS is the (while).  Although you 
  92. can use (test) and multiple rules to produce a loop, the (while) 
  93.  structure can be used to produce a loop within a single rule on the RHS.  The 
  94. general form is
  95.  
  96. (while (<fun> <<arg>>)
  97.          (<<action>>))
  98.  
  99. where <<action>> stands for one or more actions and <<arg>> are the zero or 
  100. more arguments required by the function, <fun>.  The actions of the 
  101.  (while) comprise the body of the loop.
  102.    The part of the (while) represented by (<fun> <<arg>>) represents a 
  103. condition that is tested before the actions in the body are executed. 
  104.   If the while-condition is true, then the actions in the body will be 
  105. executed.  If the while-condition is false, execution continues with the 
  106.  next statement after the (while), if any.
  107.    As an example of a while-loop, let's look at the program to generate squares 
  108. of numbers written in a while-loop form.  Compare the following 
  109.  program with that written in the first section of Chapter 6.
  110.  
  111. (defrule input-max
  112.    (start-fact)
  113. =>
  114.    (printout "number of loops? " crlf)
  115.    (bind ?max (read))
  116.    (bind ?count 0)   ;initialize ?count to 0
  117.    (while (<= ?count ?max)
  118.            (bind ?square (* ?count ?count))
  119.            (printout "square of " ?count " is " ?square crlf)
  120.            (bind ?count (+ ?count 1))))
  121.  
  122.    The actions in the while-loop body will continue printing out the squares of 
  123. numbers until ?count is greater than ?max because then the while-condition, 
  124.  (<= ?count ?max), will be false.  Enter and run this new version and you'll 
  125. see it produce the squares of numbers just as the first version 
  126.  did.
  127.    Although the (while) version works, it is more inefficient than the example 
  128. in Chapter 6 using multiple rules.  CLIPS is really designed for 
  129.  efficiency with multiple rules.  Trying to make a procedural program out of 
  130. CLIPS negates the advantages of a rule-based language.  Carried 
  131.  to an extreme, you could write a one-rule program in CLIPS using (while) and 
  132. (if then else).  However, this completely defeats the purpose of 
  133.  the language for efficiency with multiple rules.
  134.    As another example of the (while) syntax, consider a program to print the 
  135. mean or average of numbers.  The following program shows how it 
  136.  can be done using a single rule.  Enter and run the program to compute the 
  137. average of the five numbers, 1, 2, 3, 4, and 5.
  138.  
  139. (defrule input-max
  140.    (start-fact)
  141. =>
  142.    (printout "How many numbers? "crlf)
  143.    (bind ?max (read))
  144.    (bind ?count 1)
  145.    (bind ?sum 0)
  146.    (while (<= ?count ?max)
  147.           (printout "Number " ?count " = ? " crlf)
  148.           (bind ?number (read))
  149.           (bind ?sum (+ ?sum ?number))
  150.           (bind ?count (+ ?count 1)))
  151.    (bind ?answer (/ ?sum ?max))
  152.    (printout crlf crlf "Mean = " ?answer crlf))
  153.  
  154. Notice that only one rule was necessary and no facts were asserted.
  155.  
  156.  
  157. Not Meanwhile
  158.  
  159.  
  160. Although the previous rule for the mean works, it would be convenient if you 
  161. didn't have to count in advance how many numbers there are.  That 
  162.  is, it would be nice if the program kept count of the numbers that were read 
  163. until a sentinel was found.  A sentinel is a special symbol that 
  164.  indicates the end of input.
  165.    The following version of the program uses the sentinel "done" to signify end 
  166. of input.  Enter and run for numbers 1, 2, 3, 4, and 5.  Then 
  167.  type "done" when you're asked for the sixth number (do not enter the quotation 
  168. marks).  The computer will then print the mean. 
  169.  
  170. (defrule not-mean-while
  171.    (start-fact)
  172. =>
  173.    (bind ?sum 0)
  174.    (bind ?count 1)
  175.    (printout "Number 1 = ? " crlf)
  176.    (bind ?number (read))
  177.    (while (! (eq ?number done))
  178.       (bind ?count (+ ?count 1))
  179.       (bind ?sum (+ ?sum ?number))
  180.       (printout "Number " ?count " = ? " crlf)
  181.       (bind ?number (read)))
  182.    (bind ?mean (/ ?sum (- ?count 1)))
  183.    (printout crlf crlf "Mean = " ?mean crlf))
  184.  
  185.    The while-loop reads input and assigns it to ?number until a "done" is 
  186. entered.  Notice the "not" function, "!", in the while-loop test (! 
  187.  (eq ?number done)).  This test says to execute the actions in the body of the 
  188. while-loop while ?number is not equal to "done".
  189.    The "eq" function was used instead of the "not equal" operator "!=" because 
  190. "!=" can only be used with numbers, while "eq" can be used with 
  191.  both numbers and strings.  In fact, you must use "eq" for strings since CLIPS 
  192. has no other function to check the equality of strings.
  193.    An alternative approach to a string sentinel would be to use a special 
  194. number such as 1e38 as sentinel so that you can use "!=".  This sentinel 
  195.  is picked because it's a number that's unlikely to occur in data.  Of course, 
  196. if a number like 1e38 is likely to occur, you should pick another.
  197.    As another simple example of (while), the following rule is very useful if 
  198. you have a lot of (assert) statements to make and don't want to 
  199.  use a (deffacts).
  200.  
  201. (defrule auto-assertions
  202.    (initial-fact)
  203. =>
  204.    (printout "Assertion ? " crlf)
  205.    (bind ?response (read))
  206.    (printout "Asserted " ?response crlf)
  207.    (while (! (eq ?response done))
  208.           (assert (?response))
  209.           (printout "Assertion ? " crlf)
  210.           (bind ?response (read))
  211.           (printout "Asserted " ?response crlf)))
  212.  
  213.    When you run this rule, it will assert facts until you type "done".  You'll 
  214. find it convenient to save utility rules like this and incorporate 
  215.  them in programs just as you'd use a subroutine library in other languages.
  216.    As another example of the while statement, consider the program to calculate 
  217. which numbers are perfect squares that was discussed in Chapter 
  218.  8.  Instead of requiring a separate rule, make-numbers, to assert the numbers, 
  219. the program can be written as two rules.
  220.  
  221. (defrule make-numbers
  222.    (initial-fact)
  223. =>
  224.    (printout "Initial number ? " crlf)
  225.    (bind ?initial (read))
  226.    (printout "Final number ? " crlf crlf)
  227.    (bind ?final (read))
  228.    (while (<= ?initial ?final)
  229.       (assert (number ?initial))
  230.       (printout ?initial crlf)
  231.       (bind ?initial (+ ?initial 1))))
  232.  
  233. (defrule perfect-squares
  234.    (number ?x)
  235.    (number ?y)
  236.    (number ?z&:(= (* ?z ?z) (+ (* ?x ?x) (* ?y ?y))))
  237. =>
  238.    (printout "x = " ?x " y = " ?y " z = " ?z crlf))
  239.  
  240.  
  241. A Rule Of Sorts
  242.  
  243.  
  244. Suppose you wanted to write a single rule that would sort a list of numbers, 
  245. where some of the numbers might be duplicates.  Can it be done?
  246.   The answer is yes as the following program shows.  The first rule, called 
  247. input-numbers, allows the user to input the numbers to be sorted 
  248.  and asserts them as facts.  The second rule, called sort, does all the 
  249. sorting.  Actually, the sort rule just prints out the numbers in sorted 
  250.  order.  However, it would be easy enough to assert the sorted numbers as they 
  251. are printed out and so produce a sorted list of facts.
  252.    Enter the program and run for the numbers 3, -2, 1, 4, 1, and 0.  Then type 
  253. "done" (don't type in the double quotes) to stop input and the 
  254.  sort rule will print out the sorted numbers.
  255.  
  256. (defrule input-numbers
  257.    (initial-fact)
  258. =>
  259.    (bind ?count 1)
  260.    (printout "Number 1 = ? " crlf)
  261.    (bind ?number (read))
  262.    (while (! (eq ?number done))
  263.       (assert (?count ?number))
  264.       (bind ?count (+ ?count 1))
  265.       (printout "Number " ?count " = ? " crlf)
  266.       (bind ?number (read))))
  267.  
  268. (defrule sort
  269.    ?biggest <- (?count1 ?number1)
  270.    (not (~?count1 ?number2&:(> ?number2 ?number1)))
  271. =>
  272.    (printout ?number1 crlf)
  273.    (retract ?biggest))
  274.  
  275.    The input-numbers rule puts a different identifying number based on ?count 
  276. in front of each number to be asserted.  That way duplicate numbers 
  277.  can be entered.  For the sample numbers, the asserted facts would be
  278.  
  279. (1  3)
  280. (2   -2)
  281. (3   1)
  282. (4   4)
  283. (5   1)
  284. (6   0)
  285.  
  286.    The "number" field is not neccessary in this example because the program 
  287. uses facts with two fields for storage of the count and number.  
  288.  So (initial-fact) can never match because it has only one field.
  289.    Notice how count gives each of the numbers a unique identifier.  Otherwise, 
  290. the two number 1's could not be asserted.  Of course, if you don't 
  291.  have any duplicate numbers, the ?count is not necessary.
  292.    In the sort rule, the first conditional element matches to any number.  
  293. However, the second conditional element is more picky.  It says that 
  294.  there is no number greater than the number matched by the first conditional 
  295. element.  If you think about it, this is just the definition of 
  296.  the largest number in a list: there is no greater number.
  297.    Let's look at the second conditional element in more detail to understand 
  298. how it works.  The second conditional element requires several things. 
  299.   First, the count identifier must be unequal to ?count1.  This prevents a fact 
  300. from matching itself.  Second, ?number2 must be greater than 
  301.  the first number, ?number1, that was matched.
  302.    Suppose both of these requirements are met in the second conditional 
  303. element.  Then the logical not in front inverts the result and says it's 
  304.  not a match after all.  So if you think you've got it tough, imagine what it's 
  305. like to be an inference engine and told that a success is really 
  306.  a failure.        
  307.    The sort rule finds the biggest number in the fact-list and prints it out.  
  308. Then the (retract) removes the biggest number and the sort rule 
  309.  fires again to find the new biggest number.  The new biggest is printed out, 
  310. then retracted and the sort rule fires again.  This process continues 
  311.  until there are no more numbers left in the fact-list.
  312.  
  313.  
  314. If You Don't Care
  315.  
  316.  
  317. In the Sort Program, the count numbers were only used as internal identifiers 
  318. of facts.  That is, the value of the numbers was not important. 
  319.   Only the fact that each count number was unique was important in using the 
  320. counts as identifiers.
  321.    If you don't care what the actual value is, then CLIPS has just the thing 
  322. for you.  The gensym function returns a unique symbolic atom every 
  323.  time it's called of the general form
  324.  
  325. genX
  326.  
  327. where X represents a different number each time gensym is called.  The (gensym) 
  328. numbers always start at 1 when CLIPS is first started and increment 
  329.  by 1 each time (gensym) is called until you exit CLIPS.
  330.    As an example of (gensym), enter and run the following program to make ten 
  331. calls of (gensym).
  332.  
  333. (defrule make-gensym
  334.    (initial-fact)
  335. =>
  336.    (bind ?count 10)
  337.    (while (> ?count 0)
  338.       (bind ?sym (gensym))
  339.       (printout ?sym crlf)
  340.       (bind ?count (- ?count 1))))
  341.  
  342.    You'll see the following output.
  343.  
  344. gen1
  345. gen2
  346. gen3
  347. gen4
  348. gen5
  349. gen6
  350. gen7
  351. gen8
  352. gen9
  353. gen10
  354.  
  355. Now run the program again and the symbols will start with gen11.
  356.    You can also start (gensym) off with a value that you specify with the 
  357. setgen function.  The (setgen) must be used in a call to an external 
  358.  function on the RHS.  For example, modify the make-gensym rule as shown below 
  359. and run.  Now the (gensym) will start off at 100.
  360.  
  361. (defrule make-gensym
  362.    (initial-fact)
  363. =>
  364.    (bind ?count 10)
  365.    (call (setgen 100))  ; add this line
  366.    (while (> ?count 0)
  367.       (bind ?sym (gensym))
  368.       (printout ?sym crlf)
  369.       (bind ?count (- ?count 1))))
  370.  
  371.  
  372.    Now let's apply (gensym) to the sorting program.  Instead of using ?count 
  373. and having to do arithmetic to obtain new identifiers, let (gensym) 
  374.  do the work.  The following program is the (gensym) version.  Enter and run it 
  375. for some numbers and you'll see the program sorts just as good 
  376.  as before.
  377.  
  378. (defrule input-numbers
  379.    (initial-fact)
  380. =>
  381.    (printout "Number ? " crlf)
  382.    (bind ?number (read))
  383.    (while (! (eq ?number done))
  384.       (assert (=(gensym) ?number))
  385.       (printout "Number ? " crlf)
  386.       (bind ?number (read))))
  387.  
  388. (defrule sort
  389.    ?biggest <- (?count1 ?number1)
  390.    (not (~?count1 ?number2&:(> ?number2 ?number1)))
  391. =>
  392.    (printout ?number1 crlf)
  393.    (retract ?biggest))
  394.  
  395.    The only difference between the two sorting programs is that the (gensym) 
  396. version does not print out identifying numbers as you input the 
  397.  numbers.
  398.  
  399.  
  400. Whiles In Whiles
  401.  
  402.  
  403. As long as we're enhancing the program, let's go all the way and have it ask if 
  404. we want to do another.  Although this could be done by having 
  405.  another rule triggered when the not-mean-while rule ends, let's be elegant and 
  406. do everything within one rule.     The following program shows 
  407.  nested while-loops.  One loop, called the inner loop, is entirely contained 
  408. within another, the outer loop.  Run for an input of 1, 2, 3, 4, 
  409.  and 5.  Type "done" and you'll see the result mean.  Then type "n" and the 
  410. program will ask for a new set of numbers for a mean calculation.
  411.  
  412. (defrule stop-not-mean-while
  413.    (start-fact)
  414. =>
  415.    (bind ?response n)
  416.    (while (eq ?response n)
  417.       (bind ?sum 0)
  418.       (bind ?count 1)
  419.       (printout "Number 1 = ? " crlf)
  420.       (bind ?number (read))
  421.       (while (! (eq ?number done))
  422.          (bind ?count (+ ?count 1))
  423.          (bind ?sum (+ ?sum ?number))
  424.          (printout "Number " ?count " = ? " crlf)
  425.          (bind ?number (read)))
  426.       (bind ?mean (/ ?sum (- ?count 1)))
  427.       (printout crlf crlf "Mean = " ?mean crlf)
  428.       (printout "Stop program ? (y or n) " crlf)
  429.       (bind ?response (read))))
  430.  
  431.    The inner loop will keep asking for input data until the user inputs "done". 
  432.  Then the program will print out the mean and ask if another 
  433.  is to be done.  If the user answers "n", the program ends since the outer 
  434. while-loop test, (eq ?response n), is true.  
  435.    Although the if and while are very powerful, they should be used sparingly.  
  436. If you're using them a lot, your program is becoming sequential 
  437.  and an expert system language may not really be the best choice for your 
  438. application.
  439.  
  440.  
  441. The Missing Evidence
  442.  
  443.  
  444. Sometimes the absence of an item reveals more information than it's presence, 
  445. especially in murder mysteries.  CLIPS allows you to specify the 
  446.  absence of a fact as a conditional element on the LHS using the logical not 
  447. function.
  448.    The syntax of the conditional element with (not) is
  449.  
  450. (not (pattern))
  451.  
  452. where "pattern" represents the fact that is to be matched.
  453.    As a simple example, think back to the problem of telling a robot when to 
  454. cross the street.  The rules which tell when to walk are the following.
  455.  
  456.       If there is a walk-sign that says walk
  457.       then walk
  458.  
  459.       If there is no walk-sign and the light is green
  460.       then walk
  461.  
  462.    The (not) function can be conveniently applied to the simple rules above as 
  463. follows.
  464.  
  465. (defrule walk-sign-walk
  466.    (walk-sign walk)
  467. =>
  468.    (printout "Walk" crlf))
  469.  
  470. (defrule no-walk-sign
  471.    (light green)
  472.    (not (walk-sign ?))
  473. =>
  474.    (printout "Walk" crlf))
  475.  
  476.    Notice that a wildcard question mark is used for matching the second atom of 
  477. (walk-sign) in the no-walk-sign rule.  The rule won't be triggered 
  478.  on just (walk-sign) since we assume the second atom contains the status of the 
  479. sign.  That is, if there is a (walk-sign), it will be either 
  480.  (walk-sign walk) or (walk-sign dont walk).
  481.    Enter these rules and assert (light green).  Check the agenda and you'll see 
  482. that only no-walk-sign will be triggered.  Now assert (walk-sign 
  483.  walk) and you'll see that no-walk-sign will be removed from the agenda and 
  484. walk-sign-walk will be put on the agenda.
  485.    It's important to realize that the no-walk-sign rule is not the same as a 
  486. rule with one conditional element like
  487.  
  488. defrule green-light
  489.    (light green)
  490. =>
  491.    (printout "walk" crlf))
  492.  
  493.    The green-light rule will be triggered whether there is or is not a 
  494. (walk-sign) fact.  However, the no-walk-sign rule will be triggered only 
  495.  if there is not a (walk-sign).  The difference between the rules is subtle but 
  496. important.
  497.    A (not) can only be used to negate one conditional element at a time.  You 
  498. must use a separate (not) for each conditional element to be negated.
  499.                             Problems
  500.  
  501.  
  502. 1.  Rewrite problems 1 - 3 of Chapter 6 using a (while) statement to do all the 
  503. calculations in one rule.
  504.  
  505.  
  506. 2.  Write a program with one rule using nested (while) loops to generate the 
  507. multiplication tables from 1 to 9.  The output of the program should 
  508.  be in the form
  509.  
  510. 1 x 1 = 1
  511. 1 x 2 = 2
  512. 1 x 3 = 3
  513.  
  514. and so forth.
  515.  
  516.  
  517. 3.  Write a program to control the operation of a portable electric generator 
  518. driven by a Diesel engine.  A simplified diagram is shown in Fig. 
  519.  10-1.  The generator produces electricity if the engine is working properly.  
  520. The engine is working properly if there is a normal amount of 
  521.  oil for lubrication, fuel, and water for cooling.
  522.    Some of the possible abnormal conditions and corrective actions are shown in 
  523. the following table.  Based on the diagram, add other abnormal 
  524.  conditions and corrective actions to the table.  Each action should also print 
  525. out a message of the actions taken and the conditions that triggered 
  526.  the action.
  527.  
  528.         Conditions                  Actions
  529.  
  530.    Oil pressure is low       : Check Reserve Tank ORT-1
  531.    > 50 and <= 100
  532.  
  533.    Oil pressure too low      : Switch on Backup Oil Pump OP-2
  534.    <= 50                       Check Oil Pump OP-1
  535.  
  536.    Water temperature high    : Check Water Pump WP-1
  537.    > 125 and <= 165
  538.    
  539.    Water temperature too high: Turn on WP-2
  540.    > 165
  541.    
  542.    Electric demand is high   : Increase RPM to 2000
  543.    > 900 and <= 1000
  544.    
  545.    Electric demand very high : Increase RPM to 2500
  546.    > 1000 and < 1100                    
  547.  
  548.    Write a program that asks the user to input abnormal conditions.  That is, 
  549. the user can input that the oil pressure is low or that electric 
  550.  demand is very high.  The program will then print out the corrective action 
  551. and then ask the user for new conditions.  Also, include rules for 
  552.  excessive conditions such as no fuel that will cause a shutdown.
  553.    In order to simplify input of abnormal conditions by the user, display all 
  554. of the possible conditions in the form of a menu.  For example, 
  555.  part of the menu might be
  556.  
  557. Oil pressure low-------1   Water temperature high-------3
  558. Oil pressure too low---2   Water temperature too high---4
  559. Exit program----------99
  560.  
  561. The user would input the menu numbers of conditions.
  562.    Assume that a reading never just jumps to a too high or too low condition.  
  563. That is, the condition will first pass through a low or high before 
  564.  becoming too low or too high.  Thus all of the appropriate actions for the low 
  565. or high will be taken before the too low or too high conditions.
  566.    Some useful information is the following.
  567.  
  568. Fuel low     : 125
  569. Fuel too low : 50
  570.  
  571. The engine will not operate if it is out of fuel, oil, or water.
  572.  
  573. If any one backup system is operating, the system cannot meet an electric 
  574. demand that is very high and so a brownout will occur.
  575. Print out a brownout warning message.
  576.  
  577. If more than one backup system is operating, and electric demand is very high, 
  578. a blackout will occur.  Print out a blackout warning message.
  579.  
  580. If the electric demand is greater than or equal to 1100, a brownout will occur 
  581. because the RPM cannot be increased beyond 2500.  Print out a 
  582.  brownout warning message.
  583.  
  584.  
  585.