Rule Doesn't Fire as Expected

Tim Cook
  • Tim Cook

    Tim Cook - 2009-08-09

    Okay, I've been through the PyClips, Clips UG & BPG and still can't figure out why this doesn't work.

    [code & output shown below]

    I've created a Template and fill the slots with values from an object created via a webform. I assert the template as a fact and all the values are correct.  I have one rule (for now) that tests one value in in a slot (age).  If age is greater than 180 then the rule should assert a fact too_old.

    I have a "feeling" that the problem is in the way that I am using the age attribute in the rule but I can't find any examples to show anything different than what I'm doing.

            # we need to reset the fact list on each pass. This also Asserts the first fact and insures the program will begin execution.
            # Build the rules
            r1 = clips.BuildRule("age-rule", "(< 180 age)", "(assert (too_old))", "The age Limit Rule")
            print "The Age Rule is:  ", r1.PPForm()
            #build and fill the data template
            tmplt=clips.BuildTemplate("immunizations", """
                                        (slot age (type INTEGER))
                                        (slot HepB (type INTEGER))
                                        (slot tetra (type INTEGER))
                                        (slot polio (type INTEGER))
                                        (slot rota (type INTEGER))
            """, "template for child immunizations")
            immlist = clips.Fact(tmplt)  # tell Python that this is a CLIPS Fact template
            # assign the form data to the template
            immlist.Slots['age'] = self.age
            immlist.Slots['HepB'] = self.hepatitisB
            immlist.Slots['tetra'] = self.tetravalent
            immlist.Slots['polio'] = self.polio
            immlist.Slots['rota'] = self.rotavirus
            immlist.Assert() # assert the facts in the template

            print "Run Now: "

            print "The Agenda is: "
            print "The Facts are: "

    [output in terminal]
    The Age Rule is:   (defrule MAIN::age-rule "The age Limit Rule"
       (< 180 age)
       (assert (too_old)))

    Run Now:
    The Agenda is:
    The Facts are:
    f-0     (initial-fact)
    f-1     (immunizations (age 23327) (HepB 1) (tetra 3) (polio 2) (rota 3))
    For a total of 2 facts.

    All the values in the template slots are correct.  Any help is appreciated.


    • Johan Lindberg

      Johan Lindberg - 2009-08-09


      Matching a slot (in a deftemplate) within a rule requires you to state the deftemplate and the slot you wish to match against.

      You can test the age slot in immunizations with: (immunizations (age ?age&:(< 180 ?age)))

      See 5.4.1 Pattern Conditional Element in CLIPS Basic Programming Guide[1] for more information about why the "?age&:(< 180 ?age)" is required ( and specifically) .

      Johan Lindberg


    • Tim Cook

      Tim Cook - 2009-08-09

      Thanks.  I had read those sections and I'm not sure (still) that I fully understand them are some notes for others that may run into this situation.

      This format of the rule seemed to help disambiguate the properties for me at least:
              r1 = clips.BuildRule("age-rule", "(immunizations (age ?x&:(< 180 ?x)))", "(assert (too_old))", "The age Limit Rule")

      I would say "in English" immunizations['age'] is passed to x and x is tested against 180. 

      At the very top of your class definition do a clips.Clear() then between your template definition and your rules definition issue a clips.Reset().  Always setup your rules after your template assignment or when your rule fires it works and when it doesn't you'll get a parse error.

      Just my experience in making this work so far.....

      • Francesco Garosi

        Thank you Johan for helping Tim find the problem!

        The point is that, be it an ordered fact or a template based fact, the fact has to be considered in its entirety when describing a rule: in the special case of a template based fact, there is an "extra slot" (the first one) called "relation" that has to be matched anyway, to identify the fact (actually its structure). Then, to test slots, you could actually use the (test) function to perform the test, such as in the following:

        CLIPS> (deftemplate immunizations "template for child immunizations"
            (slot age (type INTEGER))
            (slot HepB (type INTEGER))
            (slot tetra (type INTEGER))
            (slot polio (type INTEGER))
            (slot rota (type INTEGER)))
        CLIPS> (defrule age-rule "The age Limit Rule"
            (immunizations (age ?x))
            (test (< 180 ?x))
            (assert (too_old)))
        CLIPS> (assert (immunizations (age 210))) ;; more than 17 y.o.!
        CLIPS> (facts)
        f-0     (immunizations (age 210) (HepB 0) (tetra 0) (polio 0) (rota 0))
        For a total of 1 fact.
        CLIPS> (agenda)
        0      age-rule: f-0
        For a total of 1 activation.

        While this can be easier to understand at a first glance, the form proposed by Johan is more clear when you're used to CLIPS syntax. So, as a thumb rule, first you must match a fact, then (or with CEs, almost in the same place) you can impose constraints on the fact.


        • Tim Cook

          Tim Cook - 2009-08-12

          Hi Franz,
          Thanks for your follow up.  Of course my initial question was simple and just beginning to get used to the PyClips syntax.  To let you know; I am trying to learn so that I can provide some practical examples for OSHIP developers to use more complex PyCLIPS situations.  This one is very simple and is actually easier to do with if/then constructs but I want to supply them both in a tutorial for instructional purposes.

          I have spent some time trying to convert your CLIPS example to PyClips but I'm not having much success.  Mostly in how to express the CE's in a PyClips rule.   Given the previous template and the fact that polio can only contain the integers 0,1,2 or 3 (this is restricted outside of PyClips). The simple if/then construct says that according to these ages (in days) the child should have a certain number of immunizations. 

                 # polio
                  if self.age >= 60:
                      if self.age <= 60 and self.age < 120 and self.polio == 0:
                          self.resultstxt += "<li>Needs Polio Dose #1</li> "
                      elif self.age <= 120 and self.age < 180 and self.polio < 2:
                          self.resultstxt += "<li>Needs Polio Dose #"+str(self.polio+1)+"</li> "
                      elif self.age = 180 and self.polio < 3:
                          self.resultstxt += "<li>Needs Polio Dose #"+str(self.polio+1)+"</li> "

          As  you can see I am constructing HTML output along the way with each immunization vs. age check.  If the age is > 180 I want to halt execution and just return text saying that this app can't analyze the child's status.

          But anyway, back to the CE problem.  If the first one is due at 60 days or after; if the child has'nt had one yet: How do I setup the connector in PyClips to test for age and # of current vaccinations from the template?   I can't seem to find an example nor can I discover the right magic to make it happen.

          r2 = clips.BuildRule("polio-rule", "(immunizations (polio ?x&:(= 0 ?x)))", "(assert (needs_polio))","The polio Dose #1 Rule")

          This of course fires if polio is zero but I only want it to fire if polio is zero and age is >= 60. Then so on with the other rules.  I believe I understand all the concepts okay.  I just can't get the bloody syntax correct. :-)

          Thanks for your help.


          • Johan Lindberg

            Johan Lindberg - 2009-08-12

            Hi again Tim,


            r2 = clips.BuildRule("polio-rule", "(immunizations (polio ?x&:(= 0 ?x)) (age ?age&:(>= ?age 60)))", "(assert (needs_polio))","The polio Dose #1 Rule")

            solve the problem?

            Johan Lindberg

            • Tim Cook

              Tim Cook - 2009-08-12

              r2 = clips.BuildRule("polio-rule", "(immunizations (polio ?x&:(= 0 ?x)) (age ?age&:(>= ?age 60)))", "(assert (needs_polio))","The polio Dose #1 Rule")

              Thanks again Johan.  WOW! Was I ever trying to complicate things connecting the elements on the LHS. 

              So in essence it is as simple as taking everything you want to evaluate on the LHS and putting it inside a set of it's own parens like this:    (polio ?x&:(= 0 ?x))       (age ?age&:(>= ?age 60))  then the entire group is inside a set of parens surrounded by double quotes; along with the leading template name.  

              As I understand it they are evaluated left to right and when any one group fails then the rule is not placed on the Agenda. ???? 

              BTW: Where are you in Sweden?  I spent Aug 07 - Feb 08 in Stockholm.  I loved the people and the city.  The weather wasn't so impressive though. :-)

        • Francesco Garosi

          Hi Tim,

          I see what you mean. As a matter of method, you should consider that every rule matches a particular configuration. Saying this, I mean that a rule should be fired for each set of constraints that you want to analyse. Think it in this way: where in Python you can nest constraints via "if" clauses, in CLIPS you generally can't.

          To reuse your example:

              if self.age >= 60:
                  if self.age <= 60 and self.age < 120 and self.polio == 0:   # (*)
                      self.resultstxt += "<li>Needs Polio Dose #1</li> "
                  elif self.age <= 120 and self.age < 180 and self.polio < 2: # (**)
                      self.resultstxt += "<li>Needs Polio Dose #"+str(self.polio+1)+"</li>"
                  elif self.age = 180 and self.polio < 3:
                      self.resultstxt += "<li>Needs Polio Dose #"+str(self.polio+1)+"</li>"

          (*) Note that the clause is not really proper, given the premise: it to be more precise, you should have left out "self.age <= 60", as you execute these clauses when self.age >= 60, so it cannot ever be less. You'll see later why I remark this.

          (**) The "self.age <= 120" test is useless when you "AND" a "self.age < 180", is this what you wanted? However I don't want to write "self.age >= 120", since it leaves out cases where self.polio > 0 in mutually exclusive clauses. However there is no catch-all rule for "2 <= self.polio <3" AND "self.age < 180". Again: is this what you wanted?

          What you want to test (we're not speaking of Python) is a series of possibilities, which defines our rules. Since you use the "if ... elif ... elif ..." construct, they are also mutually exclusive. Since you have to imagine the rule set as a sort of "sieve" that doesn't work sequentially but at once (actually it does, using various kinds of strategy), you should de-structure these rules in a way that they are actually mutually exclusive. At least at the beginning, there are ways to force the flow of evaluation, but let's keep it simpler for now. I have to split the second test in two, because the way it is written, it would include rule 1.

          NOTE: as per template, polio and age are considered integers: some rules are thus simplified to use homogeneous comparison operators and make tests shorter. Also note tat in CLIPS the comparison operators are not binary, but n-ary: the a test like this (< ?a ?b ?c ?d) is true if ?a < ?b < ?c < ?d.

          So, these can be the "rules":

          1)  when 60 <= age < 120, and polio is 0, emit "Need polio dose #1"
          2)  when 120 <= age < 180, and polio is 0, emit "Need polio dose #" + str(polio + 1)
          2a) when 60 <= age < 180, and polio is 1, emit "Need polio dose #" + str(polio + 1)
          3)  when age is 180, and 0 <=polio <= 2, emit "Need polio dose #" + str(polio + 1)
          3a) when 60 <= age < 180, and polio is 2, emit "I don't know..."
          4)  when age > 180, emit "Cannot analyze"

          In CLIPS you could write the rules as follows:

          ;; let's rewrite the template, for documentation
          (deftemplate immunizations "template for child immunizations"
              (slot age (type INTEGER))
              (slot HepB (type INTEGER))
              (slot tetra (type INTEGER))
              (slot polio (type INTEGER))
              (slot rota (type INTEGER)))

          ;; order, for what we are doing, is not that important: I write the rules
          ;; that I "dislike" (at least the ones that are not similar to each other)
          ;; first, then the other ones

          ;; the one for age > 180 days
          (defrule catch-exception "Rule 4 above"
              (immunizations (age ?x &:(> ?x 180)))
              (assert (something-wrong))  ;; might be useful
              (printout t "Cannot analyze" crlf))

          ;; the case that we somehow left out
          (defrule catch-dunno "Rule 3a above"
              (immunizations (polio ?x &:(= ?x 2)) (age ?y &:(<= 60 ?y 179)))
              (assert (something-wrong))  ;; might be useful
              (printout t "I don't know..." crlf))

          ;; standard ones: rules 1, 2 and 3
          (defrule polio-1 "Rule 1"
              (immunizations (polio ?x &:(= ?x 0)) (age ?y &:(<= 60 ?y 119)))
              (assert (polio-dose 1))
              (printout t "Need Polio Dose 1" crlf))

          (defrule polio-2 "Rule 2"
              (immunizations (polio ?x &:(= ?x 0)) (age ?y &:(<= 120 ?y 179)))
              (assert (polio-dose (+ ?x 1)))
              (printout t "Need Polio Dose " (+ ?x 1) crlf))

          (defrule polio-2 "Rule 2a"
              (immunizations (polio ?x &:(= ?x 1)) (age ?y &:(<= 60 ?y 179)))
              (assert (polio-dose (+ ?x 1)))
              (printout t "Need Polio Dose " (+ ?x 1) crlf))

          (defrule polio-3 "Rule 3"
              (immunizations (polio ?x &:(<= 0 ?x 2)) (age ?y &:(= ?y 180)))
              (assert (polio-dose (+ ?x 1)))
              (printout t "Need Polio Dose " (+ ?x 1) crlf))

          Well, the way it is now, it just would be useful if you analyse a single fact against the knowledge base each time.

          I hope this explanation will be useful. As a matter of how to write a rule in PyCLIPS, you just have to use this form:

              r = clips.BuildRule(identifier, LHS, RHS [, comment])

          So for Rule 3, for example, it will be:

          # here I use triple quotes to be able to use quotes and LF in rule parts
          r_id = "polio-3"
          r_LHS = """(immunizations (polio ?x &:(<= 0 ?x 2)) (age ?y &:(= ?y 180)))"""
          r_RHS = """(assert (polio-dose (+ ?x 1))) (printout t "Need Polio Dose " (+ ?x 1) crlf)"""
          comment = "Rule 3"
          r3 = clips.BuildRule(r_id, r_LHS, r_RHS, comment)

          You can use the strings directly in clips.BuildRule(), in case you like it more.



Log in to post a comment.