From e6a55b26a2b72afb718fa72386f336ea1b807c72 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 14 Oct 2024 16:56:03 -0600 Subject: [PATCH 1/4] Simplify imports in AOS --- pyomo/contrib/alternative_solutions/lp_enum_solnpool.py | 4 +--- pyomo/contrib/alternative_solutions/solnpool.py | 5 ----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/pyomo/contrib/alternative_solutions/lp_enum_solnpool.py b/pyomo/contrib/alternative_solutions/lp_enum_solnpool.py index 1806b96e0ec..5fa9739758e 100644 --- a/pyomo/contrib/alternative_solutions/lp_enum_solnpool.py +++ b/pyomo/contrib/alternative_solutions/lp_enum_solnpool.py @@ -45,9 +45,7 @@ def __init__( self.num_solutions = num_solutions def cut_generator_callback(self, cb_m, cb_opt, cb_where): - from gurobipy import GRB - - if cb_where == GRB.Callback.MIPSOL: + if cb_where == gurobipy.GRB.Callback.MIPSOL: cb_opt.cbGetSolution(vars=self.variables) logger.info("***FOUND SOLUTION***") diff --git a/pyomo/contrib/alternative_solutions/solnpool.py b/pyomo/contrib/alternative_solutions/solnpool.py index 51acb57c8a5..a1ecbc55ba5 100644 --- a/pyomo/contrib/alternative_solutions/solnpool.py +++ b/pyomo/contrib/alternative_solutions/solnpool.py @@ -15,8 +15,6 @@ from pyomo.common.dependencies import attempt_import -gurobipy, gurobipy_available = attempt_import("gurobipy") - import pyomo.environ as pe from pyomo.contrib import appsi import pyomo.contrib.alternative_solutions.aos_utils as aos_utils @@ -67,10 +65,7 @@ def gurobi_generate_solutions( # # Setup gurobi # - if not gurobipy_available: - raise pyomo.common.errors.ApplicationError("Solver (gurobi) not available") opt = appsi.solvers.Gurobi() - if not opt.available(): raise pyomo.common.errors.ApplicationError("Solver (gurobi) not available") From ee580d29a027158030e10c74b33e51bbd8bda1a5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 14 Oct 2024 16:56:55 -0600 Subject: [PATCH 2/4] AOS: guard tests for case where Gurobi is present but not licensed --- .../tests/test_lp_enum_solnpool.py | 18 +++++++----- .../tests/test_solnpool.py | 28 ++++++++----------- .../tests/test_solution.py | 7 ++--- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/pyomo/contrib/alternative_solutions/tests/test_lp_enum_solnpool.py b/pyomo/contrib/alternative_solutions/tests/test_lp_enum_solnpool.py index 6d3b5211f9e..ee9f4657acf 100644 --- a/pyomo/contrib/alternative_solutions/tests/test_lp_enum_solnpool.py +++ b/pyomo/contrib/alternative_solutions/tests/test_lp_enum_solnpool.py @@ -9,25 +9,29 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pyomo.environ as pe -import pyomo.opt +from pyomo.common.dependencies import numpy_available +from pyomo.common import unittest import pyomo.contrib.alternative_solutions.tests.test_cases as tc from pyomo.contrib.alternative_solutions import lp_enum from pyomo.contrib.alternative_solutions import lp_enum_solnpool +from pyomo.opt import check_available_solvers -from pyomo.common.dependencies import attempt_import +import pyomo.environ as pe -numpy, numpy_available = attempt_import("numpy") -gurobipy, gurobi_available = attempt_import("gurobipy") +# lp_enum_solnpool uses both 'gurobi' and 'appsi_gurobi' +gurobi_available = len(check_available_solvers('gurobi', 'appsi_gurobi')) == 2 # # TODO: Setup detailed tests here # -def test_here(): - if numpy_available: +@unittest.skipUnless(gurobi_available, "Gurobi MIP solver not available") +@unittest.skipUnless(numpy_available, "NumPy not found") +class TestLPEnumSolnpool(unittest.TestCase): + + def test_here(self): n = tc.get_pentagonal_pyramid_mip() n.x.domain = pe.Reals n.y.domain = pe.Reals diff --git a/pyomo/contrib/alternative_solutions/tests/test_solnpool.py b/pyomo/contrib/alternative_solutions/tests/test_solnpool.py index 0b9914a86dd..7e601906a69 100644 --- a/pyomo/contrib/alternative_solutions/tests/test_solnpool.py +++ b/pyomo/contrib/alternative_solutions/tests/test_solnpool.py @@ -9,21 +9,17 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.common.dependencies import numpy as numpy, numpy_available - -if numpy_available: - from numpy.testing import assert_array_almost_equal -from pyomo.common.dependencies import attempt_import - -gurobipy, gurobipy_available = attempt_import("gurobipy") - from collections import Counter -import pyomo.environ as pe +from pyomo.common.dependencies import numpy as np, numpy_available from pyomo.common import unittest - from pyomo.contrib.alternative_solutions import gurobi_generate_solutions +from pyomo.contrib.appsi.solvers import Gurobi + import pyomo.contrib.alternative_solutions.tests.test_cases as tc +import pyomo.environ as pe + +gurobipy_available = Gurobi().available() @unittest.skipIf(not gurobipy_available, "Gurobi MIP solver not available") @@ -51,7 +47,7 @@ def test_ip_feasibility(self): objectives = [round(result.objective[1], 2) for result in results] actual_solns_by_obj = m.num_ranked_solns unique_solns_by_obj = [val for val in Counter(objectives).values()] - assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) + np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) @unittest.skipIf(not numpy_available, "Numpy not installed") def test_ip_num_solutions(self): @@ -66,7 +62,7 @@ def test_ip_num_solutions(self): objectives = [round(result.objective[1], 2) for result in results] actual_solns_by_obj = [6, 2] unique_solns_by_obj = [val for val in Counter(objectives).values()] - assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) + np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) @unittest.skipIf(not numpy_available, "Numpy not installed") def test_mip_feasibility(self): @@ -80,7 +76,7 @@ def test_mip_feasibility(self): objectives = [round(result.objective[1], 2) for result in results] actual_solns_by_obj = m.num_ranked_solns unique_solns_by_obj = [val for val in Counter(objectives).values()] - assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) + np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) @unittest.skipIf(not numpy_available, "Numpy not installed") def test_mip_rel_feasibility(self): @@ -95,7 +91,7 @@ def test_mip_rel_feasibility(self): objectives = [round(result.objective[1], 2) for result in results] actual_solns_by_obj = m.num_ranked_solns[0:2] unique_solns_by_obj = [val for val in Counter(objectives).values()] - assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) + np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) @unittest.skipIf(not numpy_available, "Numpy not installed") def test_mip_rel_feasibility_options(self): @@ -112,7 +108,7 @@ def test_mip_rel_feasibility_options(self): objectives = [round(result.objective[1], 2) for result in results] actual_solns_by_obj = m.num_ranked_solns[0:2] unique_solns_by_obj = [val for val in Counter(objectives).values()] - assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) + np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) @unittest.skipIf(not numpy_available, "Numpy not installed") def test_mip_abs_feasibility(self): @@ -127,7 +123,7 @@ def test_mip_abs_feasibility(self): objectives = [round(result.objective[1], 2) for result in results] actual_solns_by_obj = m.num_ranked_solns[0:3] unique_solns_by_obj = [val for val in Counter(objectives).values()] - assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) + np.testing.assert_array_almost_equal(unique_solns_by_obj, actual_solns_by_obj) @unittest.skipIf(True, "Ignoring fragile test for solver timeout.") def test_mip_no_time(self): diff --git a/pyomo/contrib/alternative_solutions/tests/test_solution.py b/pyomo/contrib/alternative_solutions/tests/test_solution.py index 9df9374daef..a3ef042b5fe 100644 --- a/pyomo/contrib/alternative_solutions/tests/test_solution.py +++ b/pyomo/contrib/alternative_solutions/tests/test_solution.py @@ -15,8 +15,8 @@ import pyomo.contrib.alternative_solutions.aos_utils as au from pyomo.contrib.alternative_solutions import Solution -pyomo.opt.check_available_solvers("gurobi") mip_solver = "gurobi" +mip_available = pyomo.opt.check_available_solvers(mip_solver) class TestSolutionUnit(unittest.TestCase): @@ -40,10 +40,7 @@ def get_model(self): m.con_z = pe.Constraint(expr=m.z <= 3) return m - @unittest.skipUnless( - pe.SolverFactory(mip_solver).available(exception_flag=False), - "MIP solver not available", - ) + @unittest.skipUnless(mip_available, "MIP solver not available") def test_solution(self): """ Create a Solution Object, call its functions, and ensure the correct From 9405974bbb2f206e0ca5d32cea6daa254a2ed6fe Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 14 Oct 2024 16:57:17 -0600 Subject: [PATCH 3/4] Guard tests for case where gurobi is present but not licensed --- pyomo/solvers/tests/mip/test_qp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/tests/mip/test_qp.py b/pyomo/solvers/tests/mip/test_qp.py index 9c5cb5ffbc4..f41888a680f 100644 --- a/pyomo/solvers/tests/mip/test_qp.py +++ b/pyomo/solvers/tests/mip/test_qp.py @@ -53,7 +53,8 @@ def _qp_model(self): return m @unittest.skipUnless( - gurobi_lp.available(exception_flag=False), "needs Gurobi LP interface" + gurobi_lp.available(exception_flag=False) and gurobi_lp.license_is_valid(), + "needs Gurobi LP interface" ) def test_qp_objective_gurobi_lp(self): m = self._qp_model() @@ -61,7 +62,8 @@ def test_qp_objective_gurobi_lp(self): self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) @unittest.skipUnless( - gurobi_nl.available(exception_flag=False), "needs Gurobi NL interface" + gurobi_nl.available(exception_flag=False) and gurobi_nl.license_is_valid(), + "needs Gurobi NL interface" ) def test_qp_objective_gurobi_nl(self): m = self._qp_model() From fd3514f59efbf78b78a983e6ff4e58f074ad2a4b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 14 Oct 2024 17:01:26 -0600 Subject: [PATCH 4/4] NFC: apply black --- pyomo/solvers/tests/mip/test_qp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/tests/mip/test_qp.py b/pyomo/solvers/tests/mip/test_qp.py index f41888a680f..def2d8c91ec 100644 --- a/pyomo/solvers/tests/mip/test_qp.py +++ b/pyomo/solvers/tests/mip/test_qp.py @@ -54,7 +54,7 @@ def _qp_model(self): @unittest.skipUnless( gurobi_lp.available(exception_flag=False) and gurobi_lp.license_is_valid(), - "needs Gurobi LP interface" + "needs Gurobi LP interface", ) def test_qp_objective_gurobi_lp(self): m = self._qp_model() @@ -63,7 +63,7 @@ def test_qp_objective_gurobi_lp(self): @unittest.skipUnless( gurobi_nl.available(exception_flag=False) and gurobi_nl.license_is_valid(), - "needs Gurobi NL interface" + "needs Gurobi NL interface", ) def test_qp_objective_gurobi_nl(self): m = self._qp_model()