Pulp: slack variable to identify & measure extent infeasible in supply problem

I am currently modelling a supply problem that attempts to optimise a rail schedule which moves products from a production plant, to a warehouse to satisfy sales.

The model is working fine (thanks in part to OR!) however I would like to create a variable that shows the extent of a supply shortfall. Currently if the current inventory is 0, and demand is > 0, then the model is unable to satisfy the constraints and returns 'Infeasible'.

I would like to define a slack/soft constraint that captures the shortfall so that the end result is always 'optimal' even though a shortfall is realised, and shows this extent.

Unfortunately my current soft constraint is not recognised by the model and the output is still 'infeasible'

Desired outcome:

desired_output_df.head(10)

>>>

date            product      current_inventory     sales_demand     inventory_shortfall
'2020-01-01'    'AFM'        10000                 5000             0
'2020-01-02'    'AFM'        5000                  5000             0
'2020-01-03'    'AFM'        0                     6000             6000

# Sales Demand
# Storage levels must meet sales demand
model += storage_inventory_vars[date, product] \
+ insufficient_storage_supply[(date, product)] \
>= sales_demand[date][product]


The variable insufficient_supply[(date, product)] is the key slack constraint here that I would like to measure as it should prevent an infeasible solution (owing to insufficient supply to meet demand).

You can observe in the sales demand data on the 2020-05-18 and 19 that there is a very large spike in demand for AFE so that it greatly exceeds supply.

Here, if storage_inventory_vars['2020-05-18, 'AFE'] == 50,000 then insufficient_supply[('2020-05-18, 'AFE')] should == -50,000. The sum should then produce an optimal solution is the sum is greater than the demand.

All help very gratefully received, thank you.

• Are you sure infeasibility is due to the demand constraint ? – Kuifje May 18 '20 at 13:05
• Hi @Kuifje, yes reasonably so. I run the model on dummy data that was currently optimal before the addition of this constraint. If I remove it entirely and adjust the sales demand so that supply > demand, an optimum is found. – cmp May 18 '20 at 13:18
• can you edit your post so that we can try to run the model (I think just the data is missing) ? – Kuifje May 18 '20 at 13:34
• @Kuifje sadly there is a whole lot more code to this model and I am not sure how to reduce it to the point where it constitutes an MRE. Apologies - I am aware this is convention on SO but its sadly not possible. I have added the sales demand data to give you an idea if this helps? – cmp May 18 '20 at 13:44
• Without all the data, I won't be able to reproduce the error. Lets try something else. Can you try setting "sales_demand[date][product]" to 0 in the last constraint ? – Kuifje May 18 '20 at 14:02

The following constraints are infeasible :

 _C129: Rail_Loadings_From_Washplant_('2020_05_22',_'ABC',_'PRE')

+ Port_Inventory_Levels_('2020_05_21',_'ZBF')
- Port_Inventory_Levels_('2020_05_22',_'ZBF')  = 200000

+ Port_Inventory_Levels_('2020_05_22',_'ZBF')
- Port_Inventory_Levels_('2020_05_23',_'ZBF')  = 200000

_C241: Port_Inventory_Levels_('2020_05_21',_'ZBF') <= 200000


I think there is a problem with your inventory equations. Not exactly sure where yet.

Finding the exact error is not that easy. Either there is a typo, either the model is not written correctly. My suggestion : back to basics, write the equations of the linear problem, and before anything, lets see if the model is properly written.

• Thank you for your help @Kuifje! Hmm, C129 and C134 make sense (3 x 8400 which is the carrying capacity of the 3 trains) and so does C241 (max level) but from what I can see you are right about C134 and C161. I will check it out and the design too. What is peculiar is that the model worked perfectly before adding the slack constraint? – cmp May 18 '20 at 20:44

I found the solution.

1- The storage inventory definition is as follows:

model += storage_stockpile_current[product] \
+ pulp.lpSum(
train_consignment_variables[(date, plant, product)]
for plant in _plants_combo) \
- sales_demand[date][product] \
== storage_inventory_vars[(date, product)]


2 - Given the addition of the slack constraint:

for date, grade in storage_inventory_vars:
model += storage_inventory_vars[date, product] \
+ insufficient_storage_supply[(date, product)] \
>= sales_demand[date][product]


Whenever sales_demand greatly exceeds the storage variable, the equation becomes inbalanced, because it has a lower bound of 0, i.e. cannot be negative. Therefore the definition needs to reflect this slack constraint:

model += storage_stockpile_current[product] \
+ pulp.lpSum(
train_consignment_variables[(date, plant, product)]
for plant in _plants_combo) \
- sales_demand[date][product] \
+ insufficient_storage_supply[(date, product)] \
== storage_inventory_vars[(date, product)]


A big thank you to Kuifje for their help.

• Glad you found the bug! Cheers – Kuifje May 19 '20 at 9:45