diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 6b416b82c2b..1ed26f72320 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -555,6 +555,20 @@ def assertRaisesRegex(self, expected_exception, expected_regex, *args, **kwargs) context = contextClass(expected_exception, self, expected_regex) return context.handle('assertRaisesRegex', args, kwargs) + def assertExpressionsEqual(self, a, b, include_named_exprs=True, places=None): + from pyomo.core.expr.compare import assertExpressionsEqual + + return assertExpressionsEqual(self, a, b, include_named_exprs, places) + + def assertExpressionsStructurallyEqual( + self, a, b, include_named_exprs=True, places=None + ): + from pyomo.core.expr.compare import assertExpressionsStructurallyEqual + + return assertExpressionsStructurallyEqual( + self, a, b, include_named_exprs, places + ) + class BaselineTestDriver(object): """Generic driver for performing baseline tests in bulk diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index 13af5adc9bb..82f1e5c11c4 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -104,10 +104,6 @@ MinExpression, _balanced_parens, ) -from pyomo.core.expr.compare import ( - assertExpressionsEqual, - assertExpressionsStructurallyEqual, -) from pyomo.core.expr.relational_expr import RelationalExpression, EqualityExpression from pyomo.core.expr.relational_expr import RelationalExpression, EqualityExpression from pyomo.common.errors import PyomoException @@ -642,8 +638,7 @@ def test_simpleSum(self): m.b = Var() e = m.a + m.b # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] @@ -658,8 +653,7 @@ def test_simpleSum_API(self): m.b = Var() e = m.a + m.b e += 2 * m.a - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -675,12 +669,12 @@ def test_constSum(self): m = AbstractModel() m.a = Var() # - assertExpressionsEqual( - self, m.a + 5, LinearExpression([MonomialTermExpression((1, m.a)), 5]) + self.assertExpressionsEqual( + m.a + 5, LinearExpression([MonomialTermExpression((1, m.a)), 5]) ) - assertExpressionsEqual( - self, 5 + m.a, LinearExpression([5, MonomialTermExpression((1, m.a))]) + self.assertExpressionsEqual( + 5 + m.a, LinearExpression([5, MonomialTermExpression((1, m.a))]) ) def test_nestedSum(self): @@ -702,8 +696,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + 5 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] @@ -717,8 +710,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = 5 + e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] @@ -732,8 +724,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + m.c - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -751,8 +742,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = m.c + e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -772,8 +762,7 @@ def test_nestedSum(self): e2 = m.c + m.d e = e1 + e2 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -807,8 +796,7 @@ def test_nestedSum2(self): e1 = m.a + m.b e = 2 * e1 + m.c - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -840,8 +828,7 @@ def test_nestedSum2(self): e1 = m.a + m.b e = 3 * (2 * e1 + m.c) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, ProductExpression( ( @@ -903,8 +890,7 @@ def test_sumOf_nestedTrivialProduct(self): e1 = m.a * 5 e = e1 + m.b # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((5, m.a)), MonomialTermExpression((1, m.b))] @@ -918,8 +904,7 @@ def test_sumOf_nestedTrivialProduct(self): # a 5 e = m.b + e1 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.b)), MonomialTermExpression((5, m.a))] @@ -934,8 +919,7 @@ def test_sumOf_nestedTrivialProduct(self): e2 = m.b + m.c e = e1 + e2 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -954,8 +938,7 @@ def test_sumOf_nestedTrivialProduct(self): e2 = m.b + m.c e = e2 + e1 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -978,8 +961,7 @@ def test_simpleDiff(self): # / \ # a b e = m.a - m.b - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((-1, m.b))] @@ -996,15 +978,15 @@ def test_constDiff(self): # - # / \ # a 5 - assertExpressionsEqual( - self, m.a - 5, LinearExpression([MonomialTermExpression((1, m.a)), -5]) + self.assertExpressionsEqual( + m.a - 5, LinearExpression([MonomialTermExpression((1, m.a)), -5]) ) # - # / \ # 5 a - assertExpressionsEqual( - self, 5 - m.a, LinearExpression([5, MonomialTermExpression((-1, m.a))]) + self.assertExpressionsEqual( + 5 - m.a, LinearExpression([5, MonomialTermExpression((-1, m.a))]) ) def test_paramDiff(self): @@ -1019,8 +1001,7 @@ def test_paramDiff(self): # / \ # a p e = m.a - m.p - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), NPV_NegationExpression((m.p,))] @@ -1031,8 +1012,8 @@ def test_paramDiff(self): # / \ # m.p a e = m.p - m.a - assertExpressionsEqual( - self, e, LinearExpression([m.p, MonomialTermExpression((-1, m.a))]) + self.assertExpressionsEqual( + e, LinearExpression([m.p, MonomialTermExpression((-1, m.a))]) ) def test_constparamDiff(self): @@ -1076,8 +1057,8 @@ def test_termDiff(self): e = 5 - 2 * m.a - assertExpressionsEqual( - self, e, LinearExpression([5, MonomialTermExpression((-2, m.a))]) + self.assertExpressionsEqual( + e, LinearExpression([5, MonomialTermExpression((-2, m.a))]) ) def test_nestedDiff(self): @@ -1097,8 +1078,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = e1 - 5 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1116,8 +1096,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = 5 - e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1143,8 +1122,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = e1 - m.c - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1162,8 +1140,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = m.c - e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1190,8 +1167,7 @@ def test_nestedDiff(self): e1 = m.a - m.b e2 = m.c - m.d e = e1 - e2 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1233,8 +1209,8 @@ def test_negation_mutableparam(self): m = AbstractModel() m.p = Param(mutable=True, initialize=1.0) e = -m.p - assertExpressionsEqual(self, e, NPV_NegationExpression((m.p,))) - assertExpressionsEqual(self, -e, m.p) + self.assertExpressionsEqual(e, NPV_NegationExpression((m.p,))) + self.assertExpressionsEqual(-e, m.p) def test_negation_terms(self): # @@ -1244,15 +1220,15 @@ def test_negation_terms(self): m.v = Var() m.p = Param(mutable=True, initialize=1.0) e = -m.p * m.v - assertExpressionsEqual( - self, e, MonomialTermExpression((NPV_NegationExpression((m.p,)), m.v)) + self.assertExpressionsEqual( + e, MonomialTermExpression((NPV_NegationExpression((m.p,)), m.v)) ) - assertExpressionsEqual(self, -e, MonomialTermExpression((m.p, m.v))) + self.assertExpressionsEqual(-e, MonomialTermExpression((m.p, m.v))) # e = -5 * m.v - assertExpressionsEqual(self, e, MonomialTermExpression((-5, m.v))) - assertExpressionsEqual(self, -e, MonomialTermExpression((5, m.v))) + self.assertExpressionsEqual(e, MonomialTermExpression((-5, m.v))) + self.assertExpressionsEqual(-e, MonomialTermExpression((5, m.v))) def test_trivialDiff(self): # @@ -1389,8 +1365,7 @@ def test_sumOf_nestedTrivialProduct2(self): # a 5 e1 = m.a * m.p e = e1 - m.b - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((m.p, m.a)), MonomialTermExpression((-1, m.b))] @@ -1404,8 +1379,7 @@ def test_sumOf_nestedTrivialProduct2(self): # a 5 e1 = m.a * m.p e = m.b - e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1423,8 +1397,7 @@ def test_sumOf_nestedTrivialProduct2(self): e1 = m.a * m.p e2 = m.b - m.c e = e1 - e2 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1452,8 +1425,7 @@ def test_sumOf_nestedTrivialProduct2(self): e2 = m.b - m.c e = e2 - e1 self.maxDiff = None - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1624,8 +1596,7 @@ def test_nestedProduct2(self): e3 = e1 + m.d e = e2 * e3 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, ProductExpression( ( @@ -1671,8 +1642,7 @@ def test_nestedProduct2(self): inner = LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] ) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, ProductExpression( (ProductExpression((m.c, inner)), ProductExpression((inner, m.d))) @@ -1711,8 +1681,8 @@ def test_nestedProduct3(self): # a b e1 = m.a * m.b e = e1 * 5 - assertExpressionsEqual( - self, e, MonomialTermExpression((NPV_ProductExpression((m.a, 5)), m.b)) + self.assertExpressionsEqual( + e, MonomialTermExpression((NPV_ProductExpression((m.a, 5)), m.b)) ) # * @@ -1801,37 +1771,37 @@ def test_trivialProduct(self): m.q = Param(initialize=1) e = m.a * 0 - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) e = 0 * m.a - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) e = m.a * m.p - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) e = m.p * m.a - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) # # Check that multiplying by one gives the original expression # e = m.a * 1 - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) e = 1 * m.a - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) e = m.a * m.q - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) e = m.q * m.a - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) # # Check that numeric constants are simply muliplied out # e = NumericConstant(3) * NumericConstant(2) - assertExpressionsEqual(self, e, 6) + self.assertExpressionsEqual(e, 6) self.assertIs(type(e), int) self.assertEqual(e, 6) @@ -1996,19 +1966,19 @@ def test_trivialDivision(self): # Check that dividing zero by anything non-zero gives zero # e = 0 / m.a - assertExpressionsEqual(self, e, DivisionExpression((0, m.a))) + self.assertExpressionsEqual(e, DivisionExpression((0, m.a))) # # Check that dividing by one 1 gives the original expression # e = m.a / 1 - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) # # Check the structure dividing 1 by an expression # e = 1 / m.a - assertExpressionsEqual(self, e, DivisionExpression((1, m.a))) + self.assertExpressionsEqual(e, DivisionExpression((1, m.a))) # # Check the structure dividing 1 by an expression @@ -3782,8 +3752,7 @@ def tearDown(self): def test_summation1(self): e = sum_product(self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3798,8 +3767,7 @@ def test_summation1(self): def test_summation2(self): e = sum_product(self.m.p, self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3814,8 +3782,7 @@ def test_summation2(self): def test_summation3(self): e = sum_product(self.m.q, self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3830,8 +3797,7 @@ def test_summation3(self): def test_summation4(self): e = sum_product(self.m.a, self.m.b) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -3846,8 +3812,7 @@ def test_summation4(self): def test_summation5(self): e = sum_product(self.m.b, denom=self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -3862,8 +3827,7 @@ def test_summation5(self): def test_summation6(self): e = sum_product(self.m.a, denom=self.m.p) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3888,8 +3852,7 @@ def test_summation6(self): def test_summation7(self): e = sum_product(self.m.p, self.m.q, index=self.m.I) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, NPV_SumExpression( [ @@ -3906,8 +3869,7 @@ def test_summation_compression(self): e1 = sum_product(self.m.a) e2 = sum_product(self.m.b) e = e1 + e2 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3948,8 +3910,7 @@ def test_deprecation(self): r"DEPRECATED: The quicksum\(linear=...\) argument is deprecated " r"and ignored.", ) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3965,8 +3926,7 @@ def test_deprecation(self): def test_summation1(self): e = quicksum((self.m.a[i] for i in self.m.a)) self.assertEqual(e(), 25) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3982,8 +3942,7 @@ def test_summation1(self): def test_summation2(self): e = quicksum(self.m.p[i] * self.m.a[i] for i in self.m.a) self.assertEqual(e(), 25) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3999,8 +3958,7 @@ def test_summation2(self): def test_summation3(self): e = quicksum(self.m.q[i] * self.m.a[i] for i in self.m.a) self.assertEqual(e(), 75) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -4016,8 +3974,7 @@ def test_summation3(self): def test_summation4(self): e = quicksum(self.m.a[i] * self.m.b[i] for i in self.m.a) self.assertEqual(e(), 250) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -4033,8 +3990,7 @@ def test_summation4(self): def test_summation5(self): e = quicksum(self.m.b[i] / self.m.a[i] for i in self.m.a) self.assertEqual(e(), 10) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -4050,8 +4006,7 @@ def test_summation5(self): def test_summation6(self): e = quicksum(self.m.a[i] / self.m.p[i] for i in self.m.a) self.assertEqual(e(), 25) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -4077,8 +4032,7 @@ def test_summation6(self): def test_summation7(self): e = quicksum((self.m.p[i] * self.m.q[i] for i in self.m.I), linear=False) self.assertEqual(e(), 15) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, NPV_SumExpression( [ @@ -4453,7 +4407,7 @@ def test_Expr_if(self): # expr1 = Expr_if(IF=self.m.a + self.m.b < 20, THEN=self.m.a, ELSE=self.m.b) expr2 = expr1.clone() - assertExpressionsStructurallyEqual(self, expr1, expr2) + self.assertExpressionsStructurallyEqual(expr1, expr2) self.assertIsNot(expr1, expr2) self.assertIsNot(expr1.arg(0), expr2.arg(0)) @@ -5029,7 +4983,7 @@ def test_init(self): self.assertEqual(e.linear_vars, [m.x, m.y]) self.assertEqual(e.linear_coefs, [2, 3]) - assertExpressionsEqual(self, e, f) + self.assertExpressionsEqual(e, f) args = [10, MonomialTermExpression((4, m.y)), MonomialTermExpression((5, m.x))] with LoggingIntercept() as OUT: @@ -5116,7 +5070,7 @@ def test_sum_other(self): with linear_expression() as e: e = e - arg - assertExpressionsEqual(self, e, -arg) + self.assertExpressionsEqual(e, -arg) def test_mul_other(self): m = ConcreteModel() @@ -5255,13 +5209,12 @@ def test_pow_other(self): with linear_expression() as e: e += m.p e = 2**e - assertExpressionsEqual(self, e, NPV_PowExpression((2, m.p))) + self.assertExpressionsEqual(e, NPV_PowExpression((2, m.p))) with linear_expression() as e: e += m.v[0] + m.v[1] e = m.v[0] ** e - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, PowExpression( ( @@ -5545,7 +5498,7 @@ def test_simple(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_sum(self): M = ConcreteModel() @@ -5556,7 +5509,7 @@ def test_sum(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def Xtest_Sum(self): M = ConcreteModel() @@ -5566,7 +5519,7 @@ def Xtest_Sum(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_prod(self): M = ConcreteModel() @@ -5577,7 +5530,7 @@ def test_prod(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_negation(self): M = ConcreteModel() @@ -5586,7 +5539,7 @@ def test_negation(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_reciprocal(self): M = ConcreteModel() @@ -5597,7 +5550,7 @@ def test_reciprocal(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_multisum(self): M = ConcreteModel() @@ -5608,7 +5561,7 @@ def test_multisum(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_linear(self): M = ConcreteModel() @@ -5621,7 +5574,7 @@ def test_linear(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_linear_context(self): M = ConcreteModel() @@ -5634,7 +5587,7 @@ def test_linear_context(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_ExprIf(self): M = ConcreteModel() @@ -5643,7 +5596,7 @@ def test_ExprIf(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_getitem(self): m = ConcreteModel() @@ -5656,7 +5609,7 @@ def test_getitem(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) self.assertEqual("x[{I} + P[{I} + 1]] + 3", str(e)) def test_abs(self): @@ -5666,7 +5619,7 @@ def test_abs(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) self.assertEqual(str(e), str(e_)) def test_sin(self): @@ -5676,7 +5629,7 @@ def test_sin(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) self.assertEqual(str(e), str(e_)) def test_external_fcn(self): @@ -5687,7 +5640,7 @@ def test_external_fcn(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) # diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index f79b5009122..1081b69acff 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -26,7 +26,7 @@ document_kwargs_from_configdict, ) from pyomo.common.deprecation import deprecation_warning -from pyomo.common.errors import DeveloperError, InfeasibleConstraintException +from pyomo.common.errors import DeveloperError, InfeasibleConstraintException, MouseTrap from pyomo.common.gc_manager import PauseGC from pyomo.common.numeric_types import ( native_complex_types, @@ -1536,7 +1536,8 @@ def write(self, model): # Generate the return information eliminated_vars = [ - (var_map[_id], expr_info) for _id, expr_info in eliminated_vars.items() + (var_map[_id], expr_info.to_expr(var_map)) + for _id, expr_info in eliminated_vars.items() ] eliminated_vars.reverse() if scale_model: @@ -2132,6 +2133,24 @@ def append(self, other): elif _type is _CONSTANT: self.const += other[1] + def to_expr(self, var_map): + if self.nl is not None or self.nonlinear is not None: + # TODO: support converting general nonlinear expressiosn + # back to Pyomo expressions. This will require an AMPL + # parser. + raise MouseTrap("Cannot convert nonlinear AMPLRepn to Pyomo Expression") + if self.linear: + # Explicitly generate the LinearExpression. At time of + # writing, this is about 40% faster than standard operator + # overloading for O(1000) element sums + ans = LinearExpression( + [coef * var_map[vid] for vid, coef in self.linear.items()] + ) + ans += self.const + else: + ans = self.const + return ans * self.mult + def _create_strict_inequality_map(vars_): vars_['strict_inequality_map'] = { diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index fe5f422d323..2e366039185 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -24,6 +24,7 @@ from pyomo.repn.tests.nl_diff import nl_diff from pyomo.common.dependencies import numpy, numpy_available +from pyomo.common.errors import MouseTrap from pyomo.common.log import LoggingIntercept from pyomo.common.tee import capture_output from pyomo.common.tempfiles import TempfileManager @@ -872,6 +873,67 @@ def test_duplicate_shared_linear_expressions(self): self.assertEqual(repn2.linear, {id(m.x): 102, id(m.y): 103}) self.assertEqual(repn2.nonlinear, None) + def test_AMPLRepn_to_expr(self): + m = ConcreteModel() + m.p = Param([2, 3, 4], mutable=True, initialize=lambda m, i: i**2) + m.x = Var([2, 3, 4], initialize=lambda m, i: i) + + e = 10 + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 10) + self.assertEqual(repn.linear, {}) + self.assertEqual(repn.nonlinear, None) + ee = repn.to_expr(info.var_map) + self.assertExpressionsEqual(ee, 10) + + e += sum(m.x[i] * m.p[i] for i in m.x) + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 10) + self.assertEqual(repn.linear, {id(m.x[2]): 4, id(m.x[3]): 9, id(m.x[4]): 16}) + self.assertEqual(repn.nonlinear, None) + ee = repn.to_expr(info.var_map) + self.assertExpressionsEqual(ee, 4 * m.x[2] + 9 * m.x[3] + 16 * m.x[4] + 10) + self.assertEqual(ee(), 10 + 8 + 27 + 64) + + e = sum(m.x[i] * m.p[i] for i in m.x) + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 0) + self.assertEqual(repn.linear, {id(m.x[2]): 4, id(m.x[3]): 9, id(m.x[4]): 16}) + self.assertEqual(repn.nonlinear, None) + ee = repn.to_expr(info.var_map) + self.assertExpressionsEqual(ee, 4 * m.x[2] + 9 * m.x[3] + 16 * m.x[4]) + self.assertEqual(ee(), 8 + 27 + 64) + + e += m.x[2] ** 2 + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 0) + self.assertEqual(repn.linear, {id(m.x[2]): 4, id(m.x[3]): 9, id(m.x[4]): 16}) + self.assertEqual(repn.nonlinear, ('o5\n%s\nn2\n', [id(m.x[2])])) + with self.assertRaisesRegex( + MouseTrap, "Cannot convert nonlinear AMPLRepn to Pyomo Expression" + ): + ee = repn.to_expr(info.var_map) + class Test_NLWriter(unittest.TestCase): def test_external_function_str_args(self): @@ -1263,12 +1325,7 @@ def test_presolve_lower_triangular(self): self.assertEqual( nlinfo.eliminated_vars, - [ - (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), - (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), - (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), - (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), - ], + [(m.x[3], -4.0), (m.x[1], 4.0), (m.x[2], 3.0), (m.x[0], 5.0)], ) self.assertEqual( *nl_diff( @@ -1314,12 +1371,7 @@ def test_presolve_lower_triangular_fixed(self): self.assertEqual( nlinfo.eliminated_vars, - [ - (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), - (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), - (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), - (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), - ], + [(m.x[3], -4.0), (m.x[1], 4.0), (m.x[2], 3.0), (m.x[0], 5.0)], ) self.assertEqual( *nl_diff( @@ -1367,11 +1419,11 @@ def test_presolve_lower_triangular_implied(self): self.assertEqual( nlinfo.eliminated_vars, [ - (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), - (m.x[5], nl_writer.AMPLRepn(5.0, {}, None)), - (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), - (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), - (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), + (m.x[1], 4.0), + (m.x[5], 5.0), + (m.x[3], -4.0), + (m.x[2], 3.0), + (m.x[0], 5.0), ], ) self.assertEqual( @@ -1415,15 +1467,18 @@ def test_presolve_almost_lower_triangular(self): nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") - self.assertEqual( - nlinfo.eliminated_vars, - [ - (m.x[4], nl_writer.AMPLRepn(-12, {id(m.x[1]): 3}, None)), - (m.x[3], nl_writer.AMPLRepn(-72, {id(m.x[1]): 17}, None)), - (m.x[2], nl_writer.AMPLRepn(-13, {id(m.x[1]): 4}, None)), - (m.x[0], nl_writer.AMPLRepn(29, {id(m.x[1]): -6}, None)), - ], - ) + self.assertIs(nlinfo.eliminated_vars[0][0], m.x[4]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[0][1], 3.0 * m.x[1] - 12.0) + + self.assertIs(nlinfo.eliminated_vars[1][0], m.x[3]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[1][1], 17.0 * m.x[1] - 72.0) + + self.assertIs(nlinfo.eliminated_vars[2][0], m.x[2]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[2][1], 4.0 * m.x[1] - 13.0) + + self.assertIs(nlinfo.eliminated_vars[3][0], m.x[0]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[3][1], -6.0 * m.x[1] + 29.0) + # Note: bounds on x[1] are: # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 @@ -1469,15 +1524,18 @@ def test_presolve_almost_lower_triangular_nonlinear(self): nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") - self.assertEqual( - nlinfo.eliminated_vars, - [ - (m.x[4], nl_writer.AMPLRepn(-12, {id(m.x[1]): 3}, None)), - (m.x[3], nl_writer.AMPLRepn(-72, {id(m.x[1]): 17}, None)), - (m.x[2], nl_writer.AMPLRepn(-13, {id(m.x[1]): 4}, None)), - (m.x[0], nl_writer.AMPLRepn(29, {id(m.x[1]): -6}, None)), - ], - ) + self.assertIs(nlinfo.eliminated_vars[0][0], m.x[4]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[0][1], 3.0 * m.x[1] - 12.0) + + self.assertIs(nlinfo.eliminated_vars[1][0], m.x[3]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[1][1], 17.0 * m.x[1] - 72.0) + + self.assertIs(nlinfo.eliminated_vars[2][0], m.x[2]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[2][1], 4.0 * m.x[1] - 13.0) + + self.assertIs(nlinfo.eliminated_vars[3][0], m.x[0]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[3][1], -6.0 * m.x[1] + 29.0) + # Note: bounds on x[1] are: # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 @@ -1575,9 +1633,7 @@ def test_presolve_named_expressions(self): ) self.assertEqual(LOG.getvalue(), "") - self.assertEqual( - nlinfo.eliminated_vars, [(m.x[1], nl_writer.AMPLRepn(7, {}, None))] - ) + self.assertEqual(nlinfo.eliminated_vars, [(m.x[1], 7)]) self.assertEqual( *nl_diff(