source: coopr.pyomo/trunk/coopr/pyomo/base/param.py @ 5813

Last change on this file since 5813 was 5813, checked in by wehart, 7 years ago

Misc documentation edits.

File size: 15.0 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11__all__ = ['Param']
12
13import types
14import logging
15import weakref
16
17from pyutilib.component.core import alias
18import pyutilib.math
19
20from component import Component
21from sparse_indexed_component import SparseIndexedComponent
22from misc import apply_indexed_rule, apply_parameterized_indexed_rule, \
23     create_name
24from numvalue import NumericConstant
25from set_types import Any, Reals
26
27from numvalue import value
28
29logger = logging.getLogger('coopr.pyomo')
30
31class _ParamData(NumericConstant):
32    """Holds the numeric value of a mutable parameter"""
33
34    __slots__ = []
35
36    def __init__(self, name, domain, value):
37        """Constructor"""
38        #
39        # NumericConstant.__init__(self, name, domain, value)
40        #
41        if domain is None:
42            self.domain = Reals
43        else:
44            self.domain = domain
45        self.name = name
46        if pyutilib.math.is_nan(value):
47            self.value = pyutilib.math.nan
48        else:
49            self.value = value
50
51
52    def __str__(self):
53        return str(self.name)
54
55    def clear(self):
56        self.value = None
57
58
59class Param(SparseIndexedComponent):
60    """A parameter value, which may be defined over a index"""
61
62    """ Constructor
63        Arguments:
64           name       The name of this parameter
65           index      The index set that defines the distinct parameters.
66                         By default, this is None, indicating that there
67                         is a single parameter.
68           within     A set that defines the type of values that
69                         each parameter must be.
70           validate   A rule for validating this parameter w.r.t. data
71                         that exists in the model
72           default    A set that defines default values for this parameter
73           rule       A rule for setting up this parameter with existing model
74                         data
75           nochecking If true, various checks regarding valid domain and index
76                      values are skipped. Only intended for use by algorithm
77                      developers - not modelers!
78    """
79
80    alias( "Param",
81           doc="Parameter data that is used to define a model instance.",
82           subclass=True )
83
84    def __new__(cls, *args, **kwds):
85        if cls != Param:
86            return super(Param, cls).__new__(cls)
87        if args == ():
88            return _ParamElement.__new__(_ParamElement)
89        else:
90            return _ParamArray.__new__(_ParamArray)
91
92    def __init__(self, *args, **kwd):
93        kwd.pop('repn',None)
94       
95        self._initialize = kwd.pop('initialize', None )
96        self._initialize = kwd.pop('rule', self._initialize )
97        self._validate   = kwd.pop('validate', None )
98        self.domain      = kwd.pop('within', Any )
99        self.nochecking  = kwd.pop('nochecking',False)
100        self._mutable    = kwd.pop('mutable', True )
101
102        self._default_val = None
103        # Use set_default() so that we vaidate the value against the domain
104        self.set_default(kwd.pop('default', None ))
105       
106        #
107        kwd.setdefault('ctype', Param)
108        SparseIndexedComponent.__init__(self, *args, **kwd)
109       
110        # Because we want to defer the check for defined values until
111        # runtime, we will undo the weakref to ourselves
112        if not self.is_indexed():
113            del self._data[None]
114
115
116    def pprint(self, ostream=None, verbose=False):
117        if ostream is None:
118            ostream = sys.stdout
119        ostream.write( %s : " % (self.name,))
120        if self.doc is not None:
121            ostream.write("\t%s\n" % (self.doc,))
122        ostream.write( "\tSize=%s \tDomain=%s\n"
123                       % (len(self), self.domain.name) )
124        if not self._constructed:
125            ostream.write("\tNot constructed\n")
126        elif None in self.keys():
127            if None in self._data:
128                ostream.write("\t%s\n" % ( value(self._data[None]()), ))
129            else:
130                ostream.write("\tUndefined\n")
131        else:
132            for key, val in sorted(self.sparse_iteritems()):
133                ostream.write("\t%s : %s\n" % (key, value(val)))
134        if self._default_val is not None:
135            ostream.write("\tdefault: %s\n" % (value(self._default_val)))
136
137    # TODO: Not sure what "reset" really means in this context...
138    def reset(self):
139        pass
140
141    #
142    # A utility to extract all index-value pairs defining this
143    # parameter, returned as a dictionary. useful in many
144    # contexts, in which key iteration and repeated __getitem__
145    # calls are too expensive to extract the contents of a parameter.
146    # NOTE: We are presently not careful if an index has not been
147    #       explicitly defined - issues around defaults are bound
148    #       to crop up.
149    # NOTE: Using this method will cause sparse mutable Params to become
150    #       dense!
151    #
152    def extract_values(self):
153        return dict((key,value(val)) for key,val in self.iteritems())
154
155    def sparse_extract_values(self):
156        return dict((key,value(val)) for key,val in self.sparse_iteritems())
157
158
159    def _default(self, idx):
160        if self._default_val is None:
161            if idx is None:
162                idx_str = '%s' % (self.name,)
163            else:
164                idx_str = '%s[%s]' % (self.name, idx,)
165            raise ValueError(
166                "Error retrieving Param value (%s): The Param value is "
167                "undefined and no default value is specified"
168                % ( idx_str,) )
169        if self._mutable:
170            if self.is_indexed():
171                return self._data.setdefault(
172                    idx, _ParamData( create_name(self.name,idx), 
173                                     self.domain, self._default_val ) )
174            else:
175                self[None] = self._default_val
176                return self
177        else:
178            return self._default_val
179
180    def set_default(self, val):
181        if val is not None and val not in self.domain:
182            raise ValueError(
183                "Default value (%s) is not valid for Param domain %s" %
184                ( str(val), self.domain.name ) )
185        self._default_val = val
186
187    def __setitem__(self, ndx, val):
188        if self._constructed and not self._mutable:
189            raise TypeError(
190"""Attempring to set the value of the immutable parameter %s after the
191parameter has been constructed.  If you intend to change the value of
192this parameter dynamically, please declare the parameter as mutable
193[i.e., Param(mutable=True)]""" % (self.name,))
194
195        #print "setting %s[%s] = %s" % (self.name, ndx, val)
196        # Validate the index
197        if self.nochecking:
198            # Ironically, if we are *not* checking index values, then we
199            # *must* flatten the incoming index tuple
200            ndx = self.normalize_index(ndx)
201        # Potential optimization: if we find that updating a Param is
202        # more common than setting it in the first place, then first
203        # checking the _data and then falling back on the _index *might*
204        # be more efficient.
205        #
206        #elif ndx not in _data and ndx not in self._index:
207        elif ndx not in self._index:
208            # We rely on "most" people doing things correctly; that is,
209            # they send us either a scalar or a valid tuple.  So, for
210            # efficiency, we will check the index *first*, and only go
211            # through the hassle of flattening things if the ndx is not
212            # found.
213            ndx = self.normalize_index(ndx)
214            if ndx not in self._index:
215                if not self.is_indexed():
216                    msg = "Error setting parameter value: " \
217                          "Cannot treat the scalar Param '%s' as an array" \
218                          % ( self.name, )
219                else:
220                    msg = "Error setting parameter value: " \
221                          "Index '%s' is not valid for array Param '%s'" \
222                          % ( ndx, self.name, )
223                raise KeyError(msg)
224
225        if ndx is None:
226            self.value = val
227            self._data[ndx] = weakref.ref(self)
228        elif self._mutable:
229            if ndx in self._data:
230                self._data[ndx].value = val
231            else:
232                self._data[ndx] = _ParamData(create_name(self.name,ndx), self.domain, val)
233        else:
234            self._data[ndx] = val
235        #
236        # This is at the end, so the user can write their validation
237        # tests as if the data was already added.
238        #
239        if self.nochecking:
240            return
241        if val not in self.domain:
242            raise ValueError(
243                "Invalid parameter value: %s[%s] = '%s', value type=%s.\n"
244                "\tValue not in parameter domain %s" %
245                ( self.name, ndx, val, type(val), self.domain.name ) )
246        if self._validate is not None:
247            if self.is_indexed():
248                if type(ndx) is tuple:
249                    tmp = ndx
250                else:
251                    tmp = (ndx,)
252            else:
253                tmp = ()
254            if not apply_parameterized_indexed_rule(
255                self, self._validate, self._model(), val, tmp ):
256                raise ValueError(
257                    "Invalid parameter value: %s[%s] = '%s', value type=%s.\n"
258                    "\tValue failed parameter validation rule" %
259                    ( self.name, ndx, val, type(val) ) )
260
261    def construct(self, data=None):
262        """ Apply the rule to construct values in this set """
263        if __debug__:
264            if logger.isEnabledFor(logging.DEBUG):
265                logger.debug("Constructing Param, name=%s, from data=%s"
266                             % ( self.name, `data` ))
267        if self._constructed:
268            raise IOError(
269                "Cannot reconstruct parameter '%s' (already constructed)"
270                % self.name )
271            return
272        self.clear()
273        #
274        rule = getattr(self,'rule',None)
275        if rule is not None:
276            self._initialize=rule
277        #
278        # Construct using the initial data or the data loaded from an
279        # external source.  Note:  data values will be queried in to following
280        # order:
281        #   1) the 'data' dictionary
282        #   2) the self._initialize dictionary
283        #   3) [implicit: fall back on the default value]
284        #
285        # To accomplish this, we will first set all the values based on
286        # self._initialize, and then allow the data to overwrite
287        # anything
288        #
289        # NB: Singleton Params can always be treated as "normal" indexed
290        # params, indexed by a set {None}.
291        #
292
293        # NB: Previously, we would raise an exception for constructing
294        # scalar parameters with no defined data.  As that was a special
295        # case (i.e. didn't apply to arrays) and was frustrating for
296        # Concrete folks who were going to initialize the value later,
297        # we will allow an undefined Param to be constructed and will
298        # instead throw an exception later i the user tries to *use* the
299        # Param before it is initialized.
300        #
301        #if self._initialize is None:
302        #    if data is None and self._default_val is None:
303        #        raise ValueError(
304        #            "Error constructing Param '%s': cannot locate the Param "
305        #            "data (not found in rule or external data, and no "
306        #            "default value specified)" % ( self.name, ) )
307        if self._initialize is not None:
308            _init = self._initialize
309            _init_type = type(_init)
310            if _init_type is dict:
311                for key, val in _init.iteritems():
312                    self[key] = val
313            elif _init_type is types.FunctionType:
314                if self.is_indexed():
315                    for idx in self:
316                        # IMPORTANT: Do *not* call self.__setitem__ here
317                        #    - we already know the index is valid (and
318                        #    flattened), and validating indices is far too
319                        #    expensive to waste time doing it
320                        #    again. however, we still need to validate the
321                        #    value generated by the rule, relative to the
322                        #    parameter domain.
323                        # REVISION: given the rewrite of __setitem__,
324                        #    the index is not validated unless it is not
325                        #    found in the corresponding data / index.  We
326                        #    *could* reimplement the optimization metioned
327                        #    above if it proves necessary, but until then,
328                        #    the DRY principle dictates that we use
329                        #    __setitem__.
330                        self[idx] = apply_indexed_rule(
331                            self, _init, self._model(), idx )
332                else:
333                    self[None] = _init(self._model())
334            else:
335                # if things looks like a dictionary, then we will treat
336                # it as such
337                _isDict = '__getitem__' in dir(_init)
338                if _isDict:
339                    try:
340                        for x in _init:
341                            _init.__getitem__(x)
342                    except:
343                        _isDict = False
344                if _isDict:
345                    for key in self._initialize:
346                        self[key] = self._initialize[key]
347                else:
348                    for key in self._index:
349                        self[key] = self._initialize
350               
351        if data is not None:
352            for key, val in data.iteritems():
353                self[key] = val
354
355        self._constructed = True
356
357
358class _ParamElement(_ParamData, Param):
359
360    def __init__(self, *args, **kwds):
361        Param.__init__(self, *args, **kwds)
362        _ParamData.__init__(self, self.name, self.domain, kwds.get('default',None))
363
364    def __getstate__(self):
365        ans = _ParamData.__getstate__(self)
366        ans.update(Param.__getstate__(self))
367        return ans
368
369    def __setstate__(self, state):
370        for (slot_name, value) in state.iteritems():
371            setattr(self, slot_name, value)
372
373    def pprint(self, ostream=None, verbose=False):
374        # Needed so that users find Param.pprint and not _ParamData.pprint
375        Param.pprint(self, ostream=ostream, verbose=verbose)
376
377    def __call__(self, *args, **kwds):
378        # Needed because we rely on self[None] to know if this parameter is valid
379        return _ParamData.__call__(self[None], *args, **kwds)
380
381
382class _ParamArray(Param):
383
384    def __init__(self, *args, **kwds):
385        Param.__init__(self, *args, **kwds)
386
387    def __str__(self):
388        return str(self.name)
389
Note: See TracBrowser for help on using the repository browser.