Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Gurobi license checks in tests #3011

Merged
merged 6 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pyomo/contrib/cp/tests/test_logical_to_disjunctive.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@
TransformationFactory,
)

gurobi_available = SolverFactory('gurobi').available(exception_flag=False)
gurobi_available = (
SolverFactory('gurobi').available(exception_flag=False)
and SolverFactory('gurobi').license_is_valid()
)


class TestLogicalToDisjunctiveVisitor(unittest.TestCase):
Expand Down
28 changes: 15 additions & 13 deletions pyomo/contrib/gdpopt/tests/test_enumerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@


@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi not available')
@unittest.skipUnless(SolverFactory('gurobi').license_is_valid(), 'Gurobi not licensed')
class TestGDPoptEnumerate(unittest.TestCase):
def test_solve_two_term_disjunction(self):
m = models.makeTwoTermDisj()
Expand Down Expand Up @@ -144,18 +145,6 @@ def test_stop_at_iteration_limit(self):
results.solver.termination_condition, TerminationCondition.maxIterations
)

@unittest.skipUnless(SolverFactory('ipopt').available(), 'Ipopt not available')
def test_infeasible_GDP(self):
m = models.make_infeasible_gdp_model()

results = SolverFactory('gdpopt.enumerate').solve(m)

self.assertEqual(results.solver.iterations, 2)
self.assertEqual(
results.solver.termination_condition, TerminationCondition.infeasible
)
self.assertEqual(results.problem.lower_bound, float('inf'))

def test_unbounded_GDP(self):
m = ConcreteModel()
m.x = Var(bounds=(-1, 10))
Expand All @@ -173,7 +162,20 @@ def test_unbounded_GDP(self):
self.assertEqual(results.problem.lower_bound, -float('inf'))
self.assertEqual(results.problem.upper_bound, -float('inf'))

@unittest.skipUnless(SolverFactory('ipopt').available(), 'Ipopt not available')

@unittest.skipUnless(SolverFactory('ipopt').available(), 'Ipopt not available')
class TestGDPoptEnumerate_ipopt_tests(unittest.TestCase):
def test_infeasible_GDP(self):
m = models.make_infeasible_gdp_model()

results = SolverFactory('gdpopt.enumerate').solve(m)

self.assertEqual(results.solver.iterations, 2)
self.assertEqual(
results.solver.termination_condition, TerminationCondition.infeasible
)
self.assertEqual(results.problem.lower_bound, float('inf'))

def test_algorithm_specified_to_solve(self):
m = models.twoDisj_twoCircles_easy()

Expand Down
19 changes: 11 additions & 8 deletions pyomo/contrib/gdpopt/tests/test_gdpopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@
else False
)

gurobi_available = (
SolverFactory('gurobi').available(exception_flag=False)
and SolverFactory('gurobi').license_is_valid()
)


class TestGDPoptUnit(unittest.TestCase):
"""Real unit tests for GDPopt"""
Expand Down Expand Up @@ -129,7 +134,7 @@ def test_solve_lp(self):
self.assertAlmostEqual(results.problem.lower_bound, 1)
self.assertAlmostEqual(results.problem.upper_bound, 1)

@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi not available')
@unittest.skipUnless(gurobi_available, 'Gurobi not available')
def test_solve_nlp(self):
m = ConcreteModel()
m.x = Var(bounds=(-5, 5))
Expand Down Expand Up @@ -222,7 +227,7 @@ def test_is_feasible_function(self):
with self.assertRaisesRegex(NotImplementedError, "Found active disjunct"):
is_feasible(m, GDP_LOA_Solver.CONFIG())

@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi not available')
@unittest.skipUnless(gurobi_available, 'Gurobi not available')
def test_infeasible_or_unbounded_mip_termination(self):
m = ConcreteModel()
m.x = Var()
Expand Down Expand Up @@ -461,9 +466,7 @@ def test_unbounded_gdp_maximization(self):
# [ESJ 5/16/22]: Using Gurobi for this test because glpk seems to get angry
# on Windows when the MIP is arbitrarily bounded with the large bounds. And
# I think I blame glpk...
@unittest.skipUnless(
SolverFactory('gurobi').available(), "Gurobi solver not available"
)
@unittest.skipUnless(gurobi_available, "Gurobi solver not available")
def test_GDP_nonlinear_objective(self):
m = ConcreteModel()
m.x = Var(bounds=(-1, 10))
Expand Down Expand Up @@ -1672,7 +1675,7 @@ def make_model(self):
return m

@unittest.skipIf(not mcpp_available(), "MC++ is not available")
@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi not available')
@unittest.skipUnless(gurobi_available, 'Gurobi not available')
def test_set_options_on_config_block(self):
m = self.make_model()

Expand Down Expand Up @@ -1711,7 +1714,7 @@ def test_set_options_on_config_block(self):
self.assertAlmostEqual(value(m.obj), -0.25)

@unittest.skipIf(not mcpp_available(), "MC++ is not available")
@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi not available')
@unittest.skipUnless(gurobi_available, 'Gurobi not available')
def test_set_options_in_init(self):
m = self.make_model()

Expand All @@ -1735,7 +1738,7 @@ def test_set_options_in_init(self):
self.assertAlmostEqual(value(m.obj), -0.25)
self.assertEqual(opt.config.mip_solver, 'gurobi')

@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi not available')
@unittest.skipUnless(gurobi_available, 'Gurobi not available')
def test_no_default_algorithm(self):
m = self.make_model()

