Repository: Pyomo/pyomo

Issue:
gurobi solver option "BestObjStop" causes ValueError
When using the **BestObjStop** option offered by gurobi (see [http://www.gurobi.com/documentation/7.5/refman/bestobjstop.html#parameter:BestObjStop](http://www.gurobi.com/documentation/7.5/refman/bestobjstop.html#parameter:BestObjStop)) pyomo fails to recognize the solution.

**Solver output:**
> [...]
> Explored 1 nodes (6931 simplex iterations) in 0.43 seconds
> Thread count was 4 (of 4 available processors)
> 
> Solution count 5: 72.8522 118.624 565.215 ... 881.385
> 
> **Optimization achieved user objective limit**
> Best objective 7.285215950465e+01, best bound 0.000000000000e+00, gap 100.0000%
> 15

**Error:**
> [...]\pyomo\core\base\PyomoModel.py in load_from(self, results, allow_consistent_values_for_fixed_vars, comparison_tolerance_for_fixed_vars, ignore_invalid_labels, id, delete_symbol_map, clear, default_variable_value, select, ignore_fixed_vars)
>     239             else:
>     240                raise ValueError("Cannot load a SolverResults object "
> --> 241                                 "with bad status: %s" % str(results.solver.status))
>     242         if clear:
>     243             #
> 
> **ValueError: Cannot load a SolverResults object with bad status: error**


Discussion:
This may require some logic to be added to https://github.com/Pyomo/pyomo/blob/master/pyomo/solvers/plugins/solvers/GUROBI_RUN.py
@kfoerderer how are you calling the solver, using the `pyomo` command or using a script? If using a script could you give us the exact lines you're using? 
Thank you for your answer. I'm using a script. Here is the code calling the optimizer:

 ```
    opt = SolverFactory("gurobi")
    opt.options['timelimit'] = 30
    opt.options['mipgap'] = 0.05
    opt.options['bestobjstop'] = 100

    return opt.solve(instance, tee=True)
```
Without setting 'bestobjstop' (a gurobi specific option) it works fine. When the solver stops due to reaching 'bestobjstop' pyomo fails to recognize the solution.

This looks like a bug and it might be related to the discussion in #365. You could try using the workaround mentioned by Gabe in that issue, i.e. manually check the solver status that gets returned when you set the 'bestobjstop' option. 
I’ll have a fix pushed to GitHub as a PR in a few moments.

Gabe

> On Feb 28, 2018, at 12:00 PM, Bethany Nicholson <notifications@github.com> wrote:
> 
> This looks like a bug and it might be related to the discussion in #365 <https://github.com/Pyomo/pyomo/issues/365>. You could try using the workaround mentioned by Gabe in that issue, i.e. manually check the solver status that gets returned when you set the 'bestobjstop' option.
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub <https://github.com/Pyomo/pyomo/issues/294#issuecomment-369306968>, or mute the thread <https://github.com/notifications/unsubscribe-auth/ACM5Zsa-YF-5OS7h4EpBo8NA358J4sT1ks5tZYYYgaJpZM4RLNLX>.
> 



Resolving patch:
diff --git a/pyomo/solvers/plugins/solvers/GUROBI_RUN.py b/pyomo/solvers/plugins/solvers/GUROBI_RUN.py
--- a/pyomo/solvers/plugins/solvers/GUROBI_RUN.py
+++ b/pyomo/solvers/plugins/solvers/GUROBI_RUN.py
@@ -186,10 +186,20 @@ def gurobi_run(model_file, warmstart_file, soln_file, mipgap, options, suffixes)
         message = 'Unable to satisfy optimality tolerances; a sub-optimal solution is available.'
         term_cond = 'other'
         solution_status = 'feasible'
+    # note that USER_OBJ_LIMIT was added in Gurobi 7.0, so it may not be present
+    elif (solver_status is not None) and \
+         (solver_status == getattr(GRB,'USER_OBJ_LIMIT',None)):
+        status = 'aborted'
+        message = "User specified an objective limit " \
+                  "(a bound on either the best objective " \
+                  "or the best bound), and that limit has " \
+                  "been reached. Solution is available."
+        term_cond = 'other'
+        solution_status = 'stoppedByLimit'
     else:
-        print(solver_status)
         status = 'error'
-        message = 'Unknown return code from GUROBI model.getAttr(GRB.Attr.Status) call'
+        message = ("Unhandled Gurobi solve status "
+                   "("+str(solver_status)+")")
         term_cond = 'error'
         solution_status = 'error'
     assert solution_status is not None
diff --git a/pyomo/solvers/plugins/solvers/gurobi_direct.py b/pyomo/solvers/plugins/solvers/gurobi_direct.py
--- a/pyomo/solvers/plugins/solvers/gurobi_direct.py
+++ b/pyomo/solvers/plugins/solvers/gurobi_direct.py
@@ -507,9 +507,21 @@ def _postsolve(self):
                                                       "solution is available."
             self.results.solver.termination_condition = TerminationCondition.other
             soln.status = SolutionStatus.feasible
+        # note that USER_OBJ_LIMIT was added in Gurobi 7.0, so it may not be present
+        elif (status is not None) and \
+             (status == getattr(grb,'USER_OBJ_LIMIT',None)):
+            self.results.solver.status = SolverStatus.aborted
+            self.results.solver.termination_message = "User specified an objective limit " \
+                                                      "(a bound on either the best objective " \
+                                                      "or the best bound), and that limit has " \
+                                                      "been reached. Solution is available."
+            self.results.solver.termination_condition = TerminationCondition.other
+            soln.status = SolutionStatus.stoppedByLimit
         else:
             self.results.solver.status = SolverStatus.error
-            self.results.solver.termination_message = "Unknown return code from GUROBI."
+            self.results.solver.termination_message = \
+                ("Unhandled Gurobi solve status "
+                 "("+str(status)+")")
             self.results.solver.termination_condition = TerminationCondition.error
             soln.status = SolutionStatus.error
 


Test patch: