Red Hat Training

A Red Hat training course is available for JBoss Enterprise SOA Platform

Chapter 6. Processing

6.1. Agenda

The Agenda is a Rete feature. During actions on the WorkingMemory, rules may become fully matched and eligible for execution. A single Working Memory Action can result in multiple eligible rules. When a rule is fully matched an Activation is created, referencing the rule and the matched facts, and placed onto the Agenda. The Agenda controls the execution order of these Activations using a Conflict Resolution strategy.

6.2. Agenda Processing

The engine cycles repeatedly through two phases:
  1. Working Memory Actions. This is where most of the work takes place, either in the Consequence (the RHS itself) or the main Java application process. Once the Consequence has finished or the main Java application process calls fireAllRules() the engine switches to the Agenda Evaluation phase.
  2. Agenda Evaluation. This attempts to select a rule to fire. If no rule is found it exits, otherwise it fires the found rule, switching the phase back to Working Memory Actions.
The process repeats until the agenda is clear, in which case control returns to the calling application. When Working Memory Actions are taking place, no rules are being fired.

6.3. Default Conflict Resolution Strategies

Salience (Priority)
A user can specify that a certain rule has a higher priority (by giving it a higher number) than other rules. In that case, the rule with higher salience will be preferred.
LIFO (last in, first out)
LIFO priorities are based on the assigned Working Memory Action counter value, with all rules created during the same action receiving the same value. The execution order of a set of firings with the same priority value is arbitrary.

Note

As a general rule, it is a good idea not to count on rules firing in any particular order, and to author the rules without worrying about a "flow". However when a flow is needed a number of possibilities exist, including but not limited to: agenda groups, rule flow groups, activation groups, control/semaphore facts. These are discussed in later sections.

6.4. AgendaGroup

Agenda groups are a way to partition rules on the agenda. At any one time, only one group has "focus" which means that activations for rules in that group only will take effect. You can also have rules with "auto focus" which means that the focus is taken for its agenda group when that rule's conditions are true.
Agenda groups are known as "modules" in CLIPS terminology. Agenda groups provide a way to create a "flow" between grouped rules. You can switch the group which has focus either from within the rule engine, or via the API. If your rules have a clear need for multiple "phases" or "sequences" of processing, consider using agenda-groups for this purpose.

6.5. setFocus()

Each time setFocus() is called it pushes the specified Agenda Group onto a stack. When the focus group is empty it is popped from the stack and the focus group that is now on top evaluates. An Agenda Group can appear in multiple locations on the stack. The default Agenda Group is "MAIN", with all rules which do not specify an Agenda Group being in this group. It is also always the first group on the stack, given focus initially, by default.

6.6. setFocus() Example

This is what the setFocus() element looks like:
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();

6.7. ActivationGroup

An activation group is a set of rules bound together by the same "activation-group" rule attribute. In this group only one rule can fire, and after that rule has fired all the other rules are cancelled from the agenda. The clear() method can be called at any time, which cancels all of the activations before one has had a chance to fire.

6.8. ActivationGroup Example

This is what an ActivationGroup looks like:
ksession.getAgenda().getActivationGroup( "Group B" ).clear();

6.9. RuleFlowGroup

A rule flow group is a group of rules associated by the "ruleflow-group" rule attribute. These rules can only fire when the group is activate. The group itself can only become active when the elaboration of the ruleflow diagram reaches the node representing the group. Here too, the clear() method can be called at any time to cancels all activations still remaining on the Agenda.

6.10. RuleFlowGroup Example

This is what the RuleFlowGroup property looks like:
ksession.getAgenda().getRuleFlowGroup( "Group C" ).clear();

6.11. The Difference Between Rules and Methods

  • Methods are called directly.
  • Specific instances are passed.
  • One call results in a single execution.
  • Rules execute by matching against any data as long it is inserted into the engine.
  • Rules can never be called directly.
  • Specific instances cannot be passed to a rule.
  • Depending on the matches, a rule may fire once or several times, or not at all.

6.12. Cross Product Example

