source: trunk/coopr/pyomo/base/constraint.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: 20.1 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__ = ['Objective', 'Constraint']
12
13from numvalue import *
14from expr import *
15from plugin import ComponentRegistration
16import pyomo
17from var import Var, _VarValue
18from sets import Set, _BaseSet
19import pyutilib.misc
20import pyutilib.math
21from numtypes import *
22import sys
23from set_types import *
24
25
26class ObjectiveData(NumericValue):
27
28    def __init__(self, expr=None, name=None):
29        NumericValue.__init__(self,name=name,domain=Reals)
30        self.expr = expr
31        self.label = None
32        self.id = -1
33
34    def __call__(self, exception=True):
35        if self.expr is None:
36            return None
37        return self.expr()
38
39
40class Objective(NumericValue):
41    """An object that defines a objective expression"""
42
43    def __init__(self, *args, **kwd):
44        """Construct an objective expression with rule to construct the
45           expression
46        """
47        tmpname="unknown"
48        tmpsense = minimize
49        tmprule = None
50        self._index_set=None
51        self._data = {}
52        if args == ():
53           self._index=[None]
54           self._data[None] = ObjectiveData()
55           self._ndim=0
56        else:
57           self._ndim=0
58           for arg in args:
59             self._ndim += arg.dimen
60           if len(args) == 1:
61              self._index=args[0]
62           else:
63              self._index=None
64              self._index_set=args
65        self._quad_subexpr = None # this is a hack for now; we eventually want a "QuadraticObjective" class.       
66        for key in kwd.keys():
67          if key == "name":
68             tmpname=kwd[key]
69          elif key == "rule":
70             tmprule = kwd[key]
71          elif key == "sense":
72             tmpsense = kwd[key]
73          else:
74             raise ValueError, "Objective constructor: unknown keyword - "+key
75        NumericValue.__init__(self,name=tmpname)
76        if None in self._data:
77            self._data[None].name = tmpname
78        self.sense = tmpsense
79        self.rule = tmprule
80
81    def __call__(self, exception=True):
82        if len(self._data) == 0:
83            return None
84        if None in self._index:
85            if self._data[None].expr is None:
86                return None
87            return self._data[None].expr()
88        if exception:
89            raise ValueError, "Cannot compute the value of an array of objectives"
90
91    def dim(self):
92        return self._ndim
93
94    def __len__(self):
95        return len(self._data.keys())
96
97    def keys(self):
98        return self._index
99
100    def __contains__(self,ndx):
101        return ndx in self._index
102
103    def __getitem__(self,ndx):
104        """This method returns a ObjectiveData object.  This object can be
105           coerced to a numeric value using the value() function, or using
106           explicity coercion with float().
107        """
108        if ndx in self._data:
109           return self._data[ndx]
110        raise KeyError, "Unknown index " + str(ndx) + " in objective " + self.name
111
112    def __iter__(self):
113        return self._index.__iter__()
114
115    def _apply_rule(self,arg,ndx):
116        tmp = self.rule(*arg)
117        if tmp is None:
118           return None
119        if type(tmp) in [bool,int,long,float]:
120           return NumericConstant(value=tmp)
121        return tmp
122
123    def _construct(self, model, data):
124        ##print "HERE",self.rule,self._ndim
125        if self.rule is not None:
126           if self._ndim==0:
127              tmp = self._apply_rule((model,),None)
128              if not tmp is None and not (type(tmp) in [int,long,float] and tmp == 0):
129                 self._data[None].expr = tmp
130           else:
131              _name=self.name
132              for val in self._index:
133                if type(val) is tuple:
134                   tmp = list(val)
135                else:
136                   tmp = [val]
137                tmp.append(model)
138                tmp = tuple(tmp)
139                tmp = self._apply_rule(tmp,val)
140                if not tmp is None and not (type(tmp) in [int,long,float] and tmp == 0):
141                   self._data[val] = ObjectiveData(expr=tmp, name=_name+"["+str(val)+"]")
142           self._index = self._data.keys()
143               
144        for val in self._data:
145            if self._data[val].expr is not None:
146                self._data[val].expr = self._data[val].expr.simplify(model)
147
148    def pprint(self, ostream=None):
149        if ostream is None:
150           ostream = sys.stdout
151        print >>ostream, "  ",self.name,":",
152        print >>ostream, "\tSize="+str(len(self._data.keys())),
153        if isinstance(self._index,_BaseSet):
154           print >>ostream, "\tIndex=",self._index.name
155        else:
156           print >>ostream,""
157        for key in self._data:
158          if not self._data[key].expr is None:
159                print >>ostream, "\t",
160                self._data[key].expr.pprint(ostream)
161                print >>ostream, ""
162        if self._quad_subexpr is not None:
163           print >>ostream, "\tQuadratic sub-expression: ",
164           self._quad_subexpr.pprint(ostream)         
165           print >>ostream, ""
166
167    def display(self, prefix="", ostream=None):
168        if ostream is None:
169           ostream = sys.stdout
170        print >>ostream, prefix+"Objective "+self.name,":",
171        print >>ostream, "  Size="+str(len(self))
172        if None in self._data:
173           print >>ostream, prefix+"  Value="+pyutilib.misc.format_io(self._data[None].expr(exception=False))
174        else:
175           for key in self._data:
176             val = self._data[key].expr(exception=False)
177             print >>ostream, prefix+"  "+str(key)+" : "+str(val)
178
179
180class ConstraintData(NumericValue):
181
182    def __init__(self, name=None):
183        NumericValue.__init__(self,name=name,domain=Reals)
184        self._equality = False
185        self.lower = None
186        self.body = None
187        self.upper = None
188        self.label = None
189        self.id = None
190
191    def __call__(self, exception=True):
192        if self.body is None:
193            return None
194        return self.body()
195
196
197class Constraint(NumericValue):
198    """An object that defines a objective expression"""
199
200    def __init__(self, *args, **kwd):
201        """Construct an objective expression with rule to construct the
202           expression
203        """
204        tmpname="unknown"
205        tmprule=None
206        self._index_set=None
207        self._data = {}
208        if args == ():
209           self._index=[None]
210           self._data[None]=ConstraintData()
211           self._ndim=0
212        else:
213           self._ndim=0
214           for arg in args:
215             self._ndim += arg.dimen
216           if len(args) == 1:
217              self._index=args[0]
218           else:
219              self._index=None
220              self._index_set=args
221        for key in kwd.keys():
222          if key == "name":
223             tmpname=kwd[key]
224          elif key == "rule":
225             tmprule = kwd[key]
226          else:
227             raise ValueError, "Constraint constructor: unknown keyword - "+key
228        NumericValue.__init__(self,name=tmpname)
229        if None in self._data:
230            self._data[None].name=tmpname
231        self.rule = tmprule
232        self._model = None
233
234    def __call__(self, exception=True):
235        if len(self._data) == 0:
236            return None
237        if None in self._index:
238            if self._data[None].body is None:
239                return None
240            return self._data[None].body()
241        if exception:
242            raise ValueError, "Cannot compute the value of an array of constraints"
243
244    def dim(self):
245        return self._ndim
246
247    def __len__(self):
248        return len(self._data.keys())
249
250    def keys(self):
251        return self._index
252
253    def __contains__(self,ndx):
254        return ndx in self._index
255
256    def __getitem__(self,ndx):
257        """This method returns a ConstraintData object.  This object can be
258           coerced to a numeric value using the value() function, or using
259           explicity coercion with float().
260        """
261        if ndx in self._data:
262           return self._data[ndx]
263        raise KeyError, "Unknown index " + str(ndx) + " in constraint " + self.name
264
265    def __iter__(self):
266        return self._index.__iter__()
267
268    def _construct(self, model, data):
269        self._model = model
270       
271        if pyomo.debug("verbose") or pyomo.debug("normal"):     #pragma:nocover
272           print "Construcing constraint "+self.name
273        if self.rule is None:
274           self._index = []
275           self._data={}
276           return
277        constructed_indices=[]
278        #
279        # Local variables for code optimization
280        #
281        _self_name=self.name
282        _self_rule = self.rule
283        #
284        if not None in self._index and len(self._index) > 0 and _self_rule.func_code.co_argcount == 1:
285            if pyomo.debug("verbose"):                            #pragma:nocover
286                print "   Constructing constraint indices with rule that returns a dictionary"
287            constraints = _self_rule(model)
288            for val in constraints:
289                if val not in self._index:
290                    raise IndexError, "The index "+str(val)+" generated by the constructor rule is not valid"
291                constructed_indices.append(val)
292                self._initialize_constraint(val, constraints[val], model, _self_name)
293        else:
294            for val in self._index:
295                if pyomo.debug("verbose"):                            #pragma:nocover
296                    print "   Constructing constraint index "+str(val)
297                if val is None:
298                    expr = _self_rule(model)
299                    if expr is None or (type(expr) in [int,long,float] and expr == 0):
300                        continue
301                else:
302                    if type(val) is tuple:
303                        tmp=list(val)
304                    else:
305                        tmp=[val]
306                    tmp.append(model)
307                    tmp=tuple(tmp)
308                    expr = _self_rule(*tmp)
309                    if expr is None or (type(expr) in [int,long,float] and expr == 0):
310                        continue
311                constructed_indices.append(val)
312                self._initialize_constraint(val, expr, model, _self_name)
313        self._index=constructed_indices
314
315    def _initialize_constraint(self, val, expr, model, _name):
316        #
317        # Ignore an 'empty' constraint
318        #
319        if expr is None:
320            return
321        #
322        # Local variables to optimize runtime performance
323        #
324        _self_data = self._data
325        _self_data[val] = ConstraintData(name=_name+"["+str(val)+"]")
326        if not type(expr) in [tuple,list]:
327            _expr_args0 = expr._args[0]
328            _expr_args1 = expr._args[1]
329        #
330        # If the constructor rule returns an equality expression, then
331        # convert to a 3-tuple
332        #
333        if isinstance(expr,_EqualToExpression):
334            _self_data[val]._equality = True
335            if isinstance(_expr_args0,Expression) or\
336                    isinstance(_expr_args0,Var) or\
337                    isinstance(_expr_args0,_VarValue):
338                tpl = [ _expr_args1,_expr_args0,_expr_args1 ]
339            else:
340                tpl = [ _expr_args0,_expr_args1,_expr_args0 ]
341        #
342        # If the constructor rule returns a greater-than expression, then
343        # convert to a 3-tuple
344        #
345        elif isinstance(expr,_GreaterThanExpression) or\
346                isinstance(expr,_GreaterThanOrEqualExpression):
347
348            if isinstance(_expr_args0,_GreaterThanExpression) or\
349                    isinstance(_expr_args0,_GreaterThanOrEqualExpression):
350                tpl = [ _expr_args0._args[0],_expr_args0._args[1],_expr_args1 ]
351
352            elif isinstance(_expr_args0,_LessThanExpression) or\
353                    isinstance(_expr_args0,_LessThanOrEqualExpression):
354                tpl = [ _expr_args0._args[1],_expr_args0._args[0],_expr_args1 ]
355
356            elif isinstance(_expr_args1,_GreaterThanExpression) or\
357                    isinstance(_expr_args1,_GreaterThanOrEqualExpression):
358                tpl = [ _expr_args0,_expr_args1._args[0],_expr_args1._args[1] ]
359
360            elif isinstance(_expr_args1,_LessThanExpression) or\
361                    isinstance(_expr_args1,_LessThanOrEqualExpression):
362                tpl = [ _expr_args0,_expr_args1._args[1],_expr_args1._args[0] ]
363
364            elif isinstance(_expr_args0,_EqualToExpression) or\
365                    isinstance(_expr_args1,_EqualToExpression):
366                raise ValueError, "Bound error: > expression used with an = expression."
367
368            elif isinstance(_expr_args0,Expression) or\
369                    isinstance(_expr_args0,_VarValue) or\
370                    isinstance(_expr_args0,Var):
371                tpl = [ None,_expr_args0,_expr_args1 ]
372
373            else:
374                tpl = [ _expr_args0,_expr_args1,None ]
375        #
376        # If the constructor rule returns a less-than expression, then
377        # convert to a 3-tuple
378        #
379        elif isinstance(expr,_LessThanExpression) or\
380                isinstance(expr,_LessThanOrEqualExpression):
381
382            if isinstance(_expr_args0,_LessThanExpression) or\
383                    isinstance(_expr_args0,_LessThanOrEqualExpression):
384                tpl = [ _expr_args0._args[0],_expr_args0._args[1],_expr_args1 ]
385
386            elif isinstance(_expr_args0,_GreaterThanExpression) or\
387                    isinstance(_expr_args0,_GreaterThanOrEqualExpression):
388                tpl = [ _expr_args0._args[1],_expr_args0._args[0],_expr_args1 ]
389
390            elif isinstance(_expr_args1,_LessThanExpression) or\
391                    isinstance(_expr_args1,_LessThanOrEqualExpression):
392                tpl = [ _expr_args0,_expr_args1._args[0],_expr_args1._args[1] ]
393
394            elif isinstance(_expr_args1,_GreaterThanExpression) or\
395                    isinstance(_expr_args1,_GreaterThanOrEqualExpression):
396                tpl = [ _expr_args0,_expr_args1._args[1],_expr_args1._args[0] ]
397
398            elif isinstance(_expr_args0,_EqualToExpression) or\
399                    isinstance(_expr_args1,_EqualToExpression):
400                raise ValueError, "Bound error: < expression used with an = expression."+str(expr)+str(expr._args)
401
402            elif isinstance(_expr_args0,Expression) or\
403                    isinstance(_expr_args0,_VarValue) or\
404                    isinstance(_expr_args0,Var):
405                tpl = [ None,_expr_args0,_expr_args1 ]
406
407            else:
408                tpl = [ _expr_args0,_expr_args1,None ]
409        #
410        # If the constructor rule returns a tuple or list, then convert
411        # it into a canonical form
412        #
413        elif type(expr) in [tuple,list]:
414            #
415            # Convert tuples to list
416            #
417            if type(expr) is tuple:
418                expr = list(expr)
419            #
420            # Form equality expression
421            #
422            if len(expr) == 2:
423                _self_data[val]._equality = True
424                if isinstance(expr[0],Expression) or\
425                        isinstance(expr[0],Var) or\
426                        isinstance(expr[0],_VarValue):
427                    tpl = [ expr[1],expr[0],expr[1] ]
428                else:
429                    tpl = [ expr[0],expr[1],expr[0] ]
430            #
431            # Form inequality expression
432            #
433            elif len(expr) == 3:
434                tpl=expr
435            else:
436                raise ValueError, "Constructor rule for constraint "+self.name+" returned a tuple of length "+str(len(expr))
437        #
438        # Unrecognized constraint values
439        #
440        else:
441            raise ValueError, "Constraint \"" + self.name + "\" does not have a proper value:" + str(expr)
442        #
443        # Replace numeric bound values with a NumericConstant object,
444        # and reset the values to 'None' if they are 'infinite'
445        #
446        if type(tpl[0]) in [bool,int,long,float]:
447            if pyutilib.math.is_finite(tpl[0]):
448                tpl[0] = NumericConstant(value=float(tpl[0]))
449            else:
450                tpl[0] = None
451        elif type(tpl[0]) is NumericConstant and not pyutilib.math.is_finite(tpl[0]()):
452                tpl[0] = None
453        if type(tpl[1]) in [bool,int,long,float]:
454            raise ValueError, "Constraint \""+self.name+"\" has a numeric body.  Expecting Expression instance."
455        if _self_data[val]._equality:
456            tpl[2] = tpl[0]
457        elif type(tpl[2]) in [bool,int,long,float]:
458            if pyutilib.math.is_finite(tpl[2]):
459                tpl[2] = NumericConstant(value=float(tpl[2]))
460            else:
461                tpl[2] = None
462        elif type(tpl[2]) is NumericConstant and not pyutilib.math.is_finite(tpl[2]()):
463                tpl[2] = None
464        #
465        # Error check, to ensure that we don't have an equality constraint with
466        # 'infinite' RHS
467        #
468        if (tpl[0] is None or tpl[2] is None) and _self_data[val]._equality:
469            raise ValueError, "Equality constraint "+self.name+" defined with infinity RHS"
470        #
471        # Setup identity expressions
472        #
473        if tpl[0] is not None:
474            if not isinstance(tpl[0],Expression):
475                tpl[0] = _IdentityExpression(tpl[0])
476            tpl[0] = tpl[0].simplify(model)
477        if tpl[1] is not None:
478            if not isinstance(tpl[1],Expression):
479                tpl[1] = _IdentityExpression(tpl[1])
480            tpl[1] = tpl[1].simplify(model)
481        if tpl[2] is not None:
482            if not isinstance(tpl[2],Expression):
483                tpl[2] = _IdentityExpression(tpl[2])
484            tpl[2] = tpl[2].simplify(model)
485        #
486        # Finally, setup the data
487        #
488        _self_data[val].lower = tpl[0]
489        _self_data[val].body = tpl[1]
490        _self_data[val].upper = tpl[2]
491
492
493    def pprint(self, ostream=None):
494        if ostream is None:
495           ostream = sys.stdout
496        print >>ostream, "  ",self.name,":",
497        print >>ostream, "\tSize="+str(len(self._data.keys())),
498        if isinstance(self._index,_BaseSet):
499           print >>ostream, "\tIndex=",self._index.name
500        else:
501           print >>ostream,""
502        for val in self._data:
503          print >>ostream, "\t"+`val`
504          if self._data[val].lower is not None:
505             print >>ostream, "\t\t",
506             self._data[val].lower.pprint(ostream)
507          else:
508             print >>ostream, "\t\t-Inf"
509          if self._data[val].body is not None:
510             print >>ostream, "\t\t",
511             self._data[val].body.pprint(ostream)
512          #else:                         #pragma:nocover
513             #raise ValueError, "Unexpected empty constraint body"
514          if self._data[val].upper is not None:
515             print >>ostream, "\t\t",
516             self._data[val].upper.pprint(ostream)
517          elif self._data[val]._equality:
518             print >>ostream, "\t\t",
519             self._data[val].lower.pprint(ostream)
520          else:
521             print >>ostream, "\t\tInf"
522
523    def display(self, prefix="", ostream=None):
524        if ostream is None:
525           ostream = sys.stdout
526        print >>ostream, prefix+"Constraint "+self.name,":",
527        print >>ostream, "  Size="+str(len(self))
528        if None in self._data:
529           print >>ostream, prefix+"  Value="+pyutilib.misc.format_io(self._data[None].body())
530        else:
531           print >>ostream, prefix+"        \tLower\tBody\t\tUpper"
532           for key in self._data:
533             if self._data[key].lower is not None:
534                lval = str(self._data[key].lower())
535             else:
536                lval = "-Infinity"
537             val = str(self._data[key].body())
538             if self._data[key].upper is not None:
539                uval = str(self._data[key].upper())
540             else:
541                uval = "Infinity"
542             print >>ostream, prefix+"  "+str(key)+" :\t"+lval+"\t"+val+"\t"+uval
543
544
545ComponentRegistration("Objective", Objective, "Expressions that are minimized or maximized in a model.")
546ComponentRegistration("Constraint", Constraint, "Constraint expressions a model.")
Note: See TracBrowser for help on using the repository browser.