diff --git a/pyomo/opt/results/__init__.py b/pyomo/opt/results/__init__.py index 542fcdd7ba0..0d5a66eb363 100644 --- a/pyomo/opt/results/__init__.py +++ b/pyomo/opt/results/__init__.py @@ -11,7 +11,8 @@ #from old_results import * from pyomo.opt.results.container import * import pyomo.opt.results.problem -from pyomo.opt.results.solver import SolverStatus, TerminationCondition +from pyomo.opt.results.solver import SolverStatus, TerminationCondition, \ + check_optimal_termination, assert_optimal_termination from pyomo.opt.results.problem import ProblemSense from pyomo.opt.results.solution import SolutionStatus, Solution from pyomo.opt.results.results_ import SolverResults diff --git a/pyomo/opt/results/solver.py b/pyomo/opt/results/solver.py index 6d5e4fedea4..ae61067f5f8 100644 --- a/pyomo/opt/results/solver.py +++ b/pyomo/opt/results/solver.py @@ -8,7 +8,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['SolverInformation', 'SolverStatus', 'TerminationCondition'] +__all__ = ['SolverInformation', 'SolverStatus', 'TerminationCondition', 'check_optimal_termination', 'assert_optimal_termination'] from pyutilib.enum import Enum from pyomo.opt.results.container import MapContainer, ScalarType @@ -68,6 +68,44 @@ ) +def check_optimal_termination(results): + """ + This function returns True if the termination condition for the solver + is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' + + Parameters + ---------- + results : Pyomo results object returned from solver.solve + + Returns + ------- + `bool` + """ + if results.solver.status == SolverStatus.ok and \ + (results.solver.termination_condition == TerminationCondition.optimal + or results.solver.termination_condition == TerminationCondition.locallyOptimal + or results.solver.termination_condition == TerminationCondition.globallyOptimal): + return True + return False + + +def assert_optimal_termination(results): + """ + This function checks if the termination condition for the solver + is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' + and it raises a RuntimeError exception if this is not true. + + Parameters + ---------- + results : Pyomo results object returned from solver.solve + """ + if not check_optimal_termination(results): + msg = 'Solver failed to return an optimal solution. ' \ + 'Solver status: {}, Termination condition: {}'.format(results.solver.status, + results.solver.termination_condition) + raise RuntimeError(msg) + + class BranchAndBoundStats(MapContainer): def __init__(self): diff --git a/pyomo/opt/tests/base/test_sol.py b/pyomo/opt/tests/base/test_sol.py index d81221f2816..0f3040cd453 100644 --- a/pyomo/opt/tests/base/test_sol.py +++ b/pyomo/opt/tests/base/test_sol.py @@ -22,7 +22,9 @@ import pyomo.opt from pyomo.opt import (TerminationCondition, SolutionStatus, - SolverStatus) + SolverStatus, + check_optimal_termination, + assert_optimal_termination) old_tempdir = pyutilib.services.TempfileManager.tempdir @@ -60,6 +62,11 @@ def test_infeasible1(self): SolutionStatus.infeasible) self.assertEqual(soln.solver.status, SolverStatus.warning) + + self.assertFalse(check_optimal_termination(soln)) + + with self.assertRaises(RuntimeError): + assert_optimal_termination(soln) def test_infeasible2(self): with pyomo.opt.ReaderFactory("sol") as reader: @@ -84,6 +91,8 @@ def test_conopt_optimal(self): SolutionStatus.optimal) self.assertEqual(soln.solver.status, SolverStatus.ok) + self.assertTrue(check_optimal_termination(soln)) + assert_optimal_termination(soln) def test_bad_options(self): with pyomo.opt.ReaderFactory("sol") as reader: