Repository: Pyomo/pyomo

Issue:
GAMS writer does not accept models with Connector objects
The GAMS writer does not accept models with Connector objects. See discussion in https://github.com/Pyomo/pyomo/pull/253#discussion_r152372039 as well as #310 and #280 and #309 


Discussion:

Resolving patch:
diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py
--- a/pyomo/repn/plugins/gams_writer.py
+++ b/pyomo/repn/plugins/gams_writer.py
@@ -20,7 +20,7 @@
 from pyomo.core.base import (
     SymbolMap, AlphaNumericTextLabeler, NumericLabeler,
     Block, Constraint, Expression, Objective, Var, Set, RangeSet, Param,
-    minimize, Suffix, SortComponents)
+    minimize, Suffix, SortComponents, Connector)
 
 from pyomo.core.base.component import ComponentData
 from pyomo.opt import ProblemFormat
@@ -42,6 +42,40 @@ def _get_bound(exp):
         return value(exp)
     raise ValueError("non-fixed bound or weight: " + str(exp))
 
+
+# HACK: Temporary check for Connectors in active constriants.
+# This should be removed after the writer is moved to an
+# explicit GAMS-specific expression walker for generating the
+# constraint strings.
+import pyomo.core.base.expr_coopr3 as coopr3
+from pyomo.core.kernel.numvalue import native_types
+from pyomo.core.base.connector import _ConnectorData
+def _check_for_connectors(con):
+    _stack = [ ([con.body], 0, 1) ]
+    while _stack:
+        _argList, _idx, _len = _stack.pop()
+        while _idx < _len:
+            _sub = _argList[_idx]
+            _idx += 1
+            if type(_sub) in native_types:
+                pass
+            elif _sub.is_expression():
+                _stack.append(( _argList, _idx, _len ))
+                if type(_sub) is coopr3._ProductExpression:
+                    if _sub._denominator:
+                        _stack.append(
+                            (_sub._denominator, 0, len(_sub._denominator)) )
+                    _argList = _sub._numerator
+                else:
+                    _argList = _sub._args
+                _idx = 0
+                _len = len(_argList)
+            elif isinstance(_sub, _ConnectorData):
+                raise TypeError(
+                    "Constraint '%s' body contains unexpanded connectors"
+                    % (con.name,))
+
+
 class ProblemWriter_gams(AbstractProblemWriter):
     pyomo.util.plugin.alias('gams', 'Generate the corresponding GAMS file')
 
@@ -243,7 +277,7 @@ def _write_model(self,
         # how to deal with, plus Suffix if solving
         valid_ctypes = set([
             Block, Constraint, Expression, Objective, Param,
-            Set, RangeSet, Var, Suffix ])
+            Set, RangeSet, Var, Suffix, Connector ])
         model_ctypes = model.collect_ctypes(active=True)
         if not model_ctypes.issubset(valid_ctypes):
             invalids = [t.__name__ for t in (model_ctypes - valid_ctypes)]
@@ -252,6 +286,12 @@ def _write_model(self,
                 "export models with this component type" %
                 ", ".join(invalids))
 
+        # HACK: Temporary check for Connectors in active constriants.
+        # This should be removed after the writer is moved to an
+        # explicit GAMS-specific expression walker for generating the
+        # constraint strings.
+        has_Connectors = Connector in model_ctypes
+
         # Walk through the model and generate the constraint definition
         # for all active constraints.  Any Vars / Expressions that are
         # encountered will be added to the var_list due to the labeler
@@ -265,6 +305,10 @@ def _write_model(self,
                 assert not con.equality
                 continue # non-binding, so skip
 
+            # HACK: Temporary check for Connectors in active constriants.
+            if has_Connectors:
+                _check_for_connectors(con)
+
             con_body = as_numeric(con.body)
             if skip_trivial_constraints and con_body.is_fixed():
                 continue


Test patch:
diff --git a/pyomo/repn/tests/test_gams.py b/pyomo/repn/tests/test_gams.py
--- a/pyomo/repn/tests/test_gams.py
+++ b/pyomo/repn/tests/test_gams.py
@@ -15,6 +15,8 @@
 
 import pyutilib.th as unittest
 from pyomo.repn.plugins.gams_writer import replace_power
+from pyomo.environ import (ConcreteModel, Block, Var, Connector, Constraint,
+                           Objective)
 
 
 class GAMSTests(unittest.TestCase):
@@ -42,6 +44,21 @@ def test_gams_replace_power(self):
         line6 = "log( abc**2.0 )**4.5"
         self.assertTrue(replace_power(line6) == line6)
 
+    def test_gams_connector_in_active_constraint(self):
+        """Test connector in active constraint for GAMS writer."""
+        m = ConcreteModel()
+        m.b1 = Block()
+        m.b2 = Block()
+        m.b1.x = Var()
+        m.b2.x = Var()
+        m.b1.c = Connector()
+        m.b1.c.add(m.b1.x)
+        m.b2.c = Connector()
+        m.b2.c.add(m.b2.x)
+        m.c = Constraint(expr=m.b1.c == m.b2.c)
+        m.o = Objective(expr=m.b1.x)
+        with self.assertRaises(TypeError):
+            m.write('testgmsfile.gms')
 
 
 if __name__ == "__main__":