Repository: Pyomo/pyomo

Issue:
Pyomo.DAE transformations looping through blocked-hierarchical models incorrectly
This representative example from an IDAES unit model throws an AttributeError:

```
from pyomo.environ import *
from pyomo.dae import *

m = ConcreteModel()
m.t = ContinuousSet(bounds=(0,10))

m.CO1 = Block()
m.CO1.holdup = Block()
def _prop_in_rule(b, t):
    b.pressure = Var()
    return b
m.CO1.holdup.properties_in = Block(m.t, rule=_prop_in_rule)

def _prop_out_rule(b, t):
    b.pressure = Var()
    return b
m.CO1.holdup.properties_out = Block(m.t, rule=_prop_out_rule)

m.CO1.ratioP = Var(m.t)

def _ratio_rule(b, t):
    return b.ratioP[t] <= b.holdup.properties_in[t].pressure - b.holdup.properties_out[t].pressure 
m.CO1.eq_ratio_P = Constraint(m.t, rule=_ratio_rule)

disc = TransformationFactory('dae.finite_difference')
disc.apply_to(m, nfe=2)
```

```
$ python -i temp.py
Traceback (most recent call last):
  File "temp.py", line 26, in <module>
    disc.apply_to(m, nfe=2)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/plugin.py", line 334, in apply_to
    self._apply_to(model, **kwds)
  File "/home/blnicho/Research/pyomo/pyomo/dae/plugins/finitedifference.py", line 187, in _apply_to
    self._transformBlock(block, currentds)
  File "/home/blnicho/Research/pyomo/pyomo/dae/plugins/finitedifference.py", line 218, in _transformBlock
    update_contset_indexed_component(c)
  File "/home/blnicho/Research/pyomo/pyomo/dae/misc.py", line 145, in update_contset_indexed_component
    _update_constraint(comp)
  File "/home/blnicho/Research/pyomo/pyomo/dae/misc.py", line 189, in _update_constraint
    con.add(i, apply_indexed_rule(con, _rule, _parent, i))
  File "/home/blnicho/Research/pyomo/pyomo/core/base/misc.py", line 61, in apply_indexed_rule
    return rule(model, index)
  File "temp.py", line 22, in _ratio_rule
    return b.ratioP[t] <= b.holdup.properties_in[t].pressure - b.holdup.properties_out[t].pressure 
  File "/home/blnicho/Research/pyomo/pyomo/core/base/block.py", line 521, in __getattr__
    % (self.__class__.__name__, val))
AttributeError: '_BlockData' object has no attribute 'pressure'
>>> 
``` 
Pyomo.DAE transformations looping through blocked-hierarchical models incorrectly
This representative example from an IDAES unit model throws an AttributeError:

```
from pyomo.environ import *
from pyomo.dae import *

m = ConcreteModel()
m.t = ContinuousSet(bounds=(0,10))

m.CO1 = Block()
m.CO1.holdup = Block()
def _prop_in_rule(b, t):
    b.pressure = Var()
    return b
m.CO1.holdup.properties_in = Block(m.t, rule=_prop_in_rule)

def _prop_out_rule(b, t):
    b.pressure = Var()
    return b
m.CO1.holdup.properties_out = Block(m.t, rule=_prop_out_rule)

m.CO1.ratioP = Var(m.t)

def _ratio_rule(b, t):
    return b.ratioP[t] <= b.holdup.properties_in[t].pressure - b.holdup.properties_out[t].pressure 
m.CO1.eq_ratio_P = Constraint(m.t, rule=_ratio_rule)

disc = TransformationFactory('dae.finite_difference')
disc.apply_to(m, nfe=2)
```

```
$ python -i temp.py
Traceback (most recent call last):
  File "temp.py", line 26, in <module>
    disc.apply_to(m, nfe=2)
  File "/home/blnicho/Research/pyomo/pyomo/core/base/plugin.py", line 334, in apply_to
    self._apply_to(model, **kwds)
  File "/home/blnicho/Research/pyomo/pyomo/dae/plugins/finitedifference.py", line 187, in _apply_to
    self._transformBlock(block, currentds)
  File "/home/blnicho/Research/pyomo/pyomo/dae/plugins/finitedifference.py", line 218, in _transformBlock
    update_contset_indexed_component(c)
  File "/home/blnicho/Research/pyomo/pyomo/dae/misc.py", line 145, in update_contset_indexed_component
    _update_constraint(comp)
  File "/home/blnicho/Research/pyomo/pyomo/dae/misc.py", line 189, in _update_constraint
    con.add(i, apply_indexed_rule(con, _rule, _parent, i))
  File "/home/blnicho/Research/pyomo/pyomo/core/base/misc.py", line 61, in apply_indexed_rule
    return rule(model, index)
  File "temp.py", line 22, in _ratio_rule
    return b.ratioP[t] <= b.holdup.properties_in[t].pressure - b.holdup.properties_out[t].pressure 
  File "/home/blnicho/Research/pyomo/pyomo/core/base/block.py", line 521, in __getattr__
    % (self.__class__.__name__, val))
AttributeError: '_BlockData' object has no attribute 'pressure'
>>> 
``` 


