Friday, January 22, 2010

Drools Inference and Truth Maintenance for good rule design and maintenance

Back in November I did a blog on inference and how it can be useful for rule authoring.
What is inference and how does it facilitate good rule design and maintenance

The summary of this was:
  • De-couple knowledge responsibilities
  • Encapsulate knowledge
  • Provide semantic abstractions for those encapsulations
For my JUG in Lille I extended this example by including truth maintenance, to demonstrate self maintaining systems.

The previous example was issuing ID cards to over 18s, in this example we now issue bus passes, either a child or adult pass.
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
As before the above example is considered monolithic, leaky and providing poor separation of concerns.

As before we can provide a more robust application with a separation of concerns using inference. Notice this time we don't just insert the inferred object, we use "logicalInsert":
rule "Infer Child" when
$p : Person( age < 16 )
then
logicalInsert( new IsChild( $p ) )
end
rule "Infer Adult" when
$p : Person( age >= 16 )
then
logicalInsert( new IsAdult( $p ) )
end
A "logicalInsert" is part of the Drools Truth Maintenance System (TMS). Here the fact is logically inserted, this fact is dependant 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. So in the above rules if the person is under 16 it inserts an IsChild fact, once the person is 16 or over the IsChild fact is automatically retracted and the IsAdult fact inserted.

We can now bring back in the code to issue the passes, these two 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
logicalInsert(new ChildBusPass( $p ) );
end

rule "Issue Adult Bus Pass" when
$p : Person( age >= 16 )
IsAdult( person =$p )
then
logicalInsert(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. For bonus points we can combine this with the 'not' conditional element to handle notifications, in this situation a request for the returning of the pass. So 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

10 comments:

  1. Truth maintenance is so awesome, but it seems almost to hidden away for people to discover - need more articles like this to show how it can help.

    ReplyDelete
  2. I think TMS really comes into it's own once you have opportunistic backward chaining, aka NExpert:
    http://blog.athico.com/2009/08/nexpert-review-mactech.html

    ReplyDelete
  3. Wouldn't it be cool if the "Infer Child" and "Infer Adult" rules could be combined into one rule using an else clause? :)

    ReplyDelete
  4. OPSJ does an "else" on our equivalent of an eval. I think the concept can be extended for more generic capabilities using 'or' CE, here we call this "branch" for more conciseness.

    rule "multi value assignment" when
    (branch $p : Person( age < 5 )
    [b1] $p : Person( age >= 5 < 15 )
    [b2] $p : Person( age >= 15 < 25 )
    [b3] $p : Person( age >= 25 < 40 ) )
    then
    ....
    then[b1]
    ....
    then[b2]
    ...
    then[b3]
    ....
    end

    So then any "else" can be implemented as:
    rule "...."
    [else1] $p : Person( age < 16 )
    then
    ...
    then[else1]
    ...
    end

    Which is rewritten internally as:

    rule "multi value assignment" when
    (branch $p : Person( age < 16 )
    [b1] ( $p : Person()
    not Person( this == $p, age >= 16 ) )
    then
    ....
    then[b1]
    ....
    end

    ReplyDelete
  5. Do I see a community patch coming?

    ;)

    ReplyDelete
  6. In third code block, should it read:

    rule "Issue Adult Bus Pass" when
    $p : Person( )
    IsAdult( person =$p )

    ReplyDelete
  7. In the last rule to request the return of the pass, what if the Person was never a child (ie, the inserted Person was initially an adult)? Will then adults get a request to return the child pass they never had?

    ReplyDelete
  8. "In the last rule to request the return of the pass, what if the Person was never a child (ie, the inserted Person was initially an adult)? Will then adults get a request to return the child pass they never had?"

    Yes additional logic would need to be added.

    ReplyDelete
  9. Hi Marc, what is your rule of thumb wether to use a "declare" or a Java fact? For IsAdult we would use a declare. This solution seems to us more natural.

    ReplyDelete