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__ = ['Block', 'TraversalStrategy', 'SortComponents', 12 'active_components', 'components', 'active_components_data', 13 'components_data', 'SimpleBlock', 'ScalarBlock'] 14 15import copy 16import logging 17import sys 18import weakref 19import textwrap 20 21from inspect import isclass 22from operator import itemgetter 23from io import StringIO 24 25from pyomo.common.collections import Mapping 26from pyomo.common.deprecation import deprecated, deprecation_warning, RenamedClass 27from pyomo.common.fileutils import StreamIndenter 28from pyomo.common.log import is_debug_set 29from pyomo.common.sorting import sorted_robust 30from pyomo.common.timing import ConstructionTimer 31from pyomo.core.base.component import ( 32 Component, ActiveComponentData, ModelComponentFactory, 33) 34from pyomo.core.base.componentuid import ComponentUID 35from pyomo.core.base.set import GlobalSetBase, _SetDataBase 36from pyomo.core.base.var import Var 37from pyomo.core.base.initializer import Initializer 38from pyomo.core.base.indexed_component import ( 39 ActiveIndexedComponent, UnindexedComponent_set, 40) 41 42from pyomo.opt.base import ProblemFormat, guess_format 43from pyomo.opt import WriterFactory 44 45logger = logging.getLogger('pyomo.core') 46 47 48class _generic_component_decorator(object): 49 """A generic decorator that wraps Block.__setattr__() 50 51 Arguments 52 --------- 53 component: the Pyomo Component class to construct 54 block: the block onto which to add the new component 55 *args: positional arguments to the Component constructor 56 (*excluding* the block argument) 57 **kwds: keyword arguments to the Component constructor 58 """ 59 def __init__(self, component, block, *args, **kwds): 60 self._component = component 61 self._block = block 62 self._args = args 63 self._kwds = kwds 64 65 def __call__(self, rule): 66 setattr( 67 self._block, 68 rule.__name__, 69 self._component(*self._args, rule=rule, **(self._kwds)) 70 ) 71 return rule 72 73 74class _component_decorator(object): 75 """A class that wraps the _generic_component_decorator, which remembers 76 and provides the Block and component type to the decorator. 77 78 Arguments 79 --------- 80 component: the Pyomo Component class to construct 81 block: the block onto which to add the new component 82 83 """ 84 def __init__(self, block, component): 85 self._block = block 86 self._component = component 87 88 def __call__(self, *args, **kwds): 89 return _generic_component_decorator( 90 self._component, self._block, *args, **kwds) 91 92 93class SubclassOf(object): 94 """This mocks up a tuple-like interface based on subclass relationship. 95 96 Instances of this class present a somewhat tuple-like interface for 97 use in PseudoMap ctype / descend_into. The constructor takes a 98 single ctype argument. When used with PseudoMap (through Block APIs 99 like component_objects()), it will match any ctype that is a 100 subclass of the reference ctype. 101 102 This allows, for example: 103 104 model.component_data_objects(Var, descend_into=SubclassOf(Block)) 105 """ 106 def __init__(self, *ctype): 107 self.ctype = ctype 108 self.__name__ = 'SubclassOf(%s)' % ( 109 ','.join(x.__name__ for x in ctype),) 110 111 def __contains__(self, item): 112 return issubclass(item, self.ctype) 113 114 def __len__(self): 115 return 1 116 117 def __getitem__(self, item): 118 return self 119 120class SortComponents(object): 121 122 """ 123 This class is a convenient wrapper for specifying various sort 124 ordering. We pass these objects to the "sort" argument to various 125 accessors / iterators to control how much work we perform sorting 126 the resultant list. The idea is that 127 "sort=SortComponents.deterministic" is more descriptive than 128 "sort=True". 129 """ 130 unsorted = set() 131 indices = set([1]) 132 declOrder = set([2]) 133 declarationOrder = declOrder 134 alphaOrder = set([3]) 135 alphabeticalOrder = alphaOrder 136 alphabetical = alphaOrder 137 # both alpha and decl orders are deterministic, so only must sort indices 138 deterministic = indices 139 sortBoth = indices | alphabeticalOrder # Same as True 140 alphabetizeComponentAndIndex = sortBoth 141 142 @staticmethod 143 def default(): 144 return set() 145 146 @staticmethod 147 def sorter(sort_by_names=False, sort_by_keys=False): 148 sort = SortComponents.default() 149 if sort_by_names: 150 sort |= SortComponents.alphabeticalOrder 151 if sort_by_keys: 152 sort |= SortComponents.indices 153 return sort 154 155 @staticmethod 156 def sort_names(flag): 157 if type(flag) is bool: 158 return flag 159 else: 160 try: 161 return SortComponents.alphaOrder.issubset(flag) 162 except: 163 return False 164 165 @staticmethod 166 def sort_indices(flag): 167 if type(flag) is bool: 168 return flag 169 else: 170 try: 171 return SortComponents.indices.issubset(flag) 172 except: 173 return False 174 175 176class TraversalStrategy(object): 177 BreadthFirstSearch = (1,) 178 PrefixDepthFirstSearch = (2,) 179 PostfixDepthFirstSearch = (3,) 180 # aliases 181 BFS = BreadthFirstSearch 182 ParentLastDepthFirstSearch = PostfixDepthFirstSearch 183 PostfixDFS = PostfixDepthFirstSearch 184 ParentFirstDepthFirstSearch = PrefixDepthFirstSearch 185 PrefixDFS = PrefixDepthFirstSearch 186 DepthFirstSearch = PrefixDepthFirstSearch 187 DFS = DepthFirstSearch 188 189 190def _sortingLevelWalker(list_of_generators): 191 """Utility function for iterating over all members of a list of 192 generators that prefixes each item with the index of the original 193 generator that produced it. This is useful for creating lists where 194 we want to preserve the original generator order but want to sort 195 the sub-lists. 196 197 Note that the generators must produce tuples. 198 """ 199 lastName = '' 200 nameCounter = 0 201 for gen in list_of_generators: 202 nameCounter += 1 # Each generator starts a new component name 203 for item in gen: 204 if item[0] != lastName: 205 nameCounter += 1 206 lastName = item[0] 207 yield (nameCounter,) + item 208 209 210def _levelWalker(list_of_generators): 211 """Simple utility function for iterating over all members of a list of 212 generators. 213 """ 214 for gen in list_of_generators: 215 yield from gen 216 217 218class _BlockConstruction(object): 219 """ 220 This class holds a "global" dict used when constructing 221 (hierarchical) models. 222 """ 223 data = {} 224 225 226class PseudoMap(object): 227 """ 228 This class presents a "mock" dict interface to the internal 229 _BlockData data structures. We return this object to the 230 user to preserve the historical "{ctype : {name : obj}}" 231 interface without actually regenerating that dict-of-dicts data 232 structure. 233 234 We now support {ctype : PseudoMap()} 235 """ 236 237 __slots__ = ('_block', '_ctypes', '_active', '_sorted') 238 239 def __init__(self, block, ctype, active=None, sort=False): 240 """ 241 TODO 242 """ 243 self._block = block 244 if isclass(ctype): 245 self._ctypes = (ctype,) 246 else: 247 self._ctypes = ctype 248 self._active = active 249 self._sorted = SortComponents.sort_names(sort) 250 251 def __iter__(self): 252 """ 253 TODO 254 """ 255 return self.keys() 256 257 def __getitem__(self, key): 258 """ 259 TODO 260 """ 261 if key in self._block._decl: 262 x = self._block._decl_order[self._block._decl[key]] 263 if self._ctypes is None or x[0].ctype in self._ctypes: 264 if self._active is None or x[0].active == self._active: 265 return x[0] 266 msg = "" 267 if self._active is not None: 268 msg += self._active and "active " or "inactive " 269 if self._ctypes is not None: 270 if len(self._ctypes) == 1: 271 msg += self._ctypes[0].__name__ + " " 272 else: 273 types = sorted(x.__name__ for x in self._ctypes) 274 msg += '%s or %s ' % (', '.join(types[:-1]), types[-1]) 275 raise KeyError("%scomponent '%s' not found in block %s" 276 % (msg, key, self._block.name)) 277 278 def __nonzero__(self): 279 """ 280 TODO 281 """ 282 # Shortcut: this will bail after finding the first 283 # (non-None) item. Note that we temporarily disable sorting 284 # -- otherwise, if this is a sorted PseudoMap the entire 285 # list will be walked and sorted before returning the first 286 # element. 287 sort_order = self._sorted 288 try: 289 self._sorted = False 290 for x in self.values(): 291 return True 292 return False 293 finally: 294 self._sorted = sort_order 295 296 __bool__ = __nonzero__ 297 298 def __len__(self): 299 """ 300 TODO 301 """ 302 # 303 # If _active is None, then the number of components is 304 # simply the total of the counts of the ctypes that have 305 # been added. 306 # 307 if self._active is None: 308 if self._ctypes is None: 309 return sum(x[2] for x in self._block._ctypes.values()) 310 else: 311 return sum(self._block._ctypes.get(x, (0, 0, 0))[2] 312 for x in self._block._ctypes 313 if x in self._ctypes) 314 # 315 # If _active is True or False, then we have to count by brute force. 316 # 317 ans = 0 318 for x in self.values(): 319 ans += 1 320 return ans 321 322 def __contains__(self, key): 323 """ 324 TODO 325 """ 326 # Return True is the underlying Block contains the component 327 # name. Note, if this Pseudomap soecifies a ctype or the 328 # active flag, we need to check that the underlying 329 # component matches those flags 330 if key in self._block._decl: 331 x = self._block._decl_order[self._block._decl[key]] 332 if self._ctypes is None or x[0].ctype in self._ctypes: 333 return self._active is None or x[0].active == self._active 334 return False 335 336 def _ctypewalker(self): 337 """ 338 TODO 339 """ 340 # Note: since push/pop from the end of lists is slightly more 341 # efficient, we will reverse-sort so the next ctype index is 342 # at the end of the list. 343 _decl_order = self._block._decl_order 344 _idx_list = sorted((self._block._ctypes[x][0] 345 for x in self._block._ctypes 346 if x in self._ctypes), 347 reverse=True) 348 while _idx_list: 349 _idx = _idx_list.pop() 350 while _idx is not None: 351 _obj, _next = _decl_order[_idx] 352 if _obj is not None: 353 yield _obj 354 _idx = _next 355 if _idx is not None and _idx_list and _idx > _idx_list[-1]: 356 _idx_list.append(_idx) 357 _idx_list.sort(reverse=True) 358 break 359 360 def keys(self): 361 """ 362 Generator returning the component names defined on the Block 363 """ 364 # Iterate over the PseudoMap keys (the component names) in 365 # declaration order 366 # 367 # Ironically, the values are the fundamental thing that we 368 # can (efficiently) iterate over in decl_order. iterkeys 369 # just wraps itervalues. 370 for obj in self.values(): 371 yield obj._name 372 373 def values(self): 374 """ 375 Generator returning the components defined on the Block 376 """ 377 # Iterate over the PseudoMap values (the component objects) in 378 # declaration order 379 _active = self._active 380 if self._ctypes is None: 381 # If there is no ctype, then we will just iterate over 382 # all components and return them all 383 if _active is None: 384 walker = (obj for obj, idx in self._block._decl_order 385 if obj is not None) 386 else: 387 walker = (obj for obj, idx in self._block._decl_order 388 if obj is not None and obj.active == _active) 389 else: 390 # The user specified a desired ctype; we will leverage 391 # the _ctypewalker generator to walk the underlying linked 392 # list and just return the desired objects (again, in 393 # decl order) 394 if _active is None: 395 walker = (obj for obj in self._ctypewalker()) 396 else: 397 walker = (obj for obj in self._ctypewalker() 398 if obj.active == _active) 399 # If the user wants this sorted by name, then there is 400 # nothing we can do to save memory: we must create the whole 401 # list (so we can sort it) and then iterate over the sorted 402 # temporary list 403 if self._sorted: 404 return (obj for obj in sorted(walker, key=lambda _x: _x.local_name)) 405 else: 406 return walker 407 408 def items(self): 409 """ 410 Generator returning (name, component) tuples for components 411 defined on the Block 412 """ 413 # Ironically, the values are the fundamental thing that we 414 # can (efficiently) iterate over in decl_order. iteritems 415 # just wraps itervalues. 416 for obj in self.values(): 417 yield (obj._name, obj) 418 419 @deprecated('The iterkeys method is deprecated. Use dict.keys().', 420 version='6.0') 421 def iterkeys(self): 422 """ 423 Generator returning the component names defined on the Block 424 """ 425 return self.keys() 426 427 @deprecated('The itervalues method is deprecated. Use dict.values().', 428 version='6.0') 429 def itervalues(self): 430 """ 431 Generator returning the components defined on the Block 432 """ 433 return self.values() 434 435 @deprecated('The iteritems method is deprecated. Use dict.items().', 436 version='6.0') 437 def iteritems(self): 438 """ 439 Generator returning (name, component) tuples for components 440 defined on the Block 441 """ 442 return self.items() 443 444 445class _BlockData(ActiveComponentData): 446 """ 447 This class holds the fundamental block data. 448 """ 449 _Block_reserved_words = set() 450 451 def __init__(self, component): 452 # 453 # BLOCK DATA ELEMENTS 454 # 455 # _decl_order: [ (component, id_of_next_ctype_in_decl_order), ...] 456 # _decl: { name : index_in_decl_order } 457 # _ctypes: { ctype : [ id_first_ctype, id_last_ctype, count ] } 458 # 459 # 460 # We used to define an internal structure that looked like: 461 # 462 # _component = { ctype : OrderedDict( name : obj ) } 463 # _declarations = OrderedDict( name : obj ) 464 # 465 # This structure is convenient, but the overhead of carrying 466 # around roughly 20 dictionaries for every block consumed a 467 # nontrivial amount of memory. Plus, the generation and 468 # maintenance of OrderedDicts appeared to be disturbingly slow. 469 # 470 # We now "mock up" this data structure using 2 dicts and a list: 471 # 472 # _ctypes = { ctype : [ first idx, last idx, count ] } 473 # _decl = { name : idx } 474 # _decl_order = [ (obj, next_ctype_idx) ] 475 # 476 # Some notes: As Pyomo models rarely *delete* objects, we 477 # currently never remove items from the _decl_order list. If 478 # the component is ever removed / cleared, we simply mark the 479 # object as None. If models crop up where we start seeing a 480 # significant amount of adding / removing components, then we 481 # can revisit this decision (although we will probably continue 482 # marking entries as None and just periodically rebuild the list 483 # as opposed to maintaining the list without any holes). 484 # 485 ActiveComponentData.__init__(self, component) 486 # Note: call super() here to bypass the Block __setattr__ 487 # _ctypes: { ctype -> [1st idx, last idx, count] } 488 # _decl: { name -> idx } 489 # _decl_order: list( tuples( obj, next_type_idx ) ) 490 super(_BlockData, self).__setattr__('_ctypes', {}) 491 super(_BlockData, self).__setattr__('_decl', {}) 492 super(_BlockData, self).__setattr__('_decl_order', []) 493 494 def __getstate__(self): 495 # Note: _BlockData is NOT slot-ized, so we must pickle the 496 # entire __dict__. However, we want the base class's 497 # __getstate__ to override our blanket approach here (i.e., it 498 # will handle the _component weakref), so we will call the base 499 # class's __getstate__ and allow it to overwrite the catch-all 500 # approach we use here. 501 ans = dict(self.__dict__) 502 ans.update(super(_BlockData, self).__getstate__()) 503 # Note sure why we are deleting these... 504 if '_repn' in ans: 505 del ans['_repn'] 506 return ans 507 508 # 509 # The base class __setstate__ is sufficient (assigning all the 510 # pickled attributes to the object is appropriate 511 # 512 # def __setstate__(self, state): 513 # pass 514 515 def __getattr__(self, val): 516 if val in ModelComponentFactory: 517 return _component_decorator( 518 self, ModelComponentFactory.get_class(val)) 519 # Since the base classes don't support getattr, we can just 520 # throw the "normal" AttributeError 521 raise AttributeError("'%s' object has no attribute '%s'" 522 % (self.__class__.__name__, val)) 523 524 def __setattr__(self, name, val): 525 """ 526 Set an attribute of a block data object. 527 """ 528 # 529 # In general, the most common case for this is setting a *new* 530 # attribute. After that, there is updating an existing 531 # Component value, with the least common case being resetting an 532 # existing general attribute. 533 # 534 # Case 1. Add an attribute that is not currently in the class. 535 # 536 if name not in self.__dict__: 537 if isinstance(val, Component): 538 # 539 # Pyomo components are added with the add_component method. 540 # 541 self.add_component(name, val) 542 else: 543 # 544 # Other Python objects are added with the standard __setattr__ 545 # method. 546 # 547 super(_BlockData, self).__setattr__(name, val) 548 # 549 # Case 2. The attribute exists and it is a component in the 550 # list of declarations in this block. We will use the 551 # val to update the value of that [scalar] component 552 # through its set_value() method. 553 # 554 elif name in self._decl: 555 if isinstance(val, Component): 556 # 557 # The value is a component, so we replace the component in the 558 # block. 559 # 560 if self._decl_order[self._decl[name]][0] is val: 561 return 562 logger.warning( 563 "Implicitly replacing the Component attribute " 564 "%s (type=%s) on block %s with a new Component (type=%s)." 565 "\nThis is usually indicative of a modelling error.\n" 566 "To avoid this warning, use block.del_component() and " 567 "block.add_component()." 568 % (name, type(self.component(name)), self.name, 569 type(val))) 570 self.del_component(name) 571 self.add_component(name, val) 572 else: 573 # 574 # The incoming value is not a component, so we set the 575 # value in the existing component. 576 # 577 # Because we want to log a special error only if the 578 # set_value attribute is missing, we will fetch the 579 # attribute first and then call the method outside of 580 # the try-except so as to not suppress any exceptions 581 # generated while setting the value. 582 # 583 try: 584 _set_value = self._decl_order[self._decl[name]][0].set_value 585 except AttributeError: 586 logger.error( 587 "Expected component %s (type=%s) on block %s to have a " 588 "'set_value' method, but none was found." % 589 (name, type(self.component(name)), 590 self.name)) 591 raise 592 # 593 # Call the set_value method. 594 # 595 _set_value(val) 596 # 597 # Case 3. Handle setting non-Component attributes 598 # 599 else: 600 # 601 # NB: This is important: the _BlockData is either a scalar 602 # Block (where _parent and _component are defined) or a 603 # single block within an Indexed Block (where only 604 # _component is defined). Regardless, the 605 # _BlockData.__init__() method declares these methods and 606 # sets them either to None or a weakref. Thus, we will 607 # never have a problem converting these objects from 608 # weakrefs into Blocks and back (when pickling); the 609 # attribute is already in __dict__, we will not hit the 610 # add_component / del_component branches above. It also 611 # means that any error checking we want to do when assigning 612 # these attributes should be done here. 613 # 614 # NB: isintance() can be slow and we generally avoid it in 615 # core methods. However, it is only slow when it returns 616 # False. Since the common paths on this branch should 617 # return True, this shouldn't be too inefficient. 618 # 619 if name == '_parent': 620 if val is not None and not isinstance(val(), _BlockData): 621 raise ValueError( 622 "Cannot set the '_parent' attribute of Block '%s' " 623 "to a non-Block object (with type=%s); Did you " 624 "try to create a model component named '_parent'?" 625 % (self.name, type(val))) 626 super(_BlockData, self).__setattr__(name, val) 627 elif name == '_component': 628 if val is not None and not isinstance(val(), _BlockData): 629 raise ValueError( 630 "Cannot set the '_component' attribute of Block '%s' " 631 "to a non-Block object (with type=%s); Did you " 632 "try to create a model component named '_component'?" 633 % (self.name, type(val))) 634 super(_BlockData, self).__setattr__(name, val) 635 # 636 # At this point, we should only be seeing non-component data 637 # the user is hanging on the blocks (uncommon) or the 638 # initial setup of the object data (in __init__). 639 # 640 elif isinstance(val, Component): 641 logger.warning( 642 "Reassigning the non-component attribute %s\n" 643 "on block (model).%s with a new Component\nwith type %s.\n" 644 "This is usually indicative of a modelling error.\n" 645 "To avoid this warning, explicitly delete the attribute:\n" 646 " del %s.%s" % ( 647 name, self.name, type(val), self.name, name)) 648 delattr(self, name) 649 self.add_component(name, val) 650 else: 651 super(_BlockData, self).__setattr__(name, val) 652 653 def __delattr__(self, name): 654 """ 655 Delete an attribute on this Block. 656 """ 657 # 658 # It is important that we call del_component() whenever a 659 # component is removed from a block. Defining __delattr__ 660 # catches the case when a user attempts to remove components 661 # using, e.g. "del model.myParam" 662 # 663 if name in self._decl: 664 # 665 # The attribute exists and it is a component in the 666 # list of declarations in this block. 667 # 668 self.del_component(name) 669 else: 670 # 671 # Other Python objects are removed with the standard __detattr__ 672 # method. 673 # 674 super(_BlockData, self).__delattr__(name) 675 676 def _compact_decl_storage(self): 677 idxMap = {} 678 _new_decl_order = [] 679 j = 0 680 # Squeeze out the None entries 681 for i, entry in enumerate(self._decl_order): 682 if entry[0] is not None: 683 idxMap[i] = j 684 j += 1 685 _new_decl_order.append(entry) 686 # Update the _decl map 687 self._decl = {k:idxMap[idx] for k,idx in self._decl.items()} 688 # Update the ctypes, _decl_order linked lists 689 for ctype, info in self._ctypes.items(): 690 idx = info[0] 691 entry = self._decl_order[idx] 692 while entry[0] is None: 693 idx = entry[1] 694 entry = self._decl_order[idx] 695 info[0] = last = idxMap[idx] 696 while entry[1] is not None: 697 idx = entry[1] 698 entry = self._decl_order[idx] 699 if entry[0] is not None: 700 this = idxMap[idx] 701 _new_decl_order[last] = (_new_decl_order[last][0], this) 702 last = this 703 info[1] = last 704 _new_decl_order[last] = (_new_decl_order[last][0], None) 705 self._decl_order = _new_decl_order 706 707 def set_value(self, val): 708 raise RuntimeError(textwrap.dedent( 709 """\ 710 Block components do not support assignment or set_value(). 711 Use the transfer_attributes_from() method to transfer the 712 components and public attributes from one block to another: 713 model.b[1].transfer_attributes_from(other_block) 714 """)) 715 716 def clear(self): 717 for name in self.component_map().keys(): 718 if name not in self._Block_reserved_words: 719 self.del_component(name) 720 for attr in tuple(self.__dict__): 721 if attr not in self._Block_reserved_words: 722 delattr(self, attr) 723 self._compact_decl_storage() 724 725 def transfer_attributes_from(self, src): 726 """Transfer user-defined attributes from src to this block 727 728 This transfers all components and user-defined attributes from 729 the block or dictionary `src` and places them on this Block. 730 Components are transferred in declaration order. 731 732 If a Component on `src` is also declared on this block as either 733 a Component or attribute, the local Component or attribute is 734 replaced by the incoming component. If an attribute name on 735 `src` matches a Component declared on this block, then the 736 incoming attribute is passed to the local Component's 737 `set_value()` method. Attribute names appearing in this block's 738 `_Block_reserved_words` set will not be transferred (although 739 Components will be). 740 741 Parameters 742 ---------- 743 src: _BlockData or dict 744 The Block or mapping that contains the new attributes to 745 assign to this block. 746 """ 747 if isinstance(src, _BlockData): 748 # There is a special case where assigning a parent block to 749 # this block creates a circular hierarchy 750 if src is self: 751 return 752 p_block = self.parent_block() 753 while p_block is not None: 754 if p_block is src: 755 raise ValueError( 756 "_BlockData.transfer_attributes_from(): Cannot set a " 757 "sub-block (%s) to a parent block (%s): creates a " 758 "circular hierarchy" % (self, src)) 759 p_block = p_block.parent_block() 760 # record the components and the non-component objects added 761 # to the block 762 src_comp_map = src.component_map() 763 src_raw_dict = {k:v for k,v in src.__dict__.items() 764 if k not in src_comp_map} 765 elif isinstance(src, Mapping): 766 src_comp_map = {} 767 src_raw_dict = src 768 else: 769 raise ValueError( 770 "_BlockData.transfer_attributes_from(): expected a " 771 "Block or dict; received %s" % (type(src).__name__,)) 772 773 # Use component_map for the components to preserve decl_order 774 for k,v in src_comp_map.items(): 775 if k in self._decl: 776 self.del_component(k) 777 src.del_component(k) 778 self.add_component(k,v) 779 # Because Blocks are not slotized and we allow the 780 # assignment of arbitrary data to Blocks, we will move over 781 # any other unrecognized entries in the object's __dict__: 782 for k in sorted(src_raw_dict.keys()): 783 if k not in self._Block_reserved_words or not hasattr(self, k) \ 784 or k in self._decl: 785 setattr(self, k, src_raw_dict[k]) 786 787 def _add_implicit_sets(self, val): 788 """TODO: This method has known issues (see tickets) and needs to be 789 reviewed. [JDS 9/2014]""" 790 791 _component_sets = getattr(val, '_implicit_subsets', None) 792 # 793 # FIXME: The name attribute should begin with "_", and None 794 # should replace "_unknown_" 795 # 796 if _component_sets is not None: 797 for ctr, tset in enumerate(_component_sets): 798 if tset.parent_component().parent_block() is None \ 799 and not isinstance(tset.parent_component(), GlobalSetBase): 800 self.add_component("%s_index_%d" % (val.local_name, ctr), tset) 801 if getattr(val, '_index', None) is not None \ 802 and isinstance(val._index, _SetDataBase) \ 803 and val._index.parent_component().parent_block() is None \ 804 and not isinstance(val._index.parent_component(), GlobalSetBase): 805 self.add_component("%s_index" % (val.local_name,), val._index.parent_component()) 806 if getattr(val, 'initialize', None) is not None \ 807 and isinstance(val.initialize, _SetDataBase) \ 808 and val.initialize.parent_component().parent_block() is None \ 809 and not isinstance(val.initialize.parent_component(), GlobalSetBase): 810 self.add_component("%s_index_init" % (val.local_name,), val.initialize.parent_component()) 811 if getattr(val, 'domain', None) is not None \ 812 and isinstance(val.domain, _SetDataBase) \ 813 and val.domain.parent_block() is None \ 814 and not isinstance(val.domain, GlobalSetBase): 815 self.add_component("%s_domain" % (val.local_name,), val.domain) 816 817 def _flag_vars_as_stale(self): 818 """ 819 Configure *all* variables (on active blocks) and 820 their composite _VarData objects as stale. This 821 method is used prior to loading solver 822 results. Variable that did not particpate in the 823 solution are flagged as stale. E.g., it most cases 824 fixed variables will be flagged as stale since they 825 are compiled out of expressions; however, many 826 solver plugins support including fixed variables in 827 the output problem by overriding bounds in order to 828 minimize preprocessing requirements, meaning fixed 829 variables are not necessarily always stale. 830 """ 831 for variable in self.component_objects(Var, active=True): 832 variable.flag_as_stale() 833 834 def collect_ctypes(self, 835 active=None, 836 descend_into=True): 837 """ 838 Count all component types stored on or under this 839 block. 840 841 Args: 842 active (True/None): Set to True to indicate that 843 only active components should be 844 counted. The default value of None indicates 845 that all components (including those that 846 have been deactivated) should be counted. 847 descend_into (bool): Indicates whether or not 848 component types should be counted on 849 sub-blocks. Default is True. 850 851 Returns: A set of component types. 852 """ 853 assert active in (True, None) 854 ctypes = set() 855 for block in self.block_data_objects(active=active, 856 descend_into=descend_into, 857 sort=SortComponents.unsorted): 858 if active is None: 859 ctypes.update(ctype for ctype in block._ctypes) 860 else: 861 assert active is True 862 for ctype in block._ctypes: 863 for component in block.component_data_objects( 864 ctype=ctype, 865 active=True, 866 descend_into=False, 867 sort=SortComponents.unsorted): 868 ctypes.add(ctype) 869 break # just need 1 or more 870 return ctypes 871 872 def model(self): 873 # 874 # Special case: the "Model" is always the top-level _BlockData, 875 # so if this is the top-level block, it must be the model 876 # 877 # Also note the interesting and intentional characteristic for 878 # an IndexedBlock that is not attached to anything: 879 # b = Block([1,2,3]) 880 # b.model() is None 881 # b[1].model() is b[1] 882 # b[2].model() is b[2] 883 # 884 ans = self.parent_block() 885 if ans is None: 886 return self 887 # 888 # NOTE: This loop is probably OK, since 889 # 1) most models won't be nested very deep and 890 # 2) it is better than forcing everyone to maintain references 891 # to the top-level block from both the standpoint of memory 892 # use and update time). 893 # 894 next = ans.parent_block() 895 while next is not None: 896 ans = next 897 next = next.parent_block() 898 return ans 899 900 def find_component(self, label_or_component): 901 """ 902 Return a block component given a name. 903 """ 904 return ComponentUID(label_or_component).find_component_on(self) 905 906 def add_component(self, name, val): 907 """ 908 Add a component 'name' to the block. 909 910 This method assumes that the attribute is not in the model. 911 """ 912 # 913 # Error checks 914 # 915 if not val.valid_model_component(): 916 raise RuntimeError( 917 "Cannot add '%s' as a component to a block" % str(type(val))) 918 if name in self._Block_reserved_words and hasattr(self, name): 919 raise ValueError("Attempting to declare a block component using " 920 "the name of a reserved attribute:\n\t%s" 921 % (name,)) 922 if name in self.__dict__: 923 raise RuntimeError( 924 "Cannot add component '%s' (type %s) to block '%s': a " 925 "component by that name (type %s) is already defined." 926 % (name, type(val), self.name, type(getattr(self, name)))) 927 # 928 # Skip the add_component() logic if this is a 929 # component type that is suppressed. 930 # 931 _component = self.parent_component() 932 _type = val.ctype 933 if _type in _component._suppress_ctypes: 934 return 935 # 936 # Raise an exception if the component already has a parent. 937 # 938 if (val._parent is not None) and (val._parent() is not None): 939 if val._parent() is self: 940 msg = """ 941Attempting to re-assign the component '%s' to the same 942block under a different name (%s).""" % (val.name, name) 943 else: 944 msg = """ 945Re-assigning the component '%s' from block '%s' to 946block '%s' as '%s'.""" % (val._name, val._parent().name, 947 self.name, name) 948 949 raise RuntimeError("""%s 950 951This behavior is not supported by Pyomo; components must have a 952single owning block (or model), and a component may not appear 953multiple times in a block. If you want to re-name or move this 954component, use the block del_component() and add_component() methods. 955""" % (msg.strip(),)) 956 # 957 # If the new component is a Block, then there is the chance that 958 # it is the model(), and assigning it would create a circular 959 # hierarchy. Note that we only have to check the model as the 960 # check immediately above would catch any "internal" blocks in 961 # the block hierarchy 962 # 963 if isinstance(val, Block) and val is self.model(): 964 raise ValueError( 965 "Cannot assign the top-level block as a subblock of one of " 966 "its children (%s): creates a circular hierarchy" 967 % (self,)) 968 # 969 # Set the name and parent pointer of this component. 970 # 971 val._parent = weakref.ref(self) 972 val._name = name 973 # 974 # We want to add the temporary / implicit sets first so that 975 # they get constructed before this component 976 # 977 # FIXME: This is sloppy and wasteful (most components trigger 978 # this, even when there is no need for it). We should 979 # reconsider the whole _implicit_subsets logic to defer this 980 # kind of thing to an "update_parent()" method on the 981 # components. 982 # 983 self._add_implicit_sets(val) 984 # 985 # Add the component to the underlying Component store 986 # 987 _new_idx = len(self._decl_order) 988 self._decl[name] = _new_idx 989 self._decl_order.append((val, None)) 990 # 991 # Add the component as an attribute. Note that 992 # 993 # self.__dict__[name]=val 994 # 995 # is inappropriate here. The correct way to add the attribute 996 # is to delegate the work to the next class up the MRO. 997 # 998 super(_BlockData, self).__setattr__(name, val) 999 # 1000 # Update the ctype linked lists 1001 # 1002 if _type in self._ctypes: 1003 idx_info = self._ctypes[_type] 1004 tmp = idx_info[1] 1005 self._decl_order[tmp] = (self._decl_order[tmp][0], _new_idx) 1006 idx_info[1] = _new_idx 1007 idx_info[2] += 1 1008 else: 1009 self._ctypes[_type] = [_new_idx, _new_idx, 1] 1010 # 1011 # Propagate properties to sub-blocks: 1012 # suppressed ctypes 1013 # 1014 if _type is Block: 1015 val._suppress_ctypes |= _component._suppress_ctypes 1016 # 1017 # Error, for disabled support implicit rule names 1018 # 1019 if '_rule' in val.__dict__ and val._rule is None: 1020 _found = False 1021 try: 1022 _test = val.local_name + '_rule' 1023 for i in (1, 2): 1024 frame = sys._getframe(i) 1025 _found |= _test in frame.f_locals 1026 except: 1027 pass 1028 if _found: 1029 # JDS: Do not blindly reformat this message. The 1030 # formatter inserts arbitrarily-long names(), which can 1031 # cause the resulting logged message to be very poorly 1032 # formatted due to long lines. 1033 logger.warning( 1034 """As of Pyomo 4.0, Pyomo components no longer support implicit rules. 1035You defined a component (%s) that appears 1036to rely on an implicit rule (%s). 1037Components must now specify their rules explicitly using 'rule=' keywords.""" % 1038 (val.name, _test)) 1039 # 1040 # Don't reconstruct if this component has already been constructed. 1041 # This allows a user to move a component from one block to 1042 # another. 1043 # 1044 if val._constructed is True: 1045 return 1046 # 1047 # If the block is Concrete, construct the component 1048 # Note: we are explicitly using getattr because (Scalar) 1049 # classes that derive from Block may want to declare components 1050 # within their __init__() [notably, pyomo.gdp's Disjunct). 1051 # Those components are added *before* the _constructed flag is 1052 # added to the class by Block.__init__() 1053 # 1054 if getattr(_component, '_constructed', False): 1055 # NB: we don't have to construct the temporary / implicit 1056 # sets here: if necessary, that happens when 1057 # _add_implicit_sets() calls add_component(). 1058 if _BlockConstruction.data: 1059 data = _BlockConstruction.data.get(id(self), None) 1060 if data is not None: 1061 data = data.get(name, None) 1062 else: 1063 data = None 1064 generate_debug_messages = is_debug_set(logger) 1065 if generate_debug_messages: 1066 # This is tricky: If we are in the middle of 1067 # constructing an indexed block, the block component 1068 # already has _constructed=True. Now, if the 1069 # _BlockData.__init__() defines any local variables 1070 # (like pyomo.gdp.Disjunct's indicator_var), name(True) 1071 # will fail: this block data exists and has a parent(), 1072 # but it has not yet been added to the parent's _data 1073 # (so the idx lookup will fail in name). 1074 if self.parent_block() is None: 1075 _blockName = "[Model]" 1076 else: 1077 try: 1078 _blockName = "Block '%s'" % self.name 1079 except: 1080 _blockName = "Block '%s[...]'" \ 1081 % self.parent_component().name 1082 logger.debug("Constructing %s '%s' on %s from data=%s", 1083 val.__class__.__name__, name, 1084 _blockName, str(data)) 1085 try: 1086 val.construct(data) 1087 except: 1088 err = sys.exc_info()[1] 1089 logger.error( 1090 "Constructing component '%s' from data=%s failed:\n%s: %s", 1091 str(val.name), str(data).strip(), 1092 type(err).__name__, err) 1093 raise 1094 if generate_debug_messages: 1095 if _blockName[-1] == "'": 1096 _blockName = _blockName[:-1] + '.' + name + "'" 1097 else: 1098 _blockName = "'" + _blockName + '.' + name + "'" 1099 _out = StringIO() 1100 val.pprint(ostream=_out) 1101 logger.debug("Constructed component '%s':\n%s" 1102 % (_blockName, _out.getvalue())) 1103 1104 def del_component(self, name_or_object): 1105 """ 1106 Delete a component from this block. 1107 """ 1108 obj = self.component(name_or_object) 1109 # FIXME: Is this necessary? Should this raise an exception? 1110 if obj is None: 1111 return 1112 1113 # FIXME: Is this necessary? Should this raise an exception? 1114 # if name not in self._decl: 1115 # return 1116 1117 name = obj.local_name 1118 1119 # Replace the component in the master list with a None placeholder 1120 idx = self._decl[name] 1121 del self._decl[name] 1122 self._decl_order[idx] = (None, self._decl_order[idx][1]) 1123 1124 # Update the ctype linked lists 1125 ctype_info = self._ctypes[obj.ctype] 1126 ctype_info[2] -= 1 1127 if ctype_info[2] == 0: 1128 del self._ctypes[obj.ctype] 1129 1130 # Clear the _parent attribute 1131 obj._parent = None 1132 1133 # Now that this component is not in the _decl map, we can call 1134 # delattr as usual. 1135 # 1136 #del self.__dict__[name] 1137 # 1138 # Note: 'del self.__dict__[name]' is inappropriate here. The 1139 # correct way to add the attribute is to delegate the work to 1140 # the next class up the MRO. 1141 super(_BlockData, self).__delattr__(name) 1142 1143 def reclassify_component_type(self, name_or_object, new_ctype, 1144 preserve_declaration_order=True): 1145 """ 1146 TODO 1147 """ 1148 obj = self.component(name_or_object) 1149 # FIXME: Is this necessary? Should this raise an exception? 1150 if obj is None: 1151 return 1152 1153 if obj.ctype is new_ctype: 1154 return 1155 1156 name = obj.local_name 1157 if not preserve_declaration_order: 1158 # if we don't have to preserve the decl order, then the 1159 # easiest (and fastest) thing to do is just delete it and 1160 # re-add it. 1161 self.del_component(name) 1162 obj._ctype = new_ctype 1163 self.add_component(name, obj) 1164 return 1165 1166 idx = self._decl[name] 1167 1168 # Update the ctype linked lists 1169 ctype_info = self._ctypes[obj.ctype] 1170 ctype_info[2] -= 1 1171 if ctype_info[2] == 0: 1172 del self._ctypes[obj.ctype] 1173 elif ctype_info[0] == idx: 1174 ctype_info[0] = self._decl_order[idx][1] 1175 else: 1176 prev = None 1177 tmp = self._ctypes[obj.ctype][0] 1178 while tmp < idx: 1179 prev = tmp 1180 tmp = self._decl_order[tmp][1] 1181 1182 self._decl_order[prev] = (self._decl_order[prev][0], 1183 self._decl_order[idx][1]) 1184 if ctype_info[1] == idx: 1185 ctype_info[1] = prev 1186 1187 obj._ctype = new_ctype 1188 1189 # Insert into the new ctype list 1190 if new_ctype not in self._ctypes: 1191 self._ctypes[new_ctype] = [idx, idx, 1] 1192 self._decl_order[idx] = (obj, None) 1193 elif idx < self._ctypes[new_ctype][0]: 1194 self._decl_order[idx] = (obj, self._ctypes[new_ctype][0]) 1195 self._ctypes[new_ctype][0] = idx 1196 self._ctypes[new_ctype][2] += 1 1197 elif idx > self._ctypes[new_ctype][1]: 1198 prev = self._ctypes[new_ctype][1] 1199 self._decl_order[prev] = (self._decl_order[prev][0], idx) 1200 self._decl_order[idx] = (obj, None) 1201 self._ctypes[new_ctype][1] = idx 1202 self._ctypes[new_ctype][2] += 1 1203 else: 1204 self._ctypes[new_ctype][2] += 1 1205 prev = None 1206 tmp = self._ctypes[new_ctype][0] 1207 while tmp < idx: 1208 # this test should be unnecessary: and tmp is not None: 1209 prev = tmp 1210 tmp = self._decl_order[tmp][1] 1211 self._decl_order[prev] = (self._decl_order[prev][0], idx) 1212 self._decl_order[idx] = (obj, tmp) 1213 1214 def clone(self): 1215 """ 1216 TODO 1217 """ 1218 # FYI: we used to remove all _parent() weakrefs before 1219 # deepcopying and then restore them on the original and cloned 1220 # model. It turns out that this was completely unnecessary and 1221 # wasteful. 1222 1223 # 1224 # Note: Setting __block_scope__ determines which components are 1225 # deepcopied (anything beneath this block) and which are simply 1226 # preserved as references (anything outside this block 1227 # hierarchy). We must always go through this effort to prevent 1228 # copying certain "reserved" components (like Any, 1229 # NonNegativeReals, etc) that are not "owned" by any blocks and 1230 # should be preserved as singletons. 1231 # 1232 save_parent, self._parent = self._parent, None 1233 try: 1234 new_block = copy.deepcopy( 1235 self, { 1236 '__block_scope__': {id(self): True, id(None): False}, 1237 '__paranoid__': False, 1238 }) 1239 except: 1240 new_block = copy.deepcopy( 1241 self, { 1242 '__block_scope__': {id(self): True, id(None): False}, 1243 '__paranoid__': True, 1244 }) 1245 finally: 1246 self._parent = save_parent 1247 1248 return new_block 1249 1250 def contains_component(self, ctype): 1251 """ 1252 Return True if the component type is in _ctypes and ... TODO. 1253 """ 1254 return ctype in self._ctypes and self._ctypes[ctype][2] 1255 1256 def component(self, name_or_object): 1257 """ 1258 Return a child component of this block. 1259 1260 If passed a string, this will return the child component 1261 registered by that name. If passed a component, this will 1262 return that component IFF the component is a child of this 1263 block. Returns None on lookup failure. 1264 """ 1265 if isinstance(name_or_object, str): 1266 if name_or_object in self._decl: 1267 return self._decl_order[self._decl[name_or_object]][0] 1268 else: 1269 try: 1270 obj = name_or_object.parent_component() 1271 if obj.parent_block() is self: 1272 return obj 1273 except AttributeError: 1274 pass 1275 return None 1276 1277 def component_map(self, ctype=None, active=None, sort=False): 1278 """Returns a PseudoMap of the components in this block. 1279 1280 Parameters 1281 ---------- 1282 ctype: None or type or iterable 1283 Specifies the component types (`ctypes`) to include in the 1284 resulting PseudoMap 1285 1286 ============= =================== 1287 None All components 1288 type A single component type 1289 iterable All component types in the iterable 1290 ============= =================== 1291 1292 active: None or bool 1293 Filter components by the active flag 1294 1295 ===== =============================== 1296 None Return all components 1297 True Return only active components 1298 False Return only inactive components 1299 ===== =============================== 1300 1301 sort: bool 1302 Iterate over the components in a sorted otder 1303 1304 ===== ================================================ 1305 True Iterate using Block.alphabetizeComponentAndIndex 1306 False Iterate using Block.declarationOrder 1307 ===== ================================================ 1308 1309 """ 1310 return PseudoMap(self, ctype, active, sort) 1311 1312 def _component_typemap(self, ctype=None, active=None, sort=False): 1313 """ 1314 Return information about the block components. 1315 1316 If ctype is None, return a dictionary that maps 1317 {component type -> {name -> component instance}} 1318 Otherwise, return a dictionary that maps 1319 {name -> component instance} 1320 for the specified component type. 1321 1322 Note: The actual {name->instance} object is a PseudoMap that 1323 implements a lightweight interface to the underlying 1324 BlockComponents data structures. 1325 """ 1326 if ctype is None: 1327 ans = {} 1328 for x in self._ctypes: 1329 ans[x] = PseudoMap(self, x, active, sort) 1330 return ans 1331 else: 1332 return PseudoMap(self, ctype, active, sort) 1333 1334 def _component_data_iter(self, ctype=None, active=None, sort=False): 1335 """ 1336 Generator that returns a 3-tuple of (component name, index value, 1337 and _ComponentData) for every component data in the block. 1338 """ 1339 _sort_indices = SortComponents.sort_indices(sort) 1340 _subcomp = PseudoMap(self, ctype, active, sort) 1341 for name, comp in _subcomp.items(): 1342 # NOTE: Suffix has a dict interface (something other derived 1343 # non-indexed Components may do as well), so we don't want 1344 # to test the existence of iteritems as a check for 1345 # component datas. We will rely on is_indexed() to catch 1346 # all the indexed components. Then we will do special 1347 # processing for the scalar components to catch the case 1348 # where there are "sparse scalar components" 1349 if comp.is_indexed(): 1350 _items = comp.items() 1351 elif hasattr(comp, '_data'): 1352 # This may be an empty Scalar component (e.g., from 1353 # Constraint.Skip on a scalar Constraint) 1354 assert len(comp._data) <= 1 1355 _items = comp._data.items() 1356 else: 1357 _items = ((None, comp),) 1358 1359 if _sort_indices: 1360 _items = sorted_robust(_items, key=itemgetter(0)) 1361 if active is None or not isinstance(comp, ActiveIndexedComponent): 1362 for idx, compData in _items: 1363 yield (name, idx), compData 1364 else: 1365 for idx, compData in _items: 1366 if compData.active == active: 1367 yield (name, idx), compData 1368 1369 @deprecated("The all_components method is deprecated. " 1370 "Use the Block.component_objects() method.", 1371 version="4.1.10486") 1372 def all_components(self, *args, **kwargs): 1373 return self.component_objects(*args, **kwargs) 1374 1375 @deprecated("The active_components method is deprecated. " 1376 "Use the Block.component_objects() method.", 1377 version="4.1.10486") 1378 def active_components(self, *args, **kwargs): 1379 kwargs['active'] = True 1380 return self.component_objects(*args, **kwargs) 1381 1382 @deprecated("The all_component_data method is deprecated. " 1383 "Use the Block.component_data_objects() method.", 1384 version="4.1.10486") 1385 def all_component_data(self, *args, **kwargs): 1386 return self.component_data_objects(*args, **kwargs) 1387 1388 @deprecated("The active_component_data method is deprecated. " 1389 "Use the Block.component_data_objects() method.", 1390 version="4.1.10486") 1391 def active_component_data(self, *args, **kwargs): 1392 kwargs['active'] = True 1393 return self.component_data_objects(*args, **kwargs) 1394 1395 def component_objects(self, ctype=None, active=None, sort=False, 1396 descend_into=True, descent_order=None): 1397 """ 1398 Return a generator that iterates through the 1399 component objects in a block. By default, the 1400 generator recursively descends into sub-blocks. 1401 """ 1402 if not descend_into: 1403 yield from self.component_map(ctype, active, sort).values() 1404 return 1405 for _block in self.block_data_objects(active, sort, descend_into, descent_order): 1406 yield from _block.component_map(ctype, active, sort).values() 1407 1408 def component_data_objects(self, 1409 ctype=None, 1410 active=None, 1411 sort=False, 1412 descend_into=True, 1413 descent_order=None): 1414 """ 1415 Return a generator that iterates through the 1416 component data objects for all components in a 1417 block. By default, this generator recursively 1418 descends into sub-blocks. 1419 """ 1420 if descend_into: 1421 block_generator = self.block_data_objects( 1422 active=active, 1423 sort=sort, 1424 descend_into=descend_into, 1425 descent_order=descent_order) 1426 else: 1427 block_generator = (self,) 1428 1429 for _block in block_generator: 1430 for x in _block._component_data_iter(ctype=ctype, 1431 active=active, 1432 sort=sort): 1433 yield x[1] 1434 1435 def component_data_iterindex(self, 1436 ctype=None, 1437 active=None, 1438 sort=False, 1439 descend_into=True, 1440 descent_order=None): 1441 """ 1442 Return a generator that returns a tuple for each 1443 component data object in a block. By default, this 1444 generator recursively descends into sub-blocks. The 1445 tuple is 1446 1447 ((component name, index value), _ComponentData) 1448 1449 """ 1450 if descend_into: 1451 block_generator = self.block_data_objects( 1452 active=active, 1453 sort=sort, 1454 descend_into=descend_into, 1455 descent_order=descent_order) 1456 else: 1457 block_generator = (self,) 1458 1459 for _block in block_generator: 1460 yield from _block._component_data_iter(ctype=ctype, 1461 active=active, 1462 sort=sort) 1463 1464 @deprecated("The all_blocks method is deprecated. " 1465 "Use the Block.block_data_objects() method.", 1466 version="4.1.10486") 1467 def all_blocks(self, *args, **kwargs): 1468 return self.block_data_objects(*args, **kwargs) 1469 1470 @deprecated("The active_blocks method is deprecated. " 1471 "Use the Block.block_data_objects() method.", 1472 version="4.1.10486") 1473 def active_blocks(self, *args, **kwargs): 1474 kwargs['active'] = True 1475 return self.block_data_objects(*args, **kwargs) 1476 1477 def block_data_objects(self, 1478 active=None, 1479 sort=False, 1480 descend_into=True, 1481 descent_order=None): 1482 1483 """ 1484 This method returns a generator that iterates 1485 through the current block and recursively all 1486 sub-blocks. This is semantically equivalent to 1487 1488 component_data_objects(Block, ...) 1489 1490 """ 1491 if descend_into is False: 1492 if active is not None and self.active != active: 1493 # Return an iterator over an empty tuple 1494 return ().__iter__() 1495 else: 1496 return (self,).__iter__() 1497 # 1498 # Rely on the _tree_iterator: 1499 # 1500 if descend_into is True: 1501 descend_into = (Block,) 1502 elif isclass(descend_into): 1503 descend_into = (descend_into,) 1504 return self._tree_iterator(ctype=descend_into, 1505 active=active, 1506 sort=sort, 1507 traversal=descent_order) 1508 1509 def _tree_iterator(self, 1510 ctype=None, 1511 active=None, 1512 sort=None, 1513 traversal=None): 1514 1515 # TODO: merge into block_data_objects 1516 if ctype is None: 1517 ctype = (Block,) 1518 elif isclass(ctype): 1519 ctype = (ctype,) 1520 1521 # A little weird, but since we "normally" return a generator, we 1522 # will return a generator for an empty list instead of just 1523 # returning None or an empty list here (so that consumers can 1524 # count on us always returning a generator) 1525 if active is not None and self.active != active: 1526 return ().__iter__() 1527 1528 # ALWAYS return the "self" Block, even if it does not match 1529 # ctype. This is because we map this ctype to the 1530 # "descend_into" argument in public calling functions: callers 1531 # expect that the called thing will be iterated over. 1532 # 1533 # if self.parent_component().ctype not in ctype: 1534 # return ().__iter__() 1535 1536 if traversal is None or \ 1537 traversal == TraversalStrategy.PrefixDepthFirstSearch: 1538 return self._prefix_dfs_iterator(ctype, active, sort) 1539 elif traversal == TraversalStrategy.BreadthFirstSearch: 1540 return self._bfs_iterator(ctype, active, sort) 1541 elif traversal == TraversalStrategy.PostfixDepthFirstSearch: 1542 return self._postfix_dfs_iterator(ctype, active, sort) 1543 else: 1544 raise RuntimeError("unrecognized traversal strategy: %s" 1545 % (traversal, )) 1546 1547 def _prefix_dfs_iterator(self, ctype, active, sort): 1548 """Helper function implementing a non-recursive prefix order 1549 depth-first search. That is, the parent is returned before its 1550 children. 1551 1552 Note: this method assumes it is called ONLY by the _tree_iterator 1553 method, which centralizes certain error checking and 1554 preliminaries. 1555 """ 1556 PM = PseudoMap(self, ctype, active, sort) 1557 _stack = [(self,).__iter__(), ] 1558 while _stack: 1559 try: 1560 PM._block = _block = next(_stack[-1]) 1561 yield _block 1562 if not PM: 1563 continue 1564 _stack.append(_block.component_data_objects(ctype=ctype, 1565 active=active, 1566 sort=sort, 1567 descend_into=False)) 1568 except StopIteration: 1569 _stack.pop() 1570 1571 def _postfix_dfs_iterator(self, ctype, active, sort): 1572 """ 1573 Helper function implementing a non-recursive postfix 1574 order depth-first search. That is, the parent is 1575 returned after its children. 1576 1577 Note: this method assumes it is called ONLY by the 1578 _tree_iterator method, which centralizes certain 1579 error checking and preliminaries. 1580 """ 1581 _stack = [(self, self.component_data_iterindex(ctype, active, sort, False))] 1582 while _stack: 1583 try: 1584 _sub = next(_stack[-1][1])[-1] 1585 _stack.append((_sub, 1586 _sub.component_data_iterindex(ctype, active, sort, False) 1587 )) 1588 except StopIteration: 1589 yield _stack.pop()[0] 1590 1591 def _bfs_iterator(self, ctype, active, sort): 1592 """Helper function implementing a non-recursive breadth-first search. 1593 That is, all children at one level in the tree are returned 1594 before any of the children at the next level. 1595 1596 Note: this method assumes it is called ONLY by the _tree_iterator 1597 method, which centralizes certain error checking and 1598 preliminaries. 1599 1600 """ 1601 if SortComponents.sort_indices(sort): 1602 if SortComponents.sort_names(sort): 1603 sorter = itemgetter(1, 2) 1604 else: 1605 sorter = itemgetter(0, 2) 1606 elif SortComponents.sort_names(sort): 1607 sorter = itemgetter(1) 1608 else: 1609 sorter = None 1610 1611 _levelQueue = {0: (((None, None, self,),),)} 1612 while _levelQueue: 1613 _level = min(_levelQueue) 1614 _queue = _levelQueue.pop(_level) 1615 if not _queue: 1616 break 1617 if sorter is None: 1618 _queue = _levelWalker(_queue) 1619 else: 1620 _queue = sorted(_sortingLevelWalker(_queue), key=sorter) 1621 1622 _level += 1 1623 _levelQueue[_level] = [] 1624 # JDS: rework the _levelQueue logic so we don't need to 1625 # merge the key/value returned by the new 1626 # component_data_iterindex() method. 1627 for _items in _queue: 1628 yield _items[-1] # _block 1629 _levelQueue[_level].append( 1630 tmp[0] + (tmp[1],) for tmp in 1631 _items[-1].component_data_iterindex(ctype=ctype, 1632 active=active, 1633 sort=sort, 1634 descend_into=False)) 1635 1636 def fix_all_vars(self): 1637 # TODO: Simplify based on recursive logic 1638 for var in self.component_map(Var).values(): 1639 var.fix() 1640 for block in self.component_map(Block).values(): 1641 block.fix_all_vars() 1642 1643 def unfix_all_vars(self): 1644 # TODO: Simplify based on recursive logic 1645 for var in self.component_map(Var).values(): 1646 var.unfix() 1647 for block in self.component_map(Block).values(): 1648 block.unfix_all_vars() 1649 1650 def is_constructed(self): 1651 """ 1652 A boolean indicating whether or not all *active* components of the 1653 input model have been properly constructed. 1654 """ 1655 if not self.parent_component()._constructed: 1656 return False 1657 for x in self._decl_order: 1658 if x[0] is not None and x[0].active and not x[0].is_constructed(): 1659 return False 1660 return True 1661 1662 def _pprint_blockdata_components(self, ostream): 1663 # 1664 # We hard-code the order of the core Pyomo modeling 1665 # components, to ensure that the output follows the logical order 1666 # that expected by a user. 1667 # 1668 import pyomo.core.base.component_order 1669 items = list(pyomo.core.base.component_order.items) 1670 items_set = set(items) 1671 items_set.add(Block) 1672 # 1673 # Collect other model components that are registered 1674 # with the IModelComponent extension point. These are appended 1675 # to the end of the list of the list. 1676 # 1677 dynamic_items = set() 1678 for item in self._ctypes: 1679 if not item in items_set: 1680 dynamic_items.add(item) 1681 # extra items get added alphabetically (so output is consistent) 1682 items.append(Block) 1683 items.extend(sorted(dynamic_items, key=lambda x: x.__name__)) 1684 1685 indented_ostream = StreamIndenter(ostream, self._PPRINT_INDENT) 1686 for item in items: 1687 keys = sorted(self.component_map(item)) 1688 if not keys: 1689 continue 1690 # 1691 # NOTE: these conditional checks should not be hard-coded. 1692 # 1693 ostream.write("%d %s Declarations\n" 1694 % (len(keys), item.__name__)) 1695 for key in keys: 1696 self.component(key).pprint(ostream=indented_ostream) 1697 ostream.write("\n") 1698 # 1699 # Model Order 1700 # 1701 decl_order_keys = list(self.component_map().keys()) 1702 ostream.write("%d Declarations: %s\n" 1703 % (len(decl_order_keys), 1704 ' '.join(str(x) for x in decl_order_keys))) 1705 1706 def display(self, filename=None, ostream=None, prefix=""): 1707 """ 1708 Print the Pyomo model in a verbose format. 1709 """ 1710 if filename is not None: 1711 OUTPUT = open(filename, "w") 1712 self.display(ostream=OUTPUT, prefix=prefix) 1713 OUTPUT.close() 1714 return 1715 if ostream is None: 1716 ostream = sys.stdout 1717 if self.parent_block() is not None: 1718 ostream.write(prefix + "Block " + self.name + '\n') 1719 else: 1720 ostream.write(prefix + "Model " + self.name + '\n') 1721 # 1722 # FIXME: We should change the display order (to Obj, Var, Con, 1723 # Block) and change the printer to only display sections with 1724 # active components. That will fix the need for the special 1725 # case for blocks below. I am not implementing this now as it 1726 # would break tests just before a release. [JDS 1/7/15] 1727 import pyomo.core.base.component_order 1728 for item in pyomo.core.base.component_order.display_items: 1729 # 1730 ostream.write(prefix + "\n") 1731 ostream.write(prefix + " %s:\n" % pyomo.core.base.component_order.display_name[item]) 1732 ACTIVE = self.component_map(item, active=True) 1733 if not ACTIVE: 1734 ostream.write(prefix + " None\n") 1735 else: 1736 for obj in ACTIVE.values(): 1737 obj.display(prefix=prefix + " ", ostream=ostream) 1738 1739 item = Block 1740 ACTIVE = self.component_map(item, active=True) 1741 if ACTIVE: 1742 ostream.write(prefix + "\n") 1743 ostream.write( 1744 prefix + " %s:\n" % 1745 pyomo.core.base.component_order.display_name[item]) 1746 for obj in ACTIVE.values(): 1747 obj.display(prefix=prefix + " ", ostream=ostream) 1748 1749 # 1750 # The following methods are needed to support passing blocks as 1751 # models to a solver. 1752 # 1753 1754 def valid_problem_types(self): 1755 """This method allows the pyomo.opt convert function to work with a 1756 Model object.""" 1757 return [ProblemFormat.pyomo] 1758 1759 def write(self, 1760 filename=None, 1761 format=None, 1762 solver_capability=None, 1763 io_options={}): 1764 """ 1765 Write the model to a file, with a given format. 1766 """ 1767 # 1768 # Guess the format if none is specified 1769 # 1770 if (filename is None) and (format is None): 1771 # Preserving backwards compatibility here. 1772 # The function used to be defined with format='lp' by 1773 # default, but this led to confusing behavior when a 1774 # user did something like 'model.write("f.nl")' and 1775 # expected guess_format to create an NL file. 1776 format = ProblemFormat.cpxlp 1777 if filename is not None: 1778 try: 1779 _format = guess_format(filename) 1780 except AttributeError: 1781 # End up here if an ostream is passed to the filename argument 1782 _format = None 1783 if format is None: 1784 if _format is None: 1785 raise ValueError( 1786 "Could not infer file format from file name '%s'.\n" 1787 "Either provide a name with a recognized extension " 1788 "or specify the format using the 'format' argument." 1789 % filename) 1790 else: 1791 format = _format 1792 elif format != _format and _format is not None: 1793 logger.warning( 1794 "Filename '%s' likely does not match specified " 1795 "file format (%s)" % (filename, format)) 1796 problem_writer = WriterFactory(format) 1797 if problem_writer is None: 1798 raise ValueError( 1799 "Cannot write model in format '%s': no model " 1800 "writer registered for that format" 1801 % str(format)) 1802 1803 if solver_capability is None: 1804 def solver_capability(x): return True 1805 (filename, smap) = problem_writer(self, 1806 filename, 1807 solver_capability, 1808 io_options) 1809 smap_id = id(smap) 1810 if not hasattr(self, 'solutions'): 1811 # This is a bit of a hack. The write() method was moved 1812 # here from PyomoModel to support the solution of arbitrary 1813 # blocks in a hierarchical model. However, we cannot import 1814 # PyomoModel at the beginning of the file due to a circular 1815 # import. When we rearchitect the solution writers/solver 1816 # API, we should revisit this and remove the circular 1817 # dependency (we only need it here because we store the 1818 # SymbolMap returned by the writer in the solutions). 1819 from pyomo.core.base.PyomoModel import ModelSolutions 1820 self.solutions = ModelSolutions(self) 1821 self.solutions.add_symbol_map(smap) 1822 1823 if is_debug_set(logger): 1824 logger.debug( 1825 "Writing model '%s' to file '%s' with format %s", 1826 self.name, 1827 str(filename), 1828 str(format)) 1829 return filename, smap_id 1830 1831 1832@ModelComponentFactory.register("A component that contains one or more model components.") 1833class Block(ActiveIndexedComponent): 1834 """ 1835 Blocks are indexed components that contain other components 1836 (including blocks). Blocks have a global attribute that defines 1837 whether construction is deferred. This applies to all components 1838 that they contain except blocks. Blocks contained by other 1839 blocks use their local attribute to determine whether construction 1840 is deferred. 1841 """ 1842 1843 _ComponentDataClass = _BlockData 1844 1845 def __new__(cls, *args, **kwds): 1846 if cls != Block: 1847 return super(Block, cls).__new__(cls) 1848 if not args or (args[0] is UnindexedComponent_set and len(args) == 1): 1849 return ScalarBlock.__new__(ScalarBlock) 1850 else: 1851 return IndexedBlock.__new__(IndexedBlock) 1852 1853 def __init__(self, *args, **kwargs): 1854 """Constructor""" 1855 self._suppress_ctypes = set() 1856 _rule = kwargs.pop('rule', None) 1857 _options = kwargs.pop('options', None) 1858 # As concrete applies to the Block at declaration time, we will 1859 # not use an initializer. 1860 _concrete = kwargs.pop('concrete', False) 1861 # As dense applies to the whole container, we will not use an 1862 # initializer 1863 self._dense = kwargs.pop('dense', True) 1864 kwargs.setdefault('ctype', Block) 1865 ActiveIndexedComponent.__init__(self, *args, **kwargs) 1866 if _options is not None: 1867 deprecation_warning( 1868 "The Block 'options=' keyword is deprecated. " 1869 "Equivalent functionality can be obtained by wrapping " 1870 "the rule function to add the options dictionary to " 1871 "the function arguments", version='5.7.2') 1872 if self.is_indexed(): 1873 def rule_wrapper(model, *_idx): 1874 return _rule(model, *_idx, **_options) 1875 else: 1876 def rule_wrapper(model): 1877 return _rule(model, **_options) 1878 self._rule = Initializer(rule_wrapper) 1879 else: 1880 self._rule = Initializer(_rule) 1881 if _concrete: 1882 # Call self.construct() as opposed to just setting the _constructed 1883 # flag so that the base class construction procedure fires (this 1884 # picks up any construction rule that the user may provide) 1885 self.construct() 1886 1887 def _getitem_when_not_present(self, idx): 1888 _block = self._setitem_when_not_present(idx) 1889 if self._rule is None: 1890 return _block 1891 1892 if _BlockConstruction.data: 1893 data = _BlockConstruction.data.get(id(self), None) 1894 if data is not None: 1895 data = data.get(idx, None) 1896 if data is not None: 1897 # Note that for scalar Blocks, this will override the 1898 # entry for _BlockConstruction.data[id(self)], as _block 1899 # is self. 1900 _BlockConstruction.data[id(_block)] = data 1901 else: 1902 data = None 1903 1904 try: 1905 obj = self._rule(_block, idx) 1906 # If the user returns a block, transfer over everything 1907 # they defined into the empty one we created. We do 1908 # this inside the try block so that any abstract 1909 # components declared by the rule have the opportunity 1910 # to be initialized with data from 1911 # _BlockConstruction.data as they are transferred over. 1912 if obj is not _block and isinstance(obj, _BlockData): 1913 _block.transfer_attributes_from(obj) 1914 finally: 1915 if data is not None and _block is not self: 1916 del _BlockConstruction.data[id(_block)] 1917 1918 # TBD: Should we allow skipping Blocks??? 1919 # if obj is Block.Skip and idx is not None: 1920 # del self._data[idx] 1921 return _block 1922 1923 def construct(self, data=None): 1924 """ 1925 Initialize the block 1926 """ 1927 if is_debug_set(logger): 1928 logger.debug("Constructing %s '%s', from data=%s", 1929 self.__class__.__name__, self.name, str(data)) 1930 if self._constructed: 1931 return 1932 timer = ConstructionTimer(self) 1933 self._constructed = True 1934 1935 # Constructing blocks is tricky. Scalar blocks are already 1936 # partially constructed (they have _data[None] == self) in order 1937 # to support Abstract blocks. The block may therefore already 1938 # have components declared on it. In order to preserve 1939 # decl_order, we must construct those components *first* before 1940 # firing any rule. Indexed blocks should be empty, so we only 1941 # need to fire the rule in order. 1942 # 1943 # Since the rule does not pass any "data" on, we build a scalar 1944 # "stack" of pointers to block data (_BlockConstruction.data) 1945 # that the individual blocks' add_component() can refer back to 1946 # to handle component construction. 1947 if data is not None: 1948 _BlockConstruction.data[id(self)] = data 1949 try: 1950 if self.is_indexed(): 1951 # We can only populate Blocks with finite indexing sets 1952 if self.index_set().isfinite() and ( 1953 self._dense or self._rule is not None): 1954 for _idx in self.index_set(): 1955 # Trigger population & call the rule 1956 self._getitem_when_not_present(_idx) 1957 else: 1958 # We must check that any pre-existing components are 1959 # constructed. This catches the case where someone is 1960 # building a Concrete model by building (potentially 1961 # pseudo-abstract) sub-blocks and then adding them to a 1962 # Concrete model block. 1963 _idx = next(iter(UnindexedComponent_set)) 1964 _predefined_components = self.component_map() 1965 if _predefined_components: 1966 if _idx not in self._data: 1967 # Derived block classes may not follow the scalar 1968 # Block convention of initializing _data to point to 1969 # itself (i.e., they are not set up to support 1970 # Abstract models) 1971 self._data[_idx] = self 1972 if data is not None: 1973 data = data.get(_idx, None) 1974 if data is None: 1975 data = {} 1976 for name, obj in _predefined_components.items(): 1977 if not obj._constructed: 1978 obj.construct(data.get(name, None)) 1979 # Trigger the (normal) initialization of the block 1980 self._getitem_when_not_present(_idx) 1981 finally: 1982 # We must allow that id(self) may no longer be in 1983 # _BlockConstruction.data, as _getitem_when_not_present will 1984 # have already removed the entry for scalar blocks (as the 1985 # BlockData and the Block component are the same object) 1986 if data is not None: 1987 _BlockConstruction.data.pop(id(self), None) 1988 timer.report() 1989 1990 def _pprint_callback(self, ostream, idx, data): 1991 if not self.is_indexed(): 1992 data._pprint_blockdata_components(ostream) 1993 else: 1994 ostream.write("%s : Active=%s\n" % (data.name, data.active)) 1995 ostream = StreamIndenter(ostream, self._PPRINT_INDENT) 1996 data._pprint_blockdata_components(ostream) 1997 1998 def _pprint(self): 1999 _attrs = [ 2000 ("Size", len(self)), 2001 ("Index", self._index if self.is_indexed() else None), 2002 ('Active', self.active), 2003 ] 2004 # HACK: suppress the top-level block header (for historical reasons) 2005 if self.parent_block() is None and not self.is_indexed(): 2006 return None, self._data.items(), None, self._pprint_callback 2007 else: 2008 return _attrs, self._data.items(), None, self._pprint_callback 2009 2010 def display(self, filename=None, ostream=None, prefix=""): 2011 """ 2012 Display values in the block 2013 """ 2014 if filename is not None: 2015 OUTPUT = open(filename, "w") 2016 self.display(ostream=OUTPUT, prefix=prefix) 2017 OUTPUT.close() 2018 return 2019 if ostream is None: 2020 ostream = sys.stdout 2021 2022 for key in sorted(self): 2023 _BlockData.display(self[key], filename, ostream, prefix) 2024 2025 2026class ScalarBlock(_BlockData, Block): 2027 2028 def __init__(self, *args, **kwds): 2029 _BlockData.__init__(self, component=self) 2030 Block.__init__(self, *args, **kwds) 2031 # Initialize the data dict so that (abstract) attribute 2032 # assignment will work. Note that we do not trigger 2033 # get/setitem_when_not_present so that we do not (implicitly) 2034 # trigger the Block rule 2035 self._data[None] = self 2036 2037 # We want scalar Blocks to pick up the Block display method 2038 display = Block.display 2039 2040 2041class SimpleBlock(metaclass=RenamedClass): 2042 __renamed__new_class__ = ScalarBlock 2043 __renamed__version__ = '6.0' 2044 2045 2046class IndexedBlock(Block): 2047 2048 def __init__(self, *args, **kwds): 2049 Block.__init__(self, *args, **kwds) 2050 2051 2052# 2053# Deprecated functions. 2054# 2055@deprecated("generate_cuid_names() is deprecated. " 2056 "Use the ComponentUID.generate_cuid_string_map() static method", 2057 version="5.7.2") 2058def generate_cuid_names(block, ctype=None, descend_into=True): 2059 return ComponentUID.generate_cuid_string_map(block, ctype, descend_into) 2060 2061@deprecated("The active_components function is deprecated. " 2062 "Use the Block.component_objects() method.", 2063 version="4.1.10486") 2064def active_components(block, ctype, sort_by_names=False, sort_by_keys=False): 2065 return block.component_objects(ctype, active=True, sort=sort_by_names) 2066 2067 2068@deprecated("The components function is deprecated. " 2069 "Use the Block.component_objects() method.", 2070 version="4.1.10486") 2071def components(block, ctype, sort_by_names=False, sort_by_keys=False): 2072 return block.component_objects(ctype, active=False, sort=sort_by_names) 2073 2074 2075@deprecated("The active_components_data function is deprecated. " 2076 "Use the Block.component_data_objects() method.", 2077 version="4.1.10486") 2078def active_components_data(block, ctype, 2079 sort=None, sort_by_keys=False, sort_by_names=False): 2080 return block.component_data_objects(ctype=ctype, active=True, sort=sort) 2081 2082 2083@deprecated("The components_data function is deprecated. " 2084 "Use the Block.component_data_objects() method.", 2085 version="4.1.10486") 2086def components_data(block, ctype, 2087 sort=None, sort_by_keys=False, sort_by_names=False): 2088 return block.component_data_objects(ctype=ctype, active=False, sort=sort) 2089 2090 2091# 2092# Create a Block and record all the default attributes, methods, etc. 2093# These will be assumes to be the set of illegal component names. 2094# 2095_BlockData._Block_reserved_words = set(dir(Block())) 2096 2097 2098class _IndexedCustomBlockMeta(type): 2099 """Metaclass for creating an indexed custom block. 2100 """ 2101 2102 pass 2103 2104 2105class _ScalarCustomBlockMeta(type): 2106 """Metaclass for creating a scalar custom block. 2107 """ 2108 2109 def __new__(meta, name, bases, dct): 2110 def __init__(self, *args, **kwargs): 2111 # bases[0] is the custom block data object 2112 bases[0].__init__(self, component=self) 2113 # bases[1] is the custom block object that 2114 # is used for declaration 2115 bases[1].__init__(self, *args, **kwargs) 2116 2117 dct["__init__"] = __init__ 2118 return type.__new__(meta, name, bases, dct) 2119 2120 2121class CustomBlock(Block): 2122 """ The base class used by instances of custom block components 2123 """ 2124 2125 def __init__(self, *args, **kwds): 2126 if self._default_ctype is not None: 2127 kwds.setdefault('ctype', self._default_ctype) 2128 Block.__init__(self, *args, **kwds) 2129 2130 2131 def __new__(cls, *args, **kwds): 2132 if cls.__name__.startswith('_Indexed') or \ 2133 cls.__name__.startswith('_Scalar'): 2134 # we are entering here the second time (recursive) 2135 # therefore, we need to create what we have 2136 return super(CustomBlock, cls).__new__(cls) 2137 if not args or (args[0] is UnindexedComponent_set and len(args) == 1): 2138 n = _ScalarCustomBlockMeta( 2139 "_Scalar%s" % (cls.__name__,), 2140 (cls._ComponentDataClass, cls), 2141 {} 2142 ) 2143 return n.__new__(n) 2144 else: 2145 n = _IndexedCustomBlockMeta( 2146 "_Indexed%s" % (cls.__name__,), 2147 (cls,), 2148 {} 2149 ) 2150 return n.__new__(n) 2151 2152 2153def declare_custom_block(name, new_ctype=None): 2154 """ Decorator to declare components for a custom block data class 2155 2156 >>> @declare_custom_block(name=FooBlock) 2157 ... class FooBlockData(_BlockData): 2158 ... # custom block data class 2159 ... pass 2160 """ 2161 2162 def proc_dec(cls): 2163 # this is the decorator function that 2164 # creates the block component class 2165 2166 # Default (derived) Block attributes 2167 clsbody = { 2168 "__module__": cls.__module__, # magic to fix the module 2169 # Default IndexedComponent data object is the decorated class: 2170 "_ComponentDataClass": cls, 2171 # By default this new block does not declare a new ctype 2172 "_default_ctype": None, 2173 } 2174 2175 c = type( 2176 name, # name of new class 2177 (CustomBlock,), # base classes 2178 clsbody, # class body definitions (will populate __dict__) 2179 ) 2180 2181 if new_ctype is not None: 2182 if new_ctype is True: 2183 c._default_ctype = c 2184 elif type(new_ctype) is type: 2185 c._default_ctype = new_ctype 2186 else: 2187 raise ValueError("Expected new_ctype to be either type " 2188 "or 'True'; received: %s" % (new_ctype,)) 2189 2190 # Register the new Block type in the same module as the BlockData 2191 setattr(sys.modules[cls.__module__], name, c) 2192 # TODO: can we also register concrete Indexed* and Scalar* 2193 # classes into the original BlockData module (instead of relying 2194 # on metaclasses)? 2195 2196 # are these necessary? 2197 setattr(cls, '_orig_name', name) 2198 setattr(cls, '_orig_module', cls.__module__) 2199 return cls 2200 2201 return proc_dec 2202 2203