10. Integer Programming

This is an example of solving a integer resource allocation problem with pulp library.

import pulp
from pulp import LpProblem, LpVariable, lpSum, LpMaximize, LpStatus, value
import pandas as pd
import numpy as np
import IPython
from IPython.display import display

print(pulp.__version__)
print(pd.__version__)
print(IPython.__version__)
# dicionario para cada plantonista
plantonista = {
    'andre'   : {"s1" : 0, "s2" : 2, "s3" : 3, "s4" : 5, "s5" : 4},
    'kiyota'  : {"s1" : 3, "s2" : 1, "s3" : 0, "s4" : 4, "s5" : 2},
    'fabio'   : {"s1" : 3, "s2" : 2, "s3" : 1, "s4" : 4, "s5" : 5},
    'natalia' : {"s1" : 0, "s2" : 2, "s3" : 0, "s4" : 4, "s5" : 5},
    'junior'  : {"s1" : 4, "s2" : 1, "s3" : 2, "s4" : 5, "s5" : 3},
    'manu'    : {"s1" : 5, "s2" : 3, "s3" : 1, "s4" : 4, "s5" : 2},
    "sarda"   : {"s1" : 4, "s2" : 2, "s3" : 5, "s4" : 1, "s5" : 3},
    "corbalan": {"s1" : 1, "s2" : 3, "s3" : 0, "s4" : 5, "s5" : 4},
    "leo"     : {"s1" : 1, "s2" : 2, "s3" : 4, "s4" : 3, "s5" : 5},
    "nefs"    : {"s1" : 2, "s2" : 4, "s3" : 1, "s4" : 5, "s5" : 0},
    "victor"  : {"s1" : 1, "s2" : 2, "s3" : 3, "s4" : 4, "s5" : 5},
    "denis"   : {"s1" : 5, "s2" : 0, "s3" : 2, "s4" : 1, "s5" : 4},
    "juliane" : {"s1" : 0, "s2" : 3, "s3" : 1, "s4" : 5, "s5" : 4},
    "izumi"   : {"s1" : 0, "s2" : 4, "s3" : 2, "s4" : 5, "s5" : 1},
    }

# lista de semanas
semana = list(list(plantonista.items())[0][1].keys())

# lista de plantonistas
plantonistas = list(plantonista.keys())

# cria a variável prob
prob = LpProblem("Calendario-plantonistas", LpMaximize)

week_vars = [LpVariable.dicts(f"{plant}",
                              semana,lowBound=0,
                              upBound=1,cat='Integer') for plant in plantonista]
# função objetivo
prob +=lpSum([[plantonista[i][j]*week_vars[l][j] for l,i in enumerate(plantonistas)] for j in semana]), "Satisfação total dos plantonistas"

# restrição para nenhum plantonista ficar mais de duas vezes no periodo
for i in range(len(plantonistas)):
    prob += lpSum([week_vars[i][j] for j in semana]) <= 2, f"max_per_plantonista {plantonistas[i]}"

# restrição para nenhum plantonista ficar por semanas seguidas
for i in range(len(plantonistas)):
    for s in range(len(semana)): 
        if s+1 == len(semana):
            break
        else:
            prob += lpSum([week_vars[i][semana[s]]+week_vars[i][semana[s+1]]]) <= 1, f"non_sequence_week_plantonista {plantonistas[i]}_seq{s+1}"

# restrição para que não haja dois  plantonistas por semana
for j in semana:
    prob += lpSum([week_vars[i][j] for i in range(len(plantonistas))]) <=2, f"max_plantonistas_week_{j}"

# restrição para que tenha pelo menos um plantonista em todas as semanas
for j in semana:
    prob += lpSum([week_vars[i][j] for i in range(len(plantonistas))]) >=1, f"min_plantonistas_week_{j}"

10.1. Lista de restrições

for items in prob.constraints.items():
    print(f"{items[0] :<38}: {items[1]}")

10.2. Solução e resultados

# chama o solver
prob.solve()

# cria um arquivo csv com o calendario final
X_vars = {k:[] for k in plantonistas}
semanas = [f"Semana_{i}" for i in range(len(semana))]

for v in prob.variables():
    X_vars[v.name.split("_")[0]] +=[v.varValue]

result_df = pd.DataFrame(X_vars,index=semanas).transpose().astype(int)
result_df['Total_do_plantonista'] = result_df.sum(axis=1)

10.3. logs

print("Status:", LpStatus[prob.status])
print("Satisfação da galera = ", value(prob.objective),"\n")
print("Número de plantonistas em cada semana:")
display(result_df.sum(axis=0))
print("Disponibilidade inicial dos plantonistas:")
disponibilidades = pd.DataFrame(plantonista).T
disponibilidades.columns = semanas
disponibilidades.head(14)
X = pd.DataFrame(X_vars,index=semanas).transpose().astype(int)
for s in X.columns:
    print(f"Selecionados na {s}: {' e '.join(X[X[s]!=0].index.tolist())}")
print(f"Plantonistas não selecionados: {', '.join(result_df[result_df['Total_do_plantonista']==0].index.tolist())}")
print(f"Plantonistas selecionados 1x : {', '.join(result_df[result_df['Total_do_plantonista']==1].index.tolist())}")
print(f"Plantonistas selecionados 2x : {', '.join(result_df[result_df['Total_do_plantonista']==2].index.tolist())}")
disponibilidades.loc[["andre","izumi"]]
result_df.loc[["andre","izumi"]]
print("Semanas mais desejadas")
display(disponibilidades.sum(axis=0).sort_values(ascending=False))
result_df_ = result_df.drop(result_df.columns[-1],axis=1)
result_df_.replace(1,"SELECIONADO",inplace=True)
print("Calendário final:")
display(result_df_.head(14))