Model.write() outputs incorrect objective row in MPS format
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):
- Operating System, version: 11.6.7
- Python version: 3.7.8
- Python-MIP version (we recommend you to test with the latest version): 1.13.0
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.
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.
Maybe related to https://github.com/coin-or/Cbc/issues/519