diff --git a/pyomo/contrib/gdpopt/branch_and_bound.py b/pyomo/contrib/gdpopt/branch_and_bound.py index afabdc39123..7cf76ece553 100644 --- a/pyomo/contrib/gdpopt/branch_and_bound.py +++ b/pyomo/contrib/gdpopt/branch_and_bound.py @@ -236,7 +236,7 @@ def _solve_gdp(self, model, config): config.logger.info( 'Final bound values: LB: {} UB: {}'.format(self.LB, self.UB) ) - return self._get_final_results_object() + return self._get_final_pyomo_results_object() # Handle current node if not node_data.is_screened: diff --git a/pyomo/contrib/gdpopt/tests/test_LBB.py b/pyomo/contrib/gdpopt/tests/test_LBB.py index 871b79ecc31..e52932c0a70 100644 --- a/pyomo/contrib/gdpopt/tests/test_LBB.py +++ b/pyomo/contrib/gdpopt/tests/test_LBB.py @@ -20,8 +20,17 @@ from pyomo.common.log import LoggingIntercept import pyomo.contrib.gdpopt.tests.common_tests as ct from pyomo.contrib.satsolver.satsolver import z3_available -from pyomo.environ import SolverFactory, value, ConcreteModel, Var, Objective, maximize -from pyomo.gdp import Disjunction +from pyomo.contrib.gdpopt.branch_and_bound import GDP_LBB_Solver +from pyomo.environ import ( + SolverFactory, + value, + ConcreteModel, + Constraint, + Var, + Objective, + maximize, +) +from pyomo.gdp import Disjunct, Disjunction from pyomo.opt import TerminationCondition currdir = dirname(abspath(__file__)) @@ -35,6 +44,40 @@ ) +class TestGDPopt_LBB_TimeLimit(unittest.TestCase): + """Tests for solver-independent LBB termination paths.""" + + def test_time_limit_returns_pyomo_results_object(self): + m = ConcreteModel() + m.x = Var(bounds=(0, 2)) + m.d1 = Disjunct() + m.d2 = Disjunct() + m.d1.c = Constraint(expr=m.x <= 0.5) + m.d2.c = Constraint(expr=m.x >= 1.5) + m.disj = Disjunction(expr=[m.d1, m.d2]) + m.obj = Objective(expr=m.x) + + orig_reached_time_limit = GDP_LBB_Solver.reached_time_limit + + def force_time_limit(solver, config): + solver.pyomo_results.solver.termination_condition = ( + TerminationCondition.maxTimeLimit + ) + return True + + GDP_LBB_Solver.reached_time_limit = force_time_limit + try: + results = SolverFactory('gdpopt.lbb').solve(m, time_limit=1, tee=False) + finally: + GDP_LBB_Solver.reached_time_limit = orig_reached_time_limit + + self.assertEqual( + results.solver.termination_condition, TerminationCondition.maxTimeLimit + ) + self.assertEqual(results.problem.lower_bound, float('-inf')) + self.assertEqual(results.problem.upper_bound, float('inf')) + + @unittest.skipUnless( solver_available, "Required subsolver %s is not available" % (minlp_solver,) ) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py index 1ba46b08d75..7019f2db2ab 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_no_discrete.py @@ -10,7 +10,9 @@ from unittest.mock import MagicMock, patch -from pyomo.opt import TerminationCondition as tc, SolverStatus +from pyomo.common import timing +from pyomo.contrib.mindtpy.global_outer_approximation import MindtPy_GOA_Solver +from pyomo.opt import TerminationCondition as tc, SolverStatus, SolverResults import pyomo.common.unittest as unittest from pyomo.environ import ( @@ -455,6 +457,23 @@ def test_solver_status_and_message_mirrored(self): self.assertEqual(algo.results.solver.message, "All good") +class TestMindtPyGOATimeLimit(unittest.TestCase): + def test_goa_time_limit_sets_solver_results_condition(self): + solver = MindtPy_GOA_Solver() + solver.config = _SimpleNamespace( + logger=MagicMock(), single_tree=False, time_limit=1 + ) + solver.results = SolverResults() + solver.timing = _SimpleNamespace( + main_timer_start_time=timing.default_timer() - 2 + ) + solver.primal_bound = float('inf') + solver.dual_bound = float('-inf') + + self.assertTrue(solver.reached_time_limit()) + self.assertEqual(solver.results.solver.termination_condition, tc.maxTimeLimit) + + class _FakeLegacyMIPSolver: def __init__( self,