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

Last change on this file since 1768 was 1768, checked in by wehart, 11 years ago

Rework of Coopr to use the new PyUtilib? package decomposition.

NOTE: to use Coopr with this update, we need to work with a new version of coopr_install.

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