Package SimPy :: Module Recording |
[hide private]
[frames] | no frames]

# Source Code for Module SimPy.Recording

      1  # coding=utf-8
      2  """
      3  This file contains the classes for recording simulation results, Histogram,
      4  Monitor and Tally.
      5  """
      6  # $Revision: 504 $ $Date: 2010-05-11 08:29:10 +0200 (Tue, 11 May 2010) $
      7  # SimPy version: 2.1.0
      8
      9  # Required for backward compatibility
     10  import SimPy.Globals as Globals
     11
     12
     13 -class Histogram(list):
     14      """ A histogram gathering and sampling class"""
     15
     16 -    def __init__(self, name = '', low = 0.0, high = 100.0, nbins = 10):
     17          list.__init__(self)
     18          self.name  = name
     19          self.low   = float(low)
     20          self.high  = float(high)
     21          self.nbins = nbins
     22          self.binsize = (self.high - self.low) / nbins
     23          self._nrObs = 0
     24          self._sum = 0
     25          self[:] = [[low + (i - 1) * self.binsize, 0] for i in range(self.nbins + 2)]
     26
     27 -    def addIn(self, y):
     28          """ add a value into the correct bin"""
     29          self._nrObs += 1
     30          self._sum += y
     31          b = int((y - self.low + self.binsize) / self.binsize)
     32          if b < 0: b = 0
     33          if b > self.nbins + 1: b = self.nbins + 1
     34          assert 0 <= b <=self.nbins + 1, 'Histogram.addIn: b out of range: %s'%b
     35          self[b][1] += 1
     36
     37 -    def __str__(self):
     38          histo = self
     39          ylab = 'value'
     40          nrObs = self._nrObs
     41          width = len(str(nrObs))
     42          res = []
     43          res.append(' < Histogram %s:'%self.name)
     44          res.append('\nNumber of observations: %s'%nrObs)
     45          if nrObs:
     46              su = self._sum
     47              cum = histo[0][1]
     48              fmt = '%s'
     49              line = '\n%s <= %s < %s: %s (cum: %s/%s%s)'\
     50                   %(fmt, '%s', fmt, '%s', '%s', '%5.1f', '%s')
     51              line1 = '\n%s%s < %s: %s (cum: %s/%s%s)'\
     52                   %('%s', '%s', fmt, '%s', '%s', '%5.1f', '%s')
     53              l1width = len(('%s <= '%fmt)%histo[1][0])
     54              res.append(line1\
     55                         %(' ' * l1width, ylab, histo[1][0], str(histo[0][1]).rjust(width),\
     56                           str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
     57                        )
     58              for i in range(1, len(histo) - 1):
     59                  cum += histo[i][1]
     60                  res.append(line\
     61                         %(histo[i][0], ylab, histo[i + 1][0], str(histo[i][1]).rjust(width),\
     62                           str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
     63                            )
     64              cum += histo[-1][1]
     65              linen = '\n%s <= %s %s : %s (cum: %s/%s%s)'\
     66                    %(fmt, '%s', '%s', '%s', '%s', '%5.1f', '%s')
     67              lnwidth = len(('<%s'%fmt)%histo[1][0])
     68              res.append(linen\
     69                         %(histo[-1][0], ylab, ' ' * lnwidth, str(histo[-1][1]).rjust(width),\
     70                         str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
     71                         )
     72          res.append('\n > ')
     73          return ' '.join(res)
     74
     75
     76 -class Monitor(list):
     77      """ Monitored variables
     78
     79      A Class for monitored variables, that is, variables that allow one
     80      to gather simple statistics.  A Monitor is a subclass of list and
     81      list operations can be performed on it. An object is established
     82      using m = Monitor(name = '..'). It can be given a
     83      unique name for use in debugging and in tracing and ylab and tlab
     84      strings for labelling graphs.
     85      """
     86 -    def __init__(self, name = 'a_Monitor', ylab = 'y', tlab = 't', sim = None):
     87          list.__init__(self)
     88          if not sim: sim = Globals.sim # Use global simulation if sim is None
     89          self.sim = sim
     90          self.startTime = 0.0
     91          self.name = name
     92          self.ylab = ylab
     93          self.tlab = tlab
     94          self.sim.allMonitors.append(self)
     95
     96 -    def setHistogram(self, name = '', low = 0.0, high = 100.0, nbins = 10):
     97          """Sets histogram parameters.
     98          Must be called before call to getHistogram"""
     99          if name == '':
    100              histname = self.name
    101          else:
    102              histname = name
    103          self.histo = Histogram(name = histname, low = low, high = high, nbins = nbins)
    104
    105 -    def observe(self, y,t = None):
    106          """record y and t"""
    107          if t is  None: t = self.sim.now()
    108          self.append([t, y])
    109
    110 -    def tally(self, y):
    111          """ deprecated: tally for backward compatibility"""
    112          self.observe(y, 0)
    113
    114 -    def accum(self, y,t = None):
    115          """ deprecated:  accum for backward compatibility"""
    116          self.observe(y, t)
    117
    118 -    def reset(self, t = None):
    119          """reset the sums and counts for the monitored variable """
    120          self[:] = []
    121          if t is None: t = self.sim.now()
    122          self.startTime = t
    123
    124 -    def tseries(self):
    125          """ the series of measured times"""
    126          return list(zip(*self)[0])
    127
    128 -    def yseries(self):
    129          """ the series of measured values"""
    130          return list(zip(*self)[1])
    131
    132 -    def count(self):
    133          """ deprecated: the number of observations made """
    134          return self.__len__()
    135
    136 -    def total(self):
    137          """ the sum of the y"""
    138          if self.__len__() == 0:  return 0
    139          else:
    140              sum = 0.0
    141              for i in range(self.__len__()):
    142                  sum += self[i][1]
    143              return sum # replace by sum() later
    144
    145 -    def mean(self):
    146          """ the simple average of the monitored variable"""
    147          try: return 1.0 * self.total() / self.__len__()
    148          except:  print 'SimPy: No observations  for mean'
    149
    150 -    def var(self):
    151          """ the sample variance of the monitored variable """
    152          n = len(self)
    153          tot = self.total()
    154          ssq = 0.0
    155          for i in range(self.__len__()):
    156              ssq += self[i][1] ** 2 # replace by sum() eventually
    157          try: return (ssq - float(tot * tot) / n) / n
    158          except: print 'SimPy: No observations for sample variance'
    159
    160 -    def timeAverage(self, t = None):
    161          """ the time - weighted average of the monitored variable.
    162
    163              If t is used it is assumed to be the current time,
    164              otherwise t =  self.sim.now()
    165          """
    166          N = self.__len__()
    167          if N  == 0:
    168              print 'SimPy: No observations for timeAverage'
    169              return None
    170
    171          if t is None: t = self.sim.now()
    172          sum = 0.0
    173          tlast = self[0][0]
    174          ylast = self[0][1]
    175          for i in range(N):
    176              ti, yi = self[i]
    177              sum += ylast * (ti - tlast)
    178              tlast = ti
    179              ylast = yi
    180          sum += ylast * (t - tlast)
    181          T = t - self[0][0]
    182          if T == 0:
    183               print 'SimPy: No elapsed time for timeAverage'
    184               return None
    185          return sum / float(T)
    186
    187 -    def timeVariance(self, t = None):
    188          """ the time - weighted Variance of the monitored variable.
    189
    190              If t is used it is assumed to be the current time,
    191              otherwise t =  self.sim.now()
    192          """
    193          N = self.__len__()
    194          if N  == 0:
    195              print 'SimPy: No observations for timeVariance'
    196              return None
    197          if t is None: t = self.sim.now()
    198          sm = 0.0
    199          ssq = 0.0
    200          tlast = self[0][0]
    201          # print 'DEBUG: 1 twVar ', t, tlast
    202          ylast = self[0][1]
    203          for i in range(N):
    204              ti, yi = self[i]
    205              sm  += ylast * (ti - tlast)
    206              ssq += ylast * ylast * (ti - tlast)
    207              tlast = ti
    208              ylast = yi
    209          sm  += ylast * (t - tlast)
    210          ssq += ylast * ylast * (t - tlast)
    211          T = t - self[0][0]
    212          if T == 0:
    213               print 'SimPy: No elapsed time for timeVariance'
    214               return None
    215          mn = sm / float(T)
    216          return ssq / float(T) - mn * mn
    217
    218
    219 -    def histogram(self, low = 0.0, high = 100.0, nbins = 10):
    220          """ A histogram of the monitored y data values.
    221          """
    222          h = Histogram(name = self.name, low = low, high = high, nbins = nbins)
    223          ys = self.yseries()
    224          for y in ys: h.addIn(y)
    225          return h
    226
    227 -    def getHistogram(self):
    228          """Returns a histogram based on the parameters provided in
    229          preceding call to setHistogram.
    230          """
    231          ys = self.yseries()
    232          h = self.histo
    233          for y in ys: h.addIn(y)
    234          return h
    235
    236 -    def printHistogram(self, fmt = '%s'):
    237          """Returns formatted frequency distribution table string from Monitor.
    238          Precondition: setHistogram must have been called.
    239          fmt == format of bin range values
    240          """
    241          try:
    242              histo = self.getHistogram()
    243          except:
    244              raise FatalSimerror('histogramTable: call setHistogram first'\
    245                                  ' for Monitor %s'%self.name)
    246          ylab = self.ylab
    247          nrObs = self.count()
    248          width = len(str(nrObs))
    249          res = []
    250          res.append('\nHistogram for %s:'%histo.name)
    251          res.append('\nNumber of observations: %s'%nrObs)
    252          su = sum(self.yseries())
    253          cum = histo[0][1]
    254          line = '\n%s <= %s < %s: %s (cum: %s/%s%s)'\
    255               %(fmt, '%s', fmt, '%s', '%s', '%5.1f', '%s')
    256          line1 = '\n%s%s < %s: %s (cum: %s/%s%s)'\
    257               %('%s', '%s', fmt, '%s', '%s', '%5.1f', '%s')
    258          l1width = len(('%s <= '%fmt)%histo[1][0])
    259          res.append(line1\
    260                     %(' ' * l1width, ylab, histo[1][0], str(histo[0][1]).rjust(width),\
    261                       str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
    262                    )
    263          for i in range(1, len(histo) - 1):
    264              cum += histo[i][1]
    265              res.append(line\
    266                     %(histo[i][0], ylab, histo[i + 1][0], str(histo[i][1]).rjust(width),\
    267                       str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
    268                        )
    269          cum += histo[-1][1]
    270          linen = '\n%s <= %s %s : %s (cum: %s/%s%s)'\
    271                %(fmt, '%s', '%s', '%s', '%s', '%5.1f', '%s')
    272          lnwidth = len(('<%s'%fmt)%histo[1][0])
    273          res.append(linen\
    274                     %(histo[-1][0], ylab, ' ' * lnwidth, str(histo[-1][1]).rjust(width),\
    275                     str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
    276                     )
    277          return ' '.join(res)
    278
    279 -class Tally:
    280 -    def __init__(self, name = 'a_Tally', ylab = 'y', tlab = 't', sim = None):
    281          if not sim: sim = Globals.sim # use global simulation if sim is None
    282          self.sim = sim
    283          self.name = name
    284          self.ylab = ylab
    285          self.tlab = tlab
    286          self.reset()
    287          self.startTime = 0.0
    288          self.histo = None
    289          self.sum = 0.0
    290          self._sum_of_squares = 0
    291          self._integral = 0.0    # time - weighted sum
    292          self._integral2 = 0.0   # time - weighted sum of squares
    293          self.sim.allTallies.append(self)
    294
    295 -    def setHistogram(self, name = '', low = 0.0, high = 100.0, nbins = 10):
    296          """Sets histogram parameters.
    297          Must be called to prior to observations initiate data collection
    298          for histogram.
    299          """
    300          if name == '':
    301              hname = self.name
    302          else:
    303              hname = name
    304          self.histo = Histogram(name = hname, low = low, high = high, nbins = nbins)
    305
    306 -    def observe(self, y, t = None):
    307          if t is None:
    308              t = self.sim.now()
    309          self._integral += (t - self._last_timestamp) * self._last_observation
    310          yy =  self._last_observation * self._last_observation
    311          self._integral2 += (t - self._last_timestamp) * yy
    312          self._last_timestamp = t
    313          self._last_observation = y
    314          self._total += y
    315          self._count += 1
    316          self._sum += y
    317          self._sum_of_squares += y * y
    318          if self.histo:
    319              self.histo.addIn(y)
    320
    321 -    def reset(self, t = None):
    322          if t is None:
    323              t = self.sim.now()
    324          self.startTime = t
    325          self._last_timestamp = t
    326          self._last_observation = 0.0
    327          self._count = 0
    328          self._total = 0.0
    329          self._integral = 0.0
    330          self._integral2 = 0.0
    331          self._sum = 0.0
    332          self._sum_of_squares = 0.0
    333
    334 -    def count(self):
    335          return self._count
    336
    337 -    def total(self):
    338          return self._total
    339
    340 -    def mean(self):
    341          return 1.0 * self._total / self._count
    342
    343 -    def timeAverage(self, t = None):
    344          if t is None:
    345              t = self.sim.now()
    346          integ = self._integral + (t - self._last_timestamp) * self._last_observation
    347          if (t > self.startTime):
    348              return 1.0 * integ / (t - self.startTime)
    349          else:
    350              print 'SimPy: No elapsed time for timeAverage'
    351              return None
    352
    353 -    def var(self):
    354          return 1.0 * (self._sum_of_squares - (1.0 * (self._sum * self._sum)\
    355                 / self._count)) / (self._count)
    356
    357 -    def timeVariance(self, t = None):
    358          """ the time - weighted Variance of the Tallied variable.
    359
    360              If t is used it is assumed to be the current time,
    361              otherwise t =  self.sim.now()
    362          """
    363          if t is None:
    364              t = self.sim.now()
    365          twAve = self.timeAverage(t)
    366          #print 'Tally timeVariance DEBUG: twave:', twAve
    367          last =  self._last_observation
    368          twinteg2 = self._integral2 + (t - self._last_timestamp) * last * last
    369          #print 'Tally timeVariance DEBUG:tinteg2:', twinteg2
    370          if (t > self.startTime):
    371              return 1.0 * twinteg2 / (t - self.startTime) - twAve * twAve
    372          else:
    373              print 'SimPy: No elapsed time for timeVariance'
    374              return None
    375
    376
    377
    378 -    def __len__(self):
    379          return self._count
    380
    381 -    def __eq__(self, l):
    382          return len(l) == self._count
    383
    384 -    def getHistogram(self):
    385          return self.histo
    386
    387 -    def printHistogram(self, fmt = '%s'):
    388          """Returns formatted frequency distribution table string from Tally.
    389          Precondition: setHistogram must have been called.
    390          fmt == format of bin range values
    391          """
    392          try:
    393              histo = self.getHistogram()
    394          except:
    395              raise FatalSimerror('histogramTable: call setHistogram first'\
    396                                  ' for Tally %s'%self.name)
    397          ylab = self.ylab
    398          nrObs = self.count()
    399          width = len(str(nrObs))
    400          res = []
    401          res.append('\nHistogram for %s:'%histo.name)
    402          res.append('\nNumber of observations: %s'%nrObs)
    403          su = self.total()
    404          cum = histo[0][1]
    405          line = '\n%s <= %s < %s: %s (cum: %s/%s%s)'\
    406               %(fmt, '%s', fmt, '%s', '%s', '%5.1f', '%s')
    407          line1 = '\n%s%s < %s: %s (cum: %s/%s%s)'\
    408               %('%s', '%s', fmt, '%s', '%s', '%5.1f', '%s')
    409          l1width = len(('%s <= '%fmt)%histo[1][0])
    410          res.append(line1\
    411                     %(' ' * l1width, ylab, histo[1][0], str(histo[0][1]).rjust(width),\
    412                       str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
    413                    )
    414          for i in range(1, len(histo) - 1):
    415              cum += histo[i][1]
    416              res.append(line\
    417                     %(histo[i][0], ylab, histo[i + 1][0], str(histo[i][1]).rjust(width),\
    418                       str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
    419                        )
    420          cum += histo[-1][1]
    421          linen = '\n%s <= %s %s : %s (cum: %s/%s%s)'\
    422                %(fmt, '%s', '%s', '%s', '%s', '%5.1f', '%s')
    423          lnwidth = len(('<%s'%fmt)%histo[1][0])
    424          res.append(linen\
    425                     %(histo[-1][0], ylab, ' ' * lnwidth, str(histo[-1][1]).rjust(width),\
    426                     str(cum).rjust(width),(float(cum) / nrObs) * 100, '%')
    427                     )
    428          return ' '.join(res)
    429