Expand Down
1 change: 1 addition & 0 deletions pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def test_descend_into_expressions_objective_target(self):
)

@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi is not available')
@unittest.skipUnless(SolverFactory('gurobi').license_is_valid(), 'No license')
def test_solve_disaggregated_convex_combo_model(self):
m = models.make_log_x_model()
TransformationFactory(
Expand Down
7 changes: 3 additions & 4 deletions pyomo/contrib/piecewise/tests/test_outer_repn_gdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,9 @@ def test_descend_into_expressions_objective_target(self):
self, 'contrib.piecewise.outer_repn_gdp'
)

@unittest.skipUnless(
SolverFactory('gurobi').available() and scipy_available,
'Gurobi and/or scipy is not available',
)
@unittest.skipUnless(scipy_available, "scipy is not available")
@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi is not available')
@unittest.skipUnless(SolverFactory('gurobi').license_is_valid(), 'No license')
def test_solve_multiple_choice_model(self):
m = models.make_log_x_model()
TransformationFactory('contrib.piecewise.multiple_choice').apply_to(m)
Expand Down
1 change: 1 addition & 0 deletions pyomo/contrib/piecewise/tests/test_reduced_inner_repn.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def test_descend_into_expressions_objective_target(self):
)

@unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi is not available')
@unittest.skipUnless(SolverFactory('gurobi').license_is_valid(), 'No license')
def test_solve_convex_combo_model(self):
m = models.make_log_x_model()
TransformationFactory('contrib.piecewise.convex_combination').apply_to(m)
Expand Down
5 changes: 4 additions & 1 deletion pyomo/gdp/tests/test_mbigm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@
)
from pyomo.repn import generate_standard_repn

gurobi_available = SolverFactory('gurobi').available()
gurobi_available = (
SolverFactory('gurobi').available(exception_flag=False)
and SolverFactory('gurobi').license_is_valid()
)
exdir = normpath(join(PYOMO_ROOT_DIR, 'examples', 'gdp'))


Expand Down
21 changes: 20 additions & 1 deletion pyomo/solvers/plugins/solvers/GUROBI.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from pyomo.common import Executable
from pyomo.common.collections import Bunch
from pyomo.common.fileutils import this_file_dir
from pyomo.common.tee import capture_output
from pyomo.common.tempfiles import TempfileManager

from pyomo.opt.base import ProblemFormat, ResultsFormat, OptSolver
Expand All @@ -32,8 +33,10 @@
)
from pyomo.opt.solver import ILMLicensedSystemCallSolver
from pyomo.core.kernel.block import IBlock
from pyomo.core import ConcreteModel, Var, Objective

from .gurobi_direct import gurobipy_available
from .ASL import ASL

logger = logging.getLogger('pyomo.solvers')

Expand Down Expand Up @@ -69,14 +72,30 @@ def __new__(cls, *args, **kwds):
if mode == 'os':
opt = SolverFactory('_ossolver', **kwds)
elif mode == 'nl':
opt = SolverFactory('asl', **kwds)
opt = SolverFactory('_gurobi_nl', **kwds)
else:
logger.error('Unknown IO type: %s' % mode)
return
opt.set_options('solver=gurobi_ampl')
return opt


@SolverFactory.register('_gurobi_nl', doc='NL interface to the Gurobi solver')
class GUROBINL(ASL):
"""NL interface to gurobi_ampl."""

def license_is_valid(self):
m = ConcreteModel()
m.x = Var(bounds=(1, 2))
m.obj = Objective(expr=m.x)
try:
with capture_output():
self.solve(m)
return abs(m.x.value - 1) <= 1e-4
except:
return False


@SolverFactory.register(
'_gurobi_shell', doc='Shell interface to the GUROBI LP/MIP solver'
)
Expand Down
5 changes: 5 additions & 0 deletions pyomo/solvers/tests/checks/test_gurobi_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
except ImportError:
gurobipy_available = False

gurobi_available = GurobiDirect().available(exception_flag=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this one check the license when you ask about availability? Because if not the changes in this file don't make sense to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. gurobipy_available only checks that the gurobipy python module is importable. The gurobi_available test checks the license.



def clean_up_global_state():
# Best efforts to dispose any gurobipy objects from previous tests
Expand Down Expand Up @@ -79,6 +81,7 @@ def test_gurobipy_not_installed(self):


@unittest.skipIf(not gurobipy_available, "gurobipy is not available")
@unittest.skipIf(not gurobi_available, "gurobi license is not valid")
class GurobiParameterTests(GurobiBase):
# Test parameter handling at the model and environment level

Expand Down Expand Up @@ -158,6 +161,7 @@ def test_param_changes_4(self):


@unittest.skipIf(not gurobipy_available, "gurobipy is not available")
@unittest.skipIf(not gurobi_available, "gurobi license is not valid")
class GurobiEnvironmentTests(GurobiBase):
# Test handling of gurobi environments

Expand Down Expand Up @@ -318,6 +322,7 @@ def test_nonmanaged_env(self):


@unittest.skipIf(not gurobipy_available, "gurobipy is not available")
@unittest.skipIf(not gurobi_available, "gurobi license is not valid")
@unittest.skipIf(not single_use_license(), reason="test needs a single use license")
class GurobiSingleUseTests(GurobiBase):
# Integration tests for Gurobi single-use licenses (useful for checking all Gurobi
Expand Down