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

Revision 4331, 35.8 KB checked in by tekl, 3 years ago (diff)

Add __del__ guard on ConstraintList?.

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__ = ['Constraint', 'SOSConstraint', 'ConstraintBase', 'ConstraintList', 'simple_constraint_rule', 'simple_constraintlist_rule']
12
13import sys
14import logging
15import weakref
16
17from expr import *
18from indexed_component import IndexedComponent
19from misc import create_name, apply_indexed_rule
20from numtypes import *
21from numvalue import *
22from label import *
23from pyutilib.component.core import alias
24from sets import _BaseSet, _SetContainer, _ProductSet, Set
25from var import Var, _VarValue, _VarArray, _VarElement
26from objective import Objective
27import pyutilib.math
28import pyutilib.misc
29
30from component import Component
31from set_types import *
32from sets import _SetContainer
33
34logger = logging.getLogger('coopr.pyomo')
35
36
37def simple_constraint_rule( fn ):
38    """This is a decorator that translates None/True/False return values
39    into Constraint.Skip/Constraint.Feasible/Constraint.Infeasible.
40    This supports a simpler syntax in constraint rules, though these can be
41    more difficult to debug when errors occur.
42
43    Example use:
44
45    @simple_constraint_rule
46    def C_rule(model, i, j):
47        ...
48    """
49
50    def wrapper_function ( *args, **kwargs ):
51        value = fn( *args, **kwargs )
52        if value is None:
53            return Constraint.Skip
54        elif value is True:
55            return Constraint.Feasible
56        elif value is False:
57            return Constraint.Infeasible
58        return value
59    return wrapper_function
60
61
62def simple_constraintlist_rule( fn ):
63    """This is a decorator that translates None/True/False return values
64    into ConstraintList.End/Constraint.Feasible/Constraint.Infeasible.
65    This supports a simpler syntax in constraint rules, though these can be
66    more difficult to debug when errors occur.
67
68    Example use:
69
70    @simple_constraintlist_rule
71    def C_rule(model, i, j):
72        ...
73    """
74
75    def wrapper_function ( *args, **kwargs ):
76        value = fn( *args, **kwargs )
77        if value is None:
78            return ConstraintList.End
79        elif value is True:
80            return Constraint.Feasible
81        elif value is False:
82            return Constraint.Infeasible
83        return value
84    return wrapper_function
85
86
87
88class ConstraintBase(IndexedComponent):
89    """
90    Abstract base class for all constraint types. Keeps track of how many
91    constraint objects are in existence.
92    """
93
94    alias("ConstraintBase", "Abstract base class for all model constraints")
95
96    # The number of explicit constraints declared.
97    _nExplicitConstraints = 0
98
99    def __init__(self, *args, **kwargs):
100        ctype = {'ctype': kwargs.pop("ctype", None)}
101        IndexedComponent.__init__(self, *args, **ctype)
102
103        tmpname  = kwargs.pop('name', 'unknown')
104        self.doc = kwargs.pop('doc', None )
105
106        # Increment the class count
107        ConstraintBase._inc_count()
108
109        self._data = {}
110
111    def dim(self):
112        return self._ndim
113
114    def __len__(self):
115        return len(self._data.keys())
116
117    def keys(self):
118        return self._data.keys()
119        #return self._index
120
121    def __contains__(self,ndx):
122        return ndx in self._data
123        #return ndx in self._index
124
125    def __getitem__(self, ndx):
126        """This method returns a ConstraintData object.  This object can be
127           coerced to a numeric value using the value() function, or using
128           explicity coercion with float().
129        """
130        if ndx in self._data:
131            return self._data[ndx]
132        msg = "Unknown index in constraint '%s': %s"
133        raise KeyError, msg % ( self.name, str(ndx) )
134
135    def __iter__(self):
136        return self._data.keys().__iter__()
137
138    def __del__(self):
139        """ Remove this constraint from the count """
140        self._inc_count(-1)
141        for k in self._data.keys():
142            del self._data[k]
143
144    @classmethod
145    def _inc_count(cls, n=1):
146        """ Alter the constraint count """
147        cls._nExplicitConstraints += n
148
149    @classmethod
150    def num_explicit_constraints(cls):
151        return cls._nExplicitConstraints
152
153
154class ConstraintData(NumericValue):
155
156    __slots__ = ('con','label','id','active','_equality','lower',
157                 'lower_ineq_strict','lin_body','body','upper',
158                 'upper_ineq_strict','repn','ampl_repn','dual', 'index')
159
160    def __del__(self):
161        if NumericValue is not None:
162            NumericValue.__del__(self)
163        del self.con
164        del self.value
165
166    def __init__(self, name, con):
167
168        # IMPT: The following three lines are equivalent to calling the
169        #       basic NumericValue constructor, i.e., as follows:
170        #       NumericValue.__init__(self,name,Reals,None,False)
171        #       That particular constructor call takes a lot of time
172        #       for big models, and is unnecessary because we're not
173        #       validating any values. (Arguably, "domain" isn't even
174        #       needed.)
175        self.name = name
176        self.domain = Reals
177        self.value = None
178        self.index = None
179
180        # the parent constraint object.
181        self.con = weakref.ref(con)
182
183        # the label is a modification of the input name, with certain symbols
184        # that are typically problematic for solver input files re-mapped (e.g.,
185        # curly braces) to more friendly symbols. create a default label right
186        # away - it can be modified if a particular solver needs it.
187        # self.label = None (is set one way or another via the following method)
188        self.sync_label()
189
190        # TBD: Document where this gets populated.
191        self.id = None
192
193        # TBD: Mirroring the variable class, the constraint data class should really "know" its index and its parent constraint.
194
195        # is this variable an active component of the current model?
196        self.active = True
197
198        self._equality = False
199        self.lower = None
200        self.lower_ineq_strict = None
201        self.lin_body = None # a linear compilation of the source expressions tree.
202        self.body = None # an expression tree encoding of the constraint body. if None, an alternative representation (e.g., lin_body) will be != None.
203        self.upper = None
204        self.upper_ineq_strict = None
205
206        self.repn = None
207        self.ampl_repn = None
208
209        self.dual = None
210
211    def __getstate__(self):
212        result = NumericValue.__getstate__(self)
213        for i in ConstraintData.__slots__:
214            result[i] = getattr(self, i)
215        return result
216
217    def sync_label(self):
218        my_name = self.name
219        if my_name is not None:
220            self.label = label_from_name(my_name)
221            parent_constraint = self.con()
222            if (parent_constraint is not None) and (parent_constraint.model is not None):
223                parent_constraint.model()._label_constraint_map[self.label] = self
224        else:
225            self.label = None
226
227    def __call__(self, exception=True):
228        if self.body is None:
229            return None
230        return self.body()
231
232    def activate(self):
233        self.active=True
234
235    def deactivate(self):
236        self.active=False
237
238
239class Constraint(ConstraintBase, NumericValue):
240    """An object that defines a objective expression"""
241
242    alias("Constraint", "Constraint expressions in a model.")
243
244    NoConstraint    = (1000,)
245    Skip            = (1000,)
246    Infeasible      = (1001,)
247    Violated        = (1001,)
248    Feasible        = (1002,)
249    Satisfied       = (1002,)
250
251    def __del__(self):
252        if ConstraintBase is not None:
253            ConstraintBase.__del__(self)
254        if NumericValue is not None:
255            NumericValue.__del__(self)
256        if 'rule' in self.__dict__.keys() and self.rule is not None:
257            del self.rule
258
259    def __init__(self, *args, **kwargs):
260        """
261        Construct an objective expression with rule to construct the
262        expression
263
264        keyword arguments:
265        name: name of this object
266        rule: function or rule definition of constraint
267        expr: same as rule
268        doc:  documentation string for constraint
269        """
270        # See if 'ctype' was passed as a keyword argument;
271        # this allows derived classses to alert Pyomo to
272        # their existence through super() calls. If not,
273        # pass Constraint
274        tkwd = {'ctype': kwargs.pop('ctype', Constraint)}
275
276        # Pass some arguments to ConstraintBase
277        tkwd['doc'] = kwargs.pop('doc', None)
278        tkwd['name'] = kwargs.get('name', 'unknown')
279
280        ConstraintBase.__init__(self, *args, **tkwd)
281
282        tmpname = kwargs.pop('name', 'unknown')
283        tmprule = kwargs.pop('rule', None )
284        tmprule = kwargs.pop('expr', tmprule )
285
286        self._no_rule_init = kwargs.pop('noruleinit', None )
287
288        if ( kwargs ): # if dict not empty, there's an error.  Let user know.
289            msg = "Creating constraint '%s': unknown option(s)\n\t%s"
290            msg = msg % ( tmpname, ', '.join(kwargs.keys()) )
291            raise ValueError, msg
292
293        # _no_rule_init is a flag specified by the user to indicate that no
294        # construction rule will be specified, and that constraints will be
295        # explicitly added by the user. set via the "noruleinit" keyword. value
296        # doesn't matter, as long as it isn't "None".
297
298        self._data = {}
299
300        NumericValue.__init__(self,tmpname,None,None,True)
301
302        if None in self._data:
303            # As far as I can tell, this never executes? 2010 Jun 08
304            self._data[None].name=tmpname
305        self.rule = tmprule
306        self.trivial = False # True if all constraint indicies have trivial expressions.
307
308    def __getstate__(self):
309        result = NumericValue.__getstate__(self)
310        for key,value in self.__dict__.iteritems():
311            result[key]=value
312        return result
313
314    def clear(self):
315        self._data = {}
316
317    def __call__(self, exception=True):
318        if len(self._data) == 0:
319            return None
320        if None in self._data:
321            if self._data[None].body is None:
322                return None
323            return value(self._data[None].body)
324        if exception:
325            msg = 'Cannot compute the value of an array of constraints'
326            raise ValueError, msg
327
328
329    def construct(self, data=None):
330
331        # cache the debug generation flag, to avoid the expense of
332        # calling isEnabledFor() for each index - this is far too
333        # expensive an operation to perform deep in a loop. the only
334        # potential down-side is that the debug logging is either
335        # disabled or enabled globally for the duration of this
336        # method invocation - which doesn't seem like much of
337        # (if any) limitation in practice.
338        generate_debug_messages = (__debug__ is True) and (logger.isEnabledFor(logging.DEBUG) is True)
339
340        if generate_debug_messages is True:
341            logger.debug("Constructing constraint %s",self.name)
342
343        if (self._no_rule_init is not None) and (self.rule is not None):
344            logger.warn("noruleinit keyword is being used in conjunction with "
345                        "rule keyword for constraint '%s'; defaulting to "
346                        "rule-based construction.", self.name)
347        if self.rule is None:
348            if self._no_rule_init is None:
349                logger.warn("No construction rule or expression specified for "
350                            "constraint '%s'", self.name)
351            return
352        if self._constructed:
353            return
354        self._constructed=True
355
356        #
357        # Local variables for code optimization
358        #
359        _self_rule = self.rule
360        _self_model = self.model()
361
362        #
363        if None in self._index:
364            if len(self._index) != 1:
365                raise IndexError, "Internal error: constructing constraint "\
366                    "with both None and Index set"
367            if generate_debug_messages:
368                logger.debug("  Constructing single constraint (index=None)")
369            if isinstance(_self_rule,Expression):
370                expr = _self_rule
371            else:
372                expr = _self_rule(_self_model)
373            self.add(None, expr)
374        else:
375            if isinstance(_self_rule,Expression):
376                raise IndexError, "Cannot define multiple indices in a " \
377                    "constraint with a single expression"
378            for index in self._index:
379                if generate_debug_messages:
380                    logger.debug("  Constructing constraint index "+str(index))
381                self.add( index, apply_indexed_rule( self, _self_rule,
382                                                     _self_model, index ) )
383
384    def add(self, index, expr):
385        # index: the constraint index (should probably be renamed)
386        # expr: the constraint expression
387
388        # Convert deprecated expression values
389        if expr is None:
390            logger.warning("DEPRECATION WARNING: Expression defined using None instead of Constraint.Skip - constraint="+create_name(self.name, index))
391            expr = Constraint.Skip
392        if expr is True:
393            logger.warning("DEPRECATION WARNING: Expression defined using True instead of Constraint.Feasible - constraint="+create_name(self.name, index))
394            expr = Constraint.Feasible
395        if expr is False:
396            logger.warning("DEPRECATION WARNING: Expression defined using False instead of Constraint.Infeasible - constraint="+create_name(self.name, index))
397            expr = Constraint.Infeasible
398        #
399        expr_type = type(expr)
400        #
401        # Ignore an 'empty' constraint
402        #
403        if expr_type is tuple and len(expr) == 1:
404            if expr == Constraint.Skip or expr == Constraint.Feasible:
405                return
406            if expr == Constraint.Infeasible:
407                raise ValueError, "Constraint '%s' is always infeasible" % create_name(self.name,index)
408        #
409        #
410        # Local variables to optimize runtime performance
411        #
412        conData = ConstraintData(create_name(self.name,index), self)
413        conData.index = index
414        conData.lower_ineq_strict = conData.upper_ineq_strict = False
415
416        if expr_type is tuple: # or expr_type is list:
417            #
418            # Form equality expression
419            #
420            if len(expr) == 2:
421                arg0 = expr[0]
422                if arg0 is not None:
423                    arg0 = as_numeric(arg0)
424                arg1 = expr[1]
425                if arg1 is not None:
426                    arg1 = as_numeric(arg1)
427
428                conData._equality = True
429                if arg1 is None or arg1.is_constant():
430                    conData.lower = conData.upper = arg1
431                    conData.body = arg0
432                elif arg0 is None or arg0.is_constant():
433                    conData.lower = conData.upper = arg0
434                    conData.body = arg1
435                else:
436                    conData.lower = conData.upper = NumericConstant(None,None,0)
437                    conData.body = arg0 - arg1
438            #
439            # Form inequality expression
440            #
441            elif len(expr) == 3:
442                arg0 = expr[0]
443                if arg0 is not None:
444                    arg0 = as_numeric(arg0)
445                    if not arg0.is_constant():
446                        msg = "Constraint '%s' found a 3-tuple (lower, " \
447                              "expression, upper) but the lower value was "\
448                              "non-constant"
449                        raise ValueError, msg % (conData.name,)
450
451                arg1 = expr[1]
452                if arg1 is not None:
453                    arg1 = as_numeric(arg1)
454
455                arg2 = expr[2]
456                if arg2 is not None:
457                    arg2 = as_numeric(arg2)
458                    if not arg2.is_constant():
459                        msg = "Constraint '%s' found a 3-tuple (lower, " \
460                              "expression, upper) but the upper value was "\
461                              "non-constant"
462                        raise ValueError, msg % (conData.name,)
463
464                conData.lower = arg0
465                conData.body  = arg1
466                conData.upper = arg2
467            else:
468                msg = "Constructor rule for constraint '%s' returned a tuple" \
469                      ' of length %d.  Expecting a tuple of length 2 or 3:\n' \
470                      'Equality:   (left, right)\n' \
471                      'Inequality: (lower, expression, upper)'
472                raise ValueError, msg % ( self.name, len(expr) )
473
474            relational_expr = False
475        else:
476            try:
477                relational_expr = expr.is_relational()
478                if not relational_expr:
479                    msg = "Constraint '%s' does not have a proper value.  " \
480                          "Found '%s'\nExpecting a tuple or equation.  " \
481                          "Examples:\n" \
482                          "    summation( model.costs ) == model.income\n" \
483                          "    (0, model.price[ item ], 50)"
484                    raise ValueError, msg % ( conData.name, str(expr) )
485            except:
486                msg = "Constraint '%s' does not have a proper value.  " \
487                      "Found '%s'\nExpecting a tuple or equation.  " \
488                      "Examples:\n" \
489                      "    summation( model.costs ) == model.income\n" \
490                      "    (0, model.price[ item ], 50)" \
491                      % ( conData.name, str(expr) )
492                if type(expr) is bool:
493                    msg +="""
494Note: constant Boolean expressions are not valid constraint expressions.
495Some apparently non-constant compound inequalities (e.g. "expr >= 0 <= 1")
496can return boolean values; the proper form for compound inequalities is
497always "lb <= expr <= ub"."""
498                raise ValueError, msg
499
500        #
501        # Special check for chainedInequality errors like "if var < 1:"
502        # within rules.  Catching them here allows us to provide the
503        # user with better (and more immediate) debugging information.
504        # We don't want to check earlier because we want to provide a
505        # specific debugging message if the construction rule returned
506        # True/False; for example, if the user did ( var < 1 > 0 )
507        # (which also results in a non-None chainedInequality value)
508        #
509        if generate_relational_expression.chainedInequality is not None:
510            from expr import chainedInequalityErrorMessage
511            raise TypeError, chainedInequalityErrorMessage()
512
513        #
514        # Process relational expressions (i.e. explicit '==', '<', and '<=')
515        #
516        if relational_expr:
517            if expr_type is _EqualityExpression:
518                # Equality expression: only 2 arguments!
519                conData._equality = True
520                if expr._args[1].is_constant():
521                    conData.lower = conData.upper = expr._args[1]
522                    conData.body = expr._args[0]
523                elif expr._args[0].is_constant():
524                    conData.lower = conData.upper = expr._args[0]
525                    conData.body = expr._args[1]
526                else:
527                    conData.lower = conData.upper = NumericConstant(None,None,0)
528                    conData.body = generate_expression('_','sub', expr._args[0], expr._args[1])
529            else:
530                # Inequality expression: 2 or 3 arguments
531                if len(expr._args) == 3:
532                    if not expr._args[0].is_constant():
533                        msg = "Constraint '%s' found a double-sided "\
534                              "inequality expression (lower <= expression "\
535                              "<= upper) but the lower bound was non-constant"
536                        raise ValueError, msg % (conData.name,)
537                    if not expr._args[2].is_constant():
538                        msg = "Constraint '%s' found a double-sided "\
539                              "inequality expression (lower <= expression "\
540                              "<= upper) but the upper bound was non-constant"
541                        raise ValueError, msg % (conData.name,)
542                    conData.lower = expr._args[0]
543                    conData.body  = expr._args[1]
544                    conData.upper = expr._args[2]
545                    conData.lower_ineq_strict = expr._strict[0]
546                    conData.upper_ineq_strict = expr._strict[1]
547                else:
548                    if expr._args[1].is_constant():
549                        conData.lower = None
550                        conData.body  = expr._args[0]
551                        conData.upper = expr._args[1]
552                        conData.upper_ineq_strict = expr._strict[0]
553                    elif expr._args[0].is_constant():
554                        conData.lower = expr._args[0]
555                        conData.body  = expr._args[1]
556                        conData.upper = None
557                        conData.lower_ineq_strict = expr._strict[0]
558                    else:
559                        conData.lower = None
560                        conData.body  = generate_expression('_','sub', expr._args[0], expr._args[1])
561                        conData.upper = NumericConstant(None,None,0)
562                        conData.upper_ineq_strict = expr._strict[0]
563
564        #
565        # Replace numeric bound values with a NumericConstant object,
566        # and reset the values to 'None' if they are 'infinite'
567        #
568        if conData.lower is not None:
569            val = conData.lower()
570            if not pyutilib.math.is_finite(val):
571                if val > 0:
572                    msg = "Constraint '%s' created with a +Inf lower bound"
573                    raise ValueError, msg % ( conData.name, )
574                conData.lower = None
575                conData.lower_ineq_strict = False
576            elif bool(val > 0) == bool(val <= 0):
577                msg = "Constraint '%s' created with a non-numeric lower bound"
578                raise ValueError, msg % ( conData.name, )
579        if conData.upper is not None:
580            val = conData.upper()
581            if not pyutilib.math.is_finite(val):
582                if val < 0:
583                    msg = "Constraint '%s' created with a -Inf upper bound"
584                    raise ValueError, msg % ( conData.name, )
585                conData.upper = None
586                conData.upper_ineq_strict = False
587            elif bool(val > 0) == bool(val <= 0):
588                msg = "Constraint '%s' created with a non-numeric upper bound"
589                raise ValueError, msg % ( conData.name, )
590
591        # Error check, to ensure that we don't have a constraint that
592        # doesn't depend on any variables / parameters
593        #
594        # TBD: This is an expensive test (is_constant() is recursive).
595        # The pre-coopr-2.5 version of this test was basically never
596        # executed (just looked for int, float, etc.)  It is not clear
597        # that running this debugging test is even worth it [JDS, 28 Feb 2011].
598        #
599        #if conData.body.is_constant():
600        #    feasible = True
601        #    value = conData.body()
602        #    if conData.lower is not None:
603        #        if conData.lower_ineq_strict:
604        #            feasible = feasible and (conData.lower() < value)
605        #        else:
606        #            feasible = feasible and (conData.lower() <= value)
607        #    if conData.upper is not None:
608        #        if conData.upper_ineq_strict:
609        #            feasible = feasible and (value < conData.upper())
610        #        else:
611        #            feasible = feasible and (value < conData.upper())
612        #    if feasible:
613        #        logger.warn("Constraint '%s' has a constant constant body.")
614        #    else:
615        #        logger.error("Constraint '%s' has an constant infeasible "
616        #                     "constant body.")
617
618        # Error check, to ensure that we don't have an equality constraint with
619        # 'infinite' RHS
620        #
621        if conData._equality:
622            if conData.lower != conData.upper: #pragma:nocover
623                msg = "Equality constraint '%s' has non-equal lower and "\
624                      "upper bounds (this is indicitive of a SERIOUS "\
625                      "internal error in Pyomo)."
626                raise RuntimeError, msg % self.name
627            if conData.lower is None:
628                msg = "Equality constraint '%s' defined with non-finite term"
629                raise ValueError, msg % self.name
630
631        #
632        # hook up the constraint data object to the parent constraint.
633        #
634        self._data[index] = conData
635
636        #
637        # update the parent model's (if any) label->constraint map.
638        #
639        if self.model is not None:
640            self.model()._label_constraint_map[conData.label] = conData
641
642
643    def pprint(self, ostream=None):
644        if ostream is None:
645            ostream = sys.stdout
646        print >>ostream, "  ",self.name,":",
647        print >>ostream, "\tSize="+str(len(self._data.keys())),
648        if isinstance(self._index,_BaseSet):
649            print >>ostream, "\tIndex=",self._index.name
650        else:
651            print >>ostream,""
652        for val in self._data:
653            if not val is None:
654                print >>ostream, "\t"+`val`
655            if self._data[val].lower is not None:
656                print >>ostream, "\t\t",
657                if self._data[val].lower.is_expression():
658                    self._data[val].lower.pprint(ostream)
659                else:
660                    print >>ostream, str(self._data[val].lower)
661            else:
662                print >>ostream, "\t\t-Inf"
663            if self._data[val].lower_ineq_strict:
664                print >>ostream, "\t\t<"
665            else:
666                print >>ostream, "\t\t<="
667            if self._data[val].body is not None:
668                print >>ostream, "\t\t",
669                if self._data[val].body.is_expression():
670                    self._data[val].body.pprint(ostream)
671                else:
672                    print >>ostream, str(self._data[val].body)
673            #else:                         #pragma:nocover
674                #raise ValueError, "Unexpected empty constraint body"
675            if self._data[val].upper_ineq_strict:
676                print >>ostream, "\t\t<"
677            else:
678                print >>ostream, "\t\t<="
679            if self._data[val].upper is not None:
680                print >>ostream, "\t\t",
681                if self._data[val].upper.is_expression():
682                    self._data[val].upper.pprint(ostream)
683                else:
684                    print >>ostream, str(self._data[val].upper)
685            elif self._data[val]._equality:
686                print >>ostream, "\t\t",
687                if self._data[val].lower.is_expression():
688                    self._data[val].lower.pprint(ostream)
689                else:
690                    print >>ostream, str(self._data[val].lower)
691            else:
692                print >>ostream, "\t\tInf"
693
694    def display(self, prefix="", ostream=None):
695        if ostream is None:
696            ostream = sys.stdout
697        print >>ostream, prefix+"Constraint "+self.name,":",
698        print >>ostream, "  Size="+str(len(self))
699        if None in self._data:
700            if self._data[None].body is None:
701                val = 'none'
702            else:
703                val = pyutilib.misc.format_io(self._data[None].body())
704            print >>ostream, '%s  Value=%s' % (prefix, val)
705        else:
706            flag=True
707            for key in self._data:
708                if not self._data[key].active:
709                    continue
710                if flag:
711                    print >>ostream, prefix+"        \tLower\tBody\t\tUpper"
712                    flag=False
713                if self._data[key].lower is not None:
714                    lval = str(self._data[key].lower())
715                else:
716                    lval = "-Infinity"
717                val = str(self._data[key].body())
718                if self._data[key].upper is not None:
719                    uval = str(self._data[key].upper())
720                else:
721                    uval = "Infinity"
722                print >>ostream, "%s  %s :\t%s\t%s\t%s" % (
723                                 prefix, str(key), lval, val, uval )
724            if flag:
725                print >>ostream, prefix+"  None active"
726
727
728
729class ConstraintList(Constraint):
730    """
731    A constraint component that represents a list of constraints.  Constraints can
732    be indexed by their index, but when they are added an index value is not specified.
733    """
734
735    alias("ConstraintList", "A list of constraints in a model.")
736
737    End             = (1003,)
738
739    def __del__(self):
740        if Constraint is not None:
741            Constraint.__del__(self)
742
743    def __init__(self, *args, **kwargs):
744        if len(args) > 0:
745            raise ValueError, "Cannot specify indices for a ConstraintList object"
746
747        # Construct parents
748        kwargs['ctype'] = kwargs.get('ctype', ConstraintList)
749        self._hidden_index = Set()
750        self._nconstraints = 0
751        targs = [self._hidden_index]
752        Constraint.__init__(self, *targs, **kwargs)
753        #self._no_rule_init = kwargs.pop('noruleinit', None )
754        #tmprule = kwargs.pop('rule', None )
755        #tmprule = kwargs.pop('expr', tmprule )
756
757    def construct(self, *args, **kwds):
758        self._hidden_index.construct()
759        Constraint.construct(self, *args, **kwds)
760
761    def construct(self, data=None):
762        #
763        if __debug__:
764            logger.debug("Constructing constraint %s",self.name)
765        if (self._no_rule_init is not None) and (self.rule is not None):
766            msg = 'WARNING: noruleinit keyword is being used in conjunction ' \
767                  "with rule keyword for constraint '%s'; defaulting to "     \
768                  'rule-based construction.'
769            print msg % self.name
770        if self.rule is None:
771            return
772        if self._constructed:
773            return
774        self._constructed=True
775        #
776        # Local variables for code optimization
777        #
778        _self_rule = self.rule
779        _self_model = self.model()
780        #
781        while True:
782            val = self._nconstraints + 1
783            if __debug__:
784                if logger.isEnabledFor(logging.DEBUG):
785                    logger.debug("   Constructing constraint index "+str(val))
786            expr = apply_indexed_rule( self, _self_rule, _self_model, val )
787            if expr is None:
788                logger.warning("DEPRECATION WARNING: Constraint rule returned None instead of ConstraintList.End")
789                break
790            if (expr.__class__ is tuple and expr == ConstraintList.End) or \
791               (type(expr) in (int, long, float) and expr == 0):
792                break
793            self.add(expr)
794
795    def add(self, *args):
796        self._nconstraints += 1
797        targs = [self._nconstraints] + list(args)
798        Constraint.add(self, *targs)
799
800
801
802class SOSConstraint(ConstraintBase):
803    """
804    Represents an SOS-n constraint.
805
806    Usage:
807    model.C1 = SOSConstraint(
808                             [...],
809                             var=VAR,
810                             [set=SET OR index=SET],
811                             [sos=N OR level=N]
812                             )
813        [...] Any number of sets used to index SET
814        VAR   The set of variables making up the SOS. Indexed by SET.
815        SET   The set used to index VAR. SET is optionally indexed by
816              the [...] sets. If SET is not specified, VAR is indexed
817              over the set(s) it was defined with.
818        N     This constraint is an SOS-N constraint. Defaults to 1.
819
820    Example:
821
822      model = AbstractModel()
823      model.A = Set()
824      model.B = Set(A)
825      model.X = Set(B)
826
827      model.C1 = SOSConstraint(model.A, var=model.X, set=model.B, sos=1)
828
829    This constraint actually creates one SOS-1 constraint for each
830    element of model.A (e.g., if |A| == N, there are N constraints).
831    In each constraint, model.X is indexed by the elements of
832    model.D[a], where 'a' is the current index of model.A.
833
834      model = AbstractModel()
835      model.A = Set()
836      model.X = Var(model.A)
837
838      model.C2 = SOSConstraint(var=model.X, sos=2)
839
840    This produces exactly one SOS-2 constraint using all the variables
841    in model.X.
842    """
843
844
845    alias("SOSConstraint", "SOS constraint expressions in a model.")
846
847    def __init__(self, *args, **kwargs):
848        name = kwargs.get('name', 'unknown')
849
850        # Get the 'var' parameter
851        sosVars = kwargs.pop('var', None)
852
853        # Get the 'set' or 'index' parameters
854        if 'set' in kwargs and 'index' in kwargs:
855            raise TypeError, "Specify only one of 'set' and 'index' -- " \
856                  "they are equivalent parameters"
857        sosSet = kwargs.pop('set', None)
858        sosSet = kwargs.pop('index', sosSet)
859
860        # Get the 'sos' or 'level' parameters
861        if 'sos' in kwargs and 'index' in kwargs:
862            raise TypeError, "Specify only one of 'sos' and 'level' -- " \
863                  "they are equivalent parameters"
864        sosLevel = kwargs.pop('sos', None)
865        sosLevel = kwargs.pop('level', sosLevel)
866
867        # Make sure sosLevel has been set
868        if sosLevel is None:
869            raise TypeError, "SOSConstraint() requires that either the " \
870                  "'sos' or 'level' keyword arguments be set to indicate " \
871                  "the type of SOS."
872
873        # Make sure we have a variable
874        if sosVars is None:
875            raise TypeError, "SOSConstraint() requires the 'var' keyword " \
876                  "be specified"
877
878        # Find the default sets for sosVars if sosSets is None
879        if sosSet is None:
880            sosSet = sosVars.index()
881
882        # Construct parents
883        kwargs['ctype'] = kwargs.get('ctype', SOSConstraint)
884        ConstraintBase.__init__(self, *args, **kwargs)
885
886        # Set member attributes
887        self._sosVars = sosVars
888        self._sosSet = sosSet
889        self._sosLevel = sosLevel
890
891        # TODO figure out why exactly the code expects this to be defined
892        # likely due to Numericvalue
893        self.domain = None
894
895        # TODO should variables be ordered?
896
897    def construct(self, *args, **kwds):
898        """
899        A quick hack to call add after data has been loaded. construct
900        doesn't actually need to do anything.
901        """
902        self.add()
903
904    def add(self, *args):
905        """
906        Mimics Constraint.add, but is only used to alert preprocessors
907        to the existence of the SOS variables.
908
909        Ignores all arguments passed to it.
910        """
911
912        # self._data is a ConstraintData object, which has a member
913        # .body.  .body needs to have a 3-tuple of expressions
914        # containing the variables that are referenced by this
915        # constraint. We only use one index, None. I have no
916        # particular reason to use None, it just seems consistent with
917        # what Constraint objects do.
918
919        # For simplicity, we sum over the variables.
920
921        vars = self.sos_vars()
922
923        expr = sum(vars[i] for i in vars._index)
924
925        self._data[None] = ConstraintData(create_name(self.name, None), self)
926        self._data[None].body = expr
927
928    def sos_level(self):
929        """ Return n, where this class is an SOS-n constraint """
930        return self._sosLevel
931
932    def sos_vars(self):
933        """ Return the variables in the SOS """
934        return self._sosVars
935
936    def sos_set(self):
937        """ Return the set used to index the variables """
938        return self._sosSet
939
940    def sos_set_set(self):
941        """ Return the sets used to index the sets indexing the variables """
942        return self._index
943
944    def pprint(self, ostream=None):
945        if ostream is None:
946            ostream = sys.stdout
947        print >>ostream, "  ",self.name,":",
948        print >>ostream, "Type="+str(self._sosLevel)
949        print >>ostream, "\tVariable: "+self._sosVars.name
950        print >>ostream, "\tIndices: ",
951        for i in self._sosSet.value:
952            print >>ostream, str(i)+" ",
953        print >>ostream, ""
Note: See TracBrowser for help on using the repository browser.