I have an employee rostering application. Some of the rules are quite similar as in the Nurse Rostering example.
In the last two months I had to convert around 30 rules written in DRL to ConstraintSteams. After struggeling at the beginning I started to like it more and more. At the end I really liked it. Thanks a lot for this awesome work!
I want to mention and ask a few things:
ConsecutiveWorkingDays: Here I use the
org.optaplanner.examples.common.experimental.ExperimentalConstraintCollectorsto solve it. It works perfect. But comparing to the most other rules (I benchmarked the one by one as described in the documentation), it performes not that good (12,150/s), comparing to dayOffRequest (27,384/s) or fairDistributionGroup (21,864/s). But I imagine thats the complexity of the problem.Consecutive Working Days 2: What is the best way to include
org.optaplanner.examples.common.experimental.ExperimentalConstraintCollectorsand these classes in the project? I copied them from the example to the Project.SleepTime: The law in Switzerland has several rules for how much sleep time are legal. The description here, is minimal simplified:
- Less then 8 hours sleep is allways illegal
- Twice less the 11 hours sleep in one week is also allways illegal
- Once less then eleven hours sleep is illegal, if the average sleep time in the week is less then 11 hours.
With the drl I did an
InsertLogicalofFreetime-Objects. A Freetime starts at the end of the last shift of a day and ends with the beginning of the next shift, wherenexShift.dayIndex > firstShift.DayIndex. This calculation is quite intensive, because an employee can have more then one shift per day. TheseFreetime-ObjectsI also used to calculate the rule TwoDaysOfInARowPerWeek. With the ConstraintStreams the selection of the relevant shifts is done for every of the four rules. This decreased the calculation speed quite a lot. With the drl it was between 3000/s and 4000/s. With ConstraintStreams it decreased to 1500/s to 2000/s. Now I managed to do all the sleep time rules in one rule, so the selection of the shifts has not to be done 4 times, but only 2 times. And now the speed is okay (2700/s to 3500/s). But still, there is no way to do something likeInsertLogical? Or what alternatives are there? Here the code, how I select this shifts:private BiConstraintStream<Shift, Shift> getEmployeeFreetimeRelevantShifts(ConstraintFactory constraintFactory) { // Shift (1): Employee works on the Shift return constraintFactory.forEach(Shift.class) .filter(shift -> shift.isNotEmptyShift() && shift.getHelperShiftForFreetime() != "last") // No later Shift on the same day as Shift (1) .ifNotExistsOther( Shift.class, Joiners.equal(Shift::getEmployee), Joiners.equal(Shift::getDayIndex), Joiners.filtering((shift_1, shift_2)-> shift_2.isNotHelperShiftForFreetime()), Joiners.filtering((shift_1, later_shift_on_same_day)-> shift_1.getEndDatetimeInMinutesOfDay() < later_shift_on_same_day.getEndDatetimeInMinutesOfDay()) ) // Shift (2) a shift on a later day than shift 1 .join( Shift.class, Joiners.equal(Shift::getEmployee), Joiners.filtering((shift_1, shift_2)-> shift_2.getHelperShiftForFreetime() != "first"), Joiners.filtering((shift_1, shift_2)-> shift_1.getDayIndex() < shift_2.getDayIndex()) ) // There is no shift after Shift 2 on a earlier day between 1 and 2 .ifNotExists( Shift.class, Joiners.equal((shift_1, shift_2)-> shift_1.getEmployee().getId(), not_existing_shift -> not_existing_shift.getEmployee().getId()), Joiners.filtering((shift_1, shift_2, shift_between_1_and_2 )-> { return shift_between_1_and_2.isNotHelperShiftForFreetime(); }), Joiners.filtering((shift_1, shift_2, shift_between_1_and_2 )-> { return shift_between_1_and_2.getDayIndex() > shift_1.getDayIndex() && shift_between_1_and_2.getDayIndex() < shift_2.getDayIndex(); }) ) // and there is no earlier shift on the same day before Shift 2 .ifNotExists( Shift.class, Joiners.equal((shift_1, shift_2)-> shift_1.getEmployee().getId(), not_existing_shift -> not_existing_shift.getEmployee().getId()), Joiners.equal((shift_1, shift_2)-> shift_2.getDayIndex(), not_existing_shift -> not_existing_shift.getDayIndex()), Joiners.filtering((shift_1, shift_2, not_existing_shift_before_shift_2 )-> { return not_existing_shift_before_shift_2.isNotHelperShiftForFreetime(); }), Joiners.filtering((shift_1, shift_2, not_existing_shift_before_shift_2 )-> { return shift_2.getStartDatetimeInMinutesOfDay() > not_existing_shift_before_shift_2.getStartDatetimeInMinutesOfDay(); }) ); }Execution of Rules with 0 score: In my case a user can select which rules he wants to be executed, if it should be a hard or a soft constraint and he can change the penalty value. I solve this with a
@ConstraintConfiguration. But as far as I can see, also the rules with a penalty value of 0 are executed (but not penaltized). So if I disable all rules except of one rule, the speed is not higher then when I select all rules. is that correct? And is there a possibility to do that in a different way?
Again, thanks a lot for this awesome project!