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") 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 diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index b636dd74203..bcb5e786d08 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -38,7 +38,7 @@ def assert_block_structure(vec): raise NotFullyDefinedBlockVectorError(msg) -class BlockVector(np.ndarray, BaseBlockVector): +class BlockVector(BaseBlockVector, np.ndarray): """ Structured vector interface. This interface can be used to perform operations on vectors composed by vectors. For example, @@ -1592,86 +1592,3 @@ def toMPIBlockVector(self, rank_ownership, mpi_comm, assert_correct_owners=False mpi_bv.set_block(bid, self.get_block(bid)) return mpi_bv - - # the following methods are not supported by blockvector - - def argpartition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.argpartition(self, kth, axis=axis, kind=kind, order=order) - - def argsort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.argsort(self, axis=axis, kind=kind, order=order) - - def byteswap(self, inplace=False): - BaseBlockVector.byteswap(self, inplace=inplace) - - def choose(self, choices, out=None, mode='raise'): - BaseBlockVector.choose(self, choices, out=out, mode=mode) - - def diagonal(self, offset=0, axis1=0, axis2=1): - BaseBlockVector.diagonal(self, offset=offset, axis1=axis1, axis2=axis2) - - def dump(self, file): - BaseBlockVector.dump(self, file) - - def dumps(self): - BaseBlockVector.dumps(self) - - def getfield(self, dtype, offset=0): - BaseBlockVector.getfield(self, dtype, offset=offset) - - def item(self, *args): - BaseBlockVector.item(self, *args) - - def itemset(self, *args): - BaseBlockVector.itemset(self, *args) - - def newbyteorder(self, new_order='S'): - BaseBlockVector.newbyteorder(self, new_order=new_order) - - def put(self, indices, values, mode='raise'): - BaseBlockVector.put(self, indices, values, mode=mode) - - def partition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.partition(self, kth, axis=axis, kind=kind, order=order) - - def repeat(self, repeats, axis=None): - BaseBlockVector.repeat(self, repeats, axis=axis) - - def reshape(self, shape, order='C'): - BaseBlockVector.reshape(self, shape, order=order) - - def resize(self, new_shape, refcheck=True): - BaseBlockVector.resize(self, new_shape, refcheck=refcheck) - - def searchsorted(self, v, side='left', sorter=None): - BaseBlockVector.searchsorted(self, v, side=side, sorter=sorter) - - def setfield(self, val, dtype, offset=0): - BaseBlockVector.setfield(self, val, dtype, offset=offset) - - def setflags(self, write=None, align=None, uic=None): - BaseBlockVector.setflags(self, write=write, align=align, uic=uic) - - def sort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.sort(self, axis=axis, kind=kind, order=order) - - def squeeze(self, axis=None): - BaseBlockVector.squeeze(self, axis=axis) - - def swapaxes(self, axis1, axis2): - BaseBlockVector.swapaxes(self, axis1, axis2) - - def tobytes(self, order='C'): - BaseBlockVector.tobytes(self, order=order) - - def take(self, indices, axis=None, out=None, mode='raise'): - BaseBlockVector.take(self, indices, axis=axis, out=out, mode=mode) - - def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): - raise NotImplementedError('trace not implemented for BlockVector') - - def transpose(*axes): - BaseBlockVector.transpose(*axes) - - def tostring(order='C'): - BaseBlockVector.tostring(order=order) diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py index 89cf136a5f7..f86d450a73e 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py @@ -24,7 +24,7 @@ def assert_block_structure(vec): raise NotFullyDefinedBlockVectorError(msg) -class MPIBlockVector(np.ndarray, BaseBlockVector): +class MPIBlockVector(BaseBlockVector, np.ndarray): """ Parallel structured vector interface. This interface can be used to perform parallel operations on vectors composed by vectors. The main @@ -1447,81 +1447,3 @@ def flatten(self, order='C'): def ravel(self, order='C'): raise RuntimeError('Operation not supported by MPIBlockVector') - - def argpartition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.argpartition(self, kth, axis=axis, kind=kind, order=order) - - def argsort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.argsort(self, axis=axis, kind=kind, order=order) - - def byteswap(self, inplace=False): - BaseBlockVector.byteswap(self, inplace=inplace) - - def choose(self, choices, out=None, mode='raise'): - BaseBlockVector.choose(self, choices, out=out, mode=mode) - - def diagonal(self, offset=0, axis1=0, axis2=1): - BaseBlockVector.diagonal(self, offset=offset, axis1=axis1, axis2=axis2) - - def dump(self, file): - BaseBlockVector.dump(self, file) - - def dumps(self): - BaseBlockVector.dumps(self) - - def getfield(self, dtype, offset=0): - BaseBlockVector.getfield(self, dtype, offset=offset) - - def item(self, *args): - BaseBlockVector.item(self, *args) - - def itemset(self, *args): - BaseBlockVector.itemset(self, *args) - - def newbyteorder(self, new_order='S'): - BaseBlockVector.newbyteorder(self, new_order=new_order) - - def put(self, indices, values, mode='raise'): - BaseBlockVector.put(self, indices, values, mode=mode) - - def partition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.partition(self, kth, axis=axis, kind=kind, order=order) - - def repeat(self, repeats, axis=None): - BaseBlockVector.repeat(self, repeats, axis=axis) - - def reshape(self, shape, order='C'): - BaseBlockVector.reshape(self, shape, order=order) - - def resize(self, new_shape, refcheck=True): - BaseBlockVector.resize(self, new_shape, refcheck=refcheck) - - def searchsorted(self, v, side='left', sorter=None): - BaseBlockVector.searchsorted(self, v, side=side, sorter=sorter) - - def setfield(self, val, dtype, offset=0): - BaseBlockVector.setfield(self, val, dtype, offset=offset) - - def setflags(self, write=None, align=None, uic=None): - BaseBlockVector.setflags(self, write=write, align=align, uic=uic) - - def sort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.sort(self, axis=axis, kind=kind, order=order) - - def squeeze(self, axis=None): - BaseBlockVector.squeeze(self, axis=axis) - - def swapaxes(self, axis1, axis2): - BaseBlockVector.swapaxes(self, axis1, axis2) - - def tobytes(self, order='C'): - BaseBlockVector.tobytes(self, order=order) - - def argmax(self, axis=None, out=None): - BaseBlockVector.argmax(self, axis=axis, out=out) - - def argmin(self, axis=None, out=None): - BaseBlockVector.argmax(self, axis=axis, out=out) - - def take(self, indices, axis=None, out=None, mode='raise'): - BaseBlockVector.take(self, indices, axis=axis, out=out, mode=mode) diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index a5120759236..64f250e9860 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -62,7 +62,7 @@ def __call__(self, exception=True): return arg return arg(exception=exception) - def create_node_with_local_data(self, values): + def create_node_with_local_data(self, values, classtype=None): """ Construct a simple expression after constructing the contained expression. @@ -70,7 +70,9 @@ def create_node_with_local_data(self, values): This class provides a consistent interface for constructing a node, which is used in tree visitor scripts. """ - obj = self.__class__() + if classtype is None: + classtype = self.parent_component()._ComponentDataClass + obj = classtype() obj._args_ = values return obj diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 21896c63219..0e67801ae8a 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1094,7 +1094,7 @@ def create_node_with_local_data(self, args, classtype=None): # types, the simplest / fastest thing to do is just defer to # the operator dispatcher. return operator.mul(*args) - return self.__class__(args) + return classtype(args) class DivisionExpression(NumericExpression): diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index eb16f7c6142..92cb245fa22 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -30,6 +30,7 @@ sum_product, ) from pyomo.core.base.expression import ExpressionData +from pyomo.core.base.objective import ObjectiveData from pyomo.core.expr.compare import compare_expressions, assertExpressionsEqual from pyomo.common.tee import capture_output @@ -290,6 +291,36 @@ def obj_rule(model): self.assertEqual(inst.obj.expr(), 3.0) self.assertEqual(id(inst.obj.expr.arg(1)), id(inst.ec)) + def test_create_node_with_local_data(self): + m = ConcreteModel() + m.x = Var() + + m.e = Expression(expr=m.x) + ee = m.e.create_node_with_local_data([5]) + self.assertIsNot(m.e, ee) + self.assertIs(type(ee), ExpressionData) + self.assertEqual(ee._args_, [5]) + + m.f = Expression([0], rule=lambda m, i: m.x) + ff = m.f[0].create_node_with_local_data([5]) + self.assertIsNot(m.f, ff) + self.assertIsNot(m.f[0], ff) + self.assertIs(type(ff), ExpressionData) + self.assertEqual(ff._args_, [5]) + + m.g = Objective(expr=m.x) + gg = m.g.create_node_with_local_data([5]) + self.assertIsNot(m.g, gg) + self.assertIs(type(gg), ObjectiveData) + self.assertEqual(gg._args_, [5]) + + m.h = Objective([0], rule=lambda m, i: m.x) + hh = m.h[0].create_node_with_local_data([5]) + self.assertIsNot(m.h, hh) + self.assertIsNot(m.h[0], hh) + self.assertIs(type(hh), ObjectiveData) + self.assertEqual(hh._args_, [5]) + class TestExpression(unittest.TestCase): def setUp(self): diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index efb01e6d6ce..ca9fafd482d 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -4313,6 +4313,18 @@ def test_sin(self): total = counter.count - start self.assertEqual(total, 1) + def test_create_node_with_local_data(self): + e = self.m.p * self.m.a + self.assertIs(type(e), MonomialTermExpression) + + f = e.create_node_with_local_data([self.m.b, self.m.p]) + self.assertIs(type(f), MonomialTermExpression) + self.assertStructuredAlmostEqual(f._args_, [self.m.p, self.m.b]) + + g = e.create_node_with_local_data([self.m.b, self.m.p], ProductExpression) + self.assertIs(type(g), ProductExpression) + self.assertStructuredAlmostEqual(g._args_, [self.m.b, self.m.p]) + # # Fixed - Expr has a fixed value diff --git a/pyomo/solvers/tests/mip/test_qp.py b/pyomo/solvers/tests/mip/test_qp.py index 9c5cb5ffbc4..def2d8c91ec 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()