Skip to content

Commit

Permalink
Merge branch 'main' into new-pyros-preprocessor-and-subproblems
Browse files Browse the repository at this point in the history
  • Loading branch information
blnicho authored Oct 15, 2024
2 parents 95adf1f + d643e8f commit 6c27f8d
Show file tree
Hide file tree
Showing 12 changed files with 80 additions and 204 deletions.
4 changes: 1 addition & 3 deletions pyomo/contrib/alternative_solutions/lp_enum_solnpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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***")

Expand Down
5 changes: 0 additions & 5 deletions pyomo/contrib/alternative_solutions/solnpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")

Expand Down
18 changes: 11 additions & 7 deletions pyomo/contrib/alternative_solutions/tests/test_lp_enum_solnpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 12 additions & 16 deletions pyomo/contrib/alternative_solutions/tests/test_solnpool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand Down
7 changes: 2 additions & 5 deletions pyomo/contrib/alternative_solutions/tests/test_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
85 changes: 1 addition & 84 deletions pyomo/contrib/pynumero/sparse/block_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
80 changes: 1 addition & 79 deletions pyomo/contrib/pynumero/sparse/mpi_block_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
6 changes: 4 additions & 2 deletions pyomo/core/base/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ 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.
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

Expand Down
2 changes: 1 addition & 1 deletion pyomo/core/expr/numeric_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading

0 comments on commit 6c27f8d

Please sign in to comment.