Repository: Pyomo/pyomo

Issue:
GDP internal variable names raise problems when translating to GAMS
When using the GAMS solver to solve a model that has been transformed using the `TranformationFactory` bigM or chull specifying the option `symbolic_solver_labels=True`, Pyomo generates a GAMS file which constains equations and variables with names starting with a _ (e.g. `_gdp_bigm_relaxation_disjunction_xor`) or with double underscore __ (e.g. `d_0__indicator_var`). This raises issues when solving it with GAMS, specifically errors 409, 109 (and sometimes 119 because the variables names become too large).
Although the easy way to deal with this would be just to use the default option  `symbolic_solver_labels=False`, this issue appeared when working with @MAZamarripa in an Ideas-Related project, where the user is still interested on looking at the generated GAMS file.
I'm attaching a MWE, with its generated outputs in GAMS. Maybe just removing this 'underscored' names or hashing names of equations/variables with more than X characters can be a way of solving this.

[issue_gdp_gams.zip](https://github.com/Pyomo/pyomo/files/2362026/issue_gdp_gams.zip)
Error with trigonometric functions in GAMS writer interface 
Gams does not identify the following pyomo expressions: acos,  asin and atan. 



Discussion:
This is a duplicate. I'm almost positive that I discussed this with Grant at some point. There might also already be a solution.
I just tried this on the master branch and with GAMS 24.8.5, and one of these issues still exists: the writer still generates gms files with names starting with underscores. However, #512 fixed the issue about symbol names being too long. Additionally, GAMS does not seem to have any issue with symbols containing double underscores.

Should we change the labeler that the GAMS writer uses to make sure exported symbols do not start with underscores? Otherwise we should change the GDP transformations.
Wow, I can't believe I've been out of action for so long that I missed this. Yeah, changing the labeler would be a good idea.
@qtothec which labeler do you think should be changed? Make the GAMS writer modify symbols starting with underscores, or make GDP not generate symbols starting with underscores?
@gseastream I would say change the GAMS writer. Use of underscore fairly common in Python.

That said, I can see an argument for the other way around, since you'll need to cope with potential name conflicts depending on how you handle it.
I suppose both could be done in an effort to future-proof both of them.
@gseastream Did you actually ever get to this one? :upside_down_face: 
No, since it still felt like an open question on what to do
I'm inclined to say that we modify the GAMS writer to modify symbols starting with underscores.
Sounds fine to me. The labeler will need to be changed (or a new layer added) and we'll have to deal with name conflicts.
So if the goal is to add a new layer to the labeler that checks if symbols start with underscores (or a number because that is also not allowed), and replace it with a safe character like 'x', is the only way to deal with name conflicts going to be a dictionary of existing names and making sure the new symbol doesn't already exist (and then perhaps incrementing a counter until the symbol is unique)? I don't know if this is undesired for any reason.... Feels like it might be slow or inefficient or something.


Resolving patch:
diff --git a/pyomo/core/base/label.py b/pyomo/core/base/label.py
--- a/pyomo/core/base/label.py
+++ b/pyomo/core/base/label.py
@@ -12,6 +12,7 @@
            'AlphaNumericTextLabeler','NameLabeler', 'CuidLabeler',
            'ShortNameLabeler']
 
+import re
 import six
 if six.PY3:
     _translate = str.translate
@@ -153,20 +154,45 @@ def __call__(self, obj):
         return obj.getname(True, self.name_buffer)
 
 class ShortNameLabeler(object):
-    def __init__(self, limit, prefix, start=0, labeler=None):
+    def __init__(self, limit, suffix, start=0, labeler=None,
+                 prefix="", caseInsensitive=False, legalRegex=None):
         self.id = start
         self.prefix = prefix
+        self.suffix = suffix
         self.limit = limit
         if labeler is not None:
             self.labeler = labeler
         else:
             self.labeler = AlphaNumericTextLabeler()
+        self.known_labels = set() if caseInsensitive else None
+        if isinstance(legalRegex, six.string_types):
+            self.legalRegex = re.compile(legalRegex)
+        else:
+            self.legalRegex = legalRegex
 
     def __call__(self, obj=None):
         lbl = self.labeler(obj)