Discussion:
I think the real problem here is that we only store the component declaration order for a particular Block and not the overall declaration order for all components at all levels on a model (as far as I can tell). This makes it challenging to come up with a general way to expand block-hierarchical models in the discretization transformations because there could be a very specific order that components were added to different Blocks to ensure that dependencies are satisfied (ex. constraints involving variables at different Block levels) and this order is not recorded anywhere. This problem is exacerbated by the fact that Blocks themselves can be indexed by a ContinuousSet. 

I talked with @whart222 and he didn't see a solution for this. He suggested we start a broader discussion with the rest of the Pyomo developers on the issue of circular dependencies.

@jsiirola any ideas on how to get around this?
I think the real problem here is that we only store the component declaration order for a particular Block and not the overall declaration order for all components at all levels on a model (as far as I can tell). This makes it challenging to come up with a general way to expand block-hierarchical models in the discretization transformations because there could be a very specific order that components were added to different Blocks to ensure that dependencies are satisfied (ex. constraints involving variables at different Block levels) and this order is not recorded anywhere. This problem is exacerbated by the fact that Blocks themselves can be indexed by a ContinuousSet. 

I talked with @whart222 and he didn't see a solution for this. He suggested we start a broader discussion with the rest of the Pyomo developers on the issue of circular dependencies.

@jsiirola any ideas on how to get around this?

Resolving patch:
diff --git a/pyomo/dae/misc.py b/pyomo/dae/misc.py
--- a/pyomo/dae/misc.py
+++ b/pyomo/dae/misc.py
@@ -10,15 +10,19 @@
 
 import logging
 
-from pyomo.core import *
+from pyomo.core import Suffix, Var, Constraint, Piecewise, Block
+from pyomo.core import Expression, Param
 from pyomo.core.base.indexed_component import IndexedComponent
 from pyomo.core.base.misc import apply_indexed_rule
 from pyomo.core.base.block import _BlockData, IndexedBlock
-from pyomo.dae import *
+from pyomo.dae import ContinuousSet, DerivativeVar, DAE_Error
+from pyomo.core.kernel.component_map import ComponentMap
+from pyomo.core.base.block import SortComponents
+from pyomo.util.log import LoggingIntercept
 
-from six import iterkeys, itervalues, iteritems
+from six import iterkeys, itervalues, iteritems, StringIO
 
-logger = logging.getLogger('pyomo.core')
+logger = logging.getLogger('pyomo.dae')
 
 
 def generate_finite_elements(ds, nfe):
@@ -99,10 +103,85 @@ def generate_colloc_points(ds, tau):
     ds._sort()
 
 
-def update_contset_indexed_component(comp):
+def expand_components(block):
     """
-    Update any model components which are indexed by a ContinuousSet
-    that has changed
+    Loop over block components and try expanding them. If expansion fails
+    then save the component and try again later. This function has some
+    built-in robustness for block-hierarchical models with circular
+    references but will not work for all cases.
+    """
+
+    # expansion_map is used to map components to the functions used to
+    # expand them so that the update_contset_indexed_component function
+    # logic only has to be called once even in the case where we have to
+    # re-try expanding components due to circular references
+    expansion_map = ComponentMap()
+    redo_expansion = list()
+
+    # Record the missing BlockData before expanding components. This is for
+    # the case where a ContinuousSet indexed Block is used in a Constraint.
+    # If the Constraint is expanded before the Block then the missing
+    # BlockData will be added to the indexed Block but will not be
+    # constructed correctly.
+    for blk in block.component_objects(Block, descend_into=True):
+        missing_idx = set(blk._index) - set(iterkeys(blk._data))
+        if missing_idx:
+            blk._dae_missing_idx = missing_idx
+
+    # Wrap this whole process in a try block in order to ensure that errors
+    # swallowed by the LoggingIntercept context below are re-raised if the
+    # discretization encounters an error it isn't expecting.
+    try:
+
+        # Intercept logging to suppress Error messages arising from failed
+        # constraint rules. These error messages get logged even though the
+        # AttributeError causing the error is caught and handled by this
+        # function when expanding discretized models. We maintain a stream
+        # of the intercepted logging messages which will be printed if an
+        # unexpected exception is raised.
+        buf = StringIO()
+        with LoggingIntercept(buf, 'pyomo.core', logging.ERROR):
+
+            # Identify components that need to be expanded and try expanding
+            # them
+            for c in block.component_objects(descend_into=True,
+                                             sort=SortComponents.declOrder):
+                try:
+                    update_contset_indexed_component(c, expansion_map)
+                except AttributeError:
+                    redo_expansion.append(c)
+
+            # Re-try expansion on any components that failed the first time.
+            #  This is indicative of circular component references and not
+            # expanding components in the correct order the first time
+            # through.
+            N = len(redo_expansion)
+            while N:
+                for i in range(N):
+                    c = redo_expansion.pop()
+                    try:
+                        expansion_map[c](c)
+                    except AttributeError:
+                        redo_expansion.append(c)
+                if len(redo_expansion) == N:
+                    raise DAE_Error("Unable to fully discretize %s. Possible "
+                                    "circular references detected between "
+                                    "components %s. Reformulate your model to"
+                                    " remove circular references or apply a "
+                                    "discretization transformation before "
+                                    "linking blocks together."
+                                    % (block, str(redo_expansion)))
+
+                N = len(redo_expansion)
+
+    except Exception as e:
+        logger.error(buf.getvalue())
+        raise
+
+def update_contset_indexed_component(comp, expansion_map):
+    """
+    Update any model components which are indexed by a ContinuousSet that
+    has changed
     """
 
     # This implemenation will *NOT* check for or update
