In Drools, what does it mean to compare IDs - drools

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.

Related

OptaPlanner: Drools rule on consecutive shift assignments

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

Optaplanner Curriculum example, Explanation for curriculumCourseScoreRules.drl

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.

Latest n events, matching a certain pattern

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.

Drools accumulate method that returns zero if no matching source fact

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.

To satisfy x out of y constraints

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.