drools: rules executed more than one time - drools

I am trying hands on at the Drools rule engine , I am quite a beginner.
I have the following rules in place in a single rule file:
rule "A stand alone rule"
salience 2
no-loop
when
$account : Account()
Account($account.balance>100)
then
System.out.println("balance>100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
rule "A second Rule"
salience 1
no-loop
when
$account : Account()
Account($account.balance<100)
then
System.out.println("balance<100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
In the StatefulKnowledgeSession I am passing TWO accounts , one with balance 15000 another with balance 15 ,
Account account=new Account(7l,15000l);
Account account1=new Account(5l,15l);
Customer customer = new Customer("Samrat", 28, "Sector51", account);
Customer customer1 = new Customer("Alexi", 28, "Sector50", account1);
account.setCustomer(customer);
account1.setCustomer(customer1);
session.insert(account);
session.insert(account1);
session.fireAllRules();
According to me the expected result should be that each rule should be fired only once and the corresponding object should be printed.
But the result I am getting is :
balance>100
15000
Samrat
balance>100
15000
Samrat
balance<100
15
Alexi
balance<100
15
Alexi
I am not able to understand why each rule is running twice ????

Using multiple patterns (and not specifying any relation between them) will create a full Cartesian product (just like a select on multiple tables without a join clause).
So, the rule:
rule A
when
Account()
Account()
then
...
end
will be activated N^2 times for N objects of type Account.
One solution could be to use the magic field 'this' to specify that the second account is the same as the first one:
rule A
when
$a: Account()
Account(this == $a)
then
...
end
But, going back to your example, I think you don't even need to use 2 different patterns. You could rewrite your rules as following:
rule "A stand alone rule"
salience 2
no-loop
when
$account: Account(balance>100)
then
System.out.println("balance>100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
rule "A second Rule"
salience 1
no-loop
when
$account: Account(balance<100)
then
System.out.println("balance<100");
System.out.println($account.getBalance());
System.out.println($account.getCustomer().getName());
end
Hope it helps,

I was comparing two objects of same class and was wondering why the rules are getting fired multiple times. However after reading explanation from Esteban Aliverti I thought that my rule might also be creating Cartesian product.
So I replaced "and" from the rules to "," and it worked perfectly. However, I could not understand why "and" was creating Cartesian product.
Earlier my rule was --
rule "Rule 1"
when
$first : RuleC() and
second : RuleC(this != $first) and
RuleC($first.outcome < outcome) and
RuleC($first.target == target)
then
System.out.println("The rule has been fired ");
end
Later my rule became (And it is working absolutely fine) --
rule "Rule 1"
when
$first : RuleC() and
second : RuleC(this != $first, $first.outcome < outcome, $first.target == target)
then
System.out.println("The rule has been fired ");
end

Related

Deactivate a rule flow group in drools

I have a drl file which has rules inside 2 ruleflow-groups: "first-ruleflow-group" and "second-ruleflow-group" . The activation of these groups depend on "rule A" and "rule B". Is there any way in which I can deactivate rule B to fire when rule A condition matches, so that the focus is set only to "first-ruleflow-group"?
rule "rule A"
when
eval(true);
then
drools.setFocus("first-ruleflow-group");
end
rule "rule B"
when
eval(true);
then
drools.setFocus("second-ruleflow-group");
end
Change your rules rely on exclusive conditions.
Simple example. Let's say we have an application dealing with calendar events. We have a rule flow for public holidays. We have a rule flow for religious holidays. There are some religious holidays which are also public holidays; for these we only want to fire the public holiday rules.
rule "Public Holidays"
when
Holiday( isPublic == true )
then
drools.setFocus("publicholiday-flow");
end
rule "Religious Holidays"
when
Holiday( isReligious == true,
isPublic == false )
then
drools.setFocus("religiousholiday-flow");
end
So, basically, modify your "rule B" to include a condition that negates the rule A condition, so that if Rule A matches, Rule B necessarily does not.
A second solution would be to have a rule in your first rule flow (triggered by A) set a condition such that rule B doesn't trigger. It's basically similar to the previous solution except that the condition keeping Rule B from triggering is dynamic.
As an example, imagine an application that determines how much someone owes for parking. Parking rates are set by day -- there's one rate for Monday through Friday, a rate for Saturday, and Sundays are free. In addition, there are other discounts applied -- for example senior citizens, or if the total amount time parked is less than 5 minutes. The day-of-the-week rates are determined using the first rule flow (A), and the discounts are determined using the second rule flow (B). If it is Sunday, there's no reason to fire the second set of rules.
You could either write the discount rule flow trigger to explicitly not fire for Sundays, or you could have the Sunday rate rule retract or insert data such that it makes the discount rules no longer valid for running.
rule "Day Rates"
when
then
drools.setFocus("dayrates-flow");
end
rule "Discounts"
when
exists(Rate( hourly > 0 ))
then
drools.setFocus("discounts-flow");
end
// The Sunday rule, part of the dayrates-flow, sets hourly = 0, which makes
// the "Discounts" rule no longer valid to fire.
rule "Sunday Rate"
ruleflow-group "dayrates-flow"
when
not(Rate())
Request( day == DayOfWeek.SUNDAY ) // when parking on Sunday...
then
Rate rate = new Rate();
rate.setHourlyRate(0.0);
insert(rate);
end
Yet another option would be to trigger the second rule flow from within the first rule flow, but only as needed.
Reusing the previous example with parking:
rule "Day Rates"
when
then
drools.setFocus("dayrates-flow");
end
// Here are some day rate rules. Most are omitted for brevity. We include three (3)
// to show regular rates, the special case of Sunday, and how we trigger the discount
// rate rules
rule "Saturday Rate"
ruleflow-group "dayrates-flow"
when
not(Rate())
Request( day == DayOfWeek.SATURDAY )
then
Rate rate = new Rate();
rate.setHourly(1.0); // Saturday rate: $1/hr
insert(rate);
end
rule "Sunday Rate"
ruleflow-group "dayrates-flow"
when
not(Rate())
Request( day == DayOfWeek.SUNDAY )
then
Rate rate = new Rate();
rate.setHourlyRate(0.0); // Sunday rate: free
insert(rate);
end
// This rule only triggers the next rule flow when the rate is positive (eg not Sunday)
rule "Transition to Discounts"
ruleflow-group "dayrates-flow"
when
exists(Rate( hourly > 0 ))
then
drools.setFocus("discount-flow");
end

OptaPlanner: Drools rule on consecutive shift assignments

The context is Employee Shift Assignment with OptaPlanner using Drools rules for calculating scores.
My Employees cannot work for, say, for more than three consecutive days without a rest day.
I implement such a constraint very stupidly as:
rule "No more than three consecutive working days"
when
ShiftAssignment(
$id1 : id,
$empoloyee : empoloyee != null,
$shift1 : shift
)
ShiftAssignment(
id > $id1,
empoloyee == $empoloyee,
shift.isConsecutiveDay($shift1),
$id2 : id,
$shift2 : shift
)
ShiftAssignment(
id > $id2,
empoloyee == $empoloyee,
shift.isConsecutiveDay($shift2),
$id3 : id,
$shift3 : shift
)
ShiftAssignment(
id > $id3,
empoloyee == $empoloyee,
shift.isConsecutiveDay($shift10)
)
then
scoreHolder.penalize(kcontext);
end
I hope the name of the methods/variables clearly reveal what they do/mean.
Is there a more convenient and smart way to implement such a rule? Keep in mind that the three days above may need to change to a bigger number (I used three to avoid a more realistic ten and more lines of code in the rule). Thanks.
If we can assume an employee takes up to a single shift per day and the shift.isConsecutiveDay() may be replaced by something like shift.day == $shift1.day + 1, exists can be used:
when
ShiftAssignment($employee : empoloyee != null, $shift1 : shift)
exists ShiftAssignment(employee == $employee, shift.day == $shift1.day + 1)
exists ShiftAssignment(employee == $employee, shift.day == $shift1.day + 2)
exists ShiftAssignment(employee == $employee, shift.day == $shift1.day + 3)
then
If such an assumption cannot be made, your solution should work, with one potential corner case to think about:
The rule tries to filter out combinations of the same shifts by the condition id > $id1. This condition works, but the IDs must be generated ascendingly by the time of the shift, otherwise, it clashes with shift.isConsecutiveDay(...). In case this property cannot be guaranteed, checking for ID inequality could be preferable.
I used a combination of rules to achieve this. First rule sets up the start of a consecutive work sequence, second one sets up the end, 3rd rule creates a "Work Sequence" to fit between the start and end. Finally the "Max Consecutive Days" rule actually checks your "Work Sequence" against a limit on number of consecutive days.
This paradigm is actually in the nurse rostering example:
https://github.com/kiegroup/optaplanner/blob/master/optaplanner-examples/src/main/resources/org/optaplanner/examples/nurserostering/solver/nurseRosteringConstraints.drl

Drools rule using accumulate

Hello I it is my first time involved in drools project. I have created some simple rules that work fine, however I have trouble with more complex rules that use the accumulate function. Below I have this rule.
rule "1"
no-loop
when
$msg : Declaration(header.totalGrossMassMeasure != null,
header.totalGrossMassMeasure.compareTo(BigDecimal.ZERO) > 0 )
result : ValidationResult()
$netValue : Number() from accumulate (
GoodsItemsType($net : netNetWeightMeasure),
sum($net.doubleValue())
)
eval($netValue.doubleValue() > ($msg.getHeader().getTotalGrossMassMeasure().doubleValue() + (0.45 * $msg.getGoodsItems().size())))
then
RulesValidationError error = new RulesValidationError();
error.setErrorType(ErrorType.INCORECT_VALUE);
result.getErrorsList().add(error);
end
the concept is to sum the net value from a list of goodsItemType object and compare the sum to the total gross mass measure multiplied by one buffer number. The problem is I have been trying last couple of days not being able to fire the rule with anything. Could someone please help me?

Drools : Rule firing multiple times

I'm new to Drools and have hit a problem.
I've simplified the rule to exhibit the problem:
rule "test"
when
$ev : TestEvent()
$evList : ArrayList( size >= 3 ) from collect
(
TestEvent(linkId == $ev.getLinkId())
)
then
System.out.println("Rule fired!")
end
Basically, I want to count Events occurring on a particular Link (a Link is a section of road). When 3 events occur on the same Link I want the rule to fire.
The rule above is almost working, but when it fires, it fires 3 times, once for each event. I only want it to fire once.
What am I missing?
Many thanks in advance.
The first pattern picks any TestEvent irrespective of its linkId. If there are n TestEvent facts with a certain linkId, the acivation proceeds n times.
To restrict this rule to fire once you could select a single TestEvent out of any such group of n. Any attribute with a unique ordered value may be used, and if you have events also the event timestamp is available.
rule "test"
when
$ev: TestEvent( $lid: linkId )
not TestEvent( linkId == $lid, this before $ev )
$evList : ArrayList( size >= 3 ) from collect
(
TestEvent(linkId == $lid)
)
then
System.out.println("Rule fired!")
end
I got this working by changing my approach to the problem. I've created Link objects now and then tie the events back to the Link.
The rule ends up
rule "test"
when
$link : Link()
$events : ArrayList( size >= 3 ) from collect (TestEvent(link == $link))
then
System.out.println("Rule fired!")
end
This only fires once per link which is what I need.

Drools accumulate method that returns zero if no matching source fact

I am trying to implement a rule in Drools that calculates the sum of a some property of a fact. That works great using accumulate and sum. The problem is that when there are not fact that matches the criteria in the source part of the accumulate method the rule is not executed.
I would like the sum method to return zero if no fact is matching and that the rest of the when clauses is checked. Is that possible somehow?
Update:
I am using Drools 6.0.1
The problem seems to lie in the the and clause. Here is a code that is my problem.
rule "accu"
when
$n: Number()
from accumulate( $o: Order() and OrderLine( $v: quantity ),
sum($v))
then
System.out.println("*#*#*#*#*#*#*#*#*#* Accu has fired *#*#*#*#*#*#*#*#*#");
end
With only Order or OrderLine it works. I have a feeling I am attacking the problem the wrong way. In my real case the value I want to sum up is in the OrderLine but the criteria is in another class.
$ol : OrderLine($q : quantity)
and
$ac : ArticleClass(orderLine == $ol, crtiteria1=efg, criteria2=abc)
But accumulate does return 0 when there are no matching elements.
rule accu
when
$n: Number()
from accumulate( Fact( prop == "C", $v: value ),
sum($v))
then
//...
end
This fires in the absence of Fact facts with prop == "C" and it fires if there are no Fact facts at all. (Drools 5.5.0)
Please provide full code reproducing the error, Drools version, etc.