Skip to content

Commit b1d713b

Browse files
authored
Merge branch 'main' into only-mbm-bound-constraints
2 parents 65e684a + 14b5957 commit b1d713b

File tree

9 files changed

+66
-36
lines changed

9 files changed

+66
-36
lines changed

pyomo/core/base/block.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,6 @@ class PseudoMap(AutoSlots.Mixin):
347347

348348
__slots__ = ('_block', '_ctypes', '_active', '_sorted')
349349

350-
# If a writer cached a repn on this block, remove it when cloning
351-
# TODO: remove repn caching from the model
352-
__autoslot_mappers = {'_repn': AutoSlots.encode_as_none}
353-
354350
def __init__(self, block, ctype, active=None, sort=False):
355351
"""
356352
TODO
@@ -577,6 +573,10 @@ class _BlockData(ActiveComponentData):
577573
"""
578574
_Block_reserved_words = set()
579575

576+
# If a writer cached a repn on this block, remove it when cloning
577+
# TODO: remove repn caching from the model
578+
__autoslot_mappers = {'_repn': AutoSlots.encode_as_none}
579+
580580
def __init__(self, component):
581581
#
582582
# BLOCK DATA ELEMENTS
@@ -1348,7 +1348,7 @@ def reclassify_component_type(self, name_or_object, new_ctype,
13481348
self._decl_order[prev] = (self._decl_order[prev][0], idx)
13491349
self._decl_order[idx] = (obj, tmp)
13501350

1351-
def clone(self):
1351+
def clone(self, memo=None):
13521352
"""
13531353
TODO
13541354
"""
@@ -1365,15 +1365,23 @@ def clone(self):
13651365
# NonNegativeReals, etc) that are not "owned" by any blocks and
13661366
# should be preserved as singletons.
13671367
#
1368+
pc = self.parent_component()
1369+
if pc is self:
1370+
parent = self.parent_block()
1371+
else:
1372+
parent = pc
1373+
1374+
if memo is None:
1375+
memo = {}
1376+
memo['__block_scope__'] = {id(self): True, id(None): False}
1377+
memo[id(parent)] = parent
1378+
13681379
with PauseGC():
1369-
new_block = copy.deepcopy(
1370-
self, dict(
1371-
__block_scope__={id(self): True, id(None): False},
1372-
))
1380+
new_block = copy.deepcopy(self, memo)
13731381

13741382
# We need to "detangle" the new block from the original block
13751383
# hierarchy
1376-
if self.parent_component() is self:
1384+
if pc is self:
13771385
new_block._parent = None
13781386
else:
13791387
new_block._component = None

pyomo/gdp/basic_step.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@
2222

2323
def _clone_all_but_indicator_vars(self):
2424
"""Clone everything in a Disjunct except for the indicator_vars"""
25-
memo = {
26-
'__block_scope__': {id(self): True, id(None): False},
25+
return self.clone({
2726
id(self.indicator_var): self.indicator_var,
2827
id(self.binary_indicator_var): self.binary_indicator_var,
29-
}
30-
new_block = copy.deepcopy(self, memo)
31-
new_block._parent = None
32-
return new_block
28+
})
3329

3430

3531
def _squish_singletons(tuple_iter):
@@ -46,9 +42,15 @@ def apply_basic_step(disjunctions_or_constraints):
4642
# Basic steps only apply to XOR'd disjunctions
4743
#
4844
disjunctions = list(obj for obj in disjunctions_or_constraints
49-
if obj.ctype == Disjunction)
45+
if obj.ctype is Disjunction)
5046
constraints = list(obj for obj in disjunctions_or_constraints
51-
if obj.ctype == Constraint)
47+
if obj.ctype is Constraint)
48+
if len(disjunctions) + len(constraints) != len(disjunctions_or_constraints):
49+
raise ValueError('apply_basic_step only accepts a list containing '
50+
'Disjunctions or Constraints')
51+
if not disjunctions:
52+
raise ValueError('apply_basic_step: argument list must contain at '
53+
'least one Disjunction')
5254
for d in disjunctions:
5355
if not d.xor:
5456
raise ValueError(
@@ -76,8 +78,9 @@ def apply_basic_step(disjunctions_or_constraints):
7678
#
7779
ans.disjuncts[idx].src = Block(ans.DISJUNCTIONS)
7880
for i in ans.DISJUNCTIONS:
79-
tmp = _clone_all_but_indicator_vars(disjunctions[i].disjuncts[
80-
idx[i] if isinstance(idx, tuple) else idx])
81+
src_disj = disjunctions[i].disjuncts[
82+
idx[i] if isinstance(idx, tuple) else idx]
83+
tmp = _clone_all_but_indicator_vars(src_disj)
8184
for k,v in list(tmp.component_map().items()):
8285
if v.parent_block() is not tmp:
8386
# Skip indicator_var and binary_indicator_var

pyomo/gdp/disjunct.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,6 @@ def set_value(self, expr):
518518
@ModelComponentFactory.register("Disjunction expressions.")
519519
class Disjunction(ActiveIndexedComponent):
520520
_ComponentDataClass = _DisjunctionData
521-
__autoslot_mappers__ = {'_algebraic_constraint': AutoSlots.weakref_mapper}
522521

523522
def __new__(cls, *args, **kwds):
524523
if cls != Disjunction:
@@ -533,7 +532,6 @@ def __init__(self, *args, **kwargs):
533532
self._init_expr = kwargs.pop('expr', None)
534533
self._init_xor = _Initializer.process(kwargs.pop('xor', True))
535534
self._autodisjuncts = None
536-
self._algebraic_constraint = None
537535
kwargs.setdefault('ctype', Disjunction)
538536
super(Disjunction, self).__init__(*args, **kwargs)
539537

pyomo/gdp/plugins/bigm.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def __init__(self):
165165
}
166166
self._generate_debug_messages = False
167167
self._transformation_blocks = {}
168+
self._algebraic_constraints = {}
168169

