source: trunk/coopr/pyomo/base/var.py @ 1531

Last change on this file since 1531 was 1531, checked in by jwatson, 11 years ago

Added a "parent variable" attribute to _VarVal, which is necessary when writing the pysp extensive form (due to multiple models co-existing) - but is also generally useful.

File size: 15.6 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__ = ['Var', 'VarStatus', '_VarBase']
12
13from numvalue import *
14from plugin import ComponentRegistration
15import types
16from set_types import *
17from sets import _BaseSet
18import pyutilib
19from pyutilib.tpl.enum import Enum
20import sys
21
22VarStatus = Enum('undefined', 'fixed_by_presolve', 'fixed_by_optimizer', 'unused', 'used')
23
24class _VarValue(NumericValue):
25    """Holds the numeric value of a variable, along with suffix info"""
26
27    """Constructor"""
28    def __init__(self,**kwds):
29        NumericValue.__init__(self,**kwds)
30        self.var = None # the "parent" variable.
31        self.id = None
32        self.label = None
33        self.initial = None 
34        self.lb = None 
35        self.ub = None 
36        self.fixed = False 
37        self.status = VarStatus.undefined
38
39    def __str__(self):
40        return self.name
41
42    def fixed_value(self):
43        if self.fixed:
44            return True
45        return False
46
47    def simplify(self, model):
48        return self                 #pragma:nocover
49
50    def pprint(self, ostream=None):
51        if ostream is None:
52           ostream = sys.stdout
53        print >>ostream, str(self),
54
55#
56# Variable attributes:
57#
58# Single variable:
59#
60# x = Var()
61# x.fixed = True
62# x.domain = Reals
63# x.name = "x"
64# x.lb = -1.0
65# x.ub = 1.0
66# x.initial = 0.0
67# x = -0.4
68#
69# Array of variables:
70#
71# y = Var(set1,set2,...,setn)
72# y.fixed = True (fixes all variables)
73# y.domain = Reals
74# y.name = "y"
75# y[i,j,...,k] (returns value of a given index)
76#
77# y[i,j,...,k].fixed
78# y[i,j,...,k].lb
79# y[i,j,...,k].ub
80# y[i,j,...,k].initial
81#
82
83class _VarBase(object):
84    """A numeric variable, which may be defined over a index"""
85
86    """ Constructor
87        Arguments:
88           name                The name of this variable
89           index        The index set that defines the distinct variables.
90                          By default, this is None, indicating that there
91                          is a single variable.
92           domain       A set that defines the type of values that
93                          each parameter must be.
94           default      A set that defines default values for this
95                          variable.
96           bounds       A rule for defining bounds values for this
97                          variable.
98           rule         A rule for setting up this parameter with
99                          existing model data
100    """
101    def __init__(self, **kwd):
102        #
103        # Default keyword values
104        #
105        self._attr_declarations = {}
106        tmpname="unknown"
107        tmpdomain=Reals
108        self._ndim=0
109        tmpbounds=None
110        self._varval={}
111        self._initialize=None
112        for key in kwd.keys():
113          if key == "name":
114             tmpname=kwd[key]
115          elif key == "within" or key=="domain":
116             tmpdomain=kwd[key]
117          elif key == "initialize":
118             self.__dict__["_"+key] = kwd[key]
119          elif key == "bounds":
120             tmpbounds=kwd[key]
121          else:
122             raise ValueError, "Var constructor: unknown keyword - "+key
123        self.name = tmpname
124        self.domain = tmpdomain
125        self.bounds=tmpbounds
126        self._index_set=None
127        self._model=None
128
129    def as_numeric(self):
130        if None in self._varval:
131            return self._varval[None]
132        return self
133
134    def keys(self):
135        return self._varval.keys()
136
137    def __iter__(self):
138        return self._varval.keys().__iter__()
139
140    def __contains__(self,ndx):
141        return ndx in self._varval
142
143    def dim(self):
144        return self._ndim
145
146    def reset(self):
147        for key in self._varval:
148            if not self._varval[key].initial is None:
149                self._varval[key].set_value( self._varval[key].initial )
150
151    def __len__(self):
152        return len(self._varval)
153
154    def __setitem__(self,ndx,val):
155        #print "HERE",ndx,val, self._valid_value(val,False), self.domain
156        if None in self._index:
157            raise KeyError, "Cannot set an array value in singleton variable "+self.name
158        if ndx not in self._index:
159            raise KeyError, "Cannot set the value of array variable "+self.name+" with invalid index "+str(ndx)
160        if not self._valid_value(val,False):
161            raise ValueError, "Cannot set variable "+self.name+" with invalid value: index=" + str(ndx) + " value=" + str(val)
162        self._varval[ndx].value = val
163
164    def __getitem__(self,ndx):
165        """This method returns a _VarValue object.  This object can be
166           coerced to a numeric value using the value() function, or using
167           explicity coercion with float().
168        """
169        try:
170            return self._varval[ndx]
171        except KeyError:
172            raise KeyError, "Unknown index " + str(ndx) + " in variable " + self.name
173
174    def X__setattr__(self,name,val):
175        if name == "_attr_declarations":
176            NumericValue.__setattr__(self,name,val)
177        if (name in ("value","initial","ub","lb","fixed","status") or name in self._attr_declarations) and len(self._varval)==0:
178           raise TypeError, "Attempting to set option '"+name+"' with uninitialized variable "+self.name
179        if name == "value":
180           tmpval = value(val)
181           if tmpval is None:
182              raise ValueError, "Value "+str(val)+" is not a valid value for variable "+self.name
183           for key in self._varval:
184             self._varval[key].value = tmpval
185           try:
186              self._valid_value(tmpval)
187           except ValueError, err:
188              raise ValueError, "Bad value for variable "+self.name+" : "+str(err)
189        elif name == "initial":
190           tmpval = value(val)
191           if tmpval is None:
192              raise ValueError, "Value "+str(val)+" is not a valid value for variable "+self.name
193           for key in self._varval:
194             self._varval[key].value = tmpval
195             self._varval[key].initial = tmpval
196           self._valid_value(tmpval)
197        elif name == "ub" or name == "lb":
198           tmpval = value(val)
199           if tmpval is None:
200              raise ValueError, "Value "+str(val)+" is not a valid bound value for variable "+self.name
201           for key in self._varval:
202             if name == "ub":
203                self._varval[key].ub = tmpval
204             else:
205                self._varval[key].lb = tmpval
206           self._valid_value(tmpval)
207        elif name == "fixed":
208           if type(val) is not bool:
209              raise ValueError, "Must use a boolean to specify the 'fixed' attribute for variable "+self.name
210           for key in self._varval:
211             self._varval[key].fixed = val
212        elif name == "status":
213           if not val in VarStatus:
214              raise ValueError, "Must use a VarStatus value to specify the 'status' attribute for variable "+self.name
215           for key in self._varval:
216             self._varval[key].status = val
217        elif name in self._attr_declarations:
218            for key in self._varval:
219                setattr(self._varval[key],name,val)
220        else:
221            NumericValue.__setattr__(self,name,val)
222
223    def X__getattr__(self,name):
224        if name == "_attr_declarations":
225            return NumericValue.__getattr__(self,name)
226        if name in ("value","ub","lb","fixed","initial","status") or name in self._attr_declarations:
227           if None not in self._varval:
228              raise AttributeError, "Cannot retrieve attribute '"+name+"' for array variable "+self.name
229           return getattr(self._varval[None], name)
230        return NumericValue.__getattr__(self,name)
231
232    def simplify(self, model):
233        return self
234
235    def _construct(self, model, data):
236        self._model = model
237        #
238        # Construct _VarValue() objects for all index values
239        #
240        if self._ndim > 0:
241            if type(self._initialize) is dict:
242                self._index = self._initialize.keys()
243            for ndx in self._index:
244                if ndx is not None:
245                    self._varval[ndx] = _VarValue(name=self.name+"["+str(ndx)+"]",domain=self.domain)
246                    self._varval[ndx].var = self
247                   
248        #
249        # Initialize values with a dictionary if provided
250        #
251        if self._initialize is not None and type(self._initialize) is not types.FunctionType:
252           if type(self._initialize) is dict:
253              for key in self._initialize:
254                self._varval[key].value = None
255                self._varval[key].initial = self._initialize[key]
256                self._valid_value(self._initialize[key],True)
257           else:
258              for key in self._index:
259                self._varval[key].value = None
260                self._varval[key].initial = self._initialize
261              self._valid_value(self._initialize,True)
262        #
263        # Initialize values with the _rule function if provided
264        #
265        elif type(self._initialize) is types.FunctionType:
266           for key in self._varval:
267             if isinstance(key,tuple):
268                tmp = list(key)
269             elif key is None:
270                tmp = []
271             else:
272                tmp = [key]
273             tmp.append(model)
274             tmp = tuple(tmp)
275             self._varval[key].value = None
276             self._varval[key].initial = self._initialize(*tmp)
277             self._valid_value(self._varval[key].initial,True)
278        #
279        # Initialize bounds with the bounds function if provided
280        #
281        if self.bounds is not None:
282           if type(self.bounds) is tuple:
283             for key in self._varval:
284               (self._varval[key].lb, self._varval[key].ub) = self.bounds
285           else:
286             for key in self._varval:
287               if isinstance(key,tuple):
288                  tmp = list(key)
289               elif key is None:
290                  tmp = []
291               else:
292                  tmp = [key]
293               tmp.append(model)
294               tmp = tuple(tmp)
295               (self._varval[key].lb, self._varval[key].ub) = self.bounds(*tmp)
296           for key in self._varval:
297             if self._varval[key].lb is not None and \
298                not pyutilib.is_finite(self._varval[key].lb):
299                self._varval[key].lb = None
300             if self._varval[key].ub is not None and \
301                not pyutilib.is_finite(self._varval[key].ub):
302                self._varval[key].ub = None
303        #
304        # Iterate through all variables, and tighten the bounds based on
305        # the domain bounds information.
306        #
307        dbounds = self.domain.bounds()
308        if not dbounds is None and dbounds != (None,None):
309            for key in self._varval:
310                if not dbounds[0] is None:
311                    if self._varval[key].lb is None or dbounds[0] > self._varval[key].lb:
312                        self._varval[key].lb = dbounds[0]
313                if not dbounds[1] is None:
314                    if self._varval[key].ub is None or dbounds[1] < self._varval[key].ub:
315                        self._varval[key].ub = dbounds[1]
316        #
317        # Setup declared attributes for all variables
318        #
319        for attr in self._attr_declarations:
320            for key in self._varval:
321                setattr(self._varval[key],attr,self._attr_declarations[attr][0])
322
323    def pprint(self, ostream=None):
324        if ostream is None:
325           ostream = sys.stdout
326        print >>ostream, "  ",self.name,":",
327        print >>ostream, "\tSize="+str(len(self)),
328        print >>ostream, "\tDomain="+self.domain.name
329        if self._index_set is not None:
330            print >>ostream, "\tIndicies: ",
331            for idx in self._index_set:
332               print >>ostream, str(idx.name)+", ",
333            print ""
334        if None in self._varval:
335           print >>ostream, "\tInitial Value : Lower Bound : Upper Bound : Current Value: Fixed"
336           print >>ostream, "\t", str(self._varval[None].initial)+" : "+str(self._varval[None].lb)+" : "+str(self._varval[None].ub)+" : "+str(self._varval[None].value)+" : "+str(self._varval[None].fixed)
337        else:
338           print >>ostream, "\tKey : Initial Value : Lower Bound : Upper Bound : Current Value: Fixed"
339           tmp=self._varval.keys()
340           tmp.sort()
341           for key in tmp:
342             val = self._varval[key].initial
343             print >>ostream, "\t"+str(key)+" : "+str(val)+" : "+str(value(self._varval[key].lb))+" : "+str(value(self._varval[key].ub))+" : "+str(value(self._varval[key].value))+" : "+str(value(self._varval[key].fixed))
344
345    def display(self, prefix="", ostream=None):
346        if ostream is None:
347           ostream = sys.stdout
348        print >>ostream, prefix+"Variable "+self.name,":",
349        print >>ostream, "  Size="+str(len(self)),
350        print >>ostream, "Domain="+self.domain.name
351        if None in self._varval:
352           print >>ostream, prefix+"  Value="+pyutilib.format_io(self._varval[None].value)
353        else:
354           for key in self._varval:
355             val = self._varval[key].value
356             print >>ostream, prefix+"  "+str(key)+" : "+str(val)
357
358    def declare_attribute(self, name, default=None):
359        """
360        Declare a user-defined attribute.
361        """
362        if name[0] == "_":
363            raise AttributeError, "Cannot define an attribute that begins with  '_'"
364        if name in self._attr_declarations:
365            raise AttributeError, "Attribute %s is already defined" % name
366        self._attr_declarations[name] = (default,)
367        #if not default is None:
368            #self._valid_value(default)
369        #
370        # If this variable has been constructed, then
371        # generate this attribute for all variables
372        #
373        if len(self._varval) > 0:
374            for key in self._varval:
375                setattr(self._varval[key],name,default)
376
377
378class _VarElement(_VarBase,_VarValue):
379
380    def __init__(self, *args, **kwd):
381        _VarValue.__init__(self, **kwd)
382        _VarBase.__init__(self, **kwd)
383        self._index=[None]
384        self._varval[None] = self
385        self._varval[None].var = self
386
387
388class _VarArray(_VarBase):
389
390    def __init__(self, *args, **kwd):
391        _VarBase.__init__(self, **kwd)
392        for arg in args:
393            if not isinstance(arg,_BaseSet):
394                raise ValueError, "Cannot index variable `"+self.name+"' with object of type "+str(type(arg))
395            self._ndim += arg.dimen
396        if len(args) == 1:
397            self._index=args[0]
398        else:
399            self._index=None
400            self._index_set=args
401        #print "X",kwd
402        self._dummy_val = _VarValue(**kwd)
403
404    def __float__(self):
405        raise TypeError, "Cannot access the value of array variable "+self.name
406
407    def __int__(self):
408        raise TypeError, "Cannot access the value of array variable "+self.name
409
410    def _valid_value(self,value,use_exception=True):
411        #print "VALID",self._dummy_val.domain
412        return self._dummy_val._valid_value(value,use_exception)
413
414    def set_value(self, value):
415        raise ValueError, "Cannot specify the value of array variable "+self.name
416
417    def __str__(self):
418        return self.name
419
420class Var(object):
421    """
422    Variable objects that are used to construct Pyomo models.
423    """
424    def __new__(cls, *args, **kwds):
425        if args == ():
426            self = _VarElement(*args, **kwds)
427        else:
428            self = _VarArray(*args, **kwds)
429        return self
430
431
432ComponentRegistration("Var", _VarBase, "Decision variables in a model.")
Note: See TracBrowser for help on using the repository browser.