0

Solving an agent scheduling problem using PuLP.

8 hours of shift, 4 agents. I have to generate an output where the 8 hrs of shift is divided into 15-minute intervals. Hence, 32 total periods. (the 15 minute periods of an 8 hour shift is a fixed input, which is not allowed to be tweaked.) At any given period, there need to be a minimum of 3 agents working (i.e.not on break) Now, there needs to be a 1 hour meal break, and a 30 min short break. So, for 1 hour meal break, I will have to combine 4 periods of 15 mins, and for the 30 min short break, I'll have to combine 2 periods of 15 mins.

I tried getting 26 counts of 1... and, 6 counts of 0. The idea was to then combine 4 zeros together (meal break), and the remaining 2 zeros together (short break).

The current LpStatus is 'infeasible'... if i remove the constraint where i am trying to club the zeros, then the solution is optimal, or else it shows infeasible.

Have also pasted my final dataframe output screenshots.

import pandas as pd
import numpy as np
import scipy as sp
import seaborn as sns
import matplotlib.pyplot as plt
from pandas.plotting import table
import os, sys, json
from pulp import *
%matplotlib inline

# agent works either 1, 2, 3, 4, 5, 6, 7, 8 hours per week.
working_periods = [26]

# maximum periods that agent will work (7 hrs in each shift)
max_periods = 26

# planning_length
planning_length = 1  # TODO: Total number of shifts i.e. 21 in our case 

# Number of periods per shift:
daily_periods = [0, 1, 2, 3, 4, 5, 6, 7, 8,9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]

# Label the days from Monday to Sunday. 
s = ['Period']

# Create the required_agents dataframe
col_2 = range(0, 1*planning_length)

required_agents_per_period = pd.DataFrame(data = None, columns=s, index = daily_periods)

for j in col_2:
    # Small number is better for visualization.
    required_agents_per_period.iloc[0][j] = 3
    required_agents_per_period.iloc[1][j] = 3
    required_agents_per_period.iloc[2][j] = 3
    required_agents_per_period.iloc[3][j] = 3
    required_agents_per_period.iloc[4][j] = 3
    required_agents_per_period.iloc[5][j] = 3
    required_agents_per_period.iloc[6][j] = 3
    required_agents_per_period.iloc[7][j] = 3
    required_agents_per_period.iloc[8][j] = 3
    required_agents_per_period.iloc[9][j] = 3
    required_agents_per_period.iloc[10][j] = 3
    required_agents_per_period.iloc[11][j] = 3
    required_agents_per_period.iloc[12][j] = 3
    required_agents_per_period.iloc[13][j] = 3
    required_agents_per_period.iloc[14][j] = 3
    required_agents_per_period.iloc[15][j] = 3
    required_agents_per_period.iloc[16][j] = 3
    required_agents_per_period.iloc[17][j] = 3
    required_agents_per_period.iloc[18][j] = 3
    required_agents_per_period.iloc[19][j] = 3
    required_agents_per_period.iloc[20][j] = 3
    required_agents_per_period.iloc[21][j] = 3
    required_agents_per_period.iloc[22][j] = 3
    required_agents_per_period.iloc[23][j] = 3
    required_agents_per_period.iloc[24][j] = 3
    required_agents_per_period.iloc[25][j] = 3
    required_agents_per_period.iloc[26][j] = 3
    required_agents_per_period.iloc[27][j] = 3
    required_agents_per_period.iloc[28][j] = 3
    required_agents_per_period.iloc[29][j] = 3
    required_agents_per_period.iloc[30][j] = 3
    required_agents_per_period.iloc[31][j] = 3

# List of number of agents required in specific periods

r_p = required_agents_per_period.values.swapaxes(0,1).ravel()

print("The number of agents required for each period is: ")
print (r_p)
print("Total no. of periods is: ", len(r_p))

print ("\nIn matrix form:")
print (required_agents_per_period)


# Total number of the agents
total = 4   
print ("\nTotal number of agents are: {}".format(total))

# Create agents_id tag
agent_id_working_in_shift = ['agent7', 'agent10', 'agent13', 'agent18'] # TODO: Important: Here agent_id will be array of agents that will be extracted from dataframe. 
print ("\nThe agents are: ")
print (agent_id_working_in_shift)


# Total periods
periods = range(1*32)
agents_per_shift = range(total)

## Create shift names based on index:
period_name = []

for p in periods:
    period_name.append(s[0] + '_' + 'P' + str(p))
    
    
print("The periods are: ")
print(periods)

print("\nThe names of corresponding periods are: ")
print(period_name)

print("\nThe agents are: ")
print(agents_per_shift)



def LpProb():
    # The prob variable is created to contain the problem data   
    prob = LpProblem("Agents Meal Scheduling Per Shift",LpMinimize)

    # Creating the variables. 
    var = {
     (n, p): pulp.LpVariable(
        "schdule_{0}_{1}".format(n, p), cat = "Binary")
        for n in agents_per_shift for p in periods
    }


    # add constraints: 
    for n in agents_per_shift:
        for p in periods:
            prob.addConstraint(var[(n,p)] <= 1)

    
    

                
    
    # add constraints:
    # Exactly 32 working periods per shift 
    for n in agents_per_shift:
        prob.addConstraint(
            sum(var[(n,p)] for p in periods) == 26
        )
        

    for n in agents_per_shift:
        for p in periods:
            if(p == periods[-1] or p == periods[-2] or p == periods[-3]):
                continue
            prob.addConstraint(var[(n,p)] + var[(n,p+1)] + var[(n,p+2)] + var[(n,p+3)] == 0)
    
    # add constraints
    # for each shift, the numbers of working agents should be greater than or equal to
    # the required numbers of agents
    for p in periods:
        try:
            prob.addConstraint(
            sum(var[(n,p)] for n in agents_per_shift) >= 3
            )
        except:
            print("len(periods) should be equal to len(agents_per_shift)")
            sys.exit(-1)   
    
    prob.objective = sum(var[(n,p)] for n in agents_per_shift for p in periods)
    
    return var, prob


# Run the solver

var, prob = LpProb()
prob.solve()
print(LpStatus[prob.status])



def agent_scheduling(var = var):

    schedule = pd.DataFrame(data=None, index = agent_id_working_in_shift, columns = period_name)
    
    for k, v in var.items():
        n, p = k[0], k[1]
        schedule.iloc[n][p] = int(value(v)) # pulp.value()
        
    return schedule

schedule = agent_scheduling()

schedule.T

I was expecting an output of only zeros and one. Want to combine four 0's as meal break, that need to be consecutive, and then a two 0's consecutive for short break (remaining 26 cells should be 1's)

Also, only 1 agent can be on a break (0), the other 3 agents needs to be working in that period (1)

OUTPUTS:-

enter image description here

enter image description here

enter image description here

AshHammer
  • 1
  • 1

0 Answers0