source: coopr.pyomo/trunk/coopr/pyomo/base/sets.py @ 2369

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

Adding functionality for ordered sets.

File size: 36.3 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11__all__ = ['Set', '_BaseSet', '_SetContainer', '_SetArray', '_ProductSet', 'set_options']
12
13import pyutilib.component.core
14from coopr.pyomo.base.plugin import ComponentRegistration
15from component import Component
16import pyutilib.misc
17import sys
18import types
19import copy
20import bidict
21
22log = pyutilib.component.core.PluginGlobals.env().log
23
24#
25# Decorator for set initializer functions
26#
27def set_options(**kwds):
28    def decorator(func):
29        func.set_options = kwds
30        return func
31    return decorator
32
33
34##------------------------------------------------------------------------
35##
36## An abstract Set class
37##
38##------------------------------------------------------------------------
39
40class _BaseSet(Component):
41    """The base class for set objects that are used to index other Pyomo objects
42
43       This class has a similar look-and-feel as a built-in set class.  However,
44       the set operations defined in this class return another abstract
45       Set object.  This class contains a concrete set, which can be
46       initialized by the load() method.
47    """
48
49    """ Constructor
50        Arguments:
51           name          The name of the set
52           within        A set that defines the type of values that can
53                          be contained in this set
54           rule
55           initialize    Default set members, which may be overriden
56                          when setting up this set
57           validate      Define a function for validating membership in a
58                          set.  This has the functional form:
59                          f: data -> bool
60                          and returns true if the data belongs in the set
61           dimen         Specify the set's arity.
62           doc           Documentation for this object
63           virtual       Indicate that this is a virtual set that does not
64                            have concrete members.
65    """
66    def __init__(self, **kwds):
67        Component.__init__(self, ctype=Set)
68        self.initialize=None
69        self.name="_unknown_"
70        self.validate=None
71        self.ordered=False
72        self.order=[]
73        self.order_dict = bidict.bidict()
74        self.domain=None
75        self.dimen = None
76        self.virtual = False
77        self._bounds=None
78        self.doc = ""
79        for key in kwds.keys():
80          if key == "dimen":
81             pass
82          elif key == "doc":
83             self.doc=kwds[key]
84          elif key == "virtual":
85             self.virtual=kwds[key]
86          elif key == "bounds":
87             self._bounds=kwds[key]
88          elif key == "within":
89             self.domain = kwds[key]
90             if self.domain is not None:
91                self.dimen = self.domain.dimen
92          elif key == "rule":
93             setattr(self,"initialize",kwds[key])
94          elif key in ["name", "initialize", "validate", "ordered"]:
95             setattr(self,key,kwds[key])
96          else:
97             raise ValueError, "Unknown option '"+key+"' when constructing Set "
98        if "dimen" in kwds.keys():
99           if self.within is not None and self.dimen != kwds["dimen"]:
100              raise ValueError, "Option dimen "+str(kwds["dimen"])+" is different from the dimen of superset "+self.within.name+": "+str(self.dimen)
101           self.dimen = kwds["dimen"]
102        if self.dimen is None:
103            # We set the default to 1
104            self.dimen=1
105        if self.initialize is not None:
106           #
107           # Convert generators to lists, since a generator is not copyable
108           #
109           if type(self.initialize) is types.GeneratorType:
110              self.initialize = list(self.initialize)
111           #
112           # Try to guess dimen from the initialize list
113           #
114           tmp=None
115           if type(self.initialize) is tuple:
116              tmp = len(self.initialize)
117           elif type(self.initialize) is list and len(self.initialize) > 0 \
118                and type(self.initialize[0]) is tuple:
119              tmp = len(self.initialize[0])
120           if tmp is not None:
121              if "dimen" in kwds and tmp != self.dimen:
122                 raise ValueError, "Dimension argument differs from the data in the initialize list"
123              else:
124                 self.dimen=tmp
125
126    def reset(self):
127        pass
128
129    def dim(self):
130        return self._ndim
131
132    def display(self, ostream=None):
133        self.pprint(ostream=ostream)
134
135    def __getattr__(self,name):
136        if name is "within":
137           return self.domain
138        raise AttributeError, "Unknown attribute "+name
139
140    def __setattr__(self,name,val):
141        if name is "within":
142           self.domain = val
143        return object.__setattr__(self,name,val)
144
145    def _verify(self,element,use_exception=True):
146        # A utility routine that is used to verify if the element
147        # is valid for this set.
148        if self.domain is not None and element not in self.domain:
149           if use_exception:
150              raise ValueError, "Value "+str(element)+" is not valid for set "+self.name+", because it is not within set "+self.domain.name
151           return False
152        if self.validate is not None and not self.validate(element,self.model):
153           if use_exception:
154              raise ValueError, "Value "+str(element)+" violates the validation rule of set "+self.name
155           return False
156        if self.dimen > 1 and type(element) is not tuple:
157           if use_exception:
158              raise ValueError, "Value "+str(element)+" is not a tuple for set "+self.name+", which has dimen "+str(self.dimen)
159           return False
160        elif self.dimen == 1 and type(element) is tuple:
161           if use_exception:
162              raise ValueError, "Value "+str(element)+" is a tuple for set "+self.name+", which has dimen "+str(self.dimen)
163           return False
164        elif type(element) is tuple and len(element) != self.dimen:
165           if use_exception:
166              raise ValueError, "Value "+str(element)+" does not have dimension "+str(self.dimen)+", which is needed for set "+self.name
167           return False
168        return True
169
170    def bounds(self):
171        """Return bounds information.  The default value is 'None', which
172        indicates that this set does not contain bounds."""
173        return self._bounds
174
175
176class _SetContainer(_BaseSet):
177    """A derived _BaseSet object that contains a single set."""
178
179    def __init__(self, *args, **kwds):
180        """ Constructor """
181        if args != ():
182            raise TypeError, "A _SetContainer expects no arguments"
183        self._index_set=None
184        self._index=[None]
185        self._ndim=0
186        self.value=set()
187        _BaseSet.__init__(self,**kwds)
188
189    def construct(self, values=None):
190        """ Apply the rule to construct values in this set """
191        log.debug("Constructing _SetContainer, name="+self.name+", from data="+repr(values))
192        if self._constructed:
193            return
194        self._constructed=True
195        #
196        # Construct using the values list
197        #
198        if values is not None:
199           if type(self._bounds) is tuple:
200                first=self._bounds[0]
201                last=self._bounds[1]
202           else:
203                first=None
204                last=None
205           all_numeric=True
206           for val in values[None]:
207                if type(val) in [int,float,long]:
208                    if first is None or val<first:
209                        first=val
210                    if last is None or val>last:
211                        last=val
212                else:
213                    all_numeric=False
214                self.add(val)
215           if all_numeric:
216                self._bounds = (first,last)
217        #
218        # Construct using the rule
219        #
220        elif type(self.initialize) is types.FunctionType:
221           if self.model is None:
222              raise ValueError, "Must pass a model in to initialize with a function"
223           if self.initialize.func_code.co_argcount == 1:
224              #
225              # Using a rule of the form f(model) -> iterator
226              #
227              tmp = self.initialize(self.model)
228              for val in tmp:
229                if self.dimen is None:
230                   if type(val) in [tuple,list]:
231                        self.dimen=len(val)
232                   else:
233                        self.dimen=1
234                self.add(val)
235           else:
236              #
237              # Using a rule of the form f(z,model) -> element
238              #
239              ctr=1
240              val = self.initialize(ctr,self.model)
241              if self.dimen is None:
242                   if type(val) in [tuple,list]:
243                        self.dimen=len(val)
244                   else:
245                        self.dimen=1
246              while val is not None:
247                self.add(val)
248                ctr += 1
249                val = self.initialize(ctr,self.model)
250           #
251           # We should be verifying as part of the add() method
252           #
253           #for val in self.value:
254             #self._verify(val)
255        #
256        # Construct using the default values
257        #
258        elif self.initialize is not None:
259              if type(self.initialize) is dict:
260                 raise ValueError, "Cannot initialize set "+self.name+" with dictionary data"
261              if type(self._bounds) is tuple:
262                first=self._bounds[0]
263                last=self._bounds[1]
264              else:
265                first=None
266                last=None
267              all_numeric=True
268              for val in self.initialize:
269                if type(val) in [int,float,long]:
270                    if first is None or val<first:
271                        first=val
272                    if last is None or val>last:
273                        last=val
274                else:
275                    all_numeric=False
276                self.add(val)
277              #print "HERE",self.name,first,last,all_numeric
278              if all_numeric:
279                self._bounds = (first,last)
280
281    def data(self):
282        """The underlying set data."""
283        if self.virtual:
284           raise TypeError, "Cannot access underlying set data for virtual set "+self.name
285        return self.value
286
287    def clear(self):
288        """Remove all elements from the set."""
289        if self.virtual:
290           raise TypeError, "Cannot clear virtual Set object `"+self.name+"'"
291        self.value.clear()
292        self.order_dict=bidict.bidict()
293
294    def check_values(self):
295        """ Verify that the values in this set are valid.
296        """
297        if self.virtual:
298           return
299        for val in self.value:
300          self._verify(val)
301
302    def add(self, *args):
303        """Add one or more elements to a set."""
304        if self.virtual:
305           raise TypeError, "Cannot add elements to virtual set `"+self.name+"'"
306        for val in args:
307          tmp = pyutilib.misc.flatten_tuple(val)
308          self._verify(tmp)
309          try:
310            if tmp in self.value:
311               raise ValueError, "Element "+str(tmp)+" already exists in set "+self.name
312            self.value.add(tmp)
313            if self.ordered:
314               if tmp in self.order_dict:
315                    raise ValueError, "Element "+str(tmp)+" already exists in ordered set "+self.name
316               self.order.append(tmp)
317               self.order_dict[tmp:] = len(self.order)
318               
319
320          except TypeError:
321            raise TypeError, "Problem inserting "+str(tmp)+" into set "+self.name
322
323    def remove(self, element):
324        """Remove an element from the set.
325           If the element is not a member, raise an error.
326        """
327        if self.virtual:
328            raise KeyError, "Cannot remove element `"+str(element)+"' from virtual set "+str(self.name)
329        if element not in self.value:
330            raise KeyError, "Cannot remove element `"+str(element)+"' from set "+str(self.name)
331        self.value.remove(element)
332        if self.ordered:
333            id = self.order_dict[element]-1
334            for i in xrange(id+1,len(self.order)):
335                self.order_dict[self.order[i]] = i-1
336            del self.order[id]
337
338    def discard(self, element):
339        """Remove an element from the set.
340           If the element is not a member, do nothing.
341        """
342        if self.virtual:
343           raise KeyError, "Cannot discard element `"+str(element)+"' from virtual set "+str(self.name)
344        if self.ordered and element in self.value:
345           self.order.remove(element)
346        self.value.discard(element)
347
348    def first(self):
349        if self.virtual:
350            raise TypeError, "Cannot access the first element of virtual set `"+self.name+"'"
351        return self.member(1)
352
353    def last(self):
354        if self.virtual:
355            raise TypeError, "Cannot access the last element of virtual set `"+self.name+"'"
356        return self.member(-1)
357
358    def __getitem__(self, key):
359        return self.member(key)
360
361    def member(self, key):
362        if self.ordered:
363            return self.order_dict.inv[key]
364        #
365        # If the set is not ordered, then we use the intrinsic ordering imposed by the set.
366        # We convert the set to a list and return the specified value.
367        #
368        if key >= 1:
369            return list(self.value)[key-1]
370        elif key < 0:
371            return list(self.value)[key]
372        else:
373            raise IndexError, "Valid index values for sets are 1 ... len(set) or -1 ... -len(set)"
374
375    def ord(self, match_element):
376        """ For ordered sets, return the position index (1-based)
377            of the input element.
378        """
379        if self.ordered is False:
380           raise AttributeError, "Cannot invoke ord() method for unordered set="+str(self.name)
381        try:
382            return self.order[match_element]
383        except IndexError:
384            raise IndexError, "Unknown input element="+str(match_element)+" provided as input to ord() method for set="+str(self.name)
385
386    def next(self, match_element, k=1):
387        return self.order[self.order_dict[element]+k]
388
389    def nextw(self, match_element, k=1):
390        ndx = self.order_dict[match_element]+k
391        total = len(self.order)
392        if ndx > total:
393            ndx -= total
394        if ndx <= 0:
395            ndx += total
396        return self.order.inv[ndx]
397
398    def prev(self, match_element, k=1):
399        return self.next(match_element, k=-k)
400
401    def prevw(self, match_element, k=1):
402        return self.nextw(match_element, k=-k)
403
404    def pprint(self, ostream=None):
405        if ostream is None:
406           ostream = sys.stdout
407        print >>ostream, "  ",self.name,":",
408        print >>ostream, "\tDim="+str(self.dim()),
409        print >>ostream, "\tDimen="+str(self.dimen),
410        print >>ostream, "\tSize="+str(len(self)),
411        if self.domain is not None:
412           print >>ostream, "\tDomain="+str(self.domain.name),
413        else:
414           print >>ostream, "\tDomain="+str(None),
415        print >>ostream, "\tOrdered="+str(self.ordered),
416        print >>ostream, "\tBounds="+str(self._bounds)
417        if self.model is None:
418           print >>ostream, "\t Model=None"
419        else:
420           print >>ostream, "\t Model="+str(self.model.name)
421        if self.virtual:
422           print >>ostream, "\t  Virtual"
423        else:
424           if self.ordered:
425              tmp = copy.copy(self.order)
426           else:
427              tmp = copy.copy(list(self.value))
428              tmp.sort()
429           print >>ostream, "\t  ",tmp
430
431    def __len__(self):
432        """ The number of items in the set.
433            The set is empty until a concrete instance has been setup.
434        """
435        if self.virtual:
436           raise ValueError, "The size of a virtual set is unknown"
437        return len(self.value)
438
439    #def __repr__(self):
440        #"""Return string representation of the underlying set"""
441        #return str(self)+" : "+str(self.value)
442
443    def __iter__(self):
444        """Return an iterator for the underlying set"""
445        if self.virtual:
446           raise TypeError, "Cannot iterate over Set object `"+self.name+"', which is a virtual set"
447        if self.ordered:
448           return self.order.__iter__()
449        return self.value.__iter__()
450
451    def __hash__(self):
452        """Hash this object"""
453        return _BaseSet.__hash__(self)
454
455    def __eq__(self,other):
456        """ Equality comparison """
457        tmp = self._set_repn(other)
458        if self.virtual:
459            if other.virtual:
460                return hash(self) == hash(tmp)
461            return False
462        if self.dimen != other.dimen:
463           return False
464        return self.value.__eq__( tmp )
465
466    def __ne__(self,other):
467        return not self.__eq__(other)
468
469    def __contains__(self, element):
470        """Report whether an element is a member of this set.
471        (Called in response to the expression 'element in self'.)
472        """
473        #
474        # If the element is a set, then see if this is a subset
475        #
476        if isinstance(element,_SetContainer):
477           return element.issubset(self)
478        #
479        # If this is not a valid element, then return False
480        #
481        if not self._verify(element,False):
482           return False
483        #
484        # If the restriction rule is used then we do not actually
485        # check whether the data is in the set self.value.
486        #
487        if self.validate is not None and self.virtual:
488           return True
489        #
490        # The final check: return true if self.virtual is True, since we should
491        # have already validated this value
492        #
493        return self.virtual or element in self.value
494
495    def issubset(self,other):
496        """Report whether another set contains this set"""
497        if self.virtual:
498           raise TypeError, "ERROR: cannot perform \"issubset\" test because the current set is a virtual set."
499        if self.dimen != other.dimen:
500           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
501        for val in self.value:
502          if val not in other:
503             return False
504        return True
505
506    def issuperset(self,other):
507        """Report this set contains another set"""
508        if isinstance(other,_BaseSet):
509           if other.virtual:
510              raise TypeError, "ERROR: cannot perform \"issuperset\" test because the target set is a virtual set."
511           return other.issubset(self) and (self.virtual or len(self) >= len(other))
512        for val in other:
513          if val not in self:
514             return False
515        return True
516
517    __le__ = issubset
518    __ge__ = issuperset
519
520    def __lt__(self,other):
521        if self.virtual or \
522           (isinstance(other,_BaseSet) and other.virtual):
523           raise TypeError, "ERROR: cannot test for __lt__ because either or both of the sets are virtual."
524        if self.dimen != other.dimen:
525           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
526        for val in self.value:
527          if val not in other:
528             return False
529        return len(self) < len(other)
530
531    def __gt__(self,other):
532        if self.virtual or \
533           (isinstance(other,_BaseSet) and other.virtual):
534           raise TypeError, "ERROR: cannot test for __lt__ because either or both of the sets are virtual."
535        if self.dimen != other.dimen:
536           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
537        for val in other:
538          if val not in self.value:
539             return False
540        return len(self) > len(other)
541
542    def _set_repn(self,other):
543        if isinstance(other,_SetContainer) and other.virtual:
544            return other
545        if isinstance(other,_SetContainer) and type(other.value) is not dict:
546           return other.value
547        raise TypeError, "Cannot apply Set operation with a "+str(type(other))+" object"
548       
549    def __or__(self,other):
550        """ Return a _SetContainer object that is the union of this set
551        with another set. """
552        if self.virtual:
553           raise TypeError, "Cannot perform set operations with virtual set '"+self.name+"'"
554        if other.virtual:
555           raise TypeError, "Cannot perform set operations with virtual set '"+other.name+"'"
556        if self.dimen != other.dimen:
557           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
558        def SetUnionRule(model):
559            data = set()
560            for val in getattr(model,SetUnionRule.Set1):
561              data.add(val)
562            for val in getattr(model,SetUnionRule.Set2):
563              data.add(val)
564            return data
565        SetUnionRule.Set1 = self.name
566        SetUnionRule.Set2 = other.name
567        return _SetContainer(dimen=self.dimen, initialize=SetUnionRule)
568
569    def __and__(self,other):
570        """Return a _SetContainer object that is the intersection of this set
571        with another set"""
572        if self.virtual:
573           raise TypeError, "Cannot perform set operations with virtual set '"+self.name+"'"
574        if other.virtual:
575           raise TypeError, "Cannot perform set operations with virtual set '"+other.name+"'"
576        if self.dimen != other.dimen:
577           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
578        def SetIntersectionRule(model):
579            data = set()
580            for val in getattr(model,SetIntersectionRule.Set1):
581              if val in getattr(model,SetIntersectionRule.Set2):
582                 data.add(val)
583            return data
584        SetIntersectionRule.Set1 = self.name
585        SetIntersectionRule.Set2 = other.name
586        return _SetContainer(dimen=self.dimen, initialize=SetIntersectionRule)
587
588    def __xor__(self,other):
589        """Return a _SetContainer object that is the symmetric difference of
590        this set with another set"""
591        if self.virtual:
592           raise TypeError, "Cannot perform set operations with virtual set '"+self.name+"'"
593        if other.virtual:
594           raise TypeError, "Cannot perform set operations with virtual set '"+other.name+"'"
595        if self.dimen != other.dimen:
596           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
597        def SetSymmetricDifferenceRule(M):
598            Set1 = getattr(M,SetSymmetricDifferenceRule.Set1)
599            Set2 = getattr(M,SetSymmetricDifferenceRule.Set2)
600            data = set()
601            for val in Set1:
602              if val not in Set2:
603                 data.add(val)
604            for val in Set2:
605              if val not in Set1:
606                 data.add(val)
607            return data
608        SetSymmetricDifferenceRule.Set1 = self.name
609        SetSymmetricDifferenceRule.Set2 = other.name
610        return _SetContainer(dimen=self.dimen, initialize=SetSymmetricDifferenceRule)
611
612    def __sub__(self,other):
613        """Return a _SetContainer object that is the difference between this set
614        and another set"""
615        if self.virtual:
616           raise TypeError, "Cannot perform set operations with virtual set '"+self.name+"'"
617        if other.virtual:
618           raise TypeError, "Cannot perform set operations with virtual set '"+other.name+"'"
619        if self.dimen != other.dimen:
620           raise ValueError, "Cannot perform set operation with sets "+self.name+" and "+other.name+" that have different element dimensions: "+str(self.dimen)+" "+str(other.dimen)
621        def SetDifferenceRule(model):
622            data = set()
623            for val in getattr(model,SetDifferenceRule.Set1):
624              if val not in getattr(model,SetDifferenceRule.Set2):
625                 data.add(val)
626            return data
627        SetDifferenceRule.Set1 = self.name
628        SetDifferenceRule.Set2 = other.name
629        return _SetContainer(dimen=self.dimen, initialize=SetDifferenceRule)
630
631    def __mul__(self,other):
632        """Return a _ProductSet that is the cross-product between this set and
633        and another set"""
634        if not isinstance(other,_SetContainer):
635           raise TypeError, "Set operator __mul__ cannot be applied with an object of type "+str(type(other))
636        if self.virtual:
637           raise TypeError, "Cannot perform set operations with virtual set '"+self.name+"'"
638        if other.virtual:
639           raise TypeError, "Cannot perform set operations with virtual set '"+other.name+"'"
640        if isinstance(self,_ProductSet) and self.name=="_unknown_":
641           self.set_tuple = tuple( list(self.set_tuple) + [other] )
642           self._compute_dimen()
643           return self
644        return _ProductSet(self,other)
645
646
647class _SetArray(_BaseSet):
648    """An array of sets, which are indexed by other sets"""
649
650    def __init__(self, *args, **kwds):      #pragma:nocover
651        """ Constructor """
652        self._index_set=None
653        self.value={}
654        self._ndim=len(args)
655        if len(args) == 1:
656            if isinstance(args[0],_BaseSet):
657                self._index=args[0]
658            else:
659                try:
660                    options = getattr(args[0],'set_options')
661                    options['initialize'] = args[0]
662                    self._index=Set(**options)
663                except:
664                    self._index=Set(initialize=args[0])
665        else:
666            self._index=None
667            tmp = []
668            for arg in args:
669                if isinstance(arg,_BaseSet):
670                    tmp.append(arg)
671                else:
672                    try:
673                        options = getattr(arg,'set_options')
674                        options['initialize'] = arg
675                        tmp.append( Set(**options) )
676                    except:
677                        tmp.append( Set(initialize=arg) )
678            self._index_set=tuple(tmp)
679        _BaseSet.__init__(self,**kwds)
680        self.value={}
681
682    def keys(self):
683        return self.value.keys()
684
685    def __contains__(self, element):
686        return element in self.value.keys()
687
688    def clear(self):
689        """Remove all elements from the set."""
690        for key in self.value:
691          self.value[key].clear()
692
693    def data(self):
694        """The underlying set data."""
695        raise TypeError, "Cannot access underlying set data for array set "+self.name
696
697    def __getitem__(self, key):
698        if key not in self.value:
699           raise KeyError, "Cannot access index "+str(key)+" in array set "+self.name
700        return self.value[key]
701
702    def __setitem__(self, key, val):
703        if key not in self._index:
704           raise KeyError, "Cannot set index "+str(key)+" in array set "+self.name
705        if key in self.value:
706           self.value[key].clear()
707           for elt in val:
708             self.value[key].add(elt)
709        else:
710           self.value[key] = Set(initialize=val,within=self.domain,validate=self.validate,ordered=self.ordered,name=self.name+"["+str(key)+"]",dimen=self.dimen)
711           self.value[key].construct()
712
713    def __len__(self):
714        result = 0
715        for val in self.value.values():
716            result += len(val)
717        return result
718
719    def __iter__(self):
720        """Return an iterator for the set array"""
721        return self.value.__iter__()
722
723    def check_values(self):
724        for key in self.value:
725          for val in self.value[key]:
726            self._verify(val,True)
727
728    def pprint(self, ostream=None):
729        if ostream is None:
730           ostream = sys.stdout
731        print >>ostream, "  ",self.name,":",
732        print >>ostream, "\tDim="+str(self.dim()),
733        print >>ostream, "\tDimen="+str(self.dimen),
734        if self.domain is None:
735           print >>ostream, "\tDomain="+str(self.domain),
736        else:
737           print >>ostream, "\tDomain="+str(self.domain.name),
738        print >>ostream, "\tArraySize="+str(len(self.value.keys())),
739        print >>ostream, "\tOrdered="+str(self.ordered)
740        if self.model is None:
741           print >>ostream, "\t Model=None"
742        else:
743           print >>ostream, "\t Model="+str(self.model.name)       
744        tmp=self.value.keys()
745        if tmp is not None:
746            tmp.sort()
747        else:
748            tmp=[]
749        for val in tmp:
750          if self.ordered:
751             ttmp = copy.copy(self.value[val].order)
752          else:
753             ttmp = copy.copy(list(self.value[val].value))
754             ttmp.sort()
755          print >>ostream, "\t",self.name+"["+str(val)+"]\t",ttmp
756
757    def construct(self, values=None):
758        """ Apply the rule to construct values in each set"""
759        log.debug("Constructing _SetArray, name="+self.name+", from data="+repr(values))
760        if self._constructed:
761            return
762        self._constructed=True
763        if self.virtual:                                #pragma:nocover
764           raise TypeError, "It doesn't make sense to create a virtual set array"
765        #
766        # Construct using the values list
767        #
768        if values is not None:
769           for key in values:
770             if type(key) is tuple and len(key)==1:
771                tmpkey=key[0]
772             else:
773                tmpkey=key
774             if tmpkey not in self._index:
775                raise KeyError, "Cannot set index "+str(tmpkey)+" in array set "+self.name
776             self.value[tmpkey] = Set(initialize=values[key],within=self.domain,validate=self.validate,ordered=self.ordered,name=self.name+"["+str(tmpkey)+"]",dimen=self.dimen)
777             self.value[tmpkey].model = self.model
778             self.value[tmpkey].construct()
779        #
780        # Construct using the rule
781        #
782        elif type(self.initialize) is types.FunctionType:
783           if self.model is None:
784              raise ValueError, "Need model to construct a set array with a function"
785           if self._index is None:
786              raise ValueError, "No index for set "+self.name
787           for key in self._index:
788             if isinstance(key,tuple):
789                tmp = list(key)
790             else:
791                tmp = [key]
792             if self.initialize.func_code.co_argcount == len(tmp)+1:
793                tmp.append(self.model)
794                tmp = tuple(tmp)
795                rule_list = list(self.initialize(*tmp))
796             else:
797                rule_list=[]
798                ctr=1
799                args = tuple(tmp+[ctr,self.model])
800                val = self.initialize(*args)
801                while val is not None:
802                  rule_list.append(val)
803                  ctr += 1
804                  args = tuple(tmp+[ctr,self.model])
805                  val = self.initialize(*args)
806             if self.dimen is None and len(rule_list) > 0:
807                   if type(rule_list[0]) in [tuple,list]:
808                        self.dimen=len(rule_list[0])
809                   else:
810                        self.dimen=1
811             self.value[key] = Set(initialize=rule_list,within=self.domain,validate=self.validate,ordered=self.ordered,name=self.name+"["+str(key)+"]",dimen=self.dimen)
812             self.value[key].model = self.model
813             self.value[key].construct()
814        #
815        # Construct using the default values
816        #
817        else:
818           if self.initialize is not None:
819              if type(self.initialize) is not dict:
820                 for key in self._index:
821                   self.value[key] = Set(initialize=self.initialize,within=self.domain,validate=self.validate,ordered=self.ordered,name=self.name+"["+str(key)+"]",dimen=self.dimen)
822                   self.value[key].model = self.model
823                   self.value[key].construct()
824              else:
825                 for key in self.initialize:
826                   self.value[key] = Set(initialize=self.initialize[key],within=self.domain,validate=self.validate,ordered=self.ordered,name=self.name+"["+str(key)+"]",dimen=self.dimen)
827                   self.value[key].model = self.model
828                   self.value[key].construct()
829
830
831##------------------------------------------------------------------------
832##
833## A set that defines an abstract cross-product set.
834##
835##------------------------------------------------------------------------
836
837class _ProductSet(_SetContainer):
838    """Set that represents a cross-product of other sets"""
839
840    """ Constructor
841        Arguments:
842           name         The name of this set
843           initialize   Default set members, which may be overriden
844                            when setting up this set
845           rule         A rule for setting up this set with
846                            existing model data.  This has the functional form:
847                            f: pyomo.Model -> set
848    """
849    def __init__(self, *args, **kwd):
850        self._class_override=False
851        self.set_tuple = args
852        tmp=()
853        _SetContainer.__init__(self,*tmp,**kwd)
854        self._compute_dimen()
855        self._len=0
856
857    def __iter__(self):
858        """Returns an iterator/generator for the cross-product"""
859        return pyutilib.misc.flattened_cross_iter(*self.set_tuple)
860
861    def __len__(self):
862        return self._len
863
864    def _compute_dimen(self):
865        ans=0
866        for set in self.set_tuple:
867            if set.dimen is None:
868                self.dimen=None
869                return
870            else:
871                ans += set.dimen
872        self.dimen = ans
873
874    def _verify(self,element,use_exception=True):
875        """
876        If this set is virtual, then an additional check is made
877        to ensure that the element is in each of the underlying sets.
878        """
879        tmp = _SetContainer._verify(self,element,use_exception)
880        if not tmp or not self.virtual:
881            return tmp
882
883        next_tuple_index = 0
884        member_set_index = 0
885        for member_set in self.set_tuple:
886           tuple_slice = element[next_tuple_index:next_tuple_index + member_set.dimen]
887           if member_set.dimen == 1:
888              tuple_slice = tuple_slice[0]
889           if tuple_slice not in member_set:
890             return False
891           member_set_index += 1
892           next_tuple_index += member_set.dimen
893        return True
894       
895    def construct(self, values=None):
896        """ Apply the rule to construct values in this set """
897        log.debug("Constructing _ProductSet, name="+self.name+", from data="+repr(values))
898        if self._constructed:
899            return
900        self._constructed=True
901        if self.virtual:
902           if len(self.set_tuple) == 0:
903              self._len=0
904           else:
905              self._len=1
906              for val in self.set_tuple:
907                self._len *= len(val)
908        #
909        # Construct using the values list
910        #
911        elif values is not None:
912           for val in values[None]:
913             self.add(val)
914        #
915        # Construct using the rule
916        #
917        elif type(self.initialize) is types.FunctionType:
918           for val in self.initialize(self.model):
919             self.add(val)
920        #
921        # Construct using the default values
922        #
923        else:
924           if self.initialize is not None:
925              if type(self.initialize) is dict:
926                 for val in self.initialize:
927                   self.add(val)
928              else:
929                 self.add(self.initialize)
930           else:
931              for val in pyutilib.misc.cross(self.set_tuple):
932                self.add(val)
933        #
934        if not self.virtual:
935            self._len=len(self.value)
936        self._compute_dimen()
937        if self.dimen is None:
938            raise ValueError, "The product set dimension must be computeable after constructing this set!"
939
940
941class Set(object):
942    """Set objects that are used to index other Pyomo objects
943
944       This class has a similar look-and-feel as a built-in set class.  However,
945       the set operations defined in this class return another abstract
946       Set object.  This class contains a concrete set, which can be
947       initialized by the load() method.
948    """
949    def __new__(cls, *args, **kwds):
950        if args == ():
951            self = _SetContainer(*args, **kwds)
952        else:
953            self = _SetArray(*args, **kwds)
954        return self
955
956
957ComponentRegistration("Set", Set, "Set data that is used to define a model instance.")
958
Note: See TracBrowser for help on using the repository browser.