In our company we are evaluate Drools as framework for our products.
For that I am trying some experiments and obviously I am now overwhelmed by lot of problems.
For example I don't know how make condition on duration of an event.
I don't know if there is something similar to
..
when
Event( some conditions, duration > 1h)
then
...
Thank very much in advanced
Duration is member like any other on your event class. When you declare your event, you tell it which member is the duration like so:
declare VoiceCall
#role( event )
#timestamp( callDateTime )
#duration( callDuration )
end
Duration is in milliseconds, so you would reason against it in a rule like so:
rule "Call Longer Than One Hour"
when
VoiceCall( callDuration > 3600000 ) // 1000*60*60
then
...
end
The Drools Fusion manual describes this pretty clearly (in fact, my example event declaration is taken directly from it).
Related
I would like the "then" clause to only execute one time, but it is executing for each child object in the list that matches.
If any item in the list meets the condition I want to break out and have only one execution of the then clause.
rule "Profile - Delinquent"
when
$c : CreditReportAll( $creditLiability : creditLiability )
$cs: CreditLiability( paymentPatternData.contains("X")) from $creditLiability
then
CreditUserSegment $cu = new CreditUserSegment();
$cu.setSegmentCode("delinquent");
$c.addUserSegmentToList($cu);
end
In order to only fire the rule once when any item in the $creditLiability list meets the condition, you either need to write your rule such that it doesn't iterate across the list, or you need to update your rule so that once it does fire, it changes the facts in working memory to not allow it to fire again.
No iteration
The easiest way to do this is to change your rule to not iterate across the list. To do this, we use the exists keyword like this:
rule "Profile - Delinquent"
when
$c : CreditReportAll( $creditLiability : creditLiability )
exists( CreditLiability( paymentPatternData.contains("X")) from $creditLiability )
then
CreditUserSegment $cu = new CreditUserSegment();
$cu.setSegmentCode("delinquent");
$c.addUserSegmentToList($cu);
end
The exists keyword will match when the there is at least one element present that matches the required condition. Note that we don't assign a variable to the result anymore, because it doesn't make any sense (eg. there's no assignment of $cs here; it would be ambiguous as to what it would even refer to.)
The downside to this approach is that if you update working memory in any other rule (eg by calling insert, modify, update, and so on), you may end up triggering this rule again because the conditions on the left hand side will still remain valid and matching. To alleviate this you may be able to leverage the no-loop rule attribute (depending on your setup). Otherwise you'll want to update your rule (or data in working memory) so that your rule is no longer valid.
Invalidate the rule
The other way to only trigger the rule once is to make the rule no longer valid to fire after it fires once. A trivial way to do this (likely not best practice in this case) would be to insert a flag into working memory and check on its presence. In this case I will use a simple string "DELINQUENT" as the flag.
rule "Profile - Delinquent"
when
not(String(this == "DELINQUENT"))
$c : CreditReportAll( $creditLiability : creditLiability )
$cs: CreditLiability( paymentPatternData.contains("X")) from $creditLiability
then
CreditUserSegment $cu = new CreditUserSegment();
$cu.setSegmentCode("delinquent");
$c.addUserSegmentToList($cu);
insert("DELINQUENT");
end
When the rule fires, it inserts a string that says "DELINQUENT" into working memory. The rule conditions are such that the rule only fires if this string doesn't exist in working memory. Thus after the first execution, the rule will not execute again unless a rule retracts that string.
This solution increases the memory footprint of the rule execution because there is more information in working memory. However unlike the other solution (which is more elegant), this version will not re-fire if another rule retriggers execution (eg. via update.)
We are just getting our heads around using Optaplanner for a project. We have a very simple solution setup as per following:
Job -> PlanningEntity, PlanningVariable=Resource from resourcesList
Resource -> POJO
Solution
- List<Job> PlanningEntityCollectionProperty
- List<Resource> ProblemFactCollectionProperty, resourcesList
We have setup some rules for testing. The first rule is simply to say, don't assign more than three Jobs to a Resource:
rule "noMoreThan3JobsPerResource"
when
$resource : Resource()
$totalJobsOnResource : Number(intValue > 3) from accumulate (
Job(
resource == $resource,
$count : 1),
sum($count)
)
then
scoreHolder.addHardConstraintMatch(kcontext, 3 - $totalJobsOnResource.intValue());
end
What we want to understand is HOW and WHEN the drools rules are evaluated. For example, if we add these two rules:
rule "logWhenResource"
when
$resource: Resource()
then
System.out.println("RESOURCE encountered");
end
rule "logWhenJob"
when
$job : Job()
then
System.out.println("JOB encountered");
end
We get "JOB encountered" in the log, but never "RESOURCE encountered". And yet, our first rule has $resource : Resource() in the when? Does optaplanner fire a rule when a job is placed (in our example)? We are just a bit unclear on why logWhenResource doesn't fire, but noMoreThan3JobsPerResource does (when they both try and 'match' a Resource object? Is Resource the resource that a job has been moved to?
Thanks in advance
After some discussions on IRC, (and a lot of patient help from Geoffrey!), hopefully the following will serve as a helper for other people.
1. Turn Logging on
First off, make sure you turn on trace logging for the Optaplanner package (and maybe turn it off for drools). This really helps as it shows exactly when optaplanner is triggering score calculations. It also shows the candidate score calculation:
Move index (0), score (-3init/-2hard/0medium/0soft), move (Job 7 {null -> Resource 1}).
in addition to the final step selection:
CH step (6), time spent (110), score (-3init/-2hard/0medium/0soft), selected move count (2), picked move (Job 7 {null -> Resource 1}).
You can also log in your "then" part of Rules, by doing something like:
LoggerFactory.getLogger("org.optaplanner").debug("...);
This makes sure it gets logged in the right order as Logging vs println can be asynchronous and things may not be in time ascending order.
2. Understand when Optaplanner calculates scores, and when it doesn't
This is a pretty useful summary of the 'event loop' of optaplanner:
doMove()
fireAllRules()
undoMove()
doMove()
fireAllRules()
undoMove()
doStep()
doMove()
fireAllRules()
undoMove() ...
etc. One thing that is interesting, as per our chat on IRC is the following:
"Notice that it doesn't do fireAllRules() after an undoMove or after doStep() because it can predict the score". Neat.
3. FULL_ASSERT
To check whether you are corrupting the score, turn on FULL_ASSERT.
<environmentMode>FULL_ASSERT</environmentMode>
This is useful to determine if your score calculation isn't right (ours wasn't).
Turn on TRACE logging. It fires all rules (that have changed since last time because it's incremental calculation) every time there's a move line in that log.
We are working on a monitoring application in which we follow the processing of a task in a set of applications.
We have a set of drools rules matching our needs but we have some performance issues (we may have easily up to 50k objects in session).
We are looking for best pactices
This question is about bloolean flag usage.
We are working to remove most of org.drools.core.rule.constraint.MvelConstraint: Exception jitting: ... warns.
We have often such warn on boolean flags.
for example in:
rule "PropagateDeprecation"
when
$parent:BaseChainStep( $parent.Deprecated )
$child:BaseChainStep( $parent.Id == $child.Parent, !$child.Deprecated )
then
modify($child){
setDeprecated(true)
}
end
we have warn on both $parent.Deprecated and !$child.Deprecated.
We would like to understand why there is such warn on boolean flags.
We would like to know also the impacts of the warn on composed conditions.
For example in:
rule "App1_TriggerExpected"
when $chainStep:App1ChainStep(
HasChain
, HasParent
, !$chainStep.Deprecated
, Status in ("error", "closed")
, Places != null
, Analysis != null)
then
..
end
if we have the warn on the first condition HasChain, how is resolved the when clause ?
Does other conditions are evaluated too (with iteration on all App1ChainStep objects) or some "index" are still used to help ?
If its matter, we are using flags as boolean (and not Boolean) to ensure false value as default.
Edit:
The problem may be linked to extended classes. In our use case we have something like:
declare BaseChainStep
parent : GUID
deprecated : boolean
end
declare App1ChainStep extends BaseChainStep
// specific App1 fields
end
BaseChainStep fields may be manipulated in rules using App1ChainStep objects or BaseChainStep objects.
rule "deprecateApp1"
when $app1:App1ChainStep( BusinessLogicCondition )
then
modify($app1) {
setDeprecated(true)
}
end
Then the deprecated flag is propagated to App1 children using "PropagateDeprecation" rule.
Boolean flag causing warn are declared in BaseChainStep class.
Although you are deviating from the conventional way of accessing attributes, this should not trigger the warning you have reported. I can't reproduce this using 6.3.0. You should add (a) the Drools version (b) the Java code for a class BaseChainStep with which the problem can be reproduced with the rule as shown.
This is another (much simpler) way of writing rules combining boolean attributes:
rule bool1
when
X( big, fast )
then
System.out.println( "big fast X" );
end
You may even use boolean operators:
rule bool2
when
X( big && ! fast )
then
System.out.println( "big slow X" );
end
Note the simple use of field name, assuming conventional naming, e.g., big for the field, isBig and setBig for the accessors.
I tried using facts to set the values for date-effective and date-expires in a rule similar to how salience can be set dynamically, but keep getting parser errors. Are these attributes fixed at rule parse time or can they be set dynamically?
I couldn't find any further hints, so I just wanted to check if anyone knows if this is possible?
This works for me for dynamic salience:
rule "my rule XYZ"
salience $priority
when
// condition facts
$rrd : RuleRuntimeData(ruleCode == "xyz", $priority : priority)
...
Is something like this possible? And if so what is the syntax?
rule "my rule XYZ"
date-expires $dateExpires
date-effective $dateEffective
salience $priority
when
$rrd : RuleRuntimeData(ruleCode == "xyz", $priority : priority, $dateExpires : endDate, $dateEffective : startDate)
...
(I might have to use date facts or an agendafilter instead of the date-effective and date-expires attributes if it is a value that has to be fixed at rule parse time)
It could be that setting these values dynamically would have some use cases. But the idea of defining a time where rules should fire from the data they are about to evaluate boggles my mind. Rule evaluation would have to be done, at least partly, to get at a fact T carrying a date/time for date-effective. If the time hasn't arrived yet it should probably fail. But then imagine another rule where another fact X must arrive before evaluation arrives at the fact T carrying a date/time for date-effective. If X arrives past the time given in T, then the evaluation of T will set the date/time to a time in the past and evaluation proceeds. In sum: technical details of LHS evaluation will influence whether a rule succeeds or fails.
Note that dynamic salience is completely different: it merely sets firing priority as an afterthought after successful evaluation.
An agenda filter sounds a very reasonable way to define a window of time for rules to be active.
I'm not so sure about "date facts", but who knows - I haven't seen your idea.
This is my code into an .rdl
rule "Fulfilment 24345"
when
$evPtr: LeftArmStretched($tsmp:time)
eval((3>$tsmp)==true)
then
System.err.println("$tsmp= "+$tsmp);
end
rule "set timestamp"
when
$las:LeftArmStretched();
then
System.out.println("//change timestamp!!");
$las.setTime(6);
end
If I run my example, 1st and 2nd rules fire and print:
//change timestamp!!
$tsmp= 6
but if $tsmp=3 the rule1 does not fire!!!! (3>6 false!)
If I manually write eval((3>6)==true) into rule1, the rule1 doesn't correctly fire!
A change to a fact object must be announced to the Rule Engine, i.e., use
modify( $las ){
setTime( 6 )
}
Also, do not rely on rule "set timestamp" being fired first. Use some additional constraint, or salience.