The Task is the PlanningEntity, each task is in a group, groupId is a member of the Task class. What task is in what group is fixed, the groupId is neither PlanningVariable nor PlanningFact.
Each task has a duration, it's a shadow variable that is affected by the combination(optimization) of the tasks within the group. Let's call "the sum of task durations in a group" S. S1 for group1, S2 for group2 ...
The soft constraint is to minimize the largest S of all the groups. This is not to minimize all S, that's not the goal. I can only think of something like following which is not working. The "index" is the PlanningVariable.
rule "Minimize the largest group duration"
when
accumulate(
accumulate(
$g: Group(),
Task(index != null, groupId == g.getId(), $d: duration);
$s: sum($d)
)
$smax: max($s)
)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1, (-$smax));
end
I am not sure I entirely understand the issue, but something like this should get you the maximum duration:
Task(..., $d: duration)
not Task(..., duration > $d)
$d is now your maximum duration, and you can do anything you like with it.
(Note: You should add the drools tag and possibly also remove the optaplanner one, as this is barely related to OptaPlanner.)
Related
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).
Let's say we have the next example :
There are certain products that belong to certain product groups, and we want the total price summed up in an logical fact as either the products in the product group change or as their price changes.
private class ProductGroup {
private String name;
}
public class Product {
private ProductGroup productGroup;
private int price;
}
This is the class that will be intended for the logical facts that will get inserted by the summation rule in Drools.
private class ProductGroupTotalPrice {
private ProductGroup productGroup;
private int totalPrice;
}
There is a rule that sums up the total price for a given ProductGroup.
rule "total price for product group"
when
$productGroup : ProductGroup()
$totalPrice : Number() from accumulate(
Product(productGroup == $productGroup, $price : price),
sum($price)
)
then
insertLogical(new ProductGroupTotalPrice($productGroup, $totalPrice));
end
So my question is what will the logic be when Products from a given ProductGroup are added/deleted from the working memory, they change the ProductGroup or their price is being changed?
- Lets say that the summation is done at the beggining of the application based on the current state and the logical fact is inserted into the working memory with the total price. Then the price for one Product is changed at one point so the totalPrice needs to be updated.
Here are three cases how the process would possibly be done :
Incrementally with doing a constant time calculation. Only take into account the change that has happened and subtract the old price from the total and add the new one for the one Product that was changed. (Excelent)
The whole summation is done again but the Product instances that meet the criteria(that are from the given ProductGroup) are already known, they are not searched for. (Good)
Besides the summation a loop through all the Product instances in the working memory is done to see which ones meet the criteria(that are from the given ProductGroup). (Bad)
Is the logic that is implemented one of these three cases or it is something else?
You can look at the documentation of the other form of accumulate, i.e., the one where you can define the steps for initialization, processings (note the plural!) and returning an arbitrary function. Some functions permit the reverse operation so that removing a fact that has been used for computing the function result can be handled: e.g., 'sum'. (But compare 'max'.)
So I think that your accumulate pattern will be updated efficiently.
However, I think that this does not mean that your logically inserted ProductGroupTotalPrice will be updated. (Try it, I may be wrong.)
I would use a simple rule
rule "total price for product group"
when
$productGroup: ProductGroup()
Number( $totalPrice: intValue ) from accumulate(
Product(productGroup == $productGroup, $price : price),
sum($price)
)
$pgtp: ProductGroupTotalPrice( productGroup == $productGroup,
totalPrice != $totalPrice )
then
modify( $pgtp ){ setTotalPrice( $totalPrice ) }
end
and an addition rule to insert an initial ProductGroupTotalPrice for the product group with totalPrice 0.
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 to create accumulate function condition on Decision table. Please help me that How to create on Decision table.
My accumulate rule function is
when
$i : Double(doubleValue > 1000 ) from accumulate( Product($productQty:quantity),sum($productQty))
then
System.out.println( "The quantity is exceeded more than 1000 and the total value is " + $i );
You can create a column
rows
n condition
n+1 $i : Double() from accumulate( Product($productQty:quantity),sum($productQty))
n+2 doubleValue > $param
n+3 add quantitities and check
n+4 1000
Two comments.
This is not well-suited for decision tables unless you plan to check for different ranges of the accumulated value.
Why do you use double for counting what is, most likely, integral quantities? I'd be surprise if the accumulated stock exceeds Integer.MAX_VALUE. In short: use Number in the pattern and intValue in the constraint.
I am making a timetabling program which does one to one matches from SubjectTeacherPeriod (planning entity) to Period. There comes a case when I need to: "for y periods, atleast x of the SubjectTeacherPeriod must match a match_condition"
For example, I want to constrain 3 particular periods, atleast two of them to be taught by teachers who match to asst prof.
Here is the data structure holding such a constraint:
Class XOfYPeriods
SomeType match_condition
int x
List<Period> Periods //problem
SubjectTeacherPeriod has a Period, of course
class SubjectTeacherPeriod
int id
SomeType attrib
Period period
How do I write a rule that evaluates individual Periods from a list to check if x number of SubjectTeacherPeriods that are allocated those Periods meet the match condition?
Do correct me if I am defining my classes in bad form.
For the sake of example, here is a statement to be evaluated to determine a match: eval(matches($stp_attrib,$match_condition))
Sorry for the use of Pseudocode if it confused more than clarified. The SomeType is actually List< String> and thus the match condition is checked with a Collections.disjoint
I will give it a try, but not sure I completely understand your problem statement:
rule "X of Y Periods"
when
$c : XOfYPeriods( )
$list : List( size > $c.x ) from
accumulate( $stp : SubjectTeacherPeriod( matches(attrib, $c.match_condition),
period memberOf $c.periods ),
collectList( $stp ) )
then
// $list of STP that match the condition and
// whose period matches one of the periods in the list
end
Hope it helps.