-        if (len(lbl) < self.limit) or ((len(lbl) == self.limit) and (lbl[-len(self.prefix):] != self.prefix)):
-            return lbl
-        else:
+        lbl_len = len(lbl)
+        shorten = False
+        if lbl_len > self.limit:
+            shorten = True
+        elif lbl_len == self.limit and lbl.startswith(self.prefix) \
+             and lbl.endswith(self.suffix):
+            shorten = True
+        elif self.known_labels is not None and lbl.upper() in self.known_labels:
+            shorten = True
+        elif self.legalRegex and not self.legalRegex.match(lbl):
+            shorten = True
+        if shorten:
             self.id += 1
-            suffix = self.prefix + str(self.id) + self.prefix
-            return lbl[-self.limit + len(suffix):] + suffix
+            suffix = "%s%d%s" % (self.suffix, self.id, self.suffix)
+            tail = -self.limit + len(suffix) + len(self.prefix)
+            if tail >= 0:
+                raise RuntimeError(
+                    "Too many identifiers.\n\t"
+                    "The ShortNameLabeler cannot generate a guaranteed unique "
+                    "label limited to %d characters" % (self.limit,)) 
+            lbl = self.prefix + lbl[tail:] + suffix
+        if self.known_labels is not None:
+            self.known_labels.add(lbl.upper())
+        return lbl
diff --git a/pyomo/repn/plugins/baron_writer.py b/pyomo/repn/plugins/baron_writer.py
--- a/pyomo/repn/plugins/baron_writer.py
+++ b/pyomo/repn/plugins/baron_writer.py
@@ -27,7 +27,7 @@
 from pyomo.core.expr import current as EXPR
 from pyomo.core.base import (SortComponents,
                              SymbolMap,
-                             AlphaNumericTextLabeler,
+                             ShortNameLabeler,
                              NumericLabeler,
                              BooleanSet, Constraint,
                              IntegerSet, Objective,
@@ -643,11 +643,20 @@ def __call__(self,
         output_file.write("}\n\n")
 
         if symbolic_solver_labels:
-            v_labeler = AlphaNumericTextLabeler()
-            c_labeler = AlphaNumericTextLabeler()
+            # Note that the Var and Constraint labelers must use the
+            # same labeler, so that we can correctly detect name
+            # collisions (which can arise when we truncate the labels to
+            # the max allowable length.  BARON requires all identifiers
+            # to start with a letter.  We will (randomly) choose "s_"
+            # (for 'shortened')
+            v_labeler = c_labeler = ShortNameLabeler(
+                15, prefix='s_', suffix='_', caseInsensitive=True,
+                legalRegex='^[a-zA-Z]')
         elif labeler is None:
             v_labeler = NumericLabeler('x')
             c_labeler = NumericLabeler('c')
+        else:
+            v_labeler = c_labeler = labeler
 
         symbol_map = SymbolMap()
         symbol_map.default_labeler = v_labeler
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
@@ -36,6 +36,13 @@
 
 logger = logging.getLogger('pyomo.core')
 
+_legal_unary_functions = {
+    'ceil','floor','exp','log','log10','sqrt',
+    'sin','cos','tan','asin','acos','atan','sinh','cosh','tanh',
+}
+_arc_functions = {'acos','asin','atan'}
+_dnlp_functions = {'ceil','floor','abs'}
+
 #
 # A visitor pattern that creates a string for an expression
 # that is compatible with the GAMS syntax.
@@ -46,6 +53,7 @@ def __init__(self, smap, treechecker):
         super(ToGamsVisitor, self).__init__()
         self.smap = smap
         self.treechecker = treechecker
+        self.is_discontinuous = False
 
     def visit(self, node, values):
         """ Visit nodes that have been expanded """
@@ -87,6 +95,20 @@ def visit(self, node, values):
                 return "power({0}, {1})".format(tmp[0], tmp[1])
             else:
                 return "{0} ** {1}".format(tmp[0], tmp[1])
+        elif node.__class__ is EXPR.UnaryFunctionExpression:
+            if node.name not in _legal_unary_functions:
+                raise RuntimeError(
+                    "GAMS files cannot represent the unary function %s"
+                    % ( node.name, ))
+            if node.name in _dnlp_functions:
+                self.is_discontinuous = True
+            if node.name in _arc_functions:
+                return "arc{0}({1})".format(node.name[1:], tmp[0])
+            else:
+                return node._to_string(tmp, None, self.smap, True)
+        elif node.__class__ is EXPR.AbsExpression:
+            self.is_discontinuous = True
+            return node._to_string(tmp, None, self.smap, True)
         else:
             return node._to_string(tmp, None, self.smap, True)
 
@@ -146,7 +168,8 @@ def expression_to_string(expr, treechecker, labeler=None, smap=None):
             smap = SymbolMap()
         smap.default_labeler = labeler
     visitor = ToGamsVisitor(smap, treechecker)
-    return visitor.dfs_postorder_stack(expr)
+    expr_str = visitor.dfs_postorder_stack(expr)
+    return expr_str, visitor.is_discontinuous
 
 
 class Categorizer(object):
@@ -391,7 +414,15 @@ def __call__(self,
                              "I/O options is forbidden")
 
         if symbolic_solver_labels:
-            var_labeler = con_labeler = ShortNameLabeler(63, '_')
+            # Note that the Var and Constraint labelers must use the
+            # same labeler, so that we can correctly detect name
+            # collisions (which can arise when we truncate the labels to
+            # the max allowable length.  GAMS requires all identifiers
+            # to start with a letter.  We will (randomly) choose "s_"
+            # (for 'shortened')
+            var_labeler = con_labeler = ShortNameLabeler(
+                63, prefix='s_', suffix='_', caseInsensitive=True,
+                legalRegex='^[a-zA-Z]')
         elif labeler is None:
             var_labeler = NumericLabeler('x')
             con_labeler = NumericLabeler('c')
@@ -471,6 +502,7 @@ def _write_model(self,
         ConstraintIO = StringIO()
         linear = True
         linear_degree = set([0,1])
+        dnlp = False
 
         # Make sure there are no strange ActiveComponents. The expression
         # walker will handle strange things in constraints later.
@@ -508,11 +540,14 @@ def _write_model(self,
                     linear = False
 
             cName = symbolMap.getSymbol(con, con_labeler)
+            con_body_str, con_discontinuous = expression_to_string(
+                con_body, tc, smap=symbolMap)
+            dnlp |= con_discontinuous
             if con.equality:
                 constraint_names.append('%s' % cName)
                 ConstraintIO.write('%s.. %s =e= %s ;\n' % (
                     constraint_names[-1],
-                    expression_to_string(con_body, tc, smap=symbolMap),
+                    con_body_str,
                     _get_bound(con.upper)
                 ))
             else:
@@ -521,13 +556,13 @@ def _write_model(self,
                     ConstraintIO.write('%s.. %s =l= %s ;\n' % (
                         constraint_names[-1],
                         _get_bound(con.lower),
-                        expression_to_string(con_body, tc, smap=symbolMap)
+                        con_body_str,
                     ))
                 if con.has_ub():
                     constraint_names.append('%s_hi' % cName)
                     ConstraintIO.write('%s.. %s =l= %s ;\n' % (
                         constraint_names[-1],
-                        expression_to_string(con_body, tc, smap=symbolMap),
+                        con_body_str,
                         _get_bound(con.upper)
                     ))
 
@@ -542,11 +577,14 @@ def _write_model(self,
         if linear:
             if obj.expr.polynomial_degree() not in linear_degree:
                 linear = False
+        obj_expr_str, obj_discontinuous = expression_to_string(
+            obj.expr, tc, smap=symbolMap)
+        dnlp |= obj_discontinuous
         oName = symbolMap.getSymbol(obj, con_labeler)
         constraint_names.append(oName)
         ConstraintIO.write('%s.. GAMS_OBJECTIVE =e= %s ;\n' % (
             oName,
-            expression_to_string(obj.expr, tc, smap=symbolMap)
+            obj_expr_str,
         ))
 
         # Categorize the variables that we found
@@ -643,6 +681,8 @@ def _write_model(self,
                 (0 if linear else 1) +
                 (2 if (categorized_vars.binary or categorized_vars.ints)
                  else 0)]
+            if mtype == 'nlp' and dnlp:
+                mtype = 'dnlp'
 
         if solver is not None:
             if mtype.upper() not in valid_solvers[solver.upper()]:


Test patch:
diff --git a/pyomo/core/tests/unit/test_labelers.py b/pyomo/core/tests/unit/test_labelers.py
--- a/pyomo/core/tests/unit/test_labelers.py
+++ b/pyomo/core/tests/unit/test_labelers.py
@@ -18,6 +18,7 @@ class LabelerTests(unittest.TestCase):
     def setUp(self):
         m = ConcreteModel()
         m.mycomp = Var()
+        m.MyComp = Var()
         m.that = Var()
         self.long1 = m.myverylongcomponentname = Var()
         self.long2 = m.myverylongcomponentnamerighthere = Var()
@@ -133,10 +134,51 @@ def test_cuidlabeler(self):
         self.assertEqual(lbl(m.ind[10]), ComponentUID(m.ind[10]))
         self.assertEqual(lbl(m.ind[1]), ComponentUID(m.ind[1]))
 
-    def test_shortnamelabeler(self):
+    def test_case_insensitive_shortnamelabeler(self):
         m = self.m
-        lbl = ShortNameLabeler(20, '_')
+        lbl = ShortNameLabeler(20, '_', caseInsensitive=True)
+        self.assertEqual(lbl(m.mycomp), 'mycomp')
+        self.assertEqual(lbl(m.that), 'that')
+        self.assertEqual(lbl(self.long1), 'longcomponentname_1_')
+        self.assertEqual(lbl(self.long2), 'nentnamerighthere_2_')
+        self.assertEqual(lbl(self.long3), 'ngonebutdifferent_3_')
+        self.assertEqual(lbl(self.long4), 'longcomponentname_4_')
+        self.assertEqual(lbl(self.long5), 'gcomponentname_1__5_')
+        self.assertEqual(lbl(m.myblock), 'myblock')
+        self.assertEqual(lbl(m.myblock.mystreet), 'myblock_mystreet')
+        self.assertEqual(lbl(m.ind[3]), 'ind_3_')
+        self.assertEqual(lbl(m.ind[10]), 'ind_10_')
+        self.assertEqual(lbl(m.ind[1]), 'ind_1_')
+
+        # Test name collision
+        self.assertEqual(lbl(m.mycomp), 'mycomp_6_')
+        self.assertEqual(lbl(self.thecopy), 'myblock_mystreet_7_')
+        self.assertEqual(lbl(m.MyComp), 'MyComp_8_')
+
+    def test_shortnamelabeler_prefix(self):
+        m = self.m
+        lbl = ShortNameLabeler(20, '_', prefix='s_', caseInsensitive=True)
         self.assertEqual(lbl(m.mycomp), 'mycomp')
+        self.assertEqual(lbl(m.that), 'that')
+        self.assertEqual(lbl(self.long1), 's_ngcomponentname_1_')
+        self.assertEqual(lbl(self.long2), 's_ntnamerighthere_2_')
+        self.assertEqual(lbl(self.long3), 's_onebutdifferent_3_')
+        self.assertEqual(lbl(self.long4), 's_ngcomponentname_4_')
+        self.assertEqual(lbl(self.long5), 'longcomponentname_1_')
+        self.assertEqual(lbl(m.myblock), 'myblock')
+        self.assertEqual(lbl(m.myblock.mystreet), 'myblock_mystreet')
+        self.assertEqual(lbl(m.ind[3]), 'ind_3_')
+        self.assertEqual(lbl(m.ind[10]), 'ind_10_')
+        self.assertEqual(lbl(m.ind[1]), 'ind_1_')
+
+        # Test name collision
+        self.assertEqual(lbl(m.mycomp), 's_mycomp_5_')
+        self.assertEqual(lbl(self.thecopy), 's_yblock_mystreet_6_')
+        self.assertEqual(lbl(m.MyComp), 's_MyComp_7_')
+
+    def test_case_sensitive_shortnamelabeler(self):
+        m = self.m
+        lbl = ShortNameLabeler(20, '_')
         self.assertEqual(lbl(m.mycomp), 'mycomp')
         self.assertEqual(lbl(m.that), 'that')
         self.assertEqual(lbl(self.long1), 'longcomponentname_1_')
@@ -146,11 +188,34 @@ def test_shortnamelabeler(self):
         self.assertEqual(lbl(self.long5), 'gcomponentname_1__5_')
         self.assertEqual(lbl(m.myblock), 'myblock')
         self.assertEqual(lbl(m.myblock.mystreet), 'myblock_mystreet')
-        self.assertEqual(lbl(self.thecopy), 'myblock_mystreet')
         self.assertEqual(lbl(m.ind[3]), 'ind_3_')
         self.assertEqual(lbl(m.ind[10]), 'ind_10_')
         self.assertEqual(lbl(m.ind[1]), 'ind_1_')
 
+        # Test name collision
+        self.assertEqual(lbl(m.mycomp), 'mycomp')
+        self.assertEqual(lbl(self.thecopy), 'myblock_mystreet')
+        self.assertEqual(lbl(m.MyComp), 'MyComp')
+
+    def test_case_shortnamelabeler_overflow(self):
+        m = self.m
+        lbl = ShortNameLabeler(4, '_', caseInsensitive=True)
+        for i in range(9):
+            self.assertEqual(lbl(m.mycomp), 'p_%d_' % (i+1))
+        with self.assertRaisesRegexp(RuntimeError, "Too many identifiers"):
+            lbl(m.mycomp)
+
+    def test_shortnamelabeler_legal_regex(self):
+        m = ConcreteModel()
+        lbl = ShortNameLabeler(
+            60, suffix='_', prefix='s_', legalRegex='^[a-zA-Z]')
+
+        m.legal_var = Var()
+        self.assertEqual(lbl(m.legal_var), 'legal_var')
+
+        m._illegal_var = Var()
+        self.assertEqual(lbl(m._illegal_var), 's__illegal_var_1_')
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/pyomo/repn/tests/gams/small14.pyomo.gms b/pyomo/repn/tests/gams/small14a.pyomo.gms
similarity index 68%
rename from pyomo/repn/tests/gams/small14.pyomo.gms
rename to pyomo/repn/tests/gams/small14a.pyomo.gms
--- a/pyomo/repn/tests/gams/small14.pyomo.gms
+++ b/pyomo/repn/tests/gams/small14a.pyomo.gms
@@ -17,10 +17,7 @@ EQUATIONS
 	c14
 	c15
 	c16
-	c17
-	c18
-	c19
-	c20;
+	c17;
 
 VARIABLES
 	GAMS_OBJECTIVE
@@ -35,24 +32,21 @@ c5.. tan(x2) =e= 0.0 ;
 c6.. sinh(x2) =e= 0.0 ;
 c7.. cosh(x2) =e= 1.0 ;
 c8.. tanh(x2) =e= 0.0 ;
-c9.. asin(x2) =e= 0.0 ;
-c10.. acos(x2) =e= 1.5707963267948966 ;
-c11.. atan(x2) =e= 0.0 ;
-c12.. asinh(x2) =e= 0.0 ;
-c13.. acosh((7.3890560989306495 + x1)*0.18393972058572117) =e= 0.0 ;
-c14.. atanh(x2) =e= 0.0 ;
-c15.. exp(x2) =e= 1.0 ;
-c16.. sqrt(x1) =e= 1.0 ;
-c17.. ceil(x1) =e= 1.0 ;
-c18.. floor(x1) =e= 1.0 ;
-c19.. abs(x1) =e= 1.0 ;
-c20.. GAMS_OBJECTIVE =e= x1 + x2 ;
+c9.. arcsin(x2) =e= 0.0 ;
+c10.. arccos(x2) =e= 1.5707963267948966 ;
+c11.. arctan(x2) =e= 0.0 ;
+c12.. exp(x2) =e= 1.0 ;
+c13.. sqrt(x1) =e= 1.0 ;
+c14.. ceil(x1) =e= 1.0 ;
+c15.. floor(x1) =e= 1.0 ;
+c16.. abs(x1) =e= 1.0 ;
+c17.. GAMS_OBJECTIVE =e= x1 + x2 ;
 
 x1.l = 1;
 x2.l = 0;
 
 MODEL GAMS_MODEL /all/ ;
-SOLVE GAMS_MODEL USING nlp minimizing GAMS_OBJECTIVE;
+SOLVE GAMS_MODEL USING dnlp minimizing GAMS_OBJECTIVE;
 
 Scalars MODELSTAT 'model status', SOLVESTAT 'solve status';
 MODELSTAT = GAMS_MODEL.modelstat;
diff --git a/pyomo/repn/tests/gams/small14a_testCase.py b/pyomo/repn/tests/gams/small14a_testCase.py
new file mode 100644
--- /dev/null
+++ b/pyomo/repn/tests/gams/small14a_testCase.py
@@ -0,0 +1,46 @@
+#  ___________________________________________________________________________
+#
+#  Pyomo: Python Optimization Modeling Objects
+#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
+#  Under the terms of Contract DE-NA0003525 with National Technology and
+#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
+#  rights in this software.
+#  This software is distributed under the 3-clause BSD License.
+#  ___________________________________________________________________________
+
+from pyomo.environ import *
+from math import e, pi
+
+model = ConcreteModel()
+
+# pick a value in the domain of all of these functions
+model.ONE = Var(initialize=1)
+model.ZERO = Var(initialize=0)
+
+
+model.obj = Objective(expr=model.ONE+model.ZERO)
+
+model.c_log     = Constraint(expr=log(model.ONE) == 0)
+model.c_log10   = Constraint(expr=log10(model.ONE) == 0)
+
+model.c_sin     = Constraint(expr=sin(model.ZERO) == 0)
+model.c_cos     = Constraint(expr=cos(model.ZERO) == 1)
+model.c_tan     = Constraint(expr=tan(model.ZERO) == 0)
+
+model.c_sinh    = Constraint(expr=sinh(model.ZERO) == 0)
+model.c_cosh    = Constraint(expr=cosh(model.ZERO) == 1)
+model.c_tanh    = Constraint(expr=tanh(model.ZERO) == 0)
+
+model.c_asin    = Constraint(expr=asin(model.ZERO) == 0)
+model.c_acos    = Constraint(expr=acos(model.ZERO) == pi/2)
+model.c_atan    = Constraint(expr=atan(model.ZERO) == 0)
+
+#model.c_asinh   = Constraint(expr=asinh(model.ZERO) == 0)
+#model.c_acosh   = Constraint(expr=acosh((e**2 + model.ONE)/(2*e)) == 0)
+#model.c_atanh   = Constraint(expr=atanh(model.ZERO) == 0)
+
+model.c_exp     = Constraint(expr=exp(model.ZERO) == 1)
+model.c_sqrt    = Constraint(expr=sqrt(model.ONE) == 1)
+model.c_ceil    = Constraint(expr=ceil(model.ONE) == 1)
+model.c_floor   = Constraint(expr=floor(model.ONE) == 1)
+model.c_abs     = Constraint(expr=abs(model.ONE) == 1)
diff --git a/pyomo/repn/tests/gams/test_gams.py b/pyomo/repn/tests/gams/test_gams.py
--- a/pyomo/repn/tests/gams/test_gams.py
+++ b/pyomo/repn/tests/gams/test_gams.py
@@ -18,7 +18,8 @@
 import pyutilib.th as unittest
 from pyomo.core.base import NumericLabeler, SymbolMap
 from pyomo.environ import (Block, ConcreteModel, Connector, Constraint,
-                           Objective, TransformationFactory, Var, exp, log)
+                           Objective, TransformationFactory, Var, exp, log,
+                           ceil, floor, asin, acos, atan, asinh, acosh, atanh)
 from pyomo.repn.plugins.gams_writer import (StorageTreeChecker,
                                             expression_to_string,
                                             split_long_line)
@@ -129,32 +130,32 @@ def test_expr_xfrm(self):
         expr = M.abc**2.0
         self.assertEqual(str(expr), "abc**2.0")
         self.assertEqual(expression_to_string(
-            expr, tc, smap=smap), "power(abc, 2.0)")
+            expr, tc, smap=smap), ("power(abc, 2.0)", False))
 
         expr = log(M.abc**2.0)
         self.assertEqual(str(expr), "log(abc**2.0)")
         self.assertEqual(expression_to_string(
-            expr, tc, smap=smap), "log(power(abc, 2.0))")
+            expr, tc, smap=smap), ("log(power(abc, 2.0))", False))
 
         expr = log(M.abc**2.0) + 5
         self.assertEqual(str(expr), "log(abc**2.0) + 5")
         self.assertEqual(expression_to_string(
-            expr, tc, smap=smap), "log(power(abc, 2.0)) + 5")
+            expr, tc, smap=smap), ("log(power(abc, 2.0)) + 5", False))
 
         expr = exp(M.abc**2.0) + 5
         self.assertEqual(str(expr), "exp(abc**2.0) + 5")
         self.assertEqual(expression_to_string(
-            expr, tc, smap=smap), "exp(power(abc, 2.0)) + 5")
+            expr, tc, smap=smap), ("exp(power(abc, 2.0)) + 5", False))
 
         expr = log(M.abc**2.0)**4
         self.assertEqual(str(expr), "log(abc**2.0)**4")
         self.assertEqual(expression_to_string(
-            expr, tc, smap=smap), "power(log(power(abc, 2.0)), 4)")
+            expr, tc, smap=smap), ("power(log(power(abc, 2.0)), 4)", False))
 
         expr = log(M.abc**2.0)**4.5
         self.assertEqual(str(expr), "log(abc**2.0)**4.5")
         self.assertEqual(expression_to_string(
-            expr, tc, smap=smap), "log(power(abc, 2.0)) ** 4.5")
+            expr, tc, smap=smap), ("log(power(abc, 2.0)) ** 4.5", False))
 
     def test_power_function_to_string(self):
         m = ConcreteModel()
