Repository: Pyomo/pyomo

Issue:
Quadratic term not identified by generate_standard_repn
```
>>> import pyomo.environ as pe                                                                                                                                                                                               
>>> from pyomo.repn.standard_repn import generate_standard_repn
>>> m = pe.ConcreteModel()
>>> m.x = pe.Var()
>>> m.y = pe.Var()
>>> print(generate_standard_repn((m.x + m.y)**2, quadratic=True))

constant:       0
linear vars:    []
linear var ids: []
linear coef:    []
quadratic vars:    []
quadratic var ids: []
quadratic coef:    []
nonlinear expr:
(x + y)**2
nonlinear vars: ['x', 'y']
```


Discussion:
@michaelbynum good catch. We should also consider adding an explicit QuadraticExpression object like we have for LinearExpression.

Resolving patch:
diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py
--- a/pyomo/repn/standard_repn.py
+++ b/pyomo/repn/standard_repn.py
@@ -734,16 +734,29 @@ def _collect_pow(exp, multiplier, idMap, compute_values, verbose, quadratic):
             elif compute_values and len(res.linear) == 0:
                 return Results(constant=multiplier*res.constant**exponent)
             #
-            # If there is one linear term, then we compute the quadratic expression for it.
+            # If the base is linear, then we compute the quadratic expression for it.
             #
-            elif len(res.linear) == 1:
-                key, coef = res.linear.popitem()
-                var = idMap[key]
+            else:
                 ans = Results()
-                if not (res.constant.__class__ in native_numeric_types and res.constant == 0):
+                has_constant = (res.constant.__class__
+                                not in native_numeric_types
+                                or res.constant != 0)
+                if has_constant:
                     ans.constant = multiplier*res.constant*res.constant
-                    ans.linear[key] = 2*multiplier*coef*res.constant
-                ans.quadratic[key,key] = multiplier*coef*coef
+
+                # this is reversed since we want to pop off the end for efficiency
+                # and the quadratic terms have a convention that the indexing tuple
+                # of key1, key2 is such that key1 <= key2
+                keys = sorted(res.linear.keys(), reverse=True)
+                while len(keys) > 0:
+                    key1 = keys.pop()
+                    coef1 = res.linear[key1]
+                    if has_constant:
+                        ans.linear[key1] = 2*multiplier*coef1*res.constant
+                    ans.quadratic[key1,key1] = multiplier*coef1*coef1
+                    for key2 in keys:
+                        coef2 = res.linear[key2]
+                        ans.quadratic[key1,key2] = 2*multiplier*coef1*coef2
                 return ans
 
     #


Test patch:
diff --git a/pyomo/repn/tests/test_standard.py b/pyomo/repn/tests/test_standard.py
--- a/pyomo/repn/tests/test_standard.py
+++ b/pyomo/repn/tests/test_standard.py
@@ -3141,19 +3141,25 @@ def test_pow5(self):
         rep = generate_standard_repn(e, compute_values=False, quadratic=True)
         #
         self.assertFalse( rep.is_fixed() )
-        self.assertEqual( rep.polynomial_degree(), None )
+        self.assertEqual( rep.polynomial_degree(), 2 )
         self.assertFalse( rep.is_constant() )
         self.assertFalse( rep.is_linear() )
-        self.assertFalse( rep.is_quadratic() )
+        self.assertTrue( rep.is_quadratic() )
         self.assertTrue( rep.is_nonlinear() )
         #
         self.assertTrue(len(rep.linear_vars) == 0)
         self.assertTrue(len(rep.linear_coefs) == 0)
-        self.assertTrue(len(rep.quadratic_vars) == 0)
-        self.assertTrue(len(rep.quadratic_coefs) == 0)
-        self.assertFalse(rep.nonlinear_expr is None)
-        self.assertTrue(len(rep.nonlinear_vars) == 2)
-        baseline = { }
+        self.assertTrue(len(rep.quadratic_vars) == 3)
+        self.assertTrue(len(rep.quadratic_coefs) == 3)
+        self.assertTrue(rep.nonlinear_expr is None)
+        self.assertTrue(len(rep.nonlinear_vars) == 0)
+        baseline = { (id(m.a), id(m.a)): 1,
+                     (id(m.b), id(m.b)): 1 }
+        if id(m.a) < id(m.b):
+            baseline[id(m.a), id(m.b)] = 2
+        else:
+            baseline[id(m.b), id(m.a)] = 2
+
         self.assertEqual(baseline, repn_to_dict(rep))
 
         e = (m.a+3)**2
@@ -3258,6 +3264,51 @@ def test_pow6(self):
         baseline = { None:8 }
         self.assertEqual(baseline, repn_to_dict(rep))
 
+    def test_pow_of_lin_sum(self):
+        m = ConcreteModel()
+        m.x = Var(range(4))
+        e = sum(x for x in m.x.values())**2
+
+        rep = generate_standard_repn(e, compute_values=False, quadratic=False)
+        #
+        self.assertFalse( rep.is_fixed() )
+        self.assertEqual( rep.polynomial_degree(), None )
+        self.assertFalse( rep.is_constant() )
+        self.assertFalse( rep.is_linear() )
+        self.assertFalse( rep.is_quadratic() )
+        self.assertTrue( rep.is_nonlinear() )
+        #
+        self.assertTrue(len(rep.linear_vars) == 0)
+        self.assertTrue(len(rep.linear_coefs) == 0)
+        self.assertTrue(len(rep.quadratic_vars) == 0)
+        self.assertTrue(len(rep.quadratic_coefs) == 0)
+        self.assertFalse(rep.nonlinear_expr is None)
+        self.assertTrue(len(rep.nonlinear_vars) == 4)
+        baseline = { }
+        self.assertEqual(baseline, repn_to_dict(rep))
+
+        rep = generate_standard_repn(e, compute_values=False, quadratic=True)
+        #
+        self.assertFalse( rep.is_fixed() )
+        self.assertEqual( rep.polynomial_degree(), 2 )
+        self.assertFalse( rep.is_constant() )
+        self.assertFalse( rep.is_linear() )
+        self.assertTrue( rep.is_quadratic() )
+        self.assertTrue( rep.is_nonlinear() )
+        #
+        self.assertTrue(len(rep.linear_vars) == 0)
+        self.assertTrue(len(rep.linear_coefs) == 0)
+        self.assertTrue(len(rep.quadratic_vars) == 10)
+        self.assertTrue(len(rep.quadratic_coefs) == 10)
+        self.assertTrue(rep.nonlinear_expr is None)
+        self.assertTrue(len(rep.nonlinear_vars) == 0)
+        baseline = {(id(i), id(j)): 2
+                    for i in m.x.values()
+                    for j in m.x.values()
+                    if id(i) < id(j)}
+        baseline.update({(id(i), id(i)): 1 for i in m.x.values()})
+        self.assertEqual(baseline, repn_to_dict(rep))
+
     def test_fixed_exponent(self):
         m = ConcreteModel()
         m.x = Var()