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

Add branching priorities to CPLEXSHELL #1300

Merged
merged 19 commits into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from 5 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
15 changes: 11 additions & 4 deletions pyomo/solvers/plugins/solvers/CPLEX.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,11 @@ def _write_priorities_file(self, instance):

def _get_suffixes(self, instance):
if isinstance(instance, IBlock):
suffixes = pyomo.core.kernel.suffix.export_suffix_generator(
instance, datatype=Suffix.INT, active=True, descend_into=False
suffixes = (
(suf.name, suf)
for suf in pyomo.core.kernel.suffix.export_suffix_generator(
instance, datatype=Suffix.INT, active=True, descend_into=False
)
)
else:
suffixes = active_export_suffix_generator(instance, datatype=Suffix.INT)
Expand All @@ -254,8 +257,12 @@ def _get_suffixes(self, instance):
def _convert_priorities_to_rows(self, instance, priorities, directions):
if isinstance(instance, IBlock):
smap = getattr(instance, "._symbol_maps")[self._smap_id]
var_is_indexed = lambda var: hasattr(var, 'components')
var_iter = lambda var: var.components()
else:
smap = instance.solutions.symbol_map[self._smap_id]
var_is_indexed = lambda var: var.is_indexed()
var_iter = lambda var: var.values()
byObject = smap.byObject

rows = []
Expand All @@ -268,14 +275,14 @@ def _convert_priorities_to_rows(self, instance, priorities, directions):

var_direction = directions.get(var, BranchDirection.default)

if not var.is_indexed():
Copy link
Member

Choose a reason for hiding this comment

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

I think the easiest general solution is:

if hasattr(var, 'values'):
    # catches Var, IndexedVar, and kernel's variable_dict
    var_iter = var.values()
elif hasattr(var, '__iter__'):
    # catches list and kernel's variable_list and variable_tuple
    var_iter = iter(var)
else:
    # kernel's variable
    var_iter = iter((var,))
for v in var_iter:
    if id(v) not in byObject:
        continue
    v_direction = directions.get(v, var_direction)
    rows.append((byObject[id(v)], priority, v_direction))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @jsiirola! I've added this logic as a general-purpose helper in pyomo.util.components however let me know if you'd rather this logic is kept private to this method or this isn't the best place for it. (To be honest, I'm surprised this hasn't come up before!)

if not var_is_indexed(var):
if id(var) not in byObject:
continue

rows.append((byObject[id(var)], priority, var_direction))
continue

for child_var in var.values():
for child_var in var_iter(var):
if id(child_var) not in byObject:
continue

Expand Down
101 changes: 74 additions & 27 deletions pyomo/solvers/tests/checks/test_cplex.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
import pyutilib
import pyutilib.th as unittest

import pyomo.kernel as pmo
from pyomo.core import Binary, ConcreteModel, Constraint, Objective, Var, Integers, RangeSet, minimize, quicksum, Suffix
from pyomo.environ import *
from pyomo.opt import ProblemFormat, convert_problem, SolverFactory, BranchDirection
from pyomo.solvers.plugins.solvers.CPLEX import CPLEXSHELL, MockCPLEX, _validate_file_name, ORDFileSchema
from pyomo.solvers.plugins.solvers.CPLEX import CPLEXSHELL, MockCPLEX, _validate_file_name


class _mock_cplex_128(object):
Expand Down Expand Up @@ -61,10 +63,10 @@ def test_validate_file_name(self):


class CPLEXShellWritePrioritiesFile(unittest.TestCase):
def setUp(self):
from pyomo.solvers.plugins.converter.model import PyomoMIPConverter # register the `ProblemConverterFactory`
from pyomo.repn.plugins.cpxlp import ProblemWriter_cpxlp # register the `WriterFactory`
""" Unit test on writing of priorities via `CPLEXSHELL._write_priorities_file()` """
suffix_cls = Suffix

def setUp(self):
self.mock_model = self.get_mock_model()
self.mock_cplex_shell = self.get_mock_cplex_shell(self.mock_model)
self.mock_cplex_shell._priorities_file_name = pyutilib.services.TempfileManager.create_tempfile(
Expand Down Expand Up @@ -96,76 +98,99 @@ def get_priorities_file_as_string(self, mock_cplex_shell):
priorities_file = ord_file.read()
return priorities_file

@staticmethod
def _set_suffix_value(suffix, variable, value):
suffix.set_value(variable, value)

def test_write_without_priority_suffix(self):
with self.assertRaises(ValueError):
CPLEXSHELL._write_priorities_file(self.mock_cplex_shell, self.mock_model)

def test_write_priority_to_priorities_file(self):
self.mock_model.priority = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
self.mock_model.priority = self.suffix_cls(direction=Suffix.EXPORT, datatype=Suffix.INT)
priority_val = 10
self.mock_model.priority.set_value(self.mock_model.x, priority_val)
self._set_suffix_value(self.mock_model.priority, self.mock_model.x, priority_val)

CPLEXSHELL._write_priorities_file(self.mock_cplex_shell, self.mock_model)
priorities_file = self.get_priorities_file_as_string(self.mock_cplex_shell)

self.assertEqual(
priorities_file,
"* ENCODING=ISO-8859-1\nNAME Priority Order\n x1 %d\nENDATA\n"
% (priority_val,),
"* ENCODING=ISO-8859-1\n"
"NAME Priority Order\n"
" x1 10\n"
"ENDATA\n"
)

def test_write_priority_and_direction_to_priorities_file(self):
self.mock_model.priority = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
self.mock_model.priority = self.suffix_cls(direction=Suffix.EXPORT, datatype=Suffix.INT)
priority_val = 10
self.mock_model.priority.set_value(self.mock_model.x, priority_val)
self._set_suffix_value(self.mock_model.priority, self.mock_model.x, priority_val)

self.mock_model.direction = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
self.mock_model.direction = self.suffix_cls(direction=Suffix.EXPORT, datatype=Suffix.INT)
direction_val = BranchDirection.down
self.mock_model.direction.set_value(self.mock_model.x, direction_val)
self._set_suffix_value(self.mock_model.direction, self.mock_model.x, direction_val)

CPLEXSHELL._write_priorities_file(self.mock_cplex_shell, self.mock_model)
priorities_file = self.get_priorities_file_as_string(self.mock_cplex_shell)

self.assertEqual(
priorities_file,
"* ENCODING=ISO-8859-1\nNAME Priority Order\n %s x1 %d\nENDATA\n"
% (ORDFileSchema._direction_to_str(direction_val), priority_val),
"* ENCODING=ISO-8859-1\n"
"NAME Priority Order\n"
" DN x1 10\n"
"ENDATA\n"
)

def test_raise_due_to_invalid_priority(self):
self.mock_model.priority = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
self.mock_model.priority.set_value(self.mock_model.x, -1)
self.mock_model.priority = self.suffix_cls(direction=Suffix.EXPORT, datatype=Suffix.INT)
self._set_suffix_value(self.mock_model.priority, self.mock_model.x, -1)
with self.assertRaises(ValueError):
CPLEXSHELL._write_priorities_file(self.mock_cplex_shell, self.mock_model)

self.mock_model.priority.set_value(self.mock_model.x, 1.1)
self._set_suffix_value(self.mock_model.priority, self.mock_model.x, 1.1)
with self.assertRaises(ValueError):
CPLEXSHELL._write_priorities_file(self.mock_cplex_shell, self.mock_model)

def test_use_default_due_to_invalid_direction(self):
self.mock_model.priority = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
self.mock_model.priority = self.suffix_cls(direction=Suffix.EXPORT, datatype=Suffix.INT)
priority_val = 10
self.mock_model.priority.set_value(self.mock_model.x, priority_val)
self._set_suffix_value(self.mock_model.priority, self.mock_model.x, priority_val)

self.mock_model.direction = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
self.mock_model.direction.set_value(
self.mock_model.x, "invalid_branching_direction"
self.mock_model.direction = self.suffix_cls(direction=Suffix.EXPORT, datatype=Suffix.INT)
self._set_suffix_value(
self.mock_model.direction, self.mock_model.x, "invalid_branching_direction"
)

CPLEXSHELL._write_priorities_file(self.mock_cplex_shell, self.mock_model)
priorities_file = self.get_priorities_file_as_string(self.mock_cplex_shell)

self.assertEqual(
priorities_file,
"* ENCODING=ISO-8859-1\nNAME Priority Order\n x1 %d\nENDATA\n"
% (priority_val,),
"* ENCODING=ISO-8859-1\n"
"NAME Priority Order\n"
" x1 10\n"
"ENDATA\n"
)


class CPLEXShellSolvePrioritiesFile(unittest.TestCase):
def setUp(self):
pass
class CPLEXShellWritePrioritiesFileKernel(CPLEXShellWritePrioritiesFile):
suffix_cls = pmo.suffix

@staticmethod
def _set_suffix_value(suffix, variable, value):
suffix[variable] = value

def get_mock_model(self):
model = pmo.block()
model.x = pmo.variable(domain=Binary)
model.con = pmo.constraint(expr=model.x >= 1)
model.obj = pmo.objective(expr=model.x)
return model


class CPLEXShellSolvePrioritiesFile(unittest.TestCase):
""" Integration test on the end-to-end application of priorities via the `Suffix` through a `solve()` """
def get_mock_model_with_priorities(self):
m = ConcreteModel()
m.x = Var(domain=Integers)
Expand Down Expand Up @@ -252,5 +277,27 @@ def test_can_use_manual_priorities_file_with_lp_solve(self):
self.assertEqual(priorities_file, provided_priorities_file)


class CPLEXShellSolvePrioritiesFileKernel(CPLEXShellSolvePrioritiesFile):
def get_mock_model_with_priorities(self):
m = pmo.block()
m.x = pmo.variable(domain=Integers)
m.s = range(10)

m.y = pmo.variable_list(pmo.variable(domain=Integers) for _ in m.s)

m.o = pmo.objective(expr=m.x + sum(m.y), sense=minimize)
m.c = pmo.constraint(expr=m.x >= 1)
m.c2 = pmo.constraint(expr=quicksum(m.y[i] for i in m.s) >= 10)

m.priority = pmo.suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
m.direction = pmo.suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)

m.priority[m.x] = 1
m.priority[m.y] = 2
m.direction[m.y] = BranchDirection.down
m.direction[m.y[-1]] = BranchDirection.up
return m


if __name__ == "__main__":
unittest.main()