source: trunk/coopr/pyomo/base/constraint.py @ 1528

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

Preliminary (interim) commit of extensive-form writer in pysp. Required a few minor mods to the pyomo var/constraint core classes, specifically the attributes for the parent model.

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