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

Last change on this file since 2025 was 2025, checked in by wehart, 10 years ago

Reworking how components interact with models. Now, components can
only be initialized using data from the model that they are associated with.
Thus, models and components are tightly coupled. This allows the following
syntax:

model.x = Param(model.A)
model.construct()

The construction process uses the model data that is associated with the
parameter. Also, this commmit allows models to be components of another
model:

m1=Model()
m2=Model()
m2.m1 = m1

Note, however, that model construct is required to be done separately. When
model m2 is constructed, the m1 component is not constructed.

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