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