1# Copyright (c) 2003-2004 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Nathan Binkert
28
29from __future__ import division
30import operator, re, types
31
32class ProxyError(Exception):
33    pass
34
35def unproxy(proxy):
36    if hasattr(proxy, '__unproxy__'):
37        return proxy.__unproxy__()
38
39    return proxy
40
41def scalar(stat):
42    stat = unproxy(stat)
43    assert(stat.__scalar__() != stat.__vector__())
44    return stat.__scalar__()
45
46def vector(stat):
47    stat = unproxy(stat)
48    assert(stat.__scalar__() != stat.__vector__())
49    return stat.__vector__()
50
51def value(stat, *args):
52    stat = unproxy(stat)
53    return stat.__value__(*args)
54
55def values(stat, run):
56    stat = unproxy(stat)
57    result = []
58    for i in xrange(len(stat)):
59        val = value(stat, run, i)
60        if val is None:
61            return None
62        result.append(val)
63    return result
64
65def total(stat, run):
66    return sum(values(stat, run))
67
68def len(stat):
69    stat = unproxy(stat)
70    return stat.__len__()
71
72class Value(object):
73    def __scalar__(self):
74        raise AttributeError, "must define __scalar__ for %s" % (type (self))
75    def __vector__(self):
76        raise AttributeError, "must define __vector__ for %s" % (type (self))
77
78    def __add__(self, other):
79        return BinaryProxy(operator.__add__, self, other)
80    def __sub__(self, other):
81        return BinaryProxy(operator.__sub__, self, other)
82    def __mul__(self, other):
83        return BinaryProxy(operator.__mul__, self, other)
84    def __div__(self, other):
85        return BinaryProxy(operator.__div__, self, other)
86    def __truediv__(self, other):
87        return BinaryProxy(operator.__truediv__, self, other)
88    def __floordiv__(self, other):
89        return BinaryProxy(operator.__floordiv__, self, other)
90
91    def __radd__(self, other):
92        return BinaryProxy(operator.__add__, other, self)
93    def __rsub__(self, other):
94        return BinaryProxy(operator.__sub__, other, self)
95    def __rmul__(self, other):
96        return BinaryProxy(operator.__mul__, other, self)
97    def __rdiv__(self, other):
98        return BinaryProxy(operator.__div__, other, self)
99    def __rtruediv__(self, other):
100        return BinaryProxy(operator.__truediv__, other, self)
101    def __rfloordiv__(self, other):
102        return BinaryProxy(operator.__floordiv__, other, self)
103
104    def __neg__(self):
105        return UnaryProxy(operator.__neg__, self)
106    def __pos__(self):
107        return UnaryProxy(operator.__pos__, self)
108    def __abs__(self):
109        return UnaryProxy(operator.__abs__, self)
110
111class Scalar(Value):
112    def __scalar__(self):
113        return True
114
115    def __vector__(self):
116        return False
117
118    def __value__(self, run):
119        raise AttributeError, '__value__ must be defined'
120
121class VectorItemProxy(Value):
122    def __init__(self, proxy, index):
123        self.proxy = proxy
124        self.index = index
125
126    def __scalar__(self):
127        return True
128
129    def __vector__(self):
130        return False
131
132    def __value__(self, run):
133        return value(self.proxy, run, self.index)
134
135class Vector(Value):
136    def __scalar__(self):
137        return False
138
139    def __vector__(self):
140        return True
141
142    def __value__(self, run, index):
143        raise AttributeError, '__value__ must be defined'
144
145    def __getitem__(self, index):
146        return VectorItemProxy(self, index)
147
148class ScalarConstant(Scalar):
149    def __init__(self, constant):
150        self.constant = constant
151    def __value__(self, run):
152        return self.constant
153    def __str__(self):
154        return str(self.constant)
155
156class VectorConstant(Vector):
157    def __init__(self, constant):
158        self.constant = constant
159    def __value__(self, run, index):
160        return self.constant[index]
161    def __len__(self):
162        return len(self.constant)
163    def __str__(self):
164        return str(self.constant)
165
166def WrapValue(value):
167    if isinstance(value, (int, long, float)):
168        return ScalarConstant(value)
169    if isinstance(value, (list, tuple)):
170        return VectorConstant(value)
171    if isinstance(value, Value):
172        return value
173
174    raise AttributeError, 'Only values can be wrapped'
175
176class Statistic(object):
177    def __getattr__(self, attr):
178        if attr in ('data', 'x', 'y'):
179            result = self.source.data(self, self.ticks)
180            self.data = result.data
181            self.x = result.x
182            self.y = result.y
183        return super(Statistic, self).__getattribute__(attr)
184
185    def __setattr__(self, attr, value):
186        if attr == 'stat':
187            raise AttributeError, '%s is read only' % stat
188        if attr in ('source', 'ticks'):
189            if getattr(self, attr) != value:
190                if hasattr(self, 'data'):
191                    delattr(self, 'data')
192
193        super(Statistic, self).__setattr__(attr, value)
194
195    def __str__(self):
196        return self.name
197
198class ValueProxy(Value):
199    def __getattr__(self, attr):
200        if attr == '__value__':
201            if scalar(self):
202                return self.__scalarvalue__
203            if vector(self):
204                return self.__vectorvalue__
205        if attr == '__len__':
206            if vector(self):
207                return self.__vectorlen__
208        return super(ValueProxy, self).__getattribute__(attr)
209
210class UnaryProxy(ValueProxy):
211    def __init__(self, op, arg):
212        self.op = op
213        self.arg = WrapValue(arg)
214
215    def __scalar__(self):
216        return scalar(self.arg)
217
218    def __vector__(self):
219        return vector(self.arg)
220
221    def __scalarvalue__(self, run):
222        val = value(self.arg, run)
223        if val is None:
224            return None
225        return self.op(val)
226
227    def __vectorvalue__(self, run, index):
228        val = value(self.arg, run, index)
229        if val is None:
230            return None
231        return self.op(val)
232
233    def __vectorlen__(self):
234        return len(unproxy(self.arg))
235
236    def __str__(self):
237        if self.op == operator.__neg__:
238            return '-%s' % str(self.arg)
239        if self.op == operator.__pos__:
240            return '+%s' % str(self.arg)
241        if self.op == operator.__abs__:
242            return 'abs(%s)' % self.arg
243
244class BinaryProxy(ValueProxy):
245    def __init__(self, op, arg0, arg1):
246        super(BinaryProxy, self).__init__()
247        self.op = op
248        self.arg0 = WrapValue(arg0)
249        self.arg1 = WrapValue(arg1)
250
251    def __scalar__(self):
252        return scalar(self.arg0) and scalar(self.arg1)
253
254    def __vector__(self):
255        return vector(self.arg0) or vector(self.arg1)
256
257    def __scalarvalue__(self, run):
258        val0 = value(self.arg0, run)
259        val1 = value(self.arg1, run)
260        if val0 is None or val1 is None:
261            return None
262        try:
263            return self.op(val0, val1)
264        except ZeroDivisionError:
265            return None
266
267    def __vectorvalue__(self, run, index):
268        if scalar(self.arg0):
269            val0 = value(self.arg0, run)
270        if vector(self.arg0):
271            val0 = value(self.arg0, run, index)
272        if scalar(self.arg1):
273            val1 = value(self.arg1, run)
274        if vector(self.arg1):
275            val1 = value(self.arg1, run, index)
276
277        if val0 is None or val1 is None:
278            return None
279
280        try:
281            return self.op(val0, val1)
282        except ZeroDivisionError:
283            return None
284
285    def __vectorlen__(self):
286        if vector(self.arg0) and scalar(self.arg1):
287            return len(self.arg0)
288        if scalar(self.arg0) and vector(self.arg1):
289            return len(self.arg1)
290
291        len0 = len(self.arg0)
292        len1 = len(self.arg1)
293
294        if len0 != len1:
295            raise AttributeError, \
296                  "vectors of different lengths %d != %d" % (len0, len1)
297
298        return len0
299
300    def __str__(self):
301        ops = { operator.__add__ : '+',
302                operator.__sub__ : '-',
303                operator.__mul__ : '*',
304                operator.__div__ : '/',
305                operator.__truediv__ : '/',
306                operator.__floordiv__ : '//' }
307
308        return '(%s %s %s)' % (str(self.arg0), ops[self.op], str(self.arg1))
309
310class Proxy(Value):
311    def __init__(self, name, dict):
312        self.name = name
313        self.dict = dict
314
315    def __unproxy__(self):
316        return unproxy(self.dict[self.name])
317
318    def __getitem__(self, index):
319        return ItemProxy(self, index)
320
321    def __getattr__(self, attr):
322        return AttrProxy(self, attr)
323
324    def __str__(self):
325        return str(self.dict[self.name])
326
327class ItemProxy(Proxy):
328    def __init__(self, proxy, index):
329        self.proxy = proxy
330        self.index = index
331
332    def __unproxy__(self):
333        return unproxy(unproxy(self.proxy)[self.index])
334
335    def __str__(self):
336        return '%s[%s]' % (self.proxy, self.index)
337
338class AttrProxy(Proxy):
339    def __init__(self, proxy, attr):
340        self.proxy = proxy
341        self.attr = attr
342
343    def __unproxy__(self):
344        proxy = unproxy(self.proxy)
345        try:
346            attr = getattr(proxy, self.attr)
347        except AttributeError, e:
348            raise ProxyError, e
349        return unproxy(attr)
350
351    def __str__(self):
352        return '%s.%s' % (self.proxy, self.attr)
353
354class ProxyGroup(object):
355    def __init__(self, dict=None, **kwargs):
356        self.__dict__['dict'] = {}
357
358        if dict is not None:
359            self.dict.update(dict)
360
361        if kwargs:
362            self.dict.update(kwargs)
363
364    def __getattr__(self, name):
365        return Proxy(name, self.dict)
366
367    def __setattr__(self, attr, value):
368        self.dict[attr] = value
369
370class ScalarStat(Statistic,Scalar):
371    def __value__(self, run):
372        if run not in self.data:
373            return None
374        return self.data[run][0][0]
375
376    def display(self, run=None):
377        import display
378        p = display.Print()
379        p.name = self.name
380        p.desc = self.desc
381        p.value = value(self, run)
382        p.flags = self.flags
383        p.precision = self.precision
384        if display.all or (self.flags & flags.printable):
385            p.display()
386
387class VectorStat(Statistic,Vector):
388    def __value__(self, run, item):
389        if run not in self.data:
390            return None
391        return self.data[run][item][0]
392
393    def __len__(self):
394        return self.x
395
396    def display(self, run=None):
397        import display
398        d = display.VectorDisplay()
399        d.name = self.name
400        d.desc = self.desc
401        d.value = [ value(self, run, i) for i in xrange(len(self)) ]
402        d.flags = self.flags
403        d.precision = self.precision
404        d.display()
405
406class Formula(Value):
407    def __getattribute__(self, attr):
408        if attr not in ( '__scalar__', '__vector__', '__value__', '__len__' ):
409            return super(Formula, self).__getattribute__(attr)
410
411        formula = re.sub(':', '__', self.formula)
412        value = eval(formula, self.source.stattop)
413        return getattr(value, attr)
414
415    def __str__(self):
416        return self.name
417
418class SimpleDist(Statistic):
419    def __init__(self, sums, squares, samples):
420        self.sums = sums
421        self.squares = squares
422        self.samples = samples
423
424    def display(self, name, desc, flags, precision):
425        import display
426        p = display.Print()
427        p.flags = flags
428        p.precision = precision
429
430        if self.samples > 0:
431            p.name = name + ".mean"
432            p.value = self.sums / self.samples
433            p.display()
434
435            p.name = name + ".stdev"
436            if self.samples > 1:
437                var = (self.samples * self.squares - self.sums ** 2) \
438                      / (self.samples * (self.samples - 1))
439                if var >= 0:
440                    p.value = math.sqrt(var)
441                else:
442                    p.value = 'NaN'
443            else:
444                p.value = 0.0
445            p.display()
446
447        p.name = name + ".samples"
448        p.value = self.samples
449        p.display()
450
451    def comparable(self, other):
452        return True
453
454    def __eq__(self, other):
455        return self.sums == other.sums and self.squares == other.squares and \
456               self.samples == other.samples
457
458    def __isub__(self, other):
459        self.sums -= other.sums
460        self.squares -= other.squares
461        self.samples -= other.samples
462        return self
463
464    def __iadd__(self, other):
465        self.sums += other.sums
466        self.squares += other.squares
467        self.samples += other.samples
468        return self
469
470    def __itruediv__(self, other):
471        if not other:
472            return self
473        self.sums /= other
474        self.squares /= other
475        self.samples /= other
476        return self
477
478class FullDist(SimpleDist):
479    def __init__(self, sums, squares, samples, minval, maxval,
480                 under, vec, over, min, max, bsize, size):
481        self.sums = sums
482        self.squares = squares
483        self.samples = samples
484        self.minval = minval
485        self.maxval = maxval
486        self.under = under
487        self.vec = vec
488        self.over = over
489        self.min = min
490        self.max = max
491        self.bsize = bsize
492        self.size = size
493
494    def display(self, name, desc, flags, precision):
495        import display
496        p = display.Print()
497        p.flags = flags
498        p.precision = precision
499
500        p.name = name + '.min_val'
501        p.value = self.minval
502        p.display()
503
504        p.name = name + '.max_val'
505        p.value = self.maxval
506        p.display()
507
508        p.name = name + '.underflow'
509        p.value = self.under
510        p.display()
511
512        i = self.min
513        for val in self.vec[:-1]:
514            p.name = name + '[%d:%d]' % (i, i + self.bsize - 1)
515            p.value = val
516            p.display()
517            i += self.bsize
518
519        p.name = name + '[%d:%d]' % (i, self.max)
520        p.value = self.vec[-1]
521        p.display()
522
523
524        p.name = name + '.overflow'
525        p.value = self.over
526        p.display()
527
528        SimpleDist.display(self, name, desc, flags, precision)
529
530    def comparable(self, other):
531        return self.min == other.min and self.max == other.max and \
532               self.bsize == other.bsize and self.size == other.size
533
534    def __eq__(self, other):
535        return self.sums == other.sums and self.squares == other.squares and \
536               self.samples == other.samples
537
538    def __isub__(self, other):
539        self.sums -= other.sums
540        self.squares -= other.squares
541        self.samples -= other.samples
542
543        if other.samples:
544            self.minval = min(self.minval, other.minval)
545            self.maxval = max(self.maxval, other.maxval)
546            self.under -= under
547            self.vec = map(lambda x,y: x - y, self.vec, other.vec)
548            self.over -= over
549        return self
550
551    def __iadd__(self, other):
552        if not self.samples and other.samples:
553            self = other
554            return self
555
556        self.sums += other.sums
557        self.squares += other.squares
558        self.samples += other.samples
559
560        if other.samples:
561            self.minval = min(self.minval, other.minval)
562            self.maxval = max(self.maxval, other.maxval)
563            self.under += other.under
564            self.vec = map(lambda x,y: x + y, self.vec, other.vec)
565            self.over += other.over
566        return self
567
568    def __itruediv__(self, other):
569        if not other:
570            return self
571        self.sums /= other
572        self.squares /= other
573        self.samples /= other
574
575        if self.samples:
576            self.under /= other
577            for i in xrange(len(self.vec)):
578                self.vec[i] /= other
579            self.over /= other
580        return self
581
582class Dist(Statistic):
583    def display(self):
584        import display
585        if not display.all and not (self.flags & flags.printable):
586            return
587
588        self.dist.display(self.name, self.desc, self.flags, self.precision)
589
590    def comparable(self, other):
591        return self.name == other.name and \
592               self.dist.compareable(other.dist)
593
594    def __eq__(self, other):
595        return self.dist == other.dist
596
597    def __isub__(self, other):
598        self.dist -= other.dist
599        return self
600
601    def __iadd__(self, other):
602        self.dist += other.dist
603        return self
604
605    def __itruediv__(self, other):
606        if not other:
607            return self
608        self.dist /= other
609        return self
610
611class VectorDist(Statistic):
612    def display(self):
613        import display
614        if not display.all and not (self.flags & flags.printable):
615            return
616
617        if isinstance(self.dist, SimpleDist):
618            return
619
620        for dist,sn,sd,i in map(None, self.dist, self.subnames, self.subdescs,
621                                range(len(self.dist))):
622            if len(sn) > 0:
623                name = '%s.%s' % (self.name, sn)
624            else:
625                name = '%s[%d]' % (self.name, i)
626
627            if len(sd) > 0:
628                desc = sd
629            else:
630                desc = self.desc
631
632            dist.display(name, desc, self.flags, self.precision)
633
634        if (self.flags & flags.total) or 1:
635            if isinstance(self.dist[0], SimpleDist):
636                disttotal = SimpleDist( \
637                    reduce(sums, [d.sums for d in self.dist]),
638                    reduce(sums, [d.squares for d in self.dist]),
639                    reduce(sums, [d.samples for d in self.dist]))
640            else:
641                disttotal = FullDist( \
642                    reduce(sums, [d.sums for d in self.dist]),
643                    reduce(sums, [d.squares for d in self.dist]),
644                    reduce(sums, [d.samples for d in self.dist]),
645                    min([d.minval for d in self.dist]),
646                    max([d.maxval for d in self.dist]),
647                    reduce(sums, [d.under for d in self.dist]),
648                    reduce(sums, [d.vec for d in self.dist]),
649                    reduce(sums, [d.over for d in self.dist]),
650                    dist[0].min,
651                    dist[0].max,
652                    dist[0].bsize,
653                    dist[0].size)
654
655            name = '%s.total' % (self.name)
656            desc = self.desc
657            disttotal.display(name, desc, self.flags, self.precision)
658
659    def comparable(self, other):
660        return self.name == other.name and \
661               alltrue(map(lambda x, y : x.comparable(y),
662                           self.dist,
663                           other.dist))
664
665    def __eq__(self, other):
666        return alltrue(map(lambda x, y : x == y, self.dist, other.dist))
667
668    def __isub__(self, other):
669        if isinstance(self.dist, (list, tuple)) and \
670               isinstance(other.dist, (list, tuple)):
671            for sd,od in zip(self.dist, other.dist):
672                sd -= od
673        else:
674            self.dist -= other.dist
675        return self
676
677    def __iadd__(self, other):
678        if isinstance(self.dist, (list, tuple)) and \
679               isinstance(other.dist, (list, tuple)):
680            for sd,od in zip(self.dist, other.dist):
681                sd += od
682        else:
683            self.dist += other.dist
684        return self
685
686    def __itruediv__(self, other):
687        if not other:
688            return self
689        if isinstance(self.dist, (list, tuple)):
690            for dist in self.dist:
691                dist /= other
692        else:
693            self.dist /= other
694        return self
695
696class Vector2d(Statistic):
697    def display(self):
698        import display
699        if not display.all and not (self.flags & flags.printable):
700            return
701
702        d = display.VectorDisplay()
703        d.__dict__.update(self.__dict__)
704
705        if self.__dict__.has_key('ysubnames'):
706            ysubnames = list(self.ysubnames)
707            slack = self.x - len(ysubnames)
708            if slack > 0:
709                ysubnames.extend(['']*slack)
710        else:
711            ysubnames = range(self.x)
712
713        for x,sname in enumerate(ysubnames):
714            o = x * self.y
715            d.value = self.value[o:o+self.y]
716            d.name = '%s[%s]' % (self.name, sname)
717            d.display()
718
719        if self.flags & flags.total:
720            d.value = []
721            for y in range(self.y):
722                xtot = 0.0
723                for x in range(self.x):
724                    xtot += self.value[y + x * self.x]
725                d.value.append(xtot)
726
727            d.name = self.name + '.total'
728            d.display()
729
730    def comparable(self, other):
731        return self.name == other.name and self.x == other.x and \
732               self.y == other.y
733
734    def __eq__(self, other):
735        return True
736
737    def __isub__(self, other):
738        return self
739
740    def __iadd__(self, other):
741        return self
742
743    def __itruediv__(self, other):
744        if not other:
745            return self
746        return self
747
748def NewStat(source, data):
749    stat = None
750    if data.type == 'SCALAR':
751        stat = ScalarStat()
752    elif data.type == 'VECTOR':
753        stat = VectorStat()
754    elif data.type == 'DIST':
755        stat = Dist()
756    elif data.type == 'VECTORDIST':
757        stat = VectorDist()
758    elif data.type == 'VECTOR2D':
759        stat = Vector2d()
760    elif data.type == 'FORMULA':
761        stat = Formula()
762
763    stat.__dict__['source'] = source
764    stat.__dict__['ticks'] = None
765    stat.__dict__.update(data.__dict__)
766
767    return stat
768
769