Skip to content

Commit e30aea7

Browse files
authored
Merge pull request #3166 from emma58/simplify-gdp-mappings
GDP: Use private_data for all mappings between original and transformed components
2 parents e83235c + 1405731 commit e30aea7

9 files changed

+149
-201
lines changed

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)