Skip to content

Commit

Permalink
Merge branch 'main' into valsetindex
Browse files Browse the repository at this point in the history
  • Loading branch information
jsiirola authored Aug 12, 2024
2 parents b24f0a4 + 7f779ab commit 4d825f6
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 67 deletions.
37 changes: 35 additions & 2 deletions pyomo/contrib/cp/tests/test_logical_to_disjunctive.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,41 @@ def test_equivalence(self):

assertExpressionsEqual(self, m.cons[10].expr, m.z[5] >= 1)

def test_equivalent_to_True(self):
m = self.make_model()
e = m.a.equivalent_to(True)

visitor = LogicalToDisjunctiveVisitor()
m.cons = visitor.constraints
m.z = visitor.z_vars

visitor.walk_expression(e)

self.assertIs(m.a.get_associated_binary(), m.z[1])
self.assertEqual(len(m.z), 4)
self.assertEqual(len(m.cons), 10)

# z[2] == !a v True
assertExpressionsEqual(
self, m.cons[1].expr, (1 - m.z[2]) + (1 - m.z[1]) + 1 >= 1
)
assertExpressionsEqual(self, m.cons[2].expr, 1 - (1 - m.z[1]) + m.z[2] >= 1)
assertExpressionsEqual(self, m.cons[3].expr, m.z[2] + (1 - 1) >= 1)

# z[3] == a v ! c
assertExpressionsEqual(self, m.cons[4].expr, (1 - m.z[3]) + m.z[1] >= 1)
assertExpressionsEqual(self, m.cons[5].expr, m.z[3] + (1 - m.z[1]) >= 1)
assertExpressionsEqual(self, m.cons[6].expr, m.z[3] + 1 >= 1)

# z[4] == z[2] ^ z[3]
assertExpressionsEqual(self, m.cons[7].expr, m.z[4] <= m.z[2])
assertExpressionsEqual(self, m.cons[8].expr, m.z[4] <= m.z[3])
assertExpressionsEqual(
self, m.cons[9].expr, 1 - m.z[4] <= 2 - (m.z[2] + m.z[3])
)

assertExpressionsEqual(self, m.cons[10].expr, m.z[4] >= 1)

def test_xor(self):
m = self.make_model()
e = m.a.xor(m.b)
Expand Down Expand Up @@ -263,8 +298,6 @@ def test_at_most(self):
# z3 = a ^ b
assertExpressionsEqual(self, m.cons[1].expr, m.z[3] <= a)
assertExpressionsEqual(self, m.cons[2].expr, m.z[3] <= b)
m.cons.pprint()
print(m.cons[3].expr)
assertExpressionsEqual(self, m.cons[3].expr, 1 - m.z[3] <= 2 - sum([a, b]))

# atmost in disjunctive form
Expand Down
15 changes: 7 additions & 8 deletions pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,7 @@ def _dispatch_var(visitor, node):


def _dispatch_param(visitor, node):
if int(value(node)) == value(node):
return False, node
else:
raise ValueError(
"Found non-integer valued Param '%s' in a logical "
"expression. This cannot be written to a disjunctive "
"form." % node.name
)
return False, node


def _dispatch_expression(visitor, node):
Expand Down Expand Up @@ -244,6 +237,12 @@ def initializeWalker(self, expr):

def beforeChild(self, node, child, child_idx):
if child.__class__ in EXPR.native_types:
if child.__class__ is bool:
# If we encounter a bool, we are going to need to treat it as
# binary explicitly because we are finally pedantic enough in the
# expression system to not allow some of the mixing we will need
# (like summing a LinearExpression with a bool)
return False, int(child)
return False, child

if child.is_numeric_type():
Expand Down
22 changes: 16 additions & 6 deletions pyomo/contrib/solver/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,15 +353,20 @@ def __init__(self, **kwargs):
raise NotImplementedError('Still working on this')
# There is no reason for a user to be trying to mix both old
# and new options. That is silly. So we will yell at them.
self.options = kwargs.pop('options', None)
_options = kwargs.pop('options', None)
if 'solver_options' in kwargs:
if self.options is not None:
if _options is not None:
raise ValueError(
"Both 'options' and 'solver_options' were requested. "
"Please use one or the other, not both."
)
self.options = kwargs.pop('solver_options')
_options = kwargs.pop('solver_options')
if _options is not None:
kwargs['solver_options'] = _options
super().__init__(**kwargs)
# Make the legacy 'options' attribute an alias of the new
# config.solver_options
self.options = self.config.solver_options