Below, a rule consisting of an unconstrained fire alarm situation is shown:
rule
when
    $room : Room()
    $sprinkler : Sprinkler()
then
    System.out.println( "room:" + $room.getName() +
                        " sprinkler:" + $sprinkler.getRoom().getName() );
end
In SQL terms this would be like doing select * from Room, Sprinkler and every row in the Room table would be joined with every row in the Sprinkler table resulting in the following output:
room:office sprinkler:office
room:office sprinkler:kitchen
room:office sprinkler:livingroom
room:office sprinkler:bedroom
room:kitchen sprinkler:office
room:kitchen sprinkler:kitchen
room:kitchen sprinkler:livingroom
room:kitchen sprinkler:bedroom
room:livingroom sprinkler:office
room:livingroom sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:livingroom sprinkler:bedroom
room:bedroom sprinkler:office
room:bedroom sprinkler:kitchen
room:bedroom sprinkler:livingroom
room:bedroom sprinkler:bedroom
These cross products can become huge and can contain spurious data. This can be averted by constraining the cross products, which is done with the variable constraint:
rule
when
    $room : Room()
    $sprinkler : Sprinkler( room == $room )
then
    System.out.println( "room:" + $room.getName() +
                        " sprinkler:" + $sprinkler.getRoom().getName() );
end
This results in just four rows of data, with the correct Sprinkler for each Room. In SQL (actually HQL) the corresponding query would be select * from Room, Sprinkler where Room == Sprinkler.room.
room:office sprinkler:office
room:kitchen sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:bedroom sprinkler:bedroom

6.13. Activations, Agenda and Conflict Sets Example

In this example, a cashflow calculation system is featured. These are the three classes implemented:
public class CashFlow {
    private Date   date;
    private double amount;
    private int    type;
    long           accountNo;
    // getter and setter methods here
}

public class Account {
    private long   accountNo;
    private double balance;
    // getter and setter methods here
}

public AccountPeriod {
    private Date start;
    private Date end;
    // getter and setter methods here
}
Two rules can be used to determine the debit and credit for that quarter and update the Account balance. The two rules below constrain the cashflows for an account for a given time period. Notice the "&&" which use short cut syntax to avoid repeating the field name twice.
rule "increase balance for credits"
when
  ap : AccountPeriod()
  acc : Account( $accountNo : accountNo )
  CashFlow( type == CREDIT,
            accountNo == $accountNo,
            date >= ap.start && <= ap.end,
            $amount : amount )
then
  acc.balance  += $amount;
end
rule "decrease balance for debits" 
when 
  ap : AccountPeriod() 
  acc : Account( $accountNo : accountNo ) 
  CashFlow( type == DEBIT, 
            accountNo == $accountNo,
            date >= ap.start && <= ap.end, 
            $amount : amount ) 
then 
  acc.balance -= $amount; 
end
If the AccountPeriod is set to the first quarter we constrain the rule "increase balance for credits" to fire on two rows of data and "decrease balance for debits" to act on one row of data.
The data is matched during the insertion stage and only fires after fireAllRules() is called. Meanwhile, the rule plus its matched data is placed on the Agenda and referred to as an Activation. The Agenda is a table of Activations that are able to fire and have their consequences executed, as soon as fireAllRules() is called. Activations on the Agenda are executed in turn. Notice that the order of execution so far is considered arbitrary.
After all of the above activations are fired, the account has a balance of -25.
If the AccountPeriod is updated to the second quarter, we have just a single matched row of data, and thus just a single Activation on the Agenda.
The firing of that Activation results in a balance of 25.

6.14. Conflict Resolver Strategy

When there is one or more Activations on the Agenda they are said to be in conflict, and a conflict resolver strategy is used to determine the order of execution. At the simplest level the default strategy uses salience to determine rule priority.

6.15. Conflict Resolver Strategy Example

Each rule has a default value of 0, the higher the value the higher the priority. To illustrate this, a rule is added to print the account balance. The goal is for the rule to be executed after all the debits and credits have been applied for all accounts. This is done by assigning a negative salience to this rule so that it fires after all rules with the default salience 0.
rule "Print balance for AccountPeriod"
        salience -50
    when
        ap : AccountPeriod()
        acc : Account()        
    then
        System.out.println( acc.accountNo + " : " + acc.balance );    
end

6.16. Trigger Example

Table 6.1. Trigger Example

Rule View View Trigger
select * from Account acc,
              Cashflow cf,
              AccountPeriod ap      
where acc.accountNo == cf.accountNo and 
      cf.type == CREDIT and
      cf.date >= ap.start and 
      cf.date <= ap.end
select * from Account acc, 
              Cashflow cf,
              AccountPeriod ap 
where acc.accountNo == cf.accountNo and 
      cf.type == DEBIT and
      cf.date >= ap.start and 
      cf.date <= ap.end
trigger : acc.balance += cf.amount
trigger : acc.balance -= cf.amount

6.17. ruleflow-group Example

The use of the ruleflow-group attribute in a rule is shown below:
rule "increase balance for credits"
  ruleflow-group "calculation"
when
  ap : AccountPeriod()
  acc : Account( $accountNo : accountNo )
  CashFlow( type == CREDIT,
            accountNo == $accountNo,
            date >= ap.start && <= ap.end,
            $amount : amount )
then
  acc.balance  += $amount;
end
rule "Print balance for AccountPeriod"
  ruleflow-group "report"
when
  ap : AccountPeriod()
  acc : Account()
then
  System.out.println( acc.accountNo +
                      " : " + acc.balance );    
end

6.18. Inference Example

In the example below, the IsAdult property is used to infer a person's age.
rule "Infer Adult"
when
  $p : Person( age >= 18 )
then
  insert( new IsAdult( $p ) )
end
This inferred relation can be used in any rule:
$p : Person()
IsAdult( person == $p )
Further, de-coupling the knowledge process decreases the chance of data leakage and third party modifications to the information.

6.19. Implementing Inference and TruthMaintenance

Procedure 6.1. Task

  1. Open a set of rules. In this example, a buss pass issuing system will be used:
    rule "Issue Child Bus Pass" when
      $p : Person( age < 16 )
    then
      insert(new ChildBusPass( $p ) );
    end
     
    rule "Issue Adult Bus Pass" when
      $p : Person( age >= 16 )
    then
      insert(new AdultBusPass( $p ) );
    end
  2. Insert the fact insertLogical and add the terms you wish to be inferred.
    rule "Infer Child" when
      $p : Person( age < 16 )
    then
        insertLogical( new IsChild( $p ) )
    end
    rule "Infer Adult" when
        $p : Person( age >= 16 )
    then
        insertLogical( new IsAdult( $p ) )
    end
    The fact has been logically inserted. This fact is dependent on the truth of the "when" clause. It means that when the rule becomes false the fact is automatically retracted. This works particularly well as the two rules are mutually exclusive. In the above rules, the IsChild fact is inserted if the child is under 16. It is then automatically retracted if the person is over 16 and the IsAdult fact is inserted.
  3. Insert the code to issue the passes. These can also be logically inserted as the TMS supports chaining of logical insertions for a cascading set of retracts.
    rule "Issue Child Bus Pass" when
        $p : Person( )
        IsChild( person == $p )
    then
        insertLogical(new ChildBusPass( $p ) );
    end
     
    rule "Issue Adult Bus Pass" when
        $p : Person( age >= 16 )
        IsAdult( person =$p )
    then
        insertLogical(new AdultBusPass( $p ) );
    end
    Now when the person changes from being 15 to 16, not only is the IsChild fact automatically retracted, so is the person's ChildBusPass fact.
  4. Insert the 'not' conditional element to handle notifications. (In this situation, a request for the returning of the pass.) When the TMS automatically retracts the ChildBusPass object, this rule triggers and sends a request to the person:
    rule "Return ChildBusPass Request "when
      $p : Person( )
           not( ChildBusPass( person == $p ) )
    then
        requestChildBusPass( $p );
    end