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