From d4683829d272b05239a8fd62069002b953df47b2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 23 Jun 2022 11:00:50 -0600 Subject: [PATCH 1/4] as_numeric(IndexedComponent) should raise an exception --- pyomo/core/expr/numvalue.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index e12bab05fee..a1cb7104242 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -117,7 +117,7 @@ def value(obj, exception=True): # Test if we have a duck types for Pyomo expressions # try: - obj.is_expression_type() + obj.is_numeric_type() except AttributeError: # # If not, then try to coerce this into a numeric constant. If that @@ -370,8 +370,17 @@ def as_numeric(obj): # Ignore objects that are duck types to work with Pyomo expressions # try: - obj.is_expression_type() - return obj + if obj.is_numeric_type(): + return obj + else: + try: + _name = obj.name + except AttributeError: + _name = str(obj) + raise TypeError( + "The '%s' object '%s' is not a valid type for Pyomo " + "numeric expressions" % (type(obj).__name__, _name)) + except AttributeError: pass # @@ -386,10 +395,11 @@ def as_numeric(obj): # Generate errors # if obj.__class__ in native_types: - raise TypeError("Cannot treat the value '%s' as a constant" % str(obj)) + raise TypeError("%s values ('%s') are not allowed in Pyomo " + "numeric expressions" % (type(obj).__name__, str(obj))) raise TypeError( - "Cannot treat the value '%s' as a constant because it has unknown " - "type '%s'" % (str(obj), type(obj).__name__)) + "Cannot treat the value '%s' as a numeric value because it has " + "unknown type '%s'" % (str(obj), type(obj).__name__)) def check_if_numeric_type_and_cache(obj): From 4d94de0b0645c07f37ccdacae73e7599bcc2915c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 23 Jun 2022 11:02:50 -0600 Subject: [PATCH 2/4] Update numvalue tests --- pyomo/core/tests/unit/test_numvalue.py | 138 ++++++++++++------------- 1 file changed, 67 insertions(+), 71 deletions(-) diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 5a205b4517d..3ed7a360ebb 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -12,19 +12,21 @@ # Unit Tests for Python numeric values # -import os -from os.path import abspath, dirname -currdir = dirname(abspath(__file__))+os.sep - from math import nan, inf import pyomo.common.unittest as unittest -from pyomo.environ import (value, ConcreteModel, Param, Var, - polynomial_degree, is_constant, is_fixed, - is_potentially_variable, is_variable_type) -from pyomo.core.expr.numvalue import (NumericConstant, - as_numeric, - is_numeric_data) +from pyomo.environ import ( + value, ConcreteModel, Param, Var, + polynomial_degree, is_constant, is_fixed, + is_potentially_variable, is_variable_type +) + +from pyomo.core.pyomoobject import PyomoObject +from pyomo.core.expr.numvalue import ( + NumericConstant, as_numeric, is_numeric_data, + native_types, native_numeric_types, native_integer_types, + native_boolean_types, +) try: import numpy @@ -132,7 +134,6 @@ class A(object): pass def test_unknownNumericType(self): ref = MyBogusNumericType(42) self.assertTrue(is_numeric_data(ref)) - from pyomo.core.base.numvalue import native_numeric_types, native_types self.assertIn(MyBogusNumericType, native_numeric_types) self.assertIn(MyBogusNumericType, native_types) native_numeric_types.remove(MyBogusNumericType) @@ -213,18 +214,22 @@ def test_var2(self): def test_error1(self): class A(object): pass val = A() - try: + with self.assertRaisesRegex( + TypeError, "Cannot evaluate object with unknown type: A"): value(val) - self.fail("Expected TypeError") - except TypeError: - pass + + def test_unknownType(self): + ref = MyBogusType(42) + with self.assertRaisesRegex( + TypeError, + "Cannot evaluate object with unknown type: MyBogusType"): + value(ref) def test_unknownNumericType(self): ref = MyBogusNumericType(42) val = value(ref) self.assertEqual(val.val, 42.0) #self.assertEqual(val().val, 42) - from pyomo.core.base.numvalue import native_numeric_types, native_types self.assertIn(MyBogusNumericType, native_numeric_types) self.assertIn(MyBogusNumericType, native_types) native_numeric_types.remove(MyBogusNumericType) @@ -295,18 +300,16 @@ def test_var1(self): def test_error1(self): class A(object): pass val = A() - try: + with self.assertRaisesRegex( + TypeError, "Cannot assess properties of object " + "with unknown type: A"): polynomial_degree(val) - self.fail("Expected TypeError") - except TypeError: - pass def test_unknownNumericType(self): ref = MyBogusNumericType(42) val = polynomial_degree(ref) self.assertEqual(val, 0) #self.assertEqual(val().val, 42) - from pyomo.core.base.numvalue import native_numeric_types, native_types self.assertIn(MyBogusNumericType, native_numeric_types) self.assertIn(MyBogusNumericType, native_types) native_numeric_types.remove(MyBogusNumericType) @@ -345,16 +348,14 @@ def test_const2(self): def test_error(self): class A(object): pass val = A() - try: + with self.assertRaisesRegex( + TypeError, "Cannot assess properties of object " + "with unknown type: A"): is_constant(val) - self.fail("Expected TypeError") - except TypeError: - pass def test_unknownNumericType(self): ref = MyBogusNumericType(42) self.assertTrue(is_constant(ref)) - from pyomo.core.base.numvalue import native_numeric_types, native_types self.assertIn(MyBogusNumericType, native_numeric_types) self.assertIn(MyBogusNumericType, native_types) native_numeric_types.remove(MyBogusNumericType) @@ -389,16 +390,14 @@ def test_const1(self): def test_error(self): class A(object): pass val = A() - try: + with self.assertRaisesRegex( + TypeError, "Cannot assess properties of object " + "with unknown type: A"): is_fixed(val) - self.fail("Expected TypeError") - except TypeError: - pass def test_unknownNumericType(self): ref = MyBogusNumericType(42) self.assertTrue(is_fixed(ref)) - from pyomo.core.base.numvalue import native_numeric_types, native_types self.assertIn(MyBogusNumericType, native_numeric_types) self.assertIn(MyBogusNumericType, native_types) native_numeric_types.remove(MyBogusNumericType) @@ -479,25 +478,14 @@ class Test_as_numeric(unittest.TestCase): def test_none(self): val = None - try: + with self.assertRaisesRegex( + TypeError, r"NoneType values \('None'\) are not allowed " + "in Pyomo numeric expressions"): as_numeric(val) - self.fail("Expected ValueError") - except: - pass def test_bool(self): - val = False - try: - as_numeric(val) - self.fail("Expected ValueError") - except: - pass - val = True - try: - as_numeric(val) - self.fail("Expected ValueError") - except: - pass + self.assertEqual(as_numeric(False), 0) + self.assertEqual(as_numeric(True), 1) def test_float(self): val = 1.1 @@ -521,11 +509,10 @@ def test_long(self): def test_string(self): val = 'foo' - try: + with self.assertRaisesRegex( + TypeError, r"str values \('foo'\) are not allowed " + "in Pyomo numeric expressions"): as_numeric(val) - self.fail("Expected ValueError") - except: - pass def test_const1(self): val = NumericConstant(1.0) @@ -534,35 +521,46 @@ def test_const1(self): def test_error1(self): class A(object): pass val = A() - try: + with self.assertRaisesRegex( + TypeError, r"Cannot treat the value '.*' as a " + "numeric value because it has unknown type 'A'"): as_numeric(val) - self.fail("Expected TypeError") - except TypeError: - pass def test_unknownType(self): ref = MyBogusType(42) - try: - val = as_numeric(ref) - self.fail("Expected TypeError") - except TypeError: - pass + with self.assertRaisesRegex( + TypeError, r"Cannot treat the value '.*' as a " + "numeric value because it has unknown type 'MyBogusType'"): + as_numeric(ref) + + def test_non_numeric_component(self): + m = ConcreteModel() + m.v = Var([1,2]) + with self.assertRaisesRegex( + TypeError, "The 'IndexedVar' object 'v' is not a valid " + "type for Pyomo numeric expressions"): + as_numeric(m.v) + + obj = PyomoObject() + with self.assertRaisesRegex( + TypeError, "The 'PyomoObject' object '.*' is not a valid " + "type for Pyomo numeric expressions"): + as_numeric(obj) def test_unknownNumericType(self): ref = MyBogusNumericType(42) - val = as_numeric(ref) - self.assertEqual(val().val, 42.0) - #self.assertEqual(val().val, 42) - from pyomo.core.base.numvalue import native_numeric_types, native_types - self.assertIn(MyBogusNumericType, native_numeric_types) - self.assertIn(MyBogusNumericType, native_types) - native_numeric_types.remove(MyBogusNumericType) - native_types.remove(MyBogusNumericType) + self.assertNotIn(MyBogusNumericType, native_numeric_types) + self.assertNotIn(MyBogusNumericType, native_types) + try: + val = as_numeric(ref) + self.assertEqual(val().val, 42.0) + finally: + native_numeric_types.remove(MyBogusNumericType) + native_types.remove(MyBogusNumericType) def test_numpy_basic_float_registration(self): if not numpy_available: self.skipTest("This test requires NumPy") - from pyomo.core.base.numvalue import native_numeric_types, native_integer_types, native_boolean_types, native_types self.assertIn(numpy.float_, native_numeric_types) self.assertNotIn(numpy.float_, native_integer_types) self.assertIn(numpy.float_, native_boolean_types) @@ -571,7 +569,6 @@ def test_numpy_basic_float_registration(self): def test_numpy_basic_int_registration(self): if not numpy_available: self.skipTest("This test requires NumPy") - from pyomo.core.base.numvalue import native_numeric_types, native_integer_types, native_boolean_types, native_types self.assertIn(numpy.int_, native_numeric_types) self.assertIn(numpy.int_, native_integer_types) self.assertIn(numpy.int_, native_boolean_types) @@ -580,7 +577,6 @@ def test_numpy_basic_int_registration(self): def test_numpy_basic_bool_registration(self): if not numpy_available: self.skipTest("This test requires NumPy") - from pyomo.core.base.numvalue import native_numeric_types, native_integer_types, native_boolean_types, native_types self.assertNotIn(numpy.bool_, native_numeric_types) self.assertNotIn(numpy.bool_, native_integer_types) self.assertIn(numpy.bool_, native_boolean_types) From 49e6d7bd35342d63d972d7fe72ab866077997e71 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 23 Jun 2022 11:03:03 -0600 Subject: [PATCH 3/4] Remove redundant/unrunnable test code --- pyomo/core/tests/unit/test_numvalue.py | 68 -------------------------- 1 file changed, 68 deletions(-) diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 3ed7a360ebb..7ff9151db32 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -43,74 +43,6 @@ def __add__(self, other): return MyBogusNumericType(self.val + float(other)) -class Test_value(unittest.TestCase): - - def test_none(self): - val = None - try: - value(val) - self.fail("Expected ValueError") - except ValueError: - pass - - def test_bool(self): - val = False - self.assertEqual(val, value(val)) - val = True - self.assertEqual(val, value(val)) - - def test_float(self): - val = 1.1 - self.assertEqual(val, value(val)) - - def test_int(self): - val = 1 - self.assertEqual(val, value(val)) - - def test_long(self): - val = int(1e10) - self.assertEqual(val, value(val)) - - def test_string(self): - val = 'foo' - try: - value(val) - self.fail("Expected ValueError") - except ValueError: - pass - - def test_const1(self): - val = NumericConstant(1.0) - self.assertEqual(1.0, value(val)) - - def test_error1(self): - class A(object): pass - val = A() - try: - value(val) - self.fail("Expected TypeError") - except TypeError: - pass - - def test_unknownType(self): - ref = MyBogusType(42) - try: - val = value(ref) - self.fail("Expected TypeError") - except TypeError: - pass - - def test_unknownNumericType(self): - ref = MyBogusNumericType(42) - val = value(ref) - self.assertEqual(val().val, 42) - from pyomo.core.base.numvalue import native_numeric_types, native_types - self.assertIn(MyBogusNumericType, native_numeric_types) - self.assertIn(MyBogusNumericType, native_types) - native_numeric_types.remove(MyBogusNumericType) - native_types.remove(MyBogusNumericType) - - class Test_is_numeric_data(unittest.TestCase): def test_string(self): From 631b2a760d26b361fce058ef38fcc880c818f22a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 23 Jun 2022 11:03:52 -0600 Subject: [PATCH 4/4] NFC: doc updates / delete whitespace --- pyomo/core/expr/numvalue.py | 80 ++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index a1cb7104242..88e1423f415 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -137,7 +137,6 @@ def value(obj, exception=True): # # Here, we try to catch the exception # - try: tmp = obj(exception=True) if tmp is None: @@ -257,7 +256,7 @@ def is_numeric_data(obj): # this likely means it is a string return False try: - # Test if this is an expression object that + # Test if this is an expression object that # is not potentially variable return not obj.is_potentially_variable() except AttributeError: @@ -330,7 +329,7 @@ def as_numeric(obj): Args: obj: The numeric value that may be wrapped. - Raises: TypeError if the object is in native_types and not in + Raises: TypeError if the object is in native_types and not in native_numeric_types Returns: A NumericConstant object or the original object. @@ -338,7 +337,7 @@ def as_numeric(obj): if obj.__class__ in native_numeric_types: val = _KnownConstants.get(obj, None) if val is not None: - return val + return val # # Coerce the value to a float, if possible # @@ -367,7 +366,7 @@ def as_numeric(obj): # return retval # - # Ignore objects that are duck types to work with Pyomo expressions + # Ignore objects that are duck typed to work with Pyomo expressions # try: if obj.is_numeric_type(): @@ -425,7 +424,7 @@ def check_if_numeric_type_and_cache(obj): retval = NumericConstant(obj) try: # - # Create the numeric constant and add to the + # Create the numeric constant and add to the # list of known constants. # # Note: we don't worry about the size of the @@ -657,7 +656,7 @@ def __lt__(self,other): Less than operator This method is called when Python processes statements of the form:: - + self < other other > self """ @@ -668,7 +667,7 @@ def __gt__(self,other): Greater than operator This method is called when Python processes statements of the form:: - + self > other other < self """ @@ -679,7 +678,7 @@ def __le__(self,other): Less than or equal operator This method is called when Python processes statements of the form:: - + self <= other other >= self """ @@ -690,7 +689,7 @@ def __ge__(self,other): Greater than or equal operator This method is called when Python processes statements of the form:: - + self >= other other <= self """ @@ -701,7 +700,7 @@ def __eq__(self,other): Equal to operator This method is called when Python processes the statement:: - + self == other """ return _generate_relational_expression(_eq, self, other) @@ -711,7 +710,7 @@ def __add__(self,other): Binary addition This method is called when Python processes the statement:: - + self + other """ return _generate_sum_expression(_add,self,other) @@ -721,7 +720,7 @@ def __sub__(self,other): Binary subtraction This method is called when Python processes the statement:: - + self - other """ return _generate_sum_expression(_sub,self,other) @@ -731,7 +730,7 @@ def __mul__(self,other): Binary multiplication This method is called when Python processes the statement:: - + self * other """ return _generate_mul_expression(_mul,self,other) @@ -741,7 +740,7 @@ def __div__(self,other): Binary division This method is called when Python processes the statement:: - + self / other """ return _generate_mul_expression(_div,self,other) @@ -751,7 +750,7 @@ def __truediv__(self,other): Binary division (when __future__.division is in effect) This method is called when Python processes the statement:: - + self / other """ return _generate_mul_expression(_div,self,other) @@ -761,7 +760,7 @@ def __pow__(self,other): Binary power This method is called when Python processes the statement:: - + self ** other """ return _generate_other_expression(_pow,self,other) @@ -771,7 +770,7 @@ def __radd__(self,other): Binary addition This method is called when Python processes the statement:: - + other + self """ return _generate_sum_expression(_radd,self,other) @@ -781,7 +780,7 @@ def __rsub__(self,other): Binary subtraction This method is called when Python processes the statement:: - + other - self """ return _generate_sum_expression(_rsub,self,other) @@ -791,7 +790,7 @@ def __rmul__(self,other): Binary multiplication This method is called when Python processes the statement:: - + other * self when other is not a :class:`NumericValue ` object. @@ -802,7 +801,7 @@ def __rdiv__(self,other): """Binary division This method is called when Python processes the statement:: - + other / self """ return _generate_mul_expression(_rdiv,self,other) @@ -812,7 +811,7 @@ def __rtruediv__(self,other): Binary division (when __future__.division is in effect) This method is called when Python processes the statement:: - + other / self """ return _generate_mul_expression(_rdiv,self,other) @@ -822,7 +821,7 @@ def __rpow__(self,other): Binary power This method is called when Python processes the statement:: - + other ** self """ return _generate_other_expression(_rpow,self,other) @@ -832,7 +831,7 @@ def __iadd__(self,other): Binary addition This method is called when Python processes the statement:: - + self += other """ return _generate_sum_expression(_iadd,self,other) @@ -862,7 +861,7 @@ def __idiv__(self,other): Binary division This method is called when Python processes the statement:: - + self /= other """ return _generate_mul_expression(_idiv,self,other) @@ -872,7 +871,7 @@ def __itruediv__(self,other): Binary division (when __future__.division is in effect) This method is called when Python processes the statement:: - + self /= other """ return _generate_mul_expression(_idiv,self,other) @@ -882,7 +881,7 @@ def __ipow__(self,other): Binary power This method is called when Python processes the statement:: - + self **= other """ return _generate_other_expression(_ipow,self,other) @@ -892,7 +891,7 @@ def __neg__(self): Negation This method is called when Python processes the statement:: - + - self """ return _generate_sum_expression(_neg, self, None) @@ -902,7 +901,7 @@ def __pos__(self): Positive expression This method is called when Python processes the statement:: - + + self """ return self @@ -911,7 +910,7 @@ def __abs__(self): """ Absolute value This method is called when Python processes the statement:: - + abs(self) """ return _generate_other_expression(_abs,self, None) @@ -922,25 +921,32 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): - """ - Return a string representation of the expression tree. + """Return a string representation of the expression tree. Args: - verbose (bool): If :const:`True`, then the the string + verbose (bool): If :const:`True`, then the string representation consists of nested functions. Otherwise, - the string representation is an algebraic equation. + the string representation is an infix algebraic equation. Defaults to :const:`False`. - labeler: An object that generates string labels for - variables in the expression tree. Defaults to :const:`None`. + labeler: An object that generates string labels for + non-constant in the expression tree. Defaults to + :const:`None`. + smap: A SymbolMap instance that stores string labels for + non-constant nodes in the expression tree. Defaults to + :const:`None`. + compute_values (bool): If :const:`True`, then fixed + expressions are evaluated and the string representation + of the resulting value is returned. Returns: A string representation for the expression tree. + """ if compute_values and self.is_fixed(): try: return str(self()) except: - pass + pass if not self.is_constant(): if smap is not None: return smap.getSymbol(self, labeler)