prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Appropriate behavior for activate() and deactivate() for nested disjunctions?
  ```
  from pyomo.environ import *
  m = ConcreteModel()
  m.d1 = Disjunct()
  m.d2 = Disjunct()
  m.d1.sub1 = Disjunct()
  m.d1.sub2 = Disjunct()
  m.d1.disj = Disjunction(expr=[m.d1.sub1, m.d1.sub2])
  m.disj = Disjunction(expr=[m.d1, m.d2])
  ```
  
  What should happen when disjuncts containing nested disjunctions are deactivated? `m.d1.deactivate()`.
  The reclassifier hack complains about `m.d1.sub1` and `m.d1.sub2` not being expanded.
  
  </issue>
  <code>
  [start of pyomo/gdp/plugins/gdp_var_mover.py]
  1 #  ___________________________________________________________________________
  2 #
  3 #  Pyomo: Python Optimization Modeling Objects
  4 #  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
  5 #  Under the terms of Contract DE-NA0003525 with National Technology and
  6 #  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
  7 #  rights in this software.
  8 #  This software is distributed under the 3-clause BSD License.
  9 #  ___________________________________________________________________________
  10 
  11 """Collection of GDP-related hacks.
  12 
  13 Hacks for dealing with the fact that solver writers may sometimes fail to
  14 detect variables inside of Disjuncts or deactivated Blocks.
  15 """
  16 
  17 import logging
  18 import textwrap
  19 from pyomo.common.plugin import alias
  20 from pyomo.core.base import Transformation, Block, Constraint
  21 from pyomo.gdp import Disjunct
  22 
  23 from six import itervalues
  24 
  25 logger = logging.getLogger('pyomo.gdp')
  26 
  27 
  28 class HACK_GDP_Var_Mover(Transformation):
  29     """Move indicator vars to top block.
  30 
  31     HACK: this will move all indicator variables on the model to the top block
  32     so the writers can find them.
  33 
  34     """
  35 
  36     alias('gdp.varmover', doc=textwrap.fill(textwrap.dedent(__doc__.strip())))
  37 
  38     def _apply_to(self, instance, **kwds):
  39         assert not kwds
  40         count = 0
  41         disjunct_generator = instance.component_data_objects(
  42             Disjunct, descend_into=(Block, Disjunct))
  43         for disjunct in disjunct_generator:
  44             count += 1
  45             var = disjunct.indicator_var
  46             var.doc = "%s(Moved from %s)" % (
  47                 var.doc + " " if var.doc else "", var.name, )
  48             disjunct.del_component(var)
  49             instance.add_component("_gdp_moved_IV_%s" % (count,), var)
  50 
  51 
  52 class HACK_GDP_Disjunct_Reclassifier(Transformation):
  53     """Reclassify Disjuncts to Blocks.
  54 
  55     HACK: this will reclassify all Disjuncts to Blocks so the current writers
  56     can find the variables
  57 
  58     """
  59 
  60     alias('gdp.reclassify',
  61           doc=textwrap.fill(textwrap.dedent(__doc__.strip())))
  62 
  63     def _apply_to(self, instance, **kwds):
  64         assert not kwds
  65         disjunct_generator = instance.component_objects(
  66             Disjunct, descend_into=(Block, Disjunct))
  67         for disjunct_component in disjunct_generator:
  68             for disjunct in itervalues(disjunct_component._data):
  69                 if disjunct.active:
  70                     logger.error("""
  71                     Reclassifying active Disjunct "%s" as a Block.  This
  72                     is generally an error as it indicates that the model
  73                     was not completely relaxed before applying the
  74                     gdp.reclassify transformation""" % (disjunct.name,))
  75 
  76             # Reclassify this disjunct as a block
  77             disjunct_component.parent_block().reclassify_component_type(
  78                 disjunct_component, Block)
  79             disjunct_component._activate_without_unfixing_indicator()
  80 
  81             # Deactivate all constraints.  Note that we only need to
  82             # descend into blocks: we will catch disjuncts in the outer
  83             # loop.
  84             #
  85             # Note that we defer this until AFTER we reactivate the
  86             # block, as the component_objects generator will not
  87             # return anything when active=True and the block is
  88             # deactivated.
  89             for disjunct in itervalues(disjunct_component._data):
  90                 cons_in_disjunct = disjunct.component_objects(
  91                     Constraint, descend_into=Block, active=True)
  92                 for con in cons_in_disjunct:
  93                     con.deactivate()
  94
  [end of pyomo/gdp/plugins/gdp_var_mover.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/pyomo/gdp/plugins/gdp_var_mover.py b/pyomo/gdp/plugins/gdp_var_mover.py
  --- a/pyomo/gdp/plugins/gdp_var_mover.py
  +++ b/pyomo/gdp/plugins/gdp_var_mover.py
  @@ -18,7 +18,9 @@
   import textwrap
   from pyomo.common.plugin import alias
   from pyomo.core.base import Transformation, Block, Constraint
  -from pyomo.gdp import Disjunct
  +from pyomo.gdp import Disjunct, GDP_Error
  +from pyomo.core import TraversalStrategy
  +from pyomo.common.deprecation import deprecated
   
   from six import itervalues
   
  @@ -35,6 +37,8 @@
   
       alias('gdp.varmover', doc=textwrap.fill(textwrap.dedent(__doc__.strip())))
   
  +    @deprecated(msg="The gdp.varmover transformation has been deprecated in "
  +                "favor of the gdp.reclassify transformation.")
       def _apply_to(self, instance, **kwds):
           assert not kwds
           count = 0
  @@ -61,13 +65,19 @@
             doc=textwrap.fill(textwrap.dedent(__doc__.strip())))
   
       def _apply_to(self, instance, **kwds):
  -        assert not kwds
  +        assert not kwds  # no keywords expected to the transformation
           disjunct_generator = instance.component_objects(
  -            Disjunct, descend_into=(Block, Disjunct))
  +            Disjunct, descend_into=(Block, Disjunct),
  +            descent_order=TraversalStrategy.PostfixDFS)
           for disjunct_component in disjunct_generator:
  +            # Check that the disjuncts being reclassified are all relaxed or
  +            # are not on an active block.
               for disjunct in itervalues(disjunct_component._data):
  -                if disjunct.active:
  -                    logger.error("""
  +                if (disjunct.active and
  +                        self._disjunct_not_relaxed(disjunct) and
  +                        self._disjunct_on_active_block(disjunct) and
  +                        self._disjunct_not_fixed_true(disjunct)):
  +                    raise GDP_Error("""
                       Reclassifying active Disjunct "%s" as a Block.  This
                       is generally an error as it indicates that the model
                       was not completely relaxed before applying the
  @@ -91,3 +101,31 @@
                       Constraint, descend_into=Block, active=True)
                   for con in cons_in_disjunct:
                       con.deactivate()
  +
  +    def _disjunct_not_fixed_true(self, disjunct):
  +        # Return true if the disjunct indicator variable is not fixed to True
  +        return not (disjunct.indicator_var.fixed and
  +                    disjunct.indicator_var.value == 1)
  +
  +    def _disjunct_not_relaxed(self, disjunct):
  +        # Return True if the disjunct was not relaxed by a transformation.
  +        return not getattr(
  +            disjunct, '_gdp_transformation_info', {}).get('relaxed', False)
  +
  +    def _disjunct_on_active_block(self, disjunct):
  +        # Check first to make sure that the disjunct is not a
  +        # descendent of an inactive Block or fixed and deactivated
  +        # Disjunct, before raising a warning.
  +        parent_block = disjunct.parent_block()
  +        while parent_block is not None:
  +            if parent_block.type() is Block and not parent_block.active:
  +                return False
  +            elif (parent_block.type() is Disjunct and not parent_block.active
  +                  and parent_block.indicator_var.value == 0
  +                  and parent_block.indicator_var.fixed):
  +                return False
  +            else:
  +                # Step up one level in the hierarchy
  +                parent_block = parent_block.parent_block()
  +                continue
  +        return True
  