169170
def _get_bigm_arg_list(self, bigm_args, block):
170171
# Gather what we know about blocks from args exactly once. We'll still
@@ -192,6 +193,7 @@ def _apply_to(self, instance, **kwds):
192193
finally:
193194
self.used_args.clear()
194195
self._transformation_blocks.clear()
196+
self._algebraic_constraints.clear()
195197

196198
def _apply_to_impl(self, instance, **kwds):
197199
if not instance.ctype in (Block, Disjunct):
@@ -289,8 +291,8 @@ def _add_xor_constraint(self, disjunction, transBlock):
289291
# DisjunctionData, we did something wrong.
290292

291293
# first check if the constraint already exists
292-
if disjunction._algebraic_constraint is not None:
293-
return disjunction._algebraic_constraint()
294+
if disjunction in self._algebraic_constraints:
295+
return self._algebraic_constraints[disjunction]
294296

295297
# add the XOR (or OR) constraints to parent block (with unique name)
296298
# It's indexed if this is an IndexedDisjunction, not otherwise
@@ -301,7 +303,7 @@ def _add_xor_constraint(self, disjunction, transBlock):
301303
orCname = unique_component_name(
302304
transBlock,disjunction.getname(fully_qualified=False) + '_xor')
303305
transBlock.add_component(orCname, orC)
304-
disjunction._algebraic_constraint = weakref_ref(orC)
306+
self._algebraic_constraints[disjunction] = orC
305307

306308
return orC
307309

pyomo/gdp/plugins/hull.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def __init__(self):
199199
}
200200
self._generate_debug_messages = False
201201
self._transformation_blocks = {}
202+
self._algebraic_constraints = {}
202203
self._targets = set()
203204

204205
def _add_local_vars(self, block, local_var_dict):
@@ -235,6 +236,7 @@ def _apply_to(self, instance, **kwds):
235236
self._apply_to_impl(instance, **kwds)
236237
finally:
237238
self._transformation_blocks.clear()
239+
self._algebraic_constraints.clear()
238240
self._targets = set()
239241

240242
def _apply_to_impl(self, instance, **kwds):
@@ -341,8 +343,8 @@ def _add_xor_constraint(self, disjunction, transBlock):
341343
# Put XOR constraint on the transformation block
342344

343345
# check if the constraint already exists
344-
if disjunction._algebraic_constraint is not None:
345-
return disjunction._algebraic_constraint()
346+
if disjunction in self._algebraic_constraints:
347+
return self._algebraic_constraints[disjunction]
346348

347349
# add the XOR constraints to parent block (with unique name) It's
348350
# indexed if this is an IndexedDisjunction, not otherwise
@@ -351,7 +353,7 @@ def _add_xor_constraint(self, disjunction, transBlock):
351353
unique_component_name(transBlock,
352354
disjunction.getname(
353355
fully_qualified=True) + '_xor'), orC)
354-
disjunction._algebraic_constraint = weakref_ref(orC)
356+
self._algebraic_constraints[disjunction] = orC
355357

