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__ = ['Param'] 12 13import sys 14import types 15import logging 16from weakref import ref as weakref_ref 17 18from pyomo.common.deprecation import deprecation_warning, RenamedClass 19from pyomo.common.log import is_debug_set 20from pyomo.common.modeling import NOTSET 21from pyomo.common.timing import ConstructionTimer 22from pyomo.core.base.component import ComponentData, ModelComponentFactory 23from pyomo.core.base.indexed_component import ( 24 IndexedComponent, UnindexedComponent_set, IndexedComponent_NDArrayMixin 25) 26from pyomo.core.base.initializer import Initializer 27from pyomo.core.base.misc import apply_indexed_rule, apply_parameterized_indexed_rule 28from pyomo.core.base.numvalue import ( 29 NumericValue, native_types, value as expr_value 30) 31from pyomo.core.base.set_types import Any, Reals 32from pyomo.core.base.units_container import units 33 34logger = logging.getLogger('pyomo.core') 35 36def _raise_modifying_immutable_error(obj, index): 37 if obj.is_indexed(): 38 name = "%s[%s]" % (obj.name, index) 39 else: 40 name = obj.name 41 raise TypeError( 42 "Attempting to set the value of the immutable parameter " 43 "%s after the parameter has been constructed. If you intend " 44 "to change the value of this parameter dynamically, please " 45 "declare the parameter as mutable [i.e., Param(mutable=True)]" 46 % (name,)) 47 48class _ImplicitAny(Any.__class__): 49 """An Any that issues a deprecation warning for non-Real values. 50 51 This is a helper class to implement the deprecation warnings for the 52 change of Param's implicit domain from Any to Reals. 53 54 """ 55 def __new__(cls, **kwds): 56 return super(_ImplicitAny, cls).__new__(cls) 57 58 def __init__(self, owner, **kwds): 59 super(_ImplicitAny, self).__init__(**kwds) 60 self._owner = weakref_ref(owner) 61 self._component = weakref_ref(self) 62 self.construct() 63 64 def __getstate__(self): 65 state = super(_ImplicitAny, self).__getstate__() 66 state['_owner'] = None if self._owner is None else self._owner() 67 return state 68 69 def __setstate__(self, state): 70 _owner = state.pop('_owner') 71 super(_ImplicitAny, self).__setstate__(state) 72 self._owner = None if _owner is None else weakref_ref(_owner) 73 74 def __deepcopy__(self, memo): 75 return super(Any.__class__, self).__deepcopy__(memo) 76 77 def __contains__(self, val): 78 if val not in Reals: 79 deprecation_warning( 80 "The default domain for Param objects is 'Any'. However, " 81 "we will be changing that default to 'Reals' in the " 82 "future. If you really intend the domain of this Param (%s) " 83 "to be 'Any', you can suppress this warning by explicitly " 84 "specifying 'within=Any' to the Param constructor." 85 % ('Unknown' if self._owner is None else self._owner().name,), 86 version='5.6.9', remove_in='6.0') 87 return True 88 89 90class _ParamData(ComponentData, NumericValue): 91 """ 92 This class defines the data for a mutable parameter. 93 94 Constructor Arguments: 95 owner The Param object that owns this data. 96 value The value of this parameter. 97 98 Public Class Attributes: 99 value The numeric value of this variable. 100 """ 101 102 __slots__ = ('_value',) 103 104 def __init__(self, component): 105 # 106 # The following is equivalent to calling 107 # the base ComponentData constructor. 108 # 109 self._component = weakref_ref(component) 110 # 111 # The following is equivalent to calling the 112 # base NumericValue constructor. 113 # 114 self._value = Param.NoValue 115 116 def __getstate__(self): 117 """ 118 This method must be defined because this class uses slots. 119 """ 120 state = super(_ParamData, self).__getstate__() 121 for i in _ParamData.__slots__: 122 state[i] = getattr(self, i) 123 return state 124 125 # Note: because NONE of the slots on this class need to be edited, 126 # we don't need to implement a specialized __setstate__ method. 127 128 def clear(self): 129 """Clear the data in this component""" 130 self._value = Param.NoValue 131 132 # FIXME: ComponentData need to have pointers to their index to make 133 # operations like validation efficient. As it stands now, if 134 # set_value is called without specifying an index, this call 135 # involves a linear scan of the _data dict. 136 def set_value(self, value, idx=NOTSET): 137 # 138 # If this param has units, then we need to check the incoming 139 # value and see if it is "units compatible". We only need to 140 # check here in set_value, because all united Params are 141 # required to be mutable. 142 # 143 _comp = self.parent_component() 144 if type(value) in native_types: 145 # TODO: warn/error: check if this Param has units: assigning 146 # a dimensionless value to a united param should be an error 147 pass 148 elif _comp._units is not None: 149 _src_magnitude = expr_value(value) 150 _src_units = units.get_units(value) 151 value = units.convert_value( 152 num_value=_src_magnitude, from_units=_src_units, 153 to_units=_comp._units) 154 155 old_value, self._value = self._value, value 156 try: 157 _comp._validate_value(idx, value, data=self) 158 except: 159 self._value = old_value 160 raise 161 162 def __call__(self, exception=True): 163 """ 164 Return the value of this object. 165 """ 166 if self._value is Param.NoValue: 167 if exception: 168 raise ValueError( 169 "Error evaluating Param value (%s):\n\tThe Param value is " 170 "currently set to an invalid value. This is\n\ttypically " 171 "from a scalar Param or mutable Indexed Param without\n" 172 "\tan initial or default value." 173 % ( self.name, )) 174 else: 175 return None 176 return self._value 177 178 @property 179 def value(self): 180 """Return the value for this variable.""" 181 return self() 182 @value.setter 183 def value(self, val): 184 """Set the value for this variable.""" 185 self.set_value(val) 186 187 def get_units(self): 188 """Return the units for this ParamData""" 189 return self.parent_component()._units 190 191 def is_fixed(self): 192 """ 193 Returns True because this value is fixed. 194 """ 195 return True 196 197 def is_constant(self): 198 """ 199 Returns False because this is not a constant in an expression. 200 """ 201 return False 202 203 def is_parameter_type(self): 204 """ 205 Returns True because this is a parameter object. 206 """ 207 return True 208 209 def _compute_polynomial_degree(self, result): 210 """ 211 Returns 0 because this object can never reference variables. 212 """ 213 return 0 214 215 def __nonzero__(self): 216 """Return True if the value is defined and non-zero.""" 217 return bool(self()) 218 219 __bool__ = __nonzero__ 220 221 222@ModelComponentFactory.register("Parameter data that is used to define a model instance.") 223class Param(IndexedComponent, IndexedComponent_NDArrayMixin): 224 """ 225 A parameter value, which may be defined over an index. 226 227 Constructor Arguments: 228 name 229 The name of this parameter 230 index 231 The index set that defines the distinct parameters. By default, 232 this is None, indicating that there is a single parameter. 233 domain 234 A set that defines the type of values that each parameter must be. 235 within 236 A set that defines the type of values that each parameter must be. 237 validate 238 A rule for validating this parameter w.r.t. data that exists in 239 the model 240 default 241 A scalar, rule, or dictionary that defines default values for 242 this parameter 243 initialize 244 A dictionary or rule for setting up this parameter with existing 245 model data 246 unit: pyomo unit expression 247 An expression containing the units for the parameter 248 mutable: `boolean` 249 Flag indicating if the value of the parameter may change between 250 calls to a solver. Defaults to `False` 251 """ 252 253 DefaultMutable = False 254 255 class NoValue(object): 256 """A dummy type that is pickle-safe that we can use as the default 257 value for Params to indicate that no valid value is present.""" 258 pass 259 260 def __new__(cls, *args, **kwds): 261 if cls != Param: 262 return super(Param, cls).__new__(cls) 263 if not args or (args[0] is UnindexedComponent_set and len(args) == 1): 264 return super(Param, cls).__new__(ScalarParam) 265 else: 266 return super(Param, cls).__new__(IndexedParam) 267 268 def __init__(self, *args, **kwd): 269 _init = self._pop_from_kwargs( 270 'Param', kwd, ('rule', 'initialize'), NOTSET) 271 self._rule = Initializer(_init, 272 treat_sequences_as_mappings=False, 273 arg_not_specified=NOTSET) 274 self.domain = self._pop_from_kwargs('Param', kwd, ('domain', 'within')) 275 if self.domain is None: 276 self.domain = _ImplicitAny(owner=self, name='Any') 277 278 self._validate = kwd.pop('validate', None ) 279 self._mutable = kwd.pop('mutable', Param.DefaultMutable ) 280 self._default_val = kwd.pop('default', Param.NoValue ) 281 self._dense_initialize = kwd.pop('initialize_as_dense', False) 282 self._units = kwd.pop('units', None) 283 if self._units is not None: 284 self._units = units.get_units(self._units) 285 self._mutable = True 286 287 kwd.setdefault('ctype', Param) 288 IndexedComponent.__init__(self, *args, **kwd) 289 290 def __len__(self): 291 """ 292 Return the number of component data objects stored by this 293 component. If a default value is specified, then the 294 length equals the number of items in the component index. 295 """ 296 if self._default_val is Param.NoValue: 297 return len(self._data) 298 return len(self._index) 299 300 def __contains__(self, idx): 301 """ 302 Return true if the index is in the dictionary. If the default value 303 is specified, then all members of the component index are valid. 304 """ 305 if self._default_val is Param.NoValue: 306 return idx in self._data 307 return idx in self._index 308 309 def keys(self): 310 """ 311 Iterate over the keys in the dictionary. If the default value is 312 specified, then iterate over all keys in the component index. 313 """ 314 if self._default_val is Param.NoValue: 315 return self._data.__iter__() 316 return self._index.__iter__() 317 318 @property 319 def mutable(self): 320 return self._mutable 321 322 def get_units(self): 323 """Return the units for this ParamData""" 324 return self._units 325 326 # 327 # These are "sparse equivalent" access / iteration methods that 328 # only loop over the defined data. 329 # 330 331 def sparse_keys(self): 332 """Return a list of keys in the defined parameters""" 333 return list(self._data.keys()) 334 335 def sparse_values(self): 336 """Return a list of the defined param data objects""" 337 return list(self._data.values()) 338 339 def sparse_items(self): 340 """Return a list (index,data) tuples for defined parameters""" 341 return list(self._data.items()) 342 343 def sparse_iterkeys(self): 344 """Return an iterator for the keys in the defined parameters""" 345 return self._data.keys() 346 347 def sparse_itervalues(self): 348 """Return an iterator for the defined param data objects""" 349 return self._data.values() 350 351 def sparse_iteritems(self): 352 """Return an iterator of (index,data) tuples for defined parameters""" 353 return self._data.items() 354 355 def extract_values(self): 356 """ 357 A utility to extract all index-value pairs defined for this 358 parameter, returned as a dictionary. 359 360 This method is useful in contexts where key iteration and 361 repeated __getitem__ calls are too expensive to extract 362 the contents of a parameter. 363 """ 364 if self._mutable: 365 # 366 # The parameter is mutable, parameter data are ParamData types. 367 # Thus, we need to create a temporary dictionary that contains the 368 # values from the ParamData objects. 369 # 370 return {key:param_value() for key,param_value in self.items()} 371 elif not self.is_indexed(): 372 # 373 # The parameter is a scalar, so we need to create a temporary 374 # dictionary using the value for this parameter. 375 # 376 return { None: self() } 377 else: 378 # 379 # The parameter is not mutable, so iteritems() can be 380 # converted into a dictionary containing parameter values. 381 # 382 return dict( self.items() ) 383 384 def extract_values_sparse(self): 385 """ 386 A utility to extract all index-value pairs defined with non-default 387 values, returned as a dictionary. 388 389 This method is useful in contexts where key iteration and 390 repeated __getitem__ calls are too expensive to extract 391 the contents of a parameter. 392 """ 393 if self._mutable: 394 # 395 # The parameter is mutable, parameter data are ParamData types. 396 # Thus, we need to create a temporary dictionary that contains the 397 # values from the ParamData objects. 398 # 399 ans = {} 400 for key, param_value in self.sparse_iteritems(): 401 ans[key] = param_value() 402 return ans 403 elif not self.is_indexed(): 404 # 405 # The parameter is a scalar, so we need to create a temporary 406 # dictionary using the value for this parameter. 407 # 408 return { None: self() } 409 else: 410 # 411 # The parameter is not mutable, so sparse_iteritems() can be 412 # converted into a dictionary containing parameter values. 413 # 414 return dict( self.sparse_iteritems() ) 415 416 def store_values(self, new_values, check=True): 417 """ 418 A utility to update a Param with a dictionary or scalar. 419 420 If check=True, then both the index and value 421 are checked through the __getitem__ method. Using check=False 422 should only be used by developers! 423 """ 424 if not self._mutable: 425 _raise_modifying_immutable_error(self, '*') 426 # 427 _srcType = type(new_values) 428 _isDict = _srcType is dict or ( \ 429 hasattr(_srcType, '__getitem__') 430 and not isinstance(new_values, NumericValue) ) 431 # 432 if check: 433 if _isDict: 434 for index, new_value in new_values.items(): 435 self[index] = new_value 436 else: 437 for index in self._index: 438 self[index] = new_values 439 return 440 # 441 # The argument check is False, so we bypass almost all of the 442 # Param logic for ensuring data integrity. 443 # 444 if self.is_indexed(): 445 if _isDict: 446 # It is possible that the Param is sparse and that the 447 # index is not already in the _data dict. As these 448 # cases are rare, we will recover from the exception 449 # instead of incurring the penalty of checking. 450 for index, new_value in new_values.items(): 451 if index not in self._data: 452 self._data[index] = _ParamData(self) 453 self._data[index]._value = new_value 454 else: 455 # For scalars, we will choose an approach based on 456 # how "dense" the Param is 457 if not self._data: # empty 458 for index in self._index: 459 p = self._data[index] = _ParamData(self) 460 p._value = new_values 461 elif len(self._data) == len(self._index): 462 for index in self._index: 463 self._data[index]._value = new_values 464 else: 465 for index in self._index: 466 if index not in self._data: 467 self._data[index] = _ParamData(self) 468 self._data[index]._value = new_values 469 else: 470 # 471 # Initialize a scalar 472 # 473 if _isDict: 474 if None not in new_values: 475 raise RuntimeError( 476 "Cannot store value for scalar Param %s:\n\tNo value " 477 "with index None in the new values dict." 478 % (self.name,)) 479 new_values = new_values[None] 480 # scalars have to be handled differently 481 self[None] = new_values 482 483 def set_default(self, val): 484 """ 485 Perform error checks and then set the default value for this parameter. 486 487 NOTE: this test will not validate the value of function return values. 488 """ 489 if self._constructed \ 490 and val is not Param.NoValue \ 491 and type(val) in native_types \ 492 and val not in self.domain: 493 raise ValueError( 494 "Default value (%s) is not valid for Param %s domain %s" % 495 (str(val), self.name, self.domain.name)) 496 self._default_val = val 497 498 def default(self): 499 """ 500 Return the value of the parameter default. 501 502 Possible values: 503 Param.NoValue 504 No default value is provided. 505 Numeric 506 A constant value that is the default value for all undefined 507 parameters. 508 Function 509 f(model, i) returns the value for the default value for 510 parameter i 511 """ 512 return self._default_val 513 514 def _getitem_when_not_present(self, index): 515 """ 516 Returns the default component data value 517 """ 518 # 519 # Local values 520 # 521 val = self._default_val 522 if val is Param.NoValue: 523 # We should allow the creation of mutable params without 524 # a default value, as long as *solving* a model without 525 # reasonable values produces an informative error. 526 if self._mutable: 527 # Note: _ParamData defaults to Param.NoValue 528 if self.is_indexed(): 529 ans = self._data[index] = _ParamData(self) 530 else: 531 ans = self._data[index] = self 532 return ans 533 if self.is_indexed(): 534 idx_str = '%s[%s]' % (self.name, index,) 535 else: 536 idx_str = '%s' % (self.name,) 537 raise ValueError( 538 "Error retrieving immutable Param value (%s):\n\tThe Param " 539 "value is undefined and no default value is specified." 540 % ( idx_str,) ) 541 542 _default_type = type(val) 543 _check_value_domain = True 544 if _default_type in native_types: 545 # 546 # The set_default() method validates the domain of native types, so 547 # we can skip the check on the value domain. 548 # 549 _check_value_domain = False 550 elif _default_type is types.FunctionType: 551 val = apply_indexed_rule(self, val, self.parent_block(), index) 552 elif hasattr(val, '__getitem__') and ( 553 not isinstance(val, NumericValue) or val.is_indexed() ): 554 # Things that look like Dictionaries should be allowable. This 555 # includes other IndexedComponent objects. 556 val = val[index] 557 else: 558 # this is something simple like a non-indexed component 559 pass 560 561 # 562 # If the user wants to validate values, we need to validate the 563 # default value as well. For Mutable Params, this is easy: 564 # _setitem_impl will inject the value into _data and 565 # then call validate. 566 # 567 if self._mutable: 568 return self._setitem_when_not_present(index, val) 569 # 570 # For immutable params, we never inject the default into the data 571 # dictionary. This will break validation, as the validation rule is 572 # allowed to assume the data is already present (actually, it will 573 # die on infinite recursion, as Param.__getitem__() will re-call 574 # _getitem_when_not_present). 575 # 576 # So, we will do something very inefficient: if we are 577 # validating, we will inject the value into the dictionary, 578 # call validate, and remove it. 579 # 580 if _check_value_domain or self._validate: 581 try: 582 self._data[index] = val 583 self._validate_value(index, val, _check_value_domain) 584 finally: 585 del self._data[index] 586 587 return val 588 589 def _setitem_impl(self, index, obj, value): 590 """The __setitem__ method performs significant validation around the 591 input indices, particularly when the index value is new. In 592 various contexts, we don't need to incur this overhead 593 (e.g. during initialization). The _setitem_impl 594 assumes the input value is in the set native_types 595 596 """ 597 # 598 # We need to ensure that users don't override the value for immutable 599 # parameters. 600 # 601 if self._constructed and not self._mutable: 602 _raise_modifying_immutable_error(self, index) 603 # 604 # Params should contain *values*. Note that if we just call 605 # value(), then that forces the value to be a numeric value. 606 # Notably, we allow Params with domain==Any to hold strings, tuples, 607 # etc. The following lets us use NumericValues to initialize 608 # Params, but is optimized to check for "known" native types to 609 # bypass a potentially expensive isinstance()==False call. 610 # 611 if value.__class__ not in native_types: 612 if isinstance(value, NumericValue): 613 value = value() 614 # 615 # Set the value depending on the type of param value. 616 # 617 if self._mutable: 618 obj.set_value(value, index) 619 return obj 620 else: 621 old_value, self._data[index] = self._data[index], value 622 # Because we do not have a _ParamData, we cannot rely on the 623 # validation that occurs in _ParamData.set_value() 624 try: 625 self._validate_value(index, value) 626 return value 627 except: 628 self._data[index] = old_value 629 raise 630 631 def _setitem_when_not_present(self, index, value, _check_domain=True): 632 # 633 # We need to ensure that users don't override the value for immutable 634 # parameters. 635 # 636 if self._constructed and not self._mutable: 637 _raise_modifying_immutable_error(self, index) 638 # 639 # Params should contain *values*. Note that if we just call 640 # value(), then that forces the value to be a numeric value. 641 # Notably, we allow Params with domain==Any to hold strings, tuples, 642 # etc. The following lets us use NumericValues to initialize 643 # Params, but is optimized to check for "known" native types to 644 # bypass a potentially expensive isinstance()==False call. 645 # 646 if value.__class__ not in native_types: 647 if isinstance(value, NumericValue): 648 value = value() 649 650 # 651 # Set the value depending on the type of param value. 652 # 653 try: 654 if index is None and not self.is_indexed(): 655 self._data[None] = self 656 self.set_value(value, index) 657 return self 658 elif self._mutable: 659 obj = self._data[index] = _ParamData(self) 660 obj.set_value(value, index) 661 return obj 662 else: 663 self._data[index] = value 664 # Because we do not have a _ParamData, we cannot rely on the 665 # validation that occurs in _ParamData.set_value() 666 self._validate_value(index, value, _check_domain) 667 return value 668 except: 669 del self._data[index] 670 raise 671 672 673 def _validate_value(self, index, value, validate_domain=True, data=None): 674 """ 675 Validate a given input/value pair. 676 """ 677 # 678 # Check if the value is valid within the current domain 679 # 680 if validate_domain and not value in self.domain: 681 if index is NOTSET: 682 index = data.index() 683 raise ValueError( 684 "Invalid parameter value: %s[%s] = '%s', value type=%s.\n" 685 "\tValue not in parameter domain %s" % 686 (self.name, index, value, type(value), self.domain.name)) 687 if self._validate: 688 if index is NOTSET: 689 index = data.index() 690 valid = apply_parameterized_indexed_rule( 691 self, self._validate, self.parent_block(), value, index ) 692 if not valid: 693 raise ValueError( 694 "Invalid parameter value: %s[%s] = '%s', value type=%s.\n" 695 "\tValue failed parameter validation rule" % 696 ( self.name, index, value, type(value) ) ) 697 698 699 def construct(self, data=None): 700 """ 701 Initialize this component. 702 703 A parameter is constructed using the initial data or 704 the data loaded from an external source. We first 705 set all the values based on self._rule, and then 706 allow the data dictionary to overwrite anything. 707 708 Note that we allow an undefined Param value to be 709 constructed. We throw an exception if a user tries 710 to use an uninitialized Param. 711 """ 712 if self._constructed: 713 return 714 715 timer = ConstructionTimer(self) 716 if is_debug_set(logger): #pragma:nocover 717 logger.debug("Constructing Param, name=%s, from data=%s" 718 % ( self.name, str(data) )) 719 720 try: 721 # 722 # If the default value is a simple type, we check it versus 723 # the domain. 724 # 725 val = self._default_val 726 if val is not Param.NoValue \ 727 and type(val) in native_types \ 728 and val not in self.domain: 729 raise ValueError( 730 "Default value (%s) is not valid for Param %s domain %s" % 731 (str(val), self.name, self.domain.name)) 732 # 733 # Flag that we are in the "during construction" phase 734 # 735 self._constructed = None 736 # 737 # Step #1: initialize data from rule value 738 # 739 self._construct_from_rule_using_setitem() 740 # 741 # Step #2: allow any user-specified (external) data to override 742 # the initialization 743 # 744 if data is not None: 745 try: 746 data_items = data.items() 747 except AttributeError: 748 raise ValueError( 749 "Attempting to initialize parameter=%s with data=%s.\n" 750 "\tData type is not a mapping type, and a Mapping is " 751 "expected." % (self.name, str(data)) ) 752 else: 753 data_items = iter(()) 754 755 try: 756 for key, val in data_items: 757 self._setitem_when_not_present( 758 self._validate_index(key), val) 759 except: 760 msg = sys.exc_info()[1] 761 raise RuntimeError( 762 "Failed to set value for param=%s, index=%s, value=%s.\n" 763 "\tsource error message=%s" 764 % (self.name, str(key), str(val), str(msg)) ) 765 # 766 # Flag that things are fully constructed now (and changing an 767 # immutable Param is now an exception). 768 # 769 self._constructed = True 770 771 # populate all other indices with default data 772 # (avoids calling _set_contains on self._index at runtime) 773 if self._dense_initialize: 774 self.to_dense_data() 775 finally: 776 timer.report() 777 778 def _pprint(self): 779 """ 780 Return data that will be printed for this component. 781 """ 782 if self._default_val is Param.NoValue: 783 default = "None" # for backwards compatibility in reporting 784 elif type(self._default_val) is types.FunctionType: 785 default = "(function)" 786 else: 787 default = str(self._default_val) 788 if self._mutable or not self.is_indexed(): 789 dataGen = lambda k, v: [ v._value, ] 790 else: 791 dataGen = lambda k, v: [ v, ] 792 headers = [ 793 ("Size", len(self)), 794 ("Index", self._index if self.is_indexed() else None), 795 ("Domain", self.domain.name), 796 ("Default", default), 797 ("Mutable", self._mutable), 798 ] 799 if self._units is not None: 800 headers.append(('Units', str(self._units))) 801 return ( headers, 802 self.sparse_iteritems(), 803 ("Value",), 804 dataGen, 805 ) 806 807 808class ScalarParam(_ParamData, Param): 809 810 def __init__(self, *args, **kwds): 811 Param.__init__(self, *args, **kwds) 812 _ParamData.__init__(self, component=self) 813 814 # 815 # Since this class derives from Component and Component.__getstate__ 816 # just packs up the entire __dict__ into the state dict, there s 817 # nothng special that we need to do here. We will just defer to the 818 # super() get/set state. Since all of our get/set state methods 819 # rely on super() to traverse the MRO, this will automatically pick 820 # up both the Component and Data base classes. 821 # 822 823 def __call__(self, exception=True): 824 """ 825 Return the value of this parameter. 826 """ 827 if self._constructed: 828 if not self._data: 829 if self._mutable: 830 # This will trigger populating the _data dict and setting 831 # the _default, if applicable 832 self[None] 833 else: 834 # Immutable Param defaults never get added to the 835 # _data dict 836 return self[None] 837 return super(ScalarParam, self).__call__(exception=exception) 838 if exception: 839 raise ValueError( 840 "Evaluating the numeric value of parameter '%s' before\n\t" 841 "the Param has been constructed (there is currently no " 842 "value to return)." % (self.name,) ) 843 844 def set_value(self, value, index=NOTSET): 845 if index is NOTSET: 846 index = None 847 if self._constructed and not self._mutable: 848 _raise_modifying_immutable_error(self, index) 849 if not self._data: 850 self._data[index] = self 851 super(ScalarParam, self).set_value(value, index) 852 853 def is_constant(self): 854 """Determine if this ScalarParam is constant (and can be eliminated) 855 856 Returns False if either unconstructed or mutable, as it must be kept 857 in expressions (as it either doesn't have a value yet or the value 858 can change later. 859 """ 860 return self._constructed and not self._mutable 861 862 863class SimpleParam(metaclass=RenamedClass): 864 __renamed__new_class__ = ScalarParam 865 __renamed__version__ = '6.0' 866 867 868class IndexedParam(Param): 869 870 def __call__(self, exception=True): 871 """Compute the value of the parameter""" 872 if exception: 873 raise TypeError('Cannot compute the value of an indexed Param (%s)' 874 % (self.name,) ) 875 876