verification_info: {"golden_diff": "diff --git a/pyomo/gdp/plugins/gdp_var_mover.py b/pyomo/gdp/plugins/gdp_var_mover.py\n--- a/pyomo/gdp/plugins/gdp_var_mover.py\n+++ b/pyomo/gdp/plugins/gdp_var_mover.py\n@@ -18,7 +18,9 @@\n import textwrap\n from pyomo.common.plugin import alias\n from pyomo.core.base import Transformation, Block, Constraint\n-from pyomo.gdp import Disjunct\n+from pyomo.gdp import Disjunct, GDP_Error\n+from pyomo.core import TraversalStrategy\n+from pyomo.common.deprecation import deprecated\n \n from six import itervalues\n \n@@ -35,6 +37,8 @@\n \n     alias('gdp.varmover', doc=textwrap.fill(textwrap.dedent(__doc__.strip())))\n \n+    @deprecated(msg=\"The gdp.varmover transformation has been deprecated in \"\n+                \"favor of the gdp.reclassify transformation.\")\n     def _apply_to(self, instance, **kwds):\n         assert not kwds\n         count = 0\n@@ -61,13 +65,19 @@\n           doc=textwrap.fill(textwrap.dedent(__doc__.strip())))\n \n     def _apply_to(self, instance, **kwds):\n-        assert not kwds\n+        assert not kwds  # no keywords expected to the transformation\n         disjunct_generator = instance.component_objects(\n-            Disjunct, descend_into=(Block, Disjunct))\n+            Disjunct, descend_into=(Block, Disjunct),\n+            descent_order=TraversalStrategy.PostfixDFS)\n         for disjunct_component in disjunct_generator:\n+            # Check that the disjuncts being reclassified are all relaxed or\n+            # are not on an active block.\n             for disjunct in itervalues(disjunct_component._data):\n-                if disjunct.active:\n-                    logger.error(\"\"\"\n+                if (disjunct.active and\n+                        self._disjunct_not_relaxed(disjunct) and\n+                        self._disjunct_on_active_block(disjunct) and\n+                        self._disjunct_not_fixed_true(disjunct)):\n+                    raise GDP_Error(\"\"\"\n                     Reclassifying active Disjunct \"%s\" as a Block.  This\n                     is generally an error as it indicates that the model\n                     was not completely relaxed before applying the\n@@ -91,3 +101,31 @@\n                     Constraint, descend_into=Block, active=True)\n                 for con in cons_in_disjunct:\n                     con.deactivate()\n+\n+    def _disjunct_not_fixed_true(self, disjunct):\n+        # Return true if the disjunct indicator variable is not fixed to True\n+        return not (disjunct.indicator_var.fixed and\n+                    disjunct.indicator_var.value == 1)\n+\n+    def _disjunct_not_relaxed(self, disjunct):\n+        # Return True if the disjunct was not relaxed by a transformation.\n+        return not getattr(\n+            disjunct, '_gdp_transformation_info', {}).get('relaxed', False)\n+\n+    def _disjunct_on_active_block(self, disjunct):\n+        # Check first to make sure that the disjunct is not a\n+        # descendent of an inactive Block or fixed and deactivated\n+        # Disjunct, before raising a warning.\n+        parent_block = disjunct.parent_block()\n+        while parent_block is not None:\n+            if parent_block.type() is Block and not parent_block.active:\n+                return False\n+            elif (parent_block.type() is Disjunct and not parent_block.active\n+                  and parent_block.indicator_var.value == 0\n+                  and parent_block.indicator_var.fixed):\n+                return False\n+            else:\n+                # Step up one level in the hierarchy\n+                parent_block = parent_block.parent_block()\n+                continue\n+        return True\n", "issue": "Appropriate behavior for activate() and deactivate() for nested disjunctions?\n```\r\nfrom pyomo.environ import *\r\nm = ConcreteModel()\r\nm.d1 = Disjunct()\r\nm.d2 = Disjunct()\r\nm.d1.sub1 = Disjunct()\r\nm.d1.sub2 = Disjunct()\r\nm.d1.disj = Disjunction(expr=[m.d1.sub1, m.d1.sub2])\r\nm.disj = Disjunction(expr=[m.d1, m.d2])\r\n```\r\n\r\nWhat should happen when disjuncts containing nested disjunctions are deactivated? `m.d1.deactivate()`.\r\nThe reclassifier hack complains about `m.d1.sub1` and `m.d1.sub2` not being expanded.\n", "before_files": [{"content": "#  ___________________________________________________________________________\n#\n#  Pyomo: Python Optimization Modeling Objects\n#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC\n#  Under the terms of Contract DE-NA0003525 with National Technology and\n#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain\n#  rights in this software.\n#  This software is distributed under the 3-clause BSD License.\n#  ___________________________________________________________________________\n\n\"\"\"Collection of GDP-related hacks.\n\nHacks for dealing with the fact that solver writers may sometimes fail to\ndetect variables inside of Disjuncts or deactivated Blocks.\n\"\"\"\n\nimport logging\nimport textwrap\nfrom pyomo.common.plugin import alias\nfrom pyomo.core.base import Transformation, Block, Constraint\nfrom pyomo.gdp import Disjunct\n\nfrom six import itervalues\n\nlogger = logging.getLogger('pyomo.gdp')\n\n\nclass HACK_GDP_Var_Mover(Transformation):\n    \"\"\"Move indicator vars to top block.\n\n    HACK: this will move all indicator variables on the model to the top block\n    so the writers can find them.\n\n    \"\"\"\n\n    alias('gdp.varmover', doc=textwrap.fill(textwrap.dedent(__doc__.strip())))\n\n    def _apply_to(self, instance, **kwds):\n        assert not kwds\n        count = 0\n        disjunct_generator = instance.component_data_objects(\n            Disjunct, descend_into=(Block, Disjunct))\n        for disjunct in disjunct_generator:\n            count += 1\n            var = disjunct.indicator_var\n            var.doc = \"%s(Moved from %s)\" % (\n                var.doc + \" \" if var.doc else \"\", var.name, )\n            disjunct.del_component(var)\n            instance.add_component(\"_gdp_moved_IV_%s\" % (count,), var)\n\n\nclass HACK_GDP_Disjunct_Reclassifier(Transformation):\n    \"\"\"Reclassify Disjuncts to Blocks.\n\n    HACK: this will reclassify all Disjuncts to Blocks so the current writers\n    can find the variables\n\n    \"\"\"\n\n    alias('gdp.reclassify',\n          doc=textwrap.fill(textwrap.dedent(__doc__.strip())))\n\n    def _apply_to(self, instance, **kwds):\n        assert not kwds\n        disjunct_generator = instance.component_objects(\n            Disjunct, descend_into=(Block, Disjunct))\n        for disjunct_component in disjunct_generator:\n            for disjunct in itervalues(disjunct_component._data):\n                if disjunct.active:\n                    logger.error(\"\"\"\n                    Reclassifying active Disjunct \"%s\" as a Block.  This\n                    is generally an error as it indicates that the model\n                    was not completely relaxed before applying the\n                    gdp.reclassify transformation\"\"\" % (disjunct.name,))\n\n            # Reclassify this disjunct as a block\n            disjunct_component.parent_block().reclassify_component_type(\n                disjunct_component, Block)\n            disjunct_component._activate_without_unfixing_indicator()\n\n            # Deactivate all constraints.  Note that we only need to\n            # descend into blocks: we will catch disjuncts in the outer\n            # loop.\n            #\n            # Note that we defer this until AFTER we reactivate the\n            # block, as the component_objects generator will not\n            # return anything when active=True and the block is\n            # deactivated.\n            for disjunct in itervalues(disjunct_component._data):\n                cons_in_disjunct = disjunct.component_objects(\n                    Constraint, descend_into=Block, active=True)\n                for con in cons_in_disjunct:\n                    con.deactivate()\n", "path": "pyomo/gdp/plugins/gdp_var_mover.py"}]}