info.py revision 1934:84c12fdd57f9
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
149class VectorConstant(Vector):
150    def __init__(self, constant):
151        self.constant = constant
152    def __value__(self, run, index):
153        return self.constant[index]
154    def __len__(self):
155        return len(self.constant)
156
157def WrapValue(value):
158    if isinstance(value, (int, long, float)):
159        return ScalarConstant(value)
160    if isinstance(value, (list, tuple)):
161        return VectorConstant(value)
162    if isinstance(value, Value):
163        return value
164
165    raise AttributeError, 'Only values can be wrapped'
166
167class Statistic(object):
168    def __getattr__(self, attr):
169        if attr in ('data', 'x', 'y'):
170            result = self.source.data(self, self.bins, self.ticks)
171            self.data = result.data
172            self.x = result.x
173            self.y = result.y
174        return super(Statistic, self).__getattribute__(attr)
175
176    def __setattr__(self, attr, value):
177        if attr == 'stat':
178            raise AttributeError, '%s is read only' % stat
179        if attr in ('source', 'bins', 'ticks'):
180            if getattr(self, attr) != value:
181                if hasattr(self, 'data'):
182                    delattr(self, 'data')
183
184        super(Statistic, self).__setattr__(attr, value)
185
186class ValueProxy(Value):
187    def __getattr__(self, attr):
188        if attr == '__value__':
189            if scalar(self):
190                return self.__scalarvalue__
191            if vector(self):
192                return self.__vectorvalue__
193        if attr == '__len__':
194            if vector(self):
195                return self.__vectorlen__
196        return super(ValueProxy, self).__getattribute__(attr)
197
198class UnaryProxy(ValueProxy):
199    def __init__(self, op, arg):
200        self.op = op
201        self.arg = WrapValue(arg)
202
203    def __scalar__(self):
204        return scalar(self.arg)
205
206    def __vector__(self):
207        return vector(self.arg)
208
209    def __scalarvalue__(self, run):
210        val = value(self.arg, run)
211        if val is None:
212            return None
213        return self.op(val)
214
215    def __vectorvalue__(self, run, index):
216        val = value(self.arg, run, index)
217        if val is None:
218            return None
219        return self.op(val)
220
221    def __vectorlen__(self):
222        return len(unproxy(self.arg))
223
224class BinaryProxy(ValueProxy):
225    def __init__(self, op, arg0, arg1):
226        super(BinaryProxy, self).__init__()
227        self.op = op
228        self.arg0 = WrapValue(arg0)
229        self.arg1 = WrapValue(arg1)
230
231    def __scalar__(self):
232        return scalar(self.arg0) and scalar(self.arg1)
233
234    def __vector__(self):
235        return vector(self.arg0) or vector(self.arg1)
236
237    def __scalarvalue__(self, run):
238        val0 = value(self.arg0, run)
239        val1 = value(self.arg1, run)
240        if val0 is None or val1 is None:
241            return None
242        return self.op(val0, val1)
243
244    def __vectorvalue__(self, run, index):
245        if scalar(self.arg0):
246            val0 = value(self.arg0, run)
247        if vector(self.arg0):
248            val0 = value(self.arg0, run, index)
249        if scalar(self.arg1):
250            val1 = value(self.arg1, run)
251        if vector(self.arg1):
252            val1 = value(self.arg1, run, index)
253
254        if val0 is None or val1 is None:
255            return None
256
257        return self.op(val0, val1)
258
259    def __vectorlen__(self):
260        if vector(self.arg0) and scalar(self.arg1):
261            return len(self.arg0)
262        if scalar(self.arg0) and vector(self.arg1):
263            return len(self.arg1)
264
265        len0 = len(self.arg0)
266        len1 = len(self.arg1)
267
268        if len0 != len1:
269            raise AttributeError, \
270                  "vectors of different lengths %d != %d" % (len0, len1)
271
272        return len0
273
274class Proxy(Value):
275    def __init__(self, name, dict):
276        self.name = name
277        self.dict = dict
278
279    def __unproxy__(self):
280        return unproxy(self.dict[self.name])
281
282    def __getitem__(self, index):
283        return ItemProxy(self, index)
284
285    def __getattr__(self, attr):
286        return AttrProxy(self, attr)
287
288class ItemProxy(Proxy):
289    def __init__(self, proxy, index):
290        self.proxy = proxy
291        self.index = index
292
293    def __unproxy__(self):
294        return unproxy(unproxy(self.proxy)[self.index])
295
296class AttrProxy(Proxy):
297    def __init__(self, proxy, attr):
298        self.proxy = proxy
299        self.attr = attr
300
301    def __unproxy__(self):
302        return unproxy(getattr(unproxy(self.proxy), self.attr))
303
304class ProxyGroup(object):
305    def __init__(self, dict=None, **kwargs):
306        self.__dict__['dict'] = {}
307
308        if dict is not None:
309            self.dict.update(dict)
310
311        if kwargs:
312            self.dict.update(kwargs)
313
314    def __getattr__(self, name):
315        return Proxy(name, self.dict)
316
317    def __setattr__(self, attr, value):
318        self.dict[attr] = value
319
320class ScalarStat(Statistic,Scalar):
321    def __value__(self, run):
322        if run not in self.data:
323            return None
324        return self.data[run][0][0]
325
326    def display(self, run=None):
327        import display
328        p = display.Print()
329        p.name = self.name
330        p.desc = self.desc
331        p.value = value(self, run)
332        p.flags = self.flags
333        p.precision = self.precision
334        if display.all or (self.flags & flags.printable):
335            p.display()
336
337class VectorStat(Statistic,Vector):
338    def __value__(self, run, item):
339        if run not in self.data:
340            return None
341        return self.data[run][item][0]
342
343    def __len__(self):
344        return self.x
345
346    def display(self, run=None):
347        import display
348        d = display.VectorDisplay()
349        d.name = self.name
350        d.desc = self.desc
351        d.value = [ value(self, run, i) for i in xrange(len(self)) ]
352        d.flags = self.flags
353        d.precision = self.precision
354        d.display()
355
356class Formula(Value):
357    def __getattribute__(self, attr):
358        if attr not in ( '__scalar__', '__vector__', '__value__', '__len__' ):
359            return super(Formula, self).__getattribute__(attr)
360
361        formula = re.sub(':', '__', self.formula)
362        value = eval(formula, self.source.stattop)
363        return getattr(value, attr)
364
365class SimpleDist(Statistic):
366    def __init__(self, sums, squares, samples):
367        self.sums = sums
368        self.squares = squares
369        self.samples = samples
370
371    def display(self, name, desc, flags, precision):
372        import display
373        p = display.Print()
374        p.flags = flags
375        p.precision = precision
376
377        if self.samples > 0:
378            p.name = name + ".mean"
379            p.value = self.sums / self.samples
380            p.display()
381
382            p.name = name + ".stdev"
383            if self.samples > 1:
384                var = (self.samples * self.squares - self.sums ** 2) \
385                      / (self.samples * (self.samples - 1))
386                if var >= 0:
387                    p.value = math.sqrt(var)
388                else:
389                    p.value = 'NaN'
390            else:
391                p.value = 0.0
392            p.display()
393
394        p.name = name + ".samples"
395        p.value = self.samples
396        p.display()
397
398    def comparable(self, other):
399        return True
400
401    def __eq__(self, other):
402        return self.sums == other.sums and self.squares == other.squares and \
403               self.samples == other.samples
404
405    def __isub__(self, other):
406        self.sums -= other.sums
407        self.squares -= other.squares
408        self.samples -= other.samples
409        return self
410
411    def __iadd__(self, other):
412        self.sums += other.sums
413        self.squares += other.squares
414        self.samples += other.samples
415        return self
416
417    def __itruediv__(self, other):
418        if not other:
419            return self
420        self.sums /= other
421        self.squares /= other
422        self.samples /= other
423        return self
424
425class FullDist(SimpleDist):
426    def __init__(self, sums, squares, samples, minval, maxval,
427                 under, vec, over, min, max, bsize, size):
428        self.sums = sums
429        self.squares = squares
430        self.samples = samples
431        self.minval = minval
432        self.maxval = maxval
433        self.under = under
434        self.vec = vec
435        self.over = over
436        self.min = min
437        self.max = max
438        self.bsize = bsize
439        self.size = size
440
441    def display(self, name, desc, flags, precision):
442        import display
443        p = display.Print()
444        p.flags = flags
445        p.precision = precision
446
447        p.name = name + '.min_val'
448        p.value = self.minval
449        p.display()
450
451        p.name = name + '.max_val'
452        p.value = self.maxval
453        p.display()
454
455        p.name = name + '.underflow'
456        p.value = self.under
457        p.display()
458
459        i = self.min
460        for val in self.vec[:-1]:
461            p.name = name + '[%d:%d]' % (i, i + self.bsize - 1)
462            p.value = val
463            p.display()
464            i += self.bsize
465
466        p.name = name + '[%d:%d]' % (i, self.max)
467        p.value = self.vec[-1]
468        p.display()
469
470
471        p.name = name + '.overflow'
472        p.value = self.over
473        p.display()
474
475        SimpleDist.display(self, name, desc, flags, precision)
476
477    def comparable(self, other):
478        return self.min == other.min and self.max == other.max and \
479               self.bsize == other.bsize and self.size == other.size
480
481    def __eq__(self, other):
482        return self.sums == other.sums and self.squares == other.squares and \
483               self.samples == other.samples
484
485    def __isub__(self, other):
486        self.sums -= other.sums
487        self.squares -= other.squares
488        self.samples -= other.samples
489
490        if other.samples:
491            self.minval = min(self.minval, other.minval)
492            self.maxval = max(self.maxval, other.maxval)
493            self.under -= under
494            self.vec = map(lambda x,y: x - y, self.vec, other.vec)
495            self.over -= over
496        return self
497
498    def __iadd__(self, other):
499        if not self.samples and other.samples:
500            self = other
501            return self
502
503        self.sums += other.sums
504        self.squares += other.squares
505        self.samples += other.samples
506
507        if other.samples:
508            self.minval = min(self.minval, other.minval)
509            self.maxval = max(self.maxval, other.maxval)
510            self.under += other.under
511            self.vec = map(lambda x,y: x + y, self.vec, other.vec)
512            self.over += other.over
513        return self
514
515    def __itruediv__(self, other):
516        if not other:
517            return self
518        self.sums /= other
519        self.squares /= other
520        self.samples /= other
521
522        if self.samples:
523            self.under /= other
524            for i in xrange(len(self.vec)):
525                self.vec[i] /= other
526            self.over /= other
527        return self
528
529class Dist(Statistic):
530    def display(self):
531        import display
532        if not display.all and not (self.flags & flags.printable):
533            return
534
535        self.dist.display(self.name, self.desc, self.flags, self.precision)
536
537    def comparable(self, other):
538        return self.name == other.name and \
539               self.dist.compareable(other.dist)
540
541    def __eq__(self, other):
542        return self.dist == other.dist
543
544    def __isub__(self, other):
545        self.dist -= other.dist
546        return self
547
548    def __iadd__(self, other):
549        self.dist += other.dist
550        return self
551
552    def __itruediv__(self, other):
553        if not other:
554            return self
555        self.dist /= other
556        return self
557
558class VectorDist(Statistic):
559    def display(self):
560        import display
561        if not display.all and not (self.flags & flags.printable):
562            return
563
564        if isinstance(self.dist, SimpleDist):
565            return
566
567        for dist,sn,sd,i in map(None, self.dist, self.subnames, self.subdescs,
568                                range(len(self.dist))):
569            if len(sn) > 0:
570                name = '%s.%s' % (self.name, sn)
571            else:
572                name = '%s[%d]' % (self.name, i)
573
574            if len(sd) > 0:
575                desc = sd
576            else:
577                desc = self.desc
578
579            dist.display(name, desc, self.flags, self.precision)
580
581        if (self.flags & flags.total) or 1:
582            if isinstance(self.dist[0], SimpleDist):
583                disttotal = SimpleDist( \
584                    reduce(sums, [d.sums for d in self.dist]),
585                    reduce(sums, [d.squares for d in self.dist]),
586                    reduce(sums, [d.samples for d in self.dist]))
587            else:
588                disttotal = FullDist( \
589                    reduce(sums, [d.sums for d in self.dist]),
590                    reduce(sums, [d.squares for d in self.dist]),
591                    reduce(sums, [d.samples for d in self.dist]),
592                    min([d.minval for d in self.dist]),
593                    max([d.maxval for d in self.dist]),
594                    reduce(sums, [d.under for d in self.dist]),
595                    reduce(sums, [d.vec for d in self.dist]),
596                    reduce(sums, [d.over for d in self.dist]),
597                    dist[0].min,
598                    dist[0].max,
599                    dist[0].bsize,
600                    dist[0].size)
601
602            name = '%s.total' % (self.name)
603            desc = self.desc
604            disttotal.display(name, desc, self.flags, self.precision)
605
606    def comparable(self, other):
607        return self.name == other.name and \
608               alltrue(map(lambda x, y : x.comparable(y),
609                           self.dist,
610                           other.dist))
611
612    def __eq__(self, other):
613        return alltrue(map(lambda x, y : x == y, self.dist, other.dist))
614
615    def __isub__(self, other):
616        if isinstance(self.dist, (list, tuple)) and \
617               isinstance(other.dist, (list, tuple)):
618            for sd,od in zip(self.dist, other.dist):
619                sd -= od
620        else:
621            self.dist -= other.dist
622        return self
623
624    def __iadd__(self, other):
625        if isinstance(self.dist, (list, tuple)) and \
626               isinstance(other.dist, (list, tuple)):
627            for sd,od in zip(self.dist, other.dist):
628                sd += od
629        else:
630            self.dist += other.dist
631        return self
632
633    def __itruediv__(self, other):
634        if not other:
635            return self
636        if isinstance(self.dist, (list, tuple)):
637            for dist in self.dist:
638                dist /= other
639        else:
640            self.dist /= other
641        return self
642
643class Vector2d(Statistic):
644    def display(self):
645        import display
646        if not display.all and not (self.flags & flags.printable):
647            return
648
649        d = display.VectorDisplay()
650        d.__dict__.update(self.__dict__)
651
652        if self.__dict__.has_key('ysubnames'):
653            ysubnames = list(self.ysubnames)
654            slack = self.x - len(ysubnames)
655            if slack > 0:
656                ysubnames.extend(['']*slack)
657        else:
658            ysubnames = range(self.x)
659
660        for x,sname in enumerate(ysubnames):
661            o = x * self.y
662            d.value = self.value[o:o+self.y]
663            d.name = '%s[%s]' % (self.name, sname)
664            d.display()
665
666        if self.flags & flags.total:
667            d.value = []
668            for y in range(self.y):
669                xtot = 0.0
670                for x in range(self.x):
671                    xtot += self.value[y + x * self.x]
672                d.value.append(xtot)
673
674            d.name = self.name + '.total'
675            d.display()
676
677    def comparable(self, other):
678        return self.name == other.name and self.x == other.x and \
679               self.y == other.y
680
681    def __eq__(self, other):
682        return True
683
684    def __isub__(self, other):
685        return self
686
687    def __iadd__(self, other):
688        return self
689
690    def __itruediv__(self, other):
691        if not other:
692            return self
693        return self
694
695def NewStat(source, data):
696    stat = None
697    if data.type == 'SCALAR':
698        stat = ScalarStat()
699    elif data.type == 'VECTOR':
700        stat = VectorStat()
701    elif data.type == 'DIST':
702        stat = Dist()
703    elif data.type == 'VECTORDIST':
704        stat = VectorDist()
705    elif data.type == 'VECTOR2D':
706        stat = Vector2d()
707    elif data.type == 'FORMULA':
708        stat = Formula()
709
710    stat.__dict__['source'] = source
711    stat.__dict__['bins'] = None
712    stat.__dict__['ticks'] = None
713    stat.__dict__.update(data.__dict__)
714
715    return stat
716
717