0

The situation is as follows: I am creating a daily schedule for a workplace. Each day is divided into timeslots, and at each timeslot I know how many employees must be present. The schedule is created using two integer decision variables that describe the arrival and departure timeslots of each employee.

Currently, I use an extra variable to tell whether employee i is at work at time t, and then I sum them over employees at each timeslot to compare with the requirement. My code can be boiled down to the following:

using CP;

tuple TimeSlot {
    key int id;
    int minEmploy;
}
{TimeSlot} TSlots = ...;
{int} timeSlots = {t.id|t in TSlots};
int tMax = max(t in timeSlots) t;
range dayRange = 0..tMax;

range allEmployees = 1..10;

dvar int dayStart[allEmployees] in dayRange;
dvar int dayEnd[allEmployees] in dayRange;
dvar int workTimeT[allEmployees][timeSlots] in 0..1;

minimize ...;

subject to {
    /*Indicator constraints*/
    forall(i in allEmployees){
        forall(t in timeSlots:t>0){
            dayStart[i] <= t && t <= dayEnd[i] => workTimeT[i][t] == 1;
            dayEnd[i] < t || t < dayStart[i] => workTimeT[i][t] == 0;
        }
    } 
    /*Must satisfy requirement*/
    forall(t in timeSlots:t>0){
        sum(i in allEmployees) workTimeT[i][t] >= item(TSlots,<t>).minEmploy;
    }
}

Is there any way to get around this extra variable? It can't possibly be efficient to add #employees times #timeslots variables just to check if a number is between two decision variables.

TracedWill
  • 13
  • 7

1 Answers1

0

I would write this as the inverse of what you did - I would dump the integer variables (dayStart[i] and dayEnd[i]) and replace them with booleans (dayStart[i][t] and dayStart[i][t]). CPLEX and other solvers are pretty good at working with sets of booleans like these, and in spite of there being more variables, it might actually solver faster than what you're attempting.

It also makes it easier to find out how many people are working - it's the number of people who have started work minus the number who have finished work (assuming no wrap around graveyard shift, where you start late on one day and end the next morning, which your formulation does seem to assume will never happen).

dvar int dayStart[allEmployees][timeSlots] in 0..1;
dvar int dayEnd[allEmployees][timeSlots] in 0..1;

forall(i in allEmployees) {
    /* Every employee starts exactly once */
    sum(t in timeSlots) dayStart[i][t] == 1;

    /* Every employee stops exactly once */
    sum(t in timeSlots) dayEnd[i][t] == 1;

   /* You must start before you stop */
   forall(t in timeSlots) dayEnd[i][t] <= sum(tstart in timeSlots: tstart < t) dayStart[i][tstart];
}

/* the number of employees working is the number who have started minus
   the number who have stopped */
dexpr int NumWorking[t in timeSlots] = 
    sum(tstart in timeSlots: tstart <= t) dayStart[i][tstart] 
    -
    sum(tend in timeSlots: tend < t) dayEnd[i][tend];

/* Make sure we've got enough people working */
forall(t in timeSlots)
    NumWorking[t] >= item(TSlots,<t>).minEmploy;

If you really want the integer start and end times, you can easily write it in terms of the booleans:

dvar int+ employeeStartTime[allEmployees];
dvar int+ employeeEndTime[allEmployees];

forall(i in allEmployees) {
    employeeStartTime == sum(t in timeSlots) t*dayStart[i][t];
    employeeEndTime == sum(t in timeSlots) t*dayEnd[i][t];
}
Darryl
  • 5,907
  • 1
  • 25
  • 36