From 77890deef30d2996042ac1b596a6152b38db0f63 Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Tue, 10 Mar 2020 15:07:10 -0500 Subject: [PATCH 1/5] Added helper function to retrieve solver success (or not) --- pyomo/opt/results/__init__.py | 2 +- pyomo/opt/results/solver.py | 19 ++++++++++++++++++- pyomo/opt/tests/base/test_sol.py | 6 +++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pyomo/opt/results/__init__.py b/pyomo/opt/results/__init__.py index 542fcdd7ba0..d1a8bd4c2de 100644 --- a/pyomo/opt/results/__init__.py +++ b/pyomo/opt/results/__init__.py @@ -11,7 +11,7 @@ #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, solve_optimal 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..9da14bafd26 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', 'solve_optimal'] from pyutilib.enum import Enum from pyomo.opt.results.container import MapContainer, ScalarType @@ -68,6 +68,23 @@ ) +def solve_optimal(results): + """ + This function returns True if the solve returned an optimal solution + + Parameters + ---------- + results : Pyomo results object returned from solver.solve + + Returns + ------- + `bool` + """ + if results.solver.status == SolverStatus.ok and \ + results.solver.termination_condition == TerminationCondition.optimal: + return True + return False + 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..d11d4414532 100644 --- a/pyomo/opt/tests/base/test_sol.py +++ b/pyomo/opt/tests/base/test_sol.py @@ -22,7 +22,8 @@ import pyomo.opt from pyomo.opt import (TerminationCondition, SolutionStatus, - SolverStatus) + SolverStatus, + solve_optimal) old_tempdir = pyutilib.services.TempfileManager.tempdir @@ -60,6 +61,7 @@ def test_infeasible1(self): SolutionStatus.infeasible) self.assertEqual(soln.solver.status, SolverStatus.warning) + self.assertFalse(solve_optimal(soln)) def test_infeasible2(self): with pyomo.opt.ReaderFactory("sol") as reader: @@ -84,6 +86,8 @@ def test_conopt_optimal(self): SolutionStatus.optimal) self.assertEqual(soln.solver.status, SolverStatus.ok) + self.assertTrue(solve_optimal(soln)) + def test_bad_options(self): with pyomo.opt.ReaderFactory("sol") as reader: From bea50cbc8507ba97991a88411f6b91c558a9f2fc Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Tue, 10 Mar 2020 16:04:30 -0500 Subject: [PATCH 2/5] Added checks for locally and globally optimal as well --- pyomo/opt/results/solver.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/opt/results/solver.py b/pyomo/opt/results/solver.py index 9da14bafd26..d97a7a71194 100644 --- a/pyomo/opt/results/solver.py +++ b/pyomo/opt/results/solver.py @@ -70,7 +70,8 @@ def solve_optimal(results): """ - This function returns True if the solve returned an optimal solution + This function returns True if the termination condition for the solver + is 'optimal', 'lcoallyOptimal', or 'globallyOptimal', and the status is 'ok' Parameters ---------- @@ -81,7 +82,9 @@ def solve_optimal(results): `bool` """ if results.solver.status == SolverStatus.ok and \ - results.solver.termination_condition == TerminationCondition.optimal: + (results.solver.termination_condition == TerminationCondition.optimal + or results.solver.termination_condition == TerminationCondition.locallyOptimal + or results.solver.termination_condition == TerminationCondition.globallyOptimal): return True return False From f866369e0b261aeb5a64c11c303b14a80dba9ce1 Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Tue, 10 Mar 2020 18:15:06 -0500 Subject: [PATCH 3/5] changing name to optimal_termination from solve_optimal --- pyomo/opt/results/__init__.py | 2 +- pyomo/opt/results/solver.py | 6 +++--- pyomo/opt/tests/base/test_sol.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/opt/results/__init__.py b/pyomo/opt/results/__init__.py index d1a8bd4c2de..b1081f35a59 100644 --- a/pyomo/opt/results/__init__.py +++ b/pyomo/opt/results/__init__.py @@ -11,7 +11,7 @@ #from old_results import * from pyomo.opt.results.container import * import pyomo.opt.results.problem -from pyomo.opt.results.solver import SolverStatus, TerminationCondition, solve_optimal +from pyomo.opt.results.solver import SolverStatus, TerminationCondition, 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 d97a7a71194..3be7ee10139 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', 'solve_optimal'] +__all__ = ['SolverInformation', 'SolverStatus', 'TerminationCondition', 'optimal_termination'] from pyutilib.enum import Enum from pyomo.opt.results.container import MapContainer, ScalarType @@ -68,10 +68,10 @@ ) -def solve_optimal(results): +def optimal_termination(results): """ This function returns True if the termination condition for the solver - is 'optimal', 'lcoallyOptimal', or 'globallyOptimal', and the status is 'ok' + is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' Parameters ---------- diff --git a/pyomo/opt/tests/base/test_sol.py b/pyomo/opt/tests/base/test_sol.py index d11d4414532..e83069a251f 100644 --- a/pyomo/opt/tests/base/test_sol.py +++ b/pyomo/opt/tests/base/test_sol.py @@ -23,7 +23,7 @@ from pyomo.opt import (TerminationCondition, SolutionStatus, SolverStatus, - solve_optimal) + optimal_termination) old_tempdir = pyutilib.services.TempfileManager.tempdir @@ -61,7 +61,7 @@ def test_infeasible1(self): SolutionStatus.infeasible) self.assertEqual(soln.solver.status, SolverStatus.warning) - self.assertFalse(solve_optimal(soln)) + self.assertFalse(optimal_termination(soln)) def test_infeasible2(self): with pyomo.opt.ReaderFactory("sol") as reader: @@ -86,7 +86,7 @@ def test_conopt_optimal(self): SolutionStatus.optimal) self.assertEqual(soln.solver.status, SolverStatus.ok) - self.assertTrue(solve_optimal(soln)) + self.assertTrue(optimal_termination(soln)) def test_bad_options(self): From d191edf6a83bf84b00f4c8845201897937ed5724 Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Wed, 11 Mar 2020 10:43:51 -0500 Subject: [PATCH 4/5] added exception option so this would be a one-line check for most users --- pyomo/opt/results/__init__.py | 2 +- pyomo/opt/results/solver.py | 11 +++++++++-- pyomo/opt/tests/base/test_sol.py | 15 ++++++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pyomo/opt/results/__init__.py b/pyomo/opt/results/__init__.py index b1081f35a59..12d29f37cb3 100644 --- a/pyomo/opt/results/__init__.py +++ b/pyomo/opt/results/__init__.py @@ -11,7 +11,7 @@ #from old_results import * from pyomo.opt.results.container import * import pyomo.opt.results.problem -from pyomo.opt.results.solver import SolverStatus, TerminationCondition, optimal_termination +from pyomo.opt.results.solver import SolverStatus, TerminationCondition, check_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 3be7ee10139..63dc614bd0b 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', 'optimal_termination'] +__all__ = ['SolverInformation', 'SolverStatus', 'TerminationCondition', 'check_optimal_termination'] from pyutilib.enum import Enum from pyomo.opt.results.container import MapContainer, ScalarType @@ -68,7 +68,7 @@ ) -def optimal_termination(results): +def check_optimal_termination(results, suppress_exception=False): """ This function returns True if the termination condition for the solver is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' @@ -86,8 +86,15 @@ def optimal_termination(results): or results.solver.termination_condition == TerminationCondition.locallyOptimal or results.solver.termination_condition == TerminationCondition.globallyOptimal): return True + + if not suppress_exception: + msg = 'Solver failed to return an optimal solution. ' \ + 'Solver status: {}, Termination condition: {}'.format(results.solver.status, + results.solver.termination_condition) + raise RuntimeError(msg) return False + 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 e83069a251f..1b150ec3ef2 100644 --- a/pyomo/opt/tests/base/test_sol.py +++ b/pyomo/opt/tests/base/test_sol.py @@ -23,7 +23,7 @@ from pyomo.opt import (TerminationCondition, SolutionStatus, SolverStatus, - optimal_termination) + check_optimal_termination) old_tempdir = pyutilib.services.TempfileManager.tempdir @@ -61,7 +61,14 @@ def test_infeasible1(self): SolutionStatus.infeasible) self.assertEqual(soln.solver.status, SolverStatus.warning) - self.assertFalse(optimal_termination(soln)) + + self.assertFalse(check_optimal_termination(soln, suppress_exception=True)) + + with self.assertRaises(RuntimeError): + check_optimal_termination(soln, suppress_exception=False) + + with self.assertRaises(RuntimeError): + check_optimal_termination(soln) def test_infeasible2(self): with pyomo.opt.ReaderFactory("sol") as reader: @@ -86,7 +93,9 @@ def test_conopt_optimal(self): SolutionStatus.optimal) self.assertEqual(soln.solver.status, SolverStatus.ok) - self.assertTrue(optimal_termination(soln)) + self.assertTrue(check_optimal_termination(soln)) + self.assertTrue(check_optimal_termination(soln, suppress_exception=True)) + self.assertTrue(check_optimal_termination(soln, suppress_exception=False)) def test_bad_options(self): From cff363fd452cfe40a4c1e2f1054c981b245654a5 Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Thu, 12 Mar 2020 19:42:03 -0500 Subject: [PATCH 5/5] Renaming according to Qis suggestion - now there are two methods - one that checks and one that asserts --- pyomo/opt/results/__init__.py | 3 ++- pyomo/opt/results/solver.py | 21 ++++++++++++++++----- pyomo/opt/tests/base/test_sol.py | 14 +++++--------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/pyomo/opt/results/__init__.py b/pyomo/opt/results/__init__.py index 12d29f37cb3..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, check_optimal_termination +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 63dc614bd0b..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', 'check_optimal_termination'] +__all__ = ['SolverInformation', 'SolverStatus', 'TerminationCondition', 'check_optimal_termination', 'assert_optimal_termination'] from pyutilib.enum import Enum from pyomo.opt.results.container import MapContainer, ScalarType @@ -68,7 +68,7 @@ ) -def check_optimal_termination(results, suppress_exception=False): +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' @@ -86,14 +86,25 @@ def check_optimal_termination(results, suppress_exception=False): 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. - if not suppress_exception: + 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) - return False - + class BranchAndBoundStats(MapContainer): diff --git a/pyomo/opt/tests/base/test_sol.py b/pyomo/opt/tests/base/test_sol.py index 1b150ec3ef2..0f3040cd453 100644 --- a/pyomo/opt/tests/base/test_sol.py +++ b/pyomo/opt/tests/base/test_sol.py @@ -23,7 +23,8 @@ from pyomo.opt import (TerminationCondition, SolutionStatus, SolverStatus, - check_optimal_termination) + check_optimal_termination, + assert_optimal_termination) old_tempdir = pyutilib.services.TempfileManager.tempdir @@ -62,13 +63,10 @@ def test_infeasible1(self): self.assertEqual(soln.solver.status, SolverStatus.warning) - self.assertFalse(check_optimal_termination(soln, suppress_exception=True)) + self.assertFalse(check_optimal_termination(soln)) with self.assertRaises(RuntimeError): - check_optimal_termination(soln, suppress_exception=False) - - with self.assertRaises(RuntimeError): - check_optimal_termination(soln) + assert_optimal_termination(soln) def test_infeasible2(self): with pyomo.opt.ReaderFactory("sol") as reader: @@ -94,9 +92,7 @@ def test_conopt_optimal(self): self.assertEqual(soln.solver.status, SolverStatus.ok) self.assertTrue(check_optimal_termination(soln)) - self.assertTrue(check_optimal_termination(soln, suppress_exception=True)) - self.assertTrue(check_optimal_termination(soln, suppress_exception=False)) - + assert_optimal_termination(soln) def test_bad_options(self): with pyomo.opt.ReaderFactory("sol") as reader: