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:
- 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. - 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
- 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
- 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. - 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. - 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