Search code examples
javaoptaplannerdomain-model

PlanningEntities split across multiple collections


I am writing a staff scheduler for my office. There are at least two shifts per day (7 days a week) and I want the optimizer to make sure no one staff member works drastically more weekend shifts than another.

I have a simple working program that assigns one Staff to each Shift. The program structure is as follows:

  • SchedulingSolution is the @PlanningSolution
  • SchedulingSolution contains a List<Shift> which is the @PlanningEntityCollectionProperty
  • Shift is the @PlanningEntity
  • Shift contains a Staff which is the @PlanningVariable
  • SchedulingSolution contains a @ValueRangeProvider which returns our staff roster as a List<Staff>.

This working solution schedules all staff equally but does not consider weekends and weekdays. To delineate weekdays from weekends, I have replaced the List<Shift> in SchedulingSolution with a List<Day>. Every Day contains its own List<Shift> representing the shifts that occur on that day. Now when I want to compute the number of weekends a staff member has worked, I can find all Day objects that represent weekends and count only the Shifts contained in those days.

Unfortunately, this places the List<Shift> which is the @PlanningEntityCollectionProperty in a class that is not a @PlanningSolution. The @PlanningEntityCollectionProperty annotation is now ignored and the program fails.

Have I missed an obvious way to restructure my program, or is my only option to keep my original program structure and modify my shift objects to record which day they occur on?

Thanks in advance. I'll try to pass the help forward if I can.


Solution

  • I would restructure your Planning Solution.

    Why don't you do:

    • Keep the List<Shift> in the Planning Solution
    • Discard the List<Day> in every shift
    • Add a field day in Shift, possibly even an indicator whether that day is a weekend using an enum.

    You can then easily calculate the number of weekend days worked by each staff with Drools as so:

    (Absolutely untested!!!)

    rule "balancedStaffWeekend"
        when
            $staff: Staff()
            accumulate(
                Shift(
                    staff == $staff
                    day == Day.WEEKEND);
                    $count: count()
                );
            )
        then
            scoreHolder.addSoftConstraintMatch(kcontext, -Math.pow(count, 2));
    end
    

    Penalising the solution by counts raised to the power of two balances the number of weekends.