1#  ___________________________________________________________________________
2#
3#  Pyomo: Python Optimization Modeling Objects
4#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
5#  Under the terms of Contract DE-NA0003525 with National Technology and
6#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
7#  rights in this software.
8#  This software is distributed under the 3-clause BSD License.
9#  ___________________________________________________________________________
10
11import pyomo.common.unittest as unittest
12from pyomo.common.deprecation import RenamedClass
13
14from pyomo.environ import (TransformationFactory, Block, Set, Constraint,
15                           ComponentMap, Suffix, ConcreteModel, Var,
16                           Any, value)
17from pyomo.gdp import Disjunct, Disjunction, GDP_Error
18from pyomo.core.base import constraint, _ConstraintData
19from pyomo.repn import generate_standard_repn
20from pyomo.common.log import LoggingIntercept
21import logging
22
23import pyomo.gdp.tests.models as models
24import pyomo.gdp.tests.common_tests as ct
25
26import pyomo.network as ntwk
27
28import random
29
30from io import StringIO
31
32class CommonTests:
33    def diff_apply_to_and_create_using(self, model):
34        ct.diff_apply_to_and_create_using(self, model, 'gdp.bigm')
35
36class TwoTermDisj(unittest.TestCase, CommonTests):
37    def setUp(self):
38        # set seed so we can test name collisions predictably
39        random.seed(666)
40
41    def test_new_block_created(self):
42        m = models.makeTwoTermDisj()
43        TransformationFactory('gdp.bigm').apply_to(m)
44
45        # we have a transformation block
46        transBlock = m.component("_pyomo_gdp_bigm_reformulation")
47        self.assertIsInstance(transBlock, Block)
48
49        # check that we have the lbub set on the transformation block
50        lbub = transBlock.component("lbub")
51        self.assertIsInstance(lbub, Set)
52        self.assertEqual(len(lbub), 2)
53        self.assertEqual(lbub, ['lb', 'ub'])
54
55        disjBlock = transBlock.component("relaxedDisjuncts")
56        self.assertIsInstance(disjBlock, Block)
57        self.assertEqual(len(disjBlock), 2)
58        # it has the disjuncts on it
59        self.assertIsInstance( disjBlock[1].component("d[1].c1"), Constraint)
60        self.assertIsInstance( disjBlock[1].component("d[1].c2"), Constraint)
61        self.assertIsInstance( disjBlock[0].component("d[0].c"), Constraint)
62
63    def test_disjunction_deactivated(self):
64        ct.check_disjunction_deactivated(self, 'bigm')
65
66    def test_disjunctDatas_deactivated(self):
67        ct.check_disjunctDatas_deactivated(self, 'bigm')
68
69    def test_do_not_transform_twice_if_disjunction_reactivated(self):
70        ct.check_do_not_transform_twice_if_disjunction_reactivated(self, 'bigm')
71
72    def test_xor_constraint_mapping(self):
73        ct.check_xor_constraint_mapping(self, 'bigm')
74
75    def test_xor_constraint_mapping_two_disjunctions(self):
76        ct.check_xor_constraint_mapping_two_disjunctions(self, 'bigm')
77
78    def test_disjunct_mapping(self):
79        ct.check_disjunct_mapping(self, 'bigm')
80
81    def test_disjunct_and_constraint_maps(self):
82        """Tests the actual data structures used to store the maps."""
83        # ESJ: Note that despite outward appearances, this test really is unique
84        # to bigm. Because hull handles the a == 0 constraint by fixing the
85        # disaggregated variable rather than creating a transformed constraint.
86        m = models.makeTwoTermDisj()
87        bigm = TransformationFactory('gdp.bigm')
88        bigm.apply_to(m)
89        disjBlock = m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts
90        oldblock = m.component("d")
91
92        # we are counting on the fact that the disjuncts get relaxed in the
93        # same order every time.
94        for i in [0,1]:
95            self.assertIs(oldblock[i].transformation_block(), disjBlock[i])
96            self.assertIs(bigm.get_src_disjunct(disjBlock[i]), oldblock[i])
97
98        # check the constraint mappings
99        constraintdict1 = disjBlock[0]._constraintMap
100        self.assertIsInstance(constraintdict1, dict)
101        self.assertEqual(len(constraintdict1), 2)
102
103        constraintdict2 = disjBlock[1]._constraintMap
104        self.assertIsInstance(constraintdict2, dict)
105        self.assertEqual(len(constraintdict2), 2)
106
107        # original -> transformed
108        transformedConstraints1 = constraintdict1['transformedConstraints']
109        self.assertIsInstance(transformedConstraints1, ComponentMap)
110        self.assertEqual(len(transformedConstraints1), 1)
111        transformedConstraints2 = constraintdict2['transformedConstraints']
112        self.assertIsInstance(transformedConstraints2, ComponentMap)
113        self.assertEqual(len(transformedConstraints2), 2)
114        # check constraint dict has right mapping
115        c1_list = transformedConstraints2[oldblock[1].c1]
116        self.assertEqual(len(c1_list), 2)
117        # this is an equality, so we have both lb and ub
118        self.assertIs(c1_list[0],
119                      disjBlock[1].component(oldblock[1].c1.name)['lb'])
120        self.assertIs(c1_list[1],
121                      disjBlock[1].component(oldblock[1].c1.name)['ub'])
122        c2_list = transformedConstraints2[oldblock[1].c2]
123        # just ub
124        self.assertEqual(len(c2_list), 1)
125        self.assertIs(c2_list[0],
126                      disjBlock[1].component(oldblock[1].c2.name)['ub'])
127        c_list = transformedConstraints1[oldblock[0].c]
128        # just lb
129        self.assertEqual(len(c_list), 1)
130        self.assertIs(c_list[0],
131                      disjBlock[0].component(oldblock[0].c.name)['lb'])
132
133        # transformed -> original
134        srcdict1 = constraintdict1['srcConstraints']
135        self.assertIsInstance(srcdict1, ComponentMap)
136        self.assertEqual(len(srcdict1), 2)
137        self.assertIs(srcdict1[disjBlock[0].component(oldblock[0].c.name)],
138                      oldblock[0].c)
139        self.assertIs(srcdict1[disjBlock[0].component(oldblock[0].c.name)['lb']],
140                      oldblock[0].c)
141        srcdict2 = constraintdict2['srcConstraints']
142        self.assertIsInstance(srcdict2, ComponentMap)
143        self.assertEqual(len(srcdict2), 5)
144        self.assertIs(srcdict2[disjBlock[1].component("d[1].c1")],
145                      oldblock[1].c1)
146        self.assertIs(srcdict2[disjBlock[1].component("d[1].c1")['lb']],
147                      oldblock[1].c1)
148        self.assertIs(srcdict2[disjBlock[1].component("d[1].c1")['ub']],
149                      oldblock[1].c1)
150        self.assertIs(srcdict2[disjBlock[1].component("d[1].c2")],
151                      oldblock[1].c2)
152        self.assertIs(srcdict2[disjBlock[1].component("d[1].c2")['ub']],
153                      oldblock[1].c2)
154
155    def test_new_block_nameCollision(self):
156        ct.check_transformation_block_name_collision(self, 'bigm')
157
158    def test_indicator_vars(self):
159        ct.check_indicator_vars(self, 'bigm')
160
161    def test_xor_constraints(self):
162        ct.check_xor_constraint(self, 'bigm')
163
164    def test_or_constraints(self):
165        m = models.makeTwoTermDisj()
166        m.disjunction.xor = False
167        TransformationFactory('gdp.bigm').apply_to(m)
168
169        # check or constraint is an or (upper bound is None)
170        orcons = m._pyomo_gdp_bigm_reformulation.component("disjunction_xor")
171        self.assertIsInstance(orcons, Constraint)
172        self.assertIs(m.d[0].binary_indicator_var, orcons.body.arg(0))
173        self.assertIs(m.d[1].binary_indicator_var, orcons.body.arg(1))
174        repn = generate_standard_repn(orcons.body)
175        ct.check_linear_coef(self, repn, m.d[0].binary_indicator_var, 1)
176        ct.check_linear_coef(self, repn, m.d[1].binary_indicator_var, 1)
177        self.assertEqual(orcons.lower, 1)
178        self.assertIsNone(orcons.upper)
179
180    def test_deactivated_constraints(self):
181        ct.check_deactivated_constraints(self, 'bigm')
182
183    def test_transformed_constraints(self):
184        m = models.makeTwoTermDisj()
185        TransformationFactory('gdp.bigm').apply_to(m)
186        self.checkMs(m, -3, 2, 7, 2)
187
188    def test_do_not_transform_userDeactivated_disjuncts(self):
189        ct.check_user_deactivated_disjuncts(self, 'bigm')
190
191    def test_improperly_deactivated_disjuncts(self):
192        ct.check_improperly_deactivated_disjuncts(self, 'bigm')
193
194    def test_do_not_transform_userDeactivated_IndexedDisjunction(self):
195        ct.check_do_not_transform_userDeactivated_indexedDisjunction(self,
196                                                                     'bigm')
197
198    # helper method to check the M values in all of the transformed
199    # constraints (m, M) is the tuple for M.  This also relies on the
200    # disjuncts being transformed in the same order every time.
201    def checkMs(self, model, cons1lb, cons2lb, cons2ub, cons3ub):
202        disjBlock = model._pyomo_gdp_bigm_reformulation.relaxedDisjuncts
203
204        # first constraint
205        c = disjBlock[0].component("d[0].c")
206        self.assertEqual(len(c), 1)
207        self.assertTrue(c['lb'].active)
208        repn = generate_standard_repn(c['lb'].body)
209        self.assertTrue(repn.is_linear())
210        self.assertEqual(len(repn.linear_vars), 2)
211        ct.check_linear_coef(self, repn, model.a, 1)
212        ct.check_linear_coef(self, repn, model.d[0].indicator_var, cons1lb)
213        self.assertEqual(repn.constant, -cons1lb)
214        self.assertEqual(c['lb'].lower, model.d[0].c.lower)
215        self.assertIsNone(c['lb'].upper)
216
217        # second constraint
218        c = disjBlock[1].component("d[1].c1")
219        self.assertEqual(len(c), 2)
220        self.assertTrue(c['lb'].active)
221        repn = generate_standard_repn(c['lb'].body)
222        self.assertTrue(repn.is_linear())
223        self.assertEqual(len(repn.linear_vars), 2)
224        ct.check_linear_coef(self, repn, model.a, 1)
225        ct.check_linear_coef(self, repn, model.d[1].indicator_var, cons2lb)
226        self.assertEqual(repn.constant, -cons2lb)
227        self.assertEqual(c['lb'].lower, model.d[1].c1.lower)
228        self.assertIsNone(c['lb'].upper)
229        self.assertTrue(c['ub'].active)
230        repn = generate_standard_repn(c['ub'].body)
231        self.assertTrue(repn.is_linear())
232        self.assertEqual(len(repn.linear_vars), 2)
233        ct.check_linear_coef(self, repn, model.a, 1)
234        ct.check_linear_coef(self, repn, model.d[1].indicator_var, cons2ub)
235        self.assertEqual(repn.constant, -cons2ub)
236        self.assertIsNone(c['ub'].lower)
237        self.assertEqual(c['ub'].upper, model.d[1].c1.upper)
238
239        # third constraint
240        c = disjBlock[1].component("d[1].c2")
241        self.assertEqual(len(c), 1)
242        self.assertTrue(c['ub'].active)
243        repn = generate_standard_repn(c['ub'].body)
244        self.assertTrue(repn.is_linear())
245        self.assertEqual(len(repn.linear_vars), 2)
246        ct.check_linear_coef(self, repn, model.x, 1)
247        ct.check_linear_coef(self, repn, model.d[1].indicator_var, cons3ub)
248        self.assertEqual(repn.constant, -cons3ub)
249        self.assertIsNone(c['ub'].lower)
250        self.assertEqual(c['ub'].upper, model.d[1].c2.upper)
251
252    def test_suffix_M_None(self):
253        m = models.makeTwoTermDisj()
254        # specify a suffix on None
255        m.BigM = Suffix(direction=Suffix.LOCAL)
256        m.BigM[None] = 20
257
258        TransformationFactory('gdp.bigm').apply_to(m)
259        self.checkMs(m, -20, -20, 20, 20)
260
261    def test_suffix_M_None_on_disjunctData(self):
262        m = models.makeTwoTermDisj()
263        # specify a suffix on None
264        m.BigM = Suffix(direction=Suffix.LOCAL)
265        m.BigM[None] = 20
266        # override for the first index:
267        m.d[0].BigM = Suffix(direction=Suffix.LOCAL)
268        m.d[0].BigM[None] = 18
269
270        TransformationFactory('gdp.bigm').apply_to(m)
271        # there should now be different values of m on d[0] and d[1]
272        self.checkMs(m, -18, -20, 20, 20)
273
274    def test_suffix_M_simpleConstraint_on_disjunctData(self):
275        m = models.makeTwoTermDisj()
276        # specify a suffix on None
277        m.BigM = Suffix(direction=Suffix.LOCAL)
278        m.BigM[None] = 20
279        # override for the first index:
280        m.d[0].BigM = Suffix(direction=Suffix.LOCAL)
281        m.d[0].BigM[m.d[0].c] = 18
282
283        TransformationFactory('gdp.bigm').apply_to(m)
284        self.checkMs(m, -18, -20, 20, 20)
285
286    def test_arg_M_None(self):
287        m = models.makeTwoTermDisj()
288        # specify a suffix on None so we can be happy we overrode it.
289        m.BigM = Suffix(direction=Suffix.LOCAL)
290        m.BigM[None] = 20
291
292        # give an arg
293        TransformationFactory('gdp.bigm').apply_to(m, bigM={None: 19})
294        self.checkMs(m, -19, -19, 19, 19)
295
296    def test_arg_M_singleNum(self):
297        m = models.makeTwoTermDisj()
298        # specify a suffix on None so we can be happy we overrode it.
299        m.BigM = Suffix(direction=Suffix.LOCAL)
300        m.BigM[None] = 20
301
302        # give an arg
303        TransformationFactory('gdp.bigm').apply_to(m, bigM=19.2)
304        self.checkMs(m, -19.2, -19.2, 19.2, 19.2)
305
306    def test_singleArg_M_tuple(self):
307        m = models.makeTwoTermDisj()
308        # specify a suffix on None so we can be happy we overrode it.
309        m.BigM = Suffix(direction=Suffix.LOCAL)
310        m.BigM[None] = 20
311
312        # give an arg
313        TransformationFactory('gdp.bigm').apply_to(m, bigM=(-18, 19.2))
314        self.checkMs(m, -18, -18, 19.2, 19.2)
315
316    def test_singleArg_M_tuple_wrongLength(self):
317        m = models.makeTwoTermDisj()
318        # specify a suffix on None so we can be happy we overrode it.
319        m.BigM = Suffix(direction=Suffix.LOCAL)
320        m.BigM[None] = 20
321
322        # give an arg
323        self.assertRaisesRegex(
324            GDP_Error,
325            r"Big-M \([^)]*\) for constraint d\[0\].c is not of "
326            r"length two. Expected either a single value or "
327            r"tuple or list of length two for M.*",
328            TransformationFactory('gdp.bigm').apply_to,
329            m,
330            bigM=(-18, 19.2, 3))
331
332    def test_singleArg_M_list(self):
333        m = models.makeTwoTermDisj()
334        # specify a suffix on None so we can be happy we overrode it.
335        m.BigM = Suffix(direction=Suffix.LOCAL)
336        m.BigM[None] = 20
337
338        # give an arg
339        TransformationFactory('gdp.bigm').apply_to(m, bigM=[-18, 19.2])
340        self.checkMs(m, -18, -18, 19.2, 19.2)
341
342    def test_singleArg_M_list_wrongLength(self):
343        m = models.makeTwoTermDisj()
344        # specify a suffix on None so we can be happy we overrode it.
345        m.BigM = Suffix(direction=Suffix.LOCAL)
346        m.BigM[None] = 20
347
348        # give an arg
349        self.assertRaisesRegex(
350            GDP_Error,
351            r"Big-M \[[^\]]*\] for constraint d\[0\].c is not of "
352            r"length two. Expected either a single value or "
353            r"tuple or list of length two for M.*",
354            TransformationFactory('gdp.bigm').apply_to,
355            m,
356            bigM=[-18, 19.2, 3])
357
358    def test_arg_M_simpleConstraint(self):
359        m = models.makeTwoTermDisj()
360        # specify a suffix on None so we can be happy we overrode it.
361        m.BigM = Suffix(direction=Suffix.LOCAL)
362        m.BigM[None] = 20
363        # specify a suffix on constraints so we can be happy we overrode them
364        m.BigM[m.d[0].c] = 200
365        m.BigM[m.d[1].c1] = 200
366        m.BigM[m.d[1].c2] = 200
367
368        # give an arg
369        TransformationFactory('gdp.bigm').apply_to(
370            m,
371            bigM={None: 19,
372                  m.d[0].c: 18,
373                  m.d[1].c1: 17,
374                  m.d[1].c2: 16})
375        self.checkMs(m, -18, -17, 17, 16)
376
377    def test_tuple_M_arg(self):
378        m = models.makeTwoTermDisj()
379        # give a tuple arg
380        TransformationFactory('gdp.bigm').apply_to(
381            m,
382            bigM={None: (-20,19)})
383        self.checkMs(m, -20, -20, 19, 19)
384
385    def test_tuple_M_suffix(self):
386        m = models.makeTwoTermDisj()
387        m.BigM = Suffix(direction=Suffix.LOCAL)
388        m.BigM[None] = (-18, 20)
389        TransformationFactory('gdp.bigm').apply_to(m)
390        self.checkMs(m, -18, -18, 20, 20)
391
392    def test_list_M_arg(self):
393        m = models.makeTwoTermDisj()
394        # give a tuple arg
395        TransformationFactory('gdp.bigm').apply_to(
396            m,
397            bigM={None: [-20,19]})
398        self.checkMs(m, -20, -20, 19, 19)
399
400    def test_list_M_suffix(self):
401        m = models.makeTwoTermDisj()
402        m.BigM = Suffix(direction=Suffix.LOCAL)
403        m.BigM[None] = [-18, 20]
404        TransformationFactory('gdp.bigm').apply_to(m)
405        self.checkMs(m, -18, -18, 20, 20)
406
407    def test_tuple_wrong_length_err(self):
408        m = models.makeTwoTermDisj()
409        M = (-20,19, 32)
410        self.assertRaisesRegex(
411            GDP_Error,
412            r"Big-M \(-20, 19, 32\) for constraint d\[0\].c is not of "
413            r"length two. Expected either a single value or "
414            r"tuple or list of length two for M.*",
415            TransformationFactory('gdp.bigm').apply_to,
416            m,
417            bigM={None: M})
418
419    def test_list_wrong_length_err(self):
420        m = models.makeTwoTermDisj()
421        M = [-20, 19, 34]
422        self.assertRaisesRegex(
423            GDP_Error,
424            r"Big-M \[-20, 19, 34\] for constraint d\[0\].c is not of "
425            r"length two. Expected either a single value or "
426            r"tuple or list of length two for M.*",
427            TransformationFactory('gdp.bigm').apply_to,
428            m,
429            bigM={None: M})
430
431    def test_create_using(self):
432        m = models.makeTwoTermDisj()
433        self.diff_apply_to_and_create_using(m)
434
435    def test_indexed_constraints_in_disjunct(self):
436        m = ConcreteModel()
437        m.I = [1,2,3]
438        m.x = Var(m.I, bounds=(0,10))
439        def c_rule(b,i):
440            m = b.model()
441            return m.x[i] >= i
442        def d_rule(d,j):
443            m = d.model()
444            d.c = Constraint(m.I[:j], rule=c_rule)
445        m.d = Disjunct(m.I, rule=d_rule)
446        m.disjunction = Disjunction(expr=[m.d[i] for i in m.I])
447
448        TransformationFactory('gdp.bigm').apply_to(m)
449        transBlock = m._pyomo_gdp_bigm_reformulation
450
451        # 2 blocks: the original Disjunct and the transformation block
452        self.assertEqual(
453            len(list(m.component_objects(Block, descend_into=False))), 1)
454        self.assertEqual(
455            len(list(m.component_objects(Disjunct))), 1)
456
457        # Each relaxed disjunct should have 1 var (the reference to the
458        # indicator var), and i "d[i].c" Constraints
459        for i in [1,2,3]:
460            relaxed = transBlock.relaxedDisjuncts[i-1]
461            self.assertEqual(len(list(relaxed.component_objects(Var))), 1)
462            self.assertEqual(len(list(relaxed.component_data_objects(Var))), 1)
463            self.assertEqual(
464                len(list(relaxed.component_objects(Constraint))), 1)
465            self.assertEqual(
466                len(list(relaxed.component_data_objects(Constraint))), i)
467            self.assertEqual(len(relaxed.component('d[%s].c'%i)), i)
468
469    def test_virtual_indexed_constraints_in_disjunct(self):
470        m = ConcreteModel()
471        m.I = [1,2,3]
472        m.x = Var(m.I, bounds=(0,10))
473        def d_rule(d,j):
474            m = d.model()
475            d.c = Constraint(Any)
476            for k in range(j):
477                d.c[k+1] = m.x[k+1] >= k+1
478        m.d = Disjunct(m.I, rule=d_rule)
479        m.disjunction = Disjunction(expr=[m.d[i] for i in m.I])
480
481        TransformationFactory('gdp.bigm').apply_to(m)
482        transBlock = m._pyomo_gdp_bigm_reformulation
483
484        # 2 blocks: the original Disjunct and the transformation block
485        self.assertEqual(
486            len(list(m.component_objects(Block, descend_into=False))), 1)
487        self.assertEqual(
488            len(list(m.component_objects(Disjunct))), 1)
489
490        # Each relaxed disjunct should have 1 var (the reference to the
491        # indicator var), and i "d[i].c" Constraints
492        for i in [1,2,3]:
493            relaxed = transBlock.relaxedDisjuncts[i-1]
494            self.assertEqual(len(list(relaxed.component_objects(Var))), 1)
495            self.assertEqual(len(list(relaxed.component_data_objects(Var))), 1)
496            self.assertEqual(
497                len(list(relaxed.component_objects(Constraint))), 1)
498            self.assertEqual(
499                len(list(relaxed.component_data_objects(Constraint))), i)
500            self.assertEqual(len(relaxed.component('d[%s].c'%i)), i)
501
502    def test_local_var(self):
503        m = models.localVar()
504        bigm = TransformationFactory('gdp.bigm')
505        bigm.apply_to(m)
506
507        # we just need to make sure that constraint was transformed correctly,
508        # which just means that the M values were correct.
509        transformedC = bigm.get_transformed_constraints(m.disj2.cons)
510        self.assertEqual(len(transformedC), 2)
511        lb = transformedC[0]
512        ub = transformedC[1]
513        repn = generate_standard_repn(lb.body)
514        self.assertTrue(repn.is_linear())
515        ct.check_linear_coef(self, repn, m.disj2.indicator_var, -2)
516        repn = generate_standard_repn(ub.body)
517        self.assertTrue(repn.is_linear())
518        ct.check_linear_coef(self, repn, m.disj2.indicator_var, 3)
519
520class TwoTermDisjNonlinear(unittest.TestCase, CommonTests):
521    def test_nonlinear_bigM(self):
522        m = models.makeTwoTermDisj_Nonlinear()
523        TransformationFactory('gdp.bigm').apply_to(m)
524        disjBlock = m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts
525
526        # first constraint
527        c = disjBlock[0].component("d[0].c")
528        self.assertEqual(len(c), 1)
529        self.assertTrue(c['ub'].active)
530        repn = generate_standard_repn(c['ub'].body)
531        self.assertFalse(repn.is_linear())
532        self.assertEqual(len(repn.linear_vars), 2)
533        ct.check_linear_coef(self, repn, m.x, 1)
534        ct.check_linear_coef(self, repn, m.d[0].indicator_var, 94)
535        self.assertEqual(repn.constant, -94)
536        self.assertEqual(c['ub'].upper, m.d[0].c.upper)
537        self.assertIsNone(c['ub'].lower)
538
539    def test_nonlinear_bigM_missing_var_bounds(self):
540        m = models.makeTwoTermDisj_Nonlinear()
541        m.y.setlb(None)
542        self.assertRaisesRegex(
543            GDP_Error,
544            r"Cannot estimate M for unbounded "
545            r"expressions.\n\t\(found while processing "
546            r"constraint 'd\[0\].c'\)",
547            TransformationFactory('gdp.bigm').apply_to,
548            m)
549
550    def test_nonlinear_disjoint(self):
551        m = ConcreteModel()
552        x = m.x = Var(bounds=(-4, 4))
553        y = m.y = Var(bounds=(-10, 10))
554        m.disj = Disjunction(expr=[
555            [x**2 + y**2 <= 2, x**3 + y**2 + x * y >= 1.0/2.0],
556            [(x - 3)**2 + (y - 3)**2 <= 1]
557        ])
558        TransformationFactory('gdp.bigm').apply_to(m)
559        disjBlock = m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts
560
561        # first disjunct, first constraint
562        c = disjBlock[0].component("disj_disjuncts[0].constraint")
563        self.assertEqual(len(c), 2)
564        repn = generate_standard_repn(c[1, 'ub'].body)
565        self.assertFalse(repn.is_linear())
566        self.assertEqual(len(repn.linear_vars), 1)
567        ct.check_linear_coef(self, repn, m.disj_disjuncts[0].indicator_var, 114)
568        self.assertEqual(repn.constant, -114)
569        self.assertEqual(c[1, 'ub'].upper,
570                         m.disj_disjuncts[0].constraint[1].upper)
571        self.assertIsNone(c[1, 'ub'].lower)
572        # first disjunct, second constraint
573        repn = generate_standard_repn(c[2, 'lb'].body)
574        self.assertFalse(repn.is_linear())
575        self.assertEqual(len(repn.linear_vars), 1)
576        ct.check_linear_coef(self, repn, m.disj_disjuncts[0].indicator_var,
577                             -104.5)
578        self.assertEqual(repn.constant, 104.5)
579        self.assertEqual(c[2, 'lb'].lower,
580                         m.disj_disjuncts[0].constraint[2].lower)
581        self.assertIsNone(c[2, 'lb'].upper)
582        # second disjunct, first constraint
583        c = disjBlock[1].component("disj_disjuncts[1].constraint")
584        self.assertEqual(len(c), 1)
585        repn = generate_standard_repn(c[1, 'ub'].body)
586        self.assertFalse(repn.is_linear())
587        self.assertEqual(len(repn.linear_vars), 3)
588        ct.check_linear_coef(self, repn, m.x, -6)
589        ct.check_linear_coef(self, repn, m.y, -6)
590        ct.check_linear_coef(self, repn, m.disj_disjuncts[1].indicator_var, 217)
591        self.assertEqual(repn.constant, -199)
592        self.assertEqual(c[1, 'ub'].upper,
593                         m.disj_disjuncts[1].constraint[1].upper)
594        self.assertIsNone(c[1, 'ub'].lower)
595
596
597class TwoTermIndexedDisj(unittest.TestCase, CommonTests):
598    def setUp(self):
599        # set seed so we can test name collisions predictably
600        random.seed(666)
601        # These are the pairs of which disjunct indices map to which
602        # blocks in the list of block on the transformation
603        # block. This is needed in multiple tests, so I am storing it
604        # here.
605        self.pairs = [
606            ( (0,1,'A'), 0 ),
607            ( (1,1,'A'), 1 ),
608            ( (0,1,'B'), 2 ),
609            ( (1,1,'B'), 3 ),
610            ( (0,2,'A'), 4 ),
611            ( (1,2,'A'), 5 ),
612            ( (0,2,'B'), 6 ),
613            ( (1,2,'B'), 7 ),
614        ]
615
616    def test_xor_constraints(self):
617        ct.check_indexed_xor_constraints(self, 'bigm')
618
619    def test_deactivated_constraints(self):
620        ct.check_constraints_deactivated_indexedDisjunction(self, 'bigm')
621
622    def test_transformed_block_structure(self):
623        m = models.makeTwoTermMultiIndexedDisjunction()
624        TransformationFactory('gdp.bigm').apply_to(m)
625        transBlock = m.component("_pyomo_gdp_bigm_reformulation")
626        self.assertIsInstance(transBlock, Block)
627
628        # check that we have the lbub set on the transformation block
629        lbub = transBlock.component("lbub")
630        self.assertIsInstance(lbub, Set)
631        self.assertEqual(len(lbub), 2)
632        self.assertEqual(lbub, ['lb', 'ub'])
633
634        # check the IndexedBlock of transformed disjuncts
635        disjBlock = transBlock.relaxedDisjuncts
636        self.assertEqual(len(disjBlock), 8)
637
638        # check that all 8 blocks have the right constraint on them.
639        # this relies on the order in which they are transformed.
640        for i,j in self.pairs:
641            self.assertIsInstance(
642                disjBlock[j].component(m.disjunct[i].c.name),
643                Constraint)
644
645    def test_disjunct_and_constraint_maps(self):
646        m = models.makeTwoTermMultiIndexedDisjunction()
647        bigm = TransformationFactory('gdp.bigm')
648        bigm.apply_to(m)
649
650        disjBlock = m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts
651        oldblock = m.component("disjunct")
652
653        # this test relies on the fact that the disjuncts are going to be
654        # relaxed in the same order every time, so they will correspond to
655        # these indices on the transformation block:
656        for src, dest in self.pairs:
657            srcDisjunct = oldblock[src]
658            transformedDisjunct = disjBlock[dest]
659            self.assertIs(bigm.get_src_disjunct(transformedDisjunct),
660                          srcDisjunct)
661            self.assertIs(transformedDisjunct,
662                          srcDisjunct.transformation_block())
663
664            transformed = bigm.get_transformed_constraints(srcDisjunct.c)
665            if src[0]:
666                # equality
667                self.assertEqual(len(transformed), 2)
668                self.assertIsInstance(transformed[0], _ConstraintData)
669                self.assertIsInstance(transformed[1], _ConstraintData)
670                self.assertIs(
671                    transformed[0],
672                    disjBlock[dest].component(srcDisjunct.c.name)['lb'])
673                self.assertIs(
674                    transformed[1],
675                    disjBlock[dest].component(srcDisjunct.c.name)['ub'])
676                # check reverse maps from the _ConstraintDatas
677                self.assertIs(bigm.get_src_constraint(
678                    disjBlock[dest].component(srcDisjunct.c.name)['lb']),
679                              srcDisjunct.c)
680                self.assertIs(bigm.get_src_constraint(
681                    disjBlock[dest].component(srcDisjunct.c.name)['ub']),
682                              srcDisjunct.c)
683            else:
684                # >=
685                self.assertEqual(len(transformed), 1)
686                self.assertIsInstance(transformed[0], _ConstraintData)
687                self.assertIs(
688                    transformed[0],
689                    disjBlock[dest].component(srcDisjunct.c.name)['lb'])
690                self.assertIs(bigm.get_src_constraint(
691                    disjBlock[dest].component(srcDisjunct.c.name)['lb']),
692                              srcDisjunct.c)
693            # check reverse map from the container
694            self.assertIs(bigm.get_src_constraint(
695                disjBlock[dest].component(srcDisjunct.c.name)),
696                srcDisjunct.c)
697
698    def test_deactivated_disjuncts(self):
699        ct.check_deactivated_disjuncts(self, 'bigm')
700
701    def test_deactivated_disjunction(self):
702        ct.check_deactivated_disjunctions(self, 'bigm')
703
704    def test_create_using(self):
705        m = models.makeTwoTermMultiIndexedDisjunction()
706        self.diff_apply_to_and_create_using(m)
707
708class DisjOnBlock(unittest.TestCase, CommonTests):
709    # when the disjunction is on a block, we want all of the stuff created by
710    # the transformation to go on that block also so that solving the block
711    # maintains its meaning
712
713    def test_xor_constraint_added(self):
714        ct.check_xor_constraint_added(self, 'bigm')
715
716    def test_trans_block_created(self):
717        ct.check_trans_block_created(self, 'bigm')
718
719    def checkFirstDisjMs(self, model, disj1c1lb, disj1c1ub, disj1c2):
720        bigm = TransformationFactory('gdp.bigm')
721
722        c1 = bigm.get_transformed_constraints(model.b.disjunct[0].c)
723        self.assertEqual(len(c1), 2)
724        lb = c1[0]
725        ub = c1[1]
726        repn = generate_standard_repn(lb.body)
727        self.assertTrue(repn.is_linear())
728        self.assertEqual(repn.constant, -disj1c1lb)
729        ct.check_linear_coef(
730            self, repn, model.b.disjunct[0].indicator_var, disj1c1lb)
731        repn = generate_standard_repn(ub.body)
732        self.assertTrue(repn.is_linear())
733        self.assertEqual(repn.constant, -disj1c1ub)
734        ct.check_linear_coef(
735            self, repn, model.b.disjunct[0].indicator_var, disj1c1ub)
736
737        c2 = bigm.get_transformed_constraints(model.b.disjunct[1].c)
738        self.assertEqual(len(c2), 1)
739        ub = c2[0]
740        repn = generate_standard_repn(ub.body)
741        self.assertTrue(repn.is_linear())
742        self.assertEqual(repn.constant, -disj1c2)
743        ct.check_linear_coef(
744            self, repn, model.b.disjunct[1].indicator_var, disj1c2)
745
746    def checkMs(self, model, disj1c1lb, disj1c1ub, disj1c2, disj2c1, disj2c2):
747        bigm = TransformationFactory('gdp.bigm')
748        self.checkFirstDisjMs(model, disj1c1lb, disj1c1ub, disj1c2)
749
750        c = bigm.get_transformed_constraints(model.simpledisj.c)
751        self.assertEqual(len(c), 1)
752        lb = c[0]
753        repn = generate_standard_repn(lb.body)
754        self.assertTrue(repn.is_linear())
755        self.assertEqual(repn.constant, -disj2c1)
756        ct.check_linear_coef(
757            self, repn, model.simpledisj.indicator_var, disj2c1)
758
759        c = bigm.get_transformed_constraints(model.simpledisj2.c)
760        self.assertEqual(len(c), 1)
761        ub = c[0]
762        repn = generate_standard_repn(ub.body)
763        self.assertTrue(repn.is_linear())
764        self.assertEqual(repn.constant, -disj2c2)
765        ct.check_linear_coef(
766            self, repn, model.simpledisj2.indicator_var, disj2c2)
767
768    def test_suffix_M_onBlock(self):
769        m = models.makeTwoTermDisjOnBlock()
770        # adding something that's not on the block so that I know that only
771        # the stuff on the block was changed
772        m = models.add_disj_not_on_block(m)
773        m.b.BigM = Suffix(direction=Suffix.LOCAL)
774        m.b.BigM[None] = 34
775        bigm = TransformationFactory('gdp.bigm')
776        bigm.apply_to(m)
777
778        # check m values
779        self.checkMs(m, -34, 34, 34, -3, 1.5)
780
781        # check the source of the values
782        ((l_val, l_src, l_key),
783         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj.c)
784        self.assertIsNone(l_src)
785        self.assertIsNone(u_src)
786        self.assertIsNone(l_key)
787        self.assertIsNone(u_key)
788        self.assertEqual(l_val, -3)
789        self.assertIsNone(u_val)
790        (l_val, u_val) = bigm.get_M_value(m.simpledisj.c)
791        self.assertEqual(l_val, -3)
792        self.assertIsNone(u_val)
793
794        ((l_val, l_src, l_key),
795         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj2.c)
796        self.assertIsNone(l_src)
797        self.assertIsNone(u_src)
798        self.assertIsNone(l_key)
799        self.assertIsNone(u_key)
800        self.assertIsNone(l_val)
801        self.assertEqual(u_val, 1.5)
802        (l_val, u_val) = bigm.get_M_value(m.simpledisj2.c)
803        self.assertIsNone(l_val)
804        self.assertEqual(u_val, 1.5)
805
806        ((l_val, l_src, l_key),
807         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[0].c)
808        self.assertIs(l_src, m.b.BigM)
809        self.assertIs(u_src, m.b.BigM)
810        self.assertIsNone(l_key)
811        self.assertIsNone(u_key)
812        self.assertEqual(l_val, -34)
813        self.assertEqual(u_val, 34)
814        l_val, u_val = bigm.get_M_value(m.b.disjunct[0].c)
815        self.assertEqual(l_val, -34)
816        self.assertEqual(u_val, 34)
817
818        ((l_val, l_src, l_key),
819         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[1].c)
820        self.assertIsNone(l_src)
821        self.assertIs(u_src, m.b.BigM)
822        self.assertIsNone(l_key)
823        self.assertIsNone(u_key)
824        self.assertIsNone(l_val)
825        self.assertEqual(u_val, 34)
826        l_val, u_val = bigm.get_M_value(m.b.disjunct[1].c)
827        self.assertIsNone(l_val)
828        self.assertEqual(u_val, 34)
829
830    def test_block_M_arg(self):
831        m = models.makeTwoTermDisjOnBlock()
832        m = models.add_disj_not_on_block(m)
833        bigms = {m.b: 100, m.b.disjunct[1].c: 13}
834        bigm = TransformationFactory('gdp.bigm')
835        bigm.apply_to(m, bigM=bigms)
836        self.checkMs(m, -100, 100, 13, -3, 1.5)
837
838        # check the source of the values
839        ((l_val, l_src, l_key),
840         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj.c)
841        self.assertIsNone(l_src)
842        self.assertIsNone(u_src)
843        self.assertIsNone(l_key)
844        self.assertIsNone(u_key)
845        self.assertEqual(l_val, -3)
846        self.assertIsNone(u_val)
847        (l_val, u_val) = bigm.get_M_value(m.simpledisj.c)
848        self.assertEqual(l_val, -3)
849        self.assertIsNone(u_val)
850
851        ((l_val, l_src, l_key),
852         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj2.c)
853        self.assertIsNone(l_src)
854        self.assertIsNone(u_src)
855        self.assertIsNone(l_key)
856        self.assertIsNone(u_key)
857        self.assertIsNone(l_val)
858        self.assertEqual(u_val, 1.5)
859        (l_val, u_val) = bigm.get_M_value(m.simpledisj2.c)
860        self.assertIsNone(l_val)
861        self.assertEqual(u_val, 1.5)
862
863        ((l_val, l_src, l_key),
864         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[0].c)
865        self.assertIs(l_src, bigms)
866        self.assertIs(u_src, bigms)
867        self.assertIs(l_key, m.b)
868        self.assertIs(u_key, m.b)
869        self.assertEqual(l_val, -100)
870        self.assertEqual(u_val, 100)
871        l_val, u_val = bigm.get_M_value(m.b.disjunct[0].c)
872        self.assertEqual(l_val, -100)
873        self.assertEqual(u_val, 100)
874
875        ((l_val, l_src, l_key),
876         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[1].c)
877        self.assertIsNone(l_src)
878        self.assertIs(u_src, bigms)
879        self.assertIsNone(l_key)
880        self.assertIs(u_key, m.b.disjunct[1].c)
881        self.assertIsNone(l_val)
882        self.assertEqual(u_val, 13)
883        l_val, u_val = bigm.get_M_value(m.b.disjunct[1].c)
884        self.assertIsNone(l_val)
885        self.assertEqual(u_val, 13)
886
887    def test_disjunct_M_arg(self):
888        m = models.makeTwoTermDisjOnBlock()
889        m = models.add_disj_not_on_block(m)
890        bigm = TransformationFactory('gdp.bigm')
891        bigms = {m.b: 100, m.b.disjunct[1]: 13}
892        bigm.apply_to(m, bigM=bigms)
893        self.checkMs(m, -100, 100, 13, -3, 1.5)
894
895        # check the source of the values
896        ((l_val, l_src, l_key),
897         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj.c)
898        self.assertIsNone(l_src)
899        self.assertIsNone(u_src)
900        self.assertIsNone(l_key)
901        self.assertIsNone(u_key)
902        self.assertEqual(l_val, -3)
903        self.assertIsNone(u_val)
904        (l_val, u_val) = bigm.get_M_value(m.simpledisj.c)
905        self.assertEqual(l_val, -3)
906        self.assertIsNone(u_val)
907
908        ((l_val, l_src, l_key),
909         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj2.c)
910        self.assertIsNone(l_src)
911        self.assertIsNone(u_src)
912        self.assertIsNone(l_key)
913        self.assertIsNone(u_key)
914        self.assertIsNone(l_val)
915        self.assertEqual(u_val, 1.5)
916        (l_val, u_val) = bigm.get_M_value(m.simpledisj2.c)
917        self.assertIsNone(l_val)
918        self.assertEqual(u_val, 1.5)
919
920        ((l_val, l_src, l_key),
921         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[0].c)
922        self.assertIs(l_src, bigms)
923        self.assertIs(u_src, bigms)
924        self.assertIs(l_key, m.b)
925        self.assertIs(u_key, m.b)
926        self.assertEqual(l_val, -100)
927        self.assertEqual(u_val, 100)
928        l_val, u_val = bigm.get_M_value(m.b.disjunct[0].c)
929        self.assertEqual(l_val, -100)
930        self.assertEqual(u_val, 100)
931
932        ((l_val, l_src, l_key),
933         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[1].c)
934        self.assertIsNone(l_src)
935        self.assertIs(u_src, bigms)
936        self.assertIsNone(l_key)
937        self.assertIs(u_key, m.b.disjunct[1])
938        self.assertIsNone(l_val)
939        self.assertEqual(u_val, 13)
940        l_val, u_val = bigm.get_M_value(m.b.disjunct[1].c)
941        self.assertIsNone(l_val)
942        self.assertEqual(u_val, 13)
943
944    def test_block_M_arg_with_default(self):
945        m = models.makeTwoTermDisjOnBlock()
946        m = models.add_disj_not_on_block(m)
947        bigm = TransformationFactory('gdp.bigm')
948        bigms = {m.b: 100, m.b.disjunct[1].c: 13,
949                 m.b.disjunct[0].c: (None, 50), None: 34}
950        bigm.apply_to(m, bigM=bigms)
951        self.checkMs(m, -100, 50, 13, -34, 34)
952
953        # check the source of the values
954        ((l_val, l_src, l_key),
955         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj.c)
956        self.assertIs(l_src, bigms)
957        self.assertIsNone(u_src)
958        self.assertIsNone(l_key)
959        self.assertIsNone(u_key)
960        self.assertEqual(l_val, -34)
961        self.assertIsNone(u_val)
962        l_val, u_val = bigm.get_M_value(m.simpledisj.c)
963        self.assertEqual(l_val, -34)
964        self.assertIsNone(u_val)
965
966        ((l_val, l_src, l_key),
967         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj2.c)
968        self.assertIsNone(l_src)
969        self.assertIs(u_src, bigms)
970        self.assertIsNone(l_key)
971        self.assertIsNone(u_key)
972        self.assertIsNone(l_val)
973        self.assertEqual(u_val, 34)
974        l_val, u_val = bigm.get_M_value(m.simpledisj2.c)
975        self.assertIsNone(l_val)
976        self.assertEqual(u_val, 34)
977
978        ((l_val, l_src, l_key),
979         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[0].c)
980        self.assertIs(l_src, bigms)
981        self.assertIs(u_src, bigms)
982        self.assertIs(l_key, m.b)
983        self.assertIs(u_key, m.b.disjunct[0].c)
984        self.assertEqual(l_val, -100)
985        self.assertEqual(u_val, 50)
986        l_val, u_val = bigm.get_M_value(m.b.disjunct[0].c)
987        self.assertEqual(l_val, -100)
988        self.assertEqual(u_val, 50)
989
990        ((l_val, l_src, l_key),
991         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[1].c)
992        self.assertIsNone(l_src)
993        self.assertIs(u_src, bigms)
994        self.assertIsNone(l_key)
995        self.assertIs(u_key, m.b.disjunct[1].c)
996        self.assertIsNone(l_val)
997        self.assertEqual(u_val, 13)
998        l_val, u_val = bigm.get_M_value(m.b.disjunct[1].c)
999        self.assertIsNone(l_val)
1000        self.assertEqual(u_val, 13)
1001
1002    def test_model_M_arg(self):
1003        m = models.makeTwoTermDisjOnBlock()
1004        m = models.add_disj_not_on_block(m)
1005        out = StringIO()
1006        with LoggingIntercept(out, 'pyomo.gdp.bigm'):
1007            TransformationFactory('gdp.bigm').apply_to(
1008                m,
1009                bigM={m: 100,
1010                      m.b.disjunct[1].c: 13})
1011        self.checkMs(m, -100, 100, 13, -100, 100)
1012        # make sure we didn't get any warnings when we used all the args
1013        self.assertEqual(out.getvalue(), '')
1014
1015    def test_model_M_arg_overrides_None(self):
1016        m = models.makeTwoTermDisjOnBlock()
1017        m = models.add_disj_not_on_block(m)
1018        out = StringIO()
1019        with LoggingIntercept(out, 'pyomo.gdp.bigm'):
1020            TransformationFactory('gdp.bigm').apply_to(
1021                m,
1022                bigM={m: 100,
1023                      m.b.disjunct[1].c: 13,
1024                      None: 34})
1025        self.checkMs(m, -100, 100, 13, -100, 100)
1026        self.assertEqual(out.getvalue(),
1027                         "Unused arguments in the bigM map! "
1028                         "These arguments were not used by the "
1029                         "transformation:\n\tNone\n\n")
1030
1031    def test_warning_for_crazy_bigm_args(self):
1032        m = models.makeTwoTermDisjOnBlock()
1033        m = models.add_disj_not_on_block(m)
1034        out = StringIO()
1035        bigM = ComponentMap({m: 100, m.b.disjunct[1].c: 13})
1036        # this is silly
1037        bigM[m.a] = 34
1038        with LoggingIntercept(out, 'pyomo.gdp.bigm'):
1039            TransformationFactory('gdp.bigm').apply_to( m, bigM=bigM)
1040        self.checkMs(m, -100, 100, 13, -100, 100)
1041        self.assertEqual(out.getvalue(),
1042                         "Unused arguments in the bigM map! "
1043                         "These arguments were not used by the "
1044                         "transformation:\n\ta\n\n")
1045
1046    def test_use_above_scope_m_value(self):
1047        m = models.makeTwoTermDisjOnBlock()
1048        m = models.add_disj_not_on_block(m)
1049        bigM = ComponentMap({m: 100, m.b.disjunct[1].c: 13})
1050        out = StringIO()
1051        # transform just the block. We expect to use the M value specified on
1052        # the model, and we should comment on nothing.
1053        with LoggingIntercept(out, 'pyomo.gdp.bigm'):
1054            TransformationFactory('gdp.bigm').apply_to( m.b, bigM=bigM)
1055        self.checkFirstDisjMs(m, -100, 100, 13)
1056        self.assertEqual(out.getvalue(), '')
1057
1058    def test_unused_arguments_transform_block(self):
1059        m = models.makeTwoTermDisjOnBlock()
1060        m = models.add_disj_not_on_block(m)
1061
1062        m.BigM = Suffix(direction=Suffix.LOCAL)
1063        m.BigM[None] = 1e6
1064        m.b.BigM = Suffix(direction=Suffix.LOCAL)
1065        m.b.BigM[None] = 15
1066
1067        out = StringIO()
1068        with LoggingIntercept(out, 'pyomo.gdp.bigm'):
1069            TransformationFactory('gdp.bigm').apply_to(
1070                m.b,
1071                bigM={m: 100,
1072                      m.b: 13,
1073                      m.simpledisj2.c: 10})
1074
1075        self.checkFirstDisjMs(m, -13, 13, 13)
1076
1077        # The order these get printed depends on a dictionary order, so test
1078        # this way...
1079        self.assertIn("Unused arguments in the bigM map! "
1080                      "These arguments were not used by the "
1081                      "transformation:",
1082                      out.getvalue())
1083        self.assertIn("simpledisj2.c", out.getvalue())
1084        self.assertIn("unknown", out.getvalue())
1085
1086    def test_suffix_M_simple_disj(self):
1087        m = models.makeTwoTermDisjOnBlock()
1088        m = models.add_disj_not_on_block(m)
1089        m.simpledisj.BigM = Suffix(direction=Suffix.LOCAL)
1090        m.simpledisj.BigM[None] = 45
1091        m.BigM = Suffix(direction=Suffix.LOCAL)
1092        m.BigM[None] = 20
1093
1094        bigm = TransformationFactory('gdp.bigm')
1095        bigm.apply_to(m)
1096        self.checkMs(m, -20, 20, 20, -45, 20)
1097
1098        # check source of the m values
1099        ((l_val, l_src, l_key),
1100         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj.c)
1101        self.assertIs(l_src, m.simpledisj.BigM)
1102        self.assertIsNone(u_src)
1103        self.assertIsNone(l_key)
1104        self.assertIsNone(u_key)
1105        self.assertEqual(l_val, -45)
1106        self.assertIsNone(u_val)
1107        l_val, u_val = bigm.get_M_value(m.simpledisj.c)
1108        self.assertEqual(l_val, -45)
1109        self.assertIsNone(u_val)
1110
1111        ((l_val, l_src, l_key),
1112         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj2.c)
1113        self.assertIsNone(l_src)
1114        self.assertIs(u_src, m.BigM)
1115        self.assertIsNone(l_key)
1116        self.assertIsNone(u_key)
1117        self.assertIsNone(l_val)
1118        self.assertEqual(u_val, 20)
1119        l_val, u_val = bigm.get_M_value(m.simpledisj2.c)
1120        self.assertIsNone(l_val)
1121        self.assertEqual(u_val, 20)
1122
1123        ((l_val, l_src, l_key),
1124         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[0].c)
1125        self.assertIs(l_src, m.BigM)
1126        self.assertIs(u_src, m.BigM)
1127        self.assertIsNone(l_key)
1128        self.assertIsNone(u_key)
1129        self.assertEqual(l_val, -20)
1130        self.assertEqual(u_val, 20)
1131        l_val, u_val = bigm.get_M_value(m.b.disjunct[0].c)
1132        self.assertEqual(l_val, -20)
1133        self.assertEqual(u_val, 20)
1134
1135        ((l_val, l_src, l_key),
1136         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[1].c)
1137        self.assertIsNone(l_src)
1138        self.assertIs(u_src, m.BigM)
1139        self.assertIsNone(l_key)
1140        self.assertIsNone(u_key)
1141        self.assertIsNone(l_val)
1142        self.assertEqual(u_val, 20)
1143        l_val, u_val = bigm.get_M_value(m.b.disjunct[1].c)
1144        self.assertIsNone(l_val)
1145        self.assertEqual(u_val, 20)
1146
1147    def test_suffix_M_constraintKeyOnBlock(self):
1148        m = models.makeTwoTermDisjOnBlock()
1149        m.b.BigM = Suffix(direction=Suffix.LOCAL)
1150        m.b.BigM[m.b.disjunct[0].c] = 87
1151        m.b.BigM[None] = 64
1152
1153        TransformationFactory('gdp.bigm').apply_to(m)
1154        self.checkFirstDisjMs(m, -87, 87, 64)
1155
1156    def test_suffix_M_constraintKeyOnModel(self):
1157        m = models.makeTwoTermDisjOnBlock()
1158        m.b.BigM = Suffix(direction=Suffix.LOCAL)
1159        m.b.BigM[None] = 64
1160        m.BigM = Suffix(direction=Suffix.LOCAL)
1161        m.BigM[m.b.disjunct[0].c] = 87
1162
1163        TransformationFactory('gdp.bigm').apply_to(m)
1164        self.checkFirstDisjMs(m, -87, 87, 64)
1165
1166    def test_suffix_M_constraintKeyOnSimpleDisj(self):
1167        m = models.makeTwoTermDisjOnBlock()
1168        m = models.add_disj_not_on_block(m)
1169        m.simpledisj.BigM = Suffix(direction=Suffix.LOCAL)
1170        m.simpledisj.BigM[None] = 45
1171        m.simpledisj.BigM[m.simpledisj.c] = 87
1172        m.BigM = Suffix(direction=Suffix.LOCAL)
1173        m.BigM[None] = 20
1174
1175        bigms = {m.b.disjunct[0].c: (-15, None)}
1176        bigm = TransformationFactory('gdp.bigm')
1177
1178        bigm.apply_to(m, bigM=bigms)
1179        self.checkMs(m, -15, 20, 20, -87, 20)
1180
1181        # check source of the m values
1182        ((l_val, l_src, l_key),
1183         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj.c)
1184        self.assertIs(l_src, m.simpledisj.BigM)
1185        self.assertIsNone(u_src)
1186        self.assertIs(l_key, m.simpledisj.c)
1187        self.assertIsNone(u_key)
1188        self.assertEqual(l_val, -87)
1189        self.assertIsNone(u_val)
1190        l_val, u_val = bigm.get_M_value(m.simpledisj.c)
1191        self.assertEqual(l_val, -87)
1192        self.assertIsNone(u_val)
1193
1194        ((l_val, l_src, l_key),
1195         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.simpledisj2.c)
1196        self.assertIsNone(l_src)
1197        self.assertIs(u_src, m.BigM)
1198        self.assertIsNone(l_key)
1199        self.assertIsNone(u_key)
1200        self.assertIsNone(l_val)
1201        self.assertEqual(u_val, 20)
1202        l_val, u_val = bigm.get_M_value(m.simpledisj2.c)
1203        self.assertIsNone(l_val)
1204        self.assertEqual(u_val, 20)
1205
1206        ((l_val, l_src, l_key),
1207         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[0].c)
1208        self.assertIs(l_src, bigms)
1209        self.assertIs(u_src, m.BigM)
1210        self.assertIs(l_key, m.b.disjunct[0].c)
1211        self.assertIsNone(u_key)
1212        self.assertEqual(l_val, -15)
1213        self.assertEqual(u_val, 20)
1214        l_val, u_val = bigm.get_M_value(m.b.disjunct[0].c)
1215        self.assertEqual(l_val, -15)
1216        self.assertEqual(u_val, 20)
1217
1218        ((l_val, l_src, l_key),
1219         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.b.disjunct[1].c)
1220        self.assertIsNone(l_src)
1221        self.assertIs(u_src, m.BigM)
1222        self.assertIsNone(l_key)
1223        self.assertIsNone(u_key)
1224        self.assertIsNone(l_val)
1225        self.assertEqual(u_val, 20)
1226        l_val, u_val = bigm.get_M_value(m.b.disjunct[1].c)
1227        self.assertIsNone(l_val)
1228        self.assertEqual(u_val, 20)
1229
1230    def test_suffix_M_constraintKeyOnSimpleDisj_deprecated_m_src_method(self):
1231        m = models.makeTwoTermDisjOnBlock()
1232        m = models.add_disj_not_on_block(m)
1233        m.simpledisj.BigM = Suffix(direction=Suffix.LOCAL)
1234        m.simpledisj.BigM[None] = 45
1235        m.simpledisj.BigM[m.simpledisj.c] = 87
1236        m.BigM = Suffix(direction=Suffix.LOCAL)
1237        m.BigM[None] = 20
1238
1239        bigms = {m.b.disjunct[0].c: (-15, None)}
1240        bigm = TransformationFactory('gdp.bigm')
1241
1242        bigm.apply_to(m, bigM=bigms)
1243
1244        # check source of the m values
1245        (src, key) = bigm.get_m_value_src(m.simpledisj.c)
1246        self.assertIs(src, m.simpledisj.BigM)
1247        self.assertIs(key, m.simpledisj.c)
1248        (src, key) = bigm.get_m_value_src(m.simpledisj2.c)
1249        self.assertIs(src, m.BigM)
1250        self.assertIsNone(key)
1251        self.assertRaisesRegex(
1252            GDP_Error,
1253            r"This is why this method is deprecated: The lower "
1254            r"and upper M values for constraint b.disjunct\[0\].c "
1255            r"came from different sources, please use the "
1256            r"get_M_value_src method.",
1257            bigm.get_m_value_src,
1258            m.b.disjunct[0].c)
1259        (src, key) = bigm.get_m_value_src(m.b.disjunct[1].c)
1260        self.assertIs(src, m.BigM)
1261        self.assertIsNone(key)
1262
1263    def test_disjunct_M_arg_deprecated_m_src_method(self):
1264        m = models.makeTwoTermDisjOnBlock()
1265        m = models.add_disj_not_on_block(m)
1266        bigm = TransformationFactory('gdp.bigm')
1267        bigms = {m.b: 100, m.b.disjunct[1]: 13}
1268        bigm.apply_to(m, bigM=bigms)
1269        self.checkMs(m, -100, 100, 13, -3, 1.5)
1270
1271        # check the source of the values
1272        (src, key) = bigm.get_m_value_src(m.simpledisj.c)
1273        self.assertEqual(src, -3)
1274        self.assertIsNone(key)
1275        (src, key) = bigm.get_m_value_src(m.simpledisj2.c)
1276        self.assertIsNone(src)
1277        self.assertEqual(key, 1.5)
1278        (src, key) = bigm.get_m_value_src(m.b.disjunct[0].c)
1279        self.assertIs(src, bigms)
1280        self.assertIs(key, m.b)
1281        (src, key) = bigm.get_m_value_src(m.b.disjunct[1].c)
1282        self.assertIs(src, bigms)
1283        self.assertIs(key, m.b.disjunct[1])
1284
1285    def test_block_targets_inactive(self):
1286        ct.check_block_targets_inactive(self, 'bigm')
1287
1288    def test_block_only_targets_transformed(self):
1289        ct.check_block_only_targets_transformed(self, 'bigm')
1290
1291    def test_create_using(self):
1292        m = models.makeTwoTermDisjOnBlock()
1293        ct.diff_apply_to_and_create_using(self, m, 'gdp.bigm')
1294
1295
1296class ScalarDisjIndexedConstraints(unittest.TestCase, CommonTests):
1297    def setUp(self):
1298        # set seed so we can test name collisions predictably
1299        random.seed(666)
1300
1301    def test_do_not_transform_deactivated_constraintDatas(self):
1302        # ESJ: specific to how bigM transforms constraints (so not a common test
1303        # with hull)
1304        m = models.makeTwoTermDisj_IndexedConstraints()
1305        m.BigM = Suffix(direction=Suffix.LOCAL)
1306        m.BigM[None] = 30
1307        m.b.simpledisj1.c[1].deactivate()
1308        bigm = TransformationFactory('gdp.bigm')
1309        bigm.apply_to(m)
1310
1311        # the real test: This wasn't transformed
1312        log = StringIO()
1313        with LoggingIntercept(log, 'pyomo.gdp', logging.ERROR):
1314            self.assertRaisesRegex(
1315                KeyError,
1316                r".*b.simpledisj1.c\[1\]",
1317                bigm.get_transformed_constraints,
1318                m.b.simpledisj1.c[1])
1319        self.assertRegex(log.getvalue(),
1320                         r".*Constraint 'b.simpledisj1.c\[1\]' "
1321                         r"has not been transformed.")
1322
1323        # and the rest of the container was transformed
1324        cons_list = bigm.get_transformed_constraints(m.b.simpledisj1.c[2])
1325        self.assertEqual(len(cons_list), 2)
1326        lb = cons_list[0]
1327        ub = cons_list[1]
1328        self.assertIsInstance(lb, constraint._GeneralConstraintData)
1329        self.assertIsInstance(ub, constraint._GeneralConstraintData)
1330
1331    def checkMs(self, m, disj1c1lb, disj1c1ub, disj1c2lb, disj1c2ub, disj2c1ub,
1332                disj2c2ub):
1333        bigm = TransformationFactory('gdp.bigm')
1334        c = bigm.get_transformed_constraints(m.b.simpledisj1.c[1])
1335        self.assertEqual(len(c), 2)
1336        lb = c[0]
1337        ub = c[1]
1338        repn = generate_standard_repn(lb.body)
1339        self.assertTrue(repn.is_linear())
1340        self.assertEqual(repn.constant, -disj1c1lb)
1341        ct.check_linear_coef(
1342            self, repn, m.b.simpledisj1.indicator_var, disj1c1lb)
1343        repn = generate_standard_repn(ub.body)
1344        self.assertTrue(repn.is_linear())
1345        self.assertEqual(repn.constant, -disj1c1ub)
1346        ct.check_linear_coef(
1347            self, repn, m.b.simpledisj1.indicator_var, disj1c1ub)
1348        c = bigm.get_transformed_constraints(m.b.simpledisj1.c[2])
1349        self.assertEqual(len(c), 2)
1350        lb = c[0]
1351        ub = c[1]
1352        repn = generate_standard_repn(lb.body)
1353        self.assertTrue(repn.is_linear())
1354        self.assertEqual(repn.constant, -disj1c2lb)
1355        ct.check_linear_coef(
1356            self, repn, m.b.simpledisj1.indicator_var, disj1c2lb)
1357        repn = generate_standard_repn(ub.body)
1358        self.assertTrue(repn.is_linear())
1359        self.assertEqual(repn.constant, -disj1c2ub)
1360        ct.check_linear_coef(
1361            self, repn, m.b.simpledisj1.indicator_var, disj1c2ub)
1362
1363        c = bigm.get_transformed_constraints(m.b.simpledisj2.c[1])
1364        self.assertEqual(len(c), 1)
1365        ub = c[0]
1366        repn = generate_standard_repn(ub.body)
1367        self.assertTrue(repn.is_linear())
1368        self.assertEqual(repn.constant, -disj2c1ub)
1369        ct.check_linear_coef(
1370            self, repn, m.b.simpledisj2.indicator_var, disj2c1ub)
1371        c = bigm.get_transformed_constraints(m.b.simpledisj2.c[2])
1372        self.assertEqual(len(c), 1)
1373        ub = c[0]
1374        repn = generate_standard_repn(ub.body)
1375        self.assertTrue(repn.is_linear())
1376        self.assertEqual(repn.constant, -disj2c2ub)
1377        ct.check_linear_coef(
1378            self, repn, m.b.simpledisj2.indicator_var, disj2c2ub)
1379
1380    def test_suffix_M_constraintData_on_block(self):
1381        m = models.makeTwoTermDisj_IndexedConstraints()
1382        m.b.BigM = Suffix(direction=Suffix.LOCAL)
1383        m.b.BigM[None] = 30
1384        m.b.BigM[m.b.simpledisj1.c[1]] = 15
1385
1386        TransformationFactory('gdp.bigm').apply_to(m)
1387        self.checkMs(m, -15, 15, -30, 30, 30, 30)
1388
1389    def test_suffix_M_indexedConstraint_on_block(self):
1390        m = models.makeTwoTermDisj_IndexedConstraints()
1391        m.b.BigM = Suffix(direction=Suffix.LOCAL)
1392        m.b.BigM[None] = 30
1393        m.b.BigM[m.b.simpledisj2.c] = 15
1394
1395        TransformationFactory('gdp.bigm').apply_to(m)
1396        self.checkMs(m, -30, 30, -30, 30, 15, 15)
1397
1398    def test_suffix_M_constraintData_on_simpleDisjunct(self):
1399        m = models.makeTwoTermDisj_IndexedConstraints()
1400        m.BigM = Suffix(direction=Suffix.LOCAL)
1401        m.BigM[None] = 65
1402        m.b.simpledisj1.BigM = Suffix(direction=Suffix.LOCAL)
1403        m.b.simpledisj1.BigM[m.b.simpledisj1.c[2]] = (-14, 13)
1404
1405        TransformationFactory('gdp.bigm').apply_to(m)
1406        self.checkMs(m, -65, 65, -14, 13, 65, 65)
1407
1408    def test_suffix_M_indexedConstraint_on_simpleDisjunct(self):
1409        m = models.makeTwoTermDisj_IndexedConstraints()
1410        m.BigM = Suffix(direction=Suffix.LOCAL)
1411        m.BigM[None] = 65
1412        m.b.simpledisj1.BigM = Suffix(direction=Suffix.LOCAL)
1413        m.b.simpledisj1.BigM[m.b.simpledisj1.c] = (-14, 13)
1414
1415        TransformationFactory('gdp.bigm').apply_to(m)
1416        self.checkMs(m, -14, 13, -14, 13, 65, 65)
1417
1418    def test_unbounded_var_m_estimation_err(self):
1419        m = models.makeTwoTermDisj_IndexedConstraints()
1420        self.assertRaisesRegex(
1421            GDP_Error,
1422            r"Cannot estimate M for unbounded "
1423            r"expressions.\n\t\(found while processing "
1424            r"constraint 'b.simpledisj1.c'\). "
1425            r"Please specify a value of M "
1426            r"or ensure all variables that appear in the "
1427            r"constraint are bounded.",
1428            TransformationFactory('gdp.bigm').apply_to,
1429            m)
1430
1431    def test_create_using(self):
1432        m = models.makeTwoTermDisj_IndexedConstraints()
1433        m.BigM = Suffix(direction=Suffix.LOCAL)
1434        m.BigM[None] = 100
1435        self.diff_apply_to_and_create_using(m)
1436
1437
1438class SimpleDisjIndexedConstraints(metaclass=RenamedClass):
1439    __renamed__new_class__ = ScalarDisjIndexedConstraints
1440    __renamed__version__ = '6.0'
1441
1442
1443class MultiTermDisj(unittest.TestCase, CommonTests):
1444    def setUp(self):
1445        # set seed so we can test name collisions predictably
1446        random.seed(666)
1447
1448    def test_xor_constraint(self):
1449        ct.check_three_term_xor_constraint(self, 'bigm')
1450
1451    def test_create_using(self):
1452        m = models.makeThreeTermIndexedDisj()
1453        self.diff_apply_to_and_create_using(m)
1454
1455
1456class IndexedConstraintsInDisj(unittest.TestCase, CommonTests):
1457    def setUp(self):
1458        # set seed so we can test name collisions predictably
1459        random.seed(666)
1460
1461    def test_transformed_constraints_on_block(self):
1462        # constraints should still be moved as indexed constraints, and we will
1463        # just add ['lb', 'ub'] as another index (using both for equality and
1464        # both bounds and the one that we need when we only have one bound)
1465        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1466        TransformationFactory('gdp.bigm').apply_to(m)
1467
1468        transBlock = m.component("_pyomo_gdp_bigm_reformulation")
1469        self.assertIsInstance(transBlock, Block)
1470        disjBlock = transBlock.component("relaxedDisjuncts")
1471        self.assertIsInstance(disjBlock, Block)
1472        self.assertEqual(len(disjBlock), 2)
1473
1474        cons1 = disjBlock[0].component("disjunct[0].c")
1475        self.assertIsInstance(cons1, Constraint)
1476        self.assertTrue(cons1.active)
1477        self.assertTrue(cons1[1,'lb'].active)
1478        self.assertTrue(cons1[2,'lb'].active)
1479
1480        cons2 = disjBlock[1].component("disjunct[1].c")
1481        self.assertIsInstance(cons2, Constraint)
1482        self.assertTrue(cons2.active)
1483        self.assertTrue(cons2[1,'lb'].active)
1484        self.assertTrue(cons2[1,'ub'].active)
1485        self.assertTrue(cons2[2,'lb'].active)
1486        self.assertTrue(cons2[2,'ub'].active)
1487
1488    def checkMs(self, model, c11lb, c12lb, c21lb, c21ub, c22lb, c22ub):
1489        bigm = TransformationFactory('gdp.bigm')
1490        c = bigm.get_transformed_constraints(model.disjunct[0].c[1])
1491        self.assertEqual(len(c), 1)
1492        lb = c[0]
1493        repn = generate_standard_repn(lb.body)
1494        self.assertTrue(repn.is_linear())
1495        self.assertEqual(len(repn.linear_vars), 2)
1496        self.assertEqual(repn.constant, -c11lb)
1497        ct.check_linear_coef(self, repn, model.disjunct[0].indicator_var, c11lb)
1498        c = bigm.get_transformed_constraints(model.disjunct[0].c[2])
1499        self.assertEqual(len(c), 1)
1500        lb = c[0]
1501        repn = generate_standard_repn(lb.body)
1502        self.assertTrue(repn.is_linear())
1503        self.assertEqual(len(repn.linear_vars), 2)
1504        self.assertEqual(repn.constant, -c12lb)
1505        ct.check_linear_coef(self, repn, model.disjunct[0].indicator_var, c12lb)
1506
1507        c = bigm.get_transformed_constraints(model.disjunct[1].c[1])
1508        self.assertEqual(len(c), 2)
1509        lb = c[0]
1510        ub = c[1]
1511        repn = generate_standard_repn(lb.body)
1512        self.assertTrue(repn.is_linear())
1513        self.assertEqual(len(repn.linear_vars), 2)
1514        self.assertEqual(repn.constant, -c21lb)
1515        ct.check_linear_coef(self, repn, model.disjunct[1].indicator_var, c21lb)
1516        repn = generate_standard_repn(ub.body)
1517        self.assertTrue(repn.is_linear())
1518        self.assertEqual(len(repn.linear_vars), 2)
1519        self.assertEqual(repn.constant, -c21ub)
1520        ct.check_linear_coef(self, repn, model.disjunct[1].indicator_var, c21ub)
1521        c = bigm.get_transformed_constraints(model.disjunct[1].c[2])
1522        self.assertEqual(len(c), 2)
1523        lb = c[0]
1524        ub = c[1]
1525        repn = generate_standard_repn(lb.body)
1526        self.assertTrue(repn.is_linear())
1527        self.assertEqual(len(repn.linear_vars), 2)
1528        self.assertEqual(repn.constant, -c22lb)
1529        ct.check_linear_coef(self, repn, model.disjunct[1].indicator_var, c22lb)
1530        repn = generate_standard_repn(ub.body)
1531        self.assertTrue(repn.is_linear())
1532        self.assertEqual(len(repn.linear_vars), 2)
1533        self.assertEqual(repn.constant, -c22ub)
1534        ct.check_linear_coef(self, repn, model.disjunct[1].indicator_var, c22ub)
1535
1536    def test_arg_M_constraintdata(self):
1537        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1538        # specify a suffix on None so we can be happy we overrode it.
1539        m.BigM = Suffix(direction=Suffix.LOCAL)
1540        m.BigM[None] = 20
1541        # specify a suffix on a componentdata so we can be happy we overrode it
1542        m.BigM[m.disjunct[0].c[1]] = 19
1543
1544        # give an arg
1545        TransformationFactory('gdp.bigm').apply_to(
1546            m,
1547            bigM={None: 19, m.disjunct[0].c[1]: 17,
1548                  m.disjunct[0].c[2]: 18})
1549
1550        # check that m values are what we expect
1551        self.checkMs(m, -17, -18, -19, 19, -19, 19)
1552
1553    def test_arg_M_indexedConstraint(self):
1554        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1555        # specify a suffix on None so we can be happy we overrode it.
1556        m.BigM = Suffix(direction=Suffix.LOCAL)
1557        m.BigM[None] = 20
1558        # specify a suffix on a component so we can be happy we overrode it
1559        m.BigM[m.disjunct[0].c] = 19
1560
1561        # give an arg. Doing this one as a ComponentMap, just to make sure.
1562        TransformationFactory('gdp.bigm').apply_to(
1563            m,
1564            bigM=ComponentMap({None: 19, m.disjunct[0].c: 17}))
1565        self.checkMs(m, -17, -17, -19, 19, -19, 19)
1566
1567    def test_suffix_M_None_on_indexedConstraint(self):
1568        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1569        m.BigM = Suffix(direction=Suffix.LOCAL)
1570        m.BigM[None] = 20
1571        m.BigM[m.disjunct[0].c] = 19
1572        TransformationFactory('gdp.bigm').apply_to(m)
1573        self.checkMs(m, -19, -19, -20, 20, -20, 20)
1574
1575    def test_suffix_M_None_on_constraintdata(self):
1576        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1577        # specify a suffix on None so we can be happy we overrode it.
1578        m.BigM = Suffix(direction=Suffix.LOCAL)
1579        m.BigM[None] = 20
1580        m.BigM[m.disjunct[0].c[1]] = 19
1581
1582        TransformationFactory('gdp.bigm').apply_to(m)
1583
1584        # check that m values are what we expect
1585        self.checkMs(m, -19, -20, -20, 20, -20, 20)
1586
1587    def test_suffix_M_indexedConstraint_on_disjData(self):
1588        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1589        m.BigM = Suffix(direction=Suffix.LOCAL)
1590        m.BigM[None] = 20
1591        # specify a suffix on a disjunctData
1592        m.disjunct[0].BigM = Suffix(direction=Suffix.LOCAL)
1593        m.BigM[m.disjunct[0].c] = 19
1594
1595        TransformationFactory('gdp.bigm').apply_to(m)
1596        self.checkMs(m, -19, -19, -20, 20, -20, 20)
1597
1598    def test_suffix_M_constraintData_on_disjData(self):
1599        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1600        m.BigM = Suffix(direction=Suffix.LOCAL)
1601        m.BigM[None] = 20
1602        # specify a suffix on a disjunctData
1603        m.disjunct[0].BigM = Suffix(direction=Suffix.LOCAL)
1604        m.BigM[m.disjunct[0].c] = 19
1605        m.BigM[m.disjunct[0].c[1]] = 18
1606
1607        TransformationFactory('gdp.bigm').apply_to(m)
1608        self.checkMs(m, -18, -19, -20, 20, -20, 20)
1609
1610    def test_create_using(self):
1611        m = models.makeTwoTermDisj_IndexedConstraints_BoundedVars()
1612        self.diff_apply_to_and_create_using(m)
1613
1614
1615class DisjunctInMultipleDisjunctions(unittest.TestCase, CommonTests):
1616    def test_error_for_same_disjunct_in_multiple_disjunctions(self):
1617        ct.check_error_for_same_disjunct_in_multiple_disjunctions(self, 'bigm')
1618
1619
1620class TestTargets_SingleDisjunction(unittest.TestCase, CommonTests):
1621    def test_only_targets_inactive(self):
1622        ct.check_only_targets_inactive(self, 'bigm')
1623
1624    def test_only_targets_transformed(self):
1625        ct.check_only_targets_get_transformed(self, 'bigm')
1626
1627    def test_target_not_a_component_err(self):
1628        ct.check_target_not_a_component_error(self, 'bigm')
1629
1630    def test_targets_cannot_be_cuids(self):
1631        ct.check_targets_cannot_be_cuids(self, 'bigm')
1632
1633    # [ESJ 09/14/2019] See my rant in #1072, but I think this is why we cannot
1634    # actually support this!
1635    # def test_break_targets_with_cuids(self):
1636    #     m = models.makeTwoSimpleDisjunctions()
1637    #     b = Block() # so this guy has no parent, he's some mistake presumably
1638    #     # But we specify *him* has the target with cuid
1639    #     TransformationFactory('gdp.bigm').apply_to(m, targets=ComponentUID(b))
1640
1641    #     # No error, and we've transformed the whole model
1642    #     m.pprint()
1643
1644class TestTargets_IndexedDisjunction(unittest.TestCase, CommonTests):
1645    def test_indexedDisj_targets_inactive(self):
1646        ct.check_indexedDisj_targets_inactive(self, 'bigm')
1647
1648    def test_indexedDisj_only_targets_transformed(self):
1649        ct.check_indexedDisj_only_targets_transformed(self, 'bigm')
1650
1651    def test_warn_for_untransformed(self):
1652        ct.check_warn_for_untransformed(self, 'bigm')
1653
1654    def test_disjData_targets_inactive(self):
1655        ct.check_disjData_targets_inactive(self, 'bigm')
1656
1657    def test_disjData_only_targets_transformed(self):
1658        ct.check_disjData_only_targets_transformed(self, 'bigm')
1659
1660    def test_indexedBlock_targets_inactive(self):
1661        ct.check_indexedBlock_targets_inactive(self, 'bigm')
1662
1663    def test_indexedBlock_only_targets_transformed(self):
1664        ct.check_indexedBlock_only_targets_transformed(self, 'bigm')
1665
1666    def test_blockData_targets_inactive(self):
1667        ct.check_blockData_targets_inactive(self, 'bigm')
1668
1669    def test_blockData_only_targets_transformed(self):
1670        ct.check_blockData_only_targets_transformed(self, 'bigm')
1671
1672    def test_do_not_transform_deactivated_targets(self):
1673        ct.check_do_not_transform_deactivated_targets(self, 'bigm')
1674
1675    def test_create_using(self):
1676        m = models.makeDisjunctionsOnIndexedBlock()
1677        ct.diff_apply_to_and_create_using(self, m, 'gdp.bigm')
1678
1679
1680class DisjunctionInDisjunct(unittest.TestCase, CommonTests):
1681    def setUp(self):
1682        # set seed so we can test name collisions predictably
1683        random.seed(666)
1684
1685    def test_disjuncts_inactive(self):
1686        ct.check_disjuncts_inactive_nested(self, 'bigm')
1687
1688    def test_deactivated_disjunct_leaves_nested_disjuncts_active(self):
1689        ct.check_deactivated_disjunct_leaves_nested_disjunct_active(self, 'bigm')
1690
1691    def test_transformation_block_structure(self):
1692        m = models.makeNestedDisjunctions()
1693        TransformationFactory('gdp.bigm').apply_to(m)
1694
1695        transBlock = m._pyomo_gdp_bigm_reformulation
1696        self.assertIsInstance(transBlock, Block)
1697
1698        # check that we have the lbub set on the transformation block
1699        lbub = transBlock.component("lbub")
1700        self.assertIsInstance(lbub, Set)
1701        self.assertEqual(len(lbub), 2)
1702        self.assertEqual(lbub, ['lb', 'ub'])
1703
1704        # we have the XOR constraint
1705        self.assertIsInstance(transBlock.component("disjunction_xor"),
1706                              Constraint)
1707
1708        disjBlock = transBlock.relaxedDisjuncts
1709        self.assertIsInstance(disjBlock, Block)
1710        # All the outer and inner disjuncts should be on Block:
1711        self.assertEqual(len(disjBlock), 7)
1712        pairs = [
1713            (0, ["simpledisjunct._pyomo_gdp_bigm_reformulation.'simpledisjunct."
1714                 "innerdisjunction_xor'"]),
1715            (1, ["simpledisjunct.innerdisjunct0.c"]),
1716            (2, ["simpledisjunct.innerdisjunct1.c"]),
1717            (3, ["disjunct[0].c"]),
1718            (4, ["disjunct[1]._pyomo_gdp_bigm_reformulation.'disjunct[1]."
1719                 "innerdisjunction_xor'",
1720                 "disjunct[1].c"]),
1721            (5, ["disjunct[1].innerdisjunct[0].c"]),
1722            (6, ["disjunct[1].innerdisjunct[1].c"]),
1723        ]
1724        # This test will also rely on the disjunctions being relaxed in the same
1725        # order every time (and moved up to the new transformation block in the
1726        # same order)
1727        for i, j in pairs:
1728            for nm in j:
1729                self.assertIsInstance(
1730                    disjBlock[i].component(nm),
1731                    Constraint)
1732
1733    def test_transformation_block_on_disjunct_empty(self):
1734        m = models.makeNestedDisjunctions()
1735        TransformationFactory('gdp.bigm').apply_to(m)
1736        self.assertEqual(len(m.disjunct[1]._pyomo_gdp_bigm_reformulation.\
1737                             component("relaxedDisjuncts")), 0)
1738        self.assertEqual(len(m.simpledisjunct._pyomo_gdp_bigm_reformulation.\
1739                             component("relaxedDisjuncts")), 0)
1740
1741    def test_mappings_between_disjunctions_and_xors(self):
1742        # Note this test actually checks that the inner disjunction maps to its
1743        # original xor (which will be transformed again by the outer
1744        # disjunction.)
1745        ct.check_mappings_between_disjunctions_and_xors(self, 'bigm')
1746
1747    def test_disjunct_mappings(self):
1748        m = models.makeNestedDisjunctions()
1749        bigm = TransformationFactory('gdp.bigm')
1750        bigm.apply_to(m)
1751
1752        disjunctBlocks = m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts
1753
1754        # I want to check that I correctly updated the pointers to the
1755        # transformation blocks on the inner Disjuncts.
1756        self.assertIs(m.disjunct[1].innerdisjunct[0].transformation_block(),
1757                      disjunctBlocks[5])
1758        self.assertIs(disjunctBlocks[5]._srcDisjunct(),
1759                      m.disjunct[1].innerdisjunct[0])
1760
1761        self.assertIs(m.disjunct[1].innerdisjunct[1].transformation_block(),
1762                      disjunctBlocks[6])
1763        self.assertIs(disjunctBlocks[6]._srcDisjunct(),
1764                      m.disjunct[1].innerdisjunct[1])
1765
1766        self.assertIs(m.simpledisjunct.innerdisjunct0.transformation_block(),
1767                      disjunctBlocks[1])
1768        self.assertIs(disjunctBlocks[1]._srcDisjunct(),
1769                      m.simpledisjunct.innerdisjunct0)
1770
1771        self.assertIs(m.simpledisjunct.innerdisjunct1.transformation_block(),
1772                      disjunctBlocks[2])
1773        self.assertIs(disjunctBlocks[2]._srcDisjunct(),
1774                      m.simpledisjunct.innerdisjunct1)
1775
1776    def test_m_value_mappings(self):
1777        m = models.makeNestedDisjunctions()
1778        bigm = TransformationFactory('gdp.bigm')
1779        m.simpledisjunct.BigM = Suffix(direction=Suffix.LOCAL)
1780        m.simpledisjunct.BigM[None] = 58
1781        m.simpledisjunct.BigM[m.simpledisjunct.innerdisjunct0.c] = 42
1782        bigms = {m.disjunct[1].innerdisjunct[0]: 89}
1783        bigm.apply_to(m, bigM=bigms)
1784
1785        ((l_val, l_src, l_key),
1786         (u_val, u_src, u_key)) = bigm.get_M_value_src(
1787             m.disjunct[1].innerdisjunct[0].c)
1788        self.assertIs(l_src, bigms)
1789        self.assertIs(u_src, bigms)
1790        self.assertIs(l_key, m.disjunct[1].innerdisjunct[0])
1791        self.assertIs(u_key, m.disjunct[1].innerdisjunct[0])
1792        self.assertEqual(l_val, -89)
1793        self.assertEqual(u_val, 89)
1794
1795        ((l_val, l_src, l_key),
1796         (u_val, u_src, u_key)) = bigm.get_M_value_src(
1797             m.disjunct[1].innerdisjunct[1].c)
1798        self.assertIsNone(l_src)
1799        self.assertIsNone(u_src)
1800        self.assertIsNone(l_key)
1801        self.assertIsNone(u_key)
1802        self.assertEqual(l_val, -5)
1803        self.assertIsNone(u_val)
1804
1805        ((l_val, l_src, l_key),
1806         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.disjunct[0].c)
1807        self.assertIsNone(l_src)
1808        self.assertIsNone(u_src)
1809        self.assertIsNone(l_key)
1810        self.assertIsNone(u_key)
1811        self.assertEqual(l_val, -11)
1812        self.assertEqual(u_val, 7)
1813
1814        ((l_val, l_src, l_key),
1815         (u_val, u_src, u_key)) = bigm.get_M_value_src(m.disjunct[1].c)
1816        self.assertIsNone(l_src)
1817        self.assertIsNone(u_src)
1818        self.assertIsNone(l_key)
1819        self.assertIsNone(u_key)
1820        self.assertIsNone(l_val)
1821        self.assertEqual(u_val, 21)
1822
1823        ((l_val, l_src, l_key),
1824         (u_val, u_src, u_key)) = bigm.get_M_value_src(
1825             m.simpledisjunct.innerdisjunct0.c)
1826        self.assertIsNone(l_src)
1827        self.assertIs(u_src, m.simpledisjunct.BigM)
1828        self.assertIsNone(l_key)
1829        self.assertIs(u_key, m.simpledisjunct.innerdisjunct0.c)
1830        self.assertIsNone(l_val)
1831        self.assertEqual(u_val, 42)
1832
1833        ((l_val, l_src, l_key),
1834         (u_val, u_src, u_key)) = bigm.get_M_value_src(
1835             m.simpledisjunct.innerdisjunct1.c)
1836        self.assertIs(l_src, m.simpledisjunct.BigM)
1837        self.assertIsNone(u_src)
1838        self.assertIsNone(l_key)
1839        self.assertIsNone(u_key)
1840        self.assertEqual(l_val, -58)
1841        self.assertIsNone(u_val)
1842
1843    # many of the transformed constraints look like this, so can call this
1844    # function to test them.
1845    def check_bigM_constraint(self, cons, variable, M, indicator_var):
1846        repn = generate_standard_repn(cons.body)
1847        self.assertTrue(repn.is_linear())
1848        self.assertEqual(repn.constant, -M)
1849        self.assertEqual(len(repn.linear_vars), 2)
1850        ct.check_linear_coef(self, repn, variable, 1)
1851        ct.check_linear_coef(self, repn, indicator_var, M)
1852
1853    def check_xor_relaxation(self, cons, indvar1, indvar2, indvar3, lb):
1854        repn = generate_standard_repn(cons.body)
1855        self.assertTrue(repn.is_linear())
1856        self.assertEqual(len(repn.linear_vars), 3)
1857        ct.check_linear_coef(self, repn, indvar1, 1)
1858        ct.check_linear_coef(self, repn, indvar2, 1)
1859        if not lb:
1860            self.assertEqual(cons.upper, 1)
1861            self.assertIsNone(cons.lower)
1862            self.assertEqual(repn.constant, -1)
1863            ct.check_linear_coef(self, repn, indvar3, 1)
1864        else:
1865            self.assertEqual(cons.lower, 1)
1866            self.assertIsNone(cons.upper)
1867            self.assertEqual(repn.constant, 1)
1868            ct.check_linear_coef(self, repn, indvar3, -1)
1869
1870    def test_transformed_constraints(self):
1871        # We'll check all the transformed constraints to make sure
1872        # that nothing was transformed twice. The real key is that the
1873        # xor constraints created by the inner disjunctions get
1874        # transformed by the outer ones.
1875        m = models.makeNestedDisjunctions()
1876        TransformationFactory('gdp.bigm').apply_to(m)
1877        cons1 = m.disjunct[1].innerdisjunct[0].transformation_block().component(
1878            m.disjunct[1].innerdisjunct[0].c.name)
1879        cons1lb = cons1['lb']
1880        self.assertEqual(cons1lb.lower, 0)
1881        self.assertIsNone(cons1lb.upper)
1882        self.assertIs(cons1lb.body, m.z)
1883        cons1ub = cons1['ub']
1884        self.assertIsNone(cons1ub.lower)
1885        self.assertEqual(cons1ub.upper, 0)
1886        self.check_bigM_constraint(cons1ub, m.z, 10,
1887                                 m.disjunct[1].innerdisjunct[0].indicator_var)
1888
1889        cons2 = m.disjunct[1].innerdisjunct[1].transformation_block().component(
1890            m.disjunct[1].innerdisjunct[1].c.name)['lb']
1891        self.assertEqual(cons2.lower, 5)
1892        self.assertIsNone(cons2.upper)
1893        self.check_bigM_constraint(cons2, m.z, -5,
1894                                   m.disjunct[1].innerdisjunct[1].indicator_var)
1895
1896        cons3 = m.simpledisjunct.innerdisjunct0.transformation_block().component(
1897            m.simpledisjunct.innerdisjunct0.c.name)['ub']
1898        self.assertEqual(cons3.upper, 2)
1899        self.assertIsNone(cons3.lower)
1900        self.check_bigM_constraint(
1901            cons3, m.x, 7,
1902            m.simpledisjunct.innerdisjunct0.indicator_var)
1903
1904        cons4 = m.simpledisjunct.innerdisjunct1.transformation_block().component(
1905            m.simpledisjunct.innerdisjunct1.c.name)['lb']
1906        self.assertEqual(cons4.lower, 4)
1907        self.assertIsNone(cons4.upper)
1908        self.check_bigM_constraint(
1909            cons4, m.x, -13,
1910            m.simpledisjunct.innerdisjunct1.indicator_var)
1911
1912        # Here we check that the xor constraint from
1913        # simpledisjunct.innerdisjunction is transformed.
1914        cons5 = m.simpledisjunct.transformation_block().component(
1915            "simpledisjunct._pyomo_gdp_bigm_reformulation.'simpledisjunct."
1916            "innerdisjunction_xor'")
1917        cons5lb = cons5['lb']
1918        self.check_xor_relaxation(
1919            cons5lb,
1920            m.simpledisjunct.innerdisjunct0.indicator_var,
1921            m.simpledisjunct.innerdisjunct1.indicator_var,
1922            m.simpledisjunct.indicator_var,
1923            lb=True)
1924        cons5ub = cons5['ub']
1925        self.check_xor_relaxation(
1926            cons5ub,
1927            m.simpledisjunct.innerdisjunct0.indicator_var,
1928            m.simpledisjunct.innerdisjunct1.indicator_var,
1929            m.simpledisjunct.indicator_var,
1930            lb=False)
1931
1932        cons6 = m.disjunct[0].transformation_block().component("disjunct[0].c")
1933        cons6lb = cons6['lb']
1934        self.assertIsNone(cons6lb.upper)
1935        self.assertEqual(cons6lb.lower, 2)
1936        self.check_bigM_constraint(cons6lb, m.x, -11,
1937                                   m.disjunct[0].indicator_var)
1938        cons6ub = cons6['ub']
1939        self.assertIsNone(cons6ub.lower)
1940        self.assertEqual(cons6ub.upper, 2)
1941        self.check_bigM_constraint(cons6ub, m.x, 7, m.disjunct[0].indicator_var)
1942
1943        # now we check that the xor constraint from
1944        # disjunct[1].innerdisjunction gets transformed alongside the
1945        # other constraint in disjunct[1].
1946        cons7 = m.disjunct[1].transformation_block().component(
1947            "disjunct[1]._pyomo_gdp_bigm_reformulation.'disjunct[1]."
1948            "innerdisjunction_xor'")
1949        cons7lb = cons7[0,'lb']
1950        self.check_xor_relaxation(
1951            cons7lb,
1952            m.disjunct[1].innerdisjunct[0].indicator_var,
1953            m.disjunct[1].innerdisjunct[1].indicator_var,
1954            m.disjunct[1].indicator_var,
1955            lb=True)
1956        cons7ub = cons7[0,'ub']
1957        self.check_xor_relaxation(
1958            cons7ub,
1959            m.disjunct[1].innerdisjunct[0].indicator_var,
1960            m.disjunct[1].innerdisjunct[1].indicator_var,
1961            m.disjunct[1].indicator_var,
1962            lb=False)
1963
1964        cons8 = m.disjunct[1].transformation_block().component(
1965            "disjunct[1].c")['ub']
1966        self.assertIsNone(cons8.lower)
1967        self.assertEqual(cons8.upper, 2)
1968        self.check_bigM_constraint(cons8, m.a, 21, m.disjunct[1].indicator_var)
1969
1970    def test_unique_reference_to_nested_indicator_var(self):
1971        ct.check_unique_reference_to_nested_indicator_var(self, 'bigm')
1972
1973    def test_disjunct_targets_inactive(self):
1974        ct.check_disjunct_targets_inactive(self, 'bigm')
1975
1976    def test_disjunct_only_targets_transformed(self):
1977        ct.check_disjunct_only_targets_transformed(self, 'bigm')
1978
1979    def test_disjunctData_targets_inactive(self):
1980        ct.check_disjunctData_targets_inactive(self, 'bigm')
1981
1982    def test_disjunctData_only_targets_transformed(self):
1983        ct.check_disjunctData_only_targets_transformed(self, 'bigm')
1984
1985    def test_cannot_call_transformation_on_disjunction(self):
1986        ct.check_cannot_call_transformation_on_disjunction(self, 'bigm')
1987
1988    def test_disjunction_target_err(self):
1989        ct.check_disjunction_target_err(self, 'bigm')
1990
1991    def test_nested_disjunction_target(self):
1992        ct.check_nested_disjunction_target(self, 'bigm')
1993
1994    def test_target_appears_twice(self):
1995        ct.check_target_appears_twice(self, 'bigm')
1996
1997    def test_create_using(self):
1998        m = models.makeNestedDisjunctions()
1999        self.diff_apply_to_and_create_using(m)
2000
2001    def test_indexed_nested_disjunction(self):
2002        # When we have a nested disjunction inside of a disjunct, we need to
2003        # make sure that we don't delete the relaxedDisjuncts container because
2004        # we will end up moving things out of it in two different steps. If that
2005        # were to happen, this would throw an error when it can't find the block
2006        # the second time.
2007        m = ConcreteModel()
2008        m.d1 = Disjunct()
2009        m.d1.indexedDisjunct1 = Disjunct([0,1])
2010        m.d1.indexedDisjunct2 = Disjunct([0,1])
2011        @m.d1.Disjunction([0,1])
2012        def innerIndexed(d, i):
2013            return [d.indexedDisjunct1[i], d.indexedDisjunct2[i]]
2014        m.d2 = Disjunct()
2015        m.outer = Disjunction(expr=[m.d1, m.d2])
2016
2017        TransformationFactory('gdp.bigm').apply_to(m)
2018
2019        # we check that they all ended up on the same Block in the end (I don't
2020        # really care in what order for this test)
2021        disjuncts = [m.d1, m.d2, m.d1.indexedDisjunct1[0],
2022                     m.d1.indexedDisjunct1[1], m.d1.indexedDisjunct2[0],
2023                     m.d1.indexedDisjunct2[1]]
2024        for disjunct in disjuncts:
2025            self.assertIs(disjunct.transformation_block().parent_component(),
2026                          m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts)
2027
2028        # and we check that nothing remains on original transformation block
2029        self.assertEqual(len(m.d1._pyomo_gdp_bigm_reformulation.relaxedDisjuncts),
2030                         0)
2031
2032class IndexedDisjunction(unittest.TestCase):
2033    # this tests that if the targets are a subset of the
2034    # _DisjunctDatas in an IndexedDisjunction that the xor constraint
2035    # created on the parent block will still be indexed as expected.
2036    def test_xor_constraint(self):
2037        ct.check_indexed_xor_constraints_with_targets(self, 'bigm')
2038
2039    def test_partial_deactivate_indexed_disjunction(self):
2040        ct.check_partial_deactivate_indexed_disjunction(self, 'bigm')
2041
2042
2043class BlocksOnDisjuncts(unittest.TestCase):
2044    # ESJ: All of these tests are specific to bigm because they check how much
2045    # stuff is on the transformation blocks.
2046    def setUp(self):
2047        # set seed so we can test name collisions predictably
2048        random.seed(666)
2049
2050    def test_transformed_constraint_nameConflicts(self):
2051        m = models.makeTwoTermDisj_BlockOnDisj()
2052        TransformationFactory('gdp.bigm').apply_to(m)
2053
2054        transBlock = m._pyomo_gdp_bigm_reformulation
2055        disjBlock = transBlock.relaxedDisjuncts
2056
2057        self.assertIsInstance(disjBlock, Block)
2058        self.assertEqual(len(disjBlock), 2)
2059        self.assertEqual(len(disjBlock[0].component_map()), 2)
2060        self.assertEqual(len(disjBlock[1].component_map()), 5)
2061        self.assertIsInstance(disjBlock[0].component("evil[0].c"), Constraint)
2062        self.assertIsInstance(disjBlock[1].component("evil[1].'b.c'"),
2063                              Constraint)
2064        self.assertIsInstance(disjBlock[1].component("evil[1].bb[1].c"),
2065                              Constraint)
2066        self.assertIsInstance(
2067            disjBlock[1].component("evil[1].'b.c'"), Constraint)
2068        self.assertIsInstance(
2069            disjBlock[1].component("evil[1].b.anotherblock.c"),
2070                                                     Constraint)
2071        self.assertIsInstance(disjBlock[0].component("localVarReferences"),
2072                              Block)
2073        self.assertIsInstance(disjBlock[1].component("localVarReferences"),
2074                              Block)
2075
2076    def test_do_not_transform_deactivated_constraint(self):
2077        m = models.makeTwoTermDisj_BlockOnDisj()
2078        m.evil[1].b.anotherblock.c.deactivate()
2079
2080        TransformationFactory('gdp.bigm').apply_to(m)
2081
2082        transBlock = m._pyomo_gdp_bigm_reformulation
2083        disjBlock = transBlock.relaxedDisjuncts
2084
2085        self.assertIsInstance(disjBlock, Block)
2086        self.assertEqual(len(disjBlock), 2)
2087        self.assertEqual(len(disjBlock[0].component_map()), 2)
2088        self.assertEqual(len(disjBlock[1].component_map()), 4)
2089        self.assertIsInstance(disjBlock[0].component("evil[0].c"), Constraint)
2090        self.assertIsInstance(disjBlock[1].component("evil[1].'b.c'"),
2091                              Constraint)
2092        self.assertIsInstance(disjBlock[1].component("evil[1].bb[1].c"),
2093                              Constraint)
2094        self.assertIsInstance(
2095            disjBlock[1].component("evil[1].'b.c'"), Constraint)
2096        self.assertIsInstance(disjBlock[0].component("localVarReferences"),
2097                              Block)
2098        self.assertIsInstance(disjBlock[1].component("localVarReferences"),
2099                              Block)
2100
2101    def test_do_not_transform_deactivated_block(self):
2102        m = models.makeTwoTermDisj_BlockOnDisj()
2103        m.evil[1].b.anotherblock.deactivate()
2104
2105        TransformationFactory('gdp.bigm').apply_to(m)
2106
2107        transBlock = m._pyomo_gdp_bigm_reformulation
2108        disjBlock = transBlock.relaxedDisjuncts
2109
2110        self.assertIsInstance(disjBlock, Block)
2111        self.assertEqual(len(disjBlock), 2)
2112        self.assertEqual(len(disjBlock[0].component_map()), 2)
2113        self.assertEqual(len(disjBlock[1].component_map()), 4)
2114        self.assertIsInstance(disjBlock[0].component("evil[0].c"), Constraint)
2115        self.assertIsInstance(disjBlock[1].component("evil[1].'b.c'"),
2116                              Constraint)
2117        self.assertIsInstance(disjBlock[1].component("evil[1].bb[1].c"),
2118                              Constraint)
2119        self.assertIsInstance(
2120            disjBlock[1].component("evil[1].'b.c'"), Constraint)
2121        self.assertIsInstance(disjBlock[0].component("localVarReferences"),
2122                              Block)
2123        self.assertIsInstance(disjBlock[1].component("localVarReferences"),
2124                              Block)
2125
2126    def test_pick_up_bigm_suffix_on_block(self):
2127        m = models.makeTwoTermDisj_BlockOnDisj()
2128        m.evil[1].b.BigM = Suffix(direction=Suffix.LOCAL)
2129        m.evil[1].b.BigM[m.evil[1].b.c] = 2000
2130        bigm = TransformationFactory('gdp.bigm')
2131        bigm.apply_to(m)
2132
2133        # check that the m value got used
2134        cons_list = bigm.get_transformed_constraints(m.evil[1].b.c)
2135        ub = cons_list[1]
2136        self.assertEqual(ub.index(), 'ub')
2137        self.assertEqual(ub.upper, 0)
2138        self.assertIsNone(ub.lower)
2139        repn = generate_standard_repn(ub.body)
2140        self.assertTrue(repn.is_linear())
2141        self.assertEqual(repn.constant, -2000)
2142        self.assertEqual(len(repn.linear_vars), 2)
2143        self.assertIs(repn.linear_vars[0], m.x)
2144        self.assertEqual(repn.linear_coefs[0], 1)
2145        self.assertIs(repn.linear_vars[1], m.evil[1].binary_indicator_var)
2146        self.assertEqual(repn.linear_coefs[1], 2000)
2147
2148    def test_use_correct_none_suffix(self):
2149        m = ConcreteModel()
2150        m.x = Var(bounds=(-100, 111))
2151        m.b = Block()
2152        m.b.d = Disjunct()
2153        m.b.d.foo = Block()
2154
2155        m.b.d.c = Constraint(expr=m.x>=9)
2156
2157        m.b.BigM = Suffix()
2158        m.b.BigM[None] = 10
2159        m.b.d.foo.BigM = Suffix()
2160        m.b.d.foo.BigM[None] = 1
2161
2162        m.d = Disjunct()
2163        m.disj = Disjunction(expr=[m.d, m.b.d])
2164
2165        bigm = TransformationFactory('gdp.bigm')
2166        bigm.apply_to(m)
2167
2168        # we should have picked up 10 for m.b.d.c
2169        cons_list = bigm.get_transformed_constraints(m.b.d.c)
2170        lb = cons_list[0]
2171        self.assertEqual(lb.index(), 'lb')
2172        self.assertEqual(lb.lower, 9)
2173        self.assertIsNone(lb.upper)
2174        repn = generate_standard_repn(lb.body)
2175        self.assertTrue(repn.is_linear())
2176        self.assertEqual(repn.constant, 10)
2177        self.assertEqual(len(repn.linear_vars), 2)
2178        self.assertIs(repn.linear_vars[0], m.x)
2179        self.assertEqual(repn.linear_coefs[0], 1)
2180        self.assertIs(repn.linear_vars[1], m.b.d.binary_indicator_var)
2181        self.assertEqual(repn.linear_coefs[1], -10)
2182
2183class UntransformableObjectsOnDisjunct(unittest.TestCase):
2184    def test_RangeSet(self):
2185        ct.check_RangeSet(self, 'bigm')
2186
2187    def test_Expression(self):
2188        ct.check_Expression(self, 'bigm')
2189
2190class TransformABlock(unittest.TestCase):
2191    def test_transformation_simple_block(self):
2192        ct.check_transformation_simple_block(self, 'bigm')
2193
2194    def test_transform_block_data(self):
2195        ct.check_transform_block_data(self, 'bigm')
2196
2197    def test_simple_block_target(self):
2198        ct.check_simple_block_target(self, 'bigm')
2199
2200    def test_block_data_target(self):
2201        ct.check_block_data_target(self, 'bigm')
2202
2203    def test_indexed_block_target(self):
2204        ct.check_indexed_block_target(self, 'bigm')
2205
2206class IndexedDisjunctions(unittest.TestCase):
2207    def setUp(self):
2208        # set seed so we can test name collisions predictably
2209        random.seed(666)
2210
2211    def test_disjunction_data_target(self):
2212        ct.check_disjunction_data_target(self, 'bigm')
2213
2214    def test_disjunction_data_target_any_index(self):
2215       ct.check_disjunction_data_target_any_index(self, 'bigm')
2216
2217    # ESJ: This and the following tests are *very* similar to those in hull,
2218    # but I actually bothered to check the additional transformed objects in
2219    # hull (disaggregated variables, bounds constraints...), so they are
2220    # reproduced independently there.
2221    def check_trans_block_disjunctions_of_disjunct_datas(self, m):
2222        transBlock1 = m.component("_pyomo_gdp_bigm_reformulation")
2223        self.assertIsInstance(transBlock1, Block)
2224        self.assertIsInstance(transBlock1.component("relaxedDisjuncts"), Block)
2225        # We end up with a transformation block for every ScalarDisjunction or
2226        # IndexedDisjunction.
2227        self.assertEqual(len(transBlock1.relaxedDisjuncts), 2)
2228        self.assertIsInstance(transBlock1.relaxedDisjuncts[0].component(
2229            "firstTerm[1].cons"), Constraint)
2230        self.assertEqual(len(transBlock1.relaxedDisjuncts[0].component(
2231            "firstTerm[1].cons")), 2)
2232        self.assertIsInstance(transBlock1.relaxedDisjuncts[1].component(
2233            "secondTerm[1].cons"), Constraint)
2234        self.assertEqual(len(transBlock1.relaxedDisjuncts[1].component(
2235            "secondTerm[1].cons")), 1)
2236        transBlock2 = m.component("_pyomo_gdp_bigm_reformulation_4")
2237        self.assertIsInstance(transBlock2, Block)
2238        self.assertIsInstance(transBlock2.component("relaxedDisjuncts"), Block)
2239        self.assertEqual(len(transBlock2.relaxedDisjuncts), 2)
2240        self.assertIsInstance(transBlock2.relaxedDisjuncts[0].component(
2241            "firstTerm[2].cons"), Constraint)
2242        self.assertEqual(len(transBlock2.relaxedDisjuncts[0].component(
2243            "firstTerm[2].cons")), 2)
2244        self.assertIsInstance(transBlock2.relaxedDisjuncts[1].component(
2245            "secondTerm[2].cons"), Constraint)
2246        self.assertEqual(len(transBlock2.relaxedDisjuncts[1].component(
2247            "secondTerm[2].cons")), 1)
2248
2249    def test_simple_disjunction_of_disjunct_datas(self):
2250        ct.check_simple_disjunction_of_disjunct_datas(self, 'bigm')
2251
2252    def test_any_indexed_disjunction_of_disjunct_datas(self):
2253        m = models.makeAnyIndexedDisjunctionOfDisjunctDatas()
2254        TransformationFactory('gdp.bigm').apply_to(m)
2255
2256        transBlock = m.component("_pyomo_gdp_bigm_reformulation")
2257        self.assertIsInstance(transBlock, Block)
2258        self.assertIsInstance(transBlock.component("relaxedDisjuncts"), Block)
2259        self.assertEqual(len(transBlock.relaxedDisjuncts), 4)
2260        self.assertIsInstance(transBlock.relaxedDisjuncts[0].component(
2261            "firstTerm[1].cons"), Constraint)
2262        self.assertEqual(len(transBlock.relaxedDisjuncts[0].component(
2263            "firstTerm[1].cons")), 2)
2264        self.assertIsInstance(transBlock.relaxedDisjuncts[1].component(
2265            "secondTerm[1].cons"), Constraint)
2266        self.assertEqual(len(transBlock.relaxedDisjuncts[1].component(
2267            "secondTerm[1].cons")), 1)
2268        self.assertIsInstance(transBlock.relaxedDisjuncts[2].component(
2269            "firstTerm[2].cons"), Constraint)
2270        self.assertEqual(len(transBlock.relaxedDisjuncts[2].component(
2271            "firstTerm[2].cons")), 2)
2272        self.assertIsInstance(transBlock.relaxedDisjuncts[3].component(
2273            "secondTerm[2].cons"), Constraint)
2274        self.assertEqual(len(transBlock.relaxedDisjuncts[3].component(
2275            "secondTerm[2].cons")), 1)
2276        self.assertIsInstance( transBlock.component("disjunction_xor"),
2277                               Constraint)
2278        self.assertEqual( len(transBlock.component("disjunction_xor")), 2)
2279
2280    def check_first_iteration(self, model):
2281        transBlock = model.component("_pyomo_gdp_bigm_reformulation")
2282        self.assertIsInstance(transBlock, Block)
2283        self.assertIsInstance(
2284            transBlock.component("disjunctionList_xor"),
2285            Constraint)
2286        self.assertEqual(
2287            len(transBlock.disjunctionList_xor), 1)
2288        self.assertFalse(model.disjunctionList[0].active)
2289
2290    def check_second_iteration(self, model):
2291        transBlock = model.component("_pyomo_gdp_bigm_reformulation")
2292        self.assertIsInstance(transBlock, Block)
2293        self.assertIsInstance(transBlock.component("relaxedDisjuncts"), Block)
2294        self.assertEqual(len(transBlock.relaxedDisjuncts), 4)
2295        if model.component('firstTerm') is None:
2296            firstTerm = "'firstTerm[1]'.cons"
2297            secondTerm = "'secondTerm[1]'.cons"
2298        else:
2299            firstTerm = "firstTerm[1].cons"
2300            secondTerm = "secondTerm[1].cons"
2301        self.assertIsInstance(transBlock.relaxedDisjuncts[2].component(
2302            firstTerm), Constraint)
2303        self.assertEqual(len(transBlock.relaxedDisjuncts[2].component(
2304            firstTerm)), 2)
2305        self.assertIsInstance(transBlock.relaxedDisjuncts[3].component(
2306            secondTerm), Constraint)
2307        self.assertEqual(len(transBlock.relaxedDisjuncts[3].component(
2308            secondTerm)), 1)
2309        self.assertEqual(
2310            len(model._pyomo_gdp_bigm_reformulation.disjunctionList_xor), 2)
2311        self.assertFalse(model.disjunctionList[1].active)
2312        self.assertFalse(model.disjunctionList[0].active)
2313
2314    def test_disjunction_and_disjuncts_indexed_by_any(self):
2315        ct.check_disjunction_and_disjuncts_indexed_by_any(self, 'bigm')
2316
2317    def test_iteratively_adding_disjunctions_transform_container(self):
2318        ct.check_iteratively_adding_disjunctions_transform_container(self,
2319                                                                     'bigm')
2320
2321    def test_iteratively_adding_disjunctions_transform_model(self):
2322        ct.check_iteratively_adding_disjunctions_transform_model(self, 'bigm')
2323
2324    def test_iteratively_adding_to_indexed_disjunction_on_block(self):
2325        ct.check_iteratively_adding_to_indexed_disjunction_on_block(self,
2326                                                                    'bigm')
2327
2328class TestErrors(unittest.TestCase):
2329    def test_transform_empty_disjunction(self):
2330        ct.check_transform_empty_disjunction(self, 'bigm')
2331
2332    def test_deactivated_disjunct_nonzero_indicator_var(self):
2333        ct.check_deactivated_disjunct_nonzero_indicator_var(self,
2334                                                            'bigm')
2335
2336    def test_deactivated_disjunct_unfixed_indicator_var(self):
2337        ct.check_deactivated_disjunct_unfixed_indicator_var(self, 'bigm')
2338
2339    def test_infeasible_xor_because_all_disjuncts_deactivated(self):
2340        m = ct.setup_infeasible_xor_because_all_disjuncts_deactivated(self,
2341                                                                      'bigm')
2342
2343        transBlock = m.component("_pyomo_gdp_bigm_reformulation")
2344        self.assertIsInstance(transBlock, Block)
2345        self.assertEqual(len(transBlock.relaxedDisjuncts), 2)
2346        self.assertIsInstance(transBlock.component("disjunction_xor"),
2347                              Constraint)
2348        disjunct1 = transBlock.relaxedDisjuncts[0]
2349        # longest constraint name EVER...
2350        relaxed_xor = disjunct1.component(
2351            "disjunction_disjuncts[0]._pyomo_gdp_bigm_reformulation."
2352            "'disjunction_disjuncts[0].nestedDisjunction_xor'")
2353        self.assertIsInstance(relaxed_xor, Constraint)
2354        repn = generate_standard_repn(relaxed_xor['lb'].body)
2355        self.assertEqual(relaxed_xor['lb'].lower, 1)
2356        self.assertIsNone(relaxed_xor['lb'].upper)
2357        # the other variables got eaten in the constant because they are fixed.
2358        self.assertEqual(len(repn.linear_vars), 1)
2359        ct.check_linear_coef( self, repn,
2360                              m.disjunction.disjuncts[0].indicator_var, -1)
2361        self.assertEqual(repn.constant, 1)
2362        repn = generate_standard_repn(relaxed_xor['ub'].body)
2363        self.assertIsNone(relaxed_xor['ub'].lower)
2364        self.assertEqual(value(relaxed_xor['ub'].upper), 1)
2365        self.assertEqual(len(repn.linear_vars), 1)
2366        ct.check_linear_coef( self, repn,
2367                              m.disjunction.disjuncts[0].indicator_var, 1)
2368
2369        # and last check that the other constraints here look fine
2370        x0 = disjunct1.component("disjunction_disjuncts[0].constraint")
2371        self.assertIsInstance(x0, Constraint)
2372        lb = x0[(1, 'lb')]
2373        self.assertEqual(value(lb.lower), 0)
2374        self.assertIsNone(lb.upper)
2375        repn = generate_standard_repn(lb.body)
2376        self.assertEqual(repn.constant, 0)
2377        self.assertEqual(len(repn.linear_vars), 1)
2378        ct.check_linear_coef(self, repn, m.x, 1)
2379
2380        ub = x0[(1, 'ub')]
2381        self.assertIsNone(ub.lower)
2382        self.assertEqual(value(ub.upper), 0)
2383        repn = generate_standard_repn(ub.body)
2384        self.assertEqual(repn.constant, -8)
2385        self.assertEqual(len(repn.linear_vars), 2)
2386        ct.check_linear_coef(self, repn, m.x, 1)
2387        ct.check_linear_coef(self, repn,
2388                             m.disjunction_disjuncts[0].indicator_var, 8)
2389
2390    def test_retrieving_nondisjunctive_components(self):
2391        ct.check_retrieving_nondisjunctive_components(self, 'bigm')
2392
2393    def test_ask_for_transformed_constraint_from_untransformed_disjunct(self):
2394        ct.check_ask_for_transformed_constraint_from_untransformed_disjunct(
2395            self, 'bigm')
2396
2397    def test_silly_target(self):
2398        ct.check_silly_target(self, 'bigm')
2399
2400    def test_untransformed_arcs(self):
2401        ct.check_untransformed_network_raises_GDPError(self, 'bigm')
2402
2403class EstimatingMwithFixedVars(unittest.TestCase):
2404    def test_tighter_Ms_when_vars_fixed_forever(self):
2405        m = ConcreteModel()
2406        m.x = Var(bounds=(0, 10))
2407        m.y = Var(bounds=(0, 70))
2408        m.d = Disjunct()
2409        m.d.c = Constraint(expr=m.x + m.y <= 13)
2410        m.d2 = Disjunct()
2411        m.d2.c = Constraint(expr=m.x >= 7)
2412        m.disj = Disjunction(expr=[m.d, m.d2])
2413        m.y.fix(10)
2414        bigm = TransformationFactory('gdp.bigm')
2415        promise = bigm.create_using(m, assume_fixed_vars_permanent=True)
2416        bigm.apply_to(m, assume_fixed_vars_permanent=False)
2417
2418        # check the M values in both cases
2419        # first where y might be unfixed:
2420        xformed = bigm.get_transformed_constraints(m.d.c)
2421        self.assertEqual(len(xformed), 1)
2422        cons = xformed[0]
2423        self.assertEqual(cons.upper, 13)
2424        self.assertIsNone(cons.lower)
2425        repn = generate_standard_repn(cons.body)
2426        self.assertEqual(repn.constant, -57)
2427        self.assertEqual(len(repn.linear_vars), 2)
2428        ct.check_linear_coef(self, repn, m.x, 1)
2429        ct.check_linear_coef(self, repn, m.d.indicator_var, 67)
2430
2431        # then where it won't
2432        xformed = bigm.get_transformed_constraints(promise.d.c)
2433        self.assertEqual(len(xformed), 1)
2434        cons = xformed[0]
2435        self.assertEqual(cons.upper, 13)
2436        self.assertIsNone(cons.lower)
2437        repn = generate_standard_repn(cons.body)
2438        self.assertEqual(repn.constant, 3)
2439        self.assertEqual(len(repn.linear_vars), 2)
2440        ct.check_linear_coef(self, repn, promise.x, 1)
2441        ct.check_linear_coef(self, repn, promise.d.indicator_var, 7)
2442
2443class NetworkDisjuncts(unittest.TestCase, CommonTests):
2444
2445    @unittest.skipIf(not ct.linear_solvers, "No linear solver available")
2446    def test_solution_maximize(self):
2447        ct.check_network_disjucts(self, minimize=False, transformation='bigm')
2448
2449    @unittest.skipIf(not ct.linear_solvers, "No linear solver available")
2450    def test_solution_minimize(self):
2451        ct.check_network_disjucts(self, minimize=True, transformation='bigm')
2452
2453if __name__ == '__main__':
2454    unittest.main()
2455