Skip to content

Commit 039acdc

Browse files
Merge branch 'main' into fix-pyros-log-discrete-separation-bug
2 parents 5b5f004 + e30aea7 commit 039acdc

18 files changed

+153
-218
lines changed

pyomo/common/tests/test_config.py

-6
Original file line numberDiff line numberDiff line change
@@ -2098,7 +2098,6 @@ def test_generate_custom_documentation(self):
20982098
"generate_documentation is deprecated.",
20992099
LOG,
21002100
)
2101-
self.maxDiff = None
21022101
# print(test)
21032102
self.assertEqual(test, reference)
21042103

@@ -2113,7 +2112,6 @@ def test_generate_custom_documentation(self):
21132112
)
21142113
)
21152114
self.assertEqual(LOG.getvalue(), "")
2116-
self.maxDiff = None
21172115
# print(test)
21182116
self.assertEqual(test, reference)
21192117

@@ -2159,7 +2157,6 @@ def test_generate_custom_documentation(self):
21592157
"generate_documentation is deprecated.",
21602158
LOG,
21612159
)
2162-
self.maxDiff = None
21632160
# print(test)
21642161
self.assertEqual(test, reference)
21652162

@@ -2577,7 +2574,6 @@ def test_argparse_help_implicit_disable(self):
25772574
parser = argparse.ArgumentParser(prog='tester')
25782575
self.config.initialize_argparse(parser)
25792576
help = parser.format_help()
2580-
self.maxDiff = None
25812577
self.assertIn(
25822578
"""
25832579
-h, --help show this help message and exit
@@ -3106,8 +3102,6 @@ def test_declare_from(self):
31063102
cfg2.declare_from({})
31073103

31083104
def test_docstring_decorator(self):
3109-
self.maxDiff = None
3110-
31113105
@document_kwargs_from_configdict('CONFIG')
31123106
class ExampleClass(object):
31133107
CONFIG = ExampleConfig()

pyomo/common/tests/test_log.py

-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,6 @@ def test_verbatim(self):
511511
"\n"
512512
" quote block\n"
513513
)
514-
self.maxDiff = None
515514
self.assertEqual(self.stream.getvalue(), ans)
516515

517516

pyomo/common/tests/test_timing.py

-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ def test_report_timing(self):
107107
m.y = Var(Any, dense=False)
108108
xfrm.apply_to(m)
109109
result = out.getvalue().strip()
110-
self.maxDiff = None
111110
for l, r in zip(result.splitlines(), ref.splitlines()):
112111
self.assertRegex(str(l.strip()), str(r.strip()))
113112
finally:
@@ -122,7 +121,6 @@ def test_report_timing(self):
122121
m.y = Var(Any, dense=False)
123122
xfrm.apply_to(m)
124123
result = os.getvalue().strip()
125-
self.maxDiff = None
126124
for l, r in zip(result.splitlines(), ref.splitlines()):
127125
self.assertRegex(str(l.strip()), str(r.strip()))
128126
finally:
@@ -135,7 +133,6 @@ def test_report_timing(self):
135133
m.y = Var(Any, dense=False)
136134
xfrm.apply_to(m)
137135
result = os.getvalue().strip()
138-
self.maxDiff = None
139136
for l, r in zip(result.splitlines(), ref.splitlines()):
140137
self.assertRegex(str(l.strip()), str(r.strip()))
141138
self.assertEqual(buf.getvalue().strip(), "")
@@ -172,7 +169,6 @@ def test_report_timing_context_manager(self):
172169
xfrm.apply_to(m)
173170
self.assertEqual(OUT.getvalue(), "")
174171
result = OS.getvalue().strip()
175-
self.maxDiff = None
176172
for l, r in zip_longest(result.splitlines(), ref.splitlines()):
177173
self.assertRegex(str(l.strip()), str(r.strip()))
178174
# Active reporting is False: the previous log should not have changed

pyomo/common/unittest.py

+4
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,10 @@ class TestCase(_unittest.TestCase):
498498

499499
__doc__ += _unittest.TestCase.__doc__
500500

501+
# By default, we always want to spend the time to create the full
502+
# diff of the test reault and the baseline
503+
maxDiff = None
504+
501505
def assertStructuredAlmostEqual(
502506
self,
503507
first,

pyomo/core/tests/unit/test_block.py

-1
Original file line numberDiff line numberDiff line change
@@ -2667,7 +2667,6 @@ def test_pprint(self):
26672667
26682668
5 Declarations: a1_IDX a3_IDX c a b
26692669
"""
2670-
self.maxDiff = None
26712670
self.assertEqual(ref, buf.getvalue())
26722671

26732672
@unittest.skipIf(not 'glpk' in solvers, "glpk solver is not available")

pyomo/core/tests/unit/test_numeric_expr.py

-1
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,6 @@ def test_sumOf_nestedTrivialProduct2(self):
14241424
e1 = m.a * m.p
14251425
e2 = m.b - m.c
14261426
e = e2 - e1
1427-
self.maxDiff = None
14281427
self.assertExpressionsEqual(
14291428
e,
14301429
LinearExpression(

pyomo/core/tests/unit/test_reference.py

-2
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,6 @@ def test_contains_with_nonflattened(self):
12801280
normalize_index.flatten = _old_flatten
12811281

12821282
def test_pprint_nonfinite_sets(self):
1283-
self.maxDiff = None
12841283
m = ConcreteModel()
12851284
m.v = Var(NonNegativeIntegers, dense=False)
12861285
m.ref = Reference(m.v)
@@ -1322,7 +1321,6 @@ def test_pprint_nonfinite_sets(self):
13221321

13231322
def test_pprint_nonfinite_sets_ctypeNone(self):
13241323
# test issue #2039
1325-
self.maxDiff = None
13261324
m = ConcreteModel()
13271325
m.v = Var(NonNegativeIntegers, dense=False)
13281326
m.ref = Reference(m.v, ctype=None)

pyomo/core/tests/unit/test_set.py

-1
Original file line numberDiff line numberDiff line change
@@ -6267,7 +6267,6 @@ def test_issue_835(self):
62676267

62686268
@unittest.skipIf(NamedTuple is None, "typing module not available")
62696269
def test_issue_938(self):
6270-
self.maxDiff = None
62716270
NodeKey = NamedTuple('NodeKey', [('id', int)])
62726271
ArcKey = NamedTuple('ArcKey', [('node_from', NodeKey), ('node_to', NodeKey)])
62736272

pyomo/gdp/plugins/bigm.py

+31-30
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import logging
1515

16+
from pyomo.common.autoslots import AutoSlots
1617
from pyomo.common.collections import ComponentMap
1718
from pyomo.common.config import ConfigDict, ConfigValue
1819
from pyomo.common.gc_manager import PauseGC
@@ -58,6 +59,26 @@
5859
logger = logging.getLogger('pyomo.gdp.bigm')
5960

6061

62+
class _BigMData(AutoSlots.Mixin):
63+
__slots__ = ('bigm_src',)
64+
65+
def __init__(self):
66+
# we will keep a map of constraints (hashable, ha!) to a tuple to
67+
# indicate what their M value is and where it came from, of the form:
68+
# ((lower_value, lower_source, lower_key), (upper_value, upper_source,
69+
# upper_key)), where the first tuple is the information for the lower M,
70+
# the second tuple is the info for the upper M, source is the Suffix or
71+
# argument dictionary and None if the value was calculated, and key is
72+
# the key in the Suffix or argument dictionary, and None if it was
73+
# calculated. (Note that it is possible the lower or upper is
74+
# user-specified and the other is not, hence the need to store
75+
# information for both.)
76+
self.bigm_src = {}
77+
78+
79+
Block.register_private_data_initializer(_BigMData)
80+
81+
6182
@TransformationFactory.register(
6283
'gdp.bigm', doc="Relax disjunctive model using big-M terms."
6384
)
@@ -94,15 +115,8 @@ class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn):
94115
name beginning "_pyomo_gdp_bigm_reformulation". That Block will
95116
contain an indexed Block named "relaxedDisjuncts", which will hold
96117
the relaxed disjuncts. This block is indexed by an integer
97-
indicating the order in which the disjuncts were relaxed.
98-
Each block has a dictionary "_constraintMap":
99-
100-
'srcConstraints': ComponentMap(<transformed constraint>:
101-
<src constraint>)
102-
'transformedConstraints': ComponentMap(<src constraint>:
103-
<transformed constraint>)
104-
105-
All transformed Disjuncts will have a pointer to the block their transformed
118+
indicating the order in which the disjuncts were relaxed. All
119+
transformed Disjuncts will have a pointer to the block their transformed
106120
constraints are on, and all transformed Disjunctions will have a
107121
pointer to the corresponding 'Or' or 'ExactlyOne' constraint.
108122
@@ -247,18 +261,6 @@ def _transform_disjunct(self, obj, bigM, transBlock):
247261

248262
relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock)
249263

250-
# we will keep a map of constraints (hashable, ha!) to a tuple to
251-
# indicate what their M value is and where it came from, of the form:
252-
# ((lower_value, lower_source, lower_key), (upper_value, upper_source,
253-
# upper_key)), where the first tuple is the information for the lower M,
254-
# the second tuple is the info for the upper M, source is the Suffix or
255-
# argument dictionary and None if the value was calculated, and key is
256-
# the key in the Suffix or argument dictionary, and None if it was
257-
# calculated. (Note that it is possible the lower or upper is
258-
# user-specified and the other is not, hence the need to store
259-
# information for both.)
260-
relaxationBlock.bigm_src = {}
261-
262264
# This is crazy, but if the disjunction has been previously
263265
# relaxed, the disjunct *could* be deactivated. This is a big
264266
# deal for Hull, as it uses the component_objects /
@@ -278,8 +280,8 @@ def _transform_constraint(
278280
):
279281
# add constraint to the transformation block, we'll transform it there.
280282
transBlock = disjunct._transformation_block()
281-
bigm_src = transBlock.bigm_src
282-
constraintMap = transBlock._constraintMap
283+
bigm_src = transBlock.private_data().bigm_src
284+
constraint_map = transBlock.private_data('pyomo.gdp')
283285

284286
disjunctionRelaxationBlock = transBlock.parent_block()
285287

@@ -346,7 +348,7 @@ def _transform_constraint(
346348
bigm_src[c] = (lower, upper)
347349

348350
self._add_constraint_expressions(
349-
c, i, M, disjunct.binary_indicator_var, newConstraint, constraintMap
351+
c, i, M, disjunct.binary_indicator_var, newConstraint, constraint_map
350352
)
351353

352354
# deactivate because we relaxed
@@ -409,7 +411,7 @@ def _update_M_from_suffixes(self, constraint, suffix_list, lower, upper):
409411
def get_m_value_src(self, constraint):
410412
transBlock = _get_constraint_transBlock(constraint)
411413
((lower_val, lower_source, lower_key), (upper_val, upper_source, upper_key)) = (
412-
transBlock.bigm_src[constraint]
414+
transBlock.private_data().bigm_src[constraint]
413415
)
414416

415417
if (
@@ -464,7 +466,7 @@ def get_M_value_src(self, constraint):
464466
transBlock = _get_constraint_transBlock(constraint)
465467
# This is a KeyError if it fails, but it is also my fault if it
466468
# fails... (That is, it's a bug in the mapping.)
467-
return transBlock.bigm_src[constraint]
469+
return transBlock.private_data().bigm_src[constraint]
468470

469471
def get_M_value(self, constraint):
470472
"""Returns the M values used to transform constraint. Return is a tuple:
@@ -479,7 +481,7 @@ def get_M_value(self, constraint):
479481
transBlock = _get_constraint_transBlock(constraint)
480482
# This is a KeyError if it fails, but it is also my fault if it
481483
# fails... (That is, it's a bug in the mapping.)
482-
lower, upper = transBlock.bigm_src[constraint]
484+
lower, upper = transBlock.private_data().bigm_src[constraint]
483485
return (lower[0], upper[0])
484486

485487
def get_all_M_values_by_constraint(self, model):
@@ -499,9 +501,8 @@ def get_all_M_values_by_constraint(self, model):
499501
# First check if it was transformed at all.
500502
if transBlock is not None:
501503
# If it was transformed with BigM, we get the M values.
502-
if hasattr(transBlock, 'bigm_src'):
503-
for cons in transBlock.bigm_src:
504-
m_values[cons] = self.get_M_value(cons)
504+
for cons in transBlock.private_data().bigm_src:
505+
m_values[cons] = self.get_M_value(cons)
505506
return m_values
506507

507508
def get_largest_M_value(self, model):

pyomo/gdp/plugins/bigm_mixin.py

+9-13
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def _estimate_M(self, expr, constraint):
232232
return tuple(M)
233233

234234
def _add_constraint_expressions(
235-
self, c, i, M, indicator_var, newConstraint, constraintMap
235+
self, c, i, M, indicator_var, newConstraint, constraint_map
236236
):
237237
# Since we are both combining components from multiple blocks and using
238238
# local names, we need to make sure that the first index for
@@ -253,8 +253,10 @@ def _add_constraint_expressions(
253253
)
254254
M_expr = M[0] * (1 - indicator_var)
255255
newConstraint.add((name, i, 'lb'), c.lower <= c.body - M_expr)
256-
constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'lb']]
257-
constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c
256+
constraint_map.transformed_constraints[c].append(
257+
newConstraint[name, i, 'lb']
258+
)
259+
constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c
258260
if c.upper is not None:
259261
if M[1] is None:
260262
raise GDP_Error(
@@ -263,13 +265,7 @@ def _add_constraint_expressions(
263265
)
264266
M_expr = M[1] * (1 - indicator_var)
265267
newConstraint.add((name, i, 'ub'), c.body - M_expr <= c.upper)
266-
transformed = constraintMap['transformedConstraints'].get(c)
267-
if transformed is not None:
268-
constraintMap['transformedConstraints'][c].append(
269-
newConstraint[name, i, 'ub']
270-
)
271-
else:
272-
constraintMap['transformedConstraints'][c] = [
273-
newConstraint[name, i, 'ub']
274-
]
275-
constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c
268+
constraint_map.transformed_constraints[c].append(
269+
newConstraint[name, i, 'ub']
270+
)
271+
constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c

pyomo/gdp/plugins/binary_multiplication.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def _transform_disjunct(self, obj, transBlock):
121121
def _transform_constraint(self, obj, disjunct):
122122
# add constraint to the transformation block, we'll transform it there.
123123
transBlock = disjunct._transformation_block()
124-
constraintMap = transBlock._constraintMap
124+
constraint_map = transBlock.private_data('pyomo.gdp')
125125

126126
disjunctionRelaxationBlock = transBlock.parent_block()
127127

@@ -137,14 +137,14 @@ def _transform_constraint(self, obj, disjunct):
137137
continue
138138

139139
self._add_constraint_expressions(
140-
c, i, disjunct.binary_indicator_var, newConstraint, constraintMap
140+
c, i, disjunct.binary_indicator_var, newConstraint, constraint_map
141141
)
142142

143143
# deactivate because we relaxed
144144
c.deactivate()
145145

146146
def _add_constraint_expressions(
147-
self, c, i, indicator_var, newConstraint, constraintMap
147+
self, c, i, indicator_var, newConstraint, constraint_map
148148
):
149149
# Since we are both combining components from multiple blocks and using
150150
# local names, we need to make sure that the first index for
@@ -156,21 +156,21 @@ def _add_constraint_expressions(
156156
# over the constraint indices, but I don't think it matters a lot.)
157157
unique = len(newConstraint)
158158
name = c.local_name + "_%s" % unique
159-
transformed = constraintMap['transformedConstraints'][c] = []
159+
transformed = constraint_map.transformed_constraints[c]
160160

161161
lb, ub = c.lower, c.upper
162162
if (c.equality or lb is ub) and lb is not None:
163163
# equality
164164
newConstraint.add((name, i, 'eq'), (c.body - lb) * indicator_var == 0)
165165
transformed.append(newConstraint[name, i, 'eq'])
166-
constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c
166+
constraint_map.src_constraint[newConstraint[name, i, 'eq']] = c
167167
else:
168168
# inequality
169169
if lb is not None:
170170
newConstraint.add((name, i, 'lb'), 0 <= (c.body - lb) * indicator_var)
171171
transformed.append(newConstraint[name, i, 'lb'])
172-
constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c
172+
constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c
173173
if ub is not None:
174174
newConstraint.add((name, i, 'ub'), (c.body - ub) * indicator_var <= 0)
175175
transformed.append(newConstraint[name, i, 'ub'])
176-
constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c
176+
constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c

pyomo/gdp/plugins/gdp_to_mip_transformation.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
from functools import wraps
1313

14-
from pyomo.common.collections import ComponentMap
14+
from pyomo.common.autoslots import AutoSlots
15+
from pyomo.common.collections import ComponentMap, DefaultComponentMap
1516
from pyomo.common.log import is_debug_set
1617
from pyomo.common.modeling import unique_component_name
1718

@@ -48,6 +49,17 @@
4849
from weakref import ref as weakref_ref
4950

5051

52+
class _GDPTransformationData(AutoSlots.Mixin):
53+
__slots__ = ('src_constraint', 'transformed_constraints')
54+
55+
def __init__(self):
56+
self.src_constraint = ComponentMap()
57+
self.transformed_constraints = DefaultComponentMap(list)
58+
59+
60+
Block.register_private_data_initializer(_GDPTransformationData, scope='pyomo.gdp')
61+
62+
5163
class GDP_to_MIP_Transformation(Transformation):
5264
"""
5365
Base class for transformations from GDP to MIP
@@ -243,14 +255,7 @@ def _get_disjunct_transformation_block(self, disjunct, transBlock):
243255
relaxationBlock = relaxedDisjuncts[len(relaxedDisjuncts)]
244256

245257
relaxationBlock.transformedConstraints = Constraint(Any)
246-
247258
relaxationBlock.localVarReferences = Block()
248-
# add the map that will link back and forth between transformed
249-
# constraints and their originals.
250-
relaxationBlock._constraintMap = {
251-
'srcConstraints': ComponentMap(),
252-
'transformedConstraints': ComponentMap(),
253-
}
254259

255260
# add mappings to source disjunct (so we'll know we've relaxed)
256261
disjunct._transformation_block = weakref_ref(relaxationBlock)

0 commit comments

Comments
 (0)