Drools recursive rules - Java @ Desk

Thursday, August 22, 2013

Drools recursive rules

The most common example of recursion is a Fibonacci series. In drools, the implementation is done using the insert and modify keywords.
There are three most important parts in this implementation, one is insert keyword that is used in then part and other is not keyword that gets used in when part to satisify the breaking condition. As in Fibnocci series example, the breaking condition would be {sequence == 1} Consider below class and DRL file :

Fibonacci.java
package com.recursion;

public class Fibonacci {

    private int sequence;
    private long value;

    public Fibonacci(final int sequence) {
        this.sequence = sequence;
        this.value = -1;
    }

    public int getSequence() {
        return sequence;
    }

    public void setSequence(int sequence) {
        this.sequence = sequence;
    }

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }

}
TestRecursion.java
public class TestRecursion {

    public static final void main(String[] args) {
        try {
            // load up the knowledge base

            KnowledgeBase kbase = readKnowledgeBase();
            StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
            KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test");

            ksession.insert(new Fibonacci(10));
            ksession.fireAllRules();

            ksession.dispose();
            logger.close();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    private static KnowledgeBase readKnowledgeBase() throws Exception {
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        kbuilder.add(ResourceFactory.newClassPathResource("Fibonacci.drl"), ResourceType.DRL);
        KnowledgeBuilderErrors errors = kbuilder.getErrors();
        if (errors.size() > 0) {
            for (KnowledgeBuilderError error : errors) {
                System.err.println(error);
            }
            throw new IllegalArgumentException("Could not parse knowledge.");
        }
        KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
        kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
        return kbase;
    }
}
The implementation in terms of rules in drl file has three rules as shown below:
Recurse rule: This rule checks if the sequence is not reached to 1 and value is -1 then insert new Fibonacci class object with sequence value one less than previous object. The salience of this rule is highest, because all Fibnocci class object need to be inserted into working memory before calculation the value field of the class. The not conditional element is used to stop the rule's matching once we have all 50 Fibonacci objects in memory.
rule "Recurse"
    salience 10
    when
        f : Fibonacci ( value == -1 )
        not ( Fibonacci ( sequence == 1 ) )
    then
        insert( new Fibonacci( f.getSequence() - 1 ) );
        System.out.println( "recurse for " + f.getSequence() + "--" + f.getValue());
end
Bootstrap rule: Fibonacci series for 1 is 1 and for 2 is 1, 1. This rule gets activated for 2 Fibonacci class object that were inserted in the working memory in the earlier rule.
For Fibonacci object, sequence == 1 &
For Fibonacci object, sequence == 2
So when the sequence == 1 or sequence == 2, we need to update the value field in Fibonacci series to 1 as done below.
rule "Bootstrap"
dialect "mvel"
    when
        f : Fibonacci( sequence == 1 || == 2, value == -1 ) // multi-restriction
    then 
        modify ( f ){ value = 1 };
        System.out.println( f.getSequence()  + " == " + f.getValue()  );
end
Calculate rule: For the remaining 48 objects in the working memory where sequence > 2, below rule gets activated.
The "Calculate" rule uses field constraints to correctly constraint the thee Fibonacci patterns in the correct order; this technique is called cross product matching. The first pattern finds any Fibonacci with a value != -1 and binds both the pattern and the field. The second Fibonacci does this, too, but it adds an additional field constraint to ensure that its sequence is greater by one than the Fibonacci bound to f1. When this rule fires for the first time, we know that only sequences 1 and 2 have values of 1, and the two constraints ensure that f1 references sequence 1 and f2 references sequence 2. The final pattern finds the Fibonacci with a value equal to -1 and with a sequence one greater than f2. At this point, we have three Fibonacci objects correctly selected from the available cross products, and we can calculate the value for the third Fibonacci object that's bound to f3.
rule "Calculate"
dialect "mvel"
    when
        // Bind f1 and s1
        f1 : Fibonacci( s1 : sequence, v1 : value != -1 )
        // Bind f2 and v2; refer to bound variable s1
        f2 : Fibonacci( sequence == (s1 + 1), v2 : value != -1 )
        // Bind f3 and s3; alternative reference of f2.sequence
        f3 : Fibonacci( s3 : sequence == (f2.sequence + 1 ), value == -1 )      
    then
        // Note the various referencing rechniques.
        modify ( f3 ) { value = v1 + v2 };
        System.out.println( s3 + " == " + f3.value );
end 
The modify statement updated the value of the Fibonacci object bound to f3. This means we now have another new Fibonacci object with a value not equal to -1, which allows the "Calculate" rule to rematch and calculate the next Fibonacci number.






No comments:

Post a Comment