356358
return orC
357359

pyomo/gdp/plugins/multiple_bigm.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def __init__(self):
191191
# the network.expand_arcs transformation
192192
}
193193
self._transformation_blocks = {}
194-
self._arg_list = {}
194+
self._algebraic_constraints = {}
195195

196196
def _apply_to(self, instance, **kwds):
197197
self.used_args = ComponentMap()
@@ -200,7 +200,7 @@ def _apply_to(self, instance, **kwds):
200200
finally:
201201
self.used_args.clear()
202202
self._transformation_blocks.clear()
203-
self._arg_list.clear()
203+
self._algebraic_constraints.clear()
204204

205205
def _apply_to_impl(self, instance, **kwds):
206206
if not instance.ctype in (Block, Disjunct):
@@ -630,8 +630,8 @@ def _add_exactly_one_constraint(self, disjunction, transBlock):
630630
# Put XOR constraint on the transformation block
631631

632632
# check if the constraint already exists
633-
if disjunction.algebraic_constraint is not None:
634-
return disjunction.algebraic_constraint
633+
if disjunction in self._algebraic_constraints:
634+
return self._algebraic_constraints[disjunction]
635635

636636
# add the XOR constraints to parent block (with unique name) It's
637637
# indexed if this is an IndexedDisjunction, not otherwise
@@ -640,7 +640,7 @@ def _add_exactly_one_constraint(self, disjunction, transBlock):
640640
unique_component_name(transBlock,
641641
disjunction.getname(
642642
fully_qualified=False) + '_xor'), orC)
643-
disjunction._algebraic_constraint = weakref_ref(orC)
643+
self._algebraic_constraints[disjunction] = orC
644644

645645
return orC
646646

pyomo/gdp/tests/common_tests.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -876,8 +876,7 @@ def check_disjunction_data_target(self, transformation):
876876
# suppose we transform the next one separately
877877
TransformationFactory('gdp.%s' % transformation).apply_to(
878878
m, targets=[m.disjunction[1]])
879-
# we added to the same XOR constraint before
880-
self.assertIsInstance(transBlock.disjunction_xor[1],
879+
self.assertIsInstance(m.disjunction[1].algebraic_constraint,
881880
constraint._GeneralConstraintData)
882881
transBlock = m.component("_pyomo_gdp_%s_reformulation_4" % transformation)
883882
self.assertIsInstance(transBlock, Block)

pyomo/gdp/tests/test_basic_step.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,19 @@ def test_indicator_var_references(self):
139139
self.assertIs(refs[0][None], m.d[0].indicator_var)
140140
self.assertIs(refs[1][None], m.d[1].indicator_var)
141141

142+
def test_arg_errors(self):
143+
m = models.makeTwoTermDisj()
144+
m.simple = Constraint(expr=m.x <= m.a + 1)
145+
146+
with self.assertRaisesRegex(
147+
ValueError, 'apply_basic_step only accepts a list containing '
148+
'Disjunctions or Constraints'):
149+
apply_basic_step([m.disjunction, m.simple, m.x])
150+
with self.assertRaisesRegex(
151+
ValueError, 'apply_basic_step: argument list must contain at '
152+
'least one Disjunction'):
153+
apply_basic_step([m.simple, m.simple])
154+
155+
142156
if __name__ == '__main__':
143157
unittest.main()

pyomo/gdp/tests/test_hull.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
RealSet, ComponentMap, value, log, ConcreteModel,
1919
Any, Suffix, SolverFactory, RangeSet, Param,
2020
Objective, TerminationCondition, Reference)
21+
from pyomo.core.base import constraint
2122
from pyomo.core.expr.sympy_tools import sympy_available
2223
from pyomo.repn import generate_standard_repn
2324

@@ -1072,7 +1073,10 @@ def check_second_iteration(self, model):
10721073
secondTerm)), 1)
10731074

10741075
orig = model.component("_pyomo_gdp_hull_reformulation")
1075-
self.assertEqual(len(orig.disjunctionList_xor), 2)
1076+
self.assertIsInstance(model.disjunctionList[1].algebraic_constraint,
1077+
constraint._GeneralConstraintData)
1078+
self.assertIsInstance(model.disjunctionList[0].algebraic_constraint,
1079+
constraint._GeneralConstraintData)
10761080
self.assertFalse(model.disjunctionList[1].active)
10771081
self.assertFalse(model.disjunctionList[0].active)
10781082

0 commit comments

Comments
 (0)