ampl / mp

An open-source library for mathematical programming
https://mp.ampl.com
Other
226 stars 43 forks source link

Different objective value #167

Open glebbelov opened 2 years ago

glebbelov commented 2 years ago

When running x-multmip3.mod + multmip3.dat, see test/end2end/cases/fast/cp_global_constraints, AMPLPY reports a different objective value than the solver. This does not happen when solving in AMPL session.

fdabrandao commented 2 years ago

Running the following:

from amplpy import AMPL
ampl = AMPL()
ampl.option['solver'] = 'x-gurobi'
ampl.read('x-multmip3.mod')
ampl.read_data('multmip3.dat')
ampl.solve()
print(ampl.get_value('Total_Cost'))

produces the following output:

x-Gurobi 9.5.0: optimal solution; objective 231650
306 simplex iterations
31 branching nodes
Total_Cost = 233150

This is matching the AMPL session:

ampl: model x-multmip3.mod;
ampl: data multmip3.dat;
ampl: option solver x-gurobi;
ampl: solve;
x-Gurobi 9.5.0: optimal solution; objective 231650
306 simplex iterations
31 branching nodes
ampl: display Total_Cost;
Total_Cost = 233150

Does this AMPL session produce a different output on your machine?

glebbelov commented 2 years ago

You might need the current x-gurobi 20220303, but I did not manage to get it updated in the portal (messaged @mapgccv). But 20220217 might show similar behavior, although it reports a wrong solution.

Your Python code works well, identical to AMPL session. To "confuse" it, I added the following statement, similar to this line in AMPLRunner.py: ampl.setOption("solver_msg", 0). Then calling solve() several times, each other time gives wrong result:

>>> print(ampl.get_value('Total_Cost'))
235625.0
>>> print(ampl.getCurrentObjective().value())
235625.0
>>> ampl.setOption("solver_msg", 0)
>>> ampl.solve()
x-Gurobi 9.5.1: Set parameter Username
Academic license - for non-commercial use only - expires 2022-05-06
>>> print(ampl.getCurrentObjective().value())
232424.9979338393
>>> print(ampl.getCurrentObjective().value())
232424.9979338393
>>> ampl.solve()
x-Gurobi 9.5.1: Set parameter Username
Academic license - for non-commercial use only - expires 2022-05-06
>>> print(ampl.getCurrentObjective().value())
235625.0
>>> ampl.solve()
x-Gurobi 9.5.1: Set parameter Username
Academic license - for non-commercial use only - expires 2022-05-06
>>> print(ampl.getCurrentObjective().value())
232424.9979338393
>>> ampl.solve()
x-Gurobi 9.5.1: Set parameter Username
Academic license - for non-commercial use only - expires 2022-05-06
>>> print(ampl.getCurrentObjective().value())
235625.0
>>> ampl.solve()
x-Gurobi 9.5.1: Set parameter Username
Academic license - for non-commercial use only - expires 2022-05-06
>>> print(ampl.getCurrentObjective().value())
232424.9979338393

Not sure this is it. This might be related to the bug mentioned in line 177: without calling terminateAMPL() after every instance, when running the 102 fast tests, 1 test fails due to a wrong objective.

glebbelov commented 2 years ago

This might be related to warm start: every other iteration, Gurobi accepts it but then tries to improve it and says the solution violates tolerances. This does not happen when using big-M redefinitions (acc::or=1, acc:and=1, acc:ind_le=1).

Don't know if this is the issue. Running the tests with these options still gives different objectives.

glebbelov commented 2 years ago

Yep this seems to be the issue. Running with just "acc:and=1", only 1 of 2 examples differs. But only in the test script...

fdabrandao commented 2 years ago

I am not being able to reproduce that with the previous version and the the latest version is not building on macOS:

/Users/fdabrandao/github/solvers-private/mp/include/mp/flat/constraint_base.h:144:3: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
  GetAprioriBounds() { return {-INFINITY, INFINITY}; }
  ^
/Users/fdabrandao/github/solvers-private/mp/include/mp/flat/constraint_base.h:144:31: note: non-constexpr constructor 'pair<float, float, false>' cannot be used in a constant expression
  GetAprioriBounds() { return {-INFINITY, INFINITY}; }
                              ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/usr/include/c++/v1/utility:444:5: note: declared here
    pair(_U1&& __u1, _U2&& __u2)

Regarding the difference between the AMPL session and amplpy, it should not happen. For every API interaction there is a corresponding AMPL session that can be recorded with ampl._start_recording('session.run').

fdabrandao commented 2 years ago

I removed constexpr from static constexpr std::pair<double, double> in order to build the latest version on macOS and I was able to reproduce the issue with it.

The script:

from amplpy import AMPL
ampl = AMPL()
ampl._start_recording('session.run')
ampl.option['solver'] = 'x-gurobi'
ampl.option['solver_msg'] = 0
ampl.read('x-multmip3.mod')
ampl.read_data('multmip3.dat')
ampl.solve()
print(ampl.get_value('Total_Cost'))
ampl.solve()
print(ampl.get_value('Total_Cost'))
ampl.solve()
print(ampl.get_value('Total_Cost'))
ampl.solve()
print(ampl.get_value('Total_Cost'))

produces the output:

235624.99999999997
232424.9979338393
235624.99999999997
232424.9979338393

The corresponding AMPL session is:

option 'solver' 'x-gurobi';
option 'solver_msg' '0';
include 'x-multmip3.mod';
data 'multmip3.dat';
solve;
_display Total_Cost;
solve;
_display Total_Cost;
solve;
_display Total_Cost;
solve;
_display Total_Cost;

and it produces the same output:

_display 0 1 1:
Total_Cost
235624.99999999997
_display 0 1 1:
Total_Cost
232424.9979338393
_display 0 1 1:
Total_Cost
235624.99999999997
_display 0 1 1:
Total_Cost
232424.9979338393