coin-or / python-mip

Python-MIP: collection of Python tools for the modeling and solution of Mixed-Integer Linear programs
Eclipse Public License 2.0
514 stars 89 forks source link

Model.write() outputs incorrect objective row in MPS format #300

Open ryanjoneil opened 1 year ago

ryanjoneil commented 1 year ago

Describe the bug

When I formulate a model in python-mip to maximize an objective function and write it to a .mps file, the objective coefficients have the wrong sign.

To Reproduce

Construct the 0-1 knapsack model from the examples and write it to a .mps file.

from mip import Model, xsum, maximize, BINARY

p = [10, 13, 18, 31, 7, 15]
w = [11, 15, 20, 35, 10, 33]
c, I = 47, range(len(w))

m = Model("knapsack")

x = [m.add_var(var_type=BINARY) for i in I]

m.objective = maximize(xsum(p[i] * x[i] for i in I))

m += xsum(w[i] * x[i] for i in I) <= c

m.write('knapsack.mps')

Now call cbc or highs on the resulting file.

$ cbc knapsack.mps

The optimal objective is (incorrectly) 0.

Welcome to the CBC MILP Solver 
Version: 2.10.8 
Build Date: May  7 2022 

command line - cbc knapsack.mps (default strategy 1)
At line 1 NAME          knapsack  FREE
At line 2 ROWS
At line 5 COLUMNS
At line 12 RHS
At line 14 BOUNDS
At line 21 ENDATA
Problem knapsack has 1 rows, 6 columns and 6 elements
Coin0008I knapsack read with 0 errors
Continuous objective value is 0 - 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 0 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:                0.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.00
Time (Wallclock seconds):       0.00

Total time (CPU seconds):       0.00   (Wallclock seconds):       0.00

Expected behavior

This happens because the MPS file is minimizing the objective. The coefficients of the output file should be - in this case.

NAME          knapsack  FREE
ROWS
 N  OBJROW
 L  constr(0)
COLUMNS
    var(0) OBJROW 10.          constr(0) 11.         
    var(1) OBJROW 13.          constr(0) 15.         
    var(2) OBJROW 18.          constr(0) 20.         
    var(3) OBJROW 31.          constr(0) 35.         
    var(4) OBJROW 7.           constr(0) 10.         
    var(5) OBJROW 15.          constr(0) 33.         
RHS
    RHS constr(0) 47.         
BOUNDS
 BV BOUND var(0) 1.          
 BV BOUND var(1) 1.          
 BV BOUND var(2) 1.          
 BV BOUND var(3) 1.          
 BV BOUND var(4) 1.          
 BV BOUND var(5) 1.          
ENDATA

If I change their signs:

NAME          knapsack  FREE
ROWS
 N  OBJROW
 L  constr(0)
COLUMNS
    var(0) OBJROW -10.          constr(0) 11.         
    var(1) OBJROW -13.          constr(0) 15.         
    var(2) OBJROW -18.          constr(0) 20.         
    var(3) OBJROW -31.          constr(0) 35.         
    var(4) OBJROW -7.           constr(0) 10.         
    var(5) OBJROW -15.          constr(0) 33.         
RHS
    RHS constr(0) 47.         
BOUNDS
 BV BOUND var(0) 1.          
 BV BOUND var(1) 1.          
 BV BOUND var(2) 1.          
 BV BOUND var(3) 1.          
 BV BOUND var(4) 1.          
 BV BOUND var(5) 1.          
ENDATA

Then the solution is correct.

Result - Optimal solution found

Objective value:                -41.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.01
Time (Wallclock seconds):       0.01

Total time (CPU seconds):       0.01   (Wallclock seconds):       0.01

Desktop (please complete the following information):

ryanjoneil commented 1 year ago

Noting that this is an issue with Cbc. I posed the question on #164 as to whether it makes sense to defer writing model files using it, since it seems to have issues generating correct output.

ryanjoneil commented 1 year ago

Confirmed: If I change m = Model(sense=MAXIMIZE) to m = Model(sense=MINIMIZE) and switch the sign of every variable in the objective function in my Python code, the the result of m.write('model.mps') is equivalent to m.write('model.lp') before the changes.

sebheger commented 1 year ago

Maybe related to https://github.com/coin-or/Cbc/issues/519