I have a MILP problem which I am solving using PuLP in Python and ran into some funny behavior by the solvers that I am not able to get my head around. I would love to get some ideas on what could be causing this issue.
Little bit about the problem statement first. It's a transportation network problem with a bunch of nodes to be connected such that the total cost in minimal while satisfying some constraints like disabling/forcing some lanes.
When I solve the problem using PuLP's bundled solver, it gave me following solution.
objective = cost
opt_model.setObjective(objective)
opt_model.sense = plp.LpMinimize
opt_model.solve()
print(plp.LpStatus[opt_model.status])
print("objective=", opt_model.objective.value())
Optimal
objective= 20968423.09652282
I realized that the solver version PuLP was using was 2.9.0 but the latest one is 2.10.3 on COIN-OR website, so decided to use latest CBC solver. When I did that, this exact same problem became Infeasible with the new solver.
objective = cost
opt_model.setObjective(objective)
opt_model.sense = plp.LpMinimize
opt_model.solve(solver=plp.COIN_CMD(path='<path to solver>/cbc'))
print(plp.LpStatus[opt_model.status])
print("objective=", opt_model.objective.value())
Infeasible
objective= 18434742.025923416
However, funny thing is that it gives me an issue only when I solve the problem via PuLP. If I take the .lp file created by PuLP and solve directly using downloaded cbc binary, it solves and gives me an optimal answer which is pretty close to answer from PuLP solver!
Welcome to the CBC MILP Solver
Version: 2.10.3
Build Date: Dec 15 2019
command line - ./cbc writeLP_model_Partial.lp (default strategy 1)
Continuous objective value is 1.84347e+07 - 0.16 seconds
Cgl0003I 0 fixed, 0 tightened bounds, 854 strengthened rows, 338 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 630 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 278 strengthened rows, 0 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 100 strengthened rows, 0 substitutions
Cgl0004I processed model has 6676 rows, 5092 columns (5092 integer (3674 of which binary)) and 21162 elements
Cbc0038I Initial state - 435 integers unsatisfied sum - 73.1625
Cbc0038I Pass 1: (1.31 seconds) suminf. 54.06687 (218) obj. 2.10227e+07 iterations 631
Cbc0038I Pass 2: (1.31 seconds) suminf. 54.06685 (218) obj. 2.10227e+07 iterations 0
Cbc0038I Pass 3: (1.32 seconds) suminf. 54.06685 (218) obj. 2.10227e+07 iterations 0
Cbc0038I Pass 4: (1.33 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 1
Cbc0038I Pass 5: (1.33 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 6: (1.33 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 7: (1.34 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 8: (1.34 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 9: (1.35 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 10: (1.35 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 11: (1.35 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 12: (1.36 seconds) suminf. 53.70259 (217) obj. 2.10265e+07 iterations 0
Cbc0038I Pass 13: (1.37 seconds) suminf. 47.92774 (193) obj. 2.24017e+07 iterations 444
Cbc0038I Pass 14: (1.38 seconds) suminf. 47.92774 (193) obj. 2.24017e+07 iterations 33
Cbc0038I Pass 15: (1.38 seconds) suminf. 47.92774 (193) obj. 2.24017e+07 iterations 0
Cbc0038I Pass 16: (1.39 seconds) suminf. 47.92774 (193) obj. 2.24017e+07 iterations 0
Cbc0038I Pass 17: (1.40 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 355
Cbc0038I Pass 18: (1.41 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 20
Cbc0038I Pass 19: (1.41 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 20: (1.42 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 21: (1.42 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 22: (1.42 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 23: (1.43 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 24: (1.43 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 25: (1.44 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 26: (1.44 seconds) suminf. 47.92774 (193) obj. 2.34752e+07 iterations 0
Cbc0038I Pass 27: (1.46 seconds) suminf. 44.63425 (184) obj. 2.45218e+07 iterations 368
Cbc0038I Pass 28: (1.47 seconds) suminf. 44.63425 (184) obj. 2.45218e+07 iterations 17
Cbc0038I Pass 29: (1.47 seconds) suminf. 44.63425 (184) obj. 2.45218e+07 iterations 0
Cbc0038I Pass 30: (1.48 seconds) suminf. 44.63425 (184) obj. 2.45218e+07 iterations 0
Cbc0038I Rounding solution of 2.09662e+07 is better than previous of 1e+50
Cbc0038I Before mini branch and bound, 3925 integers at bound fixed and 1 continuous
Cbc0038I Mini branch and bound did not improve solution (1.50 seconds)
Cbc0038I After 1.50 seconds - Feasibility pump exiting with objective of 2.09662e+07 - took 0.25 seconds
Cbc0012I Integer solution of 20966181 found by feasibility pump after 0 iterations and 0 nodes (1.50 seconds)
Cbc0001I Search completed - best objective 20966181.27328906, took 0 iterations and 0 nodes (1.55 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from 2.09843e+07 to 2.09843e+07
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 s
econds)
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: 20966181.27328906
Enumerated nodes: 0
Total iterations: 0
Time (CPU seconds): 1.74
Time (Wallclock seconds): 1.82
Total time (CPU seconds): 1.85 (Wallclock seconds): 1.96
What am I missing here? I have tried different things like updating PuLP to latest version but that didn't help. Are there any solver options that I need to change? I am new to options so have no clue what to try.
It's very likely you're running into numerical precision issues. Check point 2 of this part of the pulp docs. You're using too many decimal numbers for your floats in the parameters of the constraints and objective function coefficients. Just round all parameters to 2 or 3 decimals (or whatever makes sense for your problem).
This can be seen because you're objective function value is very long (18434742.025923416) and because CBC gives different objectives as optimal (there should be only one optimal objective function value regardless of the solver you use).