Repository: Pyomo/pyomo

Issue:
component_data_objects fails for cross products with infinite sets
In this example:
```
m = ConcreteModel()
m.s = NonNegativeIntegers*['a', 'b']
for comp in m.component_data_objects():
    print(comp.name)
```
we get this error:
```
Traceback (most recent call last):
  File "infinite_set_bug.py", line 8, in <module>
    for comp in m.component_data_objects():
  File "/home/esjohn/src/pyomo/pyomo/core/base/block.py", line 1415, in component_data_objects
    sort=sort):
  File "/home/esjohn/src/pyomo/pyomo/core/base/block.py", line 1339, in _component_data_iter
    elif len(comp) or not hasattr(comp, '_data'):
  File "/home/esjohn/src/pyomo/pyomo/core/base/set.py", line 2955, in __len__
    "The length of a non-finite Set is Inf; however, Python "
OverflowError: The length of a non-finite Set is Inf; however, Python requires len() to return a non-negative integer value. Check isfinite() before calling len() for possibly infinite Sets
```
The call to `len()` in _component_data_iter is marked as a hack, so I'm not sure if adding `not isfinite(comp)` to that `or` statement is the desired solution... I'm also not sure why this happens for cross products, but not when a component is indexed by `NonNegativeIntegers`, for example?


Discussion:

Resolving patch:
diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py
--- a/pyomo/core/base/block.py
+++ b/pyomo/core/base/block.py
@@ -1317,27 +1317,22 @@ def _component_data_iter(self, ctype=None, active=None, sort=False):
         _sort_indices = SortComponents.sort_indices(sort)
         _subcomp = PseudoMap(self, ctype, active, sort)
         for name, comp in _subcomp.iteritems():
-            # _NOTE_: Suffix has a dict interface (something other
-            #         derived non-indexed Components may do as well),
-            #         so we don't want to test the existence of
-            #         iteritems as a check for components. Also,
-            #         the case where we test len(comp) after seeing
-            #         that comp.is_indexed is False is a hack for a
-            #         SimpleConstraint whose expression resolved to
-            #         Constraint.skip or Constraint.feasible (in which
-            #         case its data is empty and iteritems would have
-            #         been empty as well)
-            # try:
-            #    _items = comp.iteritems()
-            # except AttributeError:
-            #    _items = [ (None, comp) ]
+            # NOTE: Suffix has a dict interface (something other derived
+            #   non-indexed Components may do as well), so we don't want
+            #   to test the existence of iteritems as a check for
+            #   component datas. We will rely on is_indexed() to catch
+            #   all the indexed components.  Then we will do special
+            #   processing for the scalar components to catch the case
+            #   where there are "sparse scalar components"
             if comp.is_indexed():
                 _items = comp.iteritems()
-            # This is a hack (see _NOTE_ above).
-            elif len(comp) or not hasattr(comp, '_data'):
-                _items = ((None, comp),)
+            elif hasattr(comp, '_data'):
+                # This may be an empty Scalar component (e.g., from
+                # Constraint.Skip on a scalar Constraint)
+                assert len(comp._data) <= 1
+                _items = iteritems(comp._data)
             else:
-                _items = tuple()
+                _items = ((None, comp),)
 
             if _sort_indices:
                 _items = sorted(_items, key=itemgetter(0))


Test patch:
diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py
--- a/pyomo/core/tests/unit/test_block.py
+++ b/pyomo/core/tests/unit/test_block.py
@@ -61,6 +61,7 @@ def generate_model(self):
         model = ConcreteModel()
         model.q = Set(initialize=[1,2])
         model.Q = Set(model.q,initialize=[1,2])
+        model.qq = NonNegativeIntegers*model.q
         model.x = Var(initialize=-1)
         model.X = Var(model.q,initialize=-1)
         model.e = Expression(initialize=-1)
@@ -152,8 +153,8 @@ def B_rule(block,i):
 
         model.component_lists = {}
         model.component_data_lists = {}
-        model.component_lists[Set] = [model.q, model.Q]
-        model.component_data_lists[Set] = [model.q, model.Q[1], model.Q[2]]
+        model.component_lists[Set] = [model.q, model.Q, model.qq]
+        model.component_data_lists[Set] = [model.q, model.Q[1], model.Q[2], model.qq]
         model.component_lists[Var] = [model.x, model.X]
         model.component_data_lists[Var] = [model.x, model.X[1], model.X[2]]
         model.component_lists[Expression] = [model.e, model.E]
@@ -186,7 +187,8 @@ def generator_test(self, ctype):
                 generator = list(block.component_objects(ctype, active=True, descend_into=False))
             except:
                 if issubclass(ctype, Component):
-                    self.fail("component_objects(active=True) failed with ctype %s" % ctype)
+                    print("component_objects(active=True) failed with ctype %s" % ctype)
+                    raise
             else:
                 if not issubclass(ctype, Component):
                     self.fail("component_objects(active=True) should have failed with ctype %s" % ctype)
@@ -205,7 +207,8 @@ def generator_test(self, ctype):
                 generator = list(block.component_objects(ctype, descend_into=False))
             except:
                 if issubclass(ctype, Component):
-                    self.fail("components failed with ctype %s" % ctype)
+                    print("components failed with ctype %s" % ctype)
+                    raise
             else:
                 if not issubclass(ctype, Component):
                     self.fail("components should have failed with ctype %s" % ctype)
@@ -224,7 +227,8 @@ def generator_test(self, ctype):
                 generator = list(block.component_data_iterindex(ctype, active=True, sort=False, descend_into=False))
             except:
                 if issubclass(ctype, Component):
-                    self.fail("component_data_objects(active=True, sort_by_keys=False) failed with ctype %s" % ctype)
+                    print("component_data_objects(active=True, sort_by_keys=False) failed with ctype %s" % ctype)
+                    raise
             else:
                 if not issubclass(ctype, Component):
                     self.fail("component_data_objects(active=True, sort_by_keys=False) should have failed with ctype %s" % ctype)
@@ -243,7 +247,8 @@ def generator_test(self, ctype):
                 generator = list(block.component_data_iterindex(ctype, active=True, sort=True, descend_into=False))
             except:
                 if issubclass(ctype, Component):
-                    self.fail("component_data_objects(active=True, sort=True) failed with ctype %s" % ctype)
+                    print("component_data_objects(active=True, sort=True) failed with ctype %s" % ctype)
+                    raise
             else:
                 if not issubclass(ctype, Component):
                     self.fail("component_data_objects(active=True, sort=True) should have failed with ctype %s" % ctype)
@@ -262,7 +267,8 @@ def generator_test(self, ctype):
                 generator = list(block.component_data_iterindex(ctype, sort=False, descend_into=False))
             except:
                 if issubclass(ctype, Component):
-                    self.fail("components_data(sort_by_keys=True) failed with ctype %s" % ctype)
+                    print("components_data(sort_by_keys=True) failed with ctype %s" % ctype)
+                    raise
             else:
                 if not issubclass(ctype, Component):
                     self.fail("components_data(sort_by_keys=True) should have failed with ctype %s" % ctype)
@@ -281,7 +287,8 @@ def generator_test(self, ctype):
                 generator = list(block.component_data_iterindex(ctype, sort=True, descend_into=False))
             except:
                 if issubclass(ctype, Component):
-                    self.fail("components_data(sort_by_keys=False) failed with ctype %s" % ctype)
+                    print("components_data(sort_by_keys=False) failed with ctype %s" % ctype)
+                    raise
             else:
                 if not issubclass(ctype, Component):
                     self.fail("components_data(sort_by_keys=False) should have failed with ctype %s" % ctype)