# ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC # Under the terms of Contract DE-NA0003525 with National Technology and # Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ import pyutilib.th as unittest from pyomo.common.log import LoggingIntercept import logging from pyomo.environ import * from pyomo.core.base import constraint from pyomo.repn import generate_standard_repn from pyomo.gdp import * import pyomo.gdp.tests.models as models import pyomo.gdp.tests.common_tests as ct import pyomo.opt linear_solvers = pyomo.opt.check_available_solvers( 'glpk','cbc','gurobi','cplex') import random from six import iteritems, iterkeys, StringIO EPS = TransformationFactory('gdp.hull').CONFIG.EPS class CommonTests: def setUp(self): # set seed so we can test name collisions predictably random.seed(666) def diff_apply_to_and_create_using(self, model): ct.diff_apply_to_and_create_using(self, model, 'gdp.hull') class TwoTermDisj(unittest.TestCase, CommonTests): def setUp(self): # set seed to test unique namer random.seed(666) def test_transformation_block(self): m = models.makeTwoTermDisj_Nonlinear() TransformationFactory('gdp.hull').apply_to(m) transBlock = m._pyomo_gdp_hull_reformulation self.assertIsInstance(transBlock, Block) lbub = transBlock.lbub self.assertIsInstance(lbub, Set) self.assertEqual(lbub, ['lb', 'ub', 'eq']) disjBlock = transBlock.relaxedDisjuncts self.assertIsInstance(disjBlock, Block) self.assertEqual(len(disjBlock), 2) def test_transformation_block_name_collision(self): ct.check_transformation_block_name_collision(self, 'hull') def test_disaggregated_vars(self): m = models.makeTwoTermDisj_Nonlinear() TransformationFactory('gdp.hull').apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts # same on both disjuncts for i in [0,1]: relaxationBlock = disjBlock[i] w = relaxationBlock.disaggregatedVars.w x = relaxationBlock.disaggregatedVars.x y = relaxationBlock.disaggregatedVars.y # variables created self.assertIsInstance(w, Var) self.assertIsInstance(x, Var) self.assertIsInstance(y, Var) # the are in reals self.assertIsInstance(w.domain, RealSet) self.assertIsInstance(x.domain, RealSet) self.assertIsInstance(y.domain, RealSet) # they don't have bounds self.assertEqual(w.lb, 0) self.assertEqual(w.ub, 7) self.assertEqual(x.lb, 0) self.assertEqual(x.ub, 8) self.assertEqual(y.lb, -10) self.assertEqual(y.ub, 0) def check_furman_et_al_denominator(self, expr, ind_var): self.assertEqual(expr._const, EPS) self.assertEqual(len(expr._args), 1) self.assertEqual(len(expr._coef), 1) self.assertEqual(expr._coef[0], 1 - EPS) self.assertIs(expr._args[0], ind_var) def test_transformed_constraint_nonlinear(self): m = models.makeTwoTermDisj_Nonlinear() TransformationFactory('gdp.hull').apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts # the only constraint on the first block is the non-linear one disj1c = disjBlock[0].component("d[0].c") self.assertIsInstance(disj1c, Constraint) # we only have an upper bound self.assertEqual(len(disj1c), 1) cons = disj1c['ub'] self.assertIsNone(cons.lower) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertFalse(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 1) # This is a weak test, but as good as any to ensure that the # substitution was done correctly EPS_1 = 1-EPS self.assertEqual( str(cons.body), "(%s*d[0].indicator_var + %s)*(" "_pyomo_gdp_hull_reformulation.relaxedDisjuncts[0]." "disaggregatedVars.x" "/(%s*d[0].indicator_var + %s) + " "(_pyomo_gdp_hull_reformulation.relaxedDisjuncts[0]." "disaggregatedVars.y/" "(%s*d[0].indicator_var + %s))**2) - " "%s*(0.0 + 0.0**2)*(1 - d[0].indicator_var) " "- 14.0*d[0].indicator_var" % (EPS_1, EPS, EPS_1, EPS, EPS_1, EPS, EPS)) def test_transformed_constraints_linear(self): m = models.makeTwoTermDisj_Nonlinear() TransformationFactory('gdp.hull').apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts # the only constraint on the first block is the non-linear one c1 = disjBlock[1].component("d[1].c1") # has only lb self.assertEqual(len(c1), 1) cons = c1['lb'] self.assertIsNone(cons.lower) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, disjBlock[1].disaggregatedVars.x, -1) ct.check_linear_coef(self, repn, m.d[1].indicator_var, 2) self.assertEqual(repn.constant, 0) self.assertEqual(disjBlock[1].disaggregatedVars.x.lb, 0) self.assertEqual(disjBlock[1].disaggregatedVars.x.ub, 8) c2 = disjBlock[1].component("d[1].c2") # 'eq' is preserved self.assertEqual(len(c2), 1) cons = c2['eq'] self.assertEqual(cons.lower, 0) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, disjBlock[1].disaggregatedVars.w, 1) ct.check_linear_coef(self, repn, m.d[1].indicator_var, -3) self.assertEqual(repn.constant, 0) self.assertEqual(disjBlock[1].disaggregatedVars.w.lb, 0) self.assertEqual(disjBlock[1].disaggregatedVars.w.ub, 7) c3 = disjBlock[1].component("d[1].c3") # bounded inequality is split self.assertEqual(len(c3), 2) cons = c3['lb'] self.assertIsNone(cons.lower) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, disjBlock[1].disaggregatedVars.x, -1) ct.check_linear_coef(self, repn, m.d[1].indicator_var, 1) self.assertEqual(repn.constant, 0) cons = c3['ub'] self.assertIsNone(cons.lower) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, disjBlock[1].disaggregatedVars.x, 1) ct.check_linear_coef(self, repn, m.d[1].indicator_var, -3) self.assertEqual(repn.constant, 0) def check_bound_constraints(self, cons, disvar, indvar, lb, ub): self.assertIsInstance(cons, Constraint) # both lb and ub self.assertEqual(len(cons), 2) varlb = cons['lb'] self.assertIsNone(varlb.lower) self.assertEqual(varlb.upper, 0) repn = generate_standard_repn(varlb.body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, indvar, lb) ct.check_linear_coef(self, repn, disvar, -1) varub = cons['ub'] self.assertIsNone(varub.lower) self.assertEqual(varub.upper, 0) repn = generate_standard_repn(varub.body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, indvar, -ub) ct.check_linear_coef(self, repn, disvar, 1) def test_disaggregatedVar_bounds(self): m = models.makeTwoTermDisj_Nonlinear() TransformationFactory('gdp.hull').apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts for i in [0,1]: # check bounds constraints for each variable on each of the two # disjuncts. self.check_bound_constraints(disjBlock[i].w_bounds, disjBlock[i].disaggregatedVars.w, m.d[i].indicator_var, 2, 7) self.check_bound_constraints(disjBlock[i].x_bounds, disjBlock[i].disaggregatedVars.x, m.d[i].indicator_var, 1, 8) self.check_bound_constraints(disjBlock[i].y_bounds, disjBlock[i].disaggregatedVars.y, m.d[i].indicator_var, -10, -3) def test_error_for_or(self): m = models.makeTwoTermDisj_Nonlinear() m.disjunction.xor = False self.assertRaisesRegexp( GDP_Error, "Cannot do hull reformulation for Disjunction " "'disjunction' with OR constraint. Must be an XOR!*", TransformationFactory('gdp.hull').apply_to, m) def check_disaggregation_constraint(self, cons, var, disvar1, disvar2): repn = generate_standard_repn(cons.body) self.assertEqual(cons.lower, 0) self.assertEqual(cons.upper, 0) self.assertEqual(len(repn.linear_vars), 3) ct.check_linear_coef(self, repn, var, 1) ct.check_linear_coef(self, repn, disvar1, -1) ct.check_linear_coef(self, repn, disvar2, -1) def test_disaggregation_constraint(self): m = models.makeTwoTermDisj_Nonlinear() hull = TransformationFactory('gdp.hull') hull.apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts self.check_disaggregation_constraint( hull.get_disaggregation_constraint(m.w, m.disjunction), m.w, disjBlock[0].disaggregatedVars.w, disjBlock[1].disaggregatedVars.w) self.check_disaggregation_constraint( hull.get_disaggregation_constraint(m.x, m.disjunction), m.x, disjBlock[0].disaggregatedVars.x, disjBlock[1].disaggregatedVars.x) self.check_disaggregation_constraint( hull.get_disaggregation_constraint(m.y, m.disjunction), m.y, disjBlock[0].disaggregatedVars.y, disjBlock[1].disaggregatedVars.y) def test_xor_constraint_mapping(self): ct.check_xor_constraint_mapping(self, 'hull') def test_xor_constraint_mapping_two_disjunctions(self): ct.check_xor_constraint_mapping_two_disjunctions(self, 'hull') def test_transformed_disjunct_mappings(self): ct.check_disjunct_mapping(self, 'hull') def test_transformed_constraint_mappings(self): # ESJ: Letting bigm and hull test their own constraint mappings # because, though the paradigm is the same, hull doesn't always create # a transformed constraint when it can instead accomplish an x == 0 # constraint by fixing the disaggregated variable. m = models.makeTwoTermDisj_Nonlinear() hull = TransformationFactory('gdp.hull') hull.apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts # first disjunct orig1 = m.d[0].c trans1 = disjBlock[0].component("d[0].c") self.assertIs(hull.get_src_constraint(trans1), orig1) self.assertIs(hull.get_src_constraint(trans1['ub']), orig1) trans_list = hull.get_transformed_constraints(orig1) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], trans1['ub']) # second disjunct # first constraint orig1 = m.d[1].c1 trans1 = disjBlock[1].component("d[1].c1") self.assertIs(hull.get_src_constraint(trans1), orig1) self.assertIs(hull.get_src_constraint(trans1['lb']), orig1) trans_list = hull.get_transformed_constraints(orig1) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], trans1['lb']) # second constraint orig2 = m.d[1].c2 trans2 = disjBlock[1].component("d[1].c2") self.assertIs(hull.get_src_constraint(trans2), orig2) self.assertIs(hull.get_src_constraint(trans2['eq']), orig2) trans_list = hull.get_transformed_constraints(orig2) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], trans2['eq']) # third constraint orig3 = m.d[1].c3 trans3 = disjBlock[1].component("d[1].c3") self.assertIs(hull.get_src_constraint(trans3), orig3) self.assertIs(hull.get_src_constraint(trans3['lb']), orig3) self.assertIs(hull.get_src_constraint(trans3['ub']), orig3) trans_list = hull.get_transformed_constraints(orig3) self.assertEqual(len(trans_list), 2) self.assertIs(trans_list[0], trans3['lb']) self.assertIs(trans_list[1], trans3['ub']) def test_disaggregatedVar_mappings(self): m = models.makeTwoTermDisj_Nonlinear() hull = TransformationFactory('gdp.hull') hull.apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts for i in [0,1]: mappings = ComponentMap() mappings[m.w] = disjBlock[i].disaggregatedVars.w mappings[m.y] = disjBlock[i].disaggregatedVars.y mappings[m.x] = disjBlock[i].disaggregatedVars.x for orig, disagg in iteritems(mappings): self.assertIs(hull.get_src_var(disagg), orig) self.assertIs(hull.get_disaggregated_var(orig, m.d[i]), disagg) def test_bigMConstraint_mappings(self): m = models.makeTwoTermDisj_Nonlinear() hull = TransformationFactory('gdp.hull') hull.apply_to(m) disjBlock = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts for i in [0,1]: mappings = ComponentMap() # [ESJ 11/05/2019] I think this test was useless before... I think # this *map* was useless before. It should be disaggregated variable # to the constraints, not the original variable? Why did this even # work?? mappings[disjBlock[i].disaggregatedVars.w] = disjBlock[i].w_bounds mappings[disjBlock[i].disaggregatedVars.y] = disjBlock[i].y_bounds mappings[disjBlock[i].disaggregatedVars.x] = disjBlock[i].x_bounds for var, cons in iteritems(mappings): self.assertIs(hull.get_var_bounds_constraint(var), cons) def test_create_using_nonlinear(self): m = models.makeTwoTermDisj_Nonlinear() self.diff_apply_to_and_create_using(m) # [ESJ 02/14/2020] In order to match bigm and the (unfortunate) expectation # we have established, we never decide something is local based on where it # is declared. We treat variables declared on Disjuncts as if they are # declared globally. We need to use the bounds as if they are global and # also disaggregate the variable def test_locally_declared_var_bounds_used_globally(self): m = models.localVar() hull = TransformationFactory('gdp.hull') hull.apply_to(m) # check that we used the bounds on the local variable as if they are # global. Which means checking the bounds constraints... y_disagg = m.disj2.transformation_block().disaggregatedVars.y cons = hull.get_var_bounds_constraint(y_disagg) lb = cons['lb'] self.assertIsNone(lb.lower) self.assertEqual(value(lb.upper), 0) repn = generate_standard_repn(lb.body) self.assertTrue(repn.is_linear()) ct.check_linear_coef(self, repn, m.disj2.indicator_var, 1) ct.check_linear_coef(self, repn, y_disagg, -1) ub = cons['ub'] self.assertIsNone(ub.lower) self.assertEqual(value(ub.upper), 0) repn = generate_standard_repn(ub.body) self.assertTrue(repn.is_linear()) ct.check_linear_coef(self, repn, y_disagg, 1) ct.check_linear_coef(self, repn, m.disj2.indicator_var, -3) def test_locally_declared_variables_disaggregated(self): m = models.localVar() hull = TransformationFactory('gdp.hull') hull.apply_to(m) # two birds one stone: test the mappings too disj1y = hull.get_disaggregated_var(m.disj2.y, m.disj1) disj2y = hull.get_disaggregated_var(m.disj2.y, m.disj2) self.assertIs(disj1y, m.disj1._transformation_block().disaggregatedVars.y) self.assertIs(disj2y, m.disj2._transformation_block().disaggregatedVars.y) self.assertIs(hull.get_src_var(disj1y), m.disj2.y) self.assertIs(hull.get_src_var(disj2y), m.disj2.y) def test_global_vars_local_to_a_disjunction_disaggregated(self): # The point of this is that where a variable is declared has absolutely # nothing to do with whether or not it should be disaggregated. With the # only exception being that we can tell disaggregated variables and we # know they are really and truly local to only one disjunct (EVER, in the # whole model) because we declared them. # So here, for some perverse reason, we declare the variables on disj1, # but we use them in disj2. Both of them need to be disaggregated in # both disjunctions though: Neither is local. (And, unless we want to do # a search of the whole model (or disallow this kind of insanity) we # can't be smarter because what if you transformed this one disjunction # at a time? You can never assume a variable isn't used elsewhere in the # model, and if it is, you must disaggregate it.) m = ConcreteModel() m.disj1 = Disjunct() m.disj1.x = Var(bounds=(1, 10)) m.disj1.y = Var(bounds=(2, 11)) m.disj1.cons1 = Constraint(expr=m.disj1.x + m.disj1.y <= 5) m.disj2 = Disjunct() m.disj2.cons = Constraint(expr=m.disj1.y >= 8) m.disjunction1 = Disjunction(expr=[m.disj1, m.disj2]) m.disj3 = Disjunct() m.disj3.cons = Constraint(expr=m.disj1.x >= 7) m.disj4 = Disjunct() m.disj4.cons = Constraint(expr=m.disj1.y == 3) m.disjunction2 = Disjunction(expr=[m.disj3, m.disj4]) hull = TransformationFactory('gdp.hull') hull.apply_to(m) # check that all the variables are disaggregated for disj in [m.disj1, m.disj2, m.disj3, m.disj4]: transBlock = disj.transformation_block() varBlock = transBlock.disaggregatedVars self.assertEqual(len([v for v in varBlock.component_data_objects(Var)]), 2) x = varBlock.component("x") y = varBlock.component("y") self.assertIsInstance(x, Var) self.assertIsInstance(y, Var) self.assertIs(hull.get_disaggregated_var(m.disj1.x, disj), x) self.assertIs(hull.get_src_var(x), m.disj1.x) self.assertIs(hull.get_disaggregated_var(m.disj1.y, disj), y) self.assertIs(hull.get_src_var(y), m.disj1.y) def check_name_collision_disaggregated_vars(self, m, disj, name): hull = TransformationFactory('gdp.hull') transBlock = disj.transformation_block() varBlock = transBlock.disaggregatedVars self.assertEqual(len([v for v in varBlock.component_data_objects(Var)]), 2) x = varBlock.component("x") x2 = varBlock.component(name) self.assertIsInstance(x, Var) self.assertIsInstance(x2, Var) self.assertIs(hull.get_disaggregated_var(m.disj1.x, disj), x) self.assertIs(hull.get_src_var(x), m.disj1.x) self.assertIs(hull.get_disaggregated_var(m.x, disj), x2) self.assertIs(hull.get_src_var(x2), m.x) def test_disaggregated_var_name_collision(self): # same model as the test above, but now I am putting what was disj1.y # as m.x, just to invite disaster. m = ConcreteModel() m.x = Var(bounds=(2, 11)) m.disj1 = Disjunct() m.disj1.x = Var(bounds=(1, 10)) m.disj1.cons1 = Constraint(expr=m.disj1.x + m.x <= 5) m.disj2 = Disjunct() m.disj2.cons = Constraint(expr=m.x >= 8) m.disjunction1 = Disjunction(expr=[m.disj1, m.disj2]) m.disj3 = Disjunct() m.disj3.cons = Constraint(expr=m.disj1.x >= 7) m.disj4 = Disjunct() m.disj4.cons = Constraint(expr=m.x == 3) m.disjunction2 = Disjunction(expr=[m.disj3, m.disj4]) hull = TransformationFactory('gdp.hull') hull.apply_to(m) for disj, nm in ((m.disj1, "x_4"), (m.disj2, "x_9"), (m.disj3, "x_5"), (m.disj4, "x_8")): self.check_name_collision_disaggregated_vars(m, disj, nm) def test_do_not_transform_user_deactivated_disjuncts(self): ct.check_user_deactivated_disjuncts(self, 'hull') def test_improperly_deactivated_disjuncts(self): ct.check_improperly_deactivated_disjuncts(self, 'hull') def test_do_not_transform_userDeactivated_IndexedDisjunction(self): ct.check_do_not_transform_userDeactivated_indexedDisjunction(self, 'hull') def test_disjunction_deactivated(self): ct.check_disjunction_deactivated(self, 'hull') def test_disjunctDatas_deactivated(self): ct.check_disjunctDatas_deactivated(self, 'hull') def test_deactivated_constraints(self): ct.check_deactivated_constraints(self, 'hull') def check_no_double_transformation(self): ct.check_do_not_transform_twice_if_disjunction_reactivated(self, 'hull') def test_indicator_vars(self): ct.check_indicator_vars(self, 'hull') def test_xor_constraints(self): ct.check_xor_constraint(self, 'hull') def test_unbounded_var_error(self): m = models.makeTwoTermDisj_Nonlinear() # no bounds m.w.setlb(None) m.w.setub(None) self.assertRaisesRegexp( GDP_Error, "Variables that appear in disjuncts must be " "bounded in order to use the hull " "transformation! Missing bound for w.*", TransformationFactory('gdp.hull').apply_to, m) def test_indexed_constraints_in_disjunct(self): m = models.makeThreeTermDisj_IndexedConstraints() TransformationFactory('gdp.hull').apply_to(m) transBlock = m._pyomo_gdp_hull_reformulation # 2 blocks: the original Disjunct and the transformation block self.assertEqual( len(list(m.component_objects(Block, descend_into=False))), 1) self.assertEqual( len(list(m.component_objects(Disjunct))), 1) # Each relaxed disjunct should have 3 disaggregated vars, but i "d[i].c" # Constraints for i in [1,2,3]: relaxed = transBlock.relaxedDisjuncts[i-1] self.assertEqual( len(list(relaxed.disaggregatedVars.component_objects(Var))), 3) self.assertEqual( len(list(relaxed.disaggregatedVars.component_data_objects(Var))), 3) self.assertEqual( len(list(relaxed.component_objects(Constraint))), 4) # Note: m.x LB == 0, so only 3 bounds constriants (not 6) self.assertEqual( len(list(relaxed.component_data_objects(Constraint))), 3+i) self.assertEqual(len(relaxed.component('d[%s].c'%i)), i) def test_virtual_indexed_constraints_in_disjunct(self): m = ConcreteModel() m.I = [1,2,3] m.x = Var(m.I, bounds=(-1,10)) def d_rule(d,j): m = d.model() d.c = Constraint(Any) for k in range(j): d.c[k+1] = m.x[k+1] >= k+1 m.d = Disjunct(m.I, rule=d_rule) m.disjunction = Disjunction(expr=[m.d[i] for i in m.I]) TransformationFactory('gdp.hull').apply_to(m) transBlock = m._pyomo_gdp_hull_reformulation # 2 blocks: the original Disjunct and the transformation block self.assertEqual( len(list(m.component_objects(Block, descend_into=False))), 1) self.assertEqual( len(list(m.component_objects(Disjunct))), 1) # Each relaxed disjunct should have 3 disaggregated vars, but i "d[i].c" # Constraints for i in [1,2,3]: relaxed = transBlock.relaxedDisjuncts[i-1] self.assertEqual( len(list(relaxed.disaggregatedVars.component_objects( Var))), 3) self.assertEqual( len(list(relaxed.disaggregatedVars.component_data_objects( Var))), 3) self.assertEqual( len(list(relaxed.component_objects(Constraint))), 4) self.assertEqual( len(list(relaxed.component_data_objects(Constraint))), 3*2+i) self.assertEqual(len(relaxed.component('d[%s].c'%i)), i) def test_do_not_transform_deactivated_constraintDatas(self): m = models.makeTwoTermDisj_IndexedConstraints() m.a[1].setlb(0) m.a[1].setub(100) m.a[2].setlb(0) m.a[2].setub(100) m.b.simpledisj1.c[1].deactivate() hull = TransformationFactory('gdp.hull') hull.apply_to(m) # can't ask for simpledisj1.c[1]: it wasn't transformed log = StringIO() with LoggingIntercept(log, 'pyomo.gdp', logging.ERROR): self.assertRaisesRegexp( KeyError, ".*b.simpledisj1.c\[1\]", hull.get_transformed_constraints, m.b.simpledisj1.c[1]) self.assertRegexpMatches(log.getvalue(), ".*Constraint 'b.simpledisj1.c\[1\]' has not " "been transformed.") # this fixes a[2] to 0, so we should get the disggregated var transformed = hull.get_transformed_constraints(m.b.simpledisj1.c[2]) self.assertEqual(len(transformed), 1) disaggregated_a2 = hull.get_disaggregated_var(m.a[2], m.b.simpledisj1) self.assertIs(transformed[0], disaggregated_a2) self.assertIsInstance(disaggregated_a2, Var) self.assertTrue(disaggregated_a2.is_fixed()) self.assertEqual(value(disaggregated_a2), 0) transformed = hull.get_transformed_constraints(m.b.simpledisj2.c[1]) # simpledisj2.c[1] is a <= constraint self.assertEqual(len(transformed), 1) self.assertIs(transformed[0], m.b.simpledisj2.transformation_block().\ component("b.simpledisj2.c")[(1,'ub')]) transformed = hull.get_transformed_constraints(m.b.simpledisj2.c[2]) # simpledisj2.c[2] is a <= constraint self.assertEqual(len(transformed), 1) self.assertIs(transformed[0], m.b.simpledisj2.transformation_block().\ component("b.simpledisj2.c")[(2,'ub')]) class MultiTermDisj(unittest.TestCase, CommonTests): def test_xor_constraint(self): ct.check_three_term_xor_constraint(self, 'hull') def test_create_using(self): m = models.makeThreeTermIndexedDisj() self.diff_apply_to_and_create_using(m) class IndexedDisjunction(unittest.TestCase, CommonTests): def setUp(self): # set seed so we can test name collisions predictably random.seed(666) def test_disaggregation_constraints(self): m = models.makeTwoTermIndexedDisjunction() hull = TransformationFactory('gdp.hull') hull.apply_to(m) relaxedDisjuncts = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts disaggregatedVars = { 1: [relaxedDisjuncts[0].disaggregatedVars.component('x[1]'), relaxedDisjuncts[1].disaggregatedVars.component('x[1]')], 2: [relaxedDisjuncts[2].disaggregatedVars.component('x[2]'), relaxedDisjuncts[3].disaggregatedVars.component('x[2]')], 3: [relaxedDisjuncts[4].disaggregatedVars.component('x[3]'), relaxedDisjuncts[5].disaggregatedVars.component('x[3]')], } for i, disVars in iteritems(disaggregatedVars): cons = hull.get_disaggregation_constraint(m.x[i], m.disjunction[i]) self.assertEqual(cons.lower, 0) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 3) ct.check_linear_coef(self, repn, m.x[i], 1) ct.check_linear_coef(self, repn, disVars[0], -1) ct.check_linear_coef(self, repn, disVars[1], -1) def test_disaggregation_constraints_tuple_indices(self): m = models.makeTwoTermMultiIndexedDisjunction() hull = TransformationFactory('gdp.hull') hull.apply_to(m) relaxedDisjuncts = m._pyomo_gdp_hull_reformulation.relaxedDisjuncts disaggregatedVars = { (1,'A'): [relaxedDisjuncts[0].disaggregatedVars.component('a[1,A]'), relaxedDisjuncts[1].disaggregatedVars.component('a[1,A]')], (1,'B'): [relaxedDisjuncts[2].disaggregatedVars.component('a[1,B]'), relaxedDisjuncts[3].disaggregatedVars.component('a[1,B]')], (2,'A'): [relaxedDisjuncts[4].disaggregatedVars.component('a[2,A]'), relaxedDisjuncts[5].disaggregatedVars.component('a[2,A]')], (2,'B'): [relaxedDisjuncts[6].disaggregatedVars.component('a[2,B]'), relaxedDisjuncts[7].disaggregatedVars.component('a[2,B]')], } for i, disVars in iteritems(disaggregatedVars): cons = hull.get_disaggregation_constraint(m.a[i], m.disjunction[i]) self.assertEqual(cons.lower, 0) self.assertEqual(cons.upper, 0) # NOTE: fixed variables are evaluated here. repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) # The flag=1 disjunct disaggregated variable is fixed to 0, so the # below is actually correct: self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, m.a[i], 1) ct.check_linear_coef(self, repn, disVars[0], -1) self.assertTrue(disVars[1].is_fixed()) self.assertEqual(value(disVars[1]), 0) def test_xor_constraints(self): ct.check_indexed_xor_constraints(self, 'hull') def test_xor_constraints_with_targets(self): ct.check_indexed_xor_constraints_with_targets(self, 'hull') def test_create_using(self): m = models.makeTwoTermMultiIndexedDisjunction() ct.diff_apply_to_and_create_using(self, m, 'gdp.hull') def test_deactivated_constraints(self): ct.check_constraints_deactivated_indexedDisjunction(self, 'hull') def test_deactivated_disjuncts(self): ct.check_deactivated_disjuncts(self, 'hull') def test_deactivated_disjunctions(self): ct.check_deactivated_disjunctions(self, 'hull') def test_partial_deactivate_indexed_disjunction(self): ct.check_partial_deactivate_indexed_disjunction(self, 'hull') def test_disjunction_data_target(self): ct.check_disjunction_data_target(self, 'hull') def test_disjunction_data_target_any_index(self): ct.check_disjunction_data_target_any_index(self, 'hull') def test_targets_with_container_as_arg(self): ct.check_targets_with_container_as_arg(self, 'hull') def check_trans_block_disjunctions_of_disjunct_datas(self, m): transBlock1 = m.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance(transBlock1, Block) self.assertIsInstance(transBlock1.component("relaxedDisjuncts"), Block) # We end up with a transformation block for every SimpleDisjunction or # IndexedDisjunction. self.assertEqual(len(transBlock1.relaxedDisjuncts), 2) self.assertIsInstance(transBlock1.relaxedDisjuncts[0].disaggregatedVars.\ component("x"), Var) self.assertTrue(transBlock1.relaxedDisjuncts[0].disaggregatedVars.x.\ is_fixed()) self.assertEqual(value(transBlock1.relaxedDisjuncts[0].\ disaggregatedVars.x), 0) self.assertIsInstance(transBlock1.relaxedDisjuncts[0].component( "firstTerm[1].cons"), Constraint) # No constraint becuase disaggregated variable fixed to 0 self.assertEqual(len(transBlock1.relaxedDisjuncts[0].component( "firstTerm[1].cons")), 0) self.assertIsInstance(transBlock1.relaxedDisjuncts[0].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock1.relaxedDisjuncts[0].component( "x_bounds")), 2) self.assertIsInstance(transBlock1.relaxedDisjuncts[1].disaggregatedVars.\ component("x"), Var) self.assertIsInstance(transBlock1.relaxedDisjuncts[1].component( "secondTerm[1].cons"), Constraint) self.assertEqual(len(transBlock1.relaxedDisjuncts[1].component( "secondTerm[1].cons")), 1) self.assertIsInstance(transBlock1.relaxedDisjuncts[1].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock1.relaxedDisjuncts[1].component( "x_bounds")), 2) transBlock2 = m.component("_pyomo_gdp_hull_reformulation_4") self.assertIsInstance(transBlock2, Block) self.assertIsInstance(transBlock2.component("relaxedDisjuncts"), Block) self.assertEqual(len(transBlock2.relaxedDisjuncts), 2) self.assertIsInstance(transBlock2.relaxedDisjuncts[0].disaggregatedVars.\ component("x"), Var) self.assertIsInstance(transBlock2.relaxedDisjuncts[0].component( "firstTerm[2].cons"), Constraint) # we have an equality constraint self.assertEqual(len(transBlock2.relaxedDisjuncts[0].component( "firstTerm[2].cons")), 1) self.assertIsInstance(transBlock2.relaxedDisjuncts[0].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock2.relaxedDisjuncts[0].component( "x_bounds")), 2) self.assertIsInstance(transBlock2.relaxedDisjuncts[1].disaggregatedVars.\ component("x"), Var) self.assertIsInstance(transBlock2.relaxedDisjuncts[1].component( "secondTerm[2].cons"), Constraint) self.assertEqual(len(transBlock2.relaxedDisjuncts[1].component( "secondTerm[2].cons")), 1) self.assertIsInstance(transBlock2.relaxedDisjuncts[1].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock2.relaxedDisjuncts[1].component( "x_bounds")), 2) def test_simple_disjunction_of_disjunct_datas(self): ct.check_simple_disjunction_of_disjunct_datas(self, 'hull') def test_any_indexed_disjunction_of_disjunct_datas(self): m = models.makeAnyIndexedDisjunctionOfDisjunctDatas() TransformationFactory('gdp.hull').apply_to(m) transBlock = m.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance(transBlock, Block) self.assertIsInstance(transBlock.component("relaxedDisjuncts"), Block) self.assertEqual(len(transBlock.relaxedDisjuncts), 4) self.assertIsInstance(transBlock.relaxedDisjuncts[0].disaggregatedVars.\ component("x"), Var) self.assertTrue(transBlock.relaxedDisjuncts[0].disaggregatedVars.\ x.is_fixed()) self.assertEqual(value(transBlock.relaxedDisjuncts[0].disaggregatedVars.\ x), 0) self.assertIsInstance(transBlock.relaxedDisjuncts[0].component( "firstTerm[1].cons"), Constraint) # No constraint becuase disaggregated variable fixed to 0 self.assertEqual(len(transBlock.relaxedDisjuncts[0].component( "firstTerm[1].cons")), 0) self.assertIsInstance(transBlock.relaxedDisjuncts[0].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[0].component( "x_bounds")), 2) self.assertIsInstance(transBlock.relaxedDisjuncts[1].disaggregatedVars.\ component("x"), Var) self.assertIsInstance(transBlock.relaxedDisjuncts[1].component( "secondTerm[1].cons"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[1].component( "secondTerm[1].cons")), 1) self.assertIsInstance(transBlock.relaxedDisjuncts[1].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[1].component( "x_bounds")), 2) self.assertIsInstance(transBlock.relaxedDisjuncts[2].disaggregatedVars.\ component("x"), Var) self.assertIsInstance(transBlock.relaxedDisjuncts[2].component( "firstTerm[2].cons"), Constraint) # we have an equality constraint self.assertEqual(len(transBlock.relaxedDisjuncts[2].component( "firstTerm[2].cons")), 1) self.assertIsInstance(transBlock.relaxedDisjuncts[2].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[2].component( "x_bounds")), 2) self.assertIsInstance(transBlock.relaxedDisjuncts[3].disaggregatedVars.\ component("x"), Var) self.assertIsInstance(transBlock.relaxedDisjuncts[3].component( "secondTerm[2].cons"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[3].component( "secondTerm[2].cons")), 1) self.assertIsInstance(transBlock.relaxedDisjuncts[3].component( "x_bounds"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[3].component( "x_bounds")), 2) self.assertIsInstance(transBlock.component("disjunction_xor"), Constraint) self.assertEqual(len(transBlock.component("disjunction_xor")), 2) def check_first_iteration(self, model): transBlock = model.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance(transBlock, Block) self.assertIsInstance( transBlock.component("disjunctionList_xor"), Constraint) self.assertEqual(len(transBlock.disjunctionList_xor), 1) self.assertFalse(model.disjunctionList[0].active) self.assertIsInstance(transBlock.relaxedDisjuncts, Block) self.assertEqual(len(transBlock.relaxedDisjuncts), 2) self.assertIsInstance(transBlock.relaxedDisjuncts[0].disaggregatedVars.x, Var) self.assertTrue(transBlock.relaxedDisjuncts[0].disaggregatedVars.x.\ is_fixed()) self.assertEqual(value(transBlock.relaxedDisjuncts[0].disaggregatedVars.\ x), 0) self.assertIsInstance(transBlock.relaxedDisjuncts[0].component( "firstTerm[0].cons"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[0].component( "firstTerm[0].cons")), 0) self.assertIsInstance(transBlock.relaxedDisjuncts[0].x_bounds, Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[0].x_bounds), 2) self.assertIsInstance(transBlock.relaxedDisjuncts[1].disaggregatedVars.x, Var) self.assertFalse(transBlock.relaxedDisjuncts[1].disaggregatedVars.\ x.is_fixed()) self.assertIsInstance(transBlock.relaxedDisjuncts[1].component( "secondTerm[0].cons"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[1].component( "secondTerm[0].cons")), 1) self.assertIsInstance(transBlock.relaxedDisjuncts[1].x_bounds, Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[1].x_bounds), 2) def check_second_iteration(self, model): transBlock = model.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance(transBlock, Block) self.assertIsInstance(transBlock.component("relaxedDisjuncts"), Block) self.assertEqual(len(transBlock.relaxedDisjuncts), 4) self.assertIsInstance(transBlock.relaxedDisjuncts[2].component( "firstTerm[1].cons"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[2].component( "firstTerm[1].cons")), 1) self.assertIsInstance(transBlock.relaxedDisjuncts[3].component( "secondTerm[1].cons"), Constraint) self.assertEqual(len(transBlock.relaxedDisjuncts[3].component( "secondTerm[1].cons")), 1) self.assertEqual( len(transBlock.disjunctionList_xor), 2) self.assertFalse(model.disjunctionList[1].active) self.assertFalse(model.disjunctionList[0].active) def test_disjunction_and_disjuncts_indexed_by_any(self): ct.check_disjunction_and_disjuncts_indexed_by_any(self, 'hull') def test_iteratively_adding_disjunctions_transform_container(self): ct.check_iteratively_adding_disjunctions_transform_container(self, 'hull') def test_iteratively_adding_disjunctions_transform_model(self): ct.check_iteratively_adding_disjunctions_transform_model(self, 'hull') def test_iteratively_adding_to_indexed_disjunction_on_block(self): ct.check_iteratively_adding_to_indexed_disjunction_on_block(self, 'hull') class TestTargets_SingleDisjunction(unittest.TestCase, CommonTests): def test_only_targets_inactive(self): ct.check_only_targets_inactive(self, 'hull') def test_only_targets_transformed(self): ct.check_only_targets_get_transformed(self, 'hull') def test_target_not_a_component_err(self): ct.check_target_not_a_component_error(self, 'hull') def test_targets_cannot_be_cuids(self): ct.check_targets_cannot_be_cuids(self, 'hull') class TestTargets_IndexedDisjunction(unittest.TestCase, CommonTests): # There are a couple tests for targets above, but since I had the patience # to make all these for bigm also, I may as well reap the benefits here too. def test_indexedDisj_targets_inactive(self): ct.check_indexedDisj_targets_inactive(self, 'hull') def test_indexedDisj_only_targets_transformed(self): ct.check_indexedDisj_only_targets_transformed(self, 'hull') def test_warn_for_untransformed(self): ct.check_warn_for_untransformed(self, 'hull') def test_disjData_targets_inactive(self): ct.check_disjData_targets_inactive(self, 'hull') m = models.makeDisjunctionsOnIndexedBlock() def test_disjData_only_targets_transformed(self): ct.check_disjData_only_targets_transformed(self, 'hull') def test_indexedBlock_targets_inactive(self): ct.check_indexedBlock_targets_inactive(self, 'hull') def test_indexedBlock_only_targets_transformed(self): ct.check_indexedBlock_only_targets_transformed(self, 'hull') def test_blockData_targets_inactive(self): ct.check_blockData_targets_inactive(self, 'hull') def test_blockData_only_targets_transformed(self): ct.check_blockData_only_targets_transformed(self, 'hull') def test_do_not_transform_deactivated_targets(self): ct.check_do_not_transform_deactivated_targets(self, 'hull') def test_create_using(self): m = models.makeDisjunctionsOnIndexedBlock() ct.diff_apply_to_and_create_using(self, m, 'gdp.hull') class DisaggregatedVarNamingConflict(unittest.TestCase): @staticmethod def makeModel(): m = ConcreteModel() m.b = Block() m.b.x = Var(bounds=(0, 10)) m.add_component("b.x", Var(bounds=(-9, 9))) def disjunct_rule(d, i): m = d.model() if i: d.cons_block = Constraint(expr=m.b.x >= 5) d.cons_model = Constraint(expr=m.component("b.x")==0) else: d.cons_model = Constraint(expr=m.component("b.x") <= -5) m.disjunct = Disjunct([0,1], rule=disjunct_rule) m.disjunction = Disjunction(expr=[m.disjunct[0], m.disjunct[1]]) return m def test_disaggregation_constraints(self): m = self.makeModel() hull = TransformationFactory('gdp.hull') hull.apply_to(m) disaggregationConstraints = m._pyomo_gdp_hull_reformulation.\ disaggregationConstraints disaggregationConstraints.pprint() consmap = [ (m.component("b.x"), disaggregationConstraints[(0, None)]), (m.b.x, disaggregationConstraints[(1, None)]) ] for v, cons in consmap: disCons = hull.get_disaggregation_constraint(v, m.disjunction) self.assertIs(disCons, cons) class DisjunctInMultipleDisjunctions(unittest.TestCase, CommonTests): def test_error_for_same_disjunct_in_multiple_disjunctions(self): ct.check_error_for_same_disjunct_in_multiple_disjunctions(self, 'hull') class NestedDisjunction(unittest.TestCase, CommonTests): def setUp(self): # set seed so we can test name collisions predictably random.seed(666) def test_disjuncts_inactive(self): ct.check_disjuncts_inactive_nested(self, 'hull') def test_deactivated_disjunct_leaves_nested_disjuncts_active(self): ct.check_deactivated_disjunct_leaves_nested_disjunct_active(self, 'hull') def test_mappings_between_disjunctions_and_xors(self): # For the sake of not second-guessing anyone, we will let the inner # disjunction point to its original XOR constraint. This constraint # itself will be transformed by the outer disjunction, so if you want to # find what it became you will have to follow its map to the transformed # version. (But this behaves the same as bigm) ct.check_mappings_between_disjunctions_and_xors(self, 'hull') def test_disjunct_targets_inactive(self): ct.check_disjunct_targets_inactive(self, 'hull') def test_disjunct_only_targets_transformed(self): ct.check_disjunct_only_targets_transformed(self, 'hull') def test_disjunctData_targets_inactive(self): ct.check_disjunctData_targets_inactive(self, 'hull') def test_disjunctData_only_targets_transformed(self): ct.check_disjunctData_only_targets_transformed(self, 'hull') def test_disjunction_target_err(self): ct.check_disjunction_target_err(self, 'hull') @unittest.skipIf(not linear_solvers, "No linear solver available") def test_relaxation_feasibility(self): m = models.makeNestedDisjunctions_FlatDisjuncts() TransformationFactory('gdp.hull').apply_to(m) solver = SolverFactory(linear_solvers[0]) cases = [ (1,1,1,1,None), (0,0,0,0,None), (1,0,0,0,None), (0,1,0,0,1.1), (0,0,1,0,None), (0,0,0,1,None), (1,1,0,0,None), (1,0,1,0,1.2), (1,0,0,1,1.3), (1,0,1,1,None), ] for case in cases: m.d1.indicator_var.fix(case[0]) m.d2.indicator_var.fix(case[1]) m.d3.indicator_var.fix(case[2]) m.d4.indicator_var.fix(case[3]) results = solver.solve(m) if case[4] is None: self.assertEqual(results.solver.termination_condition, pyomo.opt.TerminationCondition.infeasible) else: self.assertEqual(results.solver.termination_condition, pyomo.opt.TerminationCondition.optimal) self.assertEqual(value(m.obj), case[4]) @unittest.skipIf(not linear_solvers, "No linear solver available") def test_relaxation_feasibility_transform_inner_first(self): # This test is identical to the above except that the # reference_indicator_var transformation will be called on m.d1 # first. So this makes sure that we are still doing the right thing even # if the indicator_var references already exist. m = models.makeNestedDisjunctions_FlatDisjuncts() TransformationFactory('gdp.hull').apply_to(m.d1) TransformationFactory('gdp.hull').apply_to(m) solver = SolverFactory(linear_solvers[0]) cases = [ (1,1,1,1,None), (0,0,0,0,None), (1,0,0,0,None), (0,1,0,0,1.1), (0,0,1,0,None), (0,0,0,1,None), (1,1,0,0,None), (1,0,1,0,1.2), (1,0,0,1,1.3), (1,0,1,1,None), ] for case in cases: m.d1.indicator_var.fix(case[0]) m.d2.indicator_var.fix(case[1]) m.d3.indicator_var.fix(case[2]) m.d4.indicator_var.fix(case[3]) results = solver.solve(m) if case[4] is None: self.assertEqual(results.solver.termination_condition, pyomo.opt.TerminationCondition.infeasible) else: self.assertEqual(results.solver.termination_condition, pyomo.opt.TerminationCondition.optimal) self.assertEqual(value(m.obj), case[4]) def test_create_using(self): m = models.makeNestedDisjunctions_FlatDisjuncts() self.diff_apply_to_and_create_using(m) def check_outer_disaggregation_constraint(self, cons, var, disj1, disj2): hull = TransformationFactory('gdp.hull') self.assertTrue(cons.active) self.assertEqual(cons.lower, 0) self.assertEqual(cons.upper, 0) repn = generate_standard_repn(cons.body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) ct.check_linear_coef(self, repn, var, 1) ct.check_linear_coef(self, repn, hull.get_disaggregated_var(var, disj1), -1) ct.check_linear_coef(self, repn, hull.get_disaggregated_var(var, disj2), -1) def check_bounds_constraint_ub(self, constraint, ub, dis_var, ind_var): hull = TransformationFactory('gdp.hull') self.assertIsInstance(constraint, Constraint) self.assertTrue(constraint.active) self.assertEqual(len(constraint), 1) self.assertTrue(constraint['ub'].active) self.assertEqual(constraint['ub'].upper, 0) self.assertIsNone(constraint['ub'].lower) repn = generate_standard_repn(constraint['ub'].body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, dis_var, 1) ct.check_linear_coef(self, repn, ind_var, -ub) self.assertIs(constraint, hull.get_var_bounds_constraint(dis_var)) def check_inner_disaggregated_var_bounds(self, cons, dis, ind_var, original_cons): hull = TransformationFactory('gdp.hull') self.assertIsInstance(cons, Constraint) self.assertTrue(cons.active) self.assertEqual(len(cons), 1) self.assertTrue(cons[('ub', 'ub')].active) self.assertIsNone(cons[('ub', 'ub')].lower) self.assertEqual(cons[('ub', 'ub')].upper, 0) repn = generate_standard_repn(cons[('ub', 'ub')].body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, dis, 1) ct.check_linear_coef(self, repn, ind_var, -2) self.assertIs(hull.get_var_bounds_constraint(dis), original_cons) transformed_list = hull.get_transformed_constraints(original_cons['ub']) self.assertEqual(len(transformed_list), 1) self.assertIs(transformed_list[0], cons[('ub', 'ub')]) def check_inner_transformed_constraint(self, cons, dis, lb, ind_var, first_transformed, original): hull = TransformationFactory('gdp.hull') self.assertIsInstance(cons, Constraint) self.assertTrue(cons.active) self.assertEqual(len(cons), 1) # Ha, this really isn't lovely, but its just chance that it's ub the # second time. self.assertTrue(cons[('lb', 'ub')].active) self.assertIsNone(cons[('lb', 'ub')].lower) self.assertEqual(cons[('lb', 'ub')].upper, 0) repn = generate_standard_repn(cons[('lb', 'ub')].body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, dis, -1) ct.check_linear_coef(self, repn, ind_var, lb) self.assertIs(hull.get_src_constraint(first_transformed), original) trans_list = hull.get_transformed_constraints(original) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], first_transformed['lb']) self.assertIs(hull.get_src_constraint(first_transformed['lb']), original) self.assertIs(hull.get_src_constraint(cons), first_transformed) trans_list = hull.get_transformed_constraints(first_transformed['lb']) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], cons[('lb', 'ub')]) self.assertIs(hull.get_src_constraint(cons[('lb', 'ub')]), first_transformed['lb']) def check_outer_transformed_constraint(self, cons, dis, lb, ind_var): hull = TransformationFactory('gdp.hull') self.assertIsInstance(cons, Constraint) self.assertTrue(cons.active) self.assertEqual(len(cons), 1) self.assertTrue(cons['lb'].active) self.assertIsNone(cons['lb'].lower) self.assertEqual(cons['lb'].upper, 0) repn = generate_standard_repn(cons['lb'].body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 2) ct.check_linear_coef(self, repn, dis, -1) ct.check_linear_coef(self, repn, ind_var, lb) orig = ind_var.parent_block().c self.assertIs(hull.get_src_constraint(cons), orig) trans_list = hull.get_transformed_constraints(orig) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], cons['lb']) def test_transformed_model_nestedDisjuncts(self): # This test tests *everything* for a simple nested disjunction case. m = models.makeNestedDisjunctions_NestedDisjuncts() hull = TransformationFactory('gdp.hull') hull.apply_to(m) transBlock = m._pyomo_gdp_hull_reformulation self.assertTrue(transBlock.active) # outer xor should be on this block xor = transBlock.disj_xor self.assertIsInstance(xor, Constraint) self.assertTrue(xor.active) self.assertEqual(xor.lower, 1) self.assertEqual(xor.upper, 1) repn = generate_standard_repn(xor.body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) ct.check_linear_coef(self, repn, m.d1.indicator_var, 1) ct.check_linear_coef(self, repn, m.d2.indicator_var, 1) self.assertIs(xor, m.disj.algebraic_constraint()) self.assertIs(m.disj, hull.get_src_disjunction(xor)) # so should the outer disaggregation constraint dis = transBlock.disaggregationConstraints self.assertIsInstance(dis, Constraint) self.assertTrue(dis.active) self.assertEqual(len(dis), 3) self.check_outer_disaggregation_constraint(dis[0,None], m.x, m.d1, m.d2) self.assertIs(hull.get_disaggregation_constraint(m.x, m.disj), dis[0, None]) self.check_outer_disaggregation_constraint(dis[1,None], m.d1.d3.indicator_var, m.d1, m.d2) self.assertIs(hull.get_disaggregation_constraint(m.d1.d3.indicator_var, m.disj), dis[1,None]) self.check_outer_disaggregation_constraint(dis[2,None], m.d1.d4.indicator_var, m.d1, m.d2) self.assertIs(hull.get_disaggregation_constraint(m.d1.d4.indicator_var, m.disj), dis[2,None]) # we should have four disjunct transformation blocks: 2 real ones and # then two that are just home to indicator_var and disaggregated var # References. disjBlocks = transBlock.relaxedDisjuncts self.assertTrue(disjBlocks.active) self.assertEqual(len(disjBlocks), 4) disj1 = disjBlocks[0] self.assertTrue(disj1.active) self.assertIs(disj1, m.d1.transformation_block()) self.assertIs(m.d1, hull.get_src_disjunct(disj1)) # check the disaggregated vars are here self.assertIsInstance(disj1.disaggregatedVars.x, Var) self.assertEqual(disj1.disaggregatedVars.x.lb, 0) self.assertEqual(disj1.disaggregatedVars.x.ub, 2) self.assertIs(disj1.disaggregatedVars.x, hull.get_disaggregated_var(m.x, m.d1)) self.assertIs(m.x, hull.get_src_var(disj1.disaggregatedVars.x)) d3 = disj1.disaggregatedVars.component("d1.d3.indicator_var") self.assertEqual(d3.lb, 0) self.assertEqual(d3.ub, 1) self.assertIsInstance(d3, Var) self.assertIs(d3, hull.get_disaggregated_var(m.d1.d3.indicator_var, m.d1)) self.assertIs(m.d1.d3.indicator_var, hull.get_src_var(d3)) d4 = disj1.disaggregatedVars.component("d1.d4.indicator_var") self.assertIsInstance(d4, Var) self.assertEqual(d4.lb, 0) self.assertEqual(d4.ub, 1) self.assertIs(d4, hull.get_disaggregated_var(m.d1.d4.indicator_var, m.d1)) self.assertIs(m.d1.d4.indicator_var, hull.get_src_var(d4)) # check inner disjunction disaggregated vars x3 = m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[0].\ disaggregatedVars.x self.assertIsInstance(x3, Var) self.assertEqual(x3.lb, 0) self.assertEqual(x3.ub, 2) self.assertIs(hull.get_disaggregated_var(m.x, m.d1.d3), x3) self.assertIs(hull.get_src_var(x3), m.x) x4 = m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1].\ disaggregatedVars.x self.assertIsInstance(x4, Var) self.assertEqual(x4.lb, 0) self.assertEqual(x4.ub, 2) self.assertIs(hull.get_disaggregated_var(m.x, m.d1.d4), x4) self.assertIs(hull.get_src_var(x4), m.x) # check the bounds constraints self.check_bounds_constraint_ub(disj1.x_bounds, 2, disj1.disaggregatedVars.x, m.d1.indicator_var) self.check_bounds_constraint_ub( disj1.component("d1.d3.indicator_var_bounds"), 1, disj1.disaggregatedVars.component("d1.d3.indicator_var"), m.d1.indicator_var) self.check_bounds_constraint_ub( disj1.component("d1.d4.indicator_var_bounds"), 1, disj1.disaggregatedVars.component("d1.d4.indicator_var"), m.d1.indicator_var) # check the transformed constraints # transformed xor xor = disj1.component("d1._pyomo_gdp_hull_reformulation.d1.disj2_xor") self.assertIsInstance(xor, Constraint) self.assertTrue(xor.active) self.assertEqual(len(xor), 1) self.assertTrue(xor['eq'].active) self.assertEqual(xor['eq'].lower, 0) self.assertEqual(xor['eq'].upper, 0) repn = generate_standard_repn(xor['eq'].body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 3) ct.check_linear_coef( self, repn, disj1.disaggregatedVars.component("d1.d3.indicator_var"), 1) ct.check_linear_coef( self, repn, disj1.disaggregatedVars.component("d1.d4.indicator_var"), 1) ct.check_linear_coef(self, repn, m.d1.indicator_var, -1) # inner disjunction disaggregation constraint dis_cons_inner_disjunction = disj1.component( "d1._pyomo_gdp_hull_reformulation.disaggregationConstraints") self.assertIsInstance(dis_cons_inner_disjunction, Constraint) self.assertTrue(dis_cons_inner_disjunction.active) self.assertEqual(len(dis_cons_inner_disjunction), 1) dis_cons_inner_disjunction.pprint() self.assertTrue(dis_cons_inner_disjunction[(0,None,'eq')].active) self.assertEqual(dis_cons_inner_disjunction[(0,None,'eq')].lower, 0) self.assertEqual(dis_cons_inner_disjunction[(0,None,'eq')].upper, 0) repn = generate_standard_repn(dis_cons_inner_disjunction[(0, None, 'eq')].body) self.assertTrue(repn.is_linear()) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 3) ct.check_linear_coef(self, repn, x3, -1) ct.check_linear_coef(self, repn, x4, -1) ct.check_linear_coef(self, repn, disj1.disaggregatedVars.x, 1) # disaggregated d3.x bounds constraints x3_bounds = disj1.component( "d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[0].x_bounds") original_cons = m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[0].\ x_bounds self.check_inner_disaggregated_var_bounds( x3_bounds, x3, disj1.disaggregatedVars.component("d1.d3.indicator_var"), original_cons) # disaggregated d4.x bounds constraints x4_bounds = disj1.component( "d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1].x_bounds") original_cons = m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1].\ x_bounds self.check_inner_disaggregated_var_bounds( x4_bounds, x4, disj1.disaggregatedVars.component("d1.d4.indicator_var"), original_cons) # transformed x >= 1.2 cons = disj1.component( "d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[0].d1.d3.c") first_transformed = m.d1._pyomo_gdp_hull_reformulation.\ relaxedDisjuncts[0].component("d1.d3.c") original = m.d1.d3.c self.check_inner_transformed_constraint( cons, x3, 1.2, disj1.disaggregatedVars.component("d1.d3.indicator_var"), first_transformed, original) # transformed x >= 1.3 cons = disj1.component( "d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1].d1.d4.c") first_transformed = m.d1._pyomo_gdp_hull_reformulation.\ relaxedDisjuncts[1].component("d1.d4.c") original = m.d1.d4.c self.check_inner_transformed_constraint( cons, x4, 1.3, disj1.disaggregatedVars.component("d1.d4.indicator_var"), first_transformed, original) # outer disjunction transformed constraint cons = disj1.component("d1.c") self.check_outer_transformed_constraint(cons, disj1.disaggregatedVars.x, 1, m.d1.indicator_var) # and last, check the second transformed outer disjunct disj2 = disjBlocks[3] self.assertTrue(disj2.active) self.assertIs(disj2, m.d2.transformation_block()) self.assertIs(m.d2, hull.get_src_disjunct(disj2)) # disaggregated var x2 = disj2.disaggregatedVars.x self.assertIsInstance(x2, Var) self.assertEqual(x2.lb, 0) self.assertEqual(x2.ub, 2) self.assertIs(hull.get_disaggregated_var(m.x, m.d2), x2) self.assertIs(hull.get_src_var(x2), m.x) # bounds constraint x_bounds = disj2.x_bounds self.check_bounds_constraint_ub(x_bounds, 2, x2, m.d2.indicator_var) # transformed constraint x >= 1.1 cons = disj2.component("d2.c") self.check_outer_transformed_constraint(cons, x2, 1.1, m.d2.indicator_var) # check inner xor mapping: Note that this maps to a now deactivated # (transformed again) constraint, but that it is possible to go full # circle, like so: orig_inner_xor = m.d1._pyomo_gdp_hull_reformulation.component( "d1.disj2_xor") self.assertIs(m.d1.disj2.algebraic_constraint(), orig_inner_xor) self.assertFalse(orig_inner_xor.active) trans_list = hull.get_transformed_constraints(orig_inner_xor) self.assertEqual(len(trans_list), 1) self.assertIs(trans_list[0], xor['eq']) self.assertIs(hull.get_src_constraint(xor), orig_inner_xor) self.assertIs(hull.get_src_disjunction(orig_inner_xor), m.d1.disj2) # the same goes for the disaggregation constraint orig_dis_container = m.d1._pyomo_gdp_hull_reformulation.\ disaggregationConstraints orig_dis = orig_dis_container[0,None] self.assertIs(hull.get_disaggregation_constraint(m.x, m.d1.disj2), orig_dis) self.assertFalse(orig_dis.active) transformedList = hull.get_transformed_constraints(orig_dis) self.assertEqual(len(transformedList), 1) self.assertIs(transformedList[0], dis_cons_inner_disjunction[(0, None, 'eq')]) self.assertIs(hull.get_src_constraint( dis_cons_inner_disjunction[(0, None, 'eq')]), orig_dis) self.assertIs(hull.get_src_constraint( dis_cons_inner_disjunction), orig_dis_container) # though we don't have a map back from the disaggregation constraint to # the variable because I'm not sure why you would... The variable is in # the constraint. # check the inner disjunct mappings self.assertIs(m.d1.d3.transformation_block(), m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[0]) self.assertIs(hull.get_src_disjunct( m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[0]), m.d1.d3) self.assertIs(m.d1.d4.transformation_block(), m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1]) self.assertIs(hull.get_src_disjunct( m.d1._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1]), m.d1.d4) @unittest.skipIf(not linear_solvers, "No linear solver available") def test_solve_nested_model(self): # This is really a test that our variable references have all been moved # up correctly. m = models.makeNestedDisjunctions_NestedDisjuncts() hull = TransformationFactory('gdp.hull') m_hull = hull.create_using(m) SolverFactory(linear_solvers[0]).solve(m_hull) # check solution self.assertEqual(value(m_hull.d1.indicator_var), 0) self.assertEqual(value(m_hull.d2.indicator_var), 1) self.assertEqual(value(m_hull.x), 1.1) # transform inner problem with bigm, outer with hull and make sure it # still works TransformationFactory('gdp.bigm').apply_to(m.d1.disj2) hull.apply_to(m) SolverFactory(linear_solvers[0]).solve(m) # check solution self.assertEqual(value(m.d1.indicator_var), 0) self.assertEqual(value(m.d2.indicator_var), 1) self.assertEqual(value(m.x), 1.1) class TestSpecialCases(unittest.TestCase): def test_local_vars(self): """ checks that if nothing is marked as local, we assume it is all global. We disaggregate everything to be safe.""" m = ConcreteModel() m.x = Var(bounds=(5,100)) m.y = Var(bounds=(0,100)) m.d1 = Disjunct() m.d1.c = Constraint(expr=m.y >= m.x) m.d2 = Disjunct() m.d2.z = Var() m.d2.c = Constraint(expr=m.y >= m.d2.z) m.disj = Disjunction(expr=[m.d1, m.d2]) self.assertRaisesRegexp( GDP_Error, ".*Missing bound for d2.z.*", TransformationFactory('gdp.hull').create_using, m) m.d2.z.setlb(7) self.assertRaisesRegexp( GDP_Error, ".*Missing bound for d2.z.*", TransformationFactory('gdp.hull').create_using, m) m.d2.z.setub(9) i = TransformationFactory('gdp.hull').create_using(m) rd = i._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1] varBlock = rd.disaggregatedVars # z should be disaggregated because we can't be sure it's not somewhere # else on the model self.assertEqual(sorted(varBlock.component_map(Var)), ['x','y','z']) self.assertEqual(len(rd.component_map(Constraint)), 4) # bounds haven't changed on original self.assertEqual(i.d2.z.bounds, (7,9)) # check disaggregated variable self.assertIsInstance(varBlock.component("z"), Var) self.assertEqual(varBlock.z.bounds, (0,9)) self.assertEqual(len(rd.z_bounds), 2) self.assertEqual(rd.z_bounds['lb'].lower, None) self.assertEqual(rd.z_bounds['lb'].upper, 0) self.assertEqual(rd.z_bounds['ub'].lower, None) self.assertEqual(rd.z_bounds['ub'].upper, 0) i.d2.indicator_var = 1 varBlock.z = 2 self.assertEqual(rd.z_bounds['lb'].body(), 5) self.assertEqual(rd.z_bounds['ub'].body(), -7) m.d2.z.setlb(-9) m.d2.z.setub(-7) i = TransformationFactory('gdp.hull').create_using(m) rd = i._pyomo_gdp_hull_reformulation.relaxedDisjuncts[1] varBlock = rd.disaggregatedVars self.assertEqual(sorted(varBlock.component_map(Var)), ['x','y','z']) self.assertEqual(len(rd.component_map(Constraint)), 4) # original bounds unchanged self.assertEqual(i.d2.z.bounds, (-9,-7)) # check disaggregated variable self.assertIsInstance(varBlock.component("z"), Var) self.assertEqual(varBlock.z.bounds, (-9,0)) self.assertEqual(len(rd.z_bounds), 2) self.assertEqual(rd.z_bounds['lb'].lower, None) self.assertEqual(rd.z_bounds['lb'].upper, 0) self.assertEqual(rd.z_bounds['ub'].lower, None) self.assertEqual(rd.z_bounds['ub'].upper, 0) i.d2.indicator_var = 1 varBlock.z = 2 self.assertEqual(rd.z_bounds['lb'].body(), -11) self.assertEqual(rd.z_bounds['ub'].body(), 9) def test_local_var_suffix(self): hull = TransformationFactory('gdp.hull') model = ConcreteModel() model.x = Var(bounds=(5,100)) model.y = Var(bounds=(0,100)) model.d1 = Disjunct() model.d1.c = Constraint(expr=model.y >= model.x) model.d2 = Disjunct() model.d2.z = Var(bounds=(-9, -7)) model.d2.c = Constraint(expr=model.y >= model.d2.z) model.disj = Disjunction(expr=[model.d1, model.d2]) # we don't declare z local m = hull.create_using(model) self.assertEqual(m.d2.z.lb, -9) self.assertEqual(m.d2.z.ub, -7) z_disaggregated = m.d2.transformation_block().disaggregatedVars.\ component("z") self.assertIsInstance(z_disaggregated, Var) self.assertIs(z_disaggregated, hull.get_disaggregated_var(m.d2.z, m.d2)) # we do declare z local model.d2.LocalVars = Suffix(direction=Suffix.LOCAL) model.d2.LocalVars[model.d2] = [model.d2.z] m = hull.create_using(model) # make sure we did not disaggregate z self.assertEqual(m.d2.z.lb, -9) self.assertEqual(m.d2.z.ub, 0) # it is its own disaggregated variable self.assertIs(hull.get_disaggregated_var(m.d2.z, m.d2), m.d2.z) # it does not exist on the transformation block self.assertIsNone(m.d2.transformation_block().disaggregatedVars.\ component("z")) class UntransformableObjectsOnDisjunct(unittest.TestCase): def test_RangeSet(self): ct.check_RangeSet(self, 'hull') def test_Expression(self): ct.check_Expression(self, 'hull') class TransformABlock(unittest.TestCase, CommonTests): def test_transformation_simple_block(self): ct.check_transformation_simple_block(self, 'hull') def test_transform_block_data(self): ct.check_transform_block_data(self, 'hull') def test_simple_block_target(self): ct.check_simple_block_target(self, 'hull') def test_block_data_target(self): ct.check_block_data_target(self, 'hull') def test_indexed_block_target(self): ct.check_indexed_block_target(self, 'hull') def test_block_targets_inactive(self): ct.check_block_targets_inactive(self, 'hull') def test_block_only_targets_transformed(self): ct.check_block_only_targets_transformed(self, 'hull') def test_create_using(self): m = models.makeTwoTermDisjOnBlock() ct.diff_apply_to_and_create_using(self, m, 'gdp.hull') class DisjOnBlock(unittest.TestCase, CommonTests): # when the disjunction is on a block, we want all of the stuff created by # the transformation to go on that block also so that solving the block # maintains its meaning def test_xor_constraint_added(self): ct.check_xor_constraint_added(self, 'hull') def test_trans_block_created(self): ct.check_trans_block_created(self, 'hull') class TestErrors(unittest.TestCase): def setUp(self): # set seed so we can test name collisions predictably random.seed(666) def test_ask_for_transformed_constraint_from_untransformed_disjunct(self): ct.check_ask_for_transformed_constraint_from_untransformed_disjunct( self, 'hull') def test_silly_target(self): ct.check_silly_target(self, 'hull') def test_retrieving_nondisjunctive_components(self): ct.check_retrieving_nondisjunctive_components(self, 'hull') def test_transform_empty_disjunction(self): ct.check_transform_empty_disjunction(self, 'hull') def test_deactivated_disjunct_nonzero_indicator_var(self): ct.check_deactivated_disjunct_nonzero_indicator_var(self, 'hull') def test_deactivated_disjunct_unfixed_indicator_var(self): ct.check_deactivated_disjunct_unfixed_indicator_var(self, 'hull') def test_infeasible_xor_because_all_disjuncts_deactivated(self): m = ct.setup_infeasible_xor_because_all_disjuncts_deactivated(self, 'hull') hull = TransformationFactory('gdp.hull') transBlock = m.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance(transBlock, Block) self.assertEqual(len(transBlock.relaxedDisjuncts), 2) self.assertIsInstance(transBlock.component("disjunction_xor"), Constraint) disjunct1 = transBlock.relaxedDisjuncts[0] # we disaggregated the (deactivated) indicator variables d3_ind = m.disjunction_disjuncts[0].nestedDisjunction_disjuncts[0].\ indicator_var d4_ind = m.disjunction_disjuncts[0].nestedDisjunction_disjuncts[1].\ indicator_var self.assertIs(hull.get_disaggregated_var(d3_ind, m.disjunction_disjuncts[0]), disjunct1.disaggregatedVars.indicator_var) self.assertIs(hull.get_src_var( disjunct1.disaggregatedVars.indicator_var), d3_ind) self.assertIs(hull.get_disaggregated_var(d4_ind, m.disjunction_disjuncts[0]), disjunct1.disaggregatedVars.indicator_var_4) self.assertIs(hull.get_src_var( disjunct1.disaggregatedVars.indicator_var_4), d4_ind) relaxed_xor = disjunct1.component( "disjunction_disjuncts[0]._pyomo_gdp_hull_reformulation." "disjunction_disjuncts[0].nestedDisjunction_xor") self.assertIsInstance(relaxed_xor, Constraint) self.assertEqual(len(relaxed_xor), 1) repn = generate_standard_repn(relaxed_xor['eq'].body) self.assertEqual(relaxed_xor['eq'].lower, 0) self.assertEqual(relaxed_xor['eq'].upper, 0) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 3) # constraint says that the disaggregated indicator variables of the # nested disjuncts sum to the indicator variable of the outer disjunct. ct.check_linear_coef( self, repn, m.disjunction.disjuncts[0].indicator_var, -1) ct.check_linear_coef(self, repn, disjunct1.disaggregatedVars.indicator_var, 1) ct.check_linear_coef(self, repn, disjunct1.disaggregatedVars.indicator_var_4, 1) self.assertEqual(repn.constant, 0) # but the disaggregation constraints are going to force them to 0 (which # will in turn force the outer disjunct indicator variable to 0, which # is what we want) d3_ind_dis = transBlock.disaggregationConstraints[1, None] self.assertEqual(d3_ind_dis.lower, 0) self.assertEqual(d3_ind_dis.upper, 0) repn = generate_standard_repn(d3_ind_dis.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 2) self.assertEqual(repn.constant, 0) ct.check_linear_coef(self, repn, disjunct1.disaggregatedVars.indicator_var, -1) ct.check_linear_coef(self, repn, transBlock.relaxedDisjuncts[1].disaggregatedVars.\ indicator_var, -1) d4_ind_dis = transBlock.disaggregationConstraints[2, None] self.assertEqual(d4_ind_dis.lower, 0) self.assertEqual(d4_ind_dis.upper, 0) repn = generate_standard_repn(d4_ind_dis.body) self.assertTrue(repn.is_linear()) self.assertEqual(len(repn.linear_vars), 2) self.assertEqual(repn.constant, 0) ct.check_linear_coef(self, repn, disjunct1.disaggregatedVars.indicator_var_4, -1) ct.check_linear_coef(self, repn, transBlock.relaxedDisjuncts[1].disaggregatedVars.\ indicator_var_9, -1) def test_mapping_method_errors(self): m = models.makeTwoTermDisj_Nonlinear() hull = TransformationFactory('gdp.hull') hull.apply_to(m) log = StringIO() with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): self.assertRaisesRegexp( AttributeError, "'NoneType' object has no attribute '_bigMConstraintMap'", hull.get_var_bounds_constraint, m.w) self.assertRegexpMatches( log.getvalue(), ".*Either 'w' is not a disaggregated variable, " "or the disjunction that disaggregates it has " "not been properly transformed.") log = StringIO() with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): self.assertRaisesRegexp( KeyError, ".*_pyomo_gdp_hull_reformulation.relaxedDisjuncts\[1\]." "disaggregatedVars.w", hull.get_disaggregation_constraint, m.d[1].transformation_block().disaggregatedVars.w, m.disjunction) self.assertRegexpMatches(log.getvalue(), ".*It doesn't appear that " "'_pyomo_gdp_hull_reformulation." "relaxedDisjuncts\[1\].disaggregatedVars.w' " "is a variable that was disaggregated by " "Disjunction 'disjunction'") log = StringIO() with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): self.assertRaisesRegexp( AttributeError, "'NoneType' object has no attribute '_disaggregatedVarMap'", hull.get_src_var, m.w) self.assertRegexpMatches( log.getvalue(), ".*'w' does not appear to be a disaggregated variable") log = StringIO() with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): self.assertRaisesRegexp( KeyError, ".*_pyomo_gdp_hull_reformulation.relaxedDisjuncts\[1\]." "disaggregatedVars.w", hull.get_disaggregated_var, m.d[1].transformation_block().disaggregatedVars.w, m.d[1]) self.assertRegexpMatches(log.getvalue(), ".*It does not appear " "'_pyomo_gdp_hull_reformulation." "relaxedDisjuncts\[1\].disaggregatedVars.w' " "is a variable which appears in disjunct " "'d\[1\]'") m.random_disjunction = Disjunction(expr=[m.w == 2, m.w >= 7]) self.assertRaisesRegexp( GDP_Error, "Disjunction 'random_disjunction' has not been properly " "transformed: None of its disjuncts are transformed.", hull.get_disaggregation_constraint, m.w, m.random_disjunction) self.assertRaisesRegexp( GDP_Error, "Disjunct 'random_disjunction_disjuncts\[0\]' has not been " "transformed", hull.get_disaggregated_var, m.w, m.random_disjunction.disjuncts[0]) class InnerDisjunctionSharedDisjuncts(unittest.TestCase): def test_activeInnerDisjunction_err(self): ct.check_activeInnerDisjunction_err(self, 'hull') class BlocksOnDisjuncts(unittest.TestCase): def setUp(self): # set seed so we can test name collisions predictably random.seed(666) def makeModel(self): # I'm going to multi-task and also check some types of constraints # whose expressions need to be tested m = ConcreteModel() m.x = Var(bounds=(1, 5)) m.y = Var(bounds=(0, 9)) m.disj1 = Disjunct() m.disj1.add_component("b.any_index", Constraint(expr=m.x >= 1.5)) m.disj1.b = Block() m.disj1.b.any_index = Constraint(Any) m.disj1.b.any_index['local'] = m.x <= 2 m.disj1.b.LocalVars = Suffix(direction=Suffix.LOCAL) m.disj1.b.LocalVars[m.disj1] = [m.x] m.disj1.b.any_index['nonlin-ub'] = m.y**2 <= 4 m.disj2 = Disjunct() m.disj2.non_lin_lb = Constraint(expr=log(1 + m.y) >= 1) m.disjunction = Disjunction(expr=[m.disj1, m.disj2]) return m def test_transformed_constraint_name_conflict(self): m = self.makeModel() hull = TransformationFactory('gdp.hull') hull.apply_to(m) transBlock = m.disj1.transformation_block() self.assertIsInstance(transBlock.component("disj1.b.any_index"), Constraint) self.assertIsInstance(transBlock.component("disj1.b.any_index_4"), Constraint) xformed = hull.get_transformed_constraints( m.disj1.component("b.any_index")) self.assertEqual(len(xformed), 1) self.assertIs(xformed[0], transBlock.component("disj1.b.any_index")['lb']) xformed = hull.get_transformed_constraints(m.disj1.b.any_index['local']) self.assertEqual(len(xformed), 1) self.assertIs(xformed[0], transBlock.component("disj1.b.any_index_4")[ ('local','ub')]) xformed = hull.get_transformed_constraints( m.disj1.b.any_index['nonlin-ub']) self.assertEqual(len(xformed), 1) self.assertIs(xformed[0], transBlock.component("disj1.b.any_index_4")[ ('nonlin-ub','ub')]) def test_local_var_handled_correctly(self): m = self.makeModel() hull = TransformationFactory('gdp.hull') hull.apply_to(m) # test the local variable was handled correctly. self.assertIs(hull.get_disaggregated_var(m.x, m.disj1), m.x) self.assertEqual(m.x.lb, 0) self.assertEqual(m.x.ub, 5) self.assertIsNone(m.disj1.transformation_block().disaggregatedVars.\ component("x")) self.assertIsInstance(m.disj1.transformation_block().disaggregatedVars.\ component("y"), Var) # this doesn't require the block, I'm just coopting this test to make sure # of some nonlinear expressions. def test_transformed_constraints(self): m = self.makeModel() hull = TransformationFactory('gdp.hull') hull.apply_to(m) # test the transformed nonlinear constraints nonlin_ub_list = hull.get_transformed_constraints( m.disj1.b.any_index['nonlin-ub']) self.assertEqual(len(nonlin_ub_list), 1) cons = nonlin_ub_list[0] self.assertEqual(cons.index(), ('nonlin-ub', 'ub')) self.assertIs(cons.ctype, Constraint) self.assertIsNone(cons.lower) self.assertEqual(value(cons.upper), 0) repn = generate_standard_repn(cons.body) self.assertEqual(str(repn.nonlinear_expr), "(0.9999*disj1.indicator_var + 0.0001)*" "(_pyomo_gdp_hull_reformulation.relaxedDisjuncts[0]." "disaggregatedVars.y/" "(0.9999*disj1.indicator_var + 0.0001))**2") self.assertEqual(len(repn.nonlinear_vars), 2) self.assertIs(repn.nonlinear_vars[0], m.disj1.indicator_var) self.assertIs(repn.nonlinear_vars[1], hull.get_disaggregated_var(m.y, m.disj1)) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 1) self.assertIs(repn.linear_vars[0], m.disj1.indicator_var) self.assertEqual(repn.linear_coefs[0], -4) nonlin_lb_list = hull.get_transformed_constraints(m.disj2.non_lin_lb) self.assertEqual(len(nonlin_lb_list), 1) cons = nonlin_lb_list[0] self.assertEqual(cons.index(), 'lb') self.assertIs(cons.ctype, Constraint) self.assertIsNone(cons.lower) self.assertEqual(value(cons.upper), 0) repn = generate_standard_repn(cons.body) self.assertEqual(str(repn.nonlinear_expr), "- ((0.9999*disj2.indicator_var + 0.0001)*" "log(1 + " "_pyomo_gdp_hull_reformulation.relaxedDisjuncts[1]." "disaggregatedVars.y/" "(0.9999*disj2.indicator_var + 0.0001)))") self.assertEqual(len(repn.nonlinear_vars), 2) self.assertIs(repn.nonlinear_vars[0], m.disj2.indicator_var) self.assertIs(repn.nonlinear_vars[1], hull.get_disaggregated_var(m.y, m.disj2)) self.assertEqual(repn.constant, 0) self.assertEqual(len(repn.linear_vars), 1) self.assertIs(repn.linear_vars[0], m.disj2.indicator_var) self.assertEqual(repn.linear_coefs[0], 1) class DisaggregatingFixedVars(unittest.TestCase): def test_disaggregate_fixed_variables(self): m = models.makeTwoTermDisj() m.x.fix(6) hull = TransformationFactory('gdp.hull') hull.apply_to(m) # check that we did indeed disaggregate x transBlock = m.d[1]._transformation_block() self.assertIsInstance(transBlock.disaggregatedVars.component("x"), Var) self.assertIs(hull.get_disaggregated_var(m.x, m.d[1]), transBlock.disaggregatedVars.x) self.assertIs(hull.get_src_var(transBlock.disaggregatedVars.x), m.x) def test_do_not_disaggregate_fixed_variables(self): m = models.makeTwoTermDisj() m.x.fix(6) hull = TransformationFactory('gdp.hull') hull.apply_to(m, assume_fixed_vars_permanent=True) # check that we didn't disaggregate x transBlock = m.d[1]._transformation_block() self.assertIsNone(transBlock.disaggregatedVars.component("x")) class NameDeprecationTest(unittest.TestCase): def test_name_deprecated(self): m = models.makeTwoTermDisj() output = StringIO() with LoggingIntercept(output, 'pyomo.gdp', logging.WARNING): TransformationFactory('gdp.chull').apply_to(m) self.assertIn("DEPRECATED: The 'gdp.chull' name is deprecated. " "Please use the more apt 'gdp.hull' instead.", output.getvalue().replace('\n', ' ')) def test_hull_chull_equivalent(self): m = models.makeTwoTermDisj() out1 = StringIO() out2 = StringIO() m1 = TransformationFactory('gdp.hull').create_using(m) m2 = TransformationFactory('gdp.chull').create_using(m) m1.pprint(ostream=out1) m2.pprint(ostream=out2) self.assertMultiLineEqual(out1.getvalue(), out2.getvalue())