From 01878cfb04fcb4133968bbedf2edd9d02b7f1f80 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Aug 2021 17:15:08 -0600 Subject: [PATCH] Correct calculation of RangedExpression.equality for constant bounds --- pyomo/core/base/constraint.py | 11 +++- pyomo/core/tests/unit/test_con.py | 103 ++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 8120a84d1cd..ddec111d831 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -25,6 +25,7 @@ from pyomo.core.expr import logical_expr from pyomo.core.expr.numvalue import ( NumericValue, value, as_numeric, is_fixed, native_numeric_types, + is_constant, ) from pyomo.core.base.component import ( ActiveComponentData, ModelComponentFactory, @@ -411,9 +412,13 @@ def equality(self): if self._expr.__class__ is logical_expr.EqualityExpression: return True elif self._expr.__class__ is logical_expr.RangedExpression: - # TODO: this is a very restrictive form of structural equality. - lb = self._expr.arg(0) - if lb is not None and lb is self._expr.arg(2): + lb, _, ub = self._expr.args + if lb is None: + return False # unbounded + if lb is ub: + return True # structurally equal + if is_constant(lb) and is_constant(ub) and value(lb) == value(ub): + # Should we consider an equality tolerance? return True return False diff --git a/pyomo/core/tests/unit/test_con.py b/pyomo/core/tests/unit/test_con.py index e1791fa63c4..bf709e89aec 100644 --- a/pyomo/core/tests/unit/test_con.py +++ b/pyomo/core/tests/unit/test_con.py @@ -170,10 +170,105 @@ def rule(model): return (0, model.y, 1) model.c = Constraint(rule=rule) - self.assertEqual(model.c.equality, False) - self.assertEqual(model.c.lower, 0) - self.assertIs (model.c.body, model.y) - self.assertEqual(model.c.upper, 1) + self.assertEqual(model.c.equality, False) + self.assertEqual(model.c.lower, 0) + self.assertIs (model.c.body, model.y) + self.assertEqual(model.c.upper, 1) + + model = self.create_model() + def rule(model): + return (0, model.y, 0) + model.c = Constraint(rule=rule) + + self.assertEqual(model.c.equality, True) + self.assertEqual(model.c.lower, 0) + self.assertIs (model.c.body, model.y) + self.assertEqual(model.c.upper, 0) + + # Test mixed bound types (int / float) + model = self.create_model() + def rule(model): + return (0, model.y, 0.) + model.c = Constraint(rule=rule) + + self.assertEqual(model.c.equality, True) + self.assertEqual(model.c.lower, 0) + self.assertIs (model.c.body, model.y) + self.assertEqual(model.c.upper, 0) + + def test_tuple_construct_2sided_mutable_inequality(self): + model = self.create_model() + model.p = Param(initialize=0, mutable=True) + model.q = Param(initialize=0., mutable=True) + def rule(model): + return (0, model.y, model.q) + model.c = Constraint(rule=rule) + + self.assertEqual(model.c.equality, False) + self.assertEqual(model.c.lower, 0) + self.assertIs(model.c.body, model.y) + self.assertIs(model.c.upper, model.q) + self.assertEqual(model.c.lb, 0) + self.assertEqual(model.c.ub, 0) + + def rule(model): + return (model.p, model.y, 0) + model.d = Constraint(rule=rule) + + self.assertEqual(model.d.equality, False) + self.assertIs(model.d.lower, model.p) + self.assertIs(model.d.body, model.y) + self.assertEqual(model.d.upper, 0) + self.assertEqual(model.d.lb, 0) + self.assertEqual(model.d.ub, 0) + + def rule(model): + return (model.p, model.y, model.q) + model.e = Constraint(rule=rule) + + self.assertEqual(model.e.equality, False) + self.assertIs(model.e.lower, model.p) + self.assertIs(model.e.body, model.y) + self.assertIs(model.e.upper, model.q) + self.assertEqual(model.e.lb, 0) + self.assertEqual(model.e.ub, 0) + + def test_tuple_construct_2sided_immutable_inequality(self): + model = self.create_model() + model.p = Param(initialize=0, mutable=False) + model.q = Param(initialize=0., mutable=False) + def rule(model): + return (0, model.y, model.q) + model.c = Constraint(rule=rule) + + self.assertEqual(model.c.equality, True) + self.assertEqual(model.c.lower, 0) + self.assertIs(model.c.body, model.y) + self.assertIs(model.c.upper, model.q) + self.assertEqual(model.c.lb, 0) + self.assertEqual(model.c.ub, 0) + + def rule(model): + return (model.p, model.y, 0) + model.d = Constraint(rule=rule) + + self.assertEqual(model.d.equality, True) + self.assertIs(model.d.lower, model.p) + self.assertIs(model.d.body, model.y) + self.assertEqual(model.d.upper, 0) + self.assertEqual(model.d.lb, 0) + self.assertEqual(model.d.ub, 0) + + def rule(model): + return (model.p, model.y, model.q) + model.e = Constraint(rule=rule) + + self.assertEqual(model.e.equality, True) + self.assertIs(model.e.lower, model.p) + self.assertIs(model.e.body, model.y) + self.assertIs(model.e.upper, model.q) + self.assertEqual(model.e.lb, 0) + self.assertEqual(model.e.ub, 0) def test_tuple_construct_invalid_2sided_inequality(self): model = self.create_model(abstract=True)