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__ = ('Suffix',
12           'active_export_suffix_generator',
13           'active_import_suffix_generator')
14
15import logging
16
17from pyomo.common.collections import ComponentMap
18from pyomo.common.log import is_debug_set
19from pyomo.common.timing import ConstructionTimer
20from pyomo.core.base.component import ActiveComponent, ModelComponentFactory
21
22from pyomo.common.deprecation import deprecated
23
24logger = logging.getLogger('pyomo.core')
25
26# A list of convenient suffix generators, including:
27#   - active_export_suffix_generator
28#       **(used by problem writers)
29#   - export_suffix_generator
30#   - active_import_suffix_generator
31#       **(used by OptSolver and PyomoModel._load_solution)
32#   - import_suffix_generator
33#   - active_local_suffix_generator
34#   - local_suffix_generator
35#   - active_suffix_generator
36#   - suffix_generator
37
38
39def active_export_suffix_generator(a_block, datatype=False):
40    if (datatype is False):
41        for name, suffix in a_block.component_map(Suffix, active=True).items():
42            if suffix.export_enabled() is True:
43                yield name, suffix
44    else:
45        for name, suffix in a_block.component_map(Suffix, active=True).items():
46            if (suffix.export_enabled() is True) and \
47               (suffix.get_datatype() is datatype):
48                yield name, suffix
49
50
51def export_suffix_generator(a_block, datatype=False):
52    if (datatype is False):
53        for name, suffix in a_block.component_map(Suffix).items():
54            if suffix.export_enabled() is True:
55                yield name, suffix
56    else:
57        for name, suffix in a_block.component_map(Suffix).items():
58            if (suffix.export_enabled() is True) and \
59               (suffix.get_datatype() is datatype):
60                yield name, suffix
61
62
63def active_import_suffix_generator(a_block, datatype=False):
64    if (datatype is False):
65        for name, suffix in a_block.component_map(Suffix, active=True).items():
66            if suffix.import_enabled() is True:
67                yield name, suffix
68    else:
69        for name, suffix in a_block.component_map(Suffix, active=True).items():
70            if (suffix.import_enabled() is True) and \
71               (suffix.get_datatype() is datatype):
72                yield name, suffix
73
74
75def import_suffix_generator(a_block, datatype=False):
76    if (datatype is False):
77        for name, suffix in a_block.component_map(Suffix).items():
78            if suffix.import_enabled() is True:
79                yield name, suffix
80    else:
81        for name, suffix in a_block.component_map(Suffix).items():
82            if (suffix.import_enabled() is True) and \
83               (suffix.get_datatype() is datatype):
84                yield name, suffix
85
86
87def active_local_suffix_generator(a_block, datatype=False):
88    if (datatype is False):
89        for name, suffix in a_block.component_map(Suffix, active=True).items():
90            if suffix.get_direction() is Suffix.LOCAL:
91                yield name, suffix
92    else:
93        for name, suffix in a_block.component_map(Suffix, active=True).items():
94            if (suffix.get_direction() is Suffix.LOCAL) and \
95               (suffix.get_datatype() is datatype):
96                yield name, suffix
97
98
99def local_suffix_generator(a_block, datatype=False):
100    if (datatype is False):
101        for name, suffix in a_block.component_map(Suffix).items():
102            if suffix.get_direction() is Suffix.LOCAL:
103                yield name, suffix
104    else:
105        for name, suffix in a_block.component_map(Suffix).items():
106            if (suffix.get_direction() is Suffix.LOCAL) and \
107               (suffix.get_datatype() is datatype):
108                yield name, suffix
109
110
111def active_suffix_generator(a_block, datatype=False):
112    if (datatype is False):
113        for name, suffix in a_block.component_map(Suffix, active=True).items():
114            yield name, suffix
115    else:
116        for name, suffix in a_block.component_map(Suffix, active=True).items():
117            if suffix.get_datatype() is datatype:
118                yield name, suffix
119
120
121def suffix_generator(a_block, datatype=False):
122    if (datatype is False):
123        for name, suffix in a_block.component_map(Suffix).items():
124            yield name, suffix
125    else:
126        for name, suffix in a_block.component_map(Suffix).items():
127            if suffix.get_datatype() is datatype:
128                yield name, suffix
129
130# Note: The order of inheritance here is important so that
131#       __setstate__ works correctly on the ActiveComponent base class.
132
133
134@ModelComponentFactory.register("Declare a container for extraneous model data")
135class Suffix(ComponentMap, ActiveComponent):
136    """A model suffix, representing extraneous model data"""
137
138    """
139    Constructor Arguments:
140        direction   The direction of information flow for this suffix.
141                        By default, this is LOCAL, indicating that no
142                        suffix data is exported or imported.
143        datatype    A variable type associated with all values of this
144                        suffix.
145    """
146
147    # Suffix Directions:
148    # If more directions are added be sure to update the error message
149    # in the setDirection method
150    # neither sent to solver or received from solver
151    LOCAL = 0
152    # sent to solver or other external location
153    EXPORT = 1
154    # obtained from solver or other external source
155    IMPORT = 2
156    IMPORT_EXPORT = 3  # both
157
158    SuffixDirections = (LOCAL, EXPORT, IMPORT, IMPORT_EXPORT)
159    SuffixDirectionToStr = {LOCAL: 'Suffix.LOCAL',
160                            EXPORT: 'Suffix.EXPORT',
161                            IMPORT: 'Suffix.IMPORT',
162                            IMPORT_EXPORT: 'Suffix.IMPORT_EXPORT'}
163    # Suffix Datatypes
164    FLOAT = 4
165    INT = 0
166    SuffixDatatypes = (FLOAT, INT, None)
167    SuffixDatatypeToStr = {FLOAT: 'Suffix.FLOAT',
168                           INT: 'Suffix.INT',
169                           None: str(None)}
170
171    def __init__(self, **kwds):
172
173        # Suffix type information
174        self._direction = None
175        self._datatype = None
176        self._rule = None
177
178        # The suffix direction
179        direction = kwds.pop('direction', Suffix.LOCAL)
180
181        # The suffix datatype
182        datatype = kwds.pop('datatype', Suffix.FLOAT)
183
184        # The suffix construction rule
185        # TODO: deprecate the use of 'rule'
186        self._rule = kwds.pop('rule', None)
187        self._rule = kwds.pop('initialize', self._rule)
188
189        # Check that keyword values make sense (these function have
190        # internal error checking).
191        self.set_direction(direction)
192        self.set_datatype(datatype)
193
194        # Initialize base classes
195        kwds.setdefault('ctype', Suffix)
196        ActiveComponent.__init__(self, **kwds)
197        ComponentMap.__init__(self)
198
199        if self._rule is None:
200            self.construct()
201
202    def __setstate__(self, state):
203        """
204        This method must be defined for deepcopy/pickling because this
205        class relies on component ids.
206        """
207        ActiveComponent.__setstate__(self, state)
208        ComponentMap.__setstate__(self, state)
209
210    def construct(self, data=None):
211        """
212        Constructs this component, applying rule if it exists.
213        """
214        if is_debug_set(logger):
215            logger.debug("Constructing suffix %s", self.name)
216
217        if self._constructed is True:
218            return
219
220        timer = ConstructionTimer(self)
221        self._constructed = True
222
223        if self._rule is not None:
224            self.update_values(self._rule(self._parent()))
225        timer.report()
226
227    @deprecated('Suffix.exportEnabled is replaced with Suffix.export_enabled.',
228                version='4.1.10486')
229    def exportEnabled(self):
230        return self.export_enabled()
231
232    def export_enabled(self):
233        """
234        Returns True when this suffix is enabled for export to
235        solvers.
236        """
237        return bool(self._direction & Suffix.EXPORT)
238
239    @deprecated('Suffix.importEnabled is replaced with Suffix.import_enabled.',
240                version='4.1.10486')
241    def importEnabled(self):
242        return self.import_enabled()
243
244    def import_enabled(self):
245        """
246        Returns True when this suffix is enabled for import from
247        solutions.
248        """
249        return bool(self._direction & Suffix.IMPORT)
250
251    @deprecated('Suffix.updateValues is replaced with Suffix.update_values.',
252                version='4.1.10486')
253    def updateValues(self, data, expand=True):
254        return self.update_values(data, expand)
255
256    def update_values(self, data, expand=True):
257        """
258        Updates the suffix data given a list of component,value
259        tuples. Provides an improvement in efficiency over calling
260        set_value on every component.
261        """
262        if expand:
263
264            try:
265                items = data.items()
266            except AttributeError:
267                items = data
268
269            for component, value in items:
270                self.set_value(component, value, expand=expand)
271
272        else:
273
274            # As implemented by MutableMapping
275            self.update(data)
276
277    @deprecated('Suffix.setValue is replaced with Suffix.set_value.',
278                version='4.1.10486')
279    def setValue(self, component, value, expand=True):
280        return self.set_value(component, value, expand)
281
282    def set_value(self, component, value, expand=True):
283        """
284        Sets the value of this suffix on the specified component.
285
286        When expand is True (default), array components are handled by
287        storing a reference and value for each index, with no
288        reference being stored for the array component itself. When
289        expand is False (this is the case for __setitem__), this
290        behavior is disabled and a reference to the array component
291        itself is kept.
292        """
293        if expand and component.is_indexed():
294            for component_ in component.values():
295                self[component_] = value
296        else:
297            self[component] = value
298
299    @deprecated('Suffix.setAllValues is replaced with Suffix.set_all_values.',
300                version='4.1.10486')
301    def setAllValues(self, value):
302        return self.set_all_values(value)
303
304    def set_all_values(self, value):
305        """
306        Sets the value of this suffix on all components.
307        """
308        for ndx in self:
309            self[ndx] = value
310
311    @deprecated('Suffix.clearValue is replaced with Suffix.clear_value.',
312                version='4.1.10486')
313    def clearValue(self, component, expand=True):
314        return self.clear_value(component, expand)
315
316    def clear_value(self, component, expand=True):
317        """
318        Clears suffix information for a component.
319        """
320        if expand and component.is_indexed():
321            for component_ in component.values():
322                try:
323                    del self[component_]
324                except KeyError:
325                    pass
326        else:
327            try:
328                del self[component]
329            except KeyError:
330                pass
331
332    @deprecated('Suffix.clearAllValues is replaced with '
333                'Suffix.clear_all_values.',
334                version='4.1.10486')
335    def clearAllValues(self):
336        return self.clear_all_values()
337
338    def clear_all_values(self):
339        """
340        Clears all suffix data.
341        """
342        self.clear()
343
344    @deprecated('Suffix.setDatatype is replaced with Suffix.set_datatype.',
345                version='4.1.10486')
346    def setDatatype(self, datatype):
347        return self.set_datatype(datatype)
348
349    def set_datatype(self, datatype):
350        """
351        Set the suffix datatype.
352        """
353        if datatype not in self.SuffixDatatypes:
354            raise ValueError("Suffix datatype must be one of: %s. \n"
355                             "Value given: %s"
356                             % (list(Suffix.SuffixDatatypeToStr.values()),
357                                datatype))
358        self._datatype = datatype
359
360    @deprecated('Suffix.getDatatype is replaced with Suffix.get_datatype.',
361                version='4.1.10486')
362    def getDatatype(self):
363        return self.get_datatype()
364
365    def get_datatype(self):
366        """
367        Return the suffix datatype.
368        """
369        return self._datatype
370
371    @deprecated('Suffix.setDirection is replaced with Suffix.set_direction.',
372                version='4.1.10486')
373    def setDirection(self, direction):
374        return self.set_direction(direction)
375
376    def set_direction(self, direction):
377        """
378        Set the suffix direction.
379        """
380        if direction not in self.SuffixDirections:
381            raise ValueError("Suffix direction must be one of: %s. \n"
382                             "Value given: %s"
383                             % (list(self.SuffixDirectionToStr.values()),
384                                direction))
385        self._direction = direction
386
387    @deprecated('Suffix.getDirection is replaced with Suffix.get_direction.',
388                version='4.1.10486')
389    def getDirection(self):
390        return self.get_direction()
391
392    def get_direction(self):
393        """
394        Return the suffix direction.
395        """
396        return self._direction
397
398    def __str__(self):
399        """
400        Return a string representation of the suffix.  If the name
401        attribute is None, then return ''
402        """
403        name = self.name
404        if name is None:
405            return ''
406        return name
407
408    def _pprint(self):
409        return (
410            [('Direction', self.SuffixDirectionToStr[self._direction]),
411             ('Datatype', self.SuffixDatatypeToStr[self._datatype]),
412             ],
413            ((str(k), v) for k, v in self._dict.values()),
414            ("Value",),
415            lambda k, v: [v]
416        )
417
418    # TODO: delete
419    @deprecated('Suffix.getValue is replaced with '
420                'the dict-interface method Suffix.get.',
421                version='4.1.10486')
422    def getValue(self, component, *args):
423        """
424        Returns the current value of this suffix for the specified
425        component.
426        """
427        # As implemented by MutableMapping
428        return self.get(component, *args)
429
430    # TODO: delete
431    @deprecated('Suffix.extractValues() is replaced with '
432                'the dict-interface method Suffix.items().',
433                version='4.1.10486')
434    def extractValues(self):
435        """
436        Extract all data stored on this Suffix into a list of
437        component, value tuples.
438        """
439        # As implemented by MutableMapping
440        return list(self.items())
441
442    #
443    # Override a few methods to make sure the ActiveComponent versions are
444    # called. We can't just switch the inheritance order due to
445    # complications with __setstate__
446    #
447
448    def pprint(self, *args, **kwds):
449        return ActiveComponent.pprint(self, *args, **kwds)
450
451    def __str__(self):
452        return ActiveComponent.__str__(self)
453
454    #
455    # Override NotImplementedError messages on ComponentMap base class
456    #
457
458    def __eq__(self, other):
459        """Not implemented."""
460        raise NotImplementedError("Suffix components are not comparable")
461
462    def __ne__(self, other):
463        """Not implemented."""
464        raise NotImplementedError("Suffix components are not comparable")
465
466