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

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

Misc rework of variable presolving. Now, model
statistics are stored in a more obvious location, and using more
verbose names.

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