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.
Related
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?
I'm new to drools. I've defined the following rule to add the last two numbers in the stream together. I then send in a stream of 100 events, with the values set from 1 to 100. So I would expect the output to be 0, 1, 3, 5, 7, 9, 11, 13 etc.
declare TestEvent
#role( event )
value : int
end
rule "Simple Rule"
when
$sum : Integer() from accumulate ( TestEvent( $value : value ) over window:length( 2 ); sum( $value) )
then
System.out.println("Value: " + $sum );
end
The session is started using "fireUntilHalt" in a separate thread. If I put a delay of 10 milliseconds between every event inserted it works as expected. However when I don't have the delay, the results aren't as expected. Usually it just prints 0 and 197. Sometimes I might get a number or two in-between as well.
Why does this happen and what I should do to fix?
Ok, I finally understand it.
Having fireUntilHalt running in a separate thread means that the rules are only evaluated every now and then (not sure what that time period is). My assumption was that they would be evaluated on every insert. So on every insert the accumulator values are updated, but the rules evaluated aren't evaluated. So because my rules are inserted quickly (in about one second), the first and last insert is all that seems to be evaluated.
To get this to work so every insert is evaluated, I removed the fireUntilHalt, and did a separate fireAllRules after each insert.
Sliding windows are a crutch. Replace it by simple logic.
class Pred {
Integer pred;
}
rule procInt1
when
$p: Pred( pred == null )
$i: Integer()
then
modify( $p ){ setPred( $i ); }
end
rule procInt
when
$p: Pred( $i1: pred != null )
$i2: Integer()
then
System.out.println( $i1 + $i2 );
modify( $p ){ setPred( $i2 ); }
end
The fact that you have two threads doesn't mean that you can't have race conditions. The thread inserting events isn't suspended and so everything is possible.
Time related rules rely on 'time'. Smallest time tick for drools seems to be 1ms. Rules will be evaluated on each insertion (and respective consequences will be added to the agenda) but agenda will be executed on next millisecond tick (or when you explicitly fire all rules).
Suppose we have an Event type that has 3 properties: constraint, common, and distinct. The goal is to write a rule in Drools that fires when a subset of Events exists that meets the following criteria:
Events happened in the last t seconds; and
Events Have a previously known value for constraint property; and
Share a previously unknown value for common property; and
There are at least n different values for distinct property
If the rule fires, we need the set of participating events for further processing.
How do you advise we approach this problem?
Note 1: This question is somewhat similar to link, and Steve's answer there seems promising but is incomplete.
Note 2: Performance is of the essence. We have successfully developed rules that do this task, but they reduce the performance of the whole rule-base dramatically, and thus are unacceptable.
Edit 1: The current (poorly performing) solution looks like this:
rule ""
when
$event : Event(constraint == CONSTANT_VALUE)
$events : ArrayList() from collect(
Event(constraint == CONSTANT_VALUE,
common == $event.common)
over window:time( t ))
$distinctVals : Set( size >= n ) from accumulate(Event($d : distinct) from $events, collectSet($d))
then
// process $events
end
Drools isn't meant to be used for computations that require repeated evaluations of a potentially big set of facts. In such a situation you'll have to offload the details to some Java code.
class Collector {
String constraint;
String common;
List<Event> events = ...; // or Queue
Map<String,List<Event>> dist2events = ...;
int diversity;
public void addEvent( Event e ){
// 1. remove Event sets X from events older than t
// 2. remove all members of X from dist2events,
// delete map elements where the value list is empty
events.add( e );
List<Event> ofDistinct = dist2events.get( e.getDistinct() );
if( ofDistinct == null ){
ofDistinct = new ArrayList<Event>();
dist2events.put( ofDistinct );
}
ofDistinct.add( e );
diversity = dist2events.keySet().size();
}
}
rule createCollector
when
Event( $constraint: constraint, $common: common )
not Collector( constraint == constraint, common == $common )
then
insert( new Collector( $constraint, $common ) );
end
rule addEvent
when
$event: Event( $constraint: constraint, $common: common )
$collector: Collector( constraint == constraint,
common == $common,
events not contains $event )
then
modify( $collector ){ addEvent( $event ) }
end
rule MoreThan
when
Collector( $constraint: constraint, $common: common,
$events: events,
diversity >= n ) // maybe only when n-1 to n?
then
... process $events
end
You'll have to decide whether you want this rule to fire only when the threshold n is exceeded or whenever the set changes while diversity is >= n.
You might want to add a rule to remove empty Collectors.
Is there a built-in feature in Drools, selecting the latest n events, matching a certain pattern? I've read about sliding length windows in the documentation and the stock tick example seemed to be exactly what I wanted:
"For instance, if the user wants to consider only the last 10 RHT Stock Ticks, independent of how old they are, the pattern would look like this:"
StockTick( company == "RHT" ) over window:length( 10 )
When testing the example, it seems to me that it is evaluted more like a
StockTick( company == "RHT" ) from StockTick() over window:length( 10 )
selecting the latest 10 StockTick events and afterwards filtering them by company == "RTH", resulting in 0 to 10 RHT-Ticks, event though the stream contains more then 10 RTH-events.
A workaround is something like:
$tick : StockTick( company == "RHT" )
accumulate(
$other : StockTick(this after $tick, company == "RHT" );
$cnt : count(other);
$cnt < 10)
which has bad performance and readability.
Most likely you are seeing an initial phase where the count of events in the window and according to the constraints hasn't reached the length specified in window:length yet. For instance,
rule "Your First Rule"
when
accumulate( $st : Applicant($age: age > 5) over window:length(10)
from entry-point X,
$avg: average ( $age ), $cnt: count( $st ))
then
System.out.println("~~~~~avg~~~~~");
System.out.println($avg + ", count=" + $cnt);
System.out.println("~~~~~avg~~~~~");
end
displays an output even before there are 10 matching Applicants but later on, $cnt never falls below 10, even though $age ranges from 0 to 9, periodically.
If you do think you have found an example supporting your claim, please provide full code for reproduction and make sure to indicate the Drools version.
Your workaround is very bad indeed, as it accumulates for each StockTick. But a window:length(n) can be very efficiently implemented by using an auxiliary fact maintaining a list of n events. This may even be more advantageous than window:length.
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