Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identify squared linear sums as quadratic with generate_standard_repn #1493

Merged
merged 8 commits into from
Jun 17, 2020
27 changes: 20 additions & 7 deletions pyomo/repn/standard_repn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

#
Expand Down
65 changes: 58 additions & 7 deletions pyomo/repn/tests/test_standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down