Home > Enterprise >  Finding ideal weekly staffing levels, while maintaining inventory levels using PuLP
Finding ideal weekly staffing levels, while maintaining inventory levels using PuLP

Time:11-04

I'm trying to solve for the minimal weekly manufacturing staffing levels based on our weekly inventory deliveries.

import numpy as np
import pulp as p

inventory_delivery = np.array([200, 500, 0, 900, 0]) # deliveries over 5 weeks
productivity = 1  # using 1 to keep it simple for now. This varies week by week.
prob = p.LpProblem('Decision', p.LpMinimize)

a = list()
for x in inventory_delivery:
    a.append(p.LpVariable(f'a{len(a)}', 0, None))

# objective
output = sum(a * productivity for a in a)  # number of workers * weekly productivity
prob  = output

# constraints
prob  = output == sum(inventory_delivery)  # Need total output by the end to equal total deliveries
#### How do I add a constraint that says my weekly output cannot exceed the inventory I have on hand in a given week? 
#### (i.e., Cumulative Inventory - Cumulative Output >= 0 every week)

status = prob.solve()
print(p.LpStatus[status])
print("Objective value:", p.value(prob.objective))
print ('\nThe values of the variables : \n')
for v in prob.variables():
    print(v.name, "=", v.varValue)

Right now it solves by front-loading staffing in a0, when I want it spread across all 5 weeks based on my inventory.

Current Output:

Optimal
Objective value: 1600.0

The values of the variables : 

a0 = 1600.0
a1 = 0.0
a2 = 0.0
a3 = 0.0
a4 = 0.0

EDIT: Forgot to mention that the staffing in a can only stay the same or increase week-over-week; can never decrease. So I'm trying to solve for the lowest total staffing over the course of those 5 weeks.

CodePudding user response:

Reading between the lines a bit, I think the below is about what you are looking for. A couple of key concepts that will help you:

  • It is generally good to set up your variables to be indexed by a set (or list in this case). That allows you to use easy-to-read loops to make constraints "for each week" in your model
  • You probably want to hire the minimal number of people, but without further guidance (constraints) that would leave you with an answer of (0, 0, 0, 0, 1600). So one way to handle that is to minimize the "peak" staff level (shown). There is no "cost" to hire, so without the tiny penalty that I put in the obj function, the model might just as well hire this peak in all periods (remove the penalty and see...)
  • As suggested in the comments, you'll need additional variables to account for the production and rolling inventory, and constraints (shown) to manage those variables.
  • Right now, your model is naturally integral... Meaning the solution is a natural integer. If you tweak production or such, you might get fractional hires. If that is the case, you can change the category of the staff_level to integer, which will slow the model a bit, but nothing noticeable in this small of a model.
  • Avoid numpy when able here. It just makes things more complicated and adds no acceleration in these types of models.

Code:

import pulp as p

# DATA
deliveries = (200, 500, 0, 0, 900, 0) # deliveries over 5 weeks
productivity = 1  # using 1 to keep it simple for now. This varies week by week.
weeks = list(range(len(deliveries)))

# LP Problem setup
prob = p.LpProblem('Decision', p.LpMinimize)

# VARS
# your life will be easier if you index the variables with lists/sets, like "weeks"...
staff_level = p.LpVariable.dicts('staff', weeks, lowBound=0, cat='Continuous')
inventory   = p.LpVariable.dicts('inventory', weeks, lowBound=0, cat='Continuous')
production  = p.LpVariable.dicts('production', weeks, lowBound=0, cat='Continuous')
peak_staff_level = p.LpVariable('peak_staff')


# OBJECTIVE
# minimize the "peak" staff level and include a tiny penalty for staff totals to prevent
# unnecessary hires
prob  = peak_staff_level   .01 * sum(staff_level[week] for week in weeks) 

# constraints
prob  = sum(production[week] for week in weeks) == sum(deliveries)  # Need total production by the end to equal total deliveries

# an example of a week-based constraint for staff 
for week in weeks[1:]:  # start with the 2nd week...
    prob  = staff_level[week] >= staff_level[week - 1]

# an example to keep track of inventory
# first week is special case:
prob  = inventory[0] == deliveries[0]
# remainder of weeks are rolling sum...
for week in weeks[1:]:
    prob  = inventory[week] == inventory[week - 1] - production[week - 1]   deliveries[week]

# now we need to limit the production to inventory on hand, for each week
# and limit to staff productivity...  We can combine this in one loop, or break it up.
# either way is fine
for week in weeks:
    prob  = production[week] <= inventory[week]
    prob  = production[week] <= staff_level[week] * productivity

# we cannot use max() in an LP, so we must use a constraint to tie the max level to each week's staff level
for week in weeks:
    prob  = peak_staff_level >= staff_level[week]



#### How do I add a constraint that says my weekly production cannot exceed the inventory I have on hand in a given week? 
#### (i.e., Cumulative Inventory - Cumulative Output >= 0 every week)

# inspect the model construction:
print(prob)


status = prob.solve()
print(p.LpStatus[status])
print("Objective value:", p.value(prob.objective))
print ('\nThe values of the variables : \n')
for v in prob.variables():
    print(v.name, "=", v.varValue)

(Partial) Output:

inventory_0 = 200.0
inventory_1 = 525.0
inventory_2 = 350.0
inventory_3 = 175.0
inventory_4 = 900.0
inventory_5 = 450.0
peak_staff = 450.0
production_0 = 175.0
production_1 = 175.0
production_2 = 175.0
production_3 = 175.0
production_4 = 450.0
production_5 = 450.0
staff_0 = 175.0
staff_1 = 175.0
staff_2 = 175.0
staff_3 = 175.0
staff_4 = 450.0
staff_5 = 450.0
  • Related