1

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 probelm.

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?

Thanks, Carlotta.

pchtsp
  • 794
  • 1
  • 6
  • 15
  • The example is incomplete. Are `S_*` and `D_*` parameters or pulp variables? If they are parameters, this is not really a constraint given that it does not involve any pulp variable. If they are pulp expressions, you're missing their definitions. The easiest is for you to give a complete example with all the needed data. – pchtsp Jul 06 '20 at 13:07
  • @pchtsp I added the part on the definition of `S_*` and `D_*`. Basically `D_*` is the demand that I get from the dataset the client provided. The supply is then calculated using the result of the problem. The goal is to find which cycles let me produce the items needed storing as less as possible. – Carlotta Fabris Jul 06 '20 at 13:20
  • As I said in the edit, you should edit the code to have a mwe (https://stackoverflow.com/help/minimal-reproducible-example) with some invented data that shows your issue so we could run the code in our pcs and better help ypu. – pchtsp Jul 06 '20 at 13:53
  • @pchtsp I edited the code and now you will be able to run the code on your pc. As you will see in this example the cycle A2 that shoul be >= 0 is -8.8 and hte cycles should also be integers but they are float in the given solution. The time needed is 1440, but I would like it to be more than that with the constraints on the cycles respected. – Carlotta Fabris Jul 06 '20 at 14:42

1 Answers1

4

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.

pchtsp
  • 794
  • 1
  • 6
  • 15
  • Do you have some suggestions on how to pick the `big_enough_number` so that the model will only violate the time constraint if there is no other option? – Carlotta Fabris Jul 07 '20 at 09:56
  • It will depend on the other weights in your objective function and the range you give to all your variables. You do not want to give a huge weight (because it causes numeric issues) but you want to be sure it's enough so that even delaying the `Tot_time` by one unit will be more costly than having a huge `Stock_XP`. – pchtsp Jul 08 '20 at 08:15
  • Thanks a lot @pchtsp I will follow your advice and choose `big_enough_number` accordingly – Carlotta Fabris Jul 08 '20 at 09:48