I am adding a custom rule for Employee shift where i have 4 types of shifts and in one type of shift the number of female employees have to be fix
i have tried adding a field in shift class namely requiredFemalesEmployees which is set to 1
//hard constraint
rule "OneFemaleInShiftA"
when
$gender:Employee(gender=="F")
$rfe:Shift(requiredFemalesEmployees==1)
accumulate(
$a:ShiftAssignment(employee==$gender,$shift:shift.requiredFemalesEmployees),
$total :count($a)
)
then
if($total.intValue()!=1){
scoreHolder.addHardConstraintMatch(kcontext, - 1);
}
end
any suggestions will be a great help.
First, you created a variable named $rfe, but is unused, and in this line:
$a:ShiftAssignment(employee==$gender,$shift:shift.requiredFemalesEmployees), what'r you assigning to $shift ?
This is my example:
rule "oneFemaleInShift"
when
$gender:Employee(gender=="F")
$rfe:Shift(requiredFemalesEmployees==1)
Number(intValue!=1) from accumulate(
$a:ShiftAssignment(employee==$gender, ¿¿$shift:shift.requiredFemalesEmployees??),
count($a)
)
then
scoreHolder.addHardConstraintMatch(kcontext, - 1);
We need the domain model or the source of java POJOs to know the relations between them.
I think this will help you:
rule "oneFemaleInShift"
when
$femaleEmployee:Employee(gender=="F") //GET FEMALE POJOS
$rfe:Shift(requiredFemalesEmployees==1) // GET SHIFT WHERE FEMALE IS REQUIRED
Number(intValue > 0) from accumulate( //COUNT NUMBER OF FEMALE EMPLOYEES IN THAT SHIFT, PENALIZE SOLUTION WHERE THERE ARE LESS THAN 1
$a:ShiftAssignment(employee==$femaleEmployee, shift==$rfe),
count($a)
)
then
scoreHolder.addHardConstraintMatch(kcontext, - 1); // LOOK AT THE VALUE OF HARD SCORE, PROPORTION WITH OTHER HARD CONSTRAINT
Related
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
I understand the basics of writing drools rules now but i can't seem to understand in the examples that i've seen (optaplanner), there are comparisons of IDs. Is this necessary? Why is it there?
// RoomOccupancy: Two lectures in the same room at the same period.
// Any extra lecture in the same period and room counts as one more violation.
rule "roomOccupancy"
when
Lecture($leftId : id, period != null, $period : period, room != null, $room : room)
// $leftLecture has lowest id of the period+room combo
not Lecture(period == $period, room == $room, id < $leftId)
// rightLecture has the same period
Lecture(period == $period, room == $room, id > $leftId, $rightId : id)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
From my understanding deleting the line with not Lecture(.. and leaving Lecture(period == $period, room == $room) should do the trick. Is my understanding correct or am I missing some use cases here?
You should understand that a pattern such as
$a: Lecture()
$b: Lecture()
with two Lecture facts A an B in the system will produce the following matches and firings:
$a-A, $b-B (1)
$a-B, $b-A (2)
$a-A, $b-A
$a-B, $b-B
Therefore, to reduce the unwanted combinations you need have a way to ascertain to have not identical facts matching (bound to) $a and $b:
$a: Lecture( $ida: id )
$b: Lecture( $idb: id != $ida )
However, using not equal still produces combinations (1) and (2).
Given 2 queens A and B, the id comparison in the "no 2 queens on the same horizontal row" constraint makes sure that we only match A-B and not B-A, A-A and B-B.
Same principle for lectures.
I'm currently reading the course curriculum example of optaplanner and I can't seem to understand this:
rule "conflictingLecturesSameCourseInSamePeriod"
when
// line 1
Lecture($leftId : id, $leftCourse : course, $period : period, period != null)
// line 2
Lecture(course == $leftCourse, period == $period, id > $leftId)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
Questions are:
- What's the difference between the Lecture() in line 1 and the Lecture in line 2?
- I understand the variable assignements are happening in Line 1 but in Line 2, what's the difference between course and $leftCourse, period and $period and id and $leftId
So far, I can't seem to find any explanation in the documentation
A binding such as $leftCourse : course establishes $leftCourse as a variable that refers to the field course of a Lecture object.
The lecture in line 1 is identified by its id; the one in line 2 has an id that is greater. Assuming that id values are primary keys, this combination would basically match all possible unordered pairs of Lectures, but ...
... the constraint course == $leftCourse restricts the pairings to those of identical courses and ...
... the constraint period == $period furthermore restricts it to equal (non-null) periods.
In other words, the planning rules out an assignment for two different Lectures for the same course in the same period.
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.