1# ___________________________________________________________________________ 2# 3# Pyomo: Python Optimization Modeling Objects 4# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC 5# Under the terms of Contract DE-NA0003525 with National Technology and 6# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain 7# rights in this software. 8# This software is distributed under the 3-clause BSD License. 9# ___________________________________________________________________________ 10 11__all__ = ('value', 'is_constant', 'is_fixed', 'is_variable_type', 12 'is_potentially_variable', 'NumericValue', 'ZeroConstant', 13 'native_numeric_types', 'native_types', 'nonpyomo_leaf_types', 14 'polynomial_degree') 15 16import sys 17import logging 18 19from pyomo.common.dependencies import numpy as np, numpy_available 20from pyomo.common.deprecation import deprecated 21from pyomo.core.expr.expr_common import ( 22 _add, _sub, _mul, _div, _pow, 23 _neg, _abs, _radd, 24 _rsub, _rmul, _rdiv, _rpow, 25 _iadd, _isub, _imul, _idiv, 26 _ipow, _lt, _le, _eq 27) 28# TODO: update imports of these objects to pull from numeric_types 29from pyomo.common.numeric_types import ( 30 nonpyomo_leaf_types, native_types, native_numeric_types, 31 native_integer_types, native_boolean_types, native_logical_types, 32 RegisterNumericType, RegisterIntegerType, RegisterBooleanType, 33 pyomo_constant_types, 34) 35from pyomo.core.pyomoobject import PyomoObject 36from pyomo.core.expr.expr_errors import TemplateExpressionError 37 38logger = logging.getLogger('pyomo.core') 39 40 41def _generate_sum_expression(etype, _self, _other): 42 raise RuntimeError("incomplete import of Pyomo expression system") #pragma: no cover 43def _generate_mul_expression(etype, _self, _other): 44 raise RuntimeError("incomplete import of Pyomo expression system") #pragma: no cover 45def _generate_other_expression(etype, _self, _other): 46 raise RuntimeError("incomplete import of Pyomo expression system") #pragma: no cover 47def _generate_relational_expression(etype, lhs, rhs): 48 raise RuntimeError("incomplete import of Pyomo expression system") #pragma: no cover 49 50##------------------------------------------------------------------------ 51## 52## Standard types of expressions 53## 54##------------------------------------------------------------------------ 55 56class NonNumericValue(object): 57 """An object that contains a non-numeric value 58 59 Constructor Arguments: 60 value The initial value. 61 """ 62 __slots__ = ('value',) 63 64 def __init__(self, value): 65 self.value = value 66 67 def __str__(self): 68 return str(self.value) 69 70 def __getstate__(self): 71 state = {} 72 state['value'] = getattr(self,'value') 73 return state 74 75 def __setstate__(self, state): 76 setattr(self, 'value', state['value']) 77 78nonpyomo_leaf_types.add(NonNumericValue) 79 80 81def value(obj, exception=True): 82 """ 83 A utility function that returns the value of a Pyomo object or 84 expression. 85 86 Args: 87 obj: The argument to evaluate. If it is None, a 88 string, or any other primative numeric type, 89 then this function simply returns the argument. 90 Otherwise, if the argument is a NumericValue 91 then the __call__ method is executed. 92 exception (bool): If :const:`True`, then an exception should 93 be raised when instances of NumericValue fail to 94 evaluate due to one or more objects not being 95 initialized to a numeric value (e.g, one or more 96 variables in an algebraic expression having the 97 value None). If :const:`False`, then the function 98 returns :const:`None` when an exception occurs. 99 Default is True. 100 101 Returns: A numeric value or None. 102 """ 103 if obj.__class__ in native_types: 104 return obj 105 if obj.__class__ in pyomo_constant_types: 106 # 107 # I'm commenting this out for now, but I think we should never expect 108 # to see a numeric constant with value None. 109 # 110 #if exception and obj.value is None: 111 # raise ValueError( 112 # "No value for uninitialized NumericConstant object %s" 113 # % (obj.name,)) 114 return obj.value 115 # 116 # Test if we have a duck types for Pyomo expressions 117 # 118 try: 119 obj.is_expression_type() 120 except AttributeError: 121 # 122 # If not, then try to coerce this into a numeric constant. If that 123 # works, then return the object 124 # 125 try: 126 check_if_numeric_type_and_cache(obj) 127 return obj 128 except: 129 raise TypeError( 130 "Cannot evaluate object with unknown type: %s" % 131 (type(obj).__name__,)) 132 # 133 # Evaluate the expression object 134 # 135 if exception: 136 # 137 # Here, we try to catch the exception 138 # 139 140 try: 141 tmp = obj(exception=True) 142 if tmp is None: 143 raise ValueError( 144 "No value for uninitialized NumericValue object %s" 145 % (obj.name,)) 146 return tmp 147 except TemplateExpressionError: 148 # Template expressions work by catching this error type. So 149 # we should defer this error handling and not log an error 150 # message. 151 raise 152 except: 153 logger.error( 154 "evaluating object as numeric value: %s\n (object: %s)\n%s" 155 % (obj, type(obj), sys.exc_info()[1])) 156 raise 157 else: 158 # 159 # Here, we do not try to catch the exception 160 # 161 return obj(exception=False) 162 163 164def is_constant(obj): 165 """ 166 A utility function that returns a boolean that indicates 167 whether the object is a constant. 168 """ 169 # This method is rarely, if ever, called. Plus, since the 170 # expression generation (and constraint generation) system converts 171 # everything to NumericValues, it is better (i.e., faster) to assume 172 # that the obj is a NumericValue 173 # 174 # JDS: NB: I am not sure why we allow str to be a constant, but 175 # since we have historically done so, we check for type membership 176 # in native_types and not in native_numeric_types. 177 # 178 if obj.__class__ in native_types: 179 return True 180 try: 181 return obj.is_constant() 182 except AttributeError: 183 pass 184 try: 185 # Now we need to confirm that we have an unknown numeric type 186 check_if_numeric_type_and_cache(obj) 187 # As this branch is only hit for previously unknown (to Pyomo) 188 # types that behave reasonably like numbers, we know they *must* 189 # be constant. 190 return True 191 except: 192 raise TypeError( 193 "Cannot assess properties of object with unknown type: %s" 194 % (type(obj).__name__,)) 195 196def is_fixed(obj): 197 """ 198 A utility function that returns a boolean that indicates 199 whether the input object's value is fixed. 200 """ 201 # JDS: NB: I am not sure why we allow str to be a constant, but 202 # since we have historically done so, we check for type membership 203 # in native_types and not in native_numeric_types. 204 # 205 if obj.__class__ in native_types: 206 return True 207 try: 208 return obj.is_fixed() 209 except AttributeError: 210 pass 211 try: 212 # Now we need to confirm that we have an unknown numeric type 213 check_if_numeric_type_and_cache(obj) 214 # As this branch is only hit for previously unknown (to Pyomo) 215 # types that behave reasonably like numbers, we know they *must* 216 # be fixed. 217 return True 218 except: 219 raise TypeError( 220 "Cannot assess properties of object with unknown type: %s" 221 % (type(obj).__name__,)) 222 223def is_variable_type(obj): 224 """ 225 A utility function that returns a boolean indicating 226 whether the input object is a variable. 227 """ 228 if obj.__class__ in native_types: 229 return False 230 try: 231 return obj.is_variable_type() 232 except AttributeError: 233 return False 234 235def is_potentially_variable(obj): 236 """ 237 A utility function that returns a boolean indicating 238 whether the input object can reference variables. 239 """ 240 if obj.__class__ in native_types: 241 return False 242 try: 243 return obj.is_potentially_variable() 244 except AttributeError: 245 return False 246 247def is_numeric_data(obj): 248 """ 249 A utility function that returns a boolean indicating 250 whether the input object is numeric and not potentially 251 variable. 252 """ 253 if obj.__class__ in native_numeric_types: 254 return True 255 elif obj.__class__ in native_types: 256 # this likely means it is a string 257 return False 258 try: 259 # Test if this is an expression object that 260 # is not potentially variable 261 return not obj.is_potentially_variable() 262 except AttributeError: 263 pass 264 try: 265 # Now we need to confirm that we have an unknown numeric type 266 check_if_numeric_type_and_cache(obj) 267 # As this branch is only hit for previously unknown (to Pyomo) 268 # types that behave reasonably like numbers, we know they *must* 269 # be numeric data (unless an exception is raised). 270 return True 271 except: 272 pass 273 return False 274 275def polynomial_degree(obj): 276 """ 277 A utility function that returns an integer 278 that indicates the polynomial degree for an 279 object. boolean indicating 280 """ 281 if obj.__class__ in native_numeric_types: 282 return 0 283 elif obj.__class__ in native_types: 284 raise TypeError( 285 "Cannot evaluate the polynomial degree of a non-numeric type: %s" 286 % (type(obj).__name__,)) 287 try: 288 return obj.polynomial_degree() 289 except AttributeError: 290 pass 291 try: 292 # Now we need to confirm that we have an unknown numeric type 293 check_if_numeric_type_and_cache(obj) 294 # As this branch is only hit for previously unknown (to Pyomo) 295 # types that behave reasonably like numbers, we know they *must* 296 # be a numeric constant. 297 return 0 298 except: 299 raise TypeError( 300 "Cannot assess properties of object with unknown type: %s" 301 % (type(obj).__name__,)) 302 303# 304# It is very common to have only a few constants in a model, but those 305# constants get repeated many times. KnownConstants lets us re-use / 306# share constants we have seen before. 307# 308# Note: 309# For now, all constants are coerced to floats. This avoids integer 310# division in Python 2.x. (At least some of the time.) 311# 312# When we eliminate support for Python 2.x, we will not need this 313# coercion. The main difference in the following code is that we will 314# need to index KnownConstants by both the class type and value, since 315# INT, FLOAT and LONG values sometimes hash the same. 316# 317_KnownConstants = {} 318 319def as_numeric(obj): 320 """ 321 A function that creates a NumericConstant object that 322 wraps Python numeric values. 323 324 This function also manages a cache of constants. 325 326 NOTE: This function is only intended for use when 327 data is added to a component. 328 329 Args: 330 obj: The numeric value that may be wrapped. 331 332 Raises: TypeError if the object is in native_types and not in 333 native_numeric_types 334 335 Returns: A NumericConstant object or the original object. 336 """ 337 if obj.__class__ in native_numeric_types: 338 val = _KnownConstants.get(obj, None) 339 if val is not None: 340 return val 341 # 342 # Coerce the value to a float, if possible 343 # 344 try: 345 obj = float(obj) 346 except: 347 pass 348 # 349 # Create the numeric constant. This really 350 # should be the only place in the code 351 # where these objects are constructed. 352 # 353 retval = NumericConstant(obj) 354 # 355 # Cache the numeric constants. We used a bounded cache size 356 # to avoid unexpectedly large lists of constants. There are 357 # typically a small number of constants that need to be cached. 358 # 359 # NOTE: A LFU policy might be more sensible here, but that 360 # requires a more complex cache. It's not clear that that 361 # is worth the extra cost. 362 # 363 if len(_KnownConstants) < 1024: 364 _KnownConstants[obj] = retval 365 return retval 366 # 367 return retval 368 # 369 # Ignore objects that are duck types to work with Pyomo expressions 370 # 371 try: 372 obj.is_expression_type() 373 return obj 374 except AttributeError: 375 pass 376 # 377 # Test if the object looks like a number. If so, register that type with a 378 # warning. 379 # 380 try: 381 return check_if_numeric_type_and_cache(obj) 382 except: 383 pass 384 # 385 # Generate errors 386 # 387 if obj.__class__ in native_types: 388 raise TypeError("Cannot treat the value '%s' as a constant" % str(obj)) 389 raise TypeError( 390 "Cannot treat the value '%s' as a constant because it has unknown " 391 "type '%s'" % (str(obj), type(obj).__name__)) 392 393 394def check_if_numeric_type_and_cache(obj): 395 """Test if the argument is a numeric type by checking if we can add 396 zero to it. If that works, then we cache the value and return a 397 NumericConstant object. 398 399 """ 400 obj_class = obj.__class__ 401 if obj_class is (obj + 0).__class__: 402 # 403 # Coerce the value to a float, if possible 404 # 405 try: 406 obj = float(obj) 407 except: 408 pass 409 # 410 # obj may (or may not) be hashable, so we need this try 411 # block so that things proceed normally for non-hashable 412 # "numeric" types 413 # 414 retval = NumericConstant(obj) 415 try: 416 # 417 # Create the numeric constant and add to the 418 # list of known constants. 419 # 420 # Note: we don't worry about the size of the 421 # cache here, since we need to confirm that the 422 # object is hashable. 423 # 424 _KnownConstants[obj] = retval 425 # 426 # If we get here, this is a reasonably well-behaving 427 # numeric type: add it to the native numeric types 428 # so that future lookups will be faster. 429 # 430 native_numeric_types.add(obj_class) 431 native_types.add(obj_class) 432 nonpyomo_leaf_types.add(obj_class) 433 # 434 # Generate a warning, since Pyomo's management of third-party 435 # numeric types is more robust when registering explicitly. 436 # 437 logger.warning( 438 """Dynamically registering the following numeric type: 439 %s 440Dynamic registration is supported for convenience, but there are known 441limitations to this approach. We recommend explicitly registering 442numeric types using the following functions: 443 RegisterNumericType(), RegisterIntegerType(), RegisterBooleanType().""" 444 % (obj_class.__name__,)) 445 except: 446 pass 447 return retval 448 449 450class NumericValue(PyomoObject): 451 """ 452 This is the base class for numeric values used in Pyomo. 453 """ 454 455 __slots__ = () 456 457 # This is required because we define __eq__ 458 __hash__ = None 459 460 def __getstate__(self): 461 """ 462 Prepare a picklable state of this instance for pickling. 463 464 Nominally, __getstate__() should execute the following:: 465 466 state = super(Class, self).__getstate__() 467 for i in Class.__slots__: 468 state[i] = getattr(self,i) 469 return state 470 471 However, in this case, the (nominal) parent class is 'object', 472 and object does not implement __getstate__. So, we will 473 check to make sure that there is a base __getstate__() to 474 call. You might think that there is nothing to check, but 475 multiple inheritance could mean that another class got stuck 476 between this class and "object" in the MRO. 477 478 Further, since there are actually no slots defined here, the 479 real question is to either return an empty dict or the 480 parent's dict. 481 """ 482 _base = super(NumericValue, self) 483 if hasattr(_base, '__getstate__'): 484 return _base.__getstate__() 485 else: 486 return {} 487 488 def __setstate__(self, state): 489 """ 490 Restore a pickled state into this instance 491 492 Our model for setstate is for derived classes to modify 493 the state dictionary as control passes up the inheritance 494 hierarchy (using super() calls). All assignment of state -> 495 object attributes is handled at the last class before 'object', 496 which may -- or may not (thanks to MRO) -- be here. 497 """ 498 _base = super(NumericValue, self) 499 if hasattr(_base, '__setstate__'): 500 return _base.__setstate__(state) 501 else: 502 for key, val in state.items(): 503 # Note: per the Python data model docs, we explicitly 504 # set the attribute using object.__setattr__() instead 505 # of setting self.__dict__[key] = val. 506 object.__setattr__(self, key, val) 507 508 def getname(self, fully_qualified=False, name_buffer=None): 509 """ 510 If this is a component, return the component's name on the owning 511 block; otherwise return the value converted to a string 512 """ 513 _base = super(NumericValue, self) 514 if hasattr(_base,'getname'): 515 return _base.getname(fully_qualified, name_buffer) 516 else: 517 return str(type(self)) 518 519 @property 520 def name(self): 521 return self.getname(fully_qualified=True) 522 523 @property 524 def local_name(self): 525 return self.getname(fully_qualified=False) 526 527 @deprecated("The cname() method has been renamed to getname().", 528 version='5.0') 529 def cname(self, *args, **kwds): 530 return self.getname(*args, **kwds) 531 532 def is_numeric_type(self): 533 """Return True if this class is a Pyomo numeric object""" 534 return True 535 536 def is_constant(self): 537 """Return True if this numeric value is a constant value""" 538 return False 539 540 def is_fixed(self): 541 """Return True if this is a non-constant value that has been fixed""" 542 return False 543 544 def is_potentially_variable(self): 545 """Return True if variables can appear in this expression""" 546 return False 547 548 def is_relational(self): 549 """ 550 Return True if this numeric value represents a relational expression. 551 """ 552 return False 553 554 def is_indexed(self): 555 """Return True if this numeric value is an indexed object""" 556 return False 557 558 def polynomial_degree(self): 559 """ 560 Return the polynomial degree of the expression. 561 562 Returns: 563 :const:`None` 564 """ 565 return self._compute_polynomial_degree(None) 566 567 def _compute_polynomial_degree(self, values): 568 """ 569 Compute the polynomial degree of this expression given 570 the degree values of its children. 571 572 Args: 573 values (list): A list of values that indicate the degree 574 of the children expression. 575 576 Returns: 577 :const:`None` 578 """ 579 return None 580 581 def __float__(self): 582 """ 583 Coerce the value to a floating point 584 585 Raises: 586 TypeError 587 """ 588 raise TypeError( 589"""Implicit conversion of Pyomo NumericValue type `%s' to a float is 590disabled. This error is often the result of using Pyomo components as 591arguments to one of the Python built-in math module functions when 592defining expressions. Avoid this error by using Pyomo-provided math 593functions.""" % (self.name,)) 594 595 def __int__(self): 596 """ 597 Coerce the value to an integer 598 599 Raises: 600 TypeError 601 """ 602 raise TypeError( 603"""Implicit conversion of Pyomo NumericValue type `%s' to an integer is 604disabled. This error is often the result of using Pyomo components as 605arguments to one of the Python built-in math module functions when 606defining expressions. Avoid this error by using Pyomo-provided math 607functions.""" % (self.name,)) 608 609 def __lt__(self,other): 610 """ 611 Less than operator 612 613 This method is called when Python processes statements of the form:: 614 615 self < other 616 other > self 617 """ 618 return _generate_relational_expression(_lt, self, other) 619 620 def __gt__(self,other): 621 """ 622 Greater than operator 623 624 This method is called when Python processes statements of the form:: 625 626 self > other 627 other < self 628 """ 629 return _generate_relational_expression(_lt, other, self) 630 631 def __le__(self,other): 632 """ 633 Less than or equal operator 634 635 This method is called when Python processes statements of the form:: 636 637 self <= other 638 other >= self 639 """ 640 return _generate_relational_expression(_le, self, other) 641 642 def __ge__(self,other): 643 """ 644 Greater than or equal operator 645 646 This method is called when Python processes statements of the form:: 647 648 self >= other 649 other <= self 650 """ 651 return _generate_relational_expression(_le, other, self) 652 653 def __eq__(self,other): 654 """ 655 Equal to operator 656 657 This method is called when Python processes the statement:: 658 659 self == other 660 """ 661 return _generate_relational_expression(_eq, self, other) 662 663 def __add__(self,other): 664 """ 665 Binary addition 666 667 This method is called when Python processes the statement:: 668 669 self + other 670 """ 671 return _generate_sum_expression(_add,self,other) 672 673 def __sub__(self,other): 674 """ 675 Binary subtraction 676 677 This method is called when Python processes the statement:: 678 679 self - other 680 """ 681 return _generate_sum_expression(_sub,self,other) 682 683 def __mul__(self,other): 684 """ 685 Binary multiplication 686 687 This method is called when Python processes the statement:: 688 689 self * other 690 """ 691 return _generate_mul_expression(_mul,self,other) 692 693 def __div__(self,other): 694 """ 695 Binary division 696 697 This method is called when Python processes the statement:: 698 699 self / other 700 """ 701 return _generate_mul_expression(_div,self,other) 702 703 def __truediv__(self,other): 704 """ 705 Binary division (when __future__.division is in effect) 706 707 This method is called when Python processes the statement:: 708 709 self / other 710 """ 711 return _generate_mul_expression(_div,self,other) 712 713 def __pow__(self,other): 714 """ 715 Binary power 716 717 This method is called when Python processes the statement:: 718 719 self ** other 720 """ 721 return _generate_other_expression(_pow,self,other) 722 723 def __radd__(self,other): 724 """ 725 Binary addition 726 727 This method is called when Python processes the statement:: 728 729 other + self 730 """ 731 return _generate_sum_expression(_radd,self,other) 732 733 def __rsub__(self,other): 734 """ 735 Binary subtraction 736 737 This method is called when Python processes the statement:: 738 739 other - self 740 """ 741 return _generate_sum_expression(_rsub,self,other) 742 743 def __rmul__(self,other): 744 """ 745 Binary multiplication 746 747 This method is called when Python processes the statement:: 748 749 other * self 750 751 when other is not a :class:`NumericValue <pyomo.core.expr.numvalue.NumericValue>` object. 752 """ 753 return _generate_mul_expression(_rmul,self,other) 754 755 def __rdiv__(self,other): 756 """Binary division 757 758 This method is called when Python processes the statement:: 759 760 other / self 761 """ 762 return _generate_mul_expression(_rdiv,self,other) 763 764 def __rtruediv__(self,other): 765 """ 766 Binary division (when __future__.division is in effect) 767 768 This method is called when Python processes the statement:: 769 770 other / self 771 """ 772 return _generate_mul_expression(_rdiv,self,other) 773 774 def __rpow__(self,other): 775 """ 776 Binary power 777 778 This method is called when Python processes the statement:: 779 780 other ** self 781 """ 782 return _generate_other_expression(_rpow,self,other) 783 784 def __iadd__(self,other): 785 """ 786 Binary addition 787 788 This method is called when Python processes the statement:: 789 790 self += other 791 """ 792 return _generate_sum_expression(_iadd,self,other) 793 794 def __isub__(self,other): 795 """ 796 Binary subtraction 797 798 This method is called when Python processes the statement:: 799 800 self -= other 801 """ 802 return _generate_sum_expression(_isub,self,other) 803 804 def __imul__(self,other): 805 """ 806 Binary multiplication 807 808 This method is called when Python processes the statement:: 809 810 self *= other 811 """ 812 return _generate_mul_expression(_imul,self,other) 813 814 def __idiv__(self,other): 815 """ 816 Binary division 817 818 This method is called when Python processes the statement:: 819 820 self /= other 821 """ 822 return _generate_mul_expression(_idiv,self,other) 823 824 def __itruediv__(self,other): 825 """ 826 Binary division (when __future__.division is in effect) 827 828 This method is called when Python processes the statement:: 829 830 self /= other 831 """ 832 return _generate_mul_expression(_idiv,self,other) 833 834 def __ipow__(self,other): 835 """ 836 Binary power 837 838 This method is called when Python processes the statement:: 839 840 self **= other 841 """ 842 return _generate_other_expression(_ipow,self,other) 843 844 def __neg__(self): 845 """ 846 Negation 847 848 This method is called when Python processes the statement:: 849 850 - self 851 """ 852 return _generate_sum_expression(_neg, self, None) 853 854 def __pos__(self): 855 """ 856 Positive expression 857 858 This method is called when Python processes the statement:: 859 860 + self 861 """ 862 return self 863 864 def __abs__(self): 865 """ Absolute value 866 867 This method is called when Python processes the statement:: 868 869 abs(self) 870 """ 871 return _generate_other_expression(_abs,self, None) 872 873 def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): 874 return NumericNDArray.__array_ufunc__( 875 None, ufunc, method, *inputs, **kwargs) 876 877 def to_string(self, verbose=None, labeler=None, smap=None, 878 compute_values=False): 879 """ 880 Return a string representation of the expression tree. 881 882 Args: 883 verbose (bool): If :const:`True`, then the the string 884 representation consists of nested functions. Otherwise, 885 the string representation is an algebraic equation. 886 Defaults to :const:`False`. 887 labeler: An object that generates string labels for 888 variables in the expression tree. Defaults to :const:`None`. 889 890 Returns: 891 A string representation for the expression tree. 892 """ 893 if compute_values: 894 try: 895 return str(self()) 896 except: 897 pass 898 if not self.is_constant(): 899 if smap: 900 return smap.getSymbol(self, labeler) 901 elif labeler is not None: 902 return labeler(self) 903 return self.__str__() 904 905 906class NumericConstant(NumericValue): 907 """An object that contains a constant numeric value. 908 909 Constructor Arguments: 910 value The initial value. 911 """ 912 913 __slots__ = ('value',) 914 915 def __init__(self, value): 916 self.value = value 917 918 def __getstate__(self): 919 state = super(NumericConstant, self).__getstate__() 920 for i in NumericConstant.__slots__: 921 state[i] = getattr(self,i) 922 return state 923 924 def is_constant(self): 925 return True 926 927 def is_fixed(self): 928 return True 929 930 def _compute_polynomial_degree(self, result): 931 return 0 932 933 def __str__(self): 934 return str(self.value) 935 936 def __nonzero__(self): 937 """Return True if the value is defined and non-zero""" 938 if self.value: 939 return True 940 if self.value is None: 941 raise ValueError("Numeric Constant: value is undefined") 942 return False 943 944 __bool__ = __nonzero__ 945 946 def __call__(self, exception=True): 947 """Return the constant value""" 948 return self.value 949 950 def pprint(self, ostream=None, verbose=False): 951 if ostream is None: #pragma:nocover 952 ostream = sys.stdout 953 ostream.write(str(self)) 954 955 956pyomo_constant_types.add(NumericConstant) 957 958# We use as_numeric() so that the constant is also in the cache 959ZeroConstant = as_numeric(0) 960 961# 962# Note: the "if numpy_available" in the class definition also ensures 963# that the numpy types are registered if numpy is in fact available 964# 965class NumericNDArray(np.ndarray if numpy_available else object): 966 """An ndarray subclass that stores Pyomo numeric expressions""" 967 968 def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): 969 if method == '__call__': 970 # Convert all incoming types to ndarray (to prevent recursion) 971 args = [np.asarray(i) for i in inputs] 972 # Set the return type to be an 'object'. This prevents the 973 # logical operators from casting the result to a bool. This 974 # requires numpy >= 1.6 975 kwargs['dtype'] = object 976 977 # Delegate to the base ufunc, but return an instance of this 978 # class so that additional operators hit this method. 979 ans = getattr(ufunc, method)(*args, **kwargs) 980 if isinstance(ans, np.ndarray): 981 if ans.size == 1: 982 return ans[0] 983 return ans.view(NumericNDArray) 984 else: 985 return ans 986