Repository: Pyomo/pyomo

Issue:
ExternalFunction and DAE Transformation
I think there is a problem applying DAE transformations to Pyomo models with ExternalFunction objects.  I can come up with a simpler demonstration, but hopefully the trace below is good enough.

jeslick@pds04:~/git/eslickj/models/idaes_models/process/solvent/MEA_simple> python main.py 
Traceback (most recent call last):
  File "main.py", line 96, in <module>
    flowsheet = init(filename=args.load_json)
  File "main.py", line 34, in init
    flowsheet = MeaSheet(name='MEA_Model', solver=solver)
  File "/imports/users/jeslick/git/eslickj/models/idaes_models/process/solvent/MEA_simple/flowsheet.py", line 51, in __init__
    FlowsheetModel.__init__(self, *args, **kwargs)
  File "/imports/users/jeslick/git/eslickj/models/idaes_models/core/flowsheet_model.py", line 29, in __init__
    self.build()
  File "/imports/users/jeslick/git/eslickj/models/idaes_models/process/solvent/MEA_simple/flowsheet.py", line 341, in build
    doc="CO2 absorber"))
  File "/imports/users/jeslick/git/eslickj/models/idaes_models/unit/solvent/MEA_simple/column.py", line 123, in __init__
    self.build()
  File "/imports/users/jeslick/git/eslickj/models/idaes_models/unit/solvent/MEA_simple/column.py", line 164, in build
    self._fd_transform()
  File "/imports/users/jeslick/git/eslickj/models/idaes_models/unit/solvent/MEA_simple/column.py", line 347, in _fd_transform
    blk, nfe=self.nfe, wrt=blk.z, scheme='BACKWARD')
  File "/imports/users/jeslick/anaconda2/lib/python2.7/site-packages/pyomo/core/base/plugin.py", line 330, in apply_to
    self._apply_to(model, **kwds)
  File "/imports/users/jeslick/anaconda2/lib/python2.7/site-packages/pyomo/dae/plugins/finitedifference.py", line 170, in _apply_to
    self._transformBlock(block,currentds)
  File "/imports/users/jeslick/anaconda2/lib/python2.7/site-packages/pyomo/dae/plugins/finitedifference.py", line 199, in _transformBlock
    update_contset_indexed_component(c)
  File "/imports/users/jeslick/anaconda2/lib/python2.7/site-packages/pyomo/dae/misc.py", line 115, in update_contset_indexed_component
    if comp.dim() == 1:
AttributeError: 'AMPLExternalFunction' object has no attribute 'dim'
 


Discussion:

Resolving patch:
diff --git a/pyomo/dae/misc.py b/pyomo/dae/misc.py
--- a/pyomo/dae/misc.py
+++ b/pyomo/dae/misc.py
@@ -202,6 +202,12 @@ def update_contset_indexed_component(comp, expansion_map):
     if comp.type() is Param:
         return
 
+    # Skip components that do not have a 'dim' attribute. This assumes that
+    # all components that could be indexed by a ContinuousSet have the 'dim'
+    # attribute
+    if not hasattr(comp, 'dim'):
+        return
+
     # Components indexed by a ContinuousSet must have a dimension of at
     # least 1
     if comp.dim() == 0:


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
@@ -967,6 +967,27 @@ def _con_rule(b, t):
         self.assertTrue(len(m.b.con), 6)
         self.assertTrue(len(m.b.y), 6)
 
+    def test_external_function(self):
+        m = ConcreteModel()
+        m.t = ContinuousSet(bounds=(0, 10))
+        
+        def _fun(x):
+            return x**2
+        m.x_func = ExternalFunction(_fun)
+
+        m.y = Var(m.t, initialize=3)
+        m.dy = DerivativeVar(m.y, initialize=3)
+        
+        def _con(m, t):
+            return m.dy[t] == m.x_func(m.y[t])
+        m.con = Constraint(m.t, rule=_con)
+        
+        generate_finite_elements(m.t, 5)
+        expand_components(m)
+
+        self.assertTrue(len(m.y), 6)
+        self.assertTrue(len(m.con), 6)
+
     
 if __name__ == "__main__":
     unittest.main()