@@ -140,14 +219,19 @@ def update_contset_indexed_component(comp):
             if isinstance(comp, Var):  # Don't use the type() method here
                 # because we want to catch DerivativeVar components as well
                 # as Var components
+                expansion_map[comp] = _update_var
                 _update_var(comp)
             elif comp.type() == Constraint:
+                expansion_map[comp] = _update_constraint
                 _update_constraint(comp)
             elif comp.type() == Expression:
+                expansion_map[comp] = _update_expression
                 _update_expression(comp)
             elif isinstance(comp, Piecewise):
+                expansion_map[comp] =_update_piecewise
                 _update_piecewise(comp)
-            elif comp.type() == Block: 
+            elif comp.type() == Block:
+                expansion_map[comp] = _update_block
                 _update_block(comp)    
             else:
                 raise TypeError(
@@ -224,10 +308,10 @@ def _update_block(blk):
                                              '__func__',
                                              IndexedBlock.construct):
         # check for custom update function
-        try:
+        if hasattr(blk, 'update_after_discretization'):
             blk.update_after_discretization()
             return
-        except AttributeError:
+        else:
             logger.warning(
                 'DAE(misc): Attempting to apply a discretization '
                 'transformation to the Block-derived component "%s". The '
@@ -240,7 +324,7 @@ def _update_block(blk):
                 'construct()' % blk.name)
 
     # Code taken from the construct() method of Block
