Search code examples
pythonoptimizationconstraintspulpminimization

Pulp constraints when problem is infeasible


I am trying to solve a linear optimization problem using Pulp in Python.

Here is the code:

import pandas as pd
import pulp

D_XB = 20
D_XP = 0
D_XC = 0

Available_Time = 1440 #in minutes

test = [['A1', 'A2', 'A3', 'A4', 'A5'], [1,2,1,0,3], [16,32,0,16,32], [10,10,10,10,10], [120,210,180,180,350]]

Cycles = pd.DataFrame(test, index=['Cycles', 'QTA1', 'QTA2', 'QTA3', 'T_TOT']).T

A1 = pulp.LpVariable("Cycle_A1", lowBound=0, cat='Integer')
A2 = pulp.LpVariable("Cycle_A2", lowBound=0, cat='Integer')
A3 = pulp.LpVariable("Cycle_A3", lowBound=0, cat='Integer')
A4 = pulp.LpVariable("Cycle_A4", lowBound=0, cat='Integer')
A5 = pulp.LpVariable("Cycle_A5", lowBound=0, cat='Integer')
    
# Defining the problem as a minimization problem (Minimize Storage)
problem_5 = pulp.LpProblem("Storage_Minimization_3", pulp.LpMinimize)

S_XB = pulp.lpSum((Cycles.iloc[0]["QTA1"])*A1 + (Cycles.iloc[1]["QTA1"])*A2 + (Cycles.iloc[2]["QTA1"])*A3 + (Cycles.iloc[3]["QTA1"])*A4 + (Cycles.iloc[4]["QTA1"])*A5)
S_XP = pulp.lpSum((Cycles.iloc[0]["QTA2"])*A1 + (Cycles.iloc[1]["QTA2"])*A2 + (Cycles.iloc[2]["QTA2"])*A3 + (Cycles.iloc[3]["QTA2"])*A4 + (Cycles.iloc[4]["QTA2"])*A5)
S_XC = pulp.lpSum((Cycles.iloc[0]["QTA3"])*A1 + (Cycles.iloc[1]["QTA3"])*A2 + (Cycles.iloc[2]["QTA3"])*A3 + (Cycles.iloc[3]["QTA3"])*A4 + (Cycles.iloc[4]["QTA3"])*A5)

Tot_Time = pulp.lpSum((Cycles.iloc[0]["T_TOT"])*A1 + (Cycles.iloc[1]["T_TOT"])*A2 + (Cycles.iloc[2]["T_TOT"])*A3 + (Cycles.iloc[3]["T_TOT"])*A4 + (Cycles.iloc[4]["T_TOT"])*A5)

Stock_XB = (S_XB - D_XB)
Stock_XP = (S_XP - D_XP)
Stock_XC = (S_XC - D_XC)

problem_5 += Stock_XB + Stock_XP + Stock_XC
    
# Constraints: Time constraint present
problem_5 += S_XB >= D_XB
problem_5 += S_XP >= D_XP
problem_5 += S_XC >= D_XC
problem_5 += A1 >= 0
problem_5 += A2 >= 0
problem_5 += A3 >= 0
problem_5 += A4 >= 0
problem_5 += A5 >= 0
problem_5 += Tot_Time <= Available_Time
    
# Solving the probelm
status = problem_5.solve()

result = pd.DataFrame({'A1':[pulp.value(A1)], 'A2':[pulp.value(A2)], 'A3':[pulp.value(A3)], 'A4':[pulp.value(A4)], 'A5':[pulp.value(A5)], 
                       'Demand XB':[D_XB], 'Demand XP':[D_XP], 'Demand XC':[D_XC],  'Minimum storage':[pulp.value(problem_5.objective)], 
                       'Stock_XB':[pulp.value(Stock_XB)], 'Stock_XP':[pulp.value(Stock_XP)], 'Stock_XC':[pulp.value(Stock_XC)], 
                       'Total Time needed':[pulp.value(Tot_Time)]})

Terminology:
S_* is the production of a certain item,
D_* is the demand of that item.
These are defined before the definition of the problem.

This problem is not always feasible because sometimes production exceeds the time available. In this case, I would like the constraints on the cycles to be respected and the one on time should be broken in order to solve the problem.

How can I achieve this?


Solution

  • They way I would do it is to add slack variables to the time limit constraint like so:

    First you add a new variable:

    #(...)
    slack_time = pulp.LpVariable("slack_time", lowBound=0, cat=pulp.LpContinuous)
    #(...)
    

    Then you penalize it in the objective function:

    #(...)
    big_enough_number = 10000
    problem_5 += Stock_XB + Stock_XP + Stock_XC + slack_time*big_enough_number
    #(...)
    

    Finally you add it to your time limit constraint:

    #(...)
    problem_5 += Tot_Time - slack_time <= Available_Time
    #(...)
    

    you will then have a solution that violates the time constraint the least possible. If you pick a good enough big_enough_number the model will only violate the time constraint if there is no other option.