Results between Gurobi and AMPL suggest there's an issue with Gurobi calculations
AnsweredHi,
I recently installed Gurobi to run a simple Python code in which I know the answer to. When I run with Gurobi, I get different results than when I run it with AMPL in which I use a gurobi solver within AMPL. I know the results from AMPL are correct, and I'm wondering if my Gurobi results exhibit numerical issues. I've attached screenshots and also a log of the results when using the Gurobi Interactive Shell. In the log, I include the output when following the instructions in the case I have numerical issues; however, if I do have numerical issues, I'm not sure how to fix it. Any guidance would be great as I have a lab exercise that depends on getting accurate results.
WITH AMPL
(base) ampl>./ampl
ampl: model Workspace/ex1_prod0.mod
ampl: option solver './gurobi';
ampl: solve;
Gurobi 8.1.0: optimal solution; objective 192000
ampl: display x_A, x_B, Profit;
x_A = 6000
x_B = 1400
Profit = 192000
ampl:
WITH GUROBI
gurobi> m = Model("same old problem")
gurobi> x_A = m.addVar(vtype=GRB.INTEGER, name="x_A")
gurobi> x_B = m.addVar(vtype=GRB.INTEGER, name="x_B")
gurobi> m.setObjective(25*x_A + 30*x_B, GRB.MAXIMIZE)
gurobi> m.addConstr((1/200)*x_A + (1/140)*x_B <= 40, "Time")
<gurobi.Constr *Awaiting Model Update*>
gurobi> m.addConstr(0 <= x_A <= 6000, "A_Limit")
<gurobi.Constr *Awaiting Model Update*>
gurobi> m.addConstr(0 <= x_B <= 4000, "B_Limit")
<gurobi.Constr *Awaiting Model Update*>
gurobi> m.optimize()
Optimize a model with 3 rows, 2 columns and 2 nonzeros
Variable types: 0 continuous, 2 integer (0 binary)
Coefficient statistics:
Matrix range [1e+00, 1e+00]
Objective range [2e+01, 3e+01]
Bounds range [0e+00, 0e+00]
RHS range [4e+01, 6e+03]
Found heuristic solution: objective 270000.00000
Presolve removed 3 rows and 2 columns
Presolve time: 0.00s
Presolve: All rows and columns removed
Explored 0 nodes (0 simplex iterations) in 0.00 seconds
Thread count was 1 (of 8 available processors)
Solution count 1: 270000
Optimal solution found (tolerance 1.00e04)
Best objective 2.700000000000e+05, best bound 2.700000000000e+05, gap 0.0000%
gurobi> m.printQuality()
Solution quality statistics for model same old problem :
Maximum violation:
Bound : 0.00000000e+00
Constraint : 0.00000000e+00
Integrality : 0.00000000e+00
gurobi> m = read('gurobi.rew')
Read MPS format model from file gurobi.rew
Reading time = 0.00 seconds
: 3 rows, 2 columns, 2 nonzeros
gurobi> m.printStats()
Statistics for model Unnamed :
Linear constraint matrix : 3 Constrs, 2 Vars, 2 NZs
Variable types : 0 Continuous, 2 Integer
Matrix coefficient range : [ 1, 1 ]
Objective coefficient range : [ 25, 30 ]
Variable bound range : [ 0, 0 ]
RHS coefficient range : [ 40, 6000 ]
gurobi> m = read('gurobi.rew')
Read MPS format model from file gurobi.rew
Reading time = 0.00 seconds
: 3 rows, 2 columns, 2 nonzeros
gurobi> p = m.presolve()
Presolve removed 3 rows and 2 columns
Presolve time: 0.00s
gurobi> p.printStats()
Statistics for model Presolved :
Linear constraint matrix : 0 Constrs, 0 Vars, 0 NZs
Matrix coefficient range : [ 0, 0 ]
Objective coefficient range : [ 0, 0 ]
Variable bound range : [ 0, 0 ]
RHS coefficient range : [ 0, 0 ]
gurobi>

Hi Roy,
Gurobi 8.1.0 includes the Python 2.7 interactive shell, which uses integer division when the division operator is used with two integer operands. So, this line:
m.addConstr((1/200)*x_A + (1/140)*x_B <= 40, "Time")
is adding the constraint \( 0 x_A + 0 x_B \leq 40 \). To fix this, you can add the statement "from __future__ import division" to the beginning of your code. Alternatively, you can change one of the division operands in each division operation to be a float (e.g., 1.0 / 200).
Also, note that adding two inequalities simultaneously through Model.addConstr() is not supported in Gurobi 8.x. Thus, these two statements
m.addConstr(0 <= x_A <= 6000, "A_Limit")
m.addConstr(0 <= x_B <= 4000, "B_Limit")will result in unexpected behavior (likely, the first inequality will be discarded). In Gurobi 9.0, to be released in a few weeks, this code will result in an error.
Finally, variables are nonnegative by default. You can specify variable bounds directly in the Model.addVar() method:
x_A = m.addVar(ub=6000, vtype=GRB.INTEGER, name="x_A")
x_B = m.addVar(ub=4000, vtype=GRB.INTEGER, name="x_B")I hope this helps!
Eli

thank you, Eli. This is great! All of those options worked beautifully. I was trying to figure out a way to debug it, but couldn't. If you have any recommendations for debugging issues like this in the future, please let me know.
Also, I assume you recommend I specify the variable bounds directly in the Model.addVar() instead of using the two inequalities since variables are nonnegative by default, right?
What if I need to include a range in which it also included negative values? Is there a way to add that in Model.addVar()?

Hi Roy,
To debug issues like this, you could write the model to an LP file and inspect that. In the LP file, you can often see if something went wrong when building your constraints.
Yes, it is recommended to specify variable bounds as bounds instead of as constraints. You can do this in addVar() or later via the UB attribute (e.g. x_A.UB = 6000), but it is usually clearer to do it in addVar.
If your variable has a lower bound different from 0, you can define it as follows:
x_A = m.addVar(lb=1000, ub=6000, ...)
x_B = m.addVar(lb=GRB.INFINITY, ub=4000, ...)
Please sign in to leave a comment.
Comments
4 comments