-    missing_idx = set(blk._index) - set(iterkeys(blk._data))
+    missing_idx = getattr(blk, '_dae_missing_idx', set([]))
     for idx in list(missing_idx):
         _block = blk[idx]
         obj = apply_indexed_rule(
@@ -257,6 +341,10 @@ def _update_block(blk):
                 if not hasattr(_block, name) and not hasattr(blk, name):
                     super(_BlockData, _block).__setattr__(name, val)
 
+    # Remove book-keeping data after Block is discretized
+    if hasattr(blk, '_dae_missing_idx'):
+        del blk._dae_missing_idx
+
 
 def _update_piecewise(pw):
     """
diff --git a/pyomo/dae/plugins/colloc.py b/pyomo/dae/plugins/colloc.py
--- a/pyomo/dae/plugins/colloc.py
+++ b/pyomo/dae/plugins/colloc.py
@@ -9,7 +9,6 @@
 #  ___________________________________________________________________________
 
 import logging
-from six import itervalues
 from six.moves import xrange
 
 from pyomo.core.base.plugin import alias
@@ -18,7 +17,7 @@
 from pyomo.dae import *
 from pyomo.dae.misc import generate_finite_elements
 from pyomo.dae.misc import generate_colloc_points
-from pyomo.dae.misc import update_contset_indexed_component
+from pyomo.dae.misc import expand_components
 from pyomo.dae.misc import create_partial_expression
 from pyomo.dae.misc import add_discretization_equations
 from pyomo.dae.misc import add_continuity_equations
@@ -407,15 +406,14 @@ def _apply_to(self, instance, **kwds):
         elif self._scheme_name == 'LAGRANGE-LEGENDRE':
             self._get_legendre_constants(currentds)
 
-        for block in instance.block_data_objects(active=True):
-            self._transformBlock(block, currentds)
+        self._transformBlock(instance, currentds)
 
         return instance
 
     def _transformBlock(self, block, currentds):
 
         self._fe = {}
-        for ds in itervalues(block.component_map(ContinuousSet)):
+        for ds in block.component_objects(ContinuousSet, descend_into=True):
             if currentds is None or currentds == ds.name:
                 generate_finite_elements(ds, self._nfe[currentds])
                 if not ds.get_changed():
@@ -440,10 +438,9 @@ def _transformBlock(self, block, currentds):
                 disc_info['afinal'] = self._afinal[currentds]
                 disc_info['scheme'] = self._scheme_name
 
-        for c in itervalues(block.component_map()):
-            update_contset_indexed_component(c)
+        expand_components(block)
 
-        for d in itervalues(block.component_map(DerivativeVar)):
+        for d in block.component_objects(DerivativeVar, descend_into=True):
             dsets = d.get_continuousset_list()
             for i in set(dsets):
                 if currentds is None or i.name == currentds:
@@ -462,25 +459,29 @@ def _transformBlock(self, block, currentds):
                                                         loc)
                     d.set_derivative_expression(newexpr)
                     if self._scheme_name == 'LAGRANGE-LEGENDRE':
-                        add_continuity_equations(block, d, i, loc)
+                        # Add continuity equations to DerivativeVar's parent
+                        #  block
+                        add_continuity_equations(d.parent_block(), d, i, loc)
 
             # Reclassify DerivativeVar if all indexing ContinuousSets have
-            # been discretized
+            # been discretized. Add discretization equations to the
+            # DerivativeVar's parent block.
             if d.is_fully_discretized():
-                add_discretization_equations(block, d)
-                block.reclassify_component_type(d, Var)
+                add_discretization_equations(d.parent_block(), d)
+                d.parent_block().reclassify_component_type(d, Var)
 
         # Reclassify Integrals if all ContinuousSets have been discretized
         if block_fully_discretized(block):
 
             if block.contains_component(Integral):
-                for i in itervalues(block.component_map(Integral)):
+                for i in block.component_objects(Integral, descend_into=True):
                     i.reconstruct()
-                    block.reclassify_component_type(i, Expression)
+                    i.parent_block().reclassify_component_type(i, Expression)
                 # If a model contains integrals they are most likely to appear
                 # in the objective function which will need to be reconstructed
                 # after the model is discretized.
-                for k in itervalues(block.component_map(Objective)):
+                for k in block.component_objects(Objective, descend_into=True):
+                    # TODO: check this, reconstruct might not work
                     k.reconstruct()
 
     def _get_idx(self, l, t, n, i, k):
diff --git a/pyomo/dae/plugins/finitedifference.py b/pyomo/dae/plugins/finitedifference.py
--- a/pyomo/dae/plugins/finitedifference.py
+++ b/pyomo/dae/plugins/finitedifference.py
@@ -9,17 +9,17 @@
 #  ___________________________________________________________________________
 
 import logging
-from six import itervalues
 
 from pyomo.core.base.plugin import alias
 from pyomo.core.base import Transformation
 from pyomo.core import *
 from pyomo.dae import *
 from pyomo.dae.misc import generate_finite_elements
-from pyomo.dae.misc import update_contset_indexed_component
+from pyomo.dae.misc import expand_components
 from pyomo.dae.misc import create_partial_expression
 from pyomo.dae.misc import add_discretization_equations
 from pyomo.dae.misc import block_fully_discretized
+from pyomo.core.base.block import TraversalStrategy
 
 logger = logging.getLogger('pyomo.core')
 
@@ -183,16 +183,15 @@ def _apply_to(self, instance, **kwds):
                              "'BACKWARD', 'CENTRAL', and 'FORWARD'" %
                              tmpscheme)
 
-        for block in instance.block_data_objects(active=True):
-            self._transformBlock(block, currentds)
+        self._transformBlock(instance, currentds)
 
         return instance
 
     def _transformBlock(self, block, currentds):
 
         self._fe = {}
-        for ds in itervalues(block.component_map(ContinuousSet)):
-            if currentds is None or currentds == ds.name:
+        for ds in block.component_objects(ContinuousSet):
+            if currentds is None or currentds == ds.name or currentds is ds:
                 generate_finite_elements(ds, self._nfe[currentds])
                 if not ds.get_changed():
                     if len(ds) - 1 > self._nfe[currentds]:
@@ -213,11 +212,9 @@ def _transformBlock(self, block, currentds):
         # Maybe check to see if any of the ContinuousSets have been changed,
         # if they haven't then the model components need not be updated
         # or even iterated through
+        expand_components(block)
 
-        for c in itervalues(block.component_map()):
-            update_contset_indexed_component(c)
-
-        for d in itervalues(block.component_map(DerivativeVar)):
+        for d in block.component_objects(DerivativeVar, descend_into=True):
             dsets = d.get_continuousset_list()
             for i in set(dsets):
                 if currentds is None or i.name == currentds:
@@ -236,20 +233,22 @@ def _transformBlock(self, block, currentds):
                     d.set_derivative_expression(newexpr)
 
             # Reclassify DerivativeVar if all indexing ContinuousSets have
-            # been discretized
+            # been discretized. Add discretization equations to the
+            # DerivativeVar's parent block.
             if d.is_fully_discretized():
-                add_discretization_equations(block, d)
-                block.reclassify_component_type(d, Var)
+                add_discretization_equations(d.parent_block(), d)
+                d.parent_block().reclassify_component_type(d, Var)
 
         # Reclassify Integrals if all ContinuousSets have been discretized
         if block_fully_discretized(block):
 
             if block.contains_component(Integral):
-                for i in itervalues(block.component_map(Integral)):
+                for i in block.component_objects(Integral, descend_into=True):
                     i.reconstruct()
-                    block.reclassify_component_type(i, Expression)
+                    i.parent_block().reclassify_component_type(i, Expression)
                 # If a model contains integrals they are most likely to
                 # appear in the objective function which will need to be
                 # reconstructed after the model is discretized.
-                for k in itervalues(block.component_map(Objective)):
+                for k in block.component_objects(Objective, descend_into=True):
+                    # TODO: check this, reconstruct might not work
                     k.reconstruct()


Test patch:
diff --git a/pyomo/dae/tests/test_misc.py b/pyomo/dae/tests/test_misc.py
--- a/pyomo/dae/tests/test_misc.py
+++ b/pyomo/dae/tests/test_misc.py
@@ -22,6 +22,7 @@
 from pyomo.util.log import LoggingIntercept
 from pyomo.dae import *
 from pyomo.dae.misc import *
+from pyomo.core.kernel.component_map import ComponentMap
 
 currdir = dirname(abspath(__file__)) + os.sep
 
@@ -191,12 +192,14 @@ def _init(m, i):
         m.v4 = Var(m.s, initialize=7, dense=True)
         m.v5 = Var(m.t2, dense=True)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.v1)
-        update_contset_indexed_component(m.v2)
-        update_contset_indexed_component(m.v3)
-        update_contset_indexed_component(m.v4)
-        update_contset_indexed_component(m.v5)
+        update_contset_indexed_component(m.v1, expansion_map)
+        update_contset_indexed_component(m.v2, expansion_map)
+        update_contset_indexed_component(m.v3, expansion_map)
+        update_contset_indexed_component(m.v4, expansion_map)
+        update_contset_indexed_component(m.v5, expansion_map)
 
         self.assertTrue(len(m.v1) == 6)
         self.assertTrue(len(m.v2) == 6)
@@ -236,12 +239,14 @@ def _init(m, i, j, k):
         m.v4 = Var(m.s, m.t2, initialize=7, dense=True)
         m.v5 = Var(m.s2)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.v1)
-        update_contset_indexed_component(m.v2)
-        update_contset_indexed_component(m.v3)
-        update_contset_indexed_component(m.v4)
-        update_contset_indexed_component(m.v5)
+        update_contset_indexed_component(m.v1, expansion_map)
+        update_contset_indexed_component(m.v2, expansion_map)
+        update_contset_indexed_component(m.v3, expansion_map)
+        update_contset_indexed_component(m.v4, expansion_map)
+        update_contset_indexed_component(m.v5, expansion_map)
 
         self.assertTrue(len(m.v1) == 18)
         self.assertTrue(len(m.v2) == 54)
@@ -280,11 +285,13 @@ def _con2(m):
             return sum(m.v[i] for i in m.t) >= 0
         m.con2 = Constraint(rule=_con2)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.v)
-        update_contset_indexed_component(m.p)
-        update_contset_indexed_component(m.con1)
-        update_contset_indexed_component(m.con2)
+        update_contset_indexed_component(m.v, expansion_map)
+        update_contset_indexed_component(m.p, expansion_map)
+        update_contset_indexed_component(m.con1, expansion_map)
+        update_contset_indexed_component(m.con2, expansion_map)
 
         self.assertTrue(len(m.con1) == 6)
         self.assertEqual(m.con1[2](), 15)
@@ -319,14 +326,16 @@ def _con3(m, i, ti, ti2, j, k):
             return m.v1[i, ti] - m.v3[ti2, j, k] * m.p1[i, ti] <= 20
         m.con3 = Constraint(m.s1, m.t, m.t2, m.s2, rule=_con3)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.p1)
-        update_contset_indexed_component(m.v1)
-        update_contset_indexed_component(m.v2)
-        update_contset_indexed_component(m.v3)
-        update_contset_indexed_component(m.con1)
-        update_contset_indexed_component(m.con2)
-        update_contset_indexed_component(m.con3)
+        update_contset_indexed_component(m.p1, expansion_map)
+        update_contset_indexed_component(m.v1, expansion_map)
+        update_contset_indexed_component(m.v2, expansion_map)
+        update_contset_indexed_component(m.v3, expansion_map)
+        update_contset_indexed_component(m.con1, expansion_map)
+        update_contset_indexed_component(m.con2, expansion_map)
+        update_contset_indexed_component(m.con3, expansion_map)
         
         self.assertTrue(len(m.con1) == 18)
         self.assertTrue(len(m.con2) == 12)
@@ -369,11 +378,13 @@ def _con2(m):
             return sum(m.v[i] for i in m.t)
         m.con2 = Expression(rule=_con2)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.v)
-        update_contset_indexed_component(m.p)
-        update_contset_indexed_component(m.con1)
-        update_contset_indexed_component(m.con2)
+        update_contset_indexed_component(m.v, expansion_map)
+        update_contset_indexed_component(m.p, expansion_map)
+        update_contset_indexed_component(m.con1, expansion_map)
+        update_contset_indexed_component(m.con2, expansion_map)
 
         self.assertTrue(len(m.con1) == 6)
         self.assertEqual(m.con1[2](), 15)
@@ -408,14 +419,16 @@ def _con3(m, i, ti, ti2, j, k):
             return m.v1[i, ti] - m.v3[ti2, j, k] * m.p1[i, ti]
         m.con3 = Expression(m.s1, m.t, m.t2, m.s2, rule=_con3)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.p1)
-        update_contset_indexed_component(m.v1)
-        update_contset_indexed_component(m.v2)
-        update_contset_indexed_component(m.v3)
-        update_contset_indexed_component(m.con1)
-        update_contset_indexed_component(m.con2)
-        update_contset_indexed_component(m.con3)
+        update_contset_indexed_component(m.p1, expansion_map)
+        update_contset_indexed_component(m.v1, expansion_map)
+        update_contset_indexed_component(m.v2, expansion_map)
+        update_contset_indexed_component(m.v3, expansion_map)
+        update_contset_indexed_component(m.con1, expansion_map)
+        update_contset_indexed_component(m.con2, expansion_map)
+        update_contset_indexed_component(m.con3, expansion_map)
 
         self.assertTrue(len(m.con1) == 18)
         self.assertTrue(len(m.con2) == 12)
@@ -464,8 +477,14 @@ def _con2(_b, i, ti):
      
         self.assertTrue(len(model.blk), 2)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(model.t, 5)
-        update_contset_indexed_component(model.blk)
+
+        missing_idx = set(model.blk._index) - set(iterkeys(model.blk._data))
+        model.blk._dae_missing_idx = missing_idx
+
+        update_contset_indexed_component(model.blk, expansion_map)
 
         self.assertEqual(len(model.blk), 6)
         self.assertEqual(len(model.blk[10].con1), 2)
@@ -508,11 +527,17 @@ def _con2(_b, i, j, ti):
             b.con2 = Expression(m.s2, m.t, rule=_con2)
     
         model.blk = Block(model.t, model.s1, rule=_block_rule)
-        
+
+        expansion_map = ComponentMap()
+
         self.assertTrue(len(model.blk), 6)
 
         generate_finite_elements(model.t, 5)
-        update_contset_indexed_component(model.blk)
+
+        missing_idx = set(model.blk._index) - set(iterkeys(model.blk._data))
+        model.blk._dae_missing_idx = missing_idx
+
+        update_contset_indexed_component(model.blk, expansion_map)
 
         self.assertEqual(len(model.blk), 18)
         self.assertEqual(len(model.blk[10, 'C'].con1), 6)
@@ -557,11 +582,17 @@ def _con2(_b, i, ti):
             return b
     
         model.blk = Block(model.t, rule=_block_rule)
+
+        expansion_map = ComponentMap()
      
         self.assertTrue(len(model.blk), 2)
 
         generate_finite_elements(model.t, 5)
-        update_contset_indexed_component(model.blk)
+
+        missing_idx = set(model.blk._index) - set(iterkeys(model.blk._data))
+        model.blk._dae_missing_idx = missing_idx
+
+        update_contset_indexed_component(model.blk, expansion_map)
 
         self.assertEqual(len(model.blk), 6)
         self.assertEqual(len(model.blk[10].con1), 2)
@@ -606,11 +637,17 @@ def _con2(_b, i, j, ti):
             return b
     
         model.blk = Block(model.t, model.s1, rule=_block_rule)
-        
+
+        expansion_map = ComponentMap()
+
         self.assertTrue(len(model.blk), 6)
 
         generate_finite_elements(model.t, 5)
-        update_contset_indexed_component(model.blk)
+
+        missing_idx = set(model.blk._index) - set(iterkeys(model.blk._data))
+        model.blk._dae_missing_idx = missing_idx
+
+        update_contset_indexed_component(model.blk, expansion_map)
 
         self.assertEqual(len(model.blk), 18)
         self.assertEqual(len(model.blk[10, 'C'].con1), 6)
@@ -646,8 +683,10 @@ def test_update_contset_indexed_component_piecewise_single(self):
         
         self.assertEqual(len(model.fx), 2)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(model.t, 5)
-        update_contset_indexed_component(model.fx)
+        update_contset_indexed_component(model.fx, expansion_map)
 
         self.assertEqual(len(model.fx), 6)
         self.assertEqual(len(model.fx[2].SOS2_constraint), 3)
@@ -673,8 +712,10 @@ def test_update_contset_indexed_component_piecewise_multiple(self):
         
         self.assertEqual(len(model.fx), 6)
 
+        expansion_map = ComponentMap()
+
         generate_finite_elements(model.t, 5)
-        update_contset_indexed_component(model.fx)
+        update_contset_indexed_component(model.fx, expansion_map)
 
         self.assertEqual(len(model.fx), 18)
         self.assertEqual(len(model.fx['A', 2].SOS2_constraint), 3)
@@ -691,10 +732,12 @@ def _obj(m):
             return sum(m.v[i] for i in m.s)
         m.obj = Objective(rule=_obj)
 
+        expansion_map = ComponentMap
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.junk)
-        update_contset_indexed_component(m.s)
-        update_contset_indexed_component(m.obj)
+        update_contset_indexed_component(m.junk, expansion_map)
+        update_contset_indexed_component(m.s, expansion_map)
+        update_contset_indexed_component(m.obj, expansion_map)
 
     # test unsupported components indexed by a single ContinuousSet
     def test_update_contset_indexed_component_unsupported_single(self):
@@ -702,9 +745,10 @@ def test_update_contset_indexed_component_unsupported_single(self):
         m.t = ContinuousSet(bounds=(0, 10))
         m.s = Set(m.t)
         generate_finite_elements(m.t, 5)
+        expansion_map = ComponentMap()
 
         try:
-            update_contset_indexed_component(m.s)
+            update_contset_indexed_component(m.s, expansion_map)
             self.fail("Expected TypeError because Set is not a component "
                       "that supports indexing by a ContinuousSet")
 
@@ -718,9 +762,10 @@ def test_update_contset_indexed_component_unsupported_multiple(self):
         m.i = Set(initialize=[1, 2, 3])
         m.s = Set(m.i, m.t)
         generate_finite_elements(m.t, 5)
+        expansion_map = ComponentMap()
 
         try:
-            update_contset_indexed_component(m.s)
+            update_contset_indexed_component(m.s, expansion_map)
             self.fail("Expected TypeError because Set is not a component "
                       "that supports indexing by a ContinuousSet")
 
@@ -742,8 +787,9 @@ def _init(m, j):
             b.p1 = Param(m.t, default=_init)
             b.v1 = Var(m.t, initialize=5)
         m.foo = Foo(m.t, rule=_block_rule)
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.foo)
+        expand_components(m)
 
         self.assertEqual(len(m.foo), 6)
         self.assertEqual(len(m.foo[0].p1), 6)
@@ -770,8 +816,8 @@ def _init(m, j):
         generate_finite_elements(m.t, 5)
 
         OUTPUT = StringIO()
-        with LoggingIntercept(OUTPUT, 'pyomo.core'):
-            update_contset_indexed_component(m.foo)
+        with LoggingIntercept(OUTPUT, 'pyomo.dae'):
+            expand_components(m)
         self.assertIn('transformation to the Block-derived component', 
                       OUTPUT.getvalue())
         self.assertEqual(len(m.foo), 6)
@@ -799,12 +845,13 @@ def _init(m, j):
             b.p1 = Param(m.t, default=_init)
             b.v1 = Var(m.t, initialize=5)
         m.foo = Foo(m.t, rule=_block_rule)
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.foo)
+        expand_components(m)
 
         self.assertTrue(m.foo.updated)
         self.assertEqual(len(m.foo), 2)
-        self.assertEqual(len(m.foo[0].v1), 2)
+        self.assertEqual(len(m.foo[0].v1), 6)
 
     def test_update_block_derived2(self):
         class Foo(Block):
@@ -822,8 +869,9 @@ def _init(m, j):
             b.p1 = Param(m.t, default=_init)
             b.v1 = Var(m.t, initialize=5)
         m.foo = Foo(m.t, m.s, rule=_block_rule)
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.foo)
+        expand_components(m)
 
         self.assertEqual(len(m.foo), 18)
         self.assertEqual(len(m.foo[0, 1].p1), 6)
@@ -848,11 +896,12 @@ def _init(m, j):
             b.p1 = Param(m.t, default=_init)
             b.v1 = Var(m.t, initialize=5)
         m.foo = Foo(m.t, m.s, rule=_block_rule)
+
         generate_finite_elements(m.t, 5)
 
         OUTPUT = StringIO()
-        with LoggingIntercept(OUTPUT, 'pyomo.core'):
-            update_contset_indexed_component(m.foo)
+        with LoggingIntercept(OUTPUT, 'pyomo.dae'):
+            expand_components(m)
         self.assertIn('transformation to the Block-derived component', 
                       OUTPUT.getvalue())
         self.assertEqual(len(m.foo), 18)
@@ -881,69 +930,43 @@ def _init(m, j):
             b.p1 = Param(m.t, default=_init)
             b.v1 = Var(m.t, initialize=5)
         m.foo = Foo(m.t, m.s, rule=_block_rule)
+
         generate_finite_elements(m.t, 5)
-        update_contset_indexed_component(m.foo)
+        expand_components(m)
 
         self.assertTrue(m.foo.updated)
         self.assertEqual(len(m.foo), 6)
-        self.assertEqual(len(m.foo[0, 1].v1), 2)
-        
-    # test add_equality_constraints method
-    # def test_add_equality_constraints(self):
-    #     m = ConcreteModel()
-    #     m.t = DifferentialSet(initialize=[1, 2, 3, 4, 5])
-    #     m.s = Set(initialize=[1, 2])
-    #     m.v = Var(m.t)
-    #     m.v2 = Var(m.s, m.t, initialize=2)
-        
-    #     def _vdot(m, i):
-    #         return m.v[i]**2
-    #     m.vdot = Differential(dv=m.v, rule=_vdot)
-
-    #     def _vdot2(m, i, j):
-    #         return m.v2[i, j]**2
-    #     m.vdot2 = Differential(dv=m.v2, rule=_vdot2)
-
-    #     self.assertTrue(len(m.vdot._cons)==0)
-    #     add_equality_constraints(m.vdot)
-    #     self.assertTrue(len(m.vdot._cons)==4)
-    #     m.vdot._lhs_var[2] = 3
-    #     m.v[2] = 3
-    #     self.assertEqual(m.vdot._cons[1](), -6)
-
-    #     add_equality_constraints(m.vdot2)
-    #     self.assertTrue(len(m.vdot2._cons)==8)
-    #     m.vdot2._lhs_var[1, 2] = 3
-    #     self.assertEqual(m.vdot2._cons[1](), -1)
-
-    # test add_equality_constraints method with invalid 
-    # Differential rules
-    # def test_bad_add_equality_constraint(self):
-    #     m = ConcreteModel()
-    #     m.t = DifferentialSet(initialize=[1, 2, 3])
-    #     m.v = Var(m.t)
-
-    #     def _vdot1(m, i, j):
-    #         return m.v[i, j]**2
-    #     m.vdot1 = Differential(dv=m.v, rule=_vdot1)
-
-    #     def _vdot2(m, i):
-    #         return m.v[i] == 3
-    #     m.vdot2 = Differential(dv=m.v, rule=_vdot2)
-    #
-    #     try:
-    #         add_equality_constraints(m.vdot1)
-    #         self.fail("Expected TypeError because the rule supplied to the "
-    #                   "differential had the wrong number of arguments")
-    #     except TypeError:
-    #         pass
-    #
-    #     try:
-    #         add_equality_constraints(m.vdot2)
-    #         self.fail("Expected TypeError because the rule supplied to the "
-    #                   "differential returns an invalid expression")
-    #     except TypeError:
-    #         pass
+        self.assertEqual(len(m.foo[0, 1].v1), 6)
+
+    def test_hierarchical_blocks(self):
+        m = ConcreteModel()
+
+        m.b = Block()
+        m.b.t = ContinuousSet(bounds=(0, 10))
+
+        m.b.c = Block()
+
+        def _d_rule(d, t):
+            m = d.model()
+            d.x = Var()
+            return d
+
+        m.b.c.d = Block(m.b.t, rule=_d_rule)
+
+        m.b.y = Var(m.b.t)
 
+        def _con_rule(b, t):
+            return b.y[t] <= b.c.d[t].x
+
+        m.b.con = Constraint(m.b.t, rule=_con_rule)
+
+        generate_finite_elements(m.b.t, 5)
+        expand_components(m)
+
+        self.assertTrue(len(m.b.c.d), 6)
+        self.assertTrue(len(m.b.con), 6)
+        self.assertTrue(len(m.b.y), 6)
+
+    
 if __name__ == "__main__":
     unittest.main()