#
# Support "with" statements
Expand All @@ -372,6 +377,14 @@ def __enter__(self):
def __exit__(self, t, v, traceback):
"""Exit statement - enables `with` statements."""

def __setattr__(self, attr, value):
# 'options' and 'config' are really singleton attributes. Map
# any assignment to set_value()
if attr in ('options', 'config') and attr in self.__dict__:
getattr(self, attr).set_value(value)
else:
super().__setattr__(attr, value)

def _map_config(
self,
tee=NOTSET,
Expand All @@ -390,7 +403,6 @@ def _map_config(
writer_config=NOTSET,
):
"""Map between legacy and new interface configuration options"""
self.config = self.config()
if 'report_timing' not in self.config:
self.config.declare(
'report_timing', ConfigValue(domain=bool, default=False)
Expand All @@ -405,8 +417,6 @@ def _map_config(
self.config.time_limit = timelimit
if report_timing is not NOTSET:
self.config.report_timing = report_timing
if self.options is not None:
self.config.solver_options.set_value(self.options)
if (options is not NOTSET) and (solver_options is not NOTSET):
# There is no reason for a user to be trying to mix both old
# and new options. That is silly. So we will yell at them.
Expand Down
85 changes: 37 additions & 48 deletions pyomo/contrib/solver/tests/unit/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
from pyomo.contrib.solver import base


class _LegacyWrappedSolverBase(base.LegacySolverWrapper, base.SolverBase):
pass


class TestSolverBase(unittest.TestCase):
def test_abstract_member_list(self):
expected_list = ['solve', 'available', 'version']
Expand Down Expand Up @@ -192,11 +196,13 @@ def test_class_method_list(self):
]
self.assertEqual(sorted(expected_list), sorted(method_list))

@unittest.mock.patch.multiple(_LegacyWrappedSolverBase, __abstractmethods__=set())
def test_context_manager(self):
with base.LegacySolverWrapper() as instance:
with self.assertRaises(AttributeError):
instance.available()
with _LegacyWrappedSolverBase() as instance:
self.assertIsInstance(instance, _LegacyWrappedSolverBase)
self.assertFalse(instance.available(False))

@unittest.mock.patch.multiple(_LegacyWrappedSolverBase, __abstractmethods__=set())
def test_map_config(self):
# Create a fake/empty config structure that can be added to an empty
# instance of LegacySolverWrapper
Expand All @@ -205,7 +211,7 @@ def test_map_config(self):
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
instance = base.LegacySolverWrapper()
instance = _LegacyWrappedSolverBase()
instance.config = self.config
instance._map_config(
True, False, False, 20, True, False, None, None, None, False, None, None
Expand Down Expand Up @@ -272,98 +278,81 @@ def test_map_config(self):
with self.assertRaises(AttributeError):
print(instance.config.keepfiles)

@unittest.mock.patch.multiple(_LegacyWrappedSolverBase, __abstractmethods__=set())
def test_solver_options_behavior(self):
# options can work in multiple ways (set from instantiation, set
# after instantiation, set during solve).
# Test case 1: Set at instantiation
solver = base.LegacySolverWrapper(options={'max_iter': 6})
solver = _LegacyWrappedSolverBase(options={'max_iter': 6})
self.assertEqual(solver.options, {'max_iter': 6})
self.assertEqual(solver.config.solver_options, {'max_iter': 6})

# Test case 2: Set later
solver = base.LegacySolverWrapper()
solver = _LegacyWrappedSolverBase()
solver.options = {'max_iter': 4, 'foo': 'bar'}
self.assertEqual(solver.options, {'max_iter': 4, 'foo': 'bar'})
self.assertEqual(solver.config.solver_options, {'max_iter': 4, 'foo': 'bar'})

# Test case 3: pass some options to the mapping (aka, 'solve' command)
solver = base.LegacySolverWrapper()
config = ConfigDict(implicit=True)
config.declare(
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
solver.config = config
solver = _LegacyWrappedSolverBase()
solver._map_config(options={'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 4})
self.assertEqual(solver.config.solver_options, {'max_iter': 4})

# Test case 4: Set at instantiation and override during 'solve' call
solver = base.LegacySolverWrapper(options={'max_iter': 6})
config = ConfigDict(implicit=True)
config.declare(
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
solver.config = config
solver = _LegacyWrappedSolverBase(options={'max_iter': 6})
solver._map_config(options={'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 4})
self.assertEqual(solver.config.solver_options, {'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 6})

# solver_options are also supported
# Test case 1: set at instantiation
solver = base.LegacySolverWrapper(solver_options={'max_iter': 6})
solver = _LegacyWrappedSolverBase(solver_options={'max_iter': 6})
self.assertEqual(solver.options, {'max_iter': 6})
self.assertEqual(solver.config.solver_options, {'max_iter': 6})

# Test case 2: pass some solver_options to the mapping (aka, 'solve' command)
solver = base.LegacySolverWrapper()
config = ConfigDict(implicit=True)
config.declare(
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
solver.config = config
solver = _LegacyWrappedSolverBase()
solver._map_config(solver_options={'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 4})
self.assertEqual(solver.config.solver_options, {'max_iter': 4})

# Test case 3: Set at instantiation and override during 'solve' call
solver = base.LegacySolverWrapper(solver_options={'max_iter': 6})
config = ConfigDict(implicit=True)
config.declare(
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
solver.config = config
solver = _LegacyWrappedSolverBase(solver_options={'max_iter': 6})
solver._map_config(solver_options={'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 4})
self.assertEqual(solver.config.solver_options, {'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 6})

# users can mix... sort of
# Test case 1: Initialize with options, solve with solver_options
solver = base.LegacySolverWrapper(options={'max_iter': 6})
config = ConfigDict(implicit=True)
config.declare(
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
solver.config = config
solver = _LegacyWrappedSolverBase(options={'max_iter': 6})
solver._map_config(solver_options={'max_iter': 4})
self.assertEqual(solver.options, {'max_iter': 4})
self.assertEqual(solver.config.solver_options, {'max_iter': 4})

# users CANNOT initialize both values at the same time, because how
# do we know what to do with it then?
# Test case 1: Class instance
with self.assertRaises(ValueError):
solver = base.LegacySolverWrapper(
solver = _LegacyWrappedSolverBase(
options={'max_iter': 6}, solver_options={'max_iter': 4}
)
# Test case 2: Passing to `solve`
solver = base.LegacySolverWrapper()
solver = _LegacyWrappedSolverBase()
with self.assertRaises(ValueError):
solver._map_config(solver_options={'max_iter': 4}, options={'max_iter': 6})

# Test that assignment to maps to set_value:
solver = _LegacyWrappedSolverBase()
config = ConfigDict(implicit=True)
config.declare(
'solver_options',
ConfigDict(implicit=True, description="Options to pass to the solver."),
)
solver.config = config
with self.assertRaises(ValueError):
solver._map_config(solver_options={'max_iter': 4}, options={'max_iter': 6})
solver.config.solver_options.max_iter = 6
self.assertEqual(solver.options, {'max_iter': 6})
self.assertEqual(solver.config.solver_options, {'max_iter': 6})

def test_map_results(self):
# Unclear how to test this
Expand Down
4 changes: 2 additions & 2 deletions pyomo/opt/base/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,8 +470,8 @@ def set_results_format(self, format):
Set the current results format (if it's valid for the current
problem format).
"""
if (self._problem_format in self._valid_results_formats) and (
format in self._valid_results_formats[self._problem_format]
if (self._problem_format in self._valid_result_formats) and (
format in self._valid_result_formats[self._problem_format]
):
self._results_format = format
else:
Expand Down
2 changes: 1 addition & 1 deletion pyomo/opt/tests/base/test_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def test_set_problem_format(self):
def test_set_results_format(self):
opt = pyomo.opt.SolverFactory("stest1")
opt._valid_problem_formats = ['a']
opt._valid_results_formats = {'a': 'b'}
opt._valid_result_formats = {'a': 'b'}
self.assertEqual(opt.problem_format(), None)
try:
opt.set_results_format('b')
Expand Down

0 comments on commit 4d825f6

Please sign in to comment.