Skip to content

Commit 3e12d96

Browse files
authored
Merge pull request Pyomo#3700 from jsiirola/legacy-wrapper-fixes
Expanded LegacySolverWrapper fixes
2 parents 2758f66 + 9aaa84b commit 3e12d96

File tree

3 files changed

+53
-13
lines changed

3 files changed

+53
-13
lines changed

pyomo/contrib/solver/common/base.py

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from pyomo.core.base.param import ParamData
1818
from pyomo.core.base.block import BlockData
1919
from pyomo.core.base.objective import Objective, ObjectiveData
20-
from pyomo.common.config import ConfigValue
20+
from pyomo.common.config import ConfigValue, ConfigDict
2121
from pyomo.common.enums import IntEnum, SolverAPIVersion
2222
from pyomo.common.errors import ApplicationError
2323
from pyomo.common.deprecation import deprecation_warning
@@ -408,9 +408,27 @@ class LegacySolverWrapper:
408408
interface. Necessary for backwards compatibility.
409409
"""
410410

411+
class _all_true(object):
412+
# A mockup of Bunch that returns True for all attribute lookups
413+
# or containment tests.
414+
def __getattr__(self, name):
415+
return True
416+
417+
def __contains__(self, name):
418+
return True
419+
420+
class _UpdatableConfigDict(ConfigDict):
421+
# The legacy solver interface used Option objects. we will make
422+
# the ConfigDict *look* like an Option by supporting update()
423+
__slots__ = ()
424+
425+
def update(self, value):
426+
return self.set_value(value)
427+
411428
def __init__(self, **kwargs):
412-
if 'solver_io' in kwargs:
413-
raise NotImplementedError('Still working on this')
429+
solver_io = kwargs.pop('solver_io', None)
430+
if solver_io:
431+
raise NotImplementedError(f'Still working on this ({solver_io=})')
414432
# There is no reason for a user to be trying to mix both old
415433
# and new options. That is silly. So we will yell at them.
416434
_options = kwargs.pop('options', None)
@@ -425,8 +443,14 @@ def __init__(self, **kwargs):
425443
kwargs['solver_options'] = _options
426444
super().__init__(**kwargs)
427445
# Make the legacy 'options' attribute an alias of the new
428-
# config.solver_options
446+
# config.solver_options; change its class so it behaves more
447+
# like an old Bunch/Options object.
429448
self.options = self.config.solver_options
449+
self.options.__class__ = LegacySolverWrapper._UpdatableConfigDict
450+
# We will assume the solver interface supports all capabilities
451+
# (unless a derived class actually set something
452+
if not hasattr(self, '_capabilities'):
453+
self._capabilities = LegacySolverWrapper._all_true()
430454

431455
#
432456
# Support "with" statements
@@ -567,7 +591,7 @@ def _solution_handler(
567591
self, load_solutions, model, results, legacy_results, legacy_soln
568592
):
569593
"""Method to handle the preferred action for the solution"""
570-
symbol_map = SymbolMap()
594+
symbol_map = legacy_soln.symbol_map = SymbolMap()
571595
symbol_map.default_labeler = NumericLabeler('x')
572596
if not hasattr(model, 'solutions'):
573597
# This logic gets around Issue #2130 in which
@@ -576,6 +600,7 @@ def _solution_handler(
576600

577601
setattr(model, 'solutions', ModelSolutions(model))
578602
model.solutions.add_symbol_map(symbol_map)
603+
legacy_results._smap = symbol_map
579604
legacy_results._smap_id = id(symbol_map)
580605
delete_legacy_soln = True
581606
if load_solutions:
@@ -597,10 +622,12 @@ def _solution_handler(
597622
legacy_soln.variable['Rc'] = val
598623

599624
legacy_results.solution.insert(legacy_soln)
600-
# Timing info was not originally on the legacy results, but we want
601-
# to make it accessible to folks who are utilizing the backwards
602-
# compatible version.
603-
legacy_results.timing_info = results.timing_info
625+
# Timing info was not originally on the legacy results, but we
626+
# want to make it accessible to folks who are utilizing the
627+
# backwards compatible version. Note that embedding the
628+
# ConfigDict broke pickling the legacy_results, so we will only
629+
# return raw nested dicts
630+
legacy_results.timing_info = results.timing_info.value()
604631
if delete_legacy_soln:
605632
legacy_results.solution.delete(0)
606633
return legacy_results
@@ -711,3 +738,9 @@ def set_options(self, options):
711738
opts = {k: v for k, v in options.value().items() if v is not None}
712739
if opts:
713740
self._map_config(**opts)
741+
742+
def warm_start_capable(self):
743+
return False
744+
745+
def default_variable_value(self):
746+
return None

pyomo/contrib/solver/tests/unit/test_base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_class_method_list(self):
3232
'version',
3333
]
3434
method_list = [
35-
method for method in dir(base.SolverBase) if method.startswith('_') is False
35+
method for method in dir(base.SolverBase) if not method.startswith('_')
3636
]
3737
self.assertEqual(sorted(expected_list), sorted(method_list))
3838

@@ -93,7 +93,7 @@ def test_class_method_list(self):
9393
method_list = [
9494
method
9595
for method in dir(base.PersistentSolverBase)
96-
if (method.startswith('__') or method.startswith('_abc')) is False
96+
if not (method.startswith('__') or method.startswith('_abc'))
9797
]
9898
self.assertEqual(sorted(expected_list), sorted(method_list))
9999

@@ -142,14 +142,16 @@ def test_class_method_list(self):
142142
expected_list = [
143143
'available',
144144
'config_block',
145+
'default_variable_value',
145146
'license_is_valid',
146147
'set_options',
147148
'solve',
149+
'warm_start_capable',
148150
]
149151
method_list = [
150152
method
151153
for method in dir(base.LegacySolverWrapper)
152-
if method.startswith('_') is False
154+
if not method.startswith('_')
153155
]
154156
self.assertEqual(sorted(expected_list), sorted(method_list))
155157

pyomo/core/base/suffix.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@
5252

5353

5454
def suffix_generator(a_block, datatype=NOTSET, direction=NOTSET, active=None):
55-
_iter = a_block.component_map(Suffix, active=active).items()
55+
_iter = (
56+
(s.local_name, s)
57+
for s in a_block.component_data_objects(
58+
Suffix, active=active, descend_into=False
59+
)
60+
)
5661
if direction is not NOTSET:
5762
direction = _SuffixDirectionDomain(direction)
5863
if not direction:

0 commit comments

Comments
 (0)