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