I'm new to constraint programming. Now I try to build weekly schedules to manage employee work time and to meet the labor demand. I am using ILOG CPLEX and try to fix the problem. I think this is an easy problem but I can't wrap my head around it. Here's the problem:
• We have multiple workers (i.e.47 workers), each with some constraints (let's say day min work hours, day max work hours, week min work hours, week max work hours)
• Each worker has at least one skill to do a task, with certain level
• We have some tasks (i.e. 10 tasks) every day, each day is split to 48 slots(0~47). a half hour one slot.
• A worker can only work on one task at the same slot (a half hour)
• A task at one slot has a labor demand (may be 2, means: need two workers to do this task at this slot), a min demand(may be 1), and a max demand(may be 10).
How do we assign the tasks to the workers to meet the demand and minimize the number of workers used and assign workers with higher level to do a task?
I'm just learning CP & ILOG CPLEX for two weeks. Any ideas on how to a constraint (i.e. day min work hours)? I write some code as below:
CP cp = new CP();
//objective
INumExpr skillMatrixSum = cp.IntExpr();
//day min work hours and so on
IList<SMAIlogStaffInfo> listStaffInfo = SMAScheduleByILogStaffInfoBusiness.GetStaffInfoList(unitId, startDate, endDate, connectionString);
//skill level
IList<SMAIlogPersonSkillMatrixInfo> listPersonSkillMatrixInfo = SMAScheduleByILogSkillMatrixInfoBusiness.GetPersonSkillMatrixInfoList(unitId, startDate, endDate, connectionString);
//demand for every half hour
IList<SMAIlogDemandInfo> listDemandInfo = SMAScheduleByILogDemandInfoBusiness.GetDemandInfoList(unitId, startDate, endDate, connectionString);
//staff unavailability (i.e. ask for a leave)
IList<SMAIlogUnvailInfo> listUnvailInfo = SMAScheduleByILogUnvailInfoBusiness.GetUnvailInfoList(unitId, startDate, endDate, connectionString);
int nbWorkers = listStaffInfo.Select(t => t.PersonId).Distinct().Count();
IList<string> listPerson = listStaffInfo.Select(t => t.PersonId).ToList();
//possible tasks by skill
List<IIntervalVar> allTasks = new List<IIntervalVar>();
List<IIntervalVar>[] workerTasks = new List<IIntervalVar>[nbWorkers];
for (int w = 0; w < nbWorkers; w++)
{
workerTasks[w] = new List<IIntervalVar>();
}
//schedule by day from sunday to saturday
for (int weekdayIndex = 0; weekdayIndex < 7; weekdayIndex++)
{
IList<SMAIlogDemandInfo> listWeekdayDemandInfo = listDemandInfo.Where(t => t.Weekday == weekdayIndex).ToList();
IList<SMAIlogUnvailInfo> listWeekdayUnvailInfo = listUnvailInfo.Where(t => t.Weekday == weekdayIndex).ToList();
SetScheduleByDay(cp, skillMatrixSum, allTasks, workerTasks, listWeekdayDemandInfo, listWeekdayUnvailInfo, listStaffInfo, listPersonSkillMatrixInfo);
}
//constraint: one worker can not do two tasks at the same time
for (int w = 0; w < nbWorkers; w++)
{
IIntervalSequenceVar seq = cp.IntervalSequenceVar(workerTasks[w].ToArray(), listPerson[w]);
cp.Add(cp.NoOverlap(seq));
}
//Minimize skill Matrix
cp.Add(cp.Minimize(skillMatrixSum));
public static void SetScheduleByDay(CP cp, INumExpr skillMatrixSum, List<IIntervalVar> allTasks, List<IIntervalVar>[] workerTasks,
IList<SMAIlogDemandInfo> listWeekdayDemandInfo, IList<SMAIlogUnvailInfo> listWeekdayUnvailInfo, IList<SMAIlogStaffInfo> listStaffInfo, IList<SMAIlogPersonSkillMatrixInfo> listPersonSkillMatrixInfo)
{
int weekdayIndex = listWeekdayDemandInfo.Select(t => t.Weekday).FirstOrDefault();
int skillCount = listWeekdayDemandInfo.Select(t => t.SkillId).Distinct().Count(); //task count
int nbTasks = listWeekdayDemandInfo.Count(); //weekday demand
int nbWorkers = listStaffInfo.Count();//workers count
//decision variables
IIntervalVar[] tasks = new IIntervalVar[nbTasks];
IIntervalVar[,] taskMatrix = new IIntervalVar[nbTasks, nbWorkers];
ICumulFunctionExpr[] arrayResourcesWorkHours = new ICumulFunctionExpr[nbWorkers];
for (int j = 0; j < nbWorkers; j++)
{
arrayResourcesWorkHours[j] = cp.CumulFunctionExpr();
}
string taskFullName;
string skillId = "";
string personId = "";
int levelId = 0;
int demand = 0;
int slot = 0;
for (int i = 0; i < nbTasks; i++)
{
slot = listWeekdayDemandInfo[i].Slot;
demand = listWeekdayDemandInfo[i].Demand;
if (demand == 0)
{
continue;
}
skillId = listWeekdayDemandInfo[i].SkillId;
List<IIntervalVar> alttasks = new List<IIntervalVar>();
for (int w = 0; w < listStaffInfo.Count(); w++)
{
personId = listStaffInfo[w].PersonId;
levelId = listPersonSkillMatrixInfo.Where(t => t.SkillId == skillId && t.PersonId == personId).ToList().ToList().FirstOrDefault().LevelId;
//this worker can do this task
if (levelId > 0)
{
taskFullName = personId + "-" + weekdayIndex + "-" + slot + "-" + skillId;
IIntervalVar wtask = cp.IntervalVar(1, taskFullName);
wtask.SetOptional(); // SetOptional
alttasks.Add(wtask);
taskMatrix[i, w] = wtask;
workerTasks[w].Add(wtask);
allTasks.Add(wtask);
listStaffInfo[w].listWorkerTasks.Add(wtask);
skillMatrixSum = cp.Sum(skillMatrixSum, cp.Prod(levelId, cp.PresenceOf(wtask)));
}
}
cp.Add(cp.Alternative(tasks[i], alttasks.ToArray()));
}
//here is my problem. the way to generalize cumulative is wrong (DayMin and DayMax,also unavailability ).because tasks is Set Optional
for (int j = 0; j < listStaffInfo.Count; j++)
{
IList<IIntervalVar> listWorkerTasks = listStaffInfo[j].listWorkerTasks;
if (listWorkerTasks.Count == 0)
{
continue;
}
int dayMin = listStaffInfo[j].DayMin;
int dayMax = listStaffInfo[j].DayMax;
IConstraint dayMinConstraint = cp.Ge(arrayResourcesWorkHours[j], dayMin);
cp.Add(dayMinConstraint);
IConstraint dayMaxConstraint = cp.Le(arrayResourcesWorkHours[j], dayMax);
cp.Add(dayMaxConstraint);
INumToNumStepFunction availablityNumStep = cp.NumToNumStepFunction();
IList<SMAIlogUnvailInfo> listWeekdayPersonUnvailInfo = listWeekdayUnvailInfo.Where(t => t.PersonId == personId).ToList();
if (listWeekdayPersonUnvailInfo.Count > 0)
{
for (int k = 0; k < listWeekdayPersonUnvailInfo.Count; k++)
{
int startSlot = listWeekdayPersonUnvailInfo[k].StartSlot;
int endSlot = listWeekdayPersonUnvailInfo[k].EndSlot;
for (int i = 0; i < listWorkerTasks.Count; i++)
{
while (startSlot <= endSlot)
{
availablityNumStep.SetValue(startSlot, startSlot + 1, 0);
//cp.Add(cp.ForbidExtent(listWorkerTasks[i], availablityNumStep));
cp.Add(cp.ForbidStart(listWorkerTasks[i], availablityNumStep));
cp.Add(cp.ForbidEnd(listWorkerTasks[i], availablityNumStep));
startSlot++;
}
}
}
}
}
}
I have a set of input data as below:
and also I have a example of out data as below: Schedule Output Data