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 11import logging 12import sys 13from copy import deepcopy 14from pickle import PickleError 15from weakref import ref as weakref_ref 16 17import pyomo.common 18from pyomo.common.deprecation import deprecated, relocated_module_attribute 19from pyomo.common.factory import Factory 20from pyomo.common.fileutils import StreamIndenter 21from pyomo.common.formatting import tabular_writer 22from pyomo.common.modeling import NOTSET 23from pyomo.common.sorting import sorted_robust 24from pyomo.core.pyomoobject import PyomoObject 25from pyomo.core.base.component_namer import name_repr, index_repr 26 27logger = logging.getLogger('pyomo.core') 28 29relocated_module_attribute( 30 'ComponentUID', 'pyomo.core.base.componentuid.ComponentUID', 31 version='5.7.2') 32 33 34class ModelComponentFactoryClass(Factory): 35 36 def register(self, doc=None): 37 def fn(cls): 38 return super(ModelComponentFactoryClass, self).register( 39 cls.__name__, doc)(cls) 40 return fn 41 42ModelComponentFactory = ModelComponentFactoryClass('model component') 43 44 45def name(component, index=NOTSET, fully_qualified=False, relative_to=None): 46 """ 47 Return a string representation of component for a specific 48 index value. 49 """ 50 base = component.getname( 51 fully_qualified=fully_qualified, relative_to=relative_to 52 ) 53 if index is NOTSET: 54 return base 55 else: 56 if index not in component.index_set(): 57 raise KeyError( "Index %s is not valid for component %s" 58 % (index, component.name) ) 59 return base + index_repr( index ) 60 61 62@deprecated(msg="The cname() function has been renamed to name()", 63 version='5.6.9') 64def cname(*args, **kwds): 65 return name(*args, **kwds) 66 67 68class CloneError(pyomo.common.errors.PyomoException): 69 pass 70 71class _ComponentBase(PyomoObject): 72 """A base class for Component and ComponentData 73 74 This class defines some fundamental methods and properties that are 75 expected for all Component-like objects. They are centralized here 76 to avoid repeated code in the Component and ComponentData classes. 77 """ 78 __slots__ = () 79 80 _PPRINT_INDENT = " " 81 82 def is_component_type(self): 83 """Return True if this class is a Pyomo component""" 84 return True 85 86 def __deepcopy__(self, memo): 87 # The problem we are addressing is when we want to clone a 88 # sub-block in a model. In that case, the block can have 89 # references to both child components and to external 90 # ComponentData (mostly through expressions pointing to Vars 91 # and Params outside this block). For everything stored beneath 92 # this block, we want to clone the Component (and all 93 # corresponding ComponentData objects). But for everything 94 # stored outside this Block, we want to do a simple shallow 95 # copy. 96 # 97 # Nominally, expressions only point to ComponentData 98 # derivatives. However, with the development of Expression 99 # Templates (and the corresponding _GetItemExpression object), 100 # expressions can refer to container (non-Simple) components, so 101 # we need to override __deepcopy__ for both Component and 102 # ComponentData. 103 104 #try: 105 # print("Component: %s" % (self.name,)) 106 #except: 107 # print("DANGLING ComponentData: %s on %s" % ( 108 # type(self),self.parent_component())) 109 110 # Note: there is an edge case when cloning a block: the initial 111 # call to deepcopy (on the target block) has __block_scope__ 112 # defined, however, the parent block of self is either None, or 113 # is (by definition) out of scope. So we will check that 114 # id(self) is not in __block_scope__: if it is, then this is the 115 # top-level block and we need to do the normal deepcopy. 116 if '__block_scope__' in memo and \ 117 id(self) not in memo['__block_scope__']: 118 _known = memo['__block_scope__'] 119 _new = [] 120 tmp = self.parent_block() 121 tmpId = id(tmp) 122 # Note: normally we would need to check that tmp does not 123 # end up being None. However, since clone() inserts 124 # id(None) into the __block_scope__ dictionary, we are safe 125 while tmpId not in _known: 126 _new.append(tmpId) 127 tmp = tmp.parent_block() 128 tmpId = id(tmp) 129 130 # Remember whether all newly-encountered blocks are in or 131 # out of scope (prevent duplicate work) 132 for _id in _new: 133 _known[_id] = _known[tmpId] 134 135 if not _known[tmpId]: 136 # component is out-of-scope. shallow copy only 137 ans = memo[id(self)] = self 138 return ans 139 140 # 141 # There is a particularly subtle bug with 'uncopyable' 142 # attributes: if the exception is thrown while copying a complex 143 # data structure, we can be in a state where objects have been 144 # created and assigned to the memo in the try block, but they 145 # haven't had their state set yet. When the exception moves us 146 # into the except block, we need to effectively "undo" those 147 # partially copied classes. The only way is to restore the memo 148 # to the state it was in before we started. Right now, our 149 # solution is to make a (shallow) copy of the memo before each 150 # operation and restoring it in the case of exception. 151 # Unfortunately that is a lot of usually unnecessary work. 152 # Since *most* classes are copyable, we will avoid that 153 # "paranoia" unless the naive clone generated an error - in 154 # which case Block.clone() will switch over to the more 155 # "paranoid" mode. 156 # 157 paranoid = memo.get('__paranoid__', None) 158 159 ans = memo[id(self)] = self.__class__.__new__(self.__class__) 160 # We can't do the "obvious", since this is a (partially) 161 # slot-ized class and the __dict__ structure is 162 # nonauthoritative: 163 # 164 # for key, val in self.__dict__.iteritems(): 165 # object.__setattr__(ans, key, deepcopy(val, memo)) 166 # 167 # Further, __slots__ is also nonauthoritative (this may be a 168 # singleton component -- in which case it also has a __dict__). 169 # Plus, as this may be a derived class with several layers of 170 # slots. So, we will resort to partially "pickling" the object, 171 # deepcopying the state dict, and then restoring the copy into 172 # the new instance. 173 # 174 # [JDS 7/7/14] I worry about the efficiency of using both 175 # getstate/setstate *and* deepcopy, but we need deepcopy to 176 # update the _parent refs appropriately, and since this is a 177 # slot-ized class, we cannot overwrite the __deepcopy__ 178 # attribute to prevent infinite recursion. 179 state = self.__getstate__() 180 try: 181 if paranoid: 182 saved_memo = dict(memo) 183 new_state = deepcopy(state, memo) 184 except: 185 if paranoid: 186 # Note: memo is intentionally pass-by-reference. We 187 # need to clear and reset the object we were handed (and 188 # not overwrite it) 189 memo.clear() 190 memo.update(saved_memo) 191 elif paranoid is not None: 192 raise PickleError() 193 new_state = {} 194 for k, v in state.items(): 195 try: 196 if paranoid: 197 saved_memo = dict(memo) 198 new_state[k] = deepcopy(v, memo) 199 except CloneError: 200 raise 201 except: 202 if paranoid: 203 memo.clear() 204 memo.update(saved_memo) 205 elif paranoid is None: 206 logger.warning(""" 207 Uncopyable field encountered when deep 208 copying outside the scope of Block.clone(). 209 There is a distinct possibility that the new 210 copy is not complete. To avoid this 211 situation, either use Block.clone() or set 212 'paranoid' mode by adding '__paranoid__' == 213 True to the memo before calling 214 copy.deepcopy.""") 215 if self.model() is self: 216 what = 'Model' 217 else: 218 what = 'Component' 219 logger.error( 220 "Unable to clone Pyomo component attribute.\n" 221 "%s '%s' contains an uncopyable field '%s' (%s)" 222 % ( what, self.name, k, type(v) )) 223 # If this is an abstract model, then we are probably 224 # in the middle of create_instance, and the model 225 # that will eventually become the concrete model is 226 # missing initialization data. This is an 227 # exceptional event worthy of a stronger (and more 228 # informative) error. 229 if not self.parent_component()._constructed: 230 raise CloneError( 231 "Uncopyable attribute (%s) encountered when " 232 "cloning component %s on an abstract block. " 233 "The resulting instance is therefore " 234 "missing data from the original abstract model " 235 "and likely will not construct correctly. " 236 "Consider changing how you initialize this " 237 "component or using a ConcreteModel." 238 % ( k, self.name )) 239 ans.__setstate__(new_state) 240 return ans 241 242 @deprecated("""The cname() method has been renamed to getname(). 243 The preferred method of obtaining a component name is to use the 244 .name property, which returns the fully qualified component name. 245 The .local_name property will return the component name only within 246 the context of the immediate parent container.""", version='5.0') 247 def cname(self, *args, **kwds): 248 return self.getname(*args, **kwds) 249 250 def pprint(self, ostream=None, verbose=False, prefix=""): 251 """Print component information 252 253 Note that this method is generally only reachable through 254 ComponentData objects in an IndexedComponent container. 255 Components, including unindexed Component derivatives and both 256 scalar and indexed IndexedComponent derivatives will see 257 :py:meth:`Component.pprint()` 258 """ 259 comp = self.parent_component() 260 _attr, _data, _header, _fcn = comp._pprint() 261 if isinstance(type(_data), str): 262 # If the component _pprint only returned a pre-formatted 263 # result, then we have no way to only emit the information 264 # for this _data object. 265 _name = comp.local_name 266 else: 267 # restrict output to only this data object 268 _data = iter( ((self.index(), self),) ) 269 _name = "{Member of %s}" % (comp.local_name,) 270 self._pprint_base_impl( 271 ostream, verbose, prefix, _name, comp.doc, 272 comp.is_constructed(), _attr, _data, _header, _fcn) 273 274 @property 275 def name(self): 276 """Get the fully qualifed component name.""" 277 return self.getname(fully_qualified=True) 278 279 # Adding a setter here to help users adapt to the new 280 # setting. The .name attribute is now ._name. It should 281 # never be assigned to by user code. 282 @name.setter 283 def name(self, val): 284 raise ValueError( 285 "The .name attribute is now a property method " 286 "that returns the fully qualified component name. " 287 "Assignment is not allowed.") 288 289 @property 290 def local_name(self): 291 """Get the component name only within the context of 292 the immediate parent container.""" 293 return self.getname(fully_qualified=False) 294 295 @property 296 def active(self): 297 """Return the active attribute""" 298 # Normal components cannot be deactivated 299 return True 300 301 @active.setter 302 def active(self, value): 303 """Set the active attribute to the given value""" 304 raise AttributeError( 305 "Setting the 'active' flag on a component that does not " 306 "support deactivation is not allowed.") 307 308 def _pprint_base_impl(self, ostream, verbose, prefix, _name, _doc, 309 _constructed, _attr, _data, _header, _fcn): 310 if ostream is None: 311 ostream = sys.stdout 312 if prefix: 313 ostream = StreamIndenter(ostream, prefix) 314 315 # FIXME: HACK for backwards compatability with suppressing the 316 # header for the top block 317 if not _attr and self.parent_block() is None: 318 _name = '' 319 320 # We only indent everything if we printed the header 321 if _attr or _name or _doc: 322 ostream = StreamIndenter(ostream, self._PPRINT_INDENT) 323 # The first line should be a hanging indent (i.e., not indented) 324 ostream.newline = False 325 326 if _name: 327 ostream.write(_name+" : ") 328 if _doc: 329 ostream.write(_doc+'\n') 330 if _attr: 331 ostream.write(", ".join("%s=%s" % (k,v) for k,v in _attr)) 332 if _attr or _name or _doc: 333 ostream.write("\n") 334 335 if not _constructed: 336 # HACK: for backwards compatability, Abstract blocks will 337 # still print their assigned components. Should we instead 338 # always pprint unconstructed components (possibly 339 # suppressing the table header if the table is empty)? 340 if self.parent_block() is not None: 341 ostream.write("Not constructed\n") 342 return 343 344 if type(_fcn) is tuple: 345 _fcn, _fcn2 = _fcn 346 else: 347 _fcn2 = None 348 349 if _header is not None: 350 if _fcn2 is not None: 351 _data_dict = dict(_data) 352 _data = _data_dict.items() 353 tabular_writer( ostream, '', _data, _header, _fcn ) 354 if _fcn2 is not None: 355 for _key in sorted_robust(_data_dict): 356 _fcn2(ostream, _key, _data_dict[_key]) 357 elif _fcn is not None: 358 _data_dict = dict(_data) 359 for _key in sorted_robust(_data_dict): 360 _fcn(ostream, _key, _data_dict[_key]) 361 elif _data is not None: 362 ostream.write(_data) 363 364 365class Component(_ComponentBase): 366 """ 367 This is the base class for all Pyomo modeling components. 368 369 Constructor arguments: 370 ctype The class type for the derived subclass 371 doc A text string describing this component 372 name A name for this component 373 374 Public class attributes: 375 doc A text string describing this component 376 377 Private class attributes: 378 _constructed A boolean that is true if this component has been 379 constructed 380 _parent A weakref to the parent block that owns this component 381 _ctype The class type for the derived subclass 382 """ 383 384 def __init__ (self, **kwds): 385 # 386 # Get arguments 387 # 388 self._ctype = kwds.pop('ctype', None) 389 self.doc = kwds.pop('doc', None) 390 self._name = kwds.pop('name', str(type(self).__name__)) 391 if kwds: 392 raise ValueError( 393 "Unexpected keyword options found while constructing '%s':\n\t%s" 394 % ( type(self).__name__, ','.join(sorted(kwds.keys())) )) 395 # 396 # Verify that ctype has been specified. 397 # 398 if self._ctype is None: 399 raise pyomo.common.DeveloperError( 400 "Must specify a component type for class %s!" 401 % ( type(self).__name__, ) ) 402 # 403 self._constructed = False 404 self._parent = None # Must be a weakref 405 406 def __getstate__(self): 407 """ 408 This method must be defined to support pickling because this class 409 owns weakrefs for '_parent'. 410 """ 411 # 412 # Nominally, __getstate__() should return: 413 # 414 # state = super(Class, self).__getstate__() 415 # for i in Class.__dict__: 416 # state[i] = getattr(self,i) 417 # return state 418 # 419 # However, in this case, the (nominal) parent class is 'object', 420 # and object does not implement __getstate__. So, we will check 421 # to make sure that there is a base __getstate__() to call... 422 # 423 _base = super(Component,self) 424 if hasattr(_base, '__getstate__'): 425 state = _base.__getstate__() 426 for key,val in self.__dict__.items(): 427 if key not in state: 428 state[key] = val 429 else: 430 state = dict(self.__dict__) 431 if self._parent is not None: 432 state['_parent'] = self._parent() 433 return state 434 435 def __setstate__(self, state): 436 """ 437 This method must be defined to support pickling because this class 438 owns weakrefs for '_parent'. 439 """ 440 if state['_parent'] is not None and \ 441 type(state['_parent']) is not weakref_ref: 442 state['_parent'] = weakref_ref(state['_parent']) 443 # 444 # Note: our model for setstate is for derived classes to modify 445 # the state dictionary as control passes up the inheritance 446 # hierarchy (using super() calls). All assignment of state -> 447 # object attributes is handled at the last class before 'object' 448 # (which may -- or may not (thanks to MRO) -- be here. 449 # 450 _base = super(Component,self) 451 if hasattr(_base, '__setstate__'): 452 _base.__setstate__(state) 453 else: 454 for key, val in state.items(): 455 # Note: per the Python data model docs, we explicitly 456 # set the attribute using object.__setattr__() instead 457 # of setting self.__dict__[key] = val. 458 object.__setattr__(self, key, val) 459 460 @property 461 def ctype(self): 462 """Return the class type for this component""" 463 return self._ctype 464 465 @deprecated("Component.type() method has been replaced by the " 466 ".ctype property.", version='5.7') 467 def type(self): 468 """Return the class type for this component""" 469 return self.ctype 470 471 def construct(self, data=None): #pragma:nocover 472 """API definition for constructing components""" 473 pass 474 475 def is_constructed(self): #pragma:nocover 476 """Return True if this class has been constructed""" 477 return self._constructed 478 479 def reconstruct(self, data=None): 480 """REMOVED: reconstruct() was removed in Pyomo 6.0. 481 482 Re-constructing model components was fragile and did not 483 correctly update instances of the component used in other 484 components or contexts (this was particularly problemmatic for 485 Var, Param, and Set). Users who wish to reproduce the old 486 behavior of reconstruct(), are comfortable manipulating 487 non-public interfaces, and who take the time to verify that the 488 correct thing happens to their model can approximate the old 489 behavior of reconstruct with: 490 491 component.clear() 492 component._constructed = False 493 component.construct() 494 495 """ 496 raise AttributeError(self.reconstruct.__doc__) 497 498 def valid_model_component(self): 499 """Return True if this can be used as a model component.""" 500 return True 501 502 def pprint(self, ostream=None, verbose=False, prefix=""): 503 """Print component information""" 504 self._pprint_base_impl( 505 ostream, verbose, prefix, self.local_name, self.doc, 506 self.is_constructed(), *self._pprint() 507 ) 508 509 def display(self, ostream=None, verbose=False, prefix=""): 510 self.pprint(ostream=ostream, prefix=prefix) 511 512 def parent_component(self): 513 """Returns the component associated with this object.""" 514 return self 515 516 def parent_block(self): 517 """Returns the parent of this object.""" 518 if self._parent is None: 519 return None 520 else: 521 return self._parent() 522 523 def model(self): 524 """Returns the model associated with this object.""" 525 # This is a re-implementation of Component.parent_block(), 526 # duplicated for effficiency to avoid the method call 527 if self._parent is None: 528 return None 529 ans = self._parent() 530 531 if ans is None: 532 return None 533 # Defer to the (simple) block's model() method to walk up the 534 # hierarchy. This is because the top-level block can be a model, 535 # but nothing else (e.g., calling model() on a Var not attached 536 # to a model should return None, but calling model() on a Block 537 # not attached to anything else should return the Block) 538 return ans.model() 539 540 def root_block(self): 541 """Return self.model()""" 542 return self.model() 543 544 def __str__(self): 545 """Return the component name""" 546 return self.name 547 548 def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): 549 """Return the component name""" 550 if compute_values: 551 try: 552 return str(self()) 553 except: 554 pass 555 return self.name 556 557 def getname(self, fully_qualified=False, name_buffer=None, relative_to=None): 558 """Returns the component name associated with this object. 559 560 Parameters 561 ---------- 562 fully_qualified: bool 563 Generate full name from nested block names 564 565 name_buffer: dict 566 A dictionary that caches encountered names and indices. 567 Providing a ``name_buffer`` can significantly speed up 568 iterative name generation 569 570 relative_to: Block 571 Generate fully_qualified names reletive to the specified block. 572 """ 573 local_name = self._name 574 if fully_qualified: 575 pb = self.parent_block() 576 if relative_to is None: 577 relative_to = self.model() 578 if pb is not None and pb is not relative_to: 579 ans = pb.getname(fully_qualified, name_buffer, relative_to) \ 580 + "." + name_repr(local_name) 581 elif pb is None and relative_to != self.model(): 582 raise RuntimeError( 583 "The relative_to argument was specified but not found " 584 "in the block hierarchy: %s" % str(relative_to)) 585 else: 586 ans = name_repr(local_name) 587 else: 588 # Note: we want "getattr(x.parent_block(), x.local_name) == x" 589 # so we do not want to call _safe_name_str, as that could 590 # add quotes or otherwise escape the string. 591 ans = local_name 592 if name_buffer is not None: 593 name_buffer[id(self)] = ans 594 return ans 595 596 @property 597 def name(self): 598 """Get the fully qualifed component name.""" 599 return self.getname(fully_qualified=True) 600 601 # Allow setting a componet's name if it is not owned by a parent 602 # block (this supports, e.g., naming a model) 603 @name.setter 604 def name(self, val): 605 if self.parent_block() is None: 606 self._name = val 607 else: 608 raise ValueError( 609 "The .name attribute is not settable when the component " 610 "is assigned to a Block.\nTriggered by attempting to set " 611 "component '%s' to name '%s'" % (self.name,val)) 612 613 def is_indexed(self): 614 """Return true if this component is indexed""" 615 return False 616 617 def clear_suffix_value(self, suffix_or_name, expand=True): 618 """Clear the suffix value for this component data""" 619 if isinstance(suffix_or_name, str): 620 import pyomo.core.base.suffix 621 for name_, suffix_ in pyomo.core.base.suffix.active_suffix_generator(self.model()): 622 if suffix_or_name == name_: 623 suffix_.clear_value(self, expand=expand) 624 break 625 else: 626 suffix_or_name.clear_value(self, expand=expand) 627 628 def set_suffix_value(self, suffix_or_name, value, expand=True): 629 """Set the suffix value for this component data""" 630 if isinstance(suffix_or_name, str): 631 import pyomo.core.base.suffix 632 for name_, suffix_ in pyomo.core.base.suffix.active_suffix_generator(self.model()): 633 if suffix_or_name == name_: 634 suffix_.set_value(self, value, expand=expand) 635 break 636 else: 637 suffix_or_name.set_value(self, value, expand=expand) 638 639 def get_suffix_value(self, suffix_or_name, default=None): 640 """Get the suffix value for this component data""" 641 if isinstance(suffix_or_name, str): 642 import pyomo.core.base.suffix 643 for name_, suffix_ in pyomo.core.base.suffix.active_suffix_generator(self.model()): 644 if suffix_or_name == name_: 645 return suffix_.get(self, default) 646 else: 647 return suffix_or_name.get(self, default) 648 649 650class ActiveComponent(Component): 651 """A Component that makes semantic sense to activate or deactivate 652 in a model. 653 654 Private class attributes: 655 _active A boolean that is true if this component will be 656 used in model operations 657 """ 658 659 def __init__(self, **kwds): 660 self._active = True 661 super(ActiveComponent, self).__init__(**kwds) 662 663 @property 664 def active(self): 665 """Return the active attribute""" 666 return self._active 667 668 @active.setter 669 def active(self, value): 670 """Set the active attribute to the given value""" 671 raise AttributeError( 672 "Assignment not allowed. Use the (de)activate methods." ) 673 674 def activate(self): 675 """Set the active attribute to True""" 676 self._active=True 677 678 def deactivate(self): 679 """Set the active attribute to False""" 680 self._active=False 681 682 683class ComponentData(_ComponentBase): 684 """ 685 This is the base class for the component data used 686 in Pyomo modeling components. Subclasses of ComponentData are 687 used in indexed components, and this class assumes that indexed 688 components are subclasses of IndexedComponent. Note that 689 ComponentData instances do not store their index. This makes 690 some operations significantly more expensive, but these are (a) 691 associated with I/O generation and (b) this cost can be managed 692 with caches. 693 694 Constructor arguments: 695 owner The component that owns this data object 696 697 Private class attributes: 698 _component A weakref to the component that owns this data object 699 """ 700 701 __pickle_slots__ = ('_component',) 702 __slots__ = __pickle_slots__ + ('__weakref__',) 703 704 def __init__(self, component): 705 # 706 # ComponentData objects are typically *private* objects for 707 # indexed / sparse indexed components. As such, the (derived) 708 # class needs to make sure that the owning component is *always* 709 # passed as the owner (and that owner is never None). Not validating 710 # this assumption is significantly faster. 711 # 712 self._component = weakref_ref(component) 713 714 def __getstate__(self): 715 """Prepare a picklable state of this instance for pickling. 716 717 Nominally, __getstate__() should return: 718 719 state = super(Class, self).__getstate__() 720 for i in Class.__slots__: 721 state[i] = getattr(self,i) 722 return state 723 724 However, in this case, the (nominal) parent class is 'object', 725 and object does not implement __getstate__. So, we will check 726 to make sure that there is a base __getstate__() to call... 727 You might think that there is nothing to check, but multiple 728 inheritance could mean that another class got stuck between 729 this class and "object" in the MRO. 730 731 This method must be defined to support pickling because this 732 class owns weakrefs for '_component', which must be either 733 removed or converted to hard references prior to pickling. 734 735 Further, since there is only a single slot, and that slot 736 (_component) requires special processing, we will just deal with 737 it explicitly. As _component is a weakref (not pickable), we 738 need to resolve it to a concrete object. 739 """ 740 _base = super(ComponentData,self) 741 if hasattr(_base, '__getstate__'): 742 state = _base.__getstate__() 743 else: 744 state = {} 745 # 746 if self._component is None: 747 state['_component'] = None 748 else: 749 state['_component'] = self._component() 750 return state 751 752 def __setstate__(self, state): 753 """Restore a pickled state into this instance 754 755 Note: our model for setstate is for derived classes to modify 756 the state dictionary as control passes up the inheritance 757 hierarchy (using super() calls). All assignment of state -> 758 object attributes is handled at the last class before 'object' 759 (which may -- or may not (thanks to MRO) -- be here. 760 761 This method must be defined to support unpickling because this 762 class owns weakrefs for '_component', which must be restored 763 from the hard references used in the piclke. 764 """ 765 # 766 # FIXME: We shouldn't have to check for weakref.ref here, but if 767 # we don't the model cloning appears to fail (in the Benders 768 # example) 769 # 770 if state['_component'] is not None and \ 771 type(state['_component']) is not weakref_ref: 772 state['_component'] = weakref_ref(state['_component']) 773 # 774 # Note: our model for setstate is for derived classes to modify 775 # the state dictionary as control passes up the inheritance 776 # hierarchy (using super() calls). All assignment of state -> 777 # object attributes is handled at the last class before 'object' 778 # (which may -- or may not (thanks to MRO) -- be here. 779 # 780 _base = super(ComponentData,self) 781 if hasattr(_base, '__setstate__'): 782 _base.__setstate__(state) 783 else: 784 for key, val in state.items(): 785 # Note: per the Python data model docs, we explicitly 786 # set the attribute using object.__setattr__() instead 787 # of setting self.__dict__[key] = val. 788 object.__setattr__(self, key, val) 789 790 @property 791 def ctype(self): 792 """Return the class type for this component""" 793 _parent = self.parent_component() 794 if _parent is None: 795 return None 796 return _parent._ctype 797 798 @deprecated("Component.type() method has been replaced by the " 799 ".ctype property.", version='5.7') 800 def type(self): 801 """Return the class type for this component""" 802 return self.ctype 803 804 def parent_component(self): 805 """Returns the component associated with this object.""" 806 if self._component is None: 807 return None 808 return self._component() 809 810 def parent_block(self): 811 """Return the parent of the component that owns this data. """ 812 # This is a re-implementation of parent_component(), duplicated 813 # for effficiency to avoid the method call 814 if self._component is None: 815 return None 816 comp = self._component() 817 818 # This is a re-implementation of Component.parent_block(), 819 # duplicated for effficiency to avoid the method call 820 if comp._parent is None: 821 return None 822 return comp._parent() 823 824 def model(self): 825 """Return the model of the component that owns this data. """ 826 ans = self.parent_block() 827 if ans is None: 828 return None 829 # Defer to the (simple) block's model() method to walk up the 830 # hierarchy. This is because the top-level block can be a model, 831 # but nothing else (e.g., calling model() on a Var not attached 832 # to a model should return None, but calling model() on a Block 833 # not attached to anything else should return the Block) 834 return ans.model() 835 836 def index(self): 837 """ 838 Returns the index of this ComponentData instance relative 839 to the parent component index set. None is returned if 840 this instance does not have a parent component, or if 841 - for some unknown reason - this instance does not belong 842 to the parent component's index set. This method is not 843 intended to be a fast method; it should be used rarely, 844 primarily in cases of label formulation. 845 """ 846 self_component = self.parent_component() 847 if self_component is None: 848 return None 849 for idx, component_data in self_component.items(): 850 if component_data is self: 851 return idx 852 return None 853 854 def __str__(self): 855 """Return a string with the component name and index""" 856 return self.name 857 858 def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): 859 """ 860 Return a string representation of this component, 861 applying the labeler if passed one. 862 """ 863 if compute_values: 864 try: 865 return str(self()) 866 except: 867 pass 868 if smap: 869 return smap.getSymbol(self, labeler) 870 if labeler is not None: 871 return labeler(self) 872 else: 873 return self.__str__() 874 875 def getname(self, fully_qualified=False, name_buffer=None, relative_to=None): 876 """Return a string with the component name and index""" 877 # 878 # Using the buffer, which is a dictionary: id -> string 879 # 880 if name_buffer is not None and id(self) in name_buffer: 881 # Return the name if it is in the buffer 882 return name_buffer[id(self)] 883 884 c = self.parent_component() 885 if c is self: 886 # 887 # This is a scalar component, so call the 888 # Component.getname() method 889 # 890 return super(ComponentData, self).getname( 891 fully_qualified, name_buffer, relative_to) 892 elif c is not None: 893 # 894 # Get the name of the parent component 895 # 896 base = c.getname(fully_qualified, name_buffer, relative_to) 897 else: 898 # 899 # Defensive: this is a ComponentData without a valid 900 # parent_component. As this usually occurs when handling 901 # exceptions during model construction, we need to ensure 902 # that this method doesn't itself raise another exception. 903 # 904 return '[Unattached %s]' % (type(self).__name__,) 905 906 if name_buffer is not None: 907 # Iterate through the dictionary and generate all names in 908 # the buffer 909 for idx, obj in c.items(): 910 name_buffer[id(obj)] = base + index_repr(idx) 911 if id(self) in name_buffer: 912 # Return the name if it is in the buffer 913 return name_buffer[id(self)] 914 else: 915 # 916 # No buffer, so we iterate through the component _data 917 # dictionary until we find this object. This can be much 918 # more expensive than if a buffer is provided. 919 # 920 for idx, obj in c.items(): 921 if obj is self: 922 return base + index_repr(idx) 923 # 924 raise RuntimeError("Fatal error: cannot find the component data in " 925 "the owning component's _data dictionary.") 926 927 def is_indexed(self): 928 """Return true if this component is indexed""" 929 return False 930 931 def clear_suffix_value(self, suffix_or_name, expand=True): 932 """Set the suffix value for this component data""" 933 if isinstance(suffix_or_name, str): 934 import pyomo.core.base.suffix 935 for name_, suffix_ in pyomo.core.base.suffix.active_suffix_generator(self.model()): 936 if suffix_or_name == name_: 937 suffix_.clear_value(self, expand=expand) 938 break 939 else: 940 suffix_or_name.clear_value(self, expand=expand) 941 942 def set_suffix_value(self, suffix_or_name, value, expand=True): 943 """Set the suffix value for this component data""" 944 if isinstance(suffix_or_name, str): 945 import pyomo.core.base.suffix 946 for name_, suffix_ in pyomo.core.base.suffix.active_suffix_generator(self.model()): 947 if suffix_or_name == name_: 948 suffix_.set_value(self, value, expand=expand) 949 break 950 else: 951 suffix_or_name.set_value(self, value, expand=expand) 952 953 def get_suffix_value(self, suffix_or_name, default=None): 954 """Get the suffix value for this component data""" 955 if isinstance(suffix_or_name, str): 956 import pyomo.core.base.suffix 957 for name_, suffix_ in pyomo.core.base.suffix.active_suffix_generator(self.model()): 958 if suffix_or_name == name_: 959 return suffix_.get(self, default) 960 else: 961 return suffix_or_name.get(self, default) 962 963 964class ActiveComponentData(ComponentData): 965 """ 966 This is the base class for the component data used 967 in Pyomo modeling components that can be activated and 968 deactivated. 969 970 It's possible to end up in a state where the parent Component 971 has _active=True but all ComponentData have _active=False. This 972 seems like a reasonable state, though we cannot easily detect 973 this situation. The important thing to avoid is the situation 974 where one or more ComponentData are active, but the parent 975 Component claims active=False. This class structure is designed 976 to prevent this situation. 977 978 Constructor arguments: 979 owner The component that owns this data object 980 981 Private class attributes: 982 _component A weakref to the component that owns this data object 983 _active A boolean that indicates whether this data is active 984 """ 985 986 __slots__ = ( '_active', ) 987 988 def __init__(self, component): 989 super(ActiveComponentData, self).__init__(component) 990 self._active = True 991 992 def __getstate__(self): 993 """ 994 This method must be defined because this class uses slots. 995 """ 996 result = super(ActiveComponentData, self).__getstate__() 997 for i in ActiveComponentData.__slots__: 998 result[i] = getattr(self, i) 999 return result 1000 1001 # Since this class requires no special processing of the state 1002 # dictionary, it does not need to implement __setstate__() 1003 1004 @property 1005 def active(self): 1006 """Return the active attribute""" 1007 return self._active 1008 1009 @active.setter 1010 def active(self, value): 1011 """Set the active attribute to a specified value.""" 1012 raise AttributeError( 1013 "Assignment not allowed. Use the (de)activate method" ) 1014 1015 def activate(self): 1016 """Set the active attribute to True""" 1017 self._active = self.parent_component()._active = True 1018 1019 def deactivate(self): 1020 """Set the active attribute to False""" 1021 self._active = False 1022 1023