@@ -163,11 +164,11 @@ def test_power_function_to_string(self):
         smap = SymbolMap(lbl)
         tc = StorageTreeChecker(m)
         self.assertEqual(expression_to_string(
-            m.x ** -3, tc, lbl, smap=smap), "power(x1, (-3))")
+            m.x ** -3, tc, lbl, smap=smap), ("power(x1, (-3))", False))
         self.assertEqual(expression_to_string(
-            m.x ** 0.33, tc, smap=smap), "x1 ** 0.33")
+            m.x ** 0.33, tc, smap=smap), ("x1 ** 0.33", False))
         self.assertEqual(expression_to_string(
-            pow(m.x, 2), tc, smap=smap), "power(x1, 2)")
+            pow(m.x, 2), tc, smap=smap), ("power(x1, 2)", False))
 
     def test_fixed_var_to_string(self):
         m = ConcreteModel()
@@ -179,16 +180,56 @@ def test_fixed_var_to_string(self):
         smap = SymbolMap(lbl)
         tc = StorageTreeChecker(m)
         self.assertEqual(expression_to_string(
-            m.x + m.y - m.z, tc, lbl, smap=smap), "x1 + x2 - (-3)")
+            m.x + m.y - m.z, tc, lbl, smap=smap), ("x1 + x2 - (-3)", False))
         m.z.fix(-400)
         self.assertEqual(expression_to_string(
-            m.z + m.y - m.z, tc, smap=smap), "(-400) + x2 - (-400)")
+            m.z + m.y - m.z, tc, smap=smap), ("(-400) + x2 - (-400)", False))
         m.z.fix(8.8)
         self.assertEqual(expression_to_string(
-            m.x + m.z - m.y, tc, smap=smap), "x1 + 8.8 - x2")
+            m.x + m.z - m.y, tc, smap=smap), ("x1 + 8.8 - x2", False))
         m.z.fix(-8.8)
         self.assertEqual(expression_to_string(
-            m.x * m.z - m.y, tc, smap=smap), "x1*(-8.8) - x2")
+            m.x * m.z - m.y, tc, smap=smap), ("x1*(-8.8) - x2", False))
+
+    def test_dnlp_to_string(self):
+        m = ConcreteModel()
+        m.x = Var()
+        m.y = Var()
+        m.z = Var()
+        lbl = NumericLabeler('x')
+        smap = SymbolMap(lbl)
+        tc = StorageTreeChecker(m)
+        self.assertEqual(expression_to_string(
+            ceil(m.x), tc, lbl, smap=smap), ("ceil(x1)", True))
+        self.assertEqual(expression_to_string(
+            floor(m.x), tc, lbl, smap=smap), ("floor(x1)", True))
+        self.assertEqual(expression_to_string(
+            abs(m.x), tc, lbl, smap=smap), ("abs(x1)", True))
+
+    def test_arcfcn_to_string(self):
+        m = ConcreteModel()
+        m.x = Var()
+        lbl = NumericLabeler('x')
+        smap = SymbolMap(lbl)
+        tc = StorageTreeChecker(m)
+        self.assertEqual(expression_to_string(
+            asin(m.x), tc, lbl, smap=smap), ("arcsin(x1)", False))
+        self.assertEqual(expression_to_string(
+            acos(m.x), tc, lbl, smap=smap), ("arccos(x1)", False))
+        self.assertEqual(expression_to_string(
+            atan(m.x), tc, lbl, smap=smap), ("arctan(x1)", False))
+        with self.assertRaisesRegexp(
+                RuntimeError,
+                "GAMS files cannot represent the unary function asinh"):
+            expression_to_string(asinh(m.x), tc, lbl, smap=smap)
+        with self.assertRaisesRegexp(
+                RuntimeError,
+                "GAMS files cannot represent the unary function acosh"):
+            expression_to_string(acosh(m.x), tc, lbl, smap=smap)
+        with self.assertRaisesRegexp(
+                RuntimeError,
+                "GAMS files cannot represent the unary function atanh"):
+            expression_to_string(atanh(m.x), tc, lbl, smap=smap)
 
     def test_gams_connector_in_active_constraint(self):
         m = ConcreteModel()
