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

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

Update to Coopr to account for changes in PyUtilib? package names.

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