Python Linear Programming Binary exclusive or choose only one group

Question:

I am trying to identify the designers with best cost to finish the complete dress.

The Conditions:

  1. Need to get complete dress

  2. Either Paul1 or Paul2 can be selected but not both even though the
    combination of these two could be the best cost.

Dress John Chris Paul1 Paul2
Shirt 6.33 6.62 6.37 6.52
Trouser 6.76 6.62 6.37 6.76
Jacket 6.84 6.62 6.34 6.20

Here is the result of the model from Python and Pulp:

MINIMIZE

6.84 * x_0 + 6.62 * x_1 + 6.37 * x_10 + 6.76 * x_11 + 6.34 * x_2 + 6.2 * x_3 + 6.33 * x_4 + 6.62 * x_5 + 6.37 * x_6 + 6.76 * x_7 + 6.76 * x_8 + 6.62 * x_9 + 0.0

SUBJECT TO

C1: x_0 + x_1 + x_2 + x_3 = 1

C2: x_4 + x_5 + x_6 + x_7 = 1

C3: x_10 + x_11 + x_8 + x_9 = 1

I couldn’t figure it out the condition to choose either Paul1 or Paul2 but not both.

Any help would be greatly appreciated.

Note: This is continuous to the question: Linear Programming Binary Variable Grouping

Asked By: Explore

||

Answers:

Standard way to choose either paul1 or paul2 isn’t introduce 2 binary variable indexed over y[p1] & y[p2]. Then following constraints
y[p1]<= sum(x[p1,c] over c)<=3y[p1]
y[p2]<= sum(x[p2,c] over c)<=3y[p2]
y[p1] + y[p2]<= 1
where c represents {shirt, trouser, jacket}.

The above constraints choose either p1 or p2 for all or none of the clothing items.

Answered By: Sutanu

You need better variable and constraint names, and you have only one constraint (the exclusive vendor selection per row):

import pandas as pd
import pulp


def make_assignment(suffix: str) -> pulp.LpVariable:
    return pulp.LpVariable(name='asn_' + suffix, cat=pulp.LpBinary)


def constrain_rows(row: pd.Series) -> pd.Series:
    names = (row.name + '_' + row.index).to_series(index=row.index)
    vars = names.apply(make_assignment)
    prob.addConstraint(name='excl_' + row.name, constraint=1 == pulp.lpSum(vars))
    return vars


df = pd.DataFrame({
    'John':  (6.33, 6.76, 6.84),
    'Chris': (6.62, 6.62, 6.62),
    'Paul1': (6.37, 6.37, 6.34),
    'Paul2': (6.52, 6.76, 6.20),
}, index=pd.Index(name='Dress', data=('Shirt', 'Trouser', 'Jacket')))

prob = pulp.LpProblem(name='dress_vendors', sense=pulp.LpMinimize)
assigns = df.apply(func=constrain_rows, axis=1)
prob.objective = pulp.lpDot(assigns.values.ravel(), df.values.ravel())
print(prob)
prob.solve()

_, used = assigns.applymap(pulp.LpVariable.value).values.nonzero()
for dress, vendor in zip(df.index, df.columns[used]):
    print(f'{dress} bought from {vendor}')
print(f'Dress total is ${prob.objective.value():.2f}')
dress_vendors:
MINIMIZE
6.62*asn_Jacket_Chris + 6.84*asn_Jacket_John + 6.34*asn_Jacket_Paul1 + 6.2*asn_Jacket_Paul2 + 6.62*asn_Shirt_Chris + 6.33*asn_Shirt_John + 6.37*asn_Shirt_Paul1 + 6.52*asn_Shirt_Paul2 + 6.62*asn_Trouser_Chris + 6.76*asn_Trouser_John + 6.37*asn_Trouser_Paul1 + 6.76*asn_Trouser_Paul2 + 0.0
SUBJECT TO
excl_Shirt: asn_Shirt_Chris + asn_Shirt_John + asn_Shirt_Paul1
 + asn_Shirt_Paul2 = 1

excl_Trouser: asn_Trouser_Chris + asn_Trouser_John + asn_Trouser_Paul1
 + asn_Trouser_Paul2 = 1

excl_Jacket: asn_Jacket_Chris + asn_Jacket_John + asn_Jacket_Paul1
 + asn_Jacket_Paul2 = 1

VARIABLES
0 <= asn_Jacket_Chris <= 1 Integer
0 <= asn_Jacket_John <= 1 Integer
0 <= asn_Jacket_Paul1 <= 1 Integer
0 <= asn_Jacket_Paul2 <= 1 Integer
0 <= asn_Shirt_Chris <= 1 Integer
0 <= asn_Shirt_John <= 1 Integer
0 <= asn_Shirt_Paul1 <= 1 Integer
0 <= asn_Shirt_Paul2 <= 1 Integer
0 <= asn_Trouser_Chris <= 1 Integer
0 <= asn_Trouser_John <= 1 Integer
0 <= asn_Trouser_Paul1 <= 1 Integer
0 <= asn_Trouser_Paul2 <= 1 Integer

Welcome to the CBC MILP Solver 
Version: 2.10.3 
Build Date: Dec 15 2019 

command line - .venv/lib/python3.10/site-packages/pulp/solverdir/cbc/linux/64/cbc /tmp/1eacd290c9f44c86a43b0722be3104de-pulp.mps timeMode elapsed branch printingOptions all solution /tmp/1eacd290c9f44c86a43b0722be3104de-pulp.sol (default strategy 1)
At line 2 NAME          MODEL
At line 3 ROWS
At line 8 COLUMNS
At line 57 RHS
At line 61 BOUNDS
At line 74 ENDATA
Problem MODEL has 3 rows, 12 columns and 12 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Continuous objective value is 18.9 - 0.00 seconds
Cgl0004I processed model has 0 rows, 0 columns (0 integer (0 of which binary)) and 0 elements
Cbc3007W No integer variables - nothing to do
Cuts at root node changed objective from 18.9 to -1.79769e+308
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
ZeroHalf was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)

Result - Optimal solution found

Objective value:                18.90000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.00
Time (Wallclock seconds):       0.00

Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.00   (Wallclock seconds):       0.00

Shirt bought from John
Trouser bought from Paul1
Jacket bought from Paul2
Dress total is $18.90
Answered By: Reinderien
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.