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).
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?
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'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.
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.