@@ -260,9 +301,9 @@ def test_negative_float_double_operator(self):
         smap = SymbolMap(lbl)
         tc = StorageTreeChecker(m)
         self.assertEqual(expression_to_string(
-            m.c.body, tc, smap=smap), "4*(-7)*(-2)")
+            m.c.body, tc, smap=smap), ("4*(-7)*(-2)", False))
         self.assertEqual(expression_to_string(
-            m.c2.body, tc, smap=smap), "x1 ** (-1.5)")
+            m.c2.body, tc, smap=smap), ("x1 ** (-1.5)", False))
 
 
 class TestGams_writer(unittest.TestCase):
diff --git a/pyomo/repn/tests/gams/test_gams_comparison.py b/pyomo/repn/tests/gams/test_gams_comparison.py
--- a/pyomo/repn/tests/gams/test_gams_comparison.py
+++ b/pyomo/repn/tests/gams/test_gams_comparison.py
@@ -37,37 +37,50 @@ def __init__(self, *args, **kwds):
 BaselineTests = unittest.category('smoke', 'nightly','expensive')(BaselineTests)
 
 #
-#The following test generates an nl file for the test case
-#and checks that it matches the current pyomo baseline nl file
+# The following test generates a GMS file for the test case
+# and checks that it matches the current pyomo baseline GMS file
 #
 @unittest.nottest
-def gams_writer_baseline_test(self, name):
-    if os.path.exists(datadir+name+'.dat'):
+def gams_writer_baseline_test(self, name, targetdir):
+    if os.path.exists(targetdir+name+'.dat'):
         self.pyomo(['--output='+currdir+name+'.test.gms',
-                    datadir+name+'_testCase.py',
-                    datadir+name+'.dat'])
+                    targetdir+name+'_testCase.py',
+                    targetdir+name+'.dat'])
     else:
         self.pyomo(['--output='+currdir+name+'.test.gms',
-                    datadir+name+'_testCase.py'])
+                    targetdir+name+'_testCase.py'])
 
     # Check that the pyomo nl file matches its own baseline
     self.assertFileEqualsBaseline(
         currdir+name+'.test.gms', currdir+name+'.pyomo.gms',
         tolerance=(1e-7, False))
 
-
-class ASLTests(Tests):
-
-    def __init__(self, *args, **kwds):
-        Tests.__init__(self, *args, **kwds)
-ASLTests = unittest.category('smoke','nightly','expensive')(ASLTests)
-
+@unittest.nottest
+def gams_writer_test_invalid(self, name, targetdir):
+    with self.assertRaisesRegexp(
+            RuntimeError, "GAMS files cannot represent the unary function"):
+        if os.path.exists(targetdir+name+'.dat'):
+            self.pyomo(['--output='+currdir+name+'.test.gms',
+                        targetdir+name+'_testCase.py',
+                        targetdir+name+'.dat'])
+        else:
+            self.pyomo(['--output='+currdir+name+'.test.gms',
+                        targetdir+name+'_testCase.py'])
 
 # add test methods to classes
+invalid_tests = {'small14',}
 for f in glob.glob(datadir+'*_testCase.py'):
     name = re.split('[._]',os.path.basename(f))[0]
-    BaselineTests.add_fn_test(fn=gams_writer_baseline_test, name=name)
-    #ASLTests.add_fn_test(fn=nlwriter_asl_test, name=name)
+    if name in invalid_tests:
+        fcn = lambda s,name: gams_writer_test_invalid(s,name,datadir)
+    else:
+        fcn = lambda s,name: gams_writer_baseline_test(s,name,datadir)
+    BaselineTests.add_fn_test(fn=fcn, name=name)
+
+for f in glob.glob(currdir+'*_testCase.py'):
+    name = re.split('[._]',os.path.basename(f))[0]
+    fcn = lambda s,name: gams_writer_baseline_test(s,name,currdir)
+    BaselineTests.add_fn_test(fn=fcn, name=name)
 
 if __name__ == "__main__":
     unittest.main()