#  ___________________________________________________________________________
#
#  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())
