Quantcast
Channel: Yet Another Math Programming Consultant
Viewing all articles
Browse latest Browse all 51

PuLP surprises

$
0
0

Formulating optimization models inside traditional programming languages such as Python is very popular. The main tool the developers use to make this possible is operator overloading. There are cases, where we can write code that looks somewhat reasonable, is accepted and processed without any warning or error messages, but is total nonsense. It is rather difficult to make things airtight. In [1], we see a good example. I have created a small fragment here that illustrates the problem.


import pulp 

# data
n_orders = 2
m_rows = 2  
cases = [1]*n_orders

# Define the model
model = pulp.LpProblem("Minimize_Total_Hours", pulp.LpMinimize)

# Decision variables: binary variables for assigning each order to a row
x = pulp.LpVariable.dicts("x", [(i, j) for i in range(n_orders) for j in range(m_rows)], 0, 1, pulp.LpBinary)

# Additional variable: number of orders assigned to each row
y = pulp.LpVariable.dicts("y", [j for j in range(m_rows)], 0, n_orders, pulp.LpInteger)

# CPH based on the number of orders in a rowdef get_cph(num_orders):
    if num_orders == 1:
        return152elif num_orders == 2:
        return139elif num_orders == 3:
        return128elif num_orders == 4:
        return119elif num_orders == 5:
        return112elif num_orders == 6:
        return107else:
        return104# Objective function: Minimize total hours across all rows
model += pulp.lpSum(
    (pulp.lpSum(cases[i] * x[i, j] for i in range(n_orders)) / get_cph(y[j])) for j in range(m_rows)
)

print(model)

# Solve the model
model.solve()

# print solve status
pulp.LpStatus[model.status]


There are many issues here. 

  • PuLP is for linear problems only. So, division by a variable quantity is not allowed.
  • We can't use if statements like this.
  • Functions are not callbacks but are called at model generation time.
  • The == inside the function is not a standard comparison operator but an operator hijacked by PuLP. It always returns true in this context.
So what does PuLP say about all these problems? Absolutely nothing.

When running this, we see:

Minimize_Total_Hours:
MINIMIZE
0.006578947368421052*x_(0,_0) + 0.006578947368421052*x_(0,_1) + 0.006578947368421052*x_(1,_0) + 0.006578947368421052*x_(1,_1) + 0.0
VARIABLES
0 <= x_(0,_0) <= 1 Integer
0 <= x_(0,_1) <= 1 Integer
0 <= x_(1,_0) <= 1 Integer
0 <= x_(1,_1) <= 1 Integer

 

'
Optimal
'


The values 0.006578947368421052 are 1/152. That's not at all what the author expected.

A good rule in well-designed tools is: "anything that is not forbidden, is allowed". I would like to see alarms going off for input like this. 

Other PuLP surprises are in [2,3].

How to confuse PuLP even more


If we replace
 
if num_orders == 1:

by

if num_orders == "crazy":

you will see things like:

RecursionError: maximum recursion depth exceeded in comparison

Obviously, this is crazy. 

 

Conclusion


Computing is still in the Stone Age. 

References


Viewing all articles
Browse latest Browse all 51

Trending Articles