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