0
votes

I am trying to configure my PuLP problem to ensure an employee does not have more than 10 hours per day.

The employee variable I have set up is:

cost = []
vars_by_shift = defaultdict(list)

for employee, info in employees.iterrows():
    for shift in info['availability']:
        employee_var = pl.LpVariable("%s_%s" % (employee, shift), 0, 1, pl.LpInteger)
        vars_by_shift[shift].append(employee_var)
        cost.append(employee_var * info['base_rate'])

My objective is to minimize cost:

prob = pl.LpProblem("scheduling", pl.LpMinimize)
prob += sum(cost)

An example of my shift data is:

"76749": {
    "start_date": "2019-08-14",
    "start_time": "08:00",
    "end_date": "2019-08-14",
    "end_time": "12:00",
    "duration": 4,
    "number_positions": 1
},
"76750": {
    "start_date": "2019-08-14",
    "start_time": "13:00",
    "end_date": "2019-08-14",
    "end_time": "20:00",
    "duration": 7,
    "number_positions": 1
}

An employee sometimes can be assign two short shifts on the same day. I want to ensure the total hours an employee is rostered any given day does not exceed 10 hours. How would model that constraint?

2
There are different ways to model this depending on the situation. Sometimes we can enumerate all possible shift combinations an employee can work and then pick in the model at most one shift combination. You can also just use all shifts and just prohibit shift combinations that are not allowed. You can also model the shift combination inside the optimization model, but that often requires non-trivial constraints. Finally, often column generation is being used for these type of models. That again requires careful modeling of what is allowed as a shift combination. - Erwin Kalvelagen

2 Answers

1
votes

If I understand your implementation you have a set of binary decision variables:

pl[e, s]

With one variable for each e in employees and for each s in shifts

I'm also assuming there is (or you can easily create) a list days which includes the list of days covered by the shifts, and you can easily write a function which returns the number of hours of a shift in a particular day.

You then want to add constraints:

for e in employees:
    for d in days:
        lpSum([pl[e, s]*n_hours_of_shift_in_day(s, d) for s in shifts]) <= 10.0

Where the function n_hours_of_shift_in_day(s, d) is a function which returns the number of hours of shift s in day d, so for example if your shift was:

"76749": {
    "start_date": "2019-08-14",
    "start_time": "18:00",
    "end_date": "2019-08-15",
    "end_time": "19:00",
    "duration": 25,
    "number_positions": 1
}

Then n_hours_of_shift_in_day("76749", "2019-08-14") would return 5.0, and n_hours_of_shift_in_day("76749", "2019-08-15") would return 19.0.

Also your example shift seems to use a 12-hour clock format with no indication of AM or PM which might give you some problems.

0
votes

well, you need a grouping variable for the hours, is it considered the start_date as the day you dont want to assign more than 10 hours?

if the answer is yes

then you need sth like this....

emp in employees.iterrows():
    for d in dates:
        prob.addConstrain(pl.lpSum([ employee_var*vars_by_shift[s][hours] if vars_by_shift[s]==d else 0 for s in shifts]) < 10)