source: trunk/coopr/pyomo/base/PyomoModelData.py @ 1768

Last change on this file since 1768 was 1768, checked in by wehart, 11 years ago

Rework of Coopr to use the new PyUtilib? package decomposition.

NOTE: to use Coopr with this update, we need to work with a new version of coopr_install.

File size: 25.0 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11import re
12import copy
13import math
14import os
15from pyutilib.misc import quote_split
16import pyomo
17from pyutilib.excel import ExcelSpreadsheet
18import coopr
19
20
21class ModelData(object):
22    """
23    An object that manages data for a model.
24    This object contains the interface routines for importing and
25    exporting data from external data sources.
26    """
27
28    def __init__(self,model=None,filename=None):
29        """
30        Constructor
31        """
32        self._info=[]
33        #self._info_dict={}
34        self._data={}
35        self._default={}
36        self._model=model
37        self._table={}
38        if filename is not None:
39           self.import_data_file(filename)
40
41
42    #def add_data_file(self,filename, name=None):
43        #if name is not None:
44        #   self._info_dict[name] = len(self._info)
45    def add_data_file(self,filename):
46        self._info.append(filename)
47
48
49    def add_table(self, filename, range=None, **kwds):
50        """
51        Add an Excel table
52        """
53        #if "name" in kwds:
54        #   if name is not None:
55        #      self._info_dict[kwds["name"]] = len(self._info)
56        if filename not in self._table.keys():
57           kwds["filename"]=filename
58           self._table[filename] = TableData(**kwds)
59        self._info.append((filename,range,kwds))
60
61
62    def read(self,model=None):
63        """
64        Read data
65        """
66        if model is not None:
67           self._model=model
68        if self._model is None:
69           raise ValueError, "Cannot read model data without a model"
70        #
71        # Open all tables
72        #
73        for key in self._table.keys():
74          self._table[key].open()
75        #
76        # For every information item, import the data or table
77        #
78        for item in self._info:
79          if type(item) is str:
80             if pyomo.debug("verbose"):         #pragma:nocover
81                print "Importing data from file "+item,
82             status = self.import_data_file(item)
83             if pyomo.debug("verbose"):         #pragma:nocover
84                print "... done."
85          else:       
86             if pyomo.debug("verbose"):         #pragma:nocover
87                print "Importing data from table "+item[0],
88                if item[1] is not None:
89                   print "at range "+item[1],
90             try:
91                status = self.import_table(self._table[item[0]], item[1], item[2])
92             except IOError, err:
93                raise IOError, "Problem importing data from table "+item[0]+": "+str(err)
94             except AttributeError, err:
95                raise IOError, "Problem importing data from table "+item[0]+": "+str(err)
96             if pyomo.debug("verbose"):         #pragma:nocover
97                print "... done."
98          if not status:
99             print "Warning: status is "+str(status)+" after processing "+str(item)
100        #
101        # Close all tables
102        #
103        for key in self._table.keys():
104          self._table[key].close()
105
106
107    def import_table(self, table, range, keywords):
108        """
109        Import a table
110        """
111        table.read(range, keywords)
112        tmp = table.data()
113        if pyomo.debug("reader"):               #pragma:nocover
114           print "import_table - data: "+str(tmp)
115        status = self._process_data(tmp)
116        table.clear()
117        return status
118
119
120    def import_data_file(self, filename):
121        """
122        Create a table of tuples to values, based on data from a file.
123        We assume that this data is defined in a format that is
124        compatible with AMPL.
125        """
126        global Filename
127        Filename = filename
128        global Lineno
129        Lineno = 0
130        INPUT = open(filename,"r")
131        cmd=""
132        status=True
133        for line in INPUT:
134          Lineno += 1
135          line = re.sub(":"," :",line)
136          line = line.strip()
137          if line == "" or line[0] == '#':
138             continue
139          cmd = cmd + " " + line
140          if ';' in cmd:
141             #
142             # We assume that a ';' indicates an end-of-command declaration.
143             # However, the user might have put multiple commands on a single
144             # line, so we need to split the line based on these values.
145             # BUT, at the end of the line we should see an 'empty' command,
146             # which we ignore.
147             #
148             for item in cmd.split(';'):
149               item = item.strip()
150               if item != "":
151                  status = self._process_data(quote_split("[\t ]+",item))
152               if not status:
153                  break
154             cmd = ""
155          if not status:
156                 break
157        if cmd != "":
158           raise IOError, "ERROR: There was unprocessed text at the end of the data file!: \"" + cmd + "\""
159        return status
160
161
162    def _preprocess_data(self,cmd):
163        """
164        Called by _process_data() to (1) combine tokens that comprise a tuple
165        and (2) combine the ':' token with the previous token
166        """
167        if pyomo.debug("reader"):               #pragma:nocover
168           print "_preprocess_data(start)",cmd
169        status=")"
170        newcmd=[]
171        for token in cmd:
172          if type(token) in (str,unicode):
173             token=str(token)
174             if "(" in token and ")" in token:
175                newcmd.append(token)
176                status=")"
177             elif "(" in token:
178                if status == "(":
179                   raise ValueError, "Two '('s follow each other in data "+token
180                status="("
181                newcmd.append(token)
182             elif ")" in token:
183                if status == ")":
184                   raise ValueError, "Two ')'s follow each other in data"
185                status=")"
186                newcmd[-1] = newcmd[-1]+token
187             elif status == "(":
188                newcmd[-1] = newcmd[-1]+token
189             else:
190                newcmd.append(token)
191          else:
192             if type(token) is float and math.floor(token) == token:
193                token=int(token)
194             newcmd.append(token)
195        if pyomo.debug("reader"):               #pragma:nocover
196           print "_preprocess_data(end)",newcmd
197        return newcmd
198
199
200    def _process_data(self,cmd):
201        """
202        Called by import_file() to (1) preprocess data and (2) call
203        subroutines to process different types of data
204        """
205        global Lineno
206        global Filename
207        if pyomo.debug("reader"):               #pragma:nocover
208           print "DEBUG: _process_data (start)",cmd
209        if len(cmd) == 0:                       #pragma:nocover
210           raise ValueError, "ERROR: Empty list passed to Model::_process_data"
211        cmd = self._preprocess_data(cmd)
212        if cmd[0] == "data":
213           return True
214        if cmd[0] == "end":
215           return False
216        if cmd[0][0:3] == "set":
217           self._process_set(cmd)
218        elif cmd[0][0:5] == "param":
219           self._process_param(cmd)
220        else:
221           raise IOError, "ERROR: Trouble on line "+str(Lineno)+" of file "+Filename+": Unknown data command: "+" ".join(cmd)
222        return True
223
224
225    def _process_set(self,cmd):
226        """
227        Called by _process_data() to process a set declaration.
228        """
229        if pyomo.debug("reader"):               #pragma:nocover
230           print "DEBUG: _process_set(start)",cmd
231        #
232        # Process a set
233        #
234        if "[" in cmd[1]:
235           tokens = re.split("[\[\]]",cmd[1])
236           ndx=tokens[1]
237           ndx=tuple(self._data_eval(ndx.split(",")))
238           if tokens[0] not in self._data:
239              self._data[tokens[0]] = {}
240           self._data[tokens[0]][ndx] = self._process_set_data(cmd[3:],tokens[0])
241        elif cmd[2] == ":":
242           self._data[cmd[1]] = {}
243           self._data[cmd[1]][None] = []
244           i=3
245           while cmd[i] != ":=":
246              i += 1
247           ndx1 = cmd[3:i]
248           i += 1
249           while i<len(cmd):
250              ndx=cmd[i]
251              for j in range(0,len(ndx1)):
252                if cmd[i+j+1] == "+":
253                   self._data[cmd[1]][None] += self._process_set_data(["("+str(ndx1[j])+","+str(cmd[i])+")"],cmd[1])
254              i += len(ndx1)+1
255        else:
256           self._data[cmd[1]] = {}
257           self._data[cmd[1]][None] = self._process_set_data(cmd[3:], cmd[1])
258
259
260    def _process_set_data(self,cmd,sname):
261        """
262        Called by _process_set() to process set data.
263        """
264        if pyomo.debug("reader"):               #pragma:nocover
265           print "DEBUG: _process_set_data(start)",cmd
266        if len(cmd) == 0:
267           return []
268        sd = getattr(self._model,sname)
269        #d = sd.dimen
270        cmd = self._data_eval(cmd)
271        ans=[]
272        i=0
273        flag=type(cmd[0]) is tuple
274        tmp=None
275        ndx=None
276        while i<len(cmd):
277          if type(cmd[i]) is not tuple:
278            if flag:
279               tmpval=tmp
280               tmpval[ndx] = self._data_eval([cmd[i]])[0]
281               #
282               # WEH - I'm not sure what the next two lines are for
283               #        These are called when initializing a set with more than
284               #        one dimension
285               #if d > 1:
286               #   tmpval = util.tuplize(tmpval,d,sname)
287               ans.append(tuple(tmpval))
288            else:
289               ans.append(cmd[i])
290          elif "*" not in cmd[i]:
291            ans.append(cmd[i])
292          else:
293            j = i
294            tmp=list(cmd[j])
295            ndx=tmp.index("*")
296          i += 1
297        if pyomo.debug("reader"):               #pragma:nocover
298           print "DEBUG: _process_set_data(end)",ans
299        return ans
300
301
302    def _process_param(self,cmd):
303        """
304        Called by _process_data to process data for a Parameter declaration
305        """
306        if pyomo.debug("reader"):               #pragma:nocover
307           print "DEBUG: _process_param(start)",cmd
308        #
309        # Process parameters
310        #
311        dflt = None
312        singledef = True
313        cmd = cmd[1:]
314        #print "HERE",cmd
315        #
316        # WEH - this is apparently not used
317        #
318        #if cmd[0] == "default":
319        #   dflt = self._data_eval(cmd[1])[0]
320        #   cmd = cmd[2:]
321        if cmd[0] == ":":
322           singledef = False
323           cmd = cmd[1:]
324        if singledef:
325           pname = cmd[0]
326           cmd = cmd[1:]
327           if len(cmd) >= 2 and cmd[0] == "default":
328              dflt = self._data_eval(cmd[1])[0]
329              cmd = cmd[2:]
330           if dflt != None:
331              self._default[pname] = dflt
332           if cmd[0] == ":=":
333              cmd = cmd[1:]
334           transpose = False
335           if cmd[0] == "(tr)":
336              transpose = True
337              if cmd[1] == ":":
338                 cmd = cmd[1:]
339              else:
340                 cmd[0] = ":"
341           if cmd[0] != ":":
342              if pyomo.debug("reader"):             #pragma:nocover
343                 print "DEBUG: _process_param (singledef without :...:=)",cmd
344              if not transpose:
345                 if pname not in self._data:
346                    self._data[pname] = {}
347                 finaldata = self._process_data_list(getattr(self._model,pname).dim(), self._data_eval(cmd))
348                 for key in finaldata:
349                    self._data[pname][key]=finaldata[key]
350              else:
351                 tmp = ["param", pname, ":="]
352                 i=1
353                 while i < len(cmd):
354                    i0 = i
355                    while cmd[i] != ":=":
356                      i=i+1
357                    ncol = i - i0 + 1
358                    lcmd = i
359                    while lcmd < len(cmd) and cmd[lcmd] != ":":
360                      lcmd += 1
361                    j0 = i0 - 1
362                    for j in range(1,ncol):
363                      ii = 1 + i
364                      kk = ii + j
365                      while kk < lcmd:
366                        if cmd[kk] != ".":
367                        #if 1>0:
368                           tmp.append(copy.copy(cmd[j+j0]))
369                           tmp.append(copy.copy(cmd[ii]))
370                           tmp.append(copy.copy(cmd[kk]))
371                        ii = ii + ncol
372                        kk = kk + ncol
373                    i = lcmd + 1
374                 self._process_param(tmp)
375           else:
376              tmp = ["param", pname, ":="]
377              i=1
378              if pyomo.debug("reader"):             #pragma:nocover
379                 print "DEBUG: _process_param (singledef with :...:=)",cmd
380              while i < len(cmd):
381                i0 = i
382                while i<len(cmd) and cmd[i] != ":=":
383                  i=i+1
384                if i==len(cmd):
385                   raise ValueError, "ERROR: Trouble on line "+str(Lineno)+" of file "+Filename
386                ncol = i - i0 + 1
387                lcmd = i
388                while lcmd < len(cmd) and cmd[lcmd] != ":":
389                  lcmd += 1
390                j0 = i0 - 1
391                for j in range(1,ncol):
392                  ii = 1 + i
393                  kk = ii + j
394                  while kk < lcmd:
395                    if cmd[kk] != ".":
396                        if transpose:
397                           tmp.append(copy.copy(cmd[j+j0]))
398                           tmp.append(copy.copy(cmd[ii]))
399                        else:
400                           tmp.append(copy.copy(cmd[ii]))
401                           tmp.append(copy.copy(cmd[j+j0]))
402                        tmp.append(copy.copy(cmd[kk]))
403                    ii = ii + ncol
404                    kk = kk + ncol
405                i = lcmd + 1
406                self._process_param(tmp)
407
408        else:
409           if pyomo.debug("reader"):                #pragma:nocover
410              print "DEBUG: _process_param (cmd[0]=='param:')",cmd
411           i=0
412           nsets=0
413           while i<len(cmd) and cmd[i] != ":=":
414             if cmd[i] == ":":
415                nsets = i
416             i += 1
417           if i==len(cmd):
418              raise ValueError, "Trouble on data file line "+str(Lineno)+" of file "+Filename
419           if pyomo.debug("reader"):                #pragma:nocover
420              print "NSets",nsets
421           Lcmd = len(cmd)
422           j=0
423           d = 1
424           #
425           # Process sets first
426           #
427           while j<nsets:
428             sname = cmd[j]
429             d = getattr(self._model,sname).dimen
430             np = i-1
431             if pyomo.debug("reader"):              #pragma:nocover
432                print "I,J,SName,d",i,j,sname,d
433             dnp = d + np - 1
434             #k0 = i + d - 2
435             ii = i + j + 1
436             tmp = [ "set", cmd[j], ":=" ]
437             while ii < Lcmd:
438               for dd in range(0,d):
439                 tmp.append(copy.copy(cmd[ii+dd]))
440               ii += dnp
441             self._process_set(tmp)
442             j += 1
443           if nsets > 0:
444              j += 1
445           #
446           # Process parameters second
447           #
448           while j < i:
449             pname = cmd[j]
450             if pyomo.debug("reader"):              #pragma:nocover
451                print "I,J,Pname",i,j,pname
452             #d = 1
453             d = getattr(self._model,pname).dim()
454             if nsets > 0:
455                np = i-1
456                dnp = d+np-1
457                ii = i + 1
458                kk = i + d + j-1
459             else:
460                np = i
461                dnp = d + np
462                ii = i + 1
463                kk = np + 1 + d + nsets + j
464             tmp = [ "param", pname, ":=" ]
465             if pyomo.debug("reader"):              #pragma:nocover
466                print "dnp",dnp
467                print "np",np
468             while kk < Lcmd:
469               if pyomo.debug("reader"):                #pragma:nocover
470                  print "kk,ii",kk,ii
471               iid = ii + d
472               while ii < iid:
473                 tmp.append(copy.copy(cmd[ii]))
474                 ii += 1
475               ii += dnp-d
476               tmp.append(copy.copy(cmd[kk]))
477               kk += dnp
478             self._process_param(tmp)
479             j += 1
480
481
482    def _process_data_list(self,dim,cmd):
483        """
484        Called by _process_param() to process a list of data for a
485        Parameter.
486        """
487        if pyomo.debug("reader"):               #pragma:nocover
488           print "process_data_list",dim,cmd
489        if len(cmd) % (dim+1) != 0:
490           raise ValueError, "Parameter data has "+str(len(cmd))+" values (" + str(cmd) + "), which is incompatible with having "+str(dim)+" dimensions"           
491        ans={}
492        if dim==0:
493           ans[None]=cmd[0]
494           return ans
495        i=0
496        while i<len(cmd):
497          ndx = tuple(cmd[i:i+dim])
498          ##print i,cmd[i:i+dim],ndx,cmd[i+dim]
499          if cmd[i+dim] != ".":
500                 ans[ndx] = cmd[i+dim]
501          i += dim+1
502        return ans
503
504
505    def _data_eval(self, values):
506        """
507        Evaluate the list of values to make them bool, integer or float,
508        or a tuple value.
509        """
510        if pyomo.debug("reader"):               #pragma:nocover
511           print "DEBUG: _data_eval(start)",values
512        ans = []
513        for val in values:
514          if type(val) in (bool,int,float,long):
515             ans.append(val)
516             continue
517          if val in ('True','true','TRUE'):
518             ans.append(True)
519             continue
520          if val in ('False','false','FALSE'):
521             ans.append(False)
522             continue
523          tmp = None
524          if "(" in val and ")" in val:
525             vals = []
526             tval = val[1:-1]
527             for item in tval.split(","):
528               tmp=self._data_eval([item])
529               vals.append(tmp[0])
530             ans.append(tuple(vals))
531             continue
532          try:
533             tmp = int(val)
534             ans.append(tmp)
535          except ValueError:
536             pass
537          if tmp is None:
538             try:
539               tmp = float(val)
540               ans.append(tmp)
541             except ValueError:
542               ans.append(val)
543        if pyomo.debug("reader"):               #pragma:nocover
544           print "DEBUG: _data_eval(end)",ans
545        return ans
546
547
548
549class TableData(object):
550    """
551    An object that imports data from a table in an external data source.
552    The initialization of this object depends on the format specified by the
553    file:
554 
555                *.tab       Simple ASCII table
556                *.xls       Excel spreadsheet
557                *.mdb       MSAccess database
558
559    NOTE: it probably makes sense to migrate the table handlers to
560    external classes, and to enable registration of table handlers.
561    But for ValueErrornow, this is convenient.
562    """
563
564    def __init__(self,*args,**kwds):
565        """
566        Constructor
567        """
568        self._info=None
569        #
570        self._reinitialize(kwds)
571
572
573    def _reinitialize(self,kwds):
574        if kwds is None:
575           return
576        self._data=None
577        self._filename=None
578        self._range=None
579        self._format=None
580        self._set=None
581        self._param=None
582        self._index=None
583        for key in kwds:
584          if key not in ["set","format","index","param","data","filename","range"]:
585             raise ValueError, "Unknown keyword '"+key+"' when initializing TableData object"
586          setattr(self,"_"+key,kwds[key])
587
588    def open(self,filename=None):
589        """
590        Open the table
591        """
592        if filename is not None:
593           self._filename=filename
594        if self._filename is None:
595           raise IOError, "No filename specified"
596        if not os.path.exists(self._filename):
597           raise IOError, "Cannot find file: \""+self._filename+"\""
598        tmp = self._filename.split(".")
599        if len(tmp) == 1:
600           raise IOError, "Unknown file type: \""+self._filename+"\""
601        self._type=tmp[-1]
602        if self._type == "tab":
603           self._open_tab()
604        elif self._type == "xls":
605           self._open_xls()
606        elif self._type == "mdb":       #pragma:nocover
607           self._open_mdb()
608        else:
609           raise IOError, "Unknown file type: \""+self._filename+"\""
610
611
612    def read(self,range=None, keywords=None):
613        """
614        Read data from the table
615        """
616        self._reinitialize(keywords)
617        if range is not None:
618           self._range=range
619        if self._type == "tab":
620           self._read_tab()
621        elif self._type == "xls":
622           self._read_xls()
623        elif self._type == "mdb":       #pragma:nocover
624           self._read_mdb()
625
626
627    def close(self):
628        """
629        Close the table
630        """
631        if self._type == "tab":
632           self._close_tab()
633        elif self._type == "xls":
634           self._close_xls()
635        elif self._type == "mdb":
636           self._close_mdb()            #pragma:nocover
637
638
639    def _open_tab(self):
640        self.INPUT = open(self._filename,"r")
641
642    def _read_tab(self):
643        tmp=[]
644        for line in self.INPUT.readlines():
645          line=line.strip()
646          tokens = re.split("[\t ]+",line)
647          if tokens != ['']:
648             tmp.append(tokens)
649        if len(tmp) == 0:
650           raise IOError, "Empty *.tab file"
651        elif len(tmp) == 1:
652           self._info = ["param",self._param,":=",tmp[0][0]]
653        else:
654           self._set_data(tmp[0], tmp[1:])
655
656    def _close_tab(self):
657        self.INPUT.close()
658       
659
660    def _open_xls(self):
661        self.sheet = None
662        if self._data is not None:
663           self.sheet = self._data
664        else:
665           try:
666              self.sheet = ExcelSpreadsheet(self._filename)
667           except pyutilib.ApplicationError:
668              raise
669
670    def _read_xls(self):
671        if self.sheet is None:
672           return
673        tmp = self.sheet.get_range(self._range, raw=True)
674        if type(tmp) in (int,long,float):
675           self._info = ["param",self._param,":=",tmp]
676        else:
677           self._set_data(tmp[0], tmp[1:])
678
679    def _close_xls(self):
680        if self._data is None and not self.sheet is None:
681           del self.sheet
682
683
684    def _open_mdb(self):
685        pass                #pragma:nocover
686
687    def _read_mdb(self):
688        pass                #pragma:nocover
689
690    def _close_mdb(self):
691        pass                #pragma:nocover
692
693
694    def data(self):
695        return self._info
696
697
698    def clear(self):
699        self._info = None
700
701
702    def _set_data(self, headers, rows):
703        if self._set is not None:
704           if self._format is None:
705              self._info = ["set",self._set,":="]
706              for row in rows:
707                self._info = self._info + list(row)
708           elif self._format is "array":
709              self._info = ["set",self._set, ":"]
710              self._info = self._info + list(headers[1:])
711              self._info = self._info + [":="]
712              for row in rows:
713                self._info = self._info + list(row)
714           else:
715              raise ValueError, "Unknown set format: "+self._format
716
717        elif self._index is not None and self._param is None:
718           self._info = ["param",":",self._index,":"]
719           self._info = self._info + map(str,list(headers[1:]))
720           self._info.append(":=")
721           for row in rows:
722             self._info = self._info + list(row)
723
724        elif self._index is not None and self._param is not None:
725           self._info = ["param",":",self._index,":"]
726           if type(self._param) is str:
727              self._info = self._info + [self._param]
728           elif type(self._param) is tuple:
729              self._info = self._info + list(self._param)
730           else:
731              self._info = self._info + self._param
732           self._info.append(":=")
733           for row in rows:
734             self._info = self._info + list(row)
735
736        elif self._param is not None and self._format is not None:
737           if self._format is "transposed_array":
738              self._info = ["param",self._param,"(tr)",":"] + map(str,list(headers[1:]))
739           elif self._format is "array":
740              self._info = ["param",self._param,":"] + map(str,list(headers[1:]))
741           else:
742              raise ValueError, "Unknown parameter format: "+self._format
743           self._info.append(":=")
744           for row in rows:
745             self._info = self._info + list(row)
746
747        else:
748           if len(headers) == 1:
749              self._info = ["set",headers[0],":="]
750              for row in rows:
751                self._info = self._info + list(row)
752           else:
753              self._info = ["param",":"]
754              if self._param is None:
755                 self._info = self._info + map(str,list(headers[1:]))
756              elif type(self._param) is str:
757                 self._info = self._info + [self._param]
758              else:
759                 self._info = self._info + list(self._param)
760              self._info.append(":=")
761              for row in rows:
762                self._info = self._info + list(row)
763
Note: See TracBrowser for